New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow (non-assert) type predicates to narrow by discriminant #57358
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
b3f3df5
fix bug
gabritto 24cc86a
fmt
gabritto 511c333
get rid of `hasMatchingArgument` call
gabritto b8b53a3
Revert "get rid of `hasMatchingArgument` call"
gabritto ad62d2c
get candidate discriminant property access in `hasMatchingArgument`
gabritto File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
30 changes: 30 additions & 0 deletions
30
tests/baselines/reference/typePredicatesCanNarrowByDiscriminant.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
//// [tests/cases/compiler/typePredicatesCanNarrowByDiscriminant.ts] //// | ||
|
||
//// [typePredicatesCanNarrowByDiscriminant.ts] | ||
// #45770 | ||
declare const fruit: { kind: 'apple'} | { kind: 'banana' } | { kind: 'cherry' } | ||
|
||
declare function isOneOf<T, U extends T>(item: T, array: readonly U[]): item is U | ||
if (isOneOf(fruit.kind, ['apple', 'banana'] as const)) { | ||
fruit.kind | ||
fruit | ||
} | ||
|
||
declare const fruit2: { kind: 'apple'} | { kind: 'banana' } | { kind: 'cherry' } | ||
const kind = fruit2.kind; | ||
if (isOneOf(kind, ['apple', 'banana'] as const)) { | ||
fruit2.kind | ||
fruit2 | ||
} | ||
|
||
//// [typePredicatesCanNarrowByDiscriminant.js] | ||
"use strict"; | ||
if (isOneOf(fruit.kind, ['apple', 'banana'])) { | ||
fruit.kind; | ||
fruit; | ||
} | ||
var kind = fruit2.kind; | ||
if (isOneOf(kind, ['apple', 'banana'])) { | ||
fruit2.kind; | ||
fruit2; | ||
} |
63 changes: 63 additions & 0 deletions
63
tests/baselines/reference/typePredicatesCanNarrowByDiscriminant.symbols
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
//// [tests/cases/compiler/typePredicatesCanNarrowByDiscriminant.ts] //// | ||
|
||
=== typePredicatesCanNarrowByDiscriminant.ts === | ||
// #45770 | ||
declare const fruit: { kind: 'apple'} | { kind: 'banana' } | { kind: 'cherry' } | ||
>fruit : Symbol(fruit, Decl(typePredicatesCanNarrowByDiscriminant.ts, 1, 13)) | ||
>kind : Symbol(kind, Decl(typePredicatesCanNarrowByDiscriminant.ts, 1, 22)) | ||
>kind : Symbol(kind, Decl(typePredicatesCanNarrowByDiscriminant.ts, 1, 41)) | ||
>kind : Symbol(kind, Decl(typePredicatesCanNarrowByDiscriminant.ts, 1, 62)) | ||
|
||
declare function isOneOf<T, U extends T>(item: T, array: readonly U[]): item is U | ||
>isOneOf : Symbol(isOneOf, Decl(typePredicatesCanNarrowByDiscriminant.ts, 1, 79)) | ||
>T : Symbol(T, Decl(typePredicatesCanNarrowByDiscriminant.ts, 3, 25)) | ||
>U : Symbol(U, Decl(typePredicatesCanNarrowByDiscriminant.ts, 3, 27)) | ||
>T : Symbol(T, Decl(typePredicatesCanNarrowByDiscriminant.ts, 3, 25)) | ||
>item : Symbol(item, Decl(typePredicatesCanNarrowByDiscriminant.ts, 3, 41)) | ||
>T : Symbol(T, Decl(typePredicatesCanNarrowByDiscriminant.ts, 3, 25)) | ||
>array : Symbol(array, Decl(typePredicatesCanNarrowByDiscriminant.ts, 3, 49)) | ||
>U : Symbol(U, Decl(typePredicatesCanNarrowByDiscriminant.ts, 3, 27)) | ||
>item : Symbol(item, Decl(typePredicatesCanNarrowByDiscriminant.ts, 3, 41)) | ||
>U : Symbol(U, Decl(typePredicatesCanNarrowByDiscriminant.ts, 3, 27)) | ||
|
||
if (isOneOf(fruit.kind, ['apple', 'banana'] as const)) { | ||
>isOneOf : Symbol(isOneOf, Decl(typePredicatesCanNarrowByDiscriminant.ts, 1, 79)) | ||
>fruit.kind : Symbol(kind, Decl(typePredicatesCanNarrowByDiscriminant.ts, 1, 22), Decl(typePredicatesCanNarrowByDiscriminant.ts, 1, 41), Decl(typePredicatesCanNarrowByDiscriminant.ts, 1, 62)) | ||
>fruit : Symbol(fruit, Decl(typePredicatesCanNarrowByDiscriminant.ts, 1, 13)) | ||
>kind : Symbol(kind, Decl(typePredicatesCanNarrowByDiscriminant.ts, 1, 22), Decl(typePredicatesCanNarrowByDiscriminant.ts, 1, 41), Decl(typePredicatesCanNarrowByDiscriminant.ts, 1, 62)) | ||
>const : Symbol(const) | ||
|
||
fruit.kind | ||
>fruit.kind : Symbol(kind, Decl(typePredicatesCanNarrowByDiscriminant.ts, 1, 22), Decl(typePredicatesCanNarrowByDiscriminant.ts, 1, 41)) | ||
>fruit : Symbol(fruit, Decl(typePredicatesCanNarrowByDiscriminant.ts, 1, 13)) | ||
>kind : Symbol(kind, Decl(typePredicatesCanNarrowByDiscriminant.ts, 1, 22), Decl(typePredicatesCanNarrowByDiscriminant.ts, 1, 41)) | ||
|
||
fruit | ||
>fruit : Symbol(fruit, Decl(typePredicatesCanNarrowByDiscriminant.ts, 1, 13)) | ||
} | ||
|
||
declare const fruit2: { kind: 'apple'} | { kind: 'banana' } | { kind: 'cherry' } | ||
>fruit2 : Symbol(fruit2, Decl(typePredicatesCanNarrowByDiscriminant.ts, 9, 13)) | ||
>kind : Symbol(kind, Decl(typePredicatesCanNarrowByDiscriminant.ts, 9, 23)) | ||
>kind : Symbol(kind, Decl(typePredicatesCanNarrowByDiscriminant.ts, 9, 42)) | ||
>kind : Symbol(kind, Decl(typePredicatesCanNarrowByDiscriminant.ts, 9, 63)) | ||
|
||
const kind = fruit2.kind; | ||
>kind : Symbol(kind, Decl(typePredicatesCanNarrowByDiscriminant.ts, 10, 5)) | ||
>fruit2.kind : Symbol(kind, Decl(typePredicatesCanNarrowByDiscriminant.ts, 9, 23), Decl(typePredicatesCanNarrowByDiscriminant.ts, 9, 42), Decl(typePredicatesCanNarrowByDiscriminant.ts, 9, 63)) | ||
>fruit2 : Symbol(fruit2, Decl(typePredicatesCanNarrowByDiscriminant.ts, 9, 13)) | ||
>kind : Symbol(kind, Decl(typePredicatesCanNarrowByDiscriminant.ts, 9, 23), Decl(typePredicatesCanNarrowByDiscriminant.ts, 9, 42), Decl(typePredicatesCanNarrowByDiscriminant.ts, 9, 63)) | ||
|
||
if (isOneOf(kind, ['apple', 'banana'] as const)) { | ||
>isOneOf : Symbol(isOneOf, Decl(typePredicatesCanNarrowByDiscriminant.ts, 1, 79)) | ||
>kind : Symbol(kind, Decl(typePredicatesCanNarrowByDiscriminant.ts, 10, 5)) | ||
>const : Symbol(const) | ||
|
||
fruit2.kind | ||
>fruit2.kind : Symbol(kind, Decl(typePredicatesCanNarrowByDiscriminant.ts, 9, 23), Decl(typePredicatesCanNarrowByDiscriminant.ts, 9, 42)) | ||
>fruit2 : Symbol(fruit2, Decl(typePredicatesCanNarrowByDiscriminant.ts, 9, 13)) | ||
>kind : Symbol(kind, Decl(typePredicatesCanNarrowByDiscriminant.ts, 9, 23), Decl(typePredicatesCanNarrowByDiscriminant.ts, 9, 42)) | ||
|
||
fruit2 | ||
>fruit2 : Symbol(fruit2, Decl(typePredicatesCanNarrowByDiscriminant.ts, 9, 13)) | ||
} |
64 changes: 64 additions & 0 deletions
64
tests/baselines/reference/typePredicatesCanNarrowByDiscriminant.types
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
//// [tests/cases/compiler/typePredicatesCanNarrowByDiscriminant.ts] //// | ||
|
||
=== typePredicatesCanNarrowByDiscriminant.ts === | ||
// #45770 | ||
declare const fruit: { kind: 'apple'} | { kind: 'banana' } | { kind: 'cherry' } | ||
>fruit : { kind: 'apple'; } | { kind: 'banana'; } | { kind: 'cherry'; } | ||
>kind : "apple" | ||
>kind : "banana" | ||
>kind : "cherry" | ||
|
||
declare function isOneOf<T, U extends T>(item: T, array: readonly U[]): item is U | ||
>isOneOf : <T, U extends T>(item: T, array: readonly U[]) => item is U | ||
>item : T | ||
>array : readonly U[] | ||
|
||
if (isOneOf(fruit.kind, ['apple', 'banana'] as const)) { | ||
>isOneOf(fruit.kind, ['apple', 'banana'] as const) : boolean | ||
>isOneOf : <T, U extends T>(item: T, array: readonly U[]) => item is U | ||
>fruit.kind : "apple" | "banana" | "cherry" | ||
>fruit : { kind: "apple"; } | { kind: "banana"; } | { kind: "cherry"; } | ||
>kind : "apple" | "banana" | "cherry" | ||
>['apple', 'banana'] as const : readonly ["apple", "banana"] | ||
>['apple', 'banana'] : readonly ["apple", "banana"] | ||
>'apple' : "apple" | ||
>'banana' : "banana" | ||
|
||
fruit.kind | ||
>fruit.kind : "apple" | "banana" | ||
>fruit : { kind: "apple"; } | { kind: "banana"; } | ||
>kind : "apple" | "banana" | ||
|
||
fruit | ||
>fruit : { kind: "apple"; } | { kind: "banana"; } | ||
} | ||
|
||
declare const fruit2: { kind: 'apple'} | { kind: 'banana' } | { kind: 'cherry' } | ||
>fruit2 : { kind: 'apple'; } | { kind: 'banana'; } | { kind: 'cherry'; } | ||
>kind : "apple" | ||
>kind : "banana" | ||
>kind : "cherry" | ||
|
||
const kind = fruit2.kind; | ||
>kind : "apple" | "banana" | "cherry" | ||
>fruit2.kind : "apple" | "banana" | "cherry" | ||
>fruit2 : { kind: "apple"; } | { kind: "banana"; } | { kind: "cherry"; } | ||
>kind : "apple" | "banana" | "cherry" | ||
|
||
if (isOneOf(kind, ['apple', 'banana'] as const)) { | ||
>isOneOf(kind, ['apple', 'banana'] as const) : boolean | ||
>isOneOf : <T, U extends T>(item: T, array: readonly U[]) => item is U | ||
>kind : "apple" | "banana" | "cherry" | ||
>['apple', 'banana'] as const : readonly ["apple", "banana"] | ||
>['apple', 'banana'] : readonly ["apple", "banana"] | ||
>'apple' : "apple" | ||
>'banana' : "banana" | ||
|
||
fruit2.kind | ||
>fruit2.kind : "apple" | "banana" | ||
>fruit2 : { kind: "apple"; } | { kind: "banana"; } | ||
>kind : "apple" | "banana" | ||
|
||
fruit2 | ||
>fruit2 : { kind: "apple"; } | { kind: "banana"; } | ||
} |
17 changes: 17 additions & 0 deletions
17
tests/cases/compiler/typePredicatesCanNarrowByDiscriminant.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
// @strict: true | ||
|
||
// #45770 | ||
declare const fruit: { kind: 'apple'} | { kind: 'banana' } | { kind: 'cherry' } | ||
|
||
declare function isOneOf<T, U extends T>(item: T, array: readonly U[]): item is U | ||
if (isOneOf(fruit.kind, ['apple', 'banana'] as const)) { | ||
fruit.kind | ||
fruit | ||
} | ||
|
||
declare const fruit2: { kind: 'apple'} | { kind: 'banana' } | { kind: 'cherry' } | ||
const kind = fruit2.kind; | ||
if (isOneOf(kind, ['apple', 'banana'] as const)) { | ||
fruit2.kind | ||
fruit2 | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't it be better to
getDiscriminantPropertyAccess(argument, type)
so the argument only matches if it has an actual discriminable property? AFAIK,hasMatchingArgument
is only used in two places, so specializing it a bit more for this usecase is probably fine. TechnicallygetDiscriminantPropertyAccess
can also match binding patterns and identifiers aliasing discriminable properties, too, eg,There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We'll end up calling
getDiscriminantPropertyAccess
twice, but I'll try doing that.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You know what, at this rate, if we need to call
getDiscriminantPropertyAccess
to decide whether we should callnarrowTypeByTypePredicate
, which will then possibly callgetDiscriminantPropertyAccess
(and also callisMatchingReference
anyways), then we might as well just always callnarrowTypeByTypePredicate
? I'll try that and see if it affects performance.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, getting rid of the
hasMatchingArgument
insidenarrowTypeByCallExpression
has proved disastrous: it causes a ton of errors, which look completely unrelated to narrowing. From debugging the pyright errors listed by the user tests run, I think what happens is that, insidenarrowTypeByCallExpression
, if we callgetEffectsSignature(callExpression)
, we now go type checking a bunch of things, causing circularities that then cause a ton of errors.So it looks like we do need to protect the call to
getEffectsSignature(...)
with a check ofhasMatchingArgument(...)
, to avoid running into unnecessary circularities.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@weswigham I've switched to using
getCandidateDiscriminantPropertyAccess
for the above reasons.Can you take a look again?