Skip to content
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

Treat switch (typeof x) as exhaustive when all cases are handled for unconstrained type parameter #56877

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

Andarist
Copy link
Contributor

fixes #56786

@typescript-bot typescript-bot added the For Backlog Bug PRs that fix a backlog bug label Dec 26, 2023
@@ -37014,7 +37014,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (!witnesses) {
return false;
}
const operandConstraint = getBaseConstraintOrType(checkExpressionCached((node.expression as TypeOfExpression).expression));
const operandConstraint = getBaseConstraintOfType(checkExpressionCached((node.expression as TypeOfExpression).expression)) || unknownType;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that the constraint was already obtained here before so this doesn't really change that much. It just unifies how T and T extends unknown are handled here (switch (typeof x) was already treated as exhaustive for the latter).

On top of that, this is literally what getTypeFactsWorker (that is used later here) does as the very first thing. So this change doesn't even affect the other branch of the code in a meaningful way.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to stare at this diff for a good 10 seconds before I even realized it was calling a different function. 😵‍💫 They differ by only a single character (and r and f look quite similar at small font sizes) and it feels like it would be really easy to mix these up when reading the code.

@jakebailey
Copy link
Member

@typescript-bot test top200
@typescript-bot user test this
@typescript-bot run dt

@typescript-bot perf test this
@typescript-bot pack this

@typescript-bot
Copy link
Collaborator

typescript-bot commented Dec 26, 2023

Heya @jakebailey, I've started to run the diff-based user code test suite on this PR at 5adef66. You can monitor the build here.

Update: The results are in!

@typescript-bot
Copy link
Collaborator

typescript-bot commented Dec 26, 2023

Heya @jakebailey, I've started to run the regular perf test suite on this PR at 5adef66. You can monitor the build here.

Update: The results are in!

@typescript-bot
Copy link
Collaborator

typescript-bot commented Dec 26, 2023

Heya @jakebailey, I've started to run the diff-based top-repos suite on this PR at 5adef66. You can monitor the build here.

Update: The results are in!

@typescript-bot
Copy link
Collaborator

typescript-bot commented Dec 26, 2023

Heya @jakebailey, I've started to run the tarball bundle task on this PR at 5adef66. You can monitor the build here.

@typescript-bot
Copy link
Collaborator

typescript-bot commented Dec 26, 2023

Heya @jakebailey, I've started to run the parallelized Definitely Typed test suite on this PR at 5adef66. You can monitor the build here.

Update: The results are in!

@typescript-bot
Copy link
Collaborator

typescript-bot commented Dec 26, 2023

Hey @jakebailey, I've packed this into an installable tgz. You can install it for testing by referencing it in your package.json like so:

{
    "devDependencies": {
        "typescript": "https://typescript.visualstudio.com/cf7ac146-d525-443c-b23c-0d58337efebc/_apis/build/builds/159152/artifacts?artifactName=tgz&fileId=FCE35D880985516036A77088DD2A6B5394D157E5B8B09EC5A40095E62382641002&fileName=/typescript-5.4.0-insiders.20231226.tgz"
    }
}

and then running npm install.


There is also a playground for this build and an npm module you can use via "typescript": "npm:@typescript-deploys/pr-build@5.4.0-pr-56877-6".;

@typescript-bot
Copy link
Collaborator

@jakebailey
The results of the perf run you requested are in!

Here they are:

Compiler

