Skip to content

Speed up narrowing of literal unions - #64044

Open
Jake Bailey (jakebailey) wants to merge 2 commits into
microsoft:mainfrom
jakebailey:fix-55948
Open

Speed up narrowing of literal unions#64044
Jake Bailey (jakebailey) wants to merge 2 commits into
microsoft:mainfrom
jakebailey:fix-55948

Conversation

@jakebailey

Copy link
Copy Markdown
Member

Fixes #55948

Version User CPU Wall time
Base 10.8–12.1 s 6.9–8.4 s
fix-55948 0.08–0.09 s 0.07–0.10 s

We'll see if this hurts too much elsewhere

Narrowing a union by a type predicate maps every source constituent
over every candidate constituent through the general type relation
machinery. Large generated literal unions therefore take quadratic
time even though matching literals only requires equality.

Intersect unions made entirely of non-enum literals using keyed sets.
Keep enum and mixed unions on the existing relation path because they
require broader assignability semantics. Also bypass relation work for
individual identical literals encountered by that fallback path.

Add coverage for narrowing mixed string and number literal unions,
including equal-valued literals of different primitive kinds.

Fixes microsoft#55948
Copilot AI balanced review requested due to automatic review settings August 26, 2026 22:59
@github-project-automation github-project-automation Bot moved this to Not started in PR Backlog Aug 26, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Optimizes literal-union narrowing for type predicates using direct literal comparisons and lookup-based filtering.

Changes:

  • Adds a fast path for non-enum literal unions.
  • Adds direct literal comparison during narrowing.
  • Adds a compiler regression test and baselines.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tsc/internal/checker/flow.go Implements literal-union narrowing optimizations.
tsc/testdata/tests/cases/compiler/narrowLiteralUnionByTypePredicate.ts Adds the regression test.
tsc/testdata/baselines/reference/compiler/narrowLiteralUnionByTypePredicate.types Records narrowed types.
tsc/testdata/baselines/reference/compiler/narrowLiteralUnionByTypePredicate.symbols Records symbol resolution.
tsc/testdata/baselines/reference/compiler/narrowLiteralUnionByTypePredicate.js Records emitted JavaScript.

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +928 to +933
if tLiteralFlags != 0 && tLiteralFlags == nLiteralFlags && !(t.flags&TypeFlagsEnumLiteral != 0 && n.flags&TypeFlagsEnumLiteral != 0) {
if t.AsLiteralType().value == n.AsLiteralType().value {
return t
}
return c.neverType
}
The per-literal fast path compared values and retained the source
constituent. When a plain string literal was narrowed by a predicate
whose candidate was the matching enum member, this lost the enum type
and made the result unassignable to that string enum member.

Retain the candidate when it carries enum identity and the source does
not. Continue using the existing relation machinery when both sides are
enum members, where equal underlying values are not sufficient.

Add a string-enum predicate regression that requires the narrowed value
to remain assignable to the asserted enum member.
@jakebailey

Copy link
Copy Markdown
Member Author

TypeScript Bot (@typescript-bot) perf test this faster

@typescript-automation

typescript-automation Bot commented Aug 28, 2026

Copy link
Copy Markdown

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

Command Status Results
perf test this faster ✅ Started 👀 Results

@typescript-automation

Copy link
Copy Markdown

