From cce030b8e5c438c2443d79d6141622ee147a975a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Wed, 16 Sep 2026 19:08:06 +0000 Subject: [PATCH] :bug: Type partial W matcher without an any cast MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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

with the implementation's parameter shape, R pinned to PatternReturns

, so the assignment type-checks while callers keep inferring P from the argument. Add a spec for the exhaustive (no `_`) pattern. --- src/primitive.test.ts | 17 +++++++++++++++++ src/primitive.ts | 14 ++++++++++---- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/primitive.test.ts b/src/primitive.test.ts index e50e57e..382cda0 100644 --- a/src/primitive.test.ts +++ b/src/primitive.test.ts @@ -170,3 +170,20 @@ test("getPrimitiveUnionMatcherPartialW allows gaps and widens to the union of ha assert.equal(matcher("y"), "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"); +}); diff --git a/src/primitive.ts b/src/primitive.ts index 04d411b..9ecae69 100644 --- a/src/primitive.ts +++ b/src/primitive.ts @@ -55,7 +55,13 @@ export const getPrimitiveUnionMatcherPartial: () => < export const getPrimitiveUnionMatcherPartialW: < T extends string | number, >() =>

>( - pattern: Simplify

, - - // oxlint-disable-next-line typescript/no-explicit-any typescript/no-unsafe-type-assertion -) => UnaryFn> = getPrimitiveUnionMatcherPartial as any; + /* + `Simplify

` is the inference hook: callers infer `P` from the argument. + the second half pins the impl parameter's `R` to `PatternReturns

`. + that makes the `= getPrimitiveUnionMatcherPartial` assignment type-check. + neither half works alone. + without the witness the union's `_` arm demands `_ ∈ keyof P`. + without `Simplify

` the parameter types do not compare. + */ + pattern: Simplify

& PatternPrimitiveUnionPartial, T>, +) => UnaryFn> = getPrimitiveUnionMatcherPartial;