From c7223dbe69ab28f9ec5ebba6e5f5f5011ea6639f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Fri, 18 Sep 2026 23:33:34 +0000 Subject: [PATCH] :bug: Infer strict return from the handler map The fallback overload could not infer `R` from the handlers: `R` appeared only inside the `Exact` constraint, which is not an inference site, so with inferred handler params it collapsed to `unknown` and the matcher silently returned `UnaryFn`. Adding `Partial>` to the handler parameter gives `R` an inference site, so the common return is inferred from the handlers and the fallback alike. Tests drop their explicit handler return annotations: real handlers are short and unannotated, and the common type is now inferred from them. --- src/primitive.test.ts | 26 +++++++++++++------------- src/primitive.ts | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/primitive.test.ts b/src/primitive.test.ts index 7ed81df..c222fe2 100644 --- a/src/primitive.test.ts +++ b/src/primitive.test.ts @@ -22,11 +22,11 @@ test("getMatcher: exhaustive pattern infers one common return type", () => { // Act const matcher = factory({ - a: (s): number => { + a: (s) => { expectTypeOf(s).toEqualTypeOf<"a">(); return 1; }, - b: (s): 1 | 2 => { + b: (s) => { expectTypeOf(s).toEqualTypeOf<"b">(); return 2; }, @@ -69,19 +69,19 @@ test("getMatcher: dispatches on mixed string and numeric keys", () => { // Act const matcher = factory({ - a: (s): number => { + a: (s) => { expectTypeOf(s).toEqualTypeOf<"a">(); return 1; }, - b: (s): 1 | 2 => { + b: (s) => { expectTypeOf(s).toEqualTypeOf<"b">(); return 2; }, - 1: (n): number => { + 1: (n) => { expectTypeOf(n).toEqualTypeOf<1>(); return 10; }, - 2: (n): number => { + 2: (n) => { expectTypeOf(n).toEqualTypeOf<2>(); return 20; }, @@ -106,15 +106,15 @@ test("getMatcher: a `_` fallback receives the unhandled keys", () => { // Act const matcher = factory( { - a: (s): 1 | 2 => { + a: (s) => { expectTypeOf(s).toEqualTypeOf<"a">(); - return 1; + return 1 as const; }, }, - (s): 1 | 2 => { + (s) => { // The fallback sees only the keys `a` did not handle. expectTypeOf(s).toEqualTypeOf<"b" | "c">(); - return 2; + return 2 as const; }, ); @@ -134,16 +134,16 @@ test("getMatcher: a `_` fallback routes mixed string and numeric gaps", () => { // Act const matcher = factory( { - a: (s): string => { + a: (s) => { expectTypeOf(s).toEqualTypeOf<"a">(); return "A"; }, - 1: (n): string => { + 1: (n) => { expectTypeOf(n).toEqualTypeOf<1>(); return "one"; }, }, - (s): string => { + (s) => { expectTypeOf(s).toEqualTypeOf<"b" | 2>(); return "fallback"; }, diff --git a/src/primitive.ts b/src/primitive.ts index 63bfd67..74f6c34 100644 --- a/src/primitive.ts +++ b/src/primitive.ts @@ -32,7 +32,7 @@ type Fallback = UnaryFn< interface MatcherStrict { (handlers: Handlers): UnaryFn; >, Handled>>( - handlers: Handled, + handlers: Handled & Partial>, fallback: Fallback, ): UnaryFn; }