Jake Bailey (@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
Compiler-Unions - native
Errors 41 41 ~ ~ ~ p=1.000 n=12
Symbols 115,480 115,480 ~ ~ ~ p=1.000 n=12
Types 96,945 96,945 ~ ~ ~ p=1.000 n=12
Memory Used 148,422k (± 0.59%) 148,748k (± 0.46%) ~ 146,386k 150,769k p=0.198 n=12
Memory Allocs 2,271,244 (± 0.01%) 2,271,214 (± 0.00%) ~ 2,270,985 2,271,571 p=0.932 n=12
Config Time 0.000s 0.000s ~ ~ ~ p=1.000 n=12
Parse Time 0.041s (± 3.31%) 0.041s (± 3.90%) ~ 0.035s 0.045s p=0.592 n=12
Bind Time 0.013s (±13.18%) 0.013s (±12.23%) ~ 0.010s 0.018s p=0.476 n=12
Check Time 0.491s (± 0.97%) 0.487s (± 0.78%) ~ 0.479s 0.496s p=0.270 n=12
Emit Time 0.271s (± 2.48%) 0.269s (± 1.82%) ~ 0.256s 0.278s p=0.765 n=12
Total Time 0.823s (± 0.64%) 0.816s (± 0.53%) -0.007s (- 0.82%) 0.810s 0.830s p=0.029 n=12
angular-1 - native
Errors 3 3 ~ ~ ~ p=1.000 n=12
Symbols 846,505 (± 0.11%) 847,558 (± 0.10%) ~ 844,254 849,242 p=0.078 n=12
Types 249,852 (± 0.00%) 249,851 (± 0.00%) ~ 249,850 249,853 p=0.213 n=12
Memory Used 793,483k (± 0.07%) 793,412k (± 0.06%) ~ 792,302k 794,625k p=0.713 n=12
Memory Allocs 12,952,351 (± 0.02%) 12,958,405 (± 0.08%) ~ 12,949,063 13,008,939 p=0.887 n=12
Config Time 0.016s (± 1.75%) 0.016s ~ ~ ~ p=0.217 n=12
Parse Time 0.271s (± 2.21%) 0.275s (± 2.75%) ~ 0.254s 0.293s p=0.486 n=12
Bind Time 0.066s (±14.55%) 0.063s (±12.23%) ~ 0.058s 0.101s p=0.165 n=12
Check Time 0s 0s ~ ~ ~ p=1.000 n=12
Emit Time 1.619s (± 0.79%) 1.622s (± 0.71%) ~ 1.605s 1.672s p=0.660 n=12
Total Time 1.989s (± 0.99%) 1.992s (± 0.96%) ~ 1.964s 2.078s p=0.899 n=12
mui-docs - native
Errors 11,276 (± 0.03%) 11,277 (± 0.02%) ~ 11,268 11,281 p=0.721 n=12
Symbols 4,268,528 4,268,528 ~ ~ ~ p=1.000 n=12
Types 1,593,260 1,593,260 ~ ~ ~ p=1.000 n=12
Memory Used 4,728,409k (± 0.04%) 4,728,116k (± 0.03%) ~ 4,723,833k 4,731,836k p=0.811 n=12
Memory Allocs 47,622,280 (± 0.04%) 47,631,652 (± 0.03%) ~ 47,594,353 47,665,398 p=0.319 n=12
Config Time 0.016s 0.016s (± 1.55%) ~ 0.015s 0.016s p=0.478 n=12
Parse Time 0.557s (± 2.08%) 0.547s (± 2.70%) ~ 0.498s 0.576s p=0.213 n=12
Bind Time 0.002s 0.002s ~ ~ ~ p=1.000 n=12
Check Time 16.310s (± 0.11%) 16.389s (± 0.42%) +0.079s (+ 0.49%) 16.295s 16.669s p=0.004 n=12
Emit Time 0.435s (± 1.76%) 0.432s (± 0.44%) ~ 0.429s 0.439s p=0.876 n=12
Total Time 18.069s (± 0.21%) 18.128s (± 0.50%) ~ 17.876s 18.449s p=0.203 n=12
strada-build-src - native
Errors 0 0 ~ ~ ~ p=1.000 n=12
Symbols 1,394,252 1,394,252 ~ ~ ~ p=1.000 n=12
Types 443,418 443,418 ~ ~ ~ p=1.000 n=12
Memory Used 1,650,175k (± 0.28%) 1,649,850k (± 0.28%) ~ 1,636,294k 1,661,047k p=0.977 n=12
Memory Allocs 97,080,221 (± 0.03%) 97,068,909 (± 0.04%) ~ 96,982,250 97,194,752 p=0.514 n=12
Config Time 0.004s (±14.46%) 0.004s (± 9.48%) ~ 0.003s 0.005s p=0.985 n=12
Parse Time 0.199s (± 2.30%) 0.200s (± 2.81%) ~ 0.183s 0.218s p=0.943 n=12
Bind Time 0.000s 0.000s ~ ~ ~ p=1.000 n=12
Check Time 1.808s (± 0.56%) 1.789s (± 0.42%) -0.019s (- 1.04%) 1.773s 1.808s p=0.008 n=12
Emit Time 0.274s (± 5.67%) 0.280s (± 5.44%) ~ 0.244s 0.313s p=0.560 n=12
Total Time 24.759s (± 0.77%) 24.632s (± 0.59%) ~ 24.217s 25.024s p=0.219 n=12
strada-compiler - native
Errors 0 0 ~ ~ ~ p=1.000 n=12
Symbols 337,531 337,531 ~ ~ ~ p=1.000 n=12
Types 198,830 198,830 ~ ~ ~ p=1.000 n=12
Memory Used 319,502k (± 0.01%) 319,547k (± 0.03%) ~ 319,279k 319,797k p=0.198 n=12
Memory Allocs 4,673,094 (± 0.01%) 4,672,921 (± 0.01%) ~ 4,671,914 4,673,958 p=0.514 n=12
Config Time 0.001s 0.001s ~ ~ ~ p=1.000 n=12
Parse Time 0.117s (± 4.18%) 0.117s (± 4.43%) ~ 0.104s 0.130s p=0.787 n=12
Bind Time 0.000s 0.000s ~ ~ ~ p=1.000 n=12
Check Time 1.042s (± 0.61%) 1.038s (± 0.71%) ~ 1.018s 1.054s p=0.385 n=12
Emit Time 0.129s (±13.91%) 0.147s (±11.09%) ~ 0.105s 0.184s p=0.124 n=12
Total Time 1.344s (± 0.96%) 1.356s (± 0.72%) ~ 1.336s 1.387s p=0.091 n=12
ts-pre-modules - native
Errors 87 87 ~ ~ ~ p=1.000 n=12
Symbols 305,179 305,179 ~ ~ ~ p=1.000 n=12
Types 181,876 181,876 ~ ~ ~ p=1.000 n=12
Memory Used 277,180k (± 0.03%) 277,212k (± 0.03%) ~ 277,012k 277,355k p=0.452 n=12
Memory Allocs 1,642,076 (± 0.01%) 1,641,657 (± 0.01%) -420 (- 0.03%) 1,641,269 1,642,136 p=0.008 n=12
Config Time 0.000s (±146.91%) 0.000s (±217.90%) ~ 0.000s 0.001s p=1.000 n=12
Parse Time 0.102s (± 4.67%) 0.101s (± 4.01%) ~ 0.091s 0.111s p=0.619 n=12
Bind Time 0.038s (±12.12%) 0.038s (±11.28%) ~ 0.030s 0.053s p=0.680 n=12
Check Time 0.823s (± 0.51%) 0.823s (± 0.61%) ~ 0.810s 0.838s p=0.765 n=12
Emit Time 0.000s 0.000s ~ ~ ~ p=1.000 n=12
Total Time 0.977s (± 0.89%) 0.976s (± 0.69%) ~ 0.956s 0.992s p=0.788 n=12
vscode - native
Errors 347 347 ~ ~ ~ p=1.000 n=12
Symbols 8,129,562 8,129,562 ~ ~ ~ p=1.000 n=12
Types 2,798,292 2,798,292 ~ ~ ~ p=1.000 n=12
Memory Used 5,493,055k (± 0.02%) 5,492,558k (± 0.01%) ~ 5,491,071k 5,494,419k p=0.551 n=12
Memory Allocs 42,302,535 (± 0.06%) 42,285,996 (± 0.04%) -16,540 (- 0.04%) 42,272,965 42,371,366 p=0.033 n=12
Config Time 0.064s (± 0.42%) 0.064s (± 0.51%) ~ 0.063s 0.065s p=0.832 n=12
Parse Time 1.647s (± 2.74%) 1.668s (± 2.51%) ~ 1.507s 1.733s p=0.434 n=12
Bind Time 0.381s (±11.77%) 0.367s (±11.28%) ~ 0.341s 0.575s p=0.417 n=12
Check Time 9.957s (± 1.35%) 10.011s (± 1.14%) ~ 9.550s 10.141s p=0.854 n=12
Emit Time 3.481s (±10.68%) 3.300s (± 8.82%) 🟩-0.181s (- 5.20%) 3.077s 4.430s p=0.043 n=12
Total Time 15.624s (± 1.65%) 15.502s (± 1.18%) ~ 15.295s 16.240s p=0.378 n=12
webpack - native
Errors 540 540 ~ ~ ~ p=1.000 n=12
Symbols 854,568 854,568 ~ ~ ~ p=1.000 n=12
Types 392,241 392,241 ~ ~ ~ p=1.000 n=12
Memory Used 654,123k (± 0.03%) 653,893k (± 0.02%) ~ 653,694k 654,307k p=0.068 n=12
Memory Allocs 4,372,286 (± 0.04%) 4,372,235 (± 0.03%) ~ 4,370,229 4,376,188 p=0.977 n=12
Config Time 0.008s 0.008s ~ ~ ~ p=1.000 n=12
Parse Time 0.213s (± 2.29%) 0.213s (± 2.54%) ~ 0.200s 0.227s p=0.831 n=12
Bind Time 0.045s (±11.42%) 0.045s (±18.04%) ~ 0.039s 0.086s p=0.183 n=12
Check Time 1.367s (± 0.67%) 1.382s (± 0.56%) +0.015s (+ 1.13%) 1.356s 1.395s p=0.009 n=12
Emit Time 0.000s 0.000s ~ ~ ~ p=1.000 n=12
Total Time 1.658s (± 0.35%) 1.671s (± 0.49%) +0.014s (+ 0.81%) 1.649s 1.697s p=0.005 n=12
xstate-main - native
Errors 0 0 ~ ~ ~ p=1.000 n=12
Symbols 1,022,487 1,022,487 ~ ~ ~ p=1.000 n=12
Types 376,962 376,962 ~ ~ ~ p=1.000 n=12
Memory Used 603,457k (± 0.02%) 603,484k (± 0.01%) ~ 603,248k 603,681k p=0.932 n=12
Memory Allocs 4,754,187 (± 0.04%) 4,755,753 (± 0.08%) ~ 4,750,883 4,770,668 p=0.887 n=12
Config Time 0.003s (±10.34%) 0.003s (±10.34%) ~ 0.002s 0.003s p=1.000 n=12
Parse Time 0.145s (± 3.24%) 0.147s (± 3.23%) ~ 0.138s 0.159s p=0.921 n=12
Bind Time 0.042s (±18.18%) 0.039s (±13.45%) ~ 0.033s 0.062s p=0.763 n=12
Check Time 1.036s (± 0.75%) 1.039s (± 1.13%) ~ 1.011s 1.083s p=0.989 n=12
Emit Time 0.000s 0.000s ~ ~ ~ p=1.000 n=12
Total Time 1.237s (± 1.06%) 1.238s (± 1.03%) ~ 1.203s 1.275s p=0.988 n=12
System info unknown
Hosts
  • native
Scenarios
  • Compiler-Unions - native
  • angular-1 - native
  • mui-docs - native
  • strada-build-src - native
  • strada-compiler - native
  • ts-pre-modules - native
  • vscode - native
  • webpack - native
  • xstate-main - native
Benchmark Name Iterations
Current pr 12
Baseline baseline 12

Developer Information:

Download Benchmarks

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

Labels

None yet

Projects

Status: Not started

Development

Successfully merging this pull request may close these issues.

Performance regression from #49625 and #52984

2 participants