🐛 Type partial W matcher without an any cast

getPrimitiveUnionMatcherPartialW was declared with the intended
P-inferring signature but assigned via `as any`, hiding that the
declared parameter was not assignable to the implementation's. The
union in PatternPrimitiveUnionPartial makes the `_` arm demand
`_ ∈ keyof P`, which the exhaustive arm cannot prove.

Intersect the inference hook Simplify<P> with the implementation's
parameter shape, R pinned to PatternReturns<P>, so the assignment
type-checks while callers keep inferring P from the argument.

Add a spec for the exhaustive (no `_`) pattern.
This commit is contained in:
tmu committed 2026-09-16 19:10:40 +00:00
1 parent 2ff67801e0
commit cce030b8e5
2 files changed
+27 -4

No files matched your search

+17
View File
@@ -170,3 +170,20 @@ test("getPrimitiveUnionMatcherPartialW allows gaps and widens to the union of ha
assert.equal(matcher("y"), "fallback"); assert.equal(matcher("y"), "fallback");
assert.equal(matcher("z"), "fallback"); assert.equal(matcher("z"), "fallback");
}); });
test("getPrimitiveUnionMatcherPartialW also accepts an exhaustive pattern", () => {
// Arrange
const factory = getPrimitiveUnionMatcherPartialW<"x" | "y">();
// Act
const matcher = factory({
x: () => 1 as const,
y: () => "two" as const,
});
// Assert
// ❌ ReturnsStrict: mixed handler returns widen to their union.
expectTypeOf(matcher).toEqualTypeOf<(shape: "x" | "y") => 1 | "two">();
assert.equal(matcher("x"), 1);
assert.equal(matcher("y"), "two");
});
+10 -4
View File
@@ -55,7 +55,13 @@ export const getPrimitiveUnionMatcherPartial: <T extends string | number>() => <
export const getPrimitiveUnionMatcherPartialW: < export const getPrimitiveUnionMatcherPartialW: <
T extends string | number, T extends string | number,
>() => <P extends PatternPrimitiveUnionPartial<unknown, T>>( >() => <P extends PatternPrimitiveUnionPartial<unknown, T>>(
pattern: Simplify<P>, /*
`Simplify<P>` is the inference hook: callers infer `P` from the argument.
// oxlint-disable-next-line typescript/no-explicit-any typescript/no-unsafe-type-assertion the second half pins the impl parameter's `R` to `PatternReturns<P>`.
) => UnaryFn<T, PatternReturns<P>> = getPrimitiveUnionMatcherPartial as any; that makes the `= getPrimitiveUnionMatcherPartial` assignment type-check.
neither half works alone.
without the witness the union's `_` arm demands `_ ∈ keyof P`.
without `Simplify<P>` the parameter types do not compare.
*/
pattern: Simplify<P> & PatternPrimitiveUnionPartial<PatternReturns<P>, T>,
) => UnaryFn<T, PatternReturns<P>> = getPrimitiveUnionMatcherPartial;