can1357/oh-my-pi · error · OmpTypeError

match case values must be functions

Error message

match case values must be functions

What it means

caseResolver() converts match-case values into resolver functions; a case value that is not a function throws OmpTypeError. Match cases are expected to supply handlers (functions of the input) rather than plain values, so a non-function is a misuse of the match API.

Source

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

	in<narrowed>(): MatchParser<narrowed, output>;
	in<const definition>(definition: definition): MatchParser<InferDef<definition>, output>;
}

interface MatchBranch {
	readonly definition: unknown;
	readonly schema: BaseType;
	readonly resolve: (input: unknown, ...args: readonly unknown[]) => unknown;
}

interface MatchState {
	readonly parse: (definition: unknown) => BaseType;
	readonly branches: readonly MatchBranch[];
	readonly input?: BaseType;
	readonly key?: PropertyKey;
}

function caseResolver(value: unknown): (input: unknown, ...args: readonly unknown[]) => unknown {
	if (typeof value !== "function") throw new OmpTypeError("match case values must be functions");
	return (input, ...args) => Reflect.apply(value, undefined, [input, ...args]);
}

function unionIR(branches: readonly MatchBranch[]): IR {
	const members = branches.map(branch => branch.schema.ir);
	if (members.length === 0) return { k: "never" };
	if (members.length === 1) return members[0];
	return { k: "union", members };
}

function publicMatcher<input, output>(
	state: MatchState,
	fallback: MatchDefault<input, output>,
): Matcher<input, output> {
	const fallbackResolver = typeof fallback === "function" ? caseResolver(fallback) : undefined;
	const casesIR = unionIR(state.branches);
	let casesSchema: BaseType;
	if (state.key === undefined || state.branches.length === 0) {

View on GitHub (pinned to 9690622007)

Solutions

  1. Wrap the value in a function: () => 'fallback' instead of 'fallback'
  2. If you need a schema as a case, use the API that accepts a schema, not caseResolver's function path
  3. Validate dynamically built case maps with typeof value === 'function' before passing them
  4. Check that your case object keys map to handler functions, not result constants

Example fix

// before
match({ 'string': 'it was a string' }); // throws
// after
match({ 'string': () => 'it was a string' });
Defensive patterns

Strategy: validation

Validate before calling

function assertFunctionCases(cases: Record<string, unknown>) {
  for (const [key, value] of Object.entries(cases)) {
    if (typeof value !== 'function') {
      throw new Error(`match case "${key}" must be a function`);
    }
  }
}

Type guard

function isHandlerMap(cases: Record<string, unknown>): cases is Record<string, (input: unknown) => unknown> {
  return Object.values(cases).every(v => typeof v === 'function');
}

Try / catch

try {
  const m = match(cases);
} catch (err) {
  if (err instanceof OmpTypeError && err.message === 'match case values must be functions') {
    throw new Error('Wrap constant results in functions: value -> () => value');
  }
  throw err;
}

Prevention

When it happens

Trigger: Passing a non-function as a match case value, e.g. match({ 'string': 'fallback' }) or cases built from data where an entry is a constant/schema instead of a handler function.

Common situations: Confusing value-match with handler-match syntax; JSON-driven case tables where values deserialize as strings/numbers; refactoring from a value API to a function API without wrapping constants.

Related errors


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