Comparison Report - baseline..pr
Metric baseline pr Delta Best Worst p-value
Angular - node (v18.15.0, x64)
Memory used 295,449k (± 0.01%) 295,449k (± 0.01%) ~ 295,415k 295,497k p=0.688 n=6
Parse Time 2.65s (± 0.19%) 2.65s (± 0.19%) ~ 2.64s 2.65s p=1.000 n=6
Bind Time 0.82s (± 0.00%) 0.82s (± 0.00%) ~ 0.82s 0.82s p=1.000 n=6
Check Time 8.17s (± 0.32%) 8.16s (± 0.16%) ~ 8.14s 8.18s p=0.411 n=6
Emit Time 7.10s (± 0.25%) 7.11s (± 0.27%) ~ 7.08s 7.13s p=0.220 n=6
Total Time 18.73s (± 0.15%) 18.74s (± 0.07%) ~ 18.72s 18.75s p=0.684 n=6
Compiler-Unions - node (v18.15.0, x64)
Memory used 195,404k (± 1.53%) 194,443k (± 1.64%) ~ 191,522k 197,431k p=0.689 n=6
Parse Time 1.36s (± 1.50%) 1.34s (± 1.92%) ~ 1.32s 1.38s p=0.373 n=6
Bind Time 0.72s (± 0.00%) 0.72s (± 0.00%) ~ 0.72s 0.72s p=1.000 n=6
Check Time 9.26s (± 0.43%) 9.27s (± 0.39%) ~ 9.22s 9.32s p=0.748 n=6
Emit Time 2.61s (± 0.45%) 2.63s (± 0.37%) +0.02s (+ 0.89%) 2.62s 2.64s p=0.011 n=6
Total Time 13.94s (± 0.28%) 13.96s (± 0.28%) ~ 13.93s 14.04s p=0.418 n=6
Monaco - node (v18.15.0, x64)
Memory used 347,403k (± 0.00%) 347,392k (± 0.00%) ~ 347,377k 347,413k p=0.146 n=6
Parse Time 2.46s (± 0.36%) 2.45s (± 0.58%) ~ 2.43s 2.47s p=0.215 n=6
Bind Time 0.93s (± 1.58%) 0.92s (± 0.59%) ~ 0.92s 0.93s p=0.476 n=6
Check Time 6.89s (± 0.37%) 6.86s (± 0.33%) ~ 6.83s 6.90s p=0.140 n=6
Emit Time 4.05s (± 0.43%) 4.05s (± 0.36%) ~ 4.04s 4.07s p=1.000 n=6
Total Time 14.33s (± 0.26%) 14.29s (± 0.14%) ~ 14.26s 14.30s p=0.050 n=6
TFS - node (v18.15.0, x64)
Memory used 302,724k (± 0.01%) 302,710k (± 0.00%) ~ 302,696k 302,733k p=0.336 n=6
Parse Time 2.00s (± 0.77%) 2.00s (± 1.13%) ~ 1.96s 2.02s p=0.935 n=6
Bind Time 1.00s (± 0.83%) 1.01s (± 1.25%) ~ 0.99s 1.02s p=0.445 n=6
Check Time 6.28s (± 0.22%) 6.28s (± 0.27%) ~ 6.25s 6.30s p=0.864 n=6
Emit Time 3.57s (± 0.31%) 3.57s (± 0.48%) ~ 3.55s 3.59s p=0.865 n=6
Total Time 12.86s (± 0.16%) 12.85s (± 0.21%) ~ 12.84s 12.91s p=0.622 n=6
material-ui - node (v18.15.0, x64)
Memory used 506,810k (± 0.01%) 506,850k (± 0.00%) +40k (+ 0.01%) 506,829k 506,865k p=0.030 n=6
Parse Time 2.58s (± 0.45%) 2.58s (± 0.60%) ~ 2.57s 2.61s p=1.000 n=6
Bind Time 0.99s (± 1.87%) 0.99s (± 0.64%) ~ 0.98s 1.00s p=1.000 n=6
Check Time 16.96s (± 0.30%) 16.95s (± 0.58%) ~ 16.86s 17.14s p=0.573 n=6
Emit Time 0.00s (± 0.00%) 0.00s (± 0.00%) ~ 0.00s 0.00s p=1.000 n=6
Total Time 20.53s (± 0.30%) 20.52s (± 0.47%) ~ 20.42s 20.70s p=0.748 n=6
xstate - node (v18.15.0, x64)
Memory used 512,881k (± 0.01%) 512,915k (± 0.01%) ~ 512,854k 512,995k p=0.471 n=6
Parse Time 3.27s (± 0.32%) 3.28s (± 0.25%) ~ 3.27s 3.29s p=0.796 n=6
Bind Time 1.54s (± 0.58%) 1.54s (± 0.36%) ~ 1.53s 1.54s p=0.341 n=6
Check Time 2.84s (± 0.79%) 2.83s (± 0.37%) ~ 2.81s 2.84s p=0.803 n=6
Emit Time 0.07s (± 5.69%) 0.07s (± 0.00%) ~ 0.07s 0.07s p=0.405 n=6
Total Time 7.72s (± 0.28%) 7.71s (± 0.20%) ~ 7.69s 7.73s p=0.467 n=6
System info unknown
Hosts
  • node (v18.15.0, x64)
