can1357/oh-my-pi · error · OmpTypeError

ParseError: Invalid intersection of default values

Error message

ParseError: Invalid intersection of default values ${String(ap.def)} & ${String(bp.def)}

What it means

When intersecting two object types that share a property key where BOTH sides declare a default value and those defaults differ (by Object.is), the intersection cannot pick a single default, so the library throws. Intersections must yield one deterministic default for each property.

Solutions

  1. Make the defaults identical on both sides
  2. Remove .default() from one of the two operands so only one side supplies a default
  3. Merge the schemas at the source (one canonical definition) instead of intersecting
  4. Catch OmpTypeError and report which key/defaults conflict

Example fix

// before
intersect(object({retries: number().opt().default(3)}), object({retries: number().opt().default(5)}));
// after
intersect(object({retries: number().opt().default(3)}), object({retries: number().opt()}));
Defensive patterns

Strategy: validation

Validate before calling

function hasConflictingDefaults(a, b, key) {
  const pa = a.props?.find(p => p.key === key);
  const pb = b.props?.find(p => p.key === key);
  return pa?.hasDefault && pb?.hasDefault && !Object.is(pa.def, pb.def);
}

Type guard

const isOmpTypeError = (e: unknown): e is OmpTypeError => e instanceof OmpTypeError;

Try / catch

try {
  const merged = intersect(baseSchema, overrideSchema);
} catch (err) {
  if (err instanceof OmpTypeError && err.message.startsWith('ParseError: Invalid intersection of default values')) {
    logger.warn('conflicting defaults in schema intersection', { message: err.message });
  } else throw err;
}

Prevention

When it happens

Trigger: intersect(object({retries: opt(number).default(3)}), object({retries: opt(number).default(5)})) — same key, two different .default(...) values; typically from merging two schema definitions of the same object.

Common situations: Base schema and override schema both specifying .default() for the same option with different values; a vendor schema and a local refinement both defaulting a field; copy-pasted schema fragments drifting apart.

Related errors


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

Appendix: source

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

		}
		if (members.length === 0) throw new OmpTypeError("intersection has no satisfiable branches");
		return members.length === 1 ? members[0] : { k: "union", members };
	}
	if (a.k === "lit") {
		if (walk(b, a.v) instanceof OmpErrors) throw new OmpTypeError("literal is excluded by the intersection");
		return a;
	}
	if (b.k === "lit") return intersect(b, a);
	if (a.k === "object" && b.k === "object") {
		const props = [...a.props];
		for (const bp of b.props) {
			const index = props.findIndex(prop => prop.key === bp.key);
			if (index < 0) props.push(bp);
			else {
				const ap = props[index];
				const required = (!ap.opt && !ap.hasDefault) || (!bp.opt && !bp.hasDefault);
				if (ap.hasDefault && bp.hasDefault && !Object.is(ap.def, bp.def)) {
					throw new OmpTypeError(
						`ParseError: Invalid intersection of default values ${String(ap.def)} & ${String(bp.def)}`,
					);
				}
				const defaulted = required ? undefined : ap.hasDefault ? ap : bp.hasDefault ? bp : undefined;
				props[index] = {
					key: ap.key,
					opt: ap.opt && bp.opt,
					val: intersect(ap.val, bp.val),
					...(defaulted
						? {
								def: defaulted.def,
								defFactory: defaulted.defFactory,
								hasDefault: true,
								defValidated: defaulted.defValidated,
							}
						: {}),
				};
			}

View on GitHub (pinned to 9690622007)