Skip to content

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

Closed
Ron Buckton (rbuckton) wants to merge 2 commits into
mainfrom
inferPromiseTypeFromResolve
Closed

Add inference for 'Promise' based on call to 'resolve'#40466
Ron Buckton (rbuckton) wants to merge 2 commits into
mainfrom
inferPromiseTypeFromResolve

Conversation

@rbuckton

@rbucktonRon Buckton (rbuckton) commented Sep 10, 2020

Copy link
Copy Markdown
Contributor

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:

asyncfunctionwait(ms){awaitnewPromise(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:

interfacePromiseConstructor{/** * 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>constp0=newPromise(resolve=>resolve());// Promise<number>constp1=newPromise(resolve=>resolve(1));// Promise<number | string>constp2=newPromise(resolve=>Math.random() ? resolve(Promise.resolve(1)) : resolve("hi"));declarefunctioncallWith<T>(f: (value: T)=>void,value: T): void;// Promise<boolean>constp3=newPromise(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

@rbuckton

Copy link
Copy Markdown
ContributorAuthor

@typescript-bot

TypeScript Bot (typescript-bot) commented Sep 10, 2020

Copy link
Copy Markdown
Contributor

Heya Ron Buckton (@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

TypeScript Bot (typescript-bot) commented Sep 10, 2020

Copy link
Copy Markdown
Contributor

Heya Ron Buckton (@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

TypeScript Bot (typescript-bot) commented Sep 10, 2020

Copy link
Copy Markdown
Contributor

Heya Ron Buckton (@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

TypeScript Bot (typescript-bot) commented Sep 10, 2020

Copy link
Copy Markdown
Contributor

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

@rbuckton

Copy link
Copy Markdown
ContributorAuthor

@rbucktonRon Buckton (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
Copy Markdown
Contributor

Ron Buckton (@rbuckton)
The results of the perf run you requested are in!

Here they are:

Comparison Report - master..40466

Metricmaster40466DeltaBestWorst
Angular - node (v10.16.3, x64)
Memory used345,036k (± 0.02%)345,161k (± 0.02%)+125k (+ 0.04%)344,931k345,334k
Parse Time2.02s (± 0.37%)2.00s (± 0.76%)-0.02s (- 0.94%)1.97s2.05s
Bind Time0.82s (± 0.99%)0.82s (± 0.91%)0.00s ( 0.00%)0.81s0.84s
Check Time4.83s (± 0.72%)4.81s (± 0.50%)-0.02s (- 0.41%)4.76s4.88s
Emit Time5.22s (± 1.15%)5.20s (± 0.87%)-0.02s (- 0.38%)5.12s5.30s
Total Time12.89s (± 0.63%)12.84s (± 0.48%)-0.05s (- 0.43%)12.70s12.96s
Monaco - node (v10.16.3, x64)
Memory used339,585k (± 0.03%)339,567k (± 0.03%)-18k (- 0.01%)339,237k339,695k
Parse Time1.57s (± 0.60%)1.57s (± 0.24%)-0.00s (- 0.13%)1.56s1.57s
Bind Time0.72s (± 0.66%)0.71s (± 0.70%)-0.00s (- 0.28%)0.71s0.73s
Check Time4.99s (± 0.57%)4.98s (± 0.33%)-0.01s (- 0.12%)4.96s5.03s
Emit Time2.75s (± 0.48%)2.75s (± 0.50%)+0.01s (+ 0.25%)2.73s2.80s
Total Time10.02s (± 0.39%)10.02s (± 0.20%)-0.00s (- 0.04%)9.98s10.05s
TFS - node (v10.16.3, x64)
Memory used302,399k (± 0.04%)302,514k (± 0.03%)+115k (+ 0.04%)302,312k302,678k
Parse Time1.22s (± 0.63%)1.21s (± 0.49%)-0.00s (- 0.08%)1.20s1.23s
Bind Time0.67s (± 1.20%)0.67s (± 1.04%)+0.00s (+ 0.45%)0.65s0.68s
Check Time4.46s (± 0.56%)4.49s (± 0.38%)+0.02s (+ 0.49%)4.45s4.53s
Emit Time2.93s (± 0.81%)2.92s (± 0.63%)-0.01s (- 0.34%)2.86s2.96s
Total Time9.28s (± 0.27%)9.29s (± 0.36%)+0.01s (+ 0.15%)9.22s9.38s
material-ui - node (v10.16.3, x64)
Memory used462,573k (± 0.02%)462,570k (± 0.01%)-4k (- 0.00%)462,472k462,795k
Parse Time1.98s (± 0.54%)1.99s (± 1.00%)+0.01s (+ 0.45%)1.96s2.05s
Bind Time0.65s (± 0.80%)0.65s (± 1.12%)-0.00s (- 0.15%)0.64s0.67s
Check Time13.18s (± 0.82%)13.27s (± 0.74%)+0.09s (+ 0.65%)13.06s13.48s
Emit Time0.00s (± 0.00%)0.00s (± 0.00%)0.00s ( NaN%)0.00s0.00s
Total Time15.81s (± 0.71%)15.90s (± 0.74%)+0.09s (+ 0.60%)15.68s16.15s
Angular - node (v12.1.0, x64)
Memory used322,231k (± 0.03%)322,234k (± 0.08%)+3k (+ 0.00%)321,229k322,616k
Parse Time2.00s (± 0.64%)2.00s (± 0.73%)-0.00s (- 0.05%)1.97s2.03s
Bind Time0.81s (± 0.74%)0.81s (± 0.45%)-0.00s (- 0.00%)0.80s0.81s
Check Time4.70s (± 0.51%)4.71s (± 0.62%)+0.01s (+ 0.19%)4.65s4.79s
Emit Time5.35s (± 0.63%)5.37s (± 1.30%)+0.02s (+ 0.34%)5.28s5.57s
Total Time12.85s (± 0.45%)12.88s (± 0.77%)+0.02s (+ 0.18%)12.74s13.14s
Monaco - node (v12.1.0, x64)
Memory used321,803k (± 0.03%)321,840k (± 0.02%)+37k (+ 0.01%)321,744k322,039k
Parse Time1.54s (± 0.94%)1.54s (± 0.84%)+0.00s (+ 0.19%)1.51s1.57s
Bind Time0.69s (± 0.68%)0.69s (± 0.48%)-0.00s (- 0.00%)0.68s0.70s
Check Time4.77s (± 0.34%)4.79s (± 0.34%)+0.01s (+ 0.31%)4.75s4.82s
Emit Time2.80s (± 0.80%)2.80s (± 0.55%)+0.00s (+ 0.04%)2.77s2.83s
Total Time9.81s (± 0.23%)9.82s (± 0.28%)+0.02s (+ 0.18%)9.78s9.88s
TFS - node (v12.1.0, x64)
Memory used286,789k (± 0.01%)286,781k (± 0.02%)-7k (- 0.00%)286,610k286,910k
Parse Time1.24s (± 0.55%)1.23s (± 0.81%)-0.00s (- 0.24%)1.21s1.25s
Bind Time0.64s (± 0.97%)0.64s (± 1.13%)+0.00s (+ 0.47%)0.63s0.66s
Check Time4.38s (± 0.51%)4.35s (± 0.21%)-0.03s (- 0.59%)4.34s4.37s
Emit Time2.93s (± 0.73%)2.94s (± 0.76%)+0.01s (+ 0.38%)2.88s2.98s
Total Time9.18s (± 0.33%)9.17s (± 0.34%)-0.02s (- 0.19%)9.11s9.23s
material-ui - node (v12.1.0, x64)
Memory used440,797k (± 0.08%)440,893k (± 0.01%)+96k (+ 0.02%)440,794k441,026k
Parse Time2.01s (± 0.67%)2.00s (± 0.52%)-0.00s (- 0.20%)1.98s2.02s
Bind Time0.63s (± 0.94%)0.63s (± 0.54%)-0.00s (- 0.31%)0.63s0.64s
Check Time11.83s (± 0.79%)11.78s (± 0.63%)-0.05s (- 0.44%)11.62s12.02s
Emit Time0.00s (± 0.00%)0.00s (± 0.00%)0.00s ( NaN%)0.00s0.00s
Total Time14.47s (± 0.68%)14.42s (± 0.52%)-0.06s (- 0.40%)14.25s14.66s
Angular - node (v8.9.0, x64)
Memory used341,682k (± 0.02%)341,708k (± 0.02%)+26k (+ 0.01%)341,579k341,916k
Parse Time2.55s (± 0.63%)2.55s (± 0.45%)-0.00s (- 0.16%)2.53s2.58s
Bind Time0.86s (± 0.46%)0.87s (± 0.95%)+0.01s (+ 0.58%)0.85s0.89s
Check Time5.44s (± 0.61%)5.45s (± 0.31%)+0.01s (+ 0.26%)5.42s5.49s
Emit Time5.99s (± 1.74%)6.01s (± 1.78%)+0.01s (+ 0.22%)5.71s6.20s
Total Time14.85s (± 0.70%)14.87s (± 0.76%)+0.02s (+ 0.15%)14.55s15.05s
Monaco - node (v8.9.0, x64)
Memory used340,779k (± 0.01%)340,851k (± 0.01%)+72k (+ 0.02%)340,759k340,943k
Parse Time1.89s (± 0.40%)1.89s (± 0.36%)-0.00s (- 0.05%)1.87s1.90s
Bind Time0.89s (± 0.73%)0.89s (± 0.84%)-0.00s (- 0.11%)0.87s0.90s
Check Time5.52s (± 0.58%)5.53s (± 0.50%)+0.01s (+ 0.18%)5.48s5.61s
Emit Time3.24s (± 0.79%)3.24s (± 0.83%)0.00s ( 0.00%)3.20s3.31s
Total Time11.53s (± 0.46%)11.54s (± 0.45%)+0.01s (+ 0.07%)11.44s11.66s
TFS - node (v8.9.0, x64)
Memory used304,079k (± 0.01%)304,126k (± 0.01%)+47k (+ 0.02%)304,057k304,199k
Parse Time1.55s (± 0.56%)1.55s (± 0.36%)-0.00s (- 0.26%)1.54s1.56s
Bind Time0.67s (± 0.77%)0.68s (± 0.55%)+0.00s (+ 0.60%)0.67s0.68s
Check Time5.19s (± 0.53%)5.21s (± 0.60%)+0.02s (+ 0.46%)5.13s5.27s
Emit Time2.94s (± 0.49%)2.95s (± 1.47%)+0.00s (+ 0.17%)2.87s3.10s
Total Time10.35s (± 0.30%)10.38s (± 0.64%)+0.03s (+ 0.25%)10.21s10.53s
material-ui - node (v8.9.0, x64)
Memory used466,982k (± 0.01%)467,027k (± 0.01%)+46k (+ 0.01%)466,958k467,095k
Parse Time2.40s (± 0.54%)2.40s (± 0.61%)+0.01s (+ 0.29%)2.38s2.44s
Bind Time0.81s (± 1.03%)0.80s (± 0.93%)-0.01s (- 0.74%)0.78s0.81s
Check Time17.57s (± 0.78%)17.68s (± 0.82%)+0.11s (+ 0.61%)17.40s18.02s
Emit Time0.00s (± 0.00%)0.00s (± 0.00%)0.00s ( NaN%)0.00s0.00s
Total Time20.77s (± 0.62%)20.88s (± 0.63%)+0.11s (+ 0.52%)20.64s21.20s
Angular - node (v8.9.0, x86)
Memory used196,068k (± 0.03%)196,119k (± 0.03%)+52k (+ 0.03%)195,971k196,193k
Parse Time2.47s (± 0.60%)2.47s (± 0.91%)-0.01s (- 0.20%)2.43s2.53s
Bind Time0.99s (± 1.07%)1.01s (± 1.22%)+0.01s (+ 1.31%)0.97s1.03s
Check Time4.92s (± 0.84%)4.90s (± 0.51%)-0.01s (- 0.28%)4.84s4.96s
Emit Time5.92s (± 0.79%)5.98s (± 1.07%)+0.06s (+ 1.01%)5.89s6.18s
Total Time14.30s (± 0.36%)14.36s (± 0.45%)+0.05s (+ 0.35%)14.23s14.54s
Monaco - node (v8.9.0, x86)
Memory used193,831k (± 0.03%)193,883k (± 0.02%)+52k (+ 0.03%)193,783k193,978k
Parse Time1.91s (± 0.83%)1.94s (± 1.68%)+0.02s (+ 1.26%)1.89s2.02s
Bind Time0.70s (± 0.97%)0.70s (± 0.88%)-0.00s (- 0.43%)0.69s0.72s
Check Time5.57s (± 0.47%)5.62s (± 0.72%)+0.05s (+ 0.92%)5.55s5.74s
Emit Time2.67s (± 0.55%)2.67s (± 0.91%)+0.00s (+ 0.04%)2.62s2.74s
Total Time10.85s (± 0.30%)10.93s (± 0.69%)+0.08s (+ 0.72%)10.83s11.12s
TFS - node (v8.9.0, x86)
Memory used173,987k (± 0.03%)174,071k (± 0.04%)+83k (+ 0.05%)173,913k174,187k
Parse Time1.58s (± 0.23%)1.58s (± 0.51%)+0.01s (+ 0.38%)1.56s1.60s
Bind Time0.64s (± 0.74%)0.64s (± 0.81%)-0.00s (- 0.31%)0.63s0.65s
Check Time4.73s (± 0.68%)4.70s (± 0.59%)-0.03s (- 0.66%)4.64s4.78s
Emit Time2.82s (± 0.79%)2.78s (± 0.87%)-0.04s (- 1.38%)2.73s2.85s
Total Time9.77s (± 0.53%)9.70s (± 0.43%)-0.07s (- 0.70%)9.61s9.80s
material-ui - node (v8.9.0, x86)
Memory used264,399k (± 0.01%)264,457k (± 0.02%)+58k (+ 0.02%)264,370k264,565k
Parse Time2.47s (± 0.63%)2.47s (± 1.19%)+0.01s (+ 0.24%)2.43s2.58s
Bind Time0.72s (± 4.16%)0.70s (± 2.92%)🟩-0.02s (- 3.05%)0.67s0.76s
Check Time16.31s (± 0.78%)16.32s (± 0.81%)+0.01s (+ 0.07%)16.11s16.78s
Emit Time0.00s (± 0.00%)0.00s (± 0.00%)0.00s ( NaN%)0.00s0.00s
Total Time19.50s (± 0.74%)19.49s (± 0.75%)-0.00s (- 0.02%)19.28s20.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)
BenchmarkNameIterations
Current4046610
Baselinemaster10

@typescript-bot

Copy link
Copy Markdown
Contributor

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
Copy Markdown
ContributorAuthor

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
Copy Markdown
ContributorAuthor

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

@rbuckton

Copy link
Copy Markdown
ContributorAuthor

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

Copy link
Copy Markdown
Member

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.

}
}

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

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.

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

Copy link
Copy Markdown
ContributorAuthor

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
Copy Markdown
ContributorAuthor

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
Copy Markdown
ContributorAuthor

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
Copy Markdown
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
Copy Markdown
Member

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

@microsoftMicrosoft (microsoft) locked as resolved and limited conversation to collaborators Oct 21, 2025
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Author: TeamExperimentA fork with an experimental idea which might not make it into masterFor Uncommitted BugPR for untriaged, rejected, closed or missing bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@rbuckton@typescript-bot@AlCalzone@sandersn@DanielRosenwasser