Scenarios
  • Angular - node (v18.15.0, x64)
  • Compiler-Unions - node (v18.15.0, x64)
  • Monaco - node (v18.15.0, x64)
  • TFS - node (v18.15.0, x64)
  • material-ui - node (v18.15.0, x64)
  • xstate - node (v18.15.0, x64)
Benchmark Name Iterations
Current pr 6
Baseline baseline 6

tsserver

Comparison Report - baseline..pr
Metric baseline pr Delta Best Worst p-value
Compiler-UnionsTSServer - node (v18.15.0, x64)
Req 1 - updateOpen 2,361ms (± 0.89%) 2,340ms (± 0.68%) ~ 2,325ms 2,370ms p=0.128 n=6
Req 2 - geterr 5,417ms (± 1.20%) 5,533ms (± 0.91%) +116ms (+ 2.15%) 5,446ms 5,572ms p=0.013 n=6
Req 3 - references 323ms (± 0.53%) 325ms (± 0.37%) +3ms (+ 0.77%) 324ms 327ms p=0.023 n=6
Req 4 - navto 279ms (± 0.81%) 275ms (± 0.96%) ~ 273ms 280ms p=0.085 n=6
Req 5 - completionInfo count 1,356 (± 0.00%) 1,356 (± 0.00%) ~ 1,356 1,356 p=1.000 n=6
Req 5 - completionInfo 84ms (± 4.79%) 92ms (± 1.34%) 🔻+8ms (+ 9.15%) 89ms 92ms p=0.015 n=6
CompilerTSServer - node (v18.15.0, x64)
Req 1 - updateOpen 2,460ms (± 0.87%) 2,475ms (± 0.55%) ~ 2,460ms 2,495ms p=0.295 n=6
Req 2 - geterr 4,156ms (± 1.82%) 4,137ms (± 1.65%) ~ 4,086ms 4,244ms p=1.000 n=6
Req 3 - references 337ms (± 0.29%) 340ms (± 1.43%) ~ 335ms 346ms p=0.287 n=6
Req 4 - navto 286ms (± 1.20%) 285ms (± 0.90%) ~ 283ms 290ms p=0.677 n=6
Req 5 - completionInfo count 1,518 (± 0.00%) 1,518 (± 0.00%) ~ 1,518 1,518 p=1.000 n=6
Req 5 - completionInfo 83ms (± 6.84%) 85ms (± 6.53%) ~ 78ms 89ms p=0.735 n=6
xstateTSServer - node (v18.15.0, x64)
Req 1 - updateOpen 2,606ms (± 0.67%) 2,601ms (± 0.41%) ~ 2,584ms 2,613ms p=0.378 n=6
Req 2 - geterr 1,718ms (± 2.45%) 1,688ms (± 1.65%) ~ 1,665ms 1,735ms p=0.092 n=6
Req 3 - references 115ms (± 8.74%) 110ms (± 9.56%) ~ 102ms 123ms p=0.503 n=6
Req 4 - navto 365ms (± 0.32%) 366ms (± 0.21%) ~ 365ms 367ms p=0.279 n=6
Req 5 - completionInfo count 2,073 (± 0.00%) 2,073 (± 0.00%) ~ 2,073 2,073 p=1.000 n=6
Req 5 - completionInfo 308ms (± 2.17%) 306ms (± 1.92%) ~ 296ms 314ms p=0.688 n=6
System info unknown
Hosts
  • node (v18.15.0, x64)
Scenarios
  • CompilerTSServer - node (v18.15.0, x64)
  • Compiler-UnionsTSServer - node (v18.15.0, x64)
  • xstateTSServer - node (v18.15.0, x64)
Benchmark Name Iterations
Current pr 6
Baseline baseline 6

Startup

