fix(parser): recognize annotated empty arrows in conditionals (#26537)

Recognize bare `():` as a definite TypeScript arrow head so conditional parsing does not reject valid annotated empty-parameter arrows:

```ts
const f: number | ((x: number) => any) = a ? (b) : x => x ? c : (): any => b;
```

Keep `async():` ambiguous because `async()` can be a call on the true side of a conditional, as in `a ? async() : 0`.
This commit is contained in:
camc314
2026-09-10 16:10:32 +00:00
parent 9ef028c2ad
commit d21d5cf32f
9 changed files with 35 additions and 17 deletions
+7 -2
View File
@@ -75,7 +75,8 @@ impl<'a, C: Config> ParserImpl<'a, C> {
}
fn is_parenthesized_arrow_function_expression_worker(&mut self) -> Tristate {
if self.eat(Kind::Async) {
let is_async = self.eat(Kind::Async);
if is_async {
if self.cur_token().is_on_new_line() {
return Tristate::False;
}
@@ -100,7 +101,11 @@ impl<'a, C: Config> ParserImpl<'a, C> {
self.bump_any();
let third = self.cur_kind();
match third {
Kind::Colon if self.is_ts => Tristate::Maybe,
// `async()` can be a call on the true side of a conditional.
Kind::Colon if self.is_ts && is_async => Tristate::Maybe,
// Bare `()` cannot be a parenthesized expression, so the colon
// must start a return type, even inside a conditional expression.
Kind::Colon if self.is_ts => Tristate::True,
Kind::Arrow | Kind::LCurly => Tristate::True,
_ => Tristate::False,
}
@@ -0,0 +1,12 @@
const a = true, b = 1, c = 2;
const original: number | ((x: number) => any) = a ? (b) : x => x ? c : (): any => b;
const unparenthesized = a ? b : x => x ? c : (): any => b;
const grouped = a ? (b) : (x => x ? c : (): any => b);
const unannotated = a ? (b) : x => x ? c : () => b;
const parameter = a ? (b) : x => x ? c : (y: number): any => b;
function async(): number { return b; }
const asyncCall = a ? async() : 0;
const asyncCallWithArrowAlternate = a ? async() : x => x;
const asyncArrow = a ? async (): Promise<number> => b : c;
+2 -2
View File
@@ -1,3 +1,3 @@
codegen_misc Summary:
AST Parsed : 106/106 (100.00%)
Positive Passed: 106/106 (100.00%)
AST Parsed : 107/107 (100.00%)
Positive Passed: 107/107 (100.00%)
+2 -2
View File
@@ -1,3 +1,3 @@
formatter_misc Summary:
AST Parsed : 106/106 (100.00%)
Positive Passed: 106/106 (100.00%)
AST Parsed : 107/107 (100.00%)
Positive Passed: 107/107 (100.00%)
+2 -2
View File
@@ -1,3 +1,3 @@
lexer_misc Summary:
AST Parsed : 321/321 (100.00%)
Positive Passed: 321/321 (100.00%)
AST Parsed : 322/322 (100.00%)
Positive Passed: 322/322 (100.00%)
+2 -2
View File
@@ -1,6 +1,6 @@
parser_misc Summary:
AST Parsed : 106/106 (100.00%)
Positive Passed: 106/106 (100.00%)
AST Parsed : 107/107 (100.00%)
Positive Passed: 107/107 (100.00%)
Negative Passed: 215/215 (100.00%)
× Cannot assign to 'arguments' in strict mode
@@ -27046,11 +27046,12 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/types/wit
4 │ }
╰────
× Empty parenthesized expression
╭─[typescript/tests/cases/conformance/parser/ecmascript5/RegressionTests/parser509669.ts:2:9]
× Expected `=>` but found `{`
╭─[typescript/tests/cases/conformance/parser/ecmascript5/RegressionTests/parser509669.ts:2:17]
1 │ function foo():any {
2 │ return ():void {};
· ──
· ┬
· ╰── `=>` expected
3 │ }
╰────
+2 -2
View File
@@ -1,6 +1,6 @@
semantic_misc Summary:
AST Parsed : 106/106 (100.00%)
Positive Passed: 102/106 (96.23%)
AST Parsed : 107/107 (100.00%)
Positive Passed: 103/107 (96.26%)
semantic Error: tasks/coverage/misc/pass/declare-let-private.ts
Bindings mismatch:
after transform: ScopeId(0): ["private"]
@@ -1,3 +1,3 @@
transformer_misc Summary:
AST Parsed : 106/106 (100.00%)
Positive Passed: 106/106 (100.00%)
AST Parsed : 107/107 (100.00%)
Positive Passed: 107/107 (100.00%)