Skip to content

Re-scan '<<' when parsing type arguments in heritage clauses, typeof queries, and JSX - #64716

Open
ahmad65saad wants to merge 3 commits into
microsoft:mainfrom
ahmad65saad:fix-shift-token-in-type-arguments
Open

ahmad65saad wants to merge 3 commits into
microsoft:mainfrom
ahmad65saad:fix-shift-token-in-type-arguments

Conversation

@ahmad65saad

@ahmad65saad ahmad65saad commented Oct 10, 2026 •

Copy link
Copy Markdown

Fixes #47410

The scanner produces << as a single token, and the parser is expected to call reScanLessThanToken() wherever a < may open a type argument list. parseTypeArgumentsOfTypeReference and tryParseTypeArgumentsInExpression already do this, but parseTypeArguments only checked for <. So when the first type argument itself starts with < (e.g. a generic function type), the list was never parsed and the rest was misread as a shift/relational expression:

declare const f: <T>(x: T) => T;
declare class B<F> {}
interface I<F> {}
interface J<F> {}

class C1 implements I<<T>(x: T) => T> {}   // error before, OK now
class C2 extends B<<T>(x: T) => T> {}      // error before, OK now
interface I2 extends J<<T>(x: T) => T> {}  // error before, OK now
type A1 = typeof f<<T>(x: T) => T>;        // error before, OK now
<Comp<<T>(x: T) => T> value={f} />;        // error before, OK now

const e = f<<T>(x: T) => T>;               // already worked (expression path re-scans)

Fix

  • parseTypeArguments now re-scans << before checking for <. This covers heritage clauses (parseExpressionWithTypeArguments) and JSX opening/self-closing elements. In those positions << can't be a left shift, so no valid code changes meaning. JSDoc @augments/@implements also go through parseTypeArguments, but the JSDoc scanner never produces << there, so they already worked; jsdocAugments_genericFunctionTypeArgument.ts pins that (its baselines are identical with and without this change).
  • typeof type queries are the exception: there << can legitimately be a left shift after a type in an expression, e.g. a as typeof x << 1, which compiles today. So in parseTypeQuery, when the token is <<, the type arguments are parsed speculatively (mark/rewind, same diagnostics-count check as skipParameterStart) and dropped if they don't parse cleanly.

Tests

New test parseGenericArrowRatherThanLeftShift2.tsx covers each position above, plus the instantiation/call expression forms, and ordinary << shift expressions (including inside JSX attributes and after as typeof / satisfies typeof) to make sure they still parse as shifts. The first commit adds the test with the old (erroring) baselines so the change is visible in the second commit's diff. No other baselines changed.

Note: the JSDoc test's baseline includes TS8023 (JSDoc '@augments A' does not match the 'extends A' clause). That also happens on main without this change: checkJSDocAugmentsTagMatchesExtends compares the two A<<T>(x: T) => T> types with isTypeIdenticalTo, and the separately written generic function types aren't identical. It's a checker issue unrelated to parsing, so I left it out of scope here.

  • npx hereby test ✅
  • npx hereby lint ✅
  • npx hereby check:format ✅
  • npx hereby validate ✅

This PR was written in part with the assistance of generative AI (Claude).

parseTypeArguments only checked for a '<' token, so a type argument list
whose first type starts with '<' (e.g. a generic function type) was scanned
as '<<' and never parsed. This affected heritage clauses, typeof type
queries, and JSX opening elements. Re-scan the token first, as the type
reference and expression paths already do.

In a typeof type query, '<<' can also be a left shift following a type in
an expression, as in `x as typeof y << 1`, which already compiles. There the
type arguments are parsed speculatively and only kept if they parse without
errors; otherwise the '<<' is left alone.

Fixes microsoft#47410
Copilot AI balanced review requested due to automatic review settings October 10, 2026 02:47
@typescript-automation typescript-automation Bot added the For Backlog Bug PRs that fix a backlog bug label Oct 10, 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.

🟡 Changes recommended

The newly affected JSDoc parsing path lacks regression coverage.

1 open finding
What changed in this PR

Fixes parsing of generic function types beginning with << in type arguments while preserving left-shift expressions.

Changes:

  • Re-scans << when parsing type arguments.
  • Speculatively handles ambiguous typeof queries.
  • Adds regression coverage and baselines.
