Fix parsing of async() calls in conditional expressions - #64234
Open
Mohit Nayak (mohit-nayak) wants to merge 1 commit into
Open
Fix parsing of async() calls in conditional expressions#64234Mohit Nayak (mohit-nayak) wants to merge 1 commit into
Mohit Nayak (mohit-nayak) wants to merge 1 commit into
Conversation
|
This PR doesn't have any linked issues. Please open an issue that references this PR. From there we can discuss and prioritise. |
Copilot started reviewing on behalf of
Mohit Nayak (mohit-nayak)
September 10, 2026 18:26
View session
Contributor
There was a problem hiding this comment.
🟢 Approval recommended
The focused parser change resolves the reported ambiguity while preserving async-arrow parsing with comprehensive regression baselines.
Pull request overview
Fixes ambiguity between async() calls and async arrow functions in conditional expressions.
Changes:
- Uses speculative parsing for
async():constructs. - Adds TypeScript and JavaScript regression coverage.
- Adds emit, symbol, and type baselines.
File summaries
| File | Description |
|---|---|
tsc/internal/parser/parser.go |
Corrects async-call lookahead behavior. |
tsc/testdata/tests/cases/compiler/asyncCallInConditionalExpression.ts |
Adds regression cases. |
tsc/testdata/baselines/reference/compiler/asyncCallInConditionalExpression.js |
Records emitted output. |
tsc/testdata/baselines/reference/compiler/asyncCallInConditionalExpression.symbols |
Records symbol resolution. |
tsc/testdata/baselines/reference/compiler/asyncCallInConditionalExpression.types |
Records inferred types. |
Review details
- Files reviewed: 5/5 changed files
- Comments generated: 0
- Review effort level: Balanced
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #64231
Problem
Calling a function named async with no arguments in the true branch of a conditional expression gets parsed as the start of an async arrow function:
async is not a reserved word, so async() is a plain call and the colon is the conditional's separator. Node runs the JavaScript version of this without complaint, but we reject it in both .ts and .js files. The issue says it fails in every version, and that matches: Strada has the same lookahead.
Cause
nextIsParenthesizedArrowFunctionExpression has a shortcut for empty parameter lists. If it sees "()" followed by "=>", ":" or "{", it returns TSTrue, meaning "this is definitely an arrow function". When the parens come after async, it skips the async and takes the same shortcut. That is not safe, because "async()" followed by a colon can also be a call inside a conditional.
What happens in the repro:
The shortcut is correct for a bare "()", since that can never be an expression on its own. It is only wrong after async, where "async()" is a valid call.
Solution
When the lookahead skipped async and sees "( ) :", return TSUnknown instead of TSTrue. That sends it through the same speculative path "(x):" already uses. It only commits to an arrow if "=>" or "{" follows, it respects the conditional's return type rule, and otherwise it rewinds and parses a call.
Nothing else changes. "()" followed by "=>", ":" or "{" without async is still TSTrue. "async() =>" and "async() {" are still TSTrue, since neither can be a call. "async(): T => x" still parses as an arrow outside a conditional and in either branch of one; "a ? async(): T => x : y" works through the existing second-colon check. The only extra cost is one speculative parse when "async():" shows up, and the existing notParenthesizedArrow cache already memoizes it.
Tests
Added tsc/testdata/tests/cases/compiler/asyncCallInConditionalExpression.ts with three groups of cases:
I ran the test before changing the parser to make sure it catches the bug. It produced 19 errors, with TS1005 on every repro. With the fix there are none, so there is no .errors.txt baseline. The .types baseline shows every async() in a conditional as a call returning number, and every arrow case still comes out as () => Promise.
Across the full suite the only baselines written were the three for this new test, so nothing else moved. npx hereby test:all passed clean. npx hereby test had one failure, TestFSEventsNFDOnDiskNFCSubscribe in internal/fswatch. That package does not depend on the parser and the test passed on 3 of 3 reruns, so it looks like a flake.
Checklist
Parser misinterprets async() calls in conditional expressions as async arrow functions #64231 has not been triaged yet, so it has no milestone. I cannot set one. Happy to wait for triage if you would rather look at the issue first.