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

Preserve type refinements in closures created past last assignment #56908

Merged
merged 14 commits into from
Jan 9, 2024

Conversation

ahejlsberg
Copy link
Member

@ahejlsberg ahejlsberg commented Dec 31, 2023

We currently preserve type refinements in closures for const variables and parameters that are never targeted in assignments. With this PR we also preserve type refinements for parameters, local let variables, and catch clause variables in closures that are created past the last assignment to those parameters or variables. For example:

declare function action(cb: () => void): void;

function f1() {
    let x: string | number;
    x = "abc";
    action(() => { x });  // x has type string | number
    x = 42;
    action(() => { x });  // x has type number
}

Above, it is unknown when (or even if) the action function will invoke a callback function previously passed to it. Thus, when the first arrow function is created, it is unknown whether it will be invoked with x having type string or number because there are subsequent assignments to x. However, the second arrow function is created past the last assignment to x, so x can safely be narrowed to type number in that arrow function.

A similar example that adjusts an argument value before using it in a closure:

function makeAdder(n?: number) {
    n ??= 0;
    return (m: number) => n + m;  // Now ok, previously was error
}

Type refinements are not preserved in inner function and class declarations (due to hoisting):

function f2() {
    let x: string | number;
    x = 42;
    let a = () => { x /* number */ };
    let f = function() { x /* number */ };
    let C = class {
        foo() { x /* number */ }
    };
    let o = {
        foo() { x /* number */ }
    };
    function g() { x /* string | number */ }
    class A {
        foo() { x /* string | number */ }
    }
}

Implicit any variables have a known type following the last assignment:

function f3() {
    let x;
    x = "abc";
    action(() => { x });  // Implicit any error
    x = 42;
    action(() => { x /* number */ });
}

Type refinements for catch variables are preserved past the last assignment (if any):

function f4() {
    try {
    }
    catch (e) {
        if (e instanceof Error) {
            let f = () => { e /* Error */ }
        }
    }
}

Note that effects of assignments in compound statements extend to the entire statement:

function f5(cond: boolean) {
    let x: number | undefined;
    if (cond) {
        x = 1;
        action(() => { x /* number | undefined */ });
    }
    else {
        x = 2;
        action(() => { x /* number | undefined */ });
    }
    action(() => { x /* number */ });
}

Above, the type of x is narrowed to number only in the last arrow function, even though it would be safe to narrow in the other two arrow functions. That, however, would require full control flow analysis which is significantly more complex and expensive than the single pass we do now.

This PR fixes multiple issues that have previously been attributed to #9998.

Fixes #13142.
Fixes #13560.
Fixes #13572.
Fixes #14748.
Fixes #16285.
Fixes #17240.
Fixes #17449.
Fixes #19606.
Fixes #19683.
Fixes #19698.
Fixes #19918.
Fixes #22120.
Fixes #22635.
Fixes #23776.
Fixes #29392.
Fixes #29916.
Fixes #31266.
Fixes #32625.
Fixes #33319.
Fixes #34669.
Fixes #35124.
Fixes #37339.
Fixes #38755.
Fixes #40202.
Fixes #43827.
Fixes #44218.
Fixes #46118.
Fixes #50580.
Fixes #52104.
Fixes #55528.
Fixes #56854.
Fixes #56973.

@ahejlsberg
Copy link
Member Author

@typescript-bot test top100
@typescript-bot user test this
@typescript-bot run dt
@typescript-bot perf test this faster

@typescript-bot
Copy link
Collaborator

typescript-bot commented Dec 31, 2023

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

Update: The results are in!

@typescript-bot
Copy link
Collaborator

typescript-bot commented Dec 31, 2023

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

Update: The results are in!

@typescript-bot
Copy link
Collaborator

typescript-bot commented Dec 31, 2023

Heya @ahejlsberg, I've started to run the tsc-only perf test suite on this PR at 2739399. You can monitor the build here.

