can1357/oh-my-pi · error · OmpTypeError

A postfix required element cannot follow an optional or defa

Error message

A postfix required element cannot follow an optional or defaultable element

What it means

When a tuple/function definition contains exactly one spread element followed by a required element, omptype requires that every element preceding the spread be required as well. If any element before the spread is optional (ends with `?`) or has a default (`= value`), the trailing required element could never be reliably positioned, so the library throws instead of building an ambiguous type.

Source

Thrown at packages/omptype/src/type.ts:3121

		}
		if (spreadIndexes.length > 1) {
			const secondSpread = definitions[spreadIndexes[1] + 1];
			if (
				Array.isArray(secondSpread) &&
				secondSpread.some(
					element => typeof element === "string" && (element.endsWith("?") || element.includes("=")),
				)
			) {
				throw new OmpTypeError("An optional element may not follow a variadic element");
			}
			throw new OmpTypeError("A tuple may have at most one variadic element");
		}
		if (spreadIndexes.length === 1 && spreadIndexes[0] + 2 < (marker === -1 ? definitions.length : marker)) {
			const preceding = definitions.slice(0, spreadIndexes[0]);
			if (
				preceding.some(element => typeof element === "string" && (element.endsWith("?") || /\s=\s/.test(element)))
			) {
				throw new OmpTypeError("A postfix required element cannot follow an optional or defaultable element");
			}
		}
		const parameterDefinitions = (marker === -1 ? definitions : definitions.slice(0, marker)).map(
			normalizeFnParameter,
		);
		const params = makeType<readonly unknown[], readonly unknown[]>(parseDef(parameterDefinitions, resolve), [], {});
		const returns =
			marker === -1
				? makeType<unknown>({ k: "unknown" }, [], {})
				: makeType<unknown>(parseDef(definitions[marker + 1], resolve), [], {});
		const parameterExpression = fnExpression(params.ir);
		const returnsExpression = fnExpression(returns.ir);
		return (implementation: (...arguments_: readonly unknown[]) => unknown) => {
			if (typeof implementation !== "function") throw new OmpTypeError("type.fn requires a function implementation");
			const raw = (...arguments_: readonly unknown[]): unknown => {
				const validatedArguments = params.assert(arguments_);
				const result = Reflect.apply(implementation, undefined, validatedArguments);
				return returns.assert(result);

View on GitHub (pinned to 9690622007)

Solutions

  1. Make the postfix element optional (append `?`) so nothing required follows the spread.
  2. Remove the optional/default marker from elements preceding the spread.
  3. Move the spread so no required element follows it, or split the definition into multiple types.

Example fix

// before
type(["number?", "...string[]", "boolean"])
// after
type(["number?", "...string[]", "boolean?"])
Defensive patterns

Strategy: try-catch

Validate before calling

function hasOptionalBeforeSpread(defs) { const i = defs.findIndex(d => typeof d === 'string' && d.startsWith('...')); const after = defs.slice(i + 1); return !(i !== -1 && after.length > 0 && defs.slice(0, i).some(d => typeof d === 'string' && (d.endsWith('?') || /\s=\s/.test(d)))); }

Try / catch

try { const t = type(['number?', '...string[]', 'boolean?']); } catch (e) { if (e instanceof OmpTypeError) fixDefinition(); else throw e; }

Prevention

When it happens

Trigger: Calling `type([...])` (or `type.fn`) with a tuple-style definition containing one spread (`...`) element, a required element after the spread, and at least one optional (`"x?"`) or defaultable ("x = 1") element before the spread.

Common situations: Writing variadic tuple schemas like `["number?", "...string[]", "boolean"]`; often the author intended the last element to also be optional, or copied a TypeScript pattern TypeScript itself would also reject.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/1bd2e1193e48ddc8. Report an issue: GitHub.