fix(parser): allow in in arrow block bodies within for initializers (#26395)

## Summary

Allow the `in` operator inside an arrow function’s block body when the arrow appears in a `for` initializer:

```js
for (() => { a in b; };;);
```

This is valid JavaScript, but the parser previously reported a missing semicolon at `in`. The block now parses successfully, including async arrows and arrows with a single unparenthesized parameter.

## Cause and fix

A `for` initializer disables `Context::In` so that an unparenthesized `in` can delimit a `for…in` statement. Arrow block bodies inherited that disabled context through `parse_arrow_function_expression_body`, which called `parse_function_body` directly.

The block-body branch now calls `parse_function_body` through `context_add(Context::In, ...)`. This enables `in` throughout the function body and restores the enclosing context afterward. It also permits nested concise arrows inside that block to use `in`.

The change is confined to block bodies because concise expression bodies inherit the surrounding grammar’s `In` parameter. For example, this remains invalid:

```js
for (var f = () => a in b;;);
```

Restoring the enclosing context also keeps this invalid, rather than accidentally treating `a in b` as a regular initializer expression after parsing the first arrow:

```js
for (var f = () => {}, value = a in b;;);
```
This commit is contained in:
camc314
2026-09-07 15:07:31 +00:00
parent 3cb3dc840b
commit 4e76602fdd
10 changed files with 45 additions and 14 deletions
+3 -1
View File
@@ -313,7 +313,9 @@ impl<'a, C: Config> ParserImpl<'a, C> {
self.ctx = self.ctx.and_await(r#async).and_yield(false);
let body = if self.at(Kind::LCurly) {
ArrowFunctionBody::FunctionBody(self.parse_function_body())
ArrowFunctionBody::FunctionBody(
self.context_add(Context::In, Self::parse_function_body),
)
} else {
// Remove TopLevel context for arrow function expression body
let expr = self.context_remove(Context::TopLevel, |p| {
@@ -0,0 +1 @@
for (var f = () => {}, value = a in b;;);
@@ -0,0 +1 @@
for (var f = () => a in b;;);
@@ -0,0 +1,5 @@
for (() => { a in b; };;);
for (async () => { a in b; };;);
for (x => { return x in b; };;);
for (async x => { return x in b; };;);
for (() => { () => a in b; };;);
+2 -2
View File
@@ -1,3 +1,3 @@
codegen_misc Summary:
AST Parsed : 85/85 (100.00%)
Positive Passed: 85/85 (100.00%)
AST Parsed : 86/86 (100.00%)
Positive Passed: 86/86 (100.00%)
+2 -2
View File
@@ -1,3 +1,3 @@
formatter_misc Summary:
AST Parsed : 85/85 (100.00%)
Positive Passed: 85/85 (100.00%)
AST Parsed : 86/86 (100.00%)
Positive Passed: 86/86 (100.00%)
+2 -2
View File
@@ -1,3 +1,3 @@
lexer_misc Summary:
AST Parsed : 270/270 (100.00%)
Positive Passed: 270/270 (100.00%)
AST Parsed : 273/273 (100.00%)
Positive Passed: 273/273 (100.00%)
+25 -3
View File
@@ -1,7 +1,7 @@
parser_misc Summary:
AST Parsed : 85/85 (100.00%)
Positive Passed: 85/85 (100.00%)
Negative Passed: 185/185 (100.00%)
AST Parsed : 86/86 (100.00%)
Positive Passed: 86/86 (100.00%)
Negative Passed: 187/187 (100.00%)
× Cannot assign to 'arguments' in strict mode
╭─[misc/fail/arguments-eval-ambient.ts:2:13]
@@ -136,6 +136,28 @@ Negative Passed: 185/185 (100.00%)
╰────
note: Classes are always strict mode code
× Only a single declaration is allowed in a `for...in` statement
╭─[misc/fail/arrow-block-body-in-context.js:1:6]
1 │ for (var f = () => {}, value = a in b;;);
· ───────────────────────────
╰────
× Expected `)` but found `;`
╭─[misc/fail/arrow-block-body-in-context.js:1:38]
1 │ for (var f = () => {}, value = a in b;;);
· ┬ ┬
· │ ╰── `)` expected
· ╰── Opened here
╰────
× Expected `)` but found `;`
╭─[misc/fail/arrow-expression-body-in.js:1:26]
1 │ for (var f = () => a in b;;);
· ┬ ┬
· │ ╰── `)` expected
· ╰── Opened here
╰────
× Illegal break statement
╭─[misc/fail/arrow-function-jumps.js:1:25]
1 │ while (true) { (() => { break; }); }
+2 -2
View File
@@ -1,6 +1,6 @@
semantic_misc Summary:
AST Parsed : 85/85 (100.00%)
Positive Passed: 81/85 (95.29%)
AST Parsed : 86/86 (100.00%)
Positive Passed: 82/86 (95.35%)
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 : 85/85 (100.00%)
Positive Passed: 85/85 (100.00%)
AST Parsed : 86/86 (100.00%)
Positive Passed: 86/86 (100.00%)