Update: The results are in!

@typescript-bot
Copy link
Collaborator

typescript-bot commented Dec 31, 2023

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

Update: The results are in!

@typescript-bot
Copy link
Collaborator

@ahejlsberg
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,445k (± 0.02%) 295,482k (± 0.01%) ~ 295,441k 295,536k p=0.128 n=6
Parse Time 2.65s (± 0.19%) 2.65s (± 0.21%) ~ 2.64s 2.65s p=0.640 n=6
Bind Time 0.83s (± 0.66%) 0.82s (± 0.00%) ~ 0.82s 0.82s p=0.071 n=6
Check Time 8.15s (± 0.20%) 8.17s (± 0.30%) ~ 8.15s 8.21s p=0.250 n=6
Emit Time 7.10s (± 0.21%) 7.10s (± 0.27%) ~ 7.08s 7.12s p=1.000 n=6
Total Time 18.72s (± 0.14%) 18.74s (± 0.18%) ~ 18.70s 18.80s p=0.413 n=6
Compiler-Unions - node (v18.15.0, x64)
Memory used 192,499k (± 1.23%) 192,488k (± 1.26%) ~ 191,473k 197,449k p=0.230 n=6
Parse Time 1.34s (± 0.91%) 1.35s (± 0.38%) ~ 1.35s 1.36s p=0.114 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.52%) 9.30s (± 0.50%) ~ 9.23s 9.35s p=0.147 n=6
Emit Time 2.61s (± 0.67%) 2.62s (± 0.76%) ~ 2.59s 2.64s p=0.669 n=6
Total Time 13.94s (± 0.39%) 13.99s (± 0.37%) ~ 13.91s 14.04s p=0.145 n=6
Monaco - node (v18.15.0, x64)
Memory used 347,397k (± 0.00%) 347,398k (± 0.01%) ~ 347,364k 347,415k p=0.688 n=6
Parse Time 2.46s (± 0.31%) 2.46s (± 0.49%) ~ 2.44s 2.47s p=0.867 n=6
Bind Time 0.92s (± 0.56%) 0.93s (± 0.81%) ~ 0.92s 0.94s p=0.247 n=6
Check Time 6.85s (± 0.29%) 6.88s (± 0.34%) ~ 6.85s 6.92s p=0.061 n=6
Emit Time 4.06s (± 0.57%) 4.05s (± 0.24%) ~ 4.04s 4.06s p=0.316 n=6
Total Time 14.30s (± 0.23%) 14.32s (± 0.22%) ~ 14.29s 14.38s p=0.466 n=6
TFS - node (v18.15.0, x64)
Memory used 302,730k (± 0.00%) 302,752k (± 0.01%) +22k (+ 0.01%) 302,730k 302,778k p=0.020 n=6
Parse Time 2.00s (± 1.43%) 1.98s (± 1.30%) ~ 1.95s 2.02s p=0.373 n=6
Bind Time 1.00s (± 1.17%) 1.00s (± 1.26%) ~ 0.99s 1.02s p=0.801 n=6
Check Time 6.30s (± 0.31%) 6.30s (± 0.13%) ~ 6.29s 6.31s p=0.806 n=6
Emit Time 3.58s (± 0.42%) 3.59s (± 0.34%) ~ 3.57s 3.60s p=0.805 n=6
Total Time 12.88s (± 0.28%) 12.87s (± 0.16%) ~ 12.84s 12.90s p=0.517 n=6
material-ui - node (v18.15.0, x64)
Memory used 506,824k (± 0.00%) 506,839k (± 0.00%) ~ 506,821k 506,856k p=0.298 n=6
Parse Time 2.57s (± 0.16%) 2.59s (± 0.55%) +0.02s (+ 0.71%) 2.57s 2.61s p=0.021 n=6
Bind Time 0.99s (± 0.90%) 0.99s (± 0.55%) ~ 0.99s 1.00s p=0.341 n=6
Check Time 16.87s (± 0.17%) 16.99s (± 0.35%) +0.12s (+ 0.72%) 16.92s 17.06s p=0.005 n=6
Emit Time 0.00s (± 0.00%) 0.00s (± 0.00%) ~ 0.00s 0.00s p=1.000 n=6
Total Time 20.43s (± 0.15%) 20.58s (± 0.29%) +0.14s (+ 0.70%) 20.51s 20.66s p=0.005 n=6
xstate - node (v18.15.0, x64)
Memory used 512,828k (± 0.01%) 512,920k (± 0.01%) +92k (+ 0.02%) 512,836k 512,988k p=0.031 n=6
Parse Time 3.27s (± 0.23%) 3.27s (± 0.32%) ~ 3.26s 3.29s p=0.931 n=6
Bind Time 1.54s (± 0.53%) 1.54s (± 0.41%) ~ 1.53s 1.55s p=0.432 n=6
Check Time 2.82s (± 0.82%) 2.84s (± 0.62%) ~ 2.82s 2.87s p=0.324 n=6
Emit Time 0.07s (± 0.00%) 0.07s (± 5.69%) ~ 0.07s 0.08s p=0.405 n=6
Total Time 7.69s (± 0.22%) 7.73s (± 0.25%) +0.04s (+ 0.48%) 7.70s 7.75s p=0.013 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

Developer Information:

Download Benchmarks

@typescript-bot
Copy link
Collaborator

@ahejlsberg Here are the results of running the user test suite comparing main and refs/pull/56908/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

adonis-framework

/mnt/ts_downloads/adonis-framework/tsconfig.json

  • [MISSING] error TS18048: 'avoid' is possibly 'undefined'.
    • /mnt/ts_downloads/adonis-framework/node_modules/adonis-framework/src/View/Form/index.js(75,11)

async

/mnt/ts_downloads/async/tsconfig.json

  • [MISSING] error TS2722: Cannot invoke an object which is possibly 'undefined'.
    • /mnt/ts_downloads/async/node_modules/async/dist/async.js(2524,9)
    • /mnt/ts_downloads/async/node_modules/async/dist/async.js(2666,16)
    • /mnt/ts_downloads/async/node_modules/async/dist/async.js(3022,25)
    • /mnt/ts_downloads/async/node_modules/async/dist/async.js(3567,16)
    • /mnt/ts_downloads/async/node_modules/async/dist/async.js(3691,9)
    • /mnt/ts_downloads/async/node_modules/async/dist/async.js(5107,9)
    • /mnt/ts_downloads/async/node_modules/async/dist/async.js(5163,9)
    • /mnt/ts_downloads/async/node_modules/async/dist/async.js(5225,25)
  • [MISSING] error TS2684: The 'this' context of type 'Function | undefined' is not assignable to method's 'this' of type 'Function'.
    • /mnt/ts_downloads/async/node_modules/async/dist/async.js(3025,9)
    • /mnt/ts_downloads/async/node_modules/async/dist/async.js(5228,9)
  • [MISSING] error TS18048: 'callback' is possibly 'undefined'.
    • /mnt/ts_downloads/async/node_modules/async/dist/async.js(3025,9)
    • /mnt/ts_downloads/async/node_modules/async/dist/async.js(5228,9)

lodash