File Description
tsc/​internal/​parser/​parser.go Updates type-argument parsing and ambiguity handling.
tsc/​testdata/​tests/​cases/​compiler/​parseGenericArrowRatherThanLeftShift2.tsx Adds parser regression cases.
tsc/​testdata/​baselines/​reference/​compiler/​parseGenericArrowRatherThanLeftShift2.types Records inferred types.
tsc/​testdata/​baselines/​reference/​compiler/​parseGenericArrowRatherThanLeftShift2.symbols Records resolved symbols.
tsc/​testdata/​baselines/​reference/​compiler/​parseGenericArrowRatherThanLeftShift2.js Records emitted JavaScript.

🧠 Review effort: Balanced


func (p *Parser) parseTypeArguments() *ast.NodeList {
if p.token == ast.KindLessThanToken {
if p.reScanLessThanToken() == ast.KindLessThanToken {
@ahmad65saad

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

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.

🟢 Approval recommended

The parser ambiguity is handled conservatively and the affected contexts have focused regression coverage.

1 open finding

🧠 Review effort: Balanced

@jakebailey

Copy link
Copy Markdown
Member

I think this is right and esbuild also accepts this syntax, which is good.

TypeScript Bot (@typescript-bot) test it

@typescript-automation

typescript-automation Bot commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

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

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

@typescript-automation

Copy link
Copy Markdown
Contributor

Hey Jake Bailey (@jakebailey), the results of running the DT tests are ready.

Everything looks the same!

You can check the log here.

@typescript-automation

Copy link
Copy Markdown
Contributor

Jake Bailey (@jakebailey) Here are the results of running the user tests with tsc comparing baseline and pr:

Everything looks good!

@typescript-automation

Copy link
Copy Markdown
Contributor

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,276 115,276 ~ ~ ~ p=1.000 n=12
Types 79,613 79,613 ~ ~ ~ p=1.000 n=12
Memory Used 133,224k (± 0.07%) 133,213k (± 0.06%) ~ 133,044k 133,473k p=0.932 n=12
Memory Allocs 2,190,272 (± 0.01%) 2,190,377 (± 0.01%) ~ 2,190,185 2,190,911 p=0.219 n=12
Config Time 0.000s 0.000s ~ ~ ~ p=1.000 n=12
Parse Time 0.041s (± 3.18%) 0.041s (± 2.74%) ~ 0.039s 0.044s p=0.459 n=12
Bind Time 0.012s (± 7.61%) 0.013s (±10.84%) ~ 0.010s 0.016s p=0.450 n=12
Check Time 0.506s (± 0.73%) 0.499s (± 0.81%) -0.007s (- 1.40%) 0.492s 0.511s p=0.016 n=12
Emit Time 0.268s (± 2.47%) 0.272s (± 1.91%) ~ 0.253s 0.281s p=0.297 n=12
Total Time 0.834s (± 0.86%) 0.831s (± 0.73%) ~ 0.813s 0.852s p=0.523 n=12
angular-1 - native
Errors 3 3 ~ ~ ~ p=1.000 n=12
Symbols 845,733 (± 0.04%) 846,106 (± 0.06%) ~ 845,146 848,383 p=0.347 n=12
Types 248,125 (± 0.00%) 248,125 (± 0.00%) ~ 248,122 248,134 p=0.918 n=12
Memory Used 746,089k (± 0.02%) 746,138k (± 0.01%) ~ 745,885k 746,283k p=0.579 n=12
Memory Allocs 12,913,075 (± 0.02%) 12,914,938 (± 0.04%) ~ 12,908,663 12,932,026 p=0.671 n=12
Config Time 0.017s 0.017s ~ ~ ~ p=1.000 n=12
Parse Time 0.249s (± 3.04%) 0.258s (± 2.34%) +0.009s (+ 3.61%) 0.239s 0.267s p=0.046 n=12
Bind Time 0.068s (±13.25%) 0.061s (± 7.63%) 🟩-0.007s (-10.43%) 0.056s 0.078s p=0.042 n=12
Check Time 0s 0s ~ ~ ~ p=1.000 n=12
Emit Time 1.562s (± 1.07%) 1.559s (± 0.91%) ~ 1.516s 1.604s p=0.943 n=12
Total Time 1.911s (± 1.21%) 1.907s (± 0.88%) ~ 1.863s 1.974s p=0.477 n=12
mui-docs - native
Errors 11,405 11,405 ~ ~ ~ p=1.000 n=12
Symbols 4,339,337 4,339,337 ~ ~ ~ p=1.000 n=12
Types 1,379,387 1,379,387 ~ ~ ~ p=1.000 n=12
Memory Used 2,774,842k (± 0.06%) 2,775,102k (± 0.06%) ~ 2,770,268k 2,779,406k p=0.671 n=12
Memory Allocs 32,721,445 (± 0.05%) 32,725,483 (± 0.04%) ~ 32,692,199 32,762,903 p=0.630 n=12
Config Time 0.017s 0.017s ~ ~ ~ p=1.000 n=12
Parse Time 0.497s (± 2.85%) 0.498s (± 2.21%) ~ 0.467s 0.520s p=0.810 n=12
Bind Time 0.002s 0.002s ~ ~ ~ p=1.000 n=12
Check Time 8.874s (± 0.59%) 8.849s (± 0.61%) ~ 8.764s 9.027s p=0.551 n=12
Emit Time 0.473s (±10.89%) 0.460s (±11.84%) ~ 0.419s 0.654s p=0.468 n=12
Total Time 10.636s (± 0.55%) 10.602s (± 0.56%) ~ 10.483s 10.749s p=0.660 n=12
strada-build-src - native
Errors 0 0 ~ ~ ~ p=1.000 n=12
Symbols 1,388,669 1,388,669 ~ ~ ~ p=1.000 n=12
Types 441,862 441,862 ~ ~ ~ p=1.000 n=12
Memory Used 1,598,704k (± 0.45%) 1,594,161k (± 0.44%) ~ 1,577,681k 1,614,957k p=0.242 n=12
Memory Allocs 82,453,841 (± 0.08%) 82,408,345 (± 0.14%) ~ 82,023,659 82,537,415 p=0.630 n=12
Config Time 0.003s (±16.58%) 0.003s (±14.42%) ~ 0.002s 0.004s p=0.384 n=12
Parse Time 0.255s (± 2.49%) 0.261s (± 1.95%) ~ 0.251s 0.273s p=0.272 n=12
Bind Time 0.000s 0.000s ~ ~ ~ p=1.000 n=12
Check Time 1.793s (± 0.61%) 1.799s (± 0.53%) ~ 1.765s 1.820s p=0.266 n=12
Emit Time 0.278s (± 4.79%) 0.275s (± 1.88%) ~ 0.261s 0.291s p=0.723 n=12
Total Time 20.568s (± 0.52%) 20.787s (± 0.53%) +0.219s (+ 1.07%) 20.489s 21.138s p=0.011 n=12
strada-compiler - native
Errors 0 0 ~ ~ ~ p=1.000 n=12
Symbols 334,953 334,953 ~ ~ ~ p=1.000 n=12
Types 197,616 197,616 ~ ~ ~ p=1.000 n=12
Memory Used 296,038k (± 0.03%) 296,092k (± 0.03%) ~ 295,886k 296,373k p=0.682 n=12
Memory Allocs 4,622,032 (± 0.01%) 4,621,491 (± 0.01%) ~ 4,620,893 4,622,820 p=0.104 n=12
Config Time 0.001s 0.001s ~ ~ ~ p=1.000 n=12
Parse Time 0.112s (± 2.96%) 0.116s (± 3.30%) +0.004s (+ 3.72%) 0.103s 0.125s p=0.046 n=12
Bind Time 0.000s 0.000s ~ ~ ~ p=1.000 n=12
Check Time 1.041s (± 0.66%) 1.045s (± 0.52%) ~ 1.031s 1.060s p=0.163 n=12
Emit Time 0.144s (± 9.19%) 0.148s (± 6.29%) ~ 0.125s 0.176s p=0.876 n=12
Total Time 1.354s (± 0.96%) 1.361s (± 1.02%) ~ 1.317s 1.396s p=0.451 n=12
ts-pre-modules - native
Errors 87 87 ~ ~ ~ p=1.000 n=12
Symbols 303,834 303,834 ~ ~ ~ p=1.000 n=12
Types 181,663 181,663 ~ ~ ~ p=1.000 n=12
Memory Used 256,642k (± 0.02%) 256,666k (± 0.02%) ~ 256,588k 256,862k p=0.434 n=12
Memory Allocs 1,614,062 (± 0.01%) 1,614,167 (± 0.01%) ~ 1,613,450 1,614,804 p=0.478 n=12
Config Time 0.000s 0.000s (±217.90%) ~ 0.000s 0.001s p=1.000 n=12
Parse Time 0.101s (± 4.68%) 0.099s (± 4.96%) ~ 0.090s 0.111s p=0.599 n=12
Bind Time 0.037s (± 9.89%) 0.034s (± 7.88%) ~ 0.028s 0.041s p=0.114 n=12
Check Time 0.818s (± 0.99%) 0.818s (± 0.59%) ~ 0.812s 0.831s p=1.000 n=12
Emit Time 0.000s 0.000s ~ ~ ~ p=1.000 n=12
Total Time 0.970s (± 0.92%) 0.964s (± 0.80%) ~ 0.945s 0.983s p=0.434 n=12
vscode - native
Errors 396 396 ~ ~ ~ p=1.000 n=12
Symbols 11,386,701 11,386,701 ~ ~ ~ p=1.000 n=12
Types 3,759,032 3,759,032 ~ ~ ~ p=1.000 n=12
Memory Used 6,490,472k (± 0.01%) 6,490,166k (± 0.01%) ~ 6,488,769k 6,493,425k p=0.291 n=12
Memory Allocs 55,749,504 (± 0.01%) 55,754,889 (± 0.02%) ~ 55,741,722 55,783,485 p=0.551 n=12
Config Time 0.075s (± 0.56%) 0.075s (± 0.43%) ~ 0.075s 0.076s p=1.000 n=12
Parse Time 1.923s (± 3.84%) 1.816s (± 3.04%) 🟩-0.107s (- 5.56%) 1.712s 1.961s p=0.010 n=12
Bind Time 0.565s (±23.04%) 0.645s (±17.24%) ~ 0.451s 0.950s p=0.103 n=12
Check Time 13.586s (± 1.26%) 13.430s (± 1.54%) ~ 13.079s 13.823s p=0.340 n=12
Emit Time 4.697s (±11.42%) 5.150s (±10.43%) ~ 4.109s 6.147s p=0.291 n=12
Total Time 20.927s (± 2.07%) 21.210s (± 1.93%) ~ 20.453s 22.116s p=0.242 n=12
webpack - native
Errors 585 585 ~ ~ ~ p=1.000 n=12
Symbols 1,325,855 1,325,855 ~ ~ ~ p=1.000 n=12
Types 627,988 627,988 ~ ~ ~ p=1.000 n=12
Memory Used 951,170k (± 0.01%) 951,293k (± 0.01%) ~ 950,927k 951,584k p=0.139 n=12
Memory Allocs 6,740,654 (± 0.04%) 6,739,402 (± 0.04%) ~ 6,735,724 6,747,408 p=0.242 n=12
Config Time 0.009s 0.009s (± 3.96%) ~ 0.009s 0.011s p=1.000 n=12
Parse Time 0.296s (± 4.76%) 0.320s (± 2.97%) 🔻+0.024s (+ 7.97%) 0.294s 0.345s p=0.008 n=12
Bind Time 0.103s (±16.96%) 0.084s (±20.65%) 🟩-0.019s (-18.24%) 0.066s 0.151s p=0.023 n=12
Check Time 1.944s (± 0.68%) 1.989s (± 2.87%) ~ 1.921s 2.245s p=0.193 n=12
Emit Time 0.000s 0.000s ~ ~ ~ p=1.000 n=12
Total Time 2.389s (± 0.46%) 2.438s (± 2.39%) ~ 2.361s 2.681s p=0.117 n=12
xstate-main - native
Errors 0 0 ~ ~ ~ p=1.000 n=12
Symbols 1,062,854 1,062,854 ~ ~ ~ p=1.000 n=12
Types 390,845 390,845 ~ ~ ~ p=1.000 n=12
Memory Used 548,158k (± 0.02%) 548,057k (± 0.03%) ~ 547,816k 548,625k p=0.114 n=12
Memory Allocs 4,889,883 (± 0.07%) 4,891,081 (± 0.07%) ~ 4,878,762 4,899,820 p=0.443 n=12
Config Time 0.003s 0.003s ~ ~ ~ p=1.000 n=12
Parse Time 0.145s (± 2.90%) 0.141s (± 3.77%) ~ 0.129s 0.155s p=0.234 n=12
Bind Time 0.042s (±14.84%) 0.055s (±19.86%) 🔻+0.014s (+33.00%) 0.033s 0.075s p=0.046 n=12
Check Time 1.020s (± 0.58%) 1.022s (± 0.36%) ~ 1.014s 1.036s p=0.944 n=12
Emit Time 0.000s 0.000s ~ ~ ~ p=1.000 n=12
Total Time 1.219s (± 0.93%) 1.229s (± 0.46%) ~ 1.211s 1.241s p=0.164 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

@typescript-automation

Copy link
Copy Markdown
Contributor

Jake Bailey (@jakebailey) Here are the results of running the top 400 repos with tsc comparing baseline and pr:

Everything looks good!

This branch has not been deployed

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

Labels

For Backlog Bug PRs that fix a backlog bug

Projects

Status: Not started

Development

Successfully merging this pull request may close these issues.

Bad parsing behaviour for generic arrow type arguments in class heritage / JSX open element

3 participants