can1357/oh-my-pi · error · OmpTypeError

expected bound after comparator in "${this.#src}"

Error message

expected bound after comparator in "${this.#src}"

What it means

A comparison operator ('<', '<=', '>', '>=', '==') was parsed, but the following token is not a numeric or date literal — the bound value is missing or of the wrong kind. Bounds must be num or date tokens; identifiers, keywords, strings, or end-of-input after a comparator trigger this.

Source

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

	parseBounded(): IR {
		const t = this.#peek();
		const t1 = this.#peek(1);
		if ((t?.t === "num" || t?.t === "date") && t1?.t === "op" && (t1.v === "<" || t1.v === "<=")) {
			const lo = t.v;
			this.#pos += 2;
			let node = this.#eatDivisor(this.parsePostfix());
			node = applyBound(node, flip(t1.v), lo, this.#src);
			const t2 = this.#peek();
			if (!(t2?.t === "op" && CMP[t2.v])) {
				throw new OmpTypeError(`left bound requires a corresponding right bound in "${this.#src}"`);
			}
			if (t2.v === ">" || t2.v === ">=") {
				throw new OmpTypeError(`right bound must use < or <= in "${this.#src}"`);
			}
			this.#pos++;
			const hi = this.#next();
			if (hi.t !== "num" && hi.t !== "date") {
				throw new OmpTypeError(`expected bound after comparator in "${this.#src}"`);
			}
			node = applyBound(node, t2.v, hi.v, this.#src);
			return this.#eatArraySuffixes(node);
		}
		let node = this.#eatDivisor(this.parsePostfix());
		const t2 = this.#peek();
		if (t2?.t === "op" && t2.v === "==") {
			this.#pos++;
			const limit = this.#next();
			if (limit.t !== "num" && limit.t !== "bigint" && limit.t !== "date") {
				throw new OmpTypeError(`expected literal after == in "${this.#src}"`);
			}
			node = applyEquality(node, limit.v, this.#src);
		} else if (t2?.t === "op" && CMP[t2.v] && (this.#peek(1)?.t === "num" || this.#peek(1)?.t === "date")) {
			this.#pos++;
			const limit = this.#next() as Extract<Tok, { t: "num" | "date" }>;
			node = applyBound(node, t2.v, limit.v, this.#src);
			node = this.#eatArraySuffixes(node);

View on GitHub (pinned to 9690622007)

Solutions

  1. Replace the bound with a numeric or date literal: 'number > 5', 'Date < d"2024-01-01"'.
  2. If the bound comes from a variable, stringify the raw number before interpolation, or attach the bound via the builder/object API instead.
  3. Remove quotes from numeric bounds ('5' → 5).
  4. If end-of-input, append the missing bound value after the comparator.

Example fix

// before
schema.parse(`number >= ${String(limit)}`); // limit is "high" -> throws
// after
schema.parse(`number >= ${Number(limit)}`); // or validate Number.isFinite(limit) first
Defensive patterns

Strategy: validation

Validate before calling

function assertNumericBound(v) {
  if (typeof v !== "number" || !Number.isFinite(v)) throw new Error(`bound must be a number, got ${v}`);
  return String(v);
}

Type guard

function isNumericLiteralToken(s) {
  return /^-?(?:0|[1-9]\d*)(?:\.\d+)?$/.test(s);
}

Try / catch

try {
  const schema = parse(def);
} catch (e) {
  if (String(e.message).includes("expected bound after comparator")) {
    // replace identifier/quoted bound with a plain numeric or date literal
  }
  throw e;
}

Prevention

When it happens

Trigger: 'number > max' (identifier instead of literal), 'string > ', "number == '5'" (string literal where num/bigint/date required), 'number >' at end of input.

Common situations: Interpolating named constants into definitions ('number >= ${limit}' where limit is undefined or non-numeric); quoting numeric bounds by habit; truncation when the value part of the definition was lost.

Related errors


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