/mnt/ts_downloads/lodash/tsconfig.json

  • [NEW] error TS2345: Argument of type '{ cap?: boolean | undefined; curry?: boolean | undefined; fixed?: boolean | undefined; immutable?: boolean | undefined; rearg?: boolean | undefined; }' is not assignable to parameter of type 'Function'.
    • /mnt/ts_downloads/lodash/node_modules/lodash/fp/_baseConvert.js(262,57)
  • [MISSING] error TS18048: 'start' is possibly 'undefined'.
    • /mnt/ts_downloads/lodash/node_modules/lodash/_overRest.js(20,42)
    • /mnt/ts_downloads/lodash/node_modules/lodash/_overRest.js(24,27)
    • /mnt/ts_downloads/lodash/node_modules/lodash/_overRest.js(27,27)
    • /mnt/ts_downloads/lodash/node_modules/lodash/_overRest.js(28,22)
    • /mnt/ts_downloads/lodash/node_modules/lodash/core.js(1450,44)
    • /mnt/ts_downloads/lodash/node_modules/lodash/core.js(1454,29)
    • /mnt/ts_downloads/lodash/node_modules/lodash/core.js(1457,29)
    • /mnt/ts_downloads/lodash/node_modules/lodash/core.js(1458,24)
  • [MISSING] error TS2538: Type 'undefined' cannot be used as an index type.
    • /mnt/ts_downloads/lodash/node_modules/lodash/_overRest.js(31,15)
    • /mnt/ts_downloads/lodash/node_modules/lodash/core.js(1461,17)
    • /mnt/ts_downloads/lodash/node_modules/lodash/spread.js(53,22)
  • [MISSING] error TS2345: Argument of type '{ cap?: boolean | undefined; curry?: boolean | undefined; fixed?: boolean | undefined; immutable?: boolean | undefined; rearg?: boolean | undefined; } | undefined' is not assignable to parameter of type 'Function'.
    • /mnt/ts_downloads/lodash/node_modules/lodash/fp/_baseConvert.js(262,57)
  • [MISSING] error TS2722: Cannot invoke an object which is possibly 'undefined'.
    • /mnt/ts_downloads/lodash/node_modules/lodash/mapKeys.js(31,29)
    • /mnt/ts_downloads/lodash/node_modules/lodash/mapValues.js(38,34)
    • /mnt/ts_downloads/lodash/node_modules/lodash/pickBy.js(33,12)
    • /mnt/ts_downloads/lodash/node_modules/lodash/transform.js(60,12)
  • [MISSING] error TS2345: Argument of type 'number | undefined' is not assignable to parameter of type 'number'.
    • /mnt/ts_downloads/lodash/node_modules/lodash/nthArg.js(28,26)
  • [MISSING] error TS18048: 'string' is possibly 'undefined'.
    • /mnt/ts_downloads/lodash/node_modules/lodash/template.js(199,15)

puppeteer

packages/browsers/test/src/tsconfig.json

webpack

tsconfig.json

  • [NEW] error TS2367: This comparison appears to be unintentional because the types 'boolean' and 'RuntimeSpec' have no overlap.

tsconfig.types.json

@typescript-bot
Copy link
Collaborator

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

@typescript-bot
Copy link
Collaborator

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

Something interesting changed - please have a look.

Details

quilljs/quill

1 of 2 projects failed to build with the old tsc and were ignored

packages/quill/tsconfig.json

