can1357/oh-my-pi · error · OmpTypeError

trailing tokens in definition "${this.#src}"

Error message

trailing tokens in definition "${this.#src}"

What it means

After parsing a complete definition (union, optional '?', default), #expectEnd found leftover tokens. The string is grammatically valid up to a point but continues with content the top-level grammar doesn't accept — a full definition must consume the entire token stream.

Source

Thrown at packages/omptype/src/ir.ts:386

		let def: unknown;
		let hasDefault = false;
		if (this.#eatOp("=")) {
			const t = this.#next();
			if (t.t === "num" || t.t === "bigint" || t.t === "date" || t.t === "str") def = t.v;
			else if (t.t === "id" && (t.v === "true" || t.v === "false")) def = t.v === "true";
			else if (t.t === "id" && t.v === "null") def = null;
			else if (t.t === "id" && t.v === "undefined") def = undefined;
			else throw new OmpTypeError(`unsupported default literal in "${this.#src}"`);
			hasDefault = true;
		}
		const optional = this.#eatOp("?");
		this.#expectEnd();
		return { ir, def, hasDefault, optional };
	}

	#expectEnd(): void {
		if (this.#pos < this.#toks.length) {
			throw new OmpTypeError(`trailing tokens in definition "${this.#src}"`);
		}
	}

	parseUnion(): IR {
		const first = this.parseIntersection();
		if (!this.#eatOp("|")) return first;
		const members = [first, this.parseIntersection()];
		while (this.#eatOp("|")) members.push(this.parseIntersection());
		return { k: "union", members };
	}

	parseIntersection(): IR {
		const first = this.parseBounded();
		if (!this.#eatOp("&")) return first;
		const members = [first, this.parseBounded()];
		while (this.#eatOp("&")) members.push(this.parseBounded());
		const literal = members.find((member): member is Extract<IR, { k: "lit" }> => member.k === "lit");
		if (literal && typeof literal.v === "number") {

View on GitHub (pinned to 9690622007)

Solutions

  1. Join alternatives with '|' ('string | number'), not whitespace.
  2. Remove stray separators (commas, semicolons, spaces) that aren't part of the grammar.
  3. If multiple properties are intended, wrap them in object syntax '{ key: value, ... }' rather than listing definitions sequentially.
  4. Read the echoed source in the message; everything after the valid definition is the offending tail.

Example fix

// before
schema.parse("string number");
// after
schema.parse("string | number");
Defensive patterns

Strategy: validation

Validate before calling

// tokenize-like sanity: no two adjacent keywords without a separator
if (/\b(string|number|boolean|bigint)\s+(string|number|boolean|bigint)\b/.test(def)) {
  throw new Error(`missing '|' between types: ${def}`);
}

Try / catch

try {
  const schema = parse(def);
} catch (e) {
  if (String(e.message).includes("trailing tokens")) {
    // inspect everything after the first valid type; join with '|' or remove
  }
  throw e;
}

Prevention

When it happens

Trigger: 'string string' (two keywords), 'number 5', 'string[] number', stray commas/semicolons ('string,'), 'number>=1 2', or an extra '?'/'?' after content that parseTop already handled.

Common situations: Concatenating multiple definitions with a space instead of '|' ('string number' vs 'string | number'); leftover separator characters when joining definitions programmatically; typos where a comma was used instead of '|' in object properties.

Related errors


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