Skip to content

Experiment: report functions with multiple returns that could be type predicates - #58173

Closed
Dan Vanderkam (danvk) wants to merge 5 commits into
microsoft:mainfrom
danvk:multi-return-error
Closed

Experiment: report functions with multiple returns that could be type predicates#58173
Dan Vanderkam (danvk) wants to merge 5 commits into
microsoft:mainfrom
danvk:multi-return-error

Conversation

@danvk

Copy link
Copy Markdown
Contributor

This is a side experiment for #58154. That PR extends type predicate inference to functions with multiple returns. It seems to have minimal perf impact and also minimal breakages. This makes you wonder… are there any such functions?

This fork of the PR should answer that question by reporting errors on all functions with 2+ return statements where it can infer a type predicate. To be clear, these aren't real errors! They're just identifying multi-return predicates in the wild.

There are three such function in TypeScript itself:

  1. tryAddPropertyAssignment in nodeFactory.ts:
functiontryAddPropertyAssignment(properties: PropertyAssignment[],propertyName: string,expression: Expression|undefined){if(expression){properties.push(createPropertyAssignment(propertyName,expression));returntrue;}returnfalse;}

This gets an inferred return type of expression is Expression. This is accurate, though not consequential for any of the code that calls this function.

  1. charIsPunctuation in patternMatcher.ts:
functioncharIsPunctuation(ch: number){switch(ch){caseCharacterCodes.exclamation:
caseCharacterCodes.doubleQuote:
// ...caseCharacterCodes._:
caseCharacterCodes.openBrace:
caseCharacterCodes.closeBrace:
returntrue;}returnfalse;}

This gets an inferred return type of ch is CharacterCodes._ | CharacterCodes.ampersand | CharacterCodes.asterisk | ... | CharacterCodes.slash. Again, this is accurate but not consequential for any of the calling code.

  1. An anonymous function in getLinkedEditingRangeAtPosition in services.ts:
consttag=findAncestor(token.parent,n=>{if(isJsxOpeningElement(n)||isJsxClosingElement(n)){returntrue;}returnfalse;});if(!tag)returnundefined;Debug.assert(isJsxOpeningElement(tag)||isJsxClosingElement(tag),"tag should be opening or closing element");

The arrow function becomes a type predicate with #58154 which changes the type of tag. This is a positive change that eliminates the need for the Debug.assert statement.

Of course, the function could have been written more succinctly as n => isJsxOpeningElement(n) || isJsxClosingElement(n) and the existing code would infer a type predicate.

I'm curious what other errors this reports on the user/top/etc suites. This will characterize whether this is a common pattern in the wild.

@RyanCavanaugh

Copy link
Copy Markdown
Member

TypeScript Bot (@typescript-bot) test it

@typescript-bot

TypeScript Bot (typescript-bot) commented Apr 12, 2024

Copy link
Copy Markdown
Contributor

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

CommandStatusResults
test top400✅ Started👀 Results
user test this✅ Started👀 Results
run dt✅ Started✅ Results
perf test this faster✅ Started👀 Results

@typescript-bot

Copy link
Copy Markdown
Contributor

Hey Ryan Cavanaugh (@RyanCavanaugh), the results of running the DT tests are ready.

Everything looks the same!

You can check the log here.

@typescript-bot

Copy link
Copy Markdown
Contributor

Ryan Cavanaugh (@RyanCavanaugh) Here are the results of running the user tests comparing main and refs/pull/58173/merge:

Something interesting changed - please have a look.

Details

pyright

/mnt/ts_downloads/_/m/pyright/build.sh

  • [NEW] error TS1499: Function with multiple returns is implicitly a type predicate.
    • /mnt/ts_downloads/_/m/pyright/pyright: ../pyright-internal/src/analyzer/parseTreeUtils.ts(2588,17)
    • /mnt/ts_downloads/_/m/pyright/pyright-internal: src/analyzer/parseTreeUtils.ts(2588,17)
    • /mnt/ts_downloads/_/m/pyright/vscode-pyright: ../pyright-internal/src/analyzer/parseTreeUtils.ts(2588,17)

@typescript-bot

Copy link
Copy Markdown
Contributor

Ryan Cavanaugh (@RyanCavanaugh)
The results of the perf run you requested are in!

Here they are:

tsc

Comparison Report - baseline..pr
MetricbaselineprDeltaBestWorstp-value
Angular - node (v18.15.0, x64)
Memory used296,989k (± 0.00%)297,001k (± 0.01%)~296,971k297,013kp=0.298 n=6
Parse Time2.69s (± 0.45%)2.69s (± 0.38%)~2.67s2.70sp=0.408 n=6
Bind Time0.82s (± 0.50%)0.82s (± 0.00%)~0.82s0.82sp=0.405 n=6
Check Time8.33s (± 0.42%)8.35s (± 0.37%)~8.32s8.40sp=0.469 n=6
Emit Time7.05s (± 0.31%)7.05s (± 0.32%)~7.01s7.07sp=1.000 n=6
Total Time18.89s (± 0.20%)18.90s (± 0.26%)~18.83s18.95sp=0.629 n=6
Compiler-Unions - node (v18.15.0, x64)
Memory used192,371k (± 0.74%)194,332k (± 0.94%)+1,961k (+ 1.02%)191,962k195,646kp=0.013 n=6
Parse Time2.01s (± 1.53%)2.02s (± 0.60%)~2.01s2.04sp=0.416 n=6
Bind Time1.07s (± 0.92%)1.07s (± 0.97%)~1.05s1.08sp=0.788 n=6
Check Time14.04s (± 0.18%)14.06s (± 0.50%)~13.96s14.13sp=0.574 n=6
Emit Time3.89s (± 2.32%)3.87s (± 1.04%)~3.79s3.90sp=0.292 n=6
Total Time21.01s (± 0.49%)21.02s (± 0.42%)~20.88s21.09sp=0.747 n=6
Monaco - node (v18.15.0, x64)
Memory used348,863k (± 0.01%)348,866k (± 0.00%)~348,847k348,882kp=0.688 n=6
Parse Time3.85s (± 0.86%)3.85s (± 1.12%)~3.79s3.90sp=0.936 n=6
Bind Time1.32s (± 0.62%)1.32s (± 0.80%)~1.30s1.33sp=0.865 n=6
Check Time10.26s (± 0.42%)10.27s (± 0.49%)~10.18s10.31sp=0.872 n=6
Emit Time5.99s (± 0.52%)6.00s (± 0.61%)~5.94s6.03sp=0.809 n=6
Total Time21.42s (± 0.32%)21.42s (± 0.23%)~21.35s21.50sp=1.000 n=6
TFS - node (v18.15.0, x64)
Memory used302,975k (± 0.01%)302,982k (± 0.00%)~302,964k303,001kp=0.471 n=6
Parse Time2.04s (± 0.51%)2.02s (± 0.67%)~2.01s2.04sp=0.161 n=6
Bind Time0.98s (± 0.77%)0.98s (± 0.77%)~0.97s0.99sp=1.000 n=6
Check Time6.47s (± 0.49%)6.45s (± 0.51%)~6.41s6.51sp=0.519 n=6
Emit Time3.58s (± 0.23%)3.58s (± 0.51%)~3.56s3.60sp=1.000 n=6
Total Time13.07s (± 0.27%)13.04s (± 0.31%)~12.97s13.08sp=0.170 n=6
material-ui - node (v18.15.0, x64)
Memory used511,624k (± 0.01%)511,617k (± 0.00%)~511,579k511,646kp=0.575 n=6
Parse Time4.01s (± 0.66%)4.02s (± 1.04%)~3.99s4.09sp=0.681 n=6
Bind Time1.46s (± 1.42%)1.45s (± 2.02%)~1.42s1.50sp=0.872 n=6
Check Time25.32s (± 0.24%)25.27s (± 0.18%)~25.22s25.35sp=0.148 n=6
Emit Time0.00s (± 0.00%)0.00s (± 0.00%)~0.00s0.00sp=1.000 n=6
Total Time30.79s (± 0.23%)30.75s (± 0.22%)~30.69s30.88sp=0.335 n=6
mui-docs - node (v18.15.0, x64)
Memory used1,748,693k (± 0.00%)1,748,679k (± 0.00%)~1,748,618k1,748,755kp=0.471 n=6
Parse Time9.96s (± 0.38%)10.03s (± 0.88%)~9.93s10.12sp=0.261 n=6
Bind Time3.35s (± 0.27%)3.36s (± 0.87%)~3.32s3.40sp=0.370 n=6
Check Time81.76s (± 0.27%)82.04s (± 0.60%)~81.29s82.68sp=0.230 n=6
Emit Time0.19s (± 2.81%)0.20s (± 5.07%)~0.19s0.22sp=0.138 n=6
Total Time95.26s (± 0.21%)95.63s (± 0.47%)~95.00s96.17sp=0.229 n=6
self-build-src - node (v18.15.0, x64)
Memory used2,306,333k (± 0.06%)2,307,414k (± 0.03%)~2,306,245k2,308,046kp=0.093 n=6
Parse Time6.04s (± 0.68%)6.03s (± 0.69%)~5.97s6.08sp=0.872 n=6
Bind Time2.23s (± 1.05%)2.24s (± 1.28%)~2.20s2.29sp=0.520 n=6
Check Time39.84s (± 0.28%)39.90s (± 0.39%)~39.72s40.17sp=0.423 n=6
Emit Time3.17s (± 3.58%)3.19s (± 3.58%)~3.04s3.35sp=1.000 n=6
Total Time51.30s (± 0.43%)51.38s (± 0.38%)~51.15s51.67sp=0.688 n=6
self-build-src-public-api - node (v18.15.0, x64)
Memory used2,380,781k (± 0.04%)2,381,898k (± 0.03%)~2,381,032k2,382,892kp=0.066 n=6
Parse Time7.63s (± 0.36%)7.66s (± 0.82%)~7.56s7.74sp=0.230 n=6
Bind Time2.52s (± 1.24%)2.51s (± 1.12%)~2.48s2.56sp=0.368 n=6
Check Time49.80s (± 0.92%)49.52s (± 0.53%)~49.22s49.97sp=0.298 n=6
Emit Time3.92s (± 1.70%)3.96s (± 1.99%)~3.83s4.03sp=0.261 n=6
Total Time63.87s (± 0.70%)63.65s (± 0.40%)~63.29s64.03sp=0.575 n=6
self-compiler - node (v18.15.0, x64)
Memory used419,069k (± 0.01%)419,192k (± 0.01%)+123k (+ 0.03%)419,131k419,245kp=0.005 n=6
Parse Time3.40s (± 0.76%)3.37s (± 2.43%)~3.21s3.44sp=0.808 n=6
Bind Time1.31s (± 1.01%)1.33s (± 4.80%)~1.26s1.45sp=0.466 n=6
Check Time18.04s (± 0.30%)18.02s (± 0.41%)~17.94s18.14sp=0.686 n=6
Emit Time1.37s (± 1.36%)1.37s (± 2.62%)~1.33s1.41sp=0.872 n=6
Total Time24.11s (± 0.20%)24.10s (± 0.32%)~24.01s24.22sp=0.748 n=6
vscode - node (v18.15.0, x64)
Memory used2,912,926k (± 0.00%)2,912,839k (± 0.00%)~2,912,683k2,912,925kp=0.066 n=6
Parse Time13.39s (± 0.21%)13.42s (± 0.34%)~13.35s13.49sp=0.255 n=6
Bind Time4.06s (± 0.40%)4.10s (± 2.29%)~4.05s4.29sp=0.807 n=6
Check Time72.46s (± 0.41%)72.48s (± 0.27%)~72.33s72.80sp=0.689 n=6
Emit Time19.48s (± 0.82%)20.14s (± 7.49%)~19.37s23.22sp=0.172 n=6
Total Time109.39s (± 0.39%)110.15s (± 1.31%)~109.31s113.06sp=0.199 n=6
webpack - node (v18.15.0, x64)
Memory used409,375k (± 0.01%)409,385k (± 0.01%)~409,324k409,429kp=0.689 n=6
Parse Time3.26s (± 0.82%)3.27s (± 1.19%)~3.24s3.32sp=0.466 n=6
Bind Time1.37s (± 0.75%)1.38s (± 0.76%)~1.37s1.40sp=0.098 n=6
Check Time14.38s (± 0.15%)14.39s (± 0.29%)~14.35s14.45sp=0.872 n=6
Emit Time0.00s (± 0.00%)0.00s (± 0.00%)~0.00s0.00sp=1.000 n=6
Total Time19.01s (± 0.23%)19.04s (± 0.21%)~18.99s19.08sp=0.191 n=6
xstate - node (v18.15.0, x64)
Memory used671,977k (± 0.01%)672,047k (± 0.02%)~671,934k672,265kp=0.689 n=6
Parse Time4.12s (± 0.84%)4.12s (± 0.74%)~4.08s4.17sp=1.000 n=6
Bind Time1.87s (± 0.83%)1.88s (± 0.87%)~1.86s1.90sp=0.511 n=6
Check Time3.43s (± 0.24%)3.45s (± 0.57%)~3.42s3.47sp=0.081 n=6
Emit Time0.03s (±19.35%)0.03s (±14.39%)~0.02s0.03sp=0.595 n=6
Total Time9.46s (± 0.48%)9.47s (± 0.58%)~9.40s9.56sp=0.746 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)
  • mui-docs - node (v18.15.0, x64)
  • self-build-src - node (v18.15.0, x64)
  • self-build-src-public-api - node (v18.15.0, x64)
  • self-compiler - node (v18.15.0, x64)
  • vscode - node (v18.15.0, x64)
  • webpack - node (v18.15.0, x64)
  • xstate - node (v18.15.0, x64)
