🐛 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<T, unknown>`. Adding `Partial<Handlers<T, R>>` 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.
This commit is contained in:
tmu committed 2026-09-18 23:33:34 +00:00
1 parent 2d3577716d
commit c7223dbe69
2 files changed
+14 -14

No files matched your search

+13 -13
View File
@@ -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";
},
+1 -1
View File
@@ -32,7 +32,7 @@ type Fallback<T extends string | number, Handled, R> = UnaryFn<
interface MatcherStrict<T extends string | number> {
<R>(handlers: Handlers<T, R>): UnaryFn<T, R>;
<R, Handled extends Exact<Partial<Handlers<T, R>>, Handled>>(
handlers: Handled,
handlers: Handled & Partial<Handlers<T, R>>,
fallback: Fallback<T, Handled, R>,
): UnaryFn<T, R>;
}