@@ -187,15 +183,9 @@ controlFlowAliasing.ts(280,5): error TS2454: Variable 'a' is used before being a
const isFoo = obj.kind === 'foo';
if (isFoo) {
obj.foo; // Not narrowed because obj is mutable
~~~
!!! error TS2339: Property 'foo' does not exist on type '{ kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }'.
!!! error TS2339: Property 'foo' does not exist on type '{ kind: "bar"; bar: number; }'.
}
else {
obj.bar; // Not narrowed because obj is mutable

Choose a reason for hiding this comment

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

I guess these comments are outdated now

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, I'll fix those.

@Andarist
Copy link
Contributor

Andarist commented Dec 31, 2023

Shouldn't the assigned type be preserved at least in the second branch?

declare function action(cb: () => void): void

function test(arg: string | number | undefined) {
    if (arg === undefined) {
        arg = 'foo'
        action(() => {
            arg
            // ^?
        })
    } else {
        arg = 42
        action(() => {
            arg
            // ^?
        })
    }
}

@ahejlsberg
Copy link
Member Author

Shouldn't the assigned type be preserved at least in the second branch?

Ideally, the assigned type would be preserved in both branches since the closures are both created following the last assignment. That, however, would require full control flow analysis which is significantly more complex and expensive than the single pass we do now. The next best alternative, at least in my opinion, is to extend assignment effects in compound statements to the entire statement. That's what the PR currently does. It is a bit more conservative than just using the lexical position of the last assignment, but it is more consistent.

@Andarist
Copy link
Contributor

Ideally, the assigned type would be preserved in both branches since the closures are both created following the last assignment.

Yeah, that would - indeed - be the dream :P

I haven't spent much time so far understanding how CFA is implemented. Is it always an append-only graph that is attached to nodes? In other words, is the complexity here that we can't easily peek into flow nodes that follow a specific node aka it's not easy to assess if the control flow is joined with another branch later on?

@ahejlsberg
Copy link
Member Author

ahejlsberg commented Dec 31, 2023

In other words, is the complexity here that we can't easily peek into flow nodes that follow a specific node

Right, the control flow graph is a "reverse linked" graph optimized for exploring execution effects that occurred prior to reaching a specific node. It is less well suited for exploring execution effects that occur after reaching a node.

The most similar scenario we currently handle in the type checker is definite property initialization in constructors (the isPropertyInitializedInConstructor function). For that, we construct a "fake" return control flow node that reflects all possible points of exit from the constructor. By pretending to access a property at that imaginary location we can ensure there are no surviving and unintended undefined values. One could imagine a similar control flow graph walk to ensure that every exiting control flow that passed through the creation of a particular closure has no subsequent assignments to a particular variable. It's possible to do, but it would definitely have significant performance effects.

@ahejlsberg
Copy link
Member Author

@typescript-bot perf test this faster

@typescript-bot
Copy link
Collaborator

typescript-bot commented Dec 31, 2023

Heya @ahejlsberg, I've started to run the tsc-only perf test suite on this PR at a490ef4. You can monitor the build here.

Update: The results are in!

@ahejlsberg
Copy link
Member Author

@typescript-bot pack this

@typescript-bot
Copy link
Collaborator

typescript-bot commented Dec 31, 2023

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

@typescript-bot
Copy link
Collaborator

typescript-bot commented Dec 31, 2023

Hey @ahejlsberg, 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/159175/artifacts?artifactName=tgz&fileId=73C87748830547051A361376E39D0B51FE439C89A3DA965ABEFFB4C6076F529602&fileName=/typescript-5.4.0-insiders.20231231.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/[email protected]".;

@typescript-bot
Copy link
Collaborator

@ahejlsberg
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,443k (± 0.01%) 295,482k (± 0.01%) ~ 295,426k 295,530k p=0.109 n=6
Parse Time 2.65s (± 0.15%) 2.65s (± 0.28%) ~ 2.64s 2.66s p=1.000 n=6
Bind Time 0.82s (± 0.99%) 0.82s (± 0.00%) ~ 0.82s 0.82s p=0.405 n=6
Check Time 8.16s (± 0.24%) 8.17s (± 0.29%) ~ 8.14s 8.20s p=0.569 n=6
Emit Time 7.10s (± 0.19%) 7.09s (± 0.27%) ~ 7.07s 7.12s p=0.510 n=6
Total Time 18.73s (± 0.16%) 18.73s (± 0.16%) ~ 18.68s 18.75s p=0.935 n=6
Compiler-Unions - node (v18.15.0, x64)
Memory used 191,501k (± 0.02%) 192,462k (± 1.25%) ~ 191,440k 197,374k p=0.810 n=6
Parse Time 1.34s (± 0.99%) 1.36s (± 1.72%) ~ 1.32s 1.39s p=0.250 n=6
Bind Time 0.72s (± 0.00%) 0.72s (± 0.00%) ~ 0.72s 0.72s p=1.000 n=6
Check Time 9.28s (± 0.32%) 9.30s (± 0.42%) ~ 9.24s 9.35s p=0.333 n=6
Emit Time 2.62s (± 0.45%) 2.62s (± 0.66%) ~ 2.59s 2.64s p=0.933 n=6
Total Time 13.97s (± 0.21%) 14.00s (± 0.29%) ~ 13.95s 14.06s p=0.109 n=6
Monaco - node (v18.15.0, x64)
Memory used 347,405k (± 0.00%) 347,406k (± 0.01%) ~ 347,383k 347,428k p=1.000 n=6
Parse Time 2.46s (± 0.68%) 2.46s (± 0.76%) ~ 2.44s 2.49s p=0.675 n=6
Bind Time 0.92s (± 0.59%) 0.92s (± 0.59%) ~ 0.92s 0.93s p=1.000 n=6
Check Time 6.88s (± 0.40%) 6.89s (± 0.52%) ~ 6.86s 6.94s p=0.746 n=6
Emit Time 4.04s (± 0.40%) 4.05s (± 0.40%) ~ 4.03s 4.08s p=0.121 n=6
Total Time 14.31s (± 0.21%) 14.33s (± 0.30%) ~ 14.28s 14.37s p=0.282 n=6
TFS - node (v18.15.0, x64)
Memory used 302,726k (± 0.01%) 302,743k (± 0.00%) ~ 302,730k 302,761k p=0.065 n=6
Parse Time 1.99s (± 1.10%) 2.00s (± 0.86%) ~ 1.98s 2.03s p=0.511 n=6
Bind Time 1.00s (± 0.83%) 1.00s (± 0.81%) ~ 1.00s 1.02s p=0.673 n=6
Check Time 6.29s (± 0.73%) 6.29s (± 0.31%) ~ 6.26s 6.31s p=0.466 n=6
Emit Time 3.57s (± 0.50%) 3.59s (± 0.78%) ~ 3.57s 3.64s p=0.139 n=6
Total Time 12.86s (± 0.47%) 12.89s (± 0.35%) ~ 12.81s 12.94s p=0.335 n=6
material-ui - node (v18.15.0, x64)
Memory used 506,810k (± 0.01%) 506,843k (± 0.00%) +33k (+ 0.01%) 506,830k 506,871k p=0.045 n=6
Parse Time 2.60s (± 0.62%) 2.58s (± 0.29%) -0.02s (- 0.77%) 2.57s 2.59s p=0.032 n=6
Bind Time 0.99s (± 1.05%) 1.00s (± 1.21%) ~ 0.98s 1.01s p=0.868 n=6
Check Time 16.91s (± 0.32%) 16.96s (± 0.38%) ~ 16.89s 17.08s p=0.227 n=6
Emit Time 0.00s (± 0.00%) 0.00s (± 0.00%) ~ 0.00s 0.00s p=1.000 n=6
Total Time 20.51s (± 0.31%) 20.54s (± 0.34%) ~ 20.44s 20.65s p=0.521 n=6
xstate - node (v18.15.0, x64)
Memory used 512,877k (± 0.01%) 512,951k (± 0.01%) +74k (+ 0.01%) 512,874k 513,024k p=0.045 n=6
Parse Time 3.27s (± 0.39%) 3.27s (± 0.19%) ~ 3.26s 3.28s p=0.799 n=6
Bind Time 1.54s (± 0.41%) 1.54s (± 0.34%) ~ 1.53s 1.54s p=0.386 n=6
Check Time 2.82s (± 0.52%) 2.83s (± 0.63%) ~ 2.80s 2.85s p=0.370 n=6
Emit Time 0.07s (± 0.00%) 0.07s (± 5.69%) ~ 0.07s 0.08s p=0.405 n=6
Total Time 7.70s (± 0.14%) 7.72s (± 0.23%) ~ 7.69s 7.74s p=0.107 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

Developer Information:

Download Benchmarks

@ahejlsberg
Copy link
Member Author

ahejlsberg commented Jan 8, 2024

This definitely looks like an improvement, though it seems somewhat spooky for x.a to still be number. But that's nothing new.

The change with the latest commits is that we don't preserve type refinements for mutable global variables in closures since such globals might be modified by code in other files or modules.

The issue with the spooky narrowing of x.a is really completely orthogonal. Since properties, including discriminant properties, are mutable, it technically isn't safe to narrow discriminated unions because the discriminants might be mutated. But obviously discriminants are rarely mutated in practice, and the narrowing is hugely desirable.

@jakebailey
Copy link
Member

@typescript-bot perf test public

@typescript-bot
Copy link
Collaborator

typescript-bot commented Jan 8, 2024

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

Update: The results are in!

@typescript-bot
Copy link
Collaborator

@jakebailey, the perf run you requested failed. You can check the log here.

@jakebailey
Copy link
Member

This PR appears to conflict with main and needs a merge and fix:

src/compiler/checker.ts:479:5 - error TS2305: Module '"./_namespaces/ts"' has no exported member 'isCatchClauseVariableDeclaration'.

479     isCatchClauseVariableDeclaration,
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


Found 1 error.

@jakebailey
Copy link
Member

@typescript-bot perf test public

@typescript-bot
Copy link
Collaborator

typescript-bot commented Jan 9, 2024

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

Update: The results are in!

case SyntaxKind.SwitchStatement:
case SyntaxKind.TryStatement:
case SyntaxKind.ClassDeclaration:
pos = node.end;
Copy link
Member

@DanielRosenwasser DanielRosenwasser Jan 9, 2024

Choose a reason for hiding this comment

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

I don't know if it's worth noting that LabeledStatement isn't covered here. I don't think that it really matters because its end position should be identical to any of the other cases.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, no special handling needed for LabeledStatement.

@DanielRosenwasser
Copy link
Member

Let's wait for the perf results before merging.

@typescript-bot
Copy link
Collaborator

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

Here they are:

tsc

Comparison Report - baseline..pr
Metric baseline pr Delta Best Worst p-value
self-build-src - node (v20.5.1, x64)
Memory used 2,573,421k (± 0.01%) 2,681,290k (± 5.13%) 🔻+107,869k (+ 4.19%) 2,573,861k 2,921,130k p=0.005 n=6
Parse Time 5.00s (± 0.48%) 5.02s (± 0.99%) ~ 4.96s 5.07s p=0.575 n=6
Bind Time 1.99s (± 0.45%) 1.99s (± 0.83%) ~ 1.97s 2.00s p=0.462 n=6
Check Time 32.11s (± 0.22%) 31.97s (± 0.47%) ~ 31.75s 32.15s p=0.199 n=6
Emit Time 2.83s (± 1.89%) 2.82s (± 3.98%) ~ 2.67s 2.96s p=0.810 n=6
Total Time 41.95s (± 0.31%) 41.81s (± 0.20%) ~ 41.73s 41.92s p=0.065 n=6
self-compiler - node (v20.5.1, x64)
Memory used 418,760k (± 0.02%) 418,817k (± 0.01%) ~ 418,749k 418,880k p=0.471 n=6
Parse Time 2.88s (± 1.09%) 2.88s (± 0.38%) ~ 2.87s 2.90s p=0.620 n=6
Bind Time 1.14s (± 1.03%) 1.14s (± 0.36%) ~ 1.13s 1.14s p=0.584 n=6
Check Time 14.09s (± 0.34%) 14.07s (± 0.38%) ~ 13.99s 14.14s p=0.748 n=6
Emit Time 1.05s (± 1.16%) 1.05s (± 1.84%) ~ 1.03s 1.08s p=0.797 n=6
Total Time 19.15s (± 0.27%) 19.14s (± 0.33%) ~ 19.04s 19.22s p=0.688 n=6
vscode - node (v20.5.1, x64)
Memory used 2,827,969k (± 0.00%) 2,828,011k (± 0.00%) ~ 2,827,971k 2,828,065k p=0.128 n=6
Parse Time 10.72s (± 0.15%) 10.73s (± 0.15%) ~ 10.71s 10.75s p=0.683 n=6
Bind Time 3.42s (± 0.57%) 3.42s (± 0.52%) ~ 3.39s 3.44s p=0.935 n=6
Check Time 56.07s (± 0.36%) 56.19s (± 0.25%) ~ 56.08s 56.43s p=0.261 n=6
Emit Time 16.22s (± 0.52%) 16.12s (± 0.81%) ~ 15.91s 16.32s p=0.170 n=6
Total Time 86.44s (± 0.32%) 86.46s (± 0.31%) ~ 86.15s 86.93s p=0.873 n=6
System info unknown
Hosts
  • node (v20.5.1, x64)
Scenarios
  • self-build-src - node (v20.5.1, x64)
  • self-compiler - node (v20.5.1, x64)
  • vscode - node (v20.5.1, x64)
Benchmark Name Iterations
Current pr 6
Baseline baseline 6

Developer Information:

Download Benchmarks

@ahejlsberg
Copy link
Member Author

Performance is unaffected. We're good to merge.

@ahejlsberg ahejlsberg merged commit 8ff77fb into main Jan 9, 2024
19 checks passed
@ahejlsberg ahejlsberg deleted the fix35124 branch January 9, 2024 04:21
@nicolo-ribaudo
Copy link

Coming here from the twitter thread :)

