Fix TypeScript parser failing on async calls in ternary consequent#17799
Fix TypeScript parser failing on async calls in ternary consequent#17799JLHwung merged 5 commits intobabel:mainfrom
Conversation
When the TypeScript plugin encounters `async(a)` followed by `:` inside a ternary consequent, it incorrectly treats `:` as a return type annotation for an async arrow function, rather than the ternary separator. For example, `true ? async(a) : b => b` should parse as a ternary with `async(a)` as the consequent (a call expression) and `b => b` as the alternate (an arrow function). Instead, babel tried to parse `async(a): b => b` as an async arrow with return type `b`. This commit: - Adds an `inConditionalConsequent` state flag set during ternary consequent parsing - When the flag is active, `shouldParseAsyncArrow` no longer treats `:` as a return type annotation start - Resets the flag inside parenthesized expressions, so `a ? (async(b): T => body) : c` still works correctly Fixes babel#17547
|
Build successful! You can test your changes in the REPL here: https://babeljs.io/repl/build/60944 |
|
commit: |
| } | ||
|
|
||
| return super.parseConditional(expr, startLoc, refExpressionErrors); | ||
| this.expect(tt.question); |
There was a problem hiding this comment.
| this.expect(tt.question); | |
| this.next(); // eat `?` |
Since we already tested tt.question at the very start of this function.
There was a problem hiding this comment.
Good catch, updated to this.next() since we already know it's a ? from the check at the top of the function.
|
Yes, this PR looks much simpler, but unfortunately it still can't align tsc. |
…s from babel#17572 Since the `?` token is already verified at the start of parseConditional, use `this.next()` to consume it instead of redundantly checking with `this.expect()`. Also add additional conditional expression test fixtures ported from babel#17572 to improve coverage of arrow/async ambiguity edge cases. Co-Authored-By: Claude Opus 4.6 <[email protected]>
|
Thanks for the review! I've addressed both items:
I skipped |
|
@liuxingbaoyu Good point about the tsc alignment. I looked into this — the REPL example shows |
These test cases document known limitations of the current fix: - arrow-ambiguity, arrow-like, arrow-param: Non-async arrow disambiguation in ternary expressions (out of scope for this PR) - async-call, async-call-3: Edge cases where async call parsing interacts with nested arrow/paren contexts Co-Authored-By: Claude Opus 4.6 <[email protected]>
…ures The async-call and async-call-3 test fixtures had "throws" directives in options.json expecting parse errors, but the parser fix now correctly handles these cases. Replace with output.json files containing expected ASTs. Co-Authored-By: Claude Opus 4.6 <[email protected]>
|
@liuxingbaoyu Thanks for flagging the tsc divergence. I agree that full tsc alignment would be ideal, but I believe that's a broader issue beyond the scope of this fix. This PR specifically addresses the parser crash on async calls in ternary consequents - which is a regression from valid code being rejected. Happy to open a follow-up issue to track the remaining tsc alignment gap if that would be helpful. What do you think? |
liuxingbaoyu
left a comment
There was a problem hiding this comment.
Thank you for your contribution.
Since the existing PR was not elegant enough, I agree to accept this PR until a better approach is found.
Summary
Fixes #17547
When parsing TypeScript,
true ? async(a) : b => bfails with "Unexpected token, expected:" because the parser greedily interpretsasync(a): b => bas an async arrow function with return typeb, consuming the:that should be the ternary separator.The fix adds an
inConditionalConsequentflag to disambiguate:when it appears afterasync(...)inside a ternary consequent:parseConditionaloverride, the flag is set while parsing the consequent expressionshouldParseAsyncArrowreturnsfalsefor:when the flag is activeparseParenAndDistinguishExpressionoverride, soa ? (async(b): T => body) : ccontinues to workThis mirrors TypeScript's own disambiguation behavior, where arrow return type annotations are disallowed in the consequent of a conditional expression.
Test cases
All parse correctly after this change:
true ? async(a) : b => b-- ternary with async call + arrow alternatelet f = async(a): b => a-- async arrow with return type (not in ternary)true ? (async(a): b => a) : 1-- parenthesized async arrow in ternary consequenttrue ? 1 : async(a): b => a-- async arrow in ternary alternatea ? b ? async(c) : d : e-- nested ternariesAll 15,972 existing parser tests continue to pass.