Skip to content

Add inference for 'Promise' based on call to 'resolve' #40466

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 2 commits into from

Conversation

rbuckton
Copy link
Member

@rbuckton rbuckton commented Sep 10, 2020

In #39817, we introduced a breaking change in the signature of PromiseConstructor to ensure it is more type-safe. This unfortunately results in new errors. One class of these errors relates to code like the following:

async function wait(ms) {
  await new Promise(resolve => setTimeout(() => resolve(), ms));
  //                                            ^^^^^^^^^
  //                                            error: Expected 1 argument(s) but got 0.
}

This is now an error due to the fact the type we infer for an untyped Promise with no contextual type is unknown. While this can be addressed by annotating Promise with the type void (i.e., new Promise<void>(resolve => ...)), this PR looks to address this case by introducing an inference heuristic around the "revealing constructor" pattern used by the Promise constructor.

A "revealing constructor" is a class that accepts a callback in its constructor that the constructor then invokes, providing access to one or more privileged callbacks passed as arguments:

interface PromiseConstructor {
  /**
   * Creates a new Promise.
   * @param executor A callback used to initialize the promise. This callback is passed two arguments:
   * a resolve callback used to resolve the promise with a value or the result of another promise,
   * and a reject callback used to reject the promise with a provided reason or error.
   */
  new <T>(executor: (resolve: (value: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void): Promise<T>;
}

When you create a new Promise, the constructor will invoke the executor callback with two callbacks of its own: resolve and reject.

This PR introduces inference for this very specific heuristic, such that:

// Promise<void>
const p0 = new Promise(resolve => resolve()); 

// Promise<number>
const p1 = new Promise(resolve => resolve(1));

// Promise<number | string>
const p2 = new Promise(resolve => Math.random() ? resolve(Promise.resolve(1)) : resolve("hi"));

declare function callWith<T>(f: (value: T) => void, value: T): void;

// Promise<boolean>
const p3 = new Promise(resolve => callWith(resolve, true));

The specific heuristic used for inference is as follows:

Attempt to solve for T in new C<T>(f => f(t)):

  • Restricted to NewExpression
  • The construct signature for T must only accept a single parameter (pA)
  • The parameter pA has a single call signature (spA) (i.e., executor: (resolve: (value: T | PromiseLike<T>) => void) => void)
  • The signature spA has at least one parameter (pB)
  • The parameter pB has a single call signature (spB) (i.e., resolve: (value: T | PromiseLike<T>) => void)
  • The signature spB has a single parameter (pC)
  • The parameter pC contains a type variable for which we are gathering inferences (i.e. value: T | PromiseLike<T>)
  • A function expression or arrow function (fpA) is passed as the argument to the parameter pA
  • The function fpA must have one parameter ppB that is untyped (and thus would be contextually typed by pB)

If the above conditions are met then:

  • Determine the name in given to the parameter ppB in the function fpA.
  • Find all references to parameter ppB in the body of the function fpA.
  • If ppB is called directly, collect inferences for the type of the argument passed as the parameter.
  • If ppB is passed as the argument to another function, we can attempt to use the contextual type of that parameter for inference.

These inferences are given a very low priority so that inference from contextual types will have a higher priority.

Related #40231, #39817

@typescript-bot typescript-bot added Author: Team For Uncommitted Bug PR for untriaged, rejected, closed or missing bug labels Sep 10, 2020
@rbuckton
Copy link
Member Author

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

@typescript-bot
Copy link
Collaborator

typescript-bot commented Sep 10, 2020

Heya @rbuckton, I've started to run the parallelized community code test suite on this PR at 56a9871. You can monitor the build here.

@typescript-bot
Copy link
Collaborator

typescript-bot commented Sep 10, 2020

Heya @rbuckton, I've started to run the perf test suite on this PR at 56a9871. You can monitor the build here.

Update: The results are in!

@typescript-bot
Copy link
Collaborator

typescript-bot commented Sep 10, 2020

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

@typescript-bot
Copy link
Collaborator

typescript-bot commented Sep 10, 2020

Heya @rbuckton, I've started to run the extended test suite on this PR at 56a9871. You can monitor the build here.

@rbuckton
Copy link
Member Author

/cc @DanielRosenwasser

@rbuckton rbuckton changed the title Add inference based for 'Promise' based on call to 'resolve' Add inference for 'Promise' based on call to 'resolve' Sep 10, 2020
@typescript-bot
Copy link
Collaborator

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

Here they are:

Comparison Report - master..40466

Metric master 40466 Delta Best Worst
Angular - node (v10.16.3, x64)
Memory used 345,036k (± 0.02%) 345,161k (± 0.02%) +125k (+ 0.04%) 344,931k 345,334k
Parse Time 2.02s (± 0.37%) 2.00s (± 0.76%) -0.02s (- 0.94%) 1.97s 2.05s
Bind Time 0.82s (± 0.99%) 0.82s (± 0.91%) 0.00s ( 0.00%) 0.81s 0.84s
Check Time 4.83s (± 0.72%) 4.81s (± 0.50%) -0.02s (- 0.41%) 4.76s 4.88s
Emit Time 5.22s (± 1.15%) 5.20s (± 0.87%) -0.02s (- 0.38%) 5.12s 5.30s
Total Time 12.89s (± 0.63%) 12.84s (± 0.48%) -0.05s (- 0.43%) 12.70s 12.96s
Monaco - node (v10.16.3, x64)
Memory used 339,585k (± 0.03%) 339,567k (± 0.03%) -18k (- 0.01%) 339,237k 339,695k
Parse Time 1.57s (± 0.60%) 1.57s (± 0.24%) -0.00s (- 0.13%) 1.56s 1.57s
Bind Time 0.72s (± 0.66%) 0.71s (± 0.70%) -0.00s (- 0.28%) 0.71s 0.73s
Check Time 4.99s (± 0.57%) 4.98s (± 0.33%) -0.01s (- 0.12%) 4.96s 5.03s
Emit Time 2.75s (± 0.48%) 2.75s (± 0.50%) +0.01s (+ 0.25%) 2.73s 2.80s
Total Time 10.02s (± 0.39%) 10.02s (± 0.20%) -0.00s (- 0.04%) 9.98s 10.05s
TFS - node (v10.16.3, x64)
Memory used 302,399k (± 0.04%) 302,514k (± 0.03%) +115k (+ 0.04%) 302,312k 302,678k
Parse Time 1.22s (± 0.63%) 1.21s (± 0.49%) -0.00s (- 0.08%) 1.20s 1.23s
Bind Time 0.67s (± 1.20%) 0.67s (± 1.04%) +0.00s (+ 0.45%) 0.65s 0.68s
Check Time 4.46s (± 0.56%) 4.49s (± 0.38%) +0.02s (+ 0.49%) 4.45s 4.53s
Emit Time 2.93s (± 0.81%) 2.92s (± 0.63%) -0.01s (- 0.34%) 2.86s 2.96s
Total Time 9.28s (± 0.27%) 9.29s (± 0.36%) +0.01s (+ 0.15%) 9.22s 9.38s
material-ui - node (v10.16.3, x64)
Memory used 462,573k (± 0.02%) 462,570k (± 0.01%) -4k (- 0.00%) 462,472k 462,795k
Parse Time 1.98s (± 0.54%) 1.99s (± 1.00%) +0.01s (+ 0.45%) 1.96s 2.05s
Bind Time 0.65s (± 0.80%) 0.65s (± 1.12%) -0.00s (- 0.15%) 0.64s 0.67s
Check Time 13.18s (± 0.82%) 13.27s (± 0.74%) +0.09s (+ 0.65%) 13.06s 13.48s
Emit Time 0.00s (± 0.00%) 0.00s (± 0.00%) 0.00s ( NaN%) 0.00s 0.00s
Total Time 15.81s (± 0.71%) 15.90s (± 0.74%) +0.09s (+ 0.60%) 15.68s 16.15s
Angular - node (v12.1.0, x64)
Memory used 322,231k (± 0.03%) 322,234k (± 0.08%) +3k (+ 0.00%) 321,229k 322,616k
Parse Time 2.00s (± 0.64%) 2.00s (± 0.73%) -0.00s (- 0.05%) 1.97s 2.03s
Bind Time 0.81s (± 0.74%) 0.81s (± 0.45%) -0.00s (- 0.00%) 0.80s 0.81s
Check Time 4.70s (± 0.51%) 4.71s (± 0.62%) +0.01s (+ 0.19%) 4.65s 4.79s
Emit Time 5.35s (± 0.63%) 5.37s (± 1.30%) +0.02s (+ 0.34%) 5.28s 5.57s
Total Time 12.85s (± 0.45%) 12.88s (± 0.77%) +0.02s (+ 0.18%) 12.74s 13.14s
Monaco - node (v12.1.0, x64)
Memory used 321,803k (± 0.03%) 321,840k (± 0.02%) +37k (+ 0.01%) 321,744k 322,039k
Parse Time 1.54s (± 0.94%) 1.54s (± 0.84%) +0.00s (+ 0.19%) 1.51s 1.57s
Bind Time 0.69s (± 0.68%) 0.69s (± 0.48%) -0.00s (- 0.00%) 0.68s 0.70s
Check Time 4.77s (± 0.34%) 4.79s (± 0.34%) +0.01s (+ 0.31%) 4.75s 4.82s
Emit Time 2.80s (± 0.80%) 2.80s (± 0.55%) +0.00s (+ 0.04%) 2.77s 2.83s
Total Time 9.81s (± 0.23%) 9.82s (± 0.28%) +0.02s (+ 0.18%) 9.78s 9.88s
TFS - node (v12.1.0, x64)
Memory used 286,789k (± 0.01%) 286,781k (± 0.02%) -7k (- 0.00%) 286,610k 286,910k
Parse Time 1.24s (± 0.55%) 1.23s (± 0.81%) -0.00s (- 0.24%) 1.21s 1.25s
Bind Time 0.64s (± 0.97%) 0.64s (± 1.13%) +0.00s (+ 0.47%) 0.63s 0.66s
Check Time 4.38s (± 0.51%) 4.35s (± 0.21%) -0.03s (- 0.59%) 4.34s 4.37s
Emit Time 2.93s (± 0.73%) 2.94s (± 0.76%) +0.01s (+ 0.38%) 2.88s 2.98s
Total Time 9.18s (± 0.33%) 9.17s (± 0.34%) -0.02s (- 0.19%) 9.11s 9.23s
material-ui - node (v12.1.0, x64)
Memory used 440,797k (± 0.08%) 440,893k (± 0.01%) +96k (+ 0.02%) 440,794k 441,026k
Parse Time 2.01s (± 0.67%) 2.00s (± 0.52%) -0.00s (- 0.20%) 1.98s 2.02s
Bind Time 0.63s (± 0.94%) 0.63s (± 0.54%) -0.00s (- 0.31%) 0.63s 0.64s
Check Time 11.83s (± 0.79%) 11.78s (± 0.63%) -0.05s (- 0.44%) 11.62s 12.02s
Emit Time 0.00s (± 0.00%) 0.00s (± 0.00%) 0.00s ( NaN%) 0.00s 0.00s
Total Time 14.47s (± 0.68%) 14.42s (± 0.52%) -0.06s (- 0.40%) 14.25s 14.66s
Angular - node (v8.9.0, x64)
Memory used 341,682k (± 0.02%) 341,708k (± 0.02%) +26k (+ 0.01%) 341,579k 341,916k
Parse Time 2.55s (± 0.63%) 2.55s (± 0.45%) -0.00s (- 0.16%) 2.53s 2.58s
Bind Time 0.86s (± 0.46%) 0.87s (± 0.95%) +0.01s (+ 0.58%) 0.85s 0.89s
Check Time 5.44s (± 0.61%) 5.45s (± 0.31%) +0.01s (+ 0.26%) 5.42s 5.49s
Emit Time 5.99s (± 1.74%) 6.01s (± 1.78%) +0.01s (+ 0.22%) 5.71s 6.20s
Total Time 14.85s (± 0.70%) 14.87s (± 0.76%) +0.02s (+ 0.15%) 14.55s 15.05s
Monaco - node (v8.9.0, x64)
Memory used 340,779k (± 0.01%) 340,851k (± 0.01%) +72k (+ 0.02%) 340,759k 340,943k
Parse Time 1.89s (± 0.40%) 1.89s (± 0.36%) -0.00s (- 0.05%) 1.87s 1.90s
Bind Time 0.89s (± 0.73%) 0.89s (± 0.84%) -0.00s (- 0.11%) 0.87s 0.90s
Check Time 5.52s (± 0.58%) 5.53s (± 0.50%) +0.01s (+ 0.18%) 5.48s 5.61s
Emit Time 3.24s (± 0.79%) 3.24s (± 0.83%) 0.00s ( 0.00%) 3.20s 3.31s
Total Time 11.53s (± 0.46%) 11.54s (± 0.45%) +0.01s (+ 0.07%) 11.44s 11.66s
TFS - node (v8.9.0, x64)
Memory used 304,079k (± 0.01%) 304,126k (± 0.01%) +47k (+ 0.02%) 304,057k 304,199k
Parse Time 1.55s (± 0.56%) 1.55s (± 0.36%) -0.00s (- 0.26%) 1.54s 1.56s
Bind Time 0.67s (± 0.77%) 0.68s (± 0.55%) +0.00s (+ 0.60%) 0.67s 0.68s
Check Time 5.19s (± 0.53%) 5.21s (± 0.60%) +0.02s (+ 0.46%) 5.13s 5.27s
Emit Time 2.94s (± 0.49%) 2.95s (± 1.47%) +0.00s (+ 0.17%) 2.87s 3.10s
Total Time 10.35s (± 0.30%) 10.38s (± 0.64%) +0.03s (+ 0.25%) 10.21s 10.53s
material-ui - node (v8.9.0, x64)
Memory used 466,982k (± 0.01%) 467,027k (± 0.01%) +46k (+ 0.01%) 466,958k 467,095k
Parse Time 2.40s (± 0.54%) 2.40s (± 0.61%) +0.01s (+ 0.29%) 2.38s 2.44s
Bind Time 0.81s (± 1.03%) 0.80s (± 0.93%) -0.01s (- 0.74%) 0.78s 0.81s
Check Time 17.57s (± 0.78%) 17.68s (± 0.82%) +0.11s (+ 0.61%) 17.40s 18.02s
Emit Time 0.00s (± 0.00%) 0.00s (± 0.00%) 0.00s ( NaN%) 0.00s 0.00s
Total Time 20.77s (± 0.62%) 20.88s (± 0.63%) +0.11s (+ 0.52%) 20.64s 21.20s
Angular - node (v8.9.0, x86)
Memory used 196,068k (± 0.03%) 196,119k (± 0.03%) +52k (+ 0.03%) 195,971k 196,193k
Parse Time 2.47s (± 0.60%) 2.47s (± 0.91%) -0.01s (- 0.20%) 2.43s 2.53s
Bind Time 0.99s (± 1.07%) 1.01s (± 1.22%) +0.01s (+ 1.31%) 0.97s 1.03s
Check Time 4.92s (± 0.84%) 4.90s (± 0.51%) -0.01s (- 0.28%) 4.84s 4.96s
Emit Time 5.92s (± 0.79%) 5.98s (± 1.07%) +0.06s (+ 1.01%) 5.89s 6.18s
Total Time 14.30s (± 0.36%) 14.36s (± 0.45%) +0.05s (+ 0.35%) 14.23s 14.54s
Monaco - node (v8.9.0, x86)
Memory used 193,831k (± 0.03%) 193,883k (± 0.02%) +52k (+ 0.03%) 193,783k 193,978k
Parse Time 1.91s (± 0.83%) 1.94s (± 1.68%) +0.02s (+ 1.26%) 1.89s 2.02s
Bind Time 0.70s (± 0.97%) 0.70s (± 0.88%) -0.00s (- 0.43%) 0.69s 0.72s
Check Time 5.57s (± 0.47%) 5.62s (± 0.72%) +0.05s (+ 0.92%) 5.55s 5.74s
Emit Time 2.67s (± 0.55%) 2.67s (± 0.91%) +0.00s (+ 0.04%) 2.62s 2.74s
Total Time 10.85s (± 0.30%) 10.93s (± 0.69%) +0.08s (+ 0.72%) 10.83s 11.12s
TFS - node (v8.9.0, x86)
Memory used 173,987k (± 0.03%) 174,071k (± 0.04%) +83k (+ 0.05%) 173,913k 174,187k
Parse Time 1.58s (± 0.23%) 1.58s (± 0.51%) +0.01s (+ 0.38%) 1.56s 1.60s
Bind Time 0.64s (± 0.74%) 0.64s (± 0.81%) -0.00s (- 0.31%) 0.63s 0.65s
Check Time 4.73s (± 0.68%) 4.70s (± 0.59%) -0.03s (- 0.66%) 4.64s 4.78s
Emit Time 2.82s (± 0.79%) 2.78s (± 0.87%) -0.04s (- 1.38%) 2.73s 2.85s
Total Time 9.77s (± 0.53%) 9.70s (± 0.43%) -0.07s (- 0.70%) 9.61s 9.80s
material-ui - node (v8.9.0, x86)
Memory used 264,399k (± 0.01%) 264,457k (± 0.02%) +58k (+ 0.02%) 264,370k 264,565k
Parse Time 2.47s (± 0.63%) 2.47s (± 1.19%) +0.01s (+ 0.24%) 2.43s 2.58s
Bind Time 0.72s (± 4.16%) 0.70s (± 2.92%) 🟩-0.02s (- 3.05%) 0.67s 0.76s
Check Time 16.31s (± 0.78%) 16.32s (± 0.81%) +0.01s (+ 0.07%) 16.11s 16.78s
Emit Time 0.00s (± 0.00%) 0.00s (± 0.00%) 0.00s ( NaN%) 0.00s 0.00s
Total Time 19.50s (± 0.74%) 19.49s (± 0.75%) -0.00s (- 0.02%) 19.28s 20.02s
System
Machine Namets-ci-ubuntu
Platformlinux 4.4.0-166-generic
Architecturex64
Available Memory16 GB
Available Memory1 GB
CPUs4 × Intel(R) Core(TM) i7-4770 CPU @ 3.40GHz
Hosts
  • node (v10.16.3, x64)
  • node (v12.1.0, x64)
  • node (v8.9.0, x64)
  • node (v8.9.0, x86)
Scenarios
  • Angular - node (v10.16.3, x64)
  • Angular - node (v12.1.0, x64)
  • Angular - node (v8.9.0, x64)
  • Angular - node (v8.9.0, x86)
  • Monaco - node (v10.16.3, x64)
  • Monaco - node (v12.1.0, x64)
  • Monaco - node (v8.9.0, x64)
  • Monaco - node (v8.9.0, x86)
  • TFS - node (v10.16.3, x64)
  • TFS - node (v12.1.0, x64)
  • TFS - node (v8.9.0, x64)
  • TFS - node (v8.9.0, x86)
  • material-ui - node (v10.16.3, x64)
  • material-ui - node (v12.1.0, x64)
  • material-ui - node (v8.9.0, x64)
  • material-ui - node (v8.9.0, x86)
Benchmark Name Iterations
Current 40466 10
Baseline master 10

@typescript-bot
Copy link
Collaborator

The user suite test run you requested has finished and failed. I've opened a PR with the baseline diff from master.

@rbuckton
Copy link
Member Author

From the user test baselines:

  • azure-sdk - Differences are not related to this change.
  • office-ui-fabric - Differences are not related to this change.
  • vue-next - Differences are not related to this change.
  • xterm.js - Differences are not related to this change.
  • adonis-framework - See below.
  • async - Differences are not related to this change.
  • axios - Differences are not related to this change.
  • chrome-devtools-frontend - Differences are not related to this change.
  • debug - Differences are not related to this change.
  • lodash - Differences are not related to this change.
  • npm - Differences are not related to this change.
  • npmlog - Differences are not related to this change.
  • prettier - Differences are not related to this change.
  • puppeteer - Differences are not related to this change.
  • uglify-js - Differences are not related to this change.
  • webpack - Differences are not related to this change.

adonis-framework

Most errors seem unrelated to this change. Only pertinent errors are these:

@rbuckton
Copy link
Member Author

For our RWC test suite, this fixes (and thus removes) a number of errors due to the improved inference.

@rbuckton
Copy link
Member Author

DT Tests passed, and the perf tests show no major perf regressions (numbers look about the same).

Copy link
Member

@DanielRosenwasser DanielRosenwasser left a comment

Choose a reason for hiding this comment

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

I'm kind of uncomfortable with a change like this given how hyper-specific it is. To be honest, I'm not even sure whether removing the optionality of the resolve parameter is worth the cumulative changes so far.

@@ -6903,4 +6903,77 @@ namespace ts {
return bindParentToChildIgnoringJSDoc(child, parent) || bindJSDoc(child);
}
}

export function getPossibleSymbolReferencePositions(sourceFile: SourceFile, symbolName: string, container: Node = sourceFile) {
Copy link
Member

Choose a reason for hiding this comment

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

This won't work on something with Unicode escapes. It's not a blocker, but it'd be very strange.

new Promise(resolve => {
    \u0072\u0065\u0073\u006f\u006c\u0076\u0065(100);
})

Copy link
Member Author

Choose a reason for hiding this comment

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

True, and that's documented in the find-all-references code where this function originally came from. Its unfortunate, but this heuristic is intended to improve inference in a very specific case. It might be feasible to rewrite the function to be a bit smarter (though possibly slower), by scanning the text instead of using .indexOf. That would likely help with find-all-references as well.

Copy link
Member Author

Choose a reason for hiding this comment

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

(Though I'm not sure a reasonable change based on scanning could be made prior to 4.1-beta)

Copy link
Member Author

Choose a reason for hiding this comment

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

Actually, scratch that. Its feasible to do. The implementation that supports unicode escapes depends on using a regular expression, so is costlier. I have a version I can push that actually tracks on the source file whether it contains identifiers with unicode escapes, and if it doesn't then it can use the existing indexOf logic, which should be faster:

image

I might wait on that until after this PR, however, as I'd need to also add fourslash tests for find-all-references which could be time consuming and could possibly wait until after 4.1-beta.

@AlCalzone
Copy link
Contributor

With my limited insight, this heuristic seems similar to what I requested in #36456. Would that be feasible if this PR is the way to go forward?

@sandersn
Copy link
Member

This experiment is pretty old, so I'm going to close it to reduce the number of open PRs.

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 Uncommitted Bug PR for untriaged, rejected, closed or missing bug
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants