Skip to content

Skip mark/rewind in tryParseTypeArgumentsInExpression when token isn't <#4234

Open
mds-ant wants to merge 1 commit into
microsoft:mainfrom
mds-ant:perf/parser-typeargs-early
Open

Skip mark/rewind in tryParseTypeArgumentsInExpression when token isn't <#4234
mds-ant wants to merge 1 commit into
microsoft:mainfrom
mds-ant:perf/parser-typeargs-early

Conversation

@mds-ant

@mds-ant mds-ant commented Jun 6, 2026

Copy link
Copy Markdown

Context

tryParseTypeArgumentsInExpression is called for every member-access dot and call-expression rest. It currently always saves the full parser/ scanner state, calls reScanLessThanToken, and rewinds when no type argument list is present. reScanLessThanToken only mutates state when the current token is << (splitting it into <). For any other token the mark/rewind pair is a no-op.

This PR

This PR hoists the JS-file check and a </<< token check above p.mark() so the common path returns immediately without copying state. The remaining guard only needs the re-scan, since the JS-file condition is already known to be false.

This PR was assisted by Claude Code.

Performance

goos: darwin
goarch: arm64
pkg: github.com/microsoft/typescript-go/internal/parser
cpu: Apple M1 Pro
                                                      │ /tmp/old.txt │            /tmp/new.txt             │
                                                      │    sec/op    │   sec/op     vs base                │
Parse/empty.ts-10                                        387.7n ± 0%   387.8n ± 0%        ~ (p=1.000 n=25)
Parse/checker.ts-10                                      33.78m ± 0%   29.14m ± 0%  -13.74% (p=0.000 n=25)
Parse/dom.generated.d.ts-10                              15.48m ± 0%   15.43m ± 0%   -0.29% (p=0.000 n=25)
Parse/Herebyfile.mjs-10                                  605.8µ ± 0%   557.3µ ± 0%   -8.01% (p=0.000 n=25)
Parse/jsxComplexSignatureHasApplicabilityError.tsx-10    177.0µ ± 0%   175.9µ ± 0%   -0.62% (p=0.000 n=25)
geomean                                                  465.0µ        443.2µ        -4.69%

                                                      │ /tmp/old.txt │             /tmp/new.txt              │
                                                      │     B/op     │     B/op      vs base                 │
Parse/empty.ts-10                                       1.047Ki ± 0%   1.047Ki ± 0%       ~ (p=1.000 n=25) ¹
Parse/checker.ts-10                                     26.44Mi ± 0%   26.44Mi ± 0%  -0.00% (p=0.001 n=25)
Parse/dom.generated.d.ts-10                             11.08Mi ± 0%   11.08Mi ± 0%       ~ (p=0.863 n=25)
Parse/Herebyfile.mjs-10                                 472.4Ki ± 0%   472.4Ki ± 0%       ~ (p=0.255 n=25)
Parse/jsxComplexSignatureHasApplicabilityError.tsx-10   175.5Ki ± 0%   175.5Ki ± 0%       ~ (p=0.869 n=25)
geomean                                                 484.4Ki        484.4Ki       -0.00%
¹ all samples are equal

                                                      │ /tmp/old.txt │             /tmp/new.txt             │
                                                      │  allocs/op   │  allocs/op   vs base                 │
Parse/empty.ts-10                                         4.000 ± 0%    4.000 ± 0%       ~ (p=1.000 n=25) ¹
Parse/checker.ts-10                                      12.96k ± 0%   12.96k ± 0%       ~ (p=0.138 n=25)
Parse/dom.generated.d.ts-10                              3.255k ± 0%   3.255k ± 0%       ~ (p=1.000 n=25)
Parse/Herebyfile.mjs-10                                  1.772k ± 0%   1.772k ± 0%       ~ (p=1.000 n=25) ¹
Parse/jsxComplexSignatureHasApplicabilityError.tsx-10     196.0 ± 0%    196.0 ± 0%       ~ (p=1.000 n=25) ¹
geomean                                                   567.0         567.0       +0.00%
¹ all samples are equal

…t `<`

tryParseTypeArgumentsInExpression is called for every member-access dot
and call-expression rest. It currently always saves the full parser/
scanner state, calls reScanLessThanToken, and rewinds when no type
argument list is present.

reScanLessThanToken only mutates state when the current token is `<<`
(splitting it into `<`); for any other token the mark/rewind pair is a
no-op. Hoist the JS-file check and a `<`/`<<` token check above
p.mark() so the common path returns immediately without copying state.

The remaining guard only needs the reScan, since the JS-file condition
is already known to be false.
@jakebailey

Copy link
Copy Markdown
Member

Do you have a benchmark that would show where this matters? And how often it matters?

@mds-ant

mds-ant commented Jun 6, 2026

Copy link
Copy Markdown
Author

Do you have a benchmark that would show where this matters? And how often it matters?

Yes, I'll add a benchmark before marking the PR ready for review!

@mds-ant mds-ant marked this pull request as ready for review June 6, 2026 23:39
Copilot AI review requested due to automatic review settings June 6, 2026 23:39

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

Note

Copilot was unable to run its full agentic suite in this review.

Optimizes speculative parsing of type arguments in expressions by adding early-exit checks before saving parser state.

Changes:

  • Adds cheap preconditions to avoid mark/rewind when parsing can’t possibly start.
  • Skips speculative parsing in JavaScript files and when current token isn’t </<<.

@RyanCavanaugh RyanCavanaugh added the No linked issue This PR doesn't say what bug it fixes label Jun 8, 2026
@RyanCavanaugh RyanCavanaugh added this to the Possible Improvement milestone Jun 8, 2026
@jakebailey

Copy link
Copy Markdown
Member

@typescript-bot perf test this faster

@typescript-automation

typescript-automation Bot commented Jun 8, 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

@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 4 4 ~ ~ ~ p=1.000 n=6
Symbols 81,789 (± 0.06%) 81,780 (± 0.04%) ~ 81,747 81,841 p=0.575 n=6
Types 98,821 98,821 ~ ~ ~ p=1.000 n=6
Memory Used 180,282k (± 0.84%) 180,799k (± 0.37%) ~ 180,243k 182,147k p=0.298 n=6
Memory Allocs 1,768,115 (± 0.01%) 1,767,929 (± 0.01%) ~ 1,767,684 1,768,209 p=0.298 n=6
Config Time 0.000s 0.000s ~ ~ ~ p=1.000 n=6
Parse Time 0.061s (± 7.41%) 0.055s (± 2.75%) 🟩-0.006s (-10.63%) 0.052s 0.056s p=0.019 n=6
Bind Time 0.017s (±23.53%) 0.018s (±24.43%) ~ 0.012s 0.022s p=0.517 n=6
Check Time 0s 0s ~ ~ ~ p=1.000 n=6
Emit Time 0.846s (± 1.55%) 0.858s (± 1.34%) ~ 0.843s 0.873s p=0.199 n=6
Total Time 0.925s (± 1.68%) 0.932s (± 1.43%) ~ 0.914s 0.948s p=0.471 n=6
angular-1 - native
Errors 3 3 ~ ~ ~ p=1.000 n=6
Symbols 884,445 (± 0.08%) 884,952 (± 0.07%) ~ 883,994 885,564 p=0.173 n=6
Types 263,954 (± 0.00%) 263,954 (± 0.00%) ~ 263,952 263,956 p=0.801 n=6
Memory Used 831,344k (± 0.10%) 831,605k (± 0.13%) ~ 830,565k 833,642k p=0.810 n=6
Memory Allocs 6,696,574 (± 0.23%) 6,704,581 (± 0.36%) ~ 6,681,438 6,734,120 p=0.689 n=6
Config Time 0.017s (± 2.98%) 0.018s (± 6.85%) ~ 0.017s 0.020s p=0.923 n=6
Parse Time 0.241s (± 5.45%) 0.237s (± 8.79%) ~ 0.214s 0.263s p=0.471 n=6
Bind Time 0.041s (±26.86%) 0.051s (±44.06%) ~ 0.035s 0.092s p=1.000 n=6
Check Time 0s 0s ~ ~ ~ p=1.000 n=6
Emit Time 1.858s (± 1.62%) 1.857s (± 2.37%) ~ 1.795s 1.929s p=0.936 n=6
Total Time 2.168s (± 0.98%) 2.173s (± 2.25%) ~ 2.113s 2.257s p=1.000 n=6
mui-docs - native
Errors 11,244 (± 0.05%) 11,245 (± 0.02%) ~ 11,241 11,247 p=0.867 n=6
Symbols 4,179,255 4,179,255 ~ ~ ~ p=1.000 n=6
Types 1,526,525 1,526,525 ~ ~ ~ p=1.000 n=6
Memory Used 4,866,664k (± 0.05%) 4,867,325k (± 0.04%) ~ 4,863,992k 4,869,992k p=0.689 n=6
Memory Allocs 94,114,094 (±14.81%) 88,383,296 (±10.22%) ~ 78,976,372 104,244,348 p=0.575 n=6
Config Time 0.017s (± 7.42%) 0.017s (± 5.07%) ~ 0.016s 0.018s p=0.753 n=6
Parse Time 1.127s (±19.83%) 1.013s (±12.10%) ~ 0.914s 1.244s p=0.230 n=6
Bind Time 0.002s 0.002s ~ ~ ~ p=1.000 n=6
Check Time 16.602s (± 0.48%) 16.501s (± 0.69%) ~ 16.372s 16.643s p=0.173 n=6
Emit Time 0.440s (± 1.71%) 0.551s (±32.09%) ~ 0.435s 0.783s p=0.629 n=6
Total Time 18.853s (± 1.17%) 18.733s (± 1.15%) ~ 18.430s 19.081s p=0.575 n=6
self-build-src - native
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 1,396,711 1,396,711 ~ ~ ~ p=1.000 n=6
Types 442,164 442,164 ~ ~ ~ p=1.000 n=6
Memory Used 1,647,560k (± 0.40%) 1,649,522k (± 0.27%) ~ 1,644,566k 1,657,254k p=0.298 n=6
Memory Allocs 57,506,660 (± 0.15%) 57,529,270 (± 0.10%) ~ 57,438,659 57,583,857 p=0.575 n=6
Config Time 0.017s (±52.33%) 0.021s (±31.43%) ~ 0.014s 0.033s p=0.295 n=6
Parse Time 0.288s (± 2.86%) 0.278s (± 7.37%) ~ 0.252s 0.307s p=0.471 n=6
Bind Time 0.000s 0.000s ~ ~ ~ p=1.000 n=6
Check Time 2.347s (± 1.08%) 2.336s (± 0.89%) ~ 2.305s 2.364s p=0.471 n=6
Emit Time 0.216s (± 4.11%) 0.220s (± 3.05%) ~ 0.210s 0.228s p=0.470 n=6
Total Time 29.750s (± 0.73%) 29.803s (± 1.39%) ~ 29.333s 30.430s p=0.936 n=6
self-compiler - native
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 338,176 338,176 ~ ~ ~ p=1.000 n=6
Types 199,522 199,522 ~ ~ ~ p=1.000 n=6
Memory Used 332,123k (± 0.02%) 332,109k (± 0.03%) ~ 331,970k 332,239k p=0.936 n=6
Memory Allocs 2,557,457 (± 0.03%) 2,557,295 (± 0.04%) ~ 2,556,285 2,558,794 p=0.575 n=6
Config Time 0.001s 0.001s ~ ~ ~ p=1.000 n=6
Parse Time 0.131s (± 3.35%) 0.130s (± 4.48%) ~ 0.125s 0.140s p=0.418 n=6
Bind Time 0.000s 0.000s ~ ~ ~ p=1.000 n=6
Check Time 1.402s (± 1.37%) 1.387s (± 0.80%) ~ 1.365s 1.396s p=0.054 n=6
Emit Time 0.079s (± 8.72%) 0.088s (±10.33%) 🔻+0.009s (+11.18%) 0.079s 0.099s p=0.030 n=6
Total Time 1.668s (± 0.72%) 1.660s (± 0.67%) ~ 1.647s 1.674s p=0.336 n=6
ts-pre-modules - native
Errors 3 3 ~ ~ ~ p=1.000 n=6
Symbols 97,488 97,488 ~ ~ ~ p=1.000 n=6
Types 356 356 ~ ~ ~ p=1.000 n=6
Memory Used 133,412k (± 0.02%) 133,391k (± 0.01%) ~ 133,355k 133,404k p=0.199 n=6
Memory Allocs 193,606 (± 0.17%) 193,521 (± 0.23%) ~ 193,152 194,376 p=0.471 n=6
Config Time 0.001s 0.001s ~ ~ ~ p=1.000 n=6
Parse Time 0.119s (± 4.68%) 0.113s (± 4.00%) ~ 0.110s 0.122s p=0.062 n=6
Bind Time 0.035s (±12.37%) 0.038s (±13.95%) ~ 0.029s 0.044s p=0.336 n=6
Check Time 0s 0s ~ ~ ~ p=1.000 n=6
Emit Time 0.000s 0.000s ~ ~ ~ p=1.000 n=6
Total Time 0.158s (± 4.71%) 0.155s (± 4.86%) ~ 0.143s 0.164s p=0.689 n=6
vscode - native
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 6,561,237 6,561,237 ~ ~ ~ p=1.000 n=6
Types 2,306,257 2,306,257 ~ ~ ~ p=1.000 n=6
Memory Used 4,436,438k (± 0.03%) 4,436,446k (± 0.02%) ~ 4,435,531k 4,437,750k p=0.936 n=6
Memory Allocs 31,806,669 (± 0.09%) 31,812,124 (± 0.09%) ~ 31,783,865 31,862,770 p=0.575 n=6
Config Time 0.065s (± 9.23%) 0.068s (± 9.23%) ~ 0.063s 0.079s p=0.421 n=6
Parse Time 0.835s (± 4.38%) 0.850s (± 4.80%) ~ 0.797s 0.920s p=0.471 n=6
Bind Time 0.151s (±36.07%) 0.158s (±40.83%) ~ 0.127s 0.290s p=0.296 n=6
Check Time 8.469s (± 0.35%) 8.439s (± 0.65%) ~ 8.379s 8.541s p=0.128 n=6
Emit Time 2.240s (± 7.80%) 2.245s (± 8.80%) ~ 1.927s 2.532s p=0.689 n=6
Total Time 11.778s (± 0.88%) 11.778s (± 1.27%) ~ 11.632s 12.052s p=0.689 n=6
webpack - native
Errors 583 583 ~ ~ ~ p=1.000 n=6
Symbols 717,611 717,611 ~ ~ ~ p=1.000 n=6
Types 339,294 339,294 ~ ~ ~ p=1.000 n=6
Memory Used 564,788k (± 0.02%) 564,853k (± 0.02%) ~ 564,742k 565,093k p=0.423 n=6
Memory Allocs 4,074,948 (± 0.36%) 4,082,484 (± 0.35%) ~ 4,058,848 4,096,646 p=0.471 n=6
Config Time 0.010s (±25.70%) 0.011s (±26.74%) ~ 0.007s 0.013s p=0.868 n=6
Parse Time 0.147s (± 3.31%) 0.152s (± 3.25%) ~ 0.143s 0.156s p=0.077 n=6
Bind Time 0.031s (±32.81%) 0.032s (±31.31%) ~ 0.022s 0.046s p=0.748 n=6
Check Time 1.085s (± 0.76%) 1.086s (± 1.86%) ~ 1.062s 1.117s p=0.936 n=6
Emit Time 0.001s 0.001s (±34.96%) ~ 0.001s 0.002s p=0.405 n=6
Total Time 1.287s (± 0.96%) 1.295s (± 0.76%) ~ 1.283s 1.310s p=0.296 n=6
xstate-main - native
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 1,060,815 1,060,815 ~ ~ ~ p=1.000 n=6
Types 384,533 384,533 ~ ~ ~ p=1.000 n=6
Memory Used 638,509k (± 0.02%) 638,577k (± 0.01%) ~ 638,511k 638,729k p=0.630 n=6
Memory Allocs 5,164,443 (± 0.04%) 5,169,152 (± 0.08%) +4,709 (+ 0.09%) 5,164,334 5,175,014 p=0.031 n=6
Config Time 0.005s (±11.05%) 0.005s (±17.48%) ~ 0.003s 0.005s p=0.752 n=6
Parse Time 0.142s (± 6.62%) 0.143s (± 2.43%) ~ 0.138s 0.146s p=1.000 n=6
Bind Time 0.035s (±23.54%) 0.032s (±16.87%) ~ 0.026s 0.041s p=0.469 n=6
Check Time 1.281s (± 0.48%) 1.284s (± 0.52%) ~ 1.274s 1.292s p=0.336 n=6
Emit Time 0.001s 0.001s ~ ~ ~ p=1.000 n=6
Total Time 1.468s (± 0.59%) 1.468s (± 0.88%) ~ 1.448s 1.483s p=0.748 n=6
System info unknown
Hosts
  • native
Scenarios
  • Compiler-Unions - native
  • angular-1 - native
  • mui-docs - native
  • self-build-src - native
  • self-compiler - native
  • ts-pre-modules - native
  • vscode - native
  • webpack - native
  • xstate-main - native
Benchmark Name Iterations
Current pr 6
Baseline baseline 6

Developer Information:

Download Benchmarks

Comment thread internal/parser/parser.go
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

No linked issue This PR doesn't say what bug it fixes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants