Skip to content

Add checker speculation helper, use in overload resolution #57421

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

Closed
wants to merge 20 commits into from

Conversation

weswigham
Copy link
Member

@weswigham weswigham commented Feb 15, 2024

This both prevents inference data from failed overloads from leaking into follow-on overloads, and allows contextual parameters to become unfixed after a failed overload and adopt a new type. This fixes a lot of longstanding design limitations.

For authors in the checker, the important changes in this PR are twofold:

  1. There is now a function called speculate - you pass it a callback, and it executes that callback within a speculative context. if the callback returns a truthy value, the context is kept when the callback completes (as you may want to do if a speculative operation is successful), otherwise it is rewound to the state it was when the callback was first invoked. Right now, it's just used in overload resolution, but you can imagine the use of it in a handful of other places.

  2. To support this, Node- and Symbol-links have been refactored into classes with decorators on the properties we should speculatively cache. The decorator itself implements all the speculative caching. It basically works by recording timestamps writes occur at, and lazily discarding writes at timestamps marked as discarded upon read.

I've narrowed the set of caches we consider "speculative" down about as much as I think I safely can. Only a handful of fields on the symbol and node links structures (that retain context-sensitive calculated data or diagnostic data), plus diagnostics and relationship caches (since those retain diagnostic emit data!) are checkpointed and potentially rewound on speculation boundaries. Links speculation caches are lazy, but some other global structures are still eagerly copied - in particular, relationship caches (because they contain error reporting state!) are currently eagerly copied - this is probably a location ripe for improvement in real world scenarios.

For users, since this does use the speculation helper in one big location already, this:

Fixes #49820 (Previously A2 would be fixed as a parameter type during checking of the first overload, now that is undone, and B2 can be correctly assigned on the second overload check)
Fixes #13430 (Previously, x would get an implicit any assigned during checking of the first overload - that is now discarded (the function argument arity doesn't match, so the overload ultimately fails) and the second overload is selected)
Fixes #12733 (Same as above)
Fixes #21525 (Previously the first overload would project incorrect contextual types for the second overload and fail, making the second overload fail as well)

And potentially many more - many of the issues in this vein have been classified "Design Limitation", or closed as duplicate/won't fix over the years.