BenchmarkNameIterations
Currentpr6
Baselinebaseline6

Developer Information:

Download Benchmarks

@jakebailey

Jake Bailey (jakebailey) commented Apr 12, 2024

Copy link
Copy Markdown
Member

@typescript-bot

Copy link
Copy Markdown
Contributor

Ryan Cavanaugh (@RyanCavanaugh) Here are the results of running the top 400 repos comparing main and refs/pull/58173/merge:

Something interesting changed - please have a look.

Details

facebook/lexical

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

tsconfig.json

Tencent/omi

6 of 19 projects failed to build with the old tsc and were ignored

packages/reactive-signal/tsconfig.json

packages/reactive-signal/examples/tsconfig.json

@jakebailey

Copy link
Copy Markdown
Member

I feel like it's telling that we're seeing so few of these, and that when we do, the code still compiles as it did before. That makes it seem like this isn't very valuable, and that the initial design was good enough.

@danvk

Copy link
Copy Markdown
ContributorAuthor

Closing this out since we've learned what we're going to from it.

@jakebailey

Copy link
Copy Markdown
Member

BTW thank you for sending these PRs in exactly the format we need to let the infra report something, super helpful.

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

Labels

For Uncommitted BugPR for untriaged, rejected, closed or missing bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@danvk@RyanCavanaugh@typescript-bot@jakebailey