can1357/oh-my-pi · error · OmpTypeError

right bound must use < or <= in "${this.#src}"

Error message

right bound must use < or <= in "${this.#src}"

What it means

In a two-sided range 'lo CMP base CMP2 hi', the right comparator must be '<' or '<=' — writing '0 < number > 10' or '0 < number >= 10' is contradictory (cannot be satisfied) and is rejected at parse time. The left side already establishes the lower bound with flipped semantics, so only upper-bound comparators are legal on the right.

Source

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

	/**
	 * `NUM CMP base (CMP NUM)?` or `base (CMP NUM)?`, with `[]*` postfix on the
	 * base AND after a trailing bound — `string>0[]` is an array of bounded
	 * strings, matching ArkType precedence (bounds bind tighter than `[]`).
	 */
	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);

View on GitHub (pinned to 9690622007)

Solutions

  1. Use '<' or '<=' for the right comparator: '0 < number < 10'.
  2. Express the intended (possibly odd) constraint as an intersection of two one-sided bounds if truly needed: 'number > 0 & number > 10' — though verify it isn't contradictory.
  3. Re-read the range in reading order: left value is the minimum, right value is the maximum.
  4. Validate programmatically-built ranges so lo <= hi and comparators are generated, not hand-typed.

Example fix

// before
schema.parse("0 < number > 10");
// after
schema.parse("0 < number < 10");
Defensive patterns

Strategy: validation

Validate before calling

// in a two-sided range the right comparator must be < or <=
const m = def.match(/^\s*-?\d+(?:\.\d+)?\s*[<=]\s*\S+\s*([<>=]+)/);
if (m && (m[1] === ">" || m[1] === ">=")) throw new Error(`wrong right comparator ${m[1]} in: ${def}`);

Try / catch

try {
  const schema = parse(def);
} catch (e) {
  if (String(e.message).includes("right bound must use < or <=")) {
    // flip the right comparator to < or <=
  }
  throw e;
}

Prevention

When it happens

Trigger: Definitions like '0 < number > 10' or '1 <= number >= 5' where the second comparator points the wrong way.

Common situations: Transliterating chained comparisons from prose ('greater than 0 and greater than 10'); typos when editing both bounds in the same direction; copying a lower-bound expression into the right-hand slot.

Related errors


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