can1357/oh-my-pi · error · OmpTypeError

expected literal after == in "${this.#src}"

Error message

expected literal after == in "${this.#src}"

What it means

The '==' equality constraint requires a num, bigint, or date literal immediately after it, but another token kind appeared. Unlike bounds, '==' does not accept date-vs-num mixing excuses — the operand must be one of those literal token types, so identifiers, strings, booleans, and end-of-input all throw.

Source

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

			}
			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);
		}
		return node;
	}

	#eatDivisor(node: IR): IR {
		if (!this.#eatOp("%")) return node;
		const divisor = this.#next();
		if (divisor.t !== "num") throw new OmpTypeError(`expected number after % in "${this.#src}"`);
		if (node.k !== "number") throw new OmpTypeError(`% requires number in "${this.#src}"`);
		if (!Number.isFinite(divisor.v) || !Number.isInteger(divisor.v) || divisor.v === 0)
			throw new OmpTypeError(`divisor must be a non-zero integer in "${this.#src}"`);

View on GitHub (pinned to 9690622007)

Solutions

  1. Use a literal operand: 'number == 5', "'a' == 'a'", 'bigint == 10n', 'Date == d"2024-01-01"'.
  2. Stringify numbers before interpolation and never quote them; validate the interpolated value with Number.isFinite/typeof first.
  3. For non-numeric equality, use the appropriate literal syntax (quoted string, true/false keyword) or the corresponding keyword type instead of '=='.
  4. If the operand is dynamic, build the schema via the object/IR API where values are passed as data, not parsed text.

Example fix

// before
schema.parse(`number == ${value}`); // value = "x" -> throws
// after
if (!Number.isFinite(value)) throw new Error("value must be numeric");
schema.parse(`number == ${value}`);
Defensive patterns

Strategy: validation

Validate before calling

function assertEqualityOperand(v) {
  const ok = typeof v === "number" || typeof v === "bigint" || v instanceof Date;
  if (!ok) throw new Error(`== operand must be num/bigint/date literal, got ${typeof v}`);
}

Type guard

function isEqualityLiteral(v) {
  return typeof v === "number" || typeof v === "bigint" || v instanceof Date;
}

Try / catch

try {
  const schema = parse(def);
} catch (e) {
  if (String(e.message).includes("expected literal after ==")) {
    // coerce the operand to a literal or switch to the value-based API
  }
  throw e;
}

Prevention

When it happens

Trigger: 'number == x' (identifier), "'a' == 'b'" where the right side is fine but 'number == "5"' (string token) fails, 'number ==' at end of input, 'boolean == 1'.

Common situations: Interpolating variable names into definitions expecting them to resolve at runtime; quoting the equality operand; building 'field == value' strings where value was never stringified into a literal.

Related errors


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