Comparison Report - baseline..pr
Metric baseline pr Delta Best Worst p-value
tsc-startup - node (v18.15.0, x64)
Execution time 153.21ms (± 0.22%) 153.23ms (± 0.21%) ~ 152.12ms 156.76ms p=0.629 n=600
tsserver-startup - node (v18.15.0, x64)
Execution time 228.43ms (± 0.16%) 228.19ms (± 0.15%) -0.24ms (- 0.11%) 226.70ms 232.36ms p=0.000 n=600
tsserverlibrary-startup - node (v18.15.0, x64)
Execution time 229.85ms (± 0.17%) 229.82ms (± 0.20%) ~ 227.92ms 240.42ms p=0.239 n=600
typescript-startup - node (v18.15.0, x64)
Execution time 230.29ms (± 0.23%) 230.23ms (± 0.19%) ~ 228.67ms 236.35ms p=0.490 n=600
System info unknown
Hosts
  • node (v18.15.0, x64)
Scenarios
  • tsc-startup - node (v18.15.0, x64)
  • tsserver-startup - node (v18.15.0, x64)
  • tsserverlibrary-startup - node (v18.15.0, x64)
  • typescript-startup - node (v18.15.0, x64)
Benchmark Name Iterations
Current pr 6
Baseline baseline 6

Developer Information:

Download Benchmarks

@typescript-bot
Copy link
Collaborator

@jakebailey Here are the results of running the user test suite comparing main and refs/pull/56877/merge:

There were infrastructure failures potentially unrelated to your change:

  • 1 instance of "Package install failed"

Otherwise...

Something interesting changed - please have a look.

Details

puppeteer

packages/browsers/test/src/tsconfig.json

@typescript-bot
Copy link
Collaborator

Hey @jakebailey, the results of running the DT tests are ready.
Everything looks the same!
You can check the log here.

@typescript-bot
Copy link
Collaborator

@jakebailey Here are the results of running the top-repos suite comparing main and refs/pull/56877/merge:

Everything looks good!

@gabritto gabritto self-assigned this Jan 2, 2024
@gabritto gabritto self-requested a review January 2, 2024 19:44
@@ -37014,7 +37014,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (!witnesses) {
return false;
}
const operandConstraint = getBaseConstraintOrType(checkExpressionCached((node.expression as TypeOfExpression).expression));
const operandConstraint = getBaseConstraintOfType(checkExpressionCached((node.expression as TypeOfExpression).expression)) || unknownType;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change as is breaks the following case:

// error on return type because switch is not seen as exhaustive
function woo(x: string): number {
    switch (typeof x) {
        case "string":
            return 1;
    }
}

The problem is that getBaseConstraintOfType returns undefined for primitive types like string, in addition to returning undefined for an unconstrained type parameter, which is why we use getBaseConstraintOrType currently.
I think you might need to explicitly check the unconstrained type parameter case and use unknown as the operand constraint then.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch 👍 I pushed out a fix for this - could you take a look?

@Andarist Andarist requested a review from gabritto January 2, 2024 21:37
@@ -37014,7 +37014,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (!witnesses) {
return false;
}
const operandConstraint = getBaseConstraintOrType(checkExpressionCached((node.expression as TypeOfExpression).expression));
const type = checkExpressionCached((node.expression as TypeOfExpression).expression);
const operandConstraint = type.flags & (TypeFlags.Intersection | TypeFlags.Instantiable) ?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we want to do this when the type is intersection or instantiable?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doing this for instantiables shouldn't fall into the trap that you have mentioned (with primitive types). I'm not sure about intersections though, I just followed the same logic that I found at the top of getTypeFactsWorker.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see.
I was going to say I don't know exactly if this is correct, but I realized there might be a simpler way to get what we want:
we can always say that the typeof switch is exhaustive if it has all possible typeof strings, regardless of the operand constraint.
So I think we can leave this all unchanged and just check if (TypeFacts.AllTypeofNE & notEqualFacts) === TypeFacts.AllTypeofNE is true, and if it is, we can return true (and otherwise we can do what we were already doing).

Copy link
Member

@gabritto gabritto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
For Backlog Bug PRs that fix a backlog bug
Projects
Status: Waiting on author
Development

Successfully merging this pull request may close these issues.

switch (typeof something) isn't seen as exhaustive
5 participants