Type refinements are not preserved in inner function and class declarations (due to hoisting):

Class declarations are not hoisted, so this improvement could also applied to them. The only hoisted values are function declarations.

@Methuselah96
Copy link

Methuselah96 commented Jan 9, 2024

Already discussed here (with this response):

Note that class declarations are not hoisted, so we could actually remove this restriction if we wanted to.

It isn't a new restriction, it's actually been there since the dawn of control flow analysis in the compiler. For example, with or without this PR:

function foo(x: string | number) {
  if (typeof x === "string") {
    x;  // string
    class C {
      bar() { x }  // string | number
    }
  }
}

I was wondering what the reason is, but didn't want to change in this PR without double checking. I get that declared classes are only half-hoisted (as in, subject to TDZ checks), but might there be other reasons we've had this restriction all along?

mjbvz added a commit to mjbvz/vscode that referenced this pull request Jan 11, 2024
With microsoft/TypeScript#56908, TS should better preserve type refinements. To help test this out, I made a quick pass through our code to remove type assertions that are no longer needed

Most of these are not impacted by microsoft/TypeScript#56908 but removing them helps TS test changes like this

Not adding an eslint rule for now as it requires whole program intellisense, which is too slow for commit hooks
mjbvz added a commit to microsoft/vscode that referenced this pull request Jan 30, 2024
* Remove extra not null assertions

With microsoft/TypeScript#56908, TS should better preserve type refinements. To help test this out, I made a quick pass through our code to remove type assertions that are no longer needed

Most of these are not impacted by microsoft/TypeScript#56908 but removing them helps TS test changes like this

Not adding an eslint rule for now as it requires whole program intellisense, which is too slow for commit hooks

* Fix merge
Copy link

@davidaucoin7377 davidaucoin7377 left a comment

Choose a reason for hiding this comment

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

I hope this works

@boi-network12
Copy link

The change with the latest commits is where type refinements for mutable global variables in closures are no longer preserved. This adjustment is made because such global variables may be altered by code in other files or modules.

The issue with narrowing x.a is a separate concern. While narrowing discriminated unions is technically unsafe due to the potential mutation of discriminants (which are mutable), such mutations are uncommon in practice. Despite this, the benefits of narrowing are significant and desirable.

@RyanCavanaugh
Copy link
Member

ChatGPT alert 🙄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment