Skip to content

Commit

Permalink
Merge pull request #302 from gvergnaud/P-infer-improvement
Browse files Browse the repository at this point in the history
Improvements to `P.infer` and `isMatching`
  • Loading branch information
gvergnaud authored Dec 15, 2024
2 parents b5aa458 + a009d89 commit ba71cf1
Show file tree
Hide file tree
Showing 10 changed files with 57 additions and 27 deletions.
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
8 changes: 4 additions & 4 deletions src/is-matching.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ export function isMatching<const p extends Pattern<unknown>>(
* return input.name
* }
*/
export function isMatching<const p extends Pattern<unknown>>(
pattern: p,
value: unknown
): value is P.infer<p>;
export function isMatching<const T, const P extends P.Pattern<NoInfer<T>>>(
pattern: P,
value: T
): value is P.infer<P>;

export function isMatching<const p extends Pattern<any>>(
...args: [pattern: p, value?: any]
Expand Down
10 changes: 3 additions & 7 deletions src/patterns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,7 @@ export type unstable_Matcher<
* const userPattern = { name: P.string }
* type User = P.infer<typeof userPattern>
*/
export type infer<pattern extends Pattern<any>> = InvertPattern<
pattern,
unknown
>;
export type infer<pattern> = InvertPattern<NoInfer<pattern>, unknown>;

/**
* `P.narrow<Input, Pattern>` will narrow the input type to only keep
Expand All @@ -110,9 +107,8 @@ export type infer<pattern extends Pattern<any>> = InvertPattern<
* type Narrowed = P.narrow<Input, typeof Pattern>
* // ^? ['a', 'a' | 'b']
*/
export type narrow<input, pattern extends Pattern<any>> = ExtractPreciseValue<
input,
InvertPattern<pattern, input>
export type narrow<input, pattern extends Pattern<any>> = NoInfer<
ExtractPreciseValue<input, InvertPattern<pattern, input>>
>;

function chainable<pattern extends Matcher<any, any, any, any, any>>(
Expand Down
2 changes: 1 addition & 1 deletion src/types/FindSelected.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,4 +166,4 @@ export type FindSelected<i, p> =
// This happens if the provided pattern didn't extend Pattern<i>,
// Because the type checker falls back on the general `Pattern<i>` type
// in this case.
Equal<p, Pattern<i>> extends true ? i : Selections<i, p>;
NoInfer<Equal<p, Pattern<i>> extends true ? i : Selections<i, p>>;
4 changes: 3 additions & 1 deletion src/types/Pattern.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,9 @@ export type UnknownPattern =
* @example
* const pattern: P.Pattern<User> = { name: P.string }
*/
export type Pattern<a> = unknown extends a ? UnknownPattern : KnownPattern<a>;
export type Pattern<a = unknown> = unknown extends a
? UnknownPattern
: KnownPattern<a>;

type KnownPattern<a> = KnownPatternInternal<a>;

Expand Down
17 changes: 17 additions & 0 deletions tests/infer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,21 @@ describe('P.infer', () => {
type test2 = Expect<Equal<QuizState, expected2>>;
});
});

it("P.infer shouldn't count as an inference point of the pattern", () => {
const getValueOfType = <T extends P.Pattern<unknown>>(
obj: unknown,
path: string,
pattern: T,
defaultValue: P.infer<T>
): P.infer<T> => defaultValue;

getValueOfType(
null,
'a.b.c',
{ x: P.string },
// @ts-expect-error 👇 error should be here
'oops'
);
});
});
20 changes: 16 additions & 4 deletions tests/is-matching.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' });
Expand All @@ -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
);
});
});
3 changes: 2 additions & 1 deletion tests/matcher-protocol.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ describe('matcher protocol', () => {
match: (input) => {
return {
matched:
input instanceof Some && isMatching<any>(this.value, input.value),
input instanceof Some &&
isMatching<any, any>(this.value, input.value),
};
},
};
Expand Down
4 changes: 3 additions & 1 deletion tests/not.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' })
Expand Down

0 comments on commit ba71cf1

Please sign in to comment.