@typescript-bot typescript-bot added Author: Team For Milestone Bug PRs that fix a bug with a specific milestone labels Feb 15, 2024
@@ -34848,63 +35045,67 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (!hasCorrectTypeArgumentArity(candidate, typeArguments) || !hasCorrectArity(node, args, candidate, signatureHelpTrailingComma)) {
continue;
}
const result = speculate(() => {
Copy link
Member Author

Choose a reason for hiding this comment

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

This is easier to read with whitespace diffs turned off.

spreadOfParamsFromGeneratorMakesRequiredParams.ts(6,1): error TS2554: Expected 2 arguments, but got 1.


!!! error TS2318: Cannot find global type 'IterableIterator'.
Copy link
Member Author

Choose a reason for hiding this comment

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

This error only crops up while resolving one of the overloads (the iterator one, ofc), which isn't the last one that we "accept" (though it still has an error), so it gets tossed.

@@ -37,7 +37,7 @@ foo(function(x) { x });
>["hello"] : string[]
>"hello" : "hello"
>every : { <S extends string>(predicate: (value: string, index: number, array: string[]) => value is S, thisArg?: any): this is S[]; (predicate: (value: string, index: number, array: string[]) => unknown, thisArg?: any): boolean; }
>function(v,i,a) {return true;} : (v: string, i: number, a: string[]) => true
>function(v,i,a) {return true;} : (v: string, i: number, a: string[]) => boolean
Copy link
Member Author

Choose a reason for hiding this comment

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

This is one of those funky places where parameter type fixing would make the return type's type contextually type itself in overload passes after the first one. That no longer occurs.

@@ -66,7 +66,7 @@ arr.map((a: number | string, index: number) => {

// This case still doesn't work because `reduce` has multiple overloads :(
arr.reduce((acc: Array<string>, a: number | string, index: number) => {
>arr.reduce((acc: Array<string>, a: number | string, index: number) => { return []}, []) : never[]
>arr.reduce((acc: Array<string>, a: number | string, index: number) => { return []}, []) : string[]
Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, this does fix the // This case still doesn't work because reduce has multiple overloads :( comment 😀

Now, since every overload is done in its own speculative context, the [] expression no longer has its type locked in by the first overload, and it can be interpreted as desired by the later overload instead.

@Andarist
Copy link
Contributor

Andarist commented Feb 16, 2024

I have confirmed locally that this PR would fix #46312 too

A copy-pasteable test case can be found here:

test case
// @strict: true
// @noEmit: true

interface TypegenDisabled {
  "@@xstate/typegen": false;
}
interface TypegenEnabled {
  "@@xstate/typegen": true;
}
interface EventObject {
  type: string;
}
interface ActionObject<TEvent extends EventObject> {
  _TE: TEvent;
}

interface StateMachine<
  TEvent extends EventObject,
  TTypesMeta = TypegenDisabled,
> {
  _TE: TEvent;
  _TRTM: TTypesMeta;
}

interface MachineOptions<TEvent extends EventObject> {
  action?: ActionObject<TEvent>;
}

type MaybeTypegenMachineOptions<
  TEvent extends EventObject,
  TTypesMeta = TypegenDisabled,
> = TTypesMeta extends TypegenEnabled
  ? {
      action?: ActionObject<{ type: "WITH_TYPEGEN" }>;
    }
  : MachineOptions<TEvent>;

declare function assign<TEvent extends EventObject>(
  assignment: (ev: TEvent) => void,
): ActionObject<TEvent>;

declare function useMachine<
  TEvent extends EventObject,
  TTypesMeta extends TypegenEnabled,
>(
  getMachine: StateMachine<TEvent, TTypesMeta>,
  options: MaybeTypegenMachineOptions<TEvent, TTypesMeta>,
): void;
declare function useMachine<TEvent extends EventObject>(
  getMachine: StateMachine<TEvent>,
  options?: MachineOptions<TEvent>,
): void;

const machine = {} as StateMachine<{ type: "WITHOUT_TYPEGEN" }>;

const ret = useMachine(machine, {
  action: assign((_ev) => {
    ((_type: "WITHOUT_TYPEGEN") => null)(_ev.type);
  }),
});

@fatcerberus
Copy link

I'm curious to see how badly this is going to impact performance. It definitely sounds like a potential perf trap, at least for codebases with large quantities of overloads...

@weswigham
Copy link
Member Author

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

@typescript-bot
Copy link
Collaborator

typescript-bot commented Mar 4, 2024

Starting jobs; this comment will be updated as builds start and complete.

Command Status Results
run dt ✅ Started ✅ Results
test top100 ✅ Started 👀 Results
user test this ✅ Started 👀 Results
perf test public ✅ Started ❌ Results

@typescript-bot
Copy link
Collaborator

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

There were infrastructure failures potentially unrelated to your change:

  • 1 instance of "Package install failed"
  • 1 instance of "Unknown failure"

Otherwise...

Something interesting changed - please have a look.

Details

puppeteer

packages/browsers/test/src/tsconfig.json

@typescript-bot
Copy link
Collaborator

Hey @weswigham, the results of running the DT tests are ready.
There were interesting changes:
Changes are too big to display here, please check the log.
You can check the log here.

@typescript-bot
Copy link
Collaborator

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

Something interesting changed - please have a look.

Details

niklasvh/html2canvas

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

tsconfig.json

typeorm/typeorm

tsconfig.json

@jakebailey
Copy link
Member

https://typescript.visualstudio.com/TypeScript/_build/results?buildId=160185&view=results made mui time out; I'm going to up the max time limit of the benchmarker as apparently 60 minutes was too slow, oops.

@weswigham
Copy link
Member Author

@typescript-bot run dt

@typescript-bot
Copy link
Collaborator

typescript-bot commented Mar 5, 2024

Starting jobs; this comment will be updated as builds start and complete.

Command Status Results
run dt ✅ Started ✅ Results

@typescript-bot
Copy link
Collaborator

Hey @weswigham, the results of running the DT tests are ready.
There were interesting changes:
Changes are too big to display here, please check the log.
You can check the log here.

@weswigham
Copy link
Member Author

@typescript-bot pack this

@typescript-bot
Copy link
Collaborator

typescript-bot commented Mar 5, 2024

Starting jobs; this comment will be updated as builds start and complete.

Command Status Results
pack this ✅ Started

@typescript-bot
Copy link
Collaborator

typescript-bot commented Mar 5, 2024

Hey @weswigham, 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/160205/artifacts?artifactName=tgz&fileId=1443B66398AB78EED401B3CB56FBDD76839A48419AFBD4BCD1127532995F1AF402&fileName=/typescript-5.5.0-insiders.20240305.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]".;

@weswigham weswigham force-pushed the speculation-helper-wip branch from 8aed762 to fb3cac4 Compare March 11, 2024 19:31
@weswigham
Copy link
Member Author

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

@typescript-bot
Copy link
Collaborator

typescript-bot commented Mar 11, 2024

Starting jobs; this comment will be updated as builds start and complete.

Command Status Results
run dt ✅ Started 👀 Results
test top100 ✅ Started 👀 Results
user test this ✅ Started 👀 Results
perf test public ✅ Started 👀 Results

@typescript-bot
Copy link
Collaborator

Hey @weswigham, the results of running the DT tests are ready.
There were interesting changes:
Changes are too big to display here, please check the log.
You can check the log here.

@typescript-bot
Copy link
Collaborator

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

There were infrastructure failures potentially unrelated to your change:

  • 1 instance of "Package install failed"
  • 1 instance of "Unknown failure"

Otherwise...

Something interesting changed - please have a look.

Details

fp-ts

dtslint/ts3.5/tsconfig.json

examples/tsconfig.json

tsconfig.build-es6.json

tsconfig.eslint.json

tsconfig.json

puppeteer

packages/browsers/test/src/tsconfig.json

@typescript-bot
Copy link
Collaborator

@weswigham
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
Compiler-Unions - node (v20.5.1, x64)
Memory used 192,363k (± 0.95%) 617,624k (± 0.31%) 🔻+425,261k (+221.07%) 616,092k 620,166k p=0.005 n=6
Parse Time 1.38s (± 0.59%) 1.37s (± 0.00%) ~ 1.37s 1.37s p=0.073 n=6
Bind Time 0.75s (± 1.46%) 0.76s (± 0.54%) ~ 0.75s 0.76s p=0.090 n=6
Check Time 8.86s (± 0.90%) 11.57s (± 0.25%) 🔻+2.71s (+30.66%) 11.54s 11.61s p=0.005 n=6
Emit Time 2.68s (± 0.56%) 3.40s (± 0.97%) 🔻+0.72s (+26.77%) 3.36s 3.45s p=0.005 n=6
Total Time 13.67s (± 0.59%) 17.11s (± 0.35%) 🔻+3.44s (+25.14%) 17.02s 17.19s p=0.005 n=6
mui-docs - node (v20.5.1, x64)
Memory used 1,825,214k (± 0.00%) 4,120,878k (± 0.00%) 🔻+2,295,665k (+125.78%) 4,120,791k 4,120,955k p=0.005 n=6
Parse Time 6.90s (± 0.60%) 6.93s (± 0.41%) ~ 6.89s 6.97s p=0.169 n=6
Bind Time 2.45s (± 0.79%) 2.46s (± 1.61%) ~ 2.41s 2.51s p=0.808 n=6
Check Time 56.20s (± 0.31%) 133.06s (± 0.97%) 🔻+76.86s (+136.75%) 131.43s 135.00s p=0.005 n=6
Emit Time 0.15s (± 3.36%) 0.18s (± 4.62%) 🔻+0.02s (+15.22%) 0.16s 0.18s p=0.005 n=6
Total Time 65.70s (± 0.23%) 142.63s (± 0.94%) 🔻+76.92s (+117.07%) 140.96s 144.64s p=0.005 n=6
self-build-src - node (v20.5.1, x64)
Memory used 2,586,142k (± 2.28%) 6,212,187k (± 5.23%) 🔻+3,626,045k (+140.21%) 6,079,283k 6,875,852k p=0.005 n=6
Parse Time 5.20s (± 0.90%) 5.21s (± 0.83%) ~ 5.14s 5.26s p=0.748 n=6
Bind Time 2.04s (± 0.54%) 2.02s (± 0.44%) -0.02s (- 0.98%) 2.01s 2.03s p=0.011 n=6
Check Time 32.51s (± 0.73%) 53.77s (± 1.16%) 🔻+21.26s (+65.40%) 52.76s 54.25s p=0.005 n=6
Emit Time 2.67s (± 2.08%) 2.87s (± 3.96%) 🔻+0.20s (+ 7.55%) 2.77s 3.09s p=0.005 n=6
Total Time 42.43s (± 0.48%) 63.92s (± 0.92%) 🔻+21.49s (+50.63%) 62.85s 64.35s p=0.005 n=6
self-compiler - node (v20.5.1, x64)
Memory used 414,076k (± 0.03%) 1,062,821k (± 0.01%) 🔻+648,745k (+156.67%) 1,062,720k 1,063,039k p=0.005 n=6
Parse Time 2.89s (± 0.42%) 2.93s (± 0.28%) +0.04s (+ 1.38%) 2.92s 2.94s p=0.005 n=6
Bind Time 1.13s (± 0.87%) 1.16s (± 1.15%) +0.03s (+ 2.36%) 1.15s 1.18s p=0.009 n=6
Check Time 14.17s (± 0.18%) 23.75s (± 2.23%) 🔻+9.58s (+67.61%) 23.30s 24.80s p=0.005 n=6
Emit Time 1.01s (± 1.16%) 1.07s (± 0.98%) 🔻+0.06s (+ 5.62%) 1.05s 1.08s p=0.005 n=6
Total Time 19.20s (± 0.13%) 28.90s (± 1.89%) 🔻+9.70s (+50.53%) 28.44s 29.99s p=0.005 n=6
vscode - node (v20.5.1, x64)
Memory used 2,909,791k (± 0.01%) 0k ~ 0k 0k p=1.000 n=6+0
Parse Time 10.93s (± 0.30%) 0.00s ~ 0.00s 0.00s p=1.000 n=6+0
Bind Time 3.51s (± 0.42%) 0.00s ~ 0.00s 0.00s p=1.000 n=6+0
Check Time 58.62s (± 0.35%) 0.00s ~ 0.00s 0.00s p=1.000 n=6+0
Emit Time 18.30s (±10.40%) 0.00s ~ 0.00s 0.00s p=1.000 n=6+0
Total Time 91.36s (± 2.12%) 0.00s ~ 0.00s 0.00s p=1.000 n=6+0
webpack - node (v20.5.1, x64)
Memory used 402,722k (± 0.01%) 827,719k (± 0.00%) 🔻+424,997k (+105.53%) 827,688k 827,742k p=0.005 n=6
Parse Time 3.36s (± 0.93%) 3.36s (± 0.44%) ~ 3.35s 3.38s p=1.000 n=6
Bind Time 1.43s (± 0.53%) 1.43s (± 1.38%) ~ 1.40s 1.46s p=0.357 n=6
Check Time 13.15s (± 0.32%) 18.54s (± 0.21%) 🔻+5.40s (+41.04%) 18.50s 18.61s p=0.005 n=6
Emit Time 0.00s (± 0.00%) 0.00s (±244.70%) ~ 0.00s 0.01s p=0.405 n=6
Total Time 17.94s (± 0.23%) 23.34s (± 0.22%) 🔻+5.40s (+30.07%) 23.28s 23.42s p=0.005 n=6
System info unknown
Hosts
  • node (v20.5.1, x64)
Scenarios
  • Compiler-Unions - node (v20.5.1, x64)
  • mui-docs - node (v20.5.1, x64)
  • self-build-src - node (v20.5.1, x64)
  • self-compiler - node (v20.5.1, x64)
  • vscode - node (v20.5.1, x64)
  • webpack - node (v20.5.1, x64)
Benchmark Name Iterations
Current pr 6
Baseline baseline 6

Developer Information:

Download Benchmarks

@weswigham
Copy link
Member Author

Mmmm, vscode OOMing probably shouldn't report as "no difference" in the perf results with high probability. 😆 Definitely needs an indicator that the builds failed for some reason.

@jakebailey
Copy link
Member

jakebailey commented Mar 11, 2024

Mmmm, vscode OOMing probably shouldn't report as "no difference" in the perf results with high probability. 😆 Definitely needs an indicator that the builds failed for some reason.

It's low probability, p=1 which is the most bad. Happens when the perf numbers are zero (looks exactly like Go's benchstat). I agree that the table needs to say something about when the compilation fails (it just doesn't yet, only in logs).

@typescript-bot
Copy link
Collaborator

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

Something interesting changed - please have a look.

Details

microsoft/vscode

3 of 54 projects failed to build with the old tsc and were ignored

extensions/debug-auto-launch/tsconfig.json

niklasvh/html2canvas

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

tsconfig.json

typeorm/typeorm

tsconfig.json

@RyanCavanaugh
Copy link
Member

@typescript-bot pack this

@typescript-bot
Copy link
Collaborator

typescript-bot commented Mar 21, 2024

Starting jobs; this comment will be updated as builds start and complete.

Command Status Results
pack this ✅ Started ✅ Results

@typescript-bot
Copy link
Collaborator

typescript-bot commented Mar 21, 2024

Hey @RyanCavanaugh, 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/160654/artifacts?artifactName=tgz&fileId=59EFEFE54E3A8D5887DEC192C7BDE923DB7A3BD1BC310E48AA32C34FB8AF5E5D02&fileName=/typescript-5.5.0-insiders.20240321.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]".;

@weswigham
Copy link
Member Author

Mmmm, this works, generally speaking (and there are big improvements in behavior!), but the perf cost is too high to do it this way over today's behavior (doubled checking times, much larger memory usage, ew), which feels quite bad. I'm not gonna keep this updated over time, and a future iteration is going to have to try an entirely different approach to try to get it to perform well, so I'm going to close this.

@weswigham weswigham closed this Aug 13, 2024
@weswigham weswigham added the Experiment A fork with an experimental idea which might not make it into master label Aug 13, 2024
@weswigham
Copy link
Member Author

Note for future readers: AFAIK, it's the downlevel-decorator access-intermediated caches that are slow to touch, so maybe something closer to this becomes more viable once we have native decorators, OR, if you feel like rewriting every cache access in the codebase, that might work, too. Maybe.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Author: Team Experiment A fork with an experimental idea which might not make it into master For Milestone Bug PRs that fix a bug with a specific milestone
Projects
None yet
6 participants