can1357/oh-my-pi · error · OmpTypeError

left bound requires a corresponding right bound in "${this.#

Error message

left bound requires a corresponding right bound in "${this.#src}"

What it means

A left-bounded range ('lo < base' or 'lo <= base') was written, but after the base the parser did not find a comparison operator to close the range. Two-sided ranges like '0 < number < 10' must end with a right bound; a bare '0 < number' is rejected so an incomplete range never silently becomes one-sided.

Source

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

		return { k: "intersection", members };
	}

	/**
	 * `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") {

View on GitHub (pinned to 9690622007)

Solutions

  1. Complete the range: '0 < number < 100'.
  2. If only a lower bound is wanted, flip it to postfix form: 'number > 0' or 'number >= 0'.
  3. Check that the string wasn't truncated — append the missing ' < N' portion.
  4. Note that '>' / '>=' as the right comparator is also rejected (see the companion error); only '<' or '<=' may close the range.

Example fix

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

Strategy: validation

Validate before calling

// left-bound form must contain a closing upper comparator
if (/^\s*-?\d+(?:\.\d+)?\s*[<=]\s*\S+/.test(def) && !/[<>]=?\s*-?\d+(?:\.\d+)?[^<>=]*$/.test(def)) {
  // might be one-sided; prefer rewriting or completing the range
}

Try / catch

try {
  const schema = parse(def);
} catch (e) {
  if (String(e.message).includes("left bound requires a corresponding right bound")) {
    // rewrite as "base > lo" or append " < hi"
  }
  throw e;
}

Prevention

When it happens

Trigger: Definitions like '0 < number' (missing ' < 10'), '0 < number[]', or '0 < string' followed by end-of-input or a non-comparator token.

Common situations: Thinking the left-bound form alone expresses a minimum (in omptype it must be completed on the right, or rewritten as 'number > 0'); truncation during string building; mixing syntax from other validators where '0 < number' is valid.

Related errors


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