From b6d791a79bc38846dc8d150a867007c084306862 Mon Sep 17 00:00:00 2001 From: gvergnaud Date: Sat, 14 Dec 2024 18:30:39 -0500 Subject: [PATCH 1/3] feat(P.infer): Remove inference point --- package-lock.json | 14 +++++++------- package.json | 2 +- src/patterns.ts | 10 +++------- src/types/FindSelected.ts | 2 +- tests/infer.test.ts | 17 +++++++++++++++++ tests/not.test.ts | 4 +++- 6 files changed, 32 insertions(+), 17 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4aaf7230..7abd1429 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "prettier": "^2.8.8", "rimraf": "^5.0.1", "ts-jest": "^29.1.2", - "typescript": "^5.3.3" + "typescript": "^5.7.2" } }, "node_modules/@babel/code-frame": { @@ -8230,9 +8230,9 @@ } }, "node_modules/typescript": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", - "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", + "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -14487,9 +14487,9 @@ } }, "typescript": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", - "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", + "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", "dev": true }, "unbox-primitive": { diff --git a/package.json b/package.json index 06aabb0e..46abf550 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,6 @@ "prettier": "^2.8.8", "rimraf": "^5.0.1", "ts-jest": "^29.1.2", - "typescript": "^5.3.3" + "typescript": "^5.7.2" } } diff --git a/src/patterns.ts b/src/patterns.ts index b08d8dfd..9f04b2e3 100644 --- a/src/patterns.ts +++ b/src/patterns.ts @@ -92,10 +92,7 @@ export type unstable_Matcher< * const userPattern = { name: P.string } * type User = P.infer */ -export type infer> = InvertPattern< - pattern, - unknown ->; +export type infer = InvertPattern, unknown>; /** * `P.narrow` will narrow the input type to only keep @@ -110,9 +107,8 @@ export type infer> = InvertPattern< * type Narrowed = P.narrow * // ^? ['a', 'a' | 'b'] */ -export type narrow> = ExtractPreciseValue< - input, - InvertPattern +export type narrow> = NoInfer< + ExtractPreciseValue> >; function chainable>( diff --git a/src/types/FindSelected.ts b/src/types/FindSelected.ts index c2ac5b92..67603554 100644 --- a/src/types/FindSelected.ts +++ b/src/types/FindSelected.ts @@ -166,4 +166,4 @@ export type FindSelected = // This happens if the provided pattern didn't extend Pattern, // Because the type checker falls back on the general `Pattern` type // in this case. - Equal> extends true ? i : Selections; + NoInfer> extends true ? i : Selections>; diff --git a/tests/infer.test.ts b/tests/infer.test.ts index 3a2c9c6e..8ba255fa 100644 --- a/tests/infer.test.ts +++ b/tests/infer.test.ts @@ -22,4 +22,21 @@ describe('P.infer', () => { type test2 = Expect>; }); }); + + it("P.infer shouldn't count as an inference point of the pattern", () => { + const getValueOfType = >( + obj: unknown, + path: string, + pattern: T, + defaultValue: P.infer + ): P.infer => defaultValue; + + getValueOfType( + null, + 'a.b.c', + { x: P.string }, + // @ts-expect-error 👇 error should be here + 'oops' + ); + }); }); diff --git a/tests/not.test.ts b/tests/not.test.ts index 4accb525..283115f9 100644 --- a/tests/not.test.ts +++ b/tests/not.test.ts @@ -110,7 +110,9 @@ describe('not', () => { .exhaustive() ).toBe('hello'); - const untypedNullable = P.when((x) => x === null || x === undefined); + const untypedNullable = P.when( + (x): boolean => x === null || x === undefined + ); expect( match<{ str: string }>({ str: 'hello' }) From 16e2b2c70672334d40b016bc6463f5fd7a1d2930 Mon Sep 17 00:00:00 2001 From: gvergnaud Date: Sat, 14 Dec 2024 18:53:20 -0500 Subject: [PATCH 2/3] feat(isMatching): Typecheck pattern when using isMatching wth 2 parameters --- src/is-matching.ts | 8 ++++---- tests/is-matching.test.ts | 20 ++++++++++++++++---- tests/matcher-protocol.test.ts | 3 ++- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/is-matching.ts b/src/is-matching.ts index 705a7148..f786ff1e 100644 --- a/src/is-matching.ts +++ b/src/is-matching.ts @@ -33,10 +33,10 @@ export function isMatching>( * return input.name * } */ -export function isMatching>( - pattern: p, - value: unknown -): value is P.infer

; +export function isMatching>>( + pattern: P, + value: T +): value is P.infer

; export function isMatching>( ...args: [pattern: p, value?: any] diff --git a/tests/is-matching.test.ts b/tests/is-matching.test.ts index d553cd53..882abca3 100644 --- a/tests/is-matching.test.ts +++ b/tests/is-matching.test.ts @@ -56,11 +56,11 @@ describe('isMatching', () => { } }); - it('type inference should be precise without `as const`', () => { - type Pizza = { type: 'pizza'; topping: string }; - type Sandwich = { type: 'sandwich'; condiments: string[] }; - type Food = Pizza | Sandwich; + type Pizza = { type: 'pizza'; topping: string }; + type Sandwich = { type: 'sandwich'; condiments: string[] }; + type Food = Pizza | Sandwich; + it('type inference should be precise without `as const`', () => { const food = { type: 'pizza', topping: 'cheese' } as Food; const isPizza = isMatching({ type: 'pizza' }); @@ -77,4 +77,16 @@ describe('isMatching', () => { throw new Error('Expected food to match the pizza pattern!'); } }); + + it('should reject invalid pattern when two parameters are passed', () => { + const food = { type: 'pizza', topping: 'cheese' } as Food; + + isMatching( + { + // @ts-expect-error + type: 'oops', + }, + food + ); + }); }); diff --git a/tests/matcher-protocol.test.ts b/tests/matcher-protocol.test.ts index 41940721..22eaff53 100644 --- a/tests/matcher-protocol.test.ts +++ b/tests/matcher-protocol.test.ts @@ -26,7 +26,8 @@ describe('matcher protocol', () => { match: (input) => { return { matched: - input instanceof Some && isMatching(this.value, input.value), + input instanceof Some && + isMatching(this.value, input.value), }; }, }; From a009d89f6f7c72f1c50f19de539c85258d92dd21 Mon Sep 17 00:00:00 2001 From: gvergnaud Date: Sat, 14 Dec 2024 19:14:28 -0500 Subject: [PATCH 3/3] feat(P.Pattern): support no type param --- src/types/Pattern.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/types/Pattern.ts b/src/types/Pattern.ts index 47e124d3..6fecf1fa 100644 --- a/src/types/Pattern.ts +++ b/src/types/Pattern.ts @@ -145,7 +145,9 @@ export type UnknownPattern = * @example * const pattern: P.Pattern = { name: P.string } */ -export type Pattern = unknown extends a ? UnknownPattern : KnownPattern; +export type Pattern = unknown extends a + ? UnknownPattern + : KnownPattern; type KnownPattern = KnownPatternInternal;