Repository navigation
Re-scan '<<' when parsing type arguments in heritage clauses, typeof queries, and JSX - #64716
ahmad65saad wants to merge 3 commits into
Conversation
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
There was a problem hiding this comment.
🟡 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
typeofqueries. - 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 { |
|
@microsoft-github-policy-service agree |
|
I think this is right and esbuild also accepts this syntax, which is good. TypeScript Bot (@typescript-bot) test it |
|
Hey Jake Bailey (@jakebailey), the results of running the DT tests are ready. Everything looks the same! |
|
Jake Bailey (@jakebailey) Here are the results of running the user tests with tsc comparing Everything looks good! |
|
Jake Bailey (@jakebailey) Here they are:
tscComparison Report - baseline..pr
System info unknown
Hosts
Scenarios
Developer Information: |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Jake Bailey (@jakebailey) Here are the results of running the top 400 repos with tsc comparing Everything looks good! |

Fixes #47410
The scanner produces
<<as a single token, and the parser is expected to callreScanLessThanToken()wherever a<may open a type argument list.parseTypeArgumentsOfTypeReferenceandtryParseTypeArgumentsInExpressionalready do this, butparseTypeArgumentsonly 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:Fix
parseTypeArgumentsnow 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/@implementsalso go throughparseTypeArguments, but the JSDoc scanner never produces<<there, so they already worked;jsdocAugments_genericFunctionTypeArgument.tspins that (its baselines are identical with and without this change).typeoftype 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 inparseTypeQuery, when the token is<<, the type arguments are parsed speculatively (mark/rewind, same diagnostics-count check asskipParameterStart) and dropped if they don't parse cleanly.Tests
New test
parseGenericArrowRatherThanLeftShift2.tsxcovers each position above, plus the instantiation/call expression forms, and ordinary<<shift expressions (including inside JSX attributes and afteras 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 onmainwithout this change:checkJSDocAugmentsTagMatchesExtendscompares the twoA<<T>(x: T) => T>types withisTypeIdenticalTo, 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).