can1357/oh-my-pi · error · OmpTypeError

tuple length intersection is unsatisfiable

Error message

tuple length intersection is unsatisfiable

What it means

When intersecting two tuple types, omptype computes the length interval: the intersection's minimum is the max of both required-prefix lengths, its maximum the min of both maximum lengths. If minimum > maximum no tuple can satisfy both sides, so construction throws immediately instead of yielding an uninhabitable type.

Source

Thrown at packages/omptype/src/type.ts:1946

	};
}

function intersectTuples(left: TupleIR, right: TupleIR): IR {
	if (
		left.postfix.length !== 0 ||
		right.postfix.length !== 0 ||
		left.prefix.some(item => item.hasDefault) ||
		right.prefix.some(item => item.hasDefault)
	) {
		return { k: "intersection", members: [left, right] };
	}
	const leftRequired = left.prefix.filter(item => !item.opt).length;
	const rightRequired = right.prefix.filter(item => !item.opt).length;
	const minimum = Math.max(leftRequired, rightRequired);
	const leftMaximum = left.variadic === undefined ? left.prefix.length : Number.POSITIVE_INFINITY;
	const rightMaximum = right.variadic === undefined ? right.prefix.length : Number.POSITIVE_INFINITY;
	const maximum = Math.min(leftMaximum, rightMaximum);
	if (minimum > maximum) throw new OmpTypeError("tuple length intersection is unsatisfiable");

	const prefixLength = Number.isFinite(maximum) ? maximum : Math.max(left.prefix.length, right.prefix.length);
	const prefix: TupleIR["prefix"] = [];
	for (let index = 0; index < prefixLength; index++) {
		const leftItem = left.prefix[index];
		const rightItem = right.prefix[index];
		const leftNode = leftItem?.val ?? left.variadic;
		const rightNode = rightItem?.val ?? right.variadic;
		if (leftNode === undefined || rightNode === undefined) break;
		const required = (leftItem !== undefined && !leftItem.opt) || (rightItem !== undefined && !rightItem.opt);
		try {
			prefix.push({ val: intersect(leftNode, rightNode), opt: !required });
		} catch (error) {
			if (required || !(error instanceof OmpTypeError)) throw error;
			break;
		}
	}
	const variadic =

View on GitHub (pinned to 9690622007)

Solutions

  1. Align tuple lengths so the shorter side can accept the longer side's required elements (pad with optional items or add a variadic)
  2. Make trailing items optional: tuple([string, string, optional(number)]) instead of fixed length 3
  3. If variable length is intended, give one side a rest element (...number()) so its maximum is infinite
  4. Check which two schemas are being intersected; likely one is an outdated copy

Example fix

// before
const A = tuple([string, string]); const B = tuple([string]);
intersect(A, B) // throws
// after
const B = tuple([string, optional(string)]);
intersect(A, B)
Defensive patterns

Strategy: try-catch

Validate before calling

function tupleLengthCompatible(a, b) {
  const minA = a.prefix.filter(i => !i.opt).length;
  const minB = b.prefix.filter(i => !i.opt).length;
  const maxA = a.variadic !== undefined ? Infinity : a.prefix.length;
  const maxB = b.variadic !== undefined ? Infinity : b.prefix.length;
  return Math.max(minA, minB) <= Math.min(maxA, maxB);
}

Try / catch

try {
  return intersect(tupleA, tupleB);
} catch (err) {
  if (err instanceof OmpTypeError && err.message.includes('tuple length')) {
    logger.error('incompatible tuple lengths', { a: tupleA, b: tupleB });
    return never();
  }
  throw err;
}

Prevention

When it happens

Trigger: intersect(tuple([string, string, string]), tuple([string])) — one tuple requires 3 elements while the other allows at most 1; similarly tuple([a, optional(b)]) ∩ tuple([a, b, c]).

Common situations: Version-drift between two tuple definitions (one grew a field, the other didn't); intersecting a fixed tuple with a shorter one by mistake; config schema and payload schema diverging after a code change.

Related errors


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