fix(parser): disambiguate await using in for initializers (#26527)

Allow classic `for` initializers such as `for (await using;;)` when `using` is an ordinary identifier. Reuse the statement parser’s `await using` lookahead so resource declarations require a following binding identifier, preserving the existing line-break restrictions.
This commit is contained in:
camc314 committed 2026-09-10 10:59:58 +00:00
1 parent 36d5534689
commit e98beef227
13 files changed
+45 -44

No files matched your search

+8 -3
View File
@@ -1664,7 +1664,7 @@ impl<'a, C: Config> ParserImpl<'a, C> {
/// Returns `true` when await is definitely an await expression (not ambiguous).
///
/// Unambiguous cases (returns `true`):
/// - Next token is identifier, keyword (except `of`), or literal on same line
/// - Next token is identifier, keyword, or literal on same line, except the cases below
///
/// Ambiguous cases (returns `false`):
/// - Line break after `await` (could be ASI)
@@ -1672,6 +1672,7 @@ impl<'a, C: Config> ParserImpl<'a, C> {
/// - Next token is `(` `[` (could be call/member or grouping/array)
/// - Next token is template literal
/// - Next token is `of` (for-await-of ambiguity: `for (await of [])`)
/// - Next tokens form an `await using` declaration
/// - Next token is `/` (division or regex literal)
/// - Next token cannot start an expression (`)`, `}`, `;`, etc.)
fn is_unambiguous_await(&mut self) -> bool {
@@ -1685,8 +1686,12 @@ impl<'a, C: Config> ParserImpl<'a, C> {
let kind = token.kind();
// Special case: `await of` is ambiguous (for-await-of loop)
// Special case: `await using` should be handled as a declaration, not `await (using)`
if matches!(kind, Kind::Of | Kind::Using) {
if kind == Kind::Of {
return false;
}
// Only reserve `await using` for a declaration when a binding identifier follows.
if kind == Kind::Using && self.is_using_statement() {
return false;
}
+2 -11
View File
@@ -422,17 +422,8 @@ impl<'a, C: Config> ParserImpl<'a, C> {
_ => {}
}
// [+Using, +Await] await [no LineTerminator here] using [no LineTerminator here]
if self.at(Kind::Await)
&& self.lookahead(|p| {
p.bump_any();
if !p.at(Kind::Using) || p.cur_token().is_on_new_line() {
return false;
}
p.bump_any();
!p.cur_token().is_on_new_line()
})
{
// [+Using, +Await] await [no LineTerminator here] using [no LineTerminator here] ForBinding[?Yield, ?Await, ~Pattern]
if self.at(Kind::Await) && self.is_using_statement() {
return self.parse_using_declaration_for_statement(
for_start,
parenthesis_opening_span,
@@ -0,0 +1,2 @@
for (await using;;);
export {};
@@ -0,0 +1 @@
for (await using;;);
@@ -0,0 +1,11 @@
async function f(using) {
for (await using;;) { break; }
for (await using + 1;;) { break; }
for (await using.foo;;) { break; }
for (await using(1);;) { break; }
for (await (using);;) { break; }
for (await using
;;) { break; }
for (await
using;;) { break; }
}
File renamed without changes.
+2 -2
View File
@@ -1,3 +1,3 @@
codegen_misc Summary:
AST Parsed : 99/99 (100.00%)
Positive Passed: 99/99 (100.00%)
AST Parsed : 103/103 (100.00%)
Positive Passed: 103/103 (100.00%)
+2 -2
View File
@@ -1,3 +1,3 @@
formatter_misc Summary:
AST Parsed : 99/99 (100.00%)
Positive Passed: 99/99 (100.00%)
AST Parsed : 103/103 (100.00%)
Positive Passed: 103/103 (100.00%)
+2 -2
View File
@@ -1,3 +1,3 @@
lexer_misc Summary:
AST Parsed : 302/302 (100.00%)
Positive Passed: 302/302 (100.00%)
AST Parsed : 305/305 (100.00%)
Positive Passed: 305/305 (100.00%)
+3 -12
View File
@@ -1,7 +1,7 @@
parser_misc Summary:
AST Parsed : 99/99 (100.00%)
Positive Passed: 99/99 (100.00%)
Negative Passed: 203/203 (100.00%)
AST Parsed : 103/103 (100.00%)
Positive Passed: 103/103 (100.00%)
Negative Passed: 202/202 (100.00%)
× Cannot assign to 'arguments' in strict mode
╭─[misc/fail/arguments-eval-ambient.ts:2:13]
@@ -1071,15 +1071,6 @@ Negative Passed: 203/203 (100.00%)
· ╰── `b` has already been declared here
╰────
× Expected a semicolon or an implicit semicolon after a statement, but found none
╭─[misc/fail/oxc-10503.ts:4:6]
3 │
4 │ await using
· ▲
5 │ foo = bar();
╰────
help: Try inserting a semicolon here
× Unexpected token
╭─[misc/fail/oxc-10638.js:2:1]
1 │ for(
@@ -28867,14 +28867,13 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/types/wit
╰────
help: Wrap this declaration in a block statement
× Expected a semicolon or an implicit semicolon after a statement, but found none
╭─[typescript/tests/cases/conformance/statements/VariableStatements/usingDeclarations/awaitUsingDeclarations.4.ts:2:10]
× Cannot assign to this expression
╭─[typescript/tests/cases/conformance/statements/VariableStatements/usingDeclarations/awaitUsingDeclarations.4.ts:2:5]
1 │ {
2 │ await using [a] = null;
· ▲
· ───────────────
3 │ }
╰────
help: Try inserting a semicolon here
× Using declarations may not have binding patterns.
╭─[typescript/tests/cases/conformance/statements/VariableStatements/usingDeclarations/awaitUsingDeclarations.5.ts:3:17]
@@ -28885,10 +28884,10 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/types/wit
╰────
× Expected a semicolon or an implicit semicolon after a statement, but found none
╭─[typescript/tests/cases/conformance/statements/VariableStatements/usingDeclarations/awaitUsingDeclarations.6.ts:2:10]
╭─[typescript/tests/cases/conformance/statements/VariableStatements/usingDeclarations/awaitUsingDeclarations.6.ts:2:16]
1 │ {
2 │ await using {a} = null;
· ▲
· ▲
3 │ }
╰────
help: Try inserting a semicolon here
@@ -28928,11 +28927,12 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/types/wit
╰────
help: Did you mean to use a for...of statement?
× Using declarations may not have binding patterns.
× Expected `;` but found `{`
╭─[typescript/tests/cases/conformance/statements/VariableStatements/usingDeclarations/awaitUsingDeclarationsInForOf.3.ts:2:22]
1 │ async function main() {
2 │ for (await using {} of []) {
· ──
· ┬
· ╰── `;` expected
3 │ }
╰────
+2 -2
View File
@@ -1,6 +1,6 @@
semantic_misc Summary:
AST Parsed : 99/99 (100.00%)
Positive Passed: 95/99 (95.96%)
AST Parsed : 103/103 (100.00%)
Positive Passed: 99/103 (96.12%)
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 : 99/99 (100.00%)
Positive Passed: 99/99 (100.00%)
AST Parsed : 103/103 (100.00%)
Positive Passed: 103/103 (100.00%)