Skip to content

Commit 8bd0290

Browse files
authored
Improve handling of using/await using in for loops
FIX: Fix a number of corner case bugs when `using` or `await using` appear in `for` loop specs. Issue #1432
1 parent f25fded commit 8bd0290

3 files changed

Lines changed: 183 additions & 69 deletions

File tree

acorn/src/statement.js

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,18 @@ pp.isUsingKeyword = function(isAwaitUsing, isFor) {
104104
while (isIdentifierChar(ch = this.fullCharCodeAt(next)))
105105
if (ch === 92) return true
106106
let id = this.input.slice(idStart, next)
107-
if (keywordRelationalOperator.test(id) || isFor && id === "of") return false
107+
if (keywordRelationalOperator.test(id)) return false
108+
if (isFor && !isAwaitUsing && id === "of") {
109+
// Look ahead for using declaration with initializer, i.e., `for (using of = ...)`
110+
skipWhiteSpace.lastIndex = next
111+
const skipAfterOf = skipWhiteSpace.exec(this.input)
112+
next = next + skipAfterOf[0].length
113+
if (this.input.charCodeAt(next) !== 61 /* '=' */ ||
114+
// Check for ==, === and => operators
115+
(ch = this.input.charCodeAt(next + 1)) === 61 /* '=' */ || ch === 62 /* '>' */) {
116+
return false
117+
}
118+
}
108119
return true
109120
}
110121

@@ -197,6 +208,10 @@ pp.parseStatement = function(context, topLevel, exports) {
197208
if (!this.allowUsing) {
198209
this.raise(this.start, "Using declaration cannot appear in the top level when source type is `script` or in the bare case statement")
199210
}
211+
if (context) {
212+
// Cases like `for (;;) using x = ...;`, `if (true) await using x = ...;`, etc. are not allowed.
213+
this.raise(this.start, "Using declaration is not allowed in single-statement positions")
214+
}
200215
if (usingKind === "await using") {
201216
if (!this.canAwait) {
202217
this.raise(this.start, "Await using cannot appear outside of async function")
@@ -330,11 +345,12 @@ pp.parseForStatement = function(node) {
330345
// Helper method to parse for loop after variable initialization
331346
pp.parseForAfterInit = function(node, init, awaitAt) {
332347
if ((this.type === tt._in || (this.options.ecmaVersion >= 6 && this.isContextual("of"))) && init.declarations.length === 1) {
333-
if (this.options.ecmaVersion >= 9) {
334-
if (this.type === tt._in) {
335-
if (awaitAt > -1) this.unexpected(awaitAt)
336-
} else node.await = awaitAt > -1
337-
}
348+
if (this.type === tt._in) {
349+
if ((init.kind === "using" || init.kind === "await using") && !init.declarations[0].init) {
350+
this.raise(this.start, "Using declaration is not allowed in for-in loops")
351+
}
352+
if (this.options.ecmaVersion >= 9 && awaitAt > -1) this.unexpected(awaitAt)
353+
} else if (this.options.ecmaVersion >= 9) node.await = awaitAt > -1
338354
return this.parseForIn(node, init)
339355
}
340356
if (awaitAt > -1) this.unexpected(awaitAt)

bin/test262.whitelist

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,10 @@ staging/explicit-resource-management/await-using-in-switch-case-block.js (defaul
22
staging/explicit-resource-management/await-using-in-switch-case-block.js (strict mode)
33
staging/explicit-resource-management/call-dispose-methods.js (default)
44
staging/explicit-resource-management/call-dispose-methods.js (strict mode)
5-
language/statements/await-using/syntax/await-using-invalid-for-in.js (default)
6-
language/statements/await-using/syntax/await-using-invalid-for-in.js (strict mode)
7-
language/statements/await-using/syntax/with-initializer-do-statement-while-expression.js (default)
8-
language/statements/await-using/syntax/with-initializer-do-statement-while-expression.js (strict mode)
9-
language/statements/await-using/syntax/with-initializer-for-statement.js (default)
10-
language/statements/await-using/syntax/with-initializer-for-statement.js (strict mode)
11-
language/statements/await-using/syntax/with-initializer-if-expression-statement-else-statement.js (default)
12-
language/statements/await-using/syntax/with-initializer-if-expression-statement-else-statement.js (strict mode)
13-
language/statements/await-using/syntax/with-initializer-if-expression-statement.js (default)
14-
language/statements/await-using/syntax/with-initializer-if-expression-statement.js (strict mode)
15-
language/statements/await-using/syntax/with-initializer-label-statement.js (default)
16-
language/statements/await-using/syntax/with-initializer-label-statement.js (strict mode)
17-
language/statements/await-using/syntax/with-initializer-while-expression-statement.js (default)
18-
language/statements/await-using/syntax/with-initializer-while-expression-statement.js (strict mode)
19-
language/statements/using/syntax/using-invalid-for-in.js (default)
20-
language/statements/using/syntax/using-invalid-for-in.js (strict mode)
21-
language/statements/using/syntax/with-initializer-for-statement.js (default)
22-
language/statements/using/syntax/with-initializer-for-statement.js (strict mode)
235
annexB/language/expressions/assignmenttargettype/callexpression-as-for-in-lhs.js (default)
246
annexB/language/expressions/assignmenttargettype/callexpression-as-for-of-lhs.js (default)
257
annexB/language/expressions/assignmenttargettype/callexpression-in-compound-assignment.js (default)
268
annexB/language/expressions/assignmenttargettype/callexpression-in-prefix-update.js (default)
279
annexB/language/expressions/assignmenttargettype/callexpression.js (default)
2810
annexB/language/expressions/assignmenttargettype/callexpression-in-postfix-update.js (default)
2911
annexB/language/expressions/assignmenttargettype/cover-callexpression-and-asyncarrowhead.js (default)
30-
language/statements/await-using/syntax/await-using-valid-for-await-using-of-of.js (default)
31-
language/statements/await-using/syntax/await-using-valid-for-await-using-of-of.js (strict mode)
32-
language/statements/using/syntax/using-for-statement.js (default)
33-
language/statements/using/syntax/using-for-statement.js (strict mode)

test/tests-using.js

Lines changed: 161 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -874,47 +874,10 @@ test("for (using x of resources) {}", {
874874
}, {ecmaVersion: 17, sourceType: "module"});
875875

876876
// For-in loop with using
877-
test("for (using x in obj) {}", {
878-
type: "Program",
879-
start: 0,
880-
end: 23,
881-
body: [{
882-
type: "ForInStatement",
883-
start: 0,
884-
end: 23,
885-
left: {
886-
type: "VariableDeclaration",
887-
start: 5,
888-
end: 12,
889-
declarations: [{
890-
type: "VariableDeclarator",
891-
start: 11,
892-
end: 12,
893-
id: {
894-
type: "Identifier",
895-
start: 11,
896-
end: 12,
897-
name: "x"
898-
},
899-
init: null
900-
}],
901-
kind: "using"
902-
},
903-
right: {
904-
type: "Identifier",
905-
start: 16,
906-
end: 19,
907-
name: "obj"
908-
},
909-
body: {
910-
type: "BlockStatement",
911-
start: 21,
912-
end: 23,
913-
body: []
914-
}
915-
}],
916-
sourceType: "module"
917-
}, {ecmaVersion: 17, sourceType: "module"});
877+
testFail("for (using x in obj) {}", "Using declaration is not allowed in for-in loops (1:13)", {ecmaVersion: 17, sourceType: "module"});
878+
879+
// For-in loop with await using
880+
testFail("for (await using x in obj) {}", "Using declaration is not allowed in for-in loops (1:19)", {ecmaVersion: 17, sourceType: "module"});
918881

919882
// Await using in for-of loop
920883
test("async function test() { for (await using x of resources) {} }", {
@@ -2990,3 +2953,160 @@ test(`async function f() {
29902953
],
29912954
"sourceType": "script"
29922955
}, {ecmaVersion: 17})
2956+
2957+
// `(await) using of` edge cases in for loops
2958+
2959+
test("for (using of = x;;) {}", {
2960+
"type": "Program",
2961+
"start": 0,
2962+
"end": 23,
2963+
"body": [
2964+
{
2965+
"type": "ForStatement",
2966+
"start": 0,
2967+
"end": 23,
2968+
"init": {
2969+
"type": "VariableDeclaration",
2970+
"start": 5,
2971+
"end": 17,
2972+
"declarations": [
2973+
{
2974+
"type": "VariableDeclarator",
2975+
"start": 11,
2976+
"end": 17,
2977+
"id": {
2978+
"type": "Identifier",
2979+
"start": 11,
2980+
"end": 13,
2981+
"name": "of"
2982+
},
2983+
"init": {
2984+
"type": "Identifier",
2985+
"start": 16,
2986+
"end": 17,
2987+
"name": "x"
2988+
}
2989+
}
2990+
],
2991+
"kind": "using"
2992+
},
2993+
"test": null,
2994+
"update": null,
2995+
"body": {
2996+
"type": "BlockStatement",
2997+
"start": 21,
2998+
"end": 23,
2999+
"body": []
3000+
}
3001+
}
3002+
],
3003+
"sourceType": "module"
3004+
}, {ecmaVersion: 17, sourceType: "module"})
3005+
3006+
test("for (await using of = x;;) {}", {
3007+
"type": "Program",
3008+
"start": 0,
3009+
"end": 29,
3010+
"body": [
3011+
{
3012+
"type": "ForStatement",
3013+
"start": 0,
3014+
"end": 29,
3015+
"init": {
3016+
"type": "VariableDeclaration",
3017+
"start": 5,
3018+
"end": 23,
3019+
"declarations": [
3020+
{
3021+
"type": "VariableDeclarator",
3022+
"start": 17,
3023+
"end": 23,
3024+
"id": {
3025+
"type": "Identifier",
3026+
"start": 17,
3027+
"end": 19,
3028+
"name": "of"
3029+
},
3030+
"init": {
3031+
"type": "Identifier",
3032+
"start": 22,
3033+
"end": 23,
3034+
"name": "x"
3035+
}
3036+
}
3037+
],
3038+
"kind": "await using"
3039+
},
3040+
"test": null,
3041+
"update": null,
3042+
"body": {
3043+
"type": "BlockStatement",
3044+
"start": 27,
3045+
"end": 29,
3046+
"body": []
3047+
}
3048+
}
3049+
],
3050+
"sourceType": "module"
3051+
}, {ecmaVersion: 17, sourceType: "module"})
3052+
3053+
testFail("for (using of = x of []) {}", "for-of loop variable declaration may not have an initializer (1:5)", {ecmaVersion: 17, sourceType: "module"})
3054+
3055+
testFail("for (using of = x in {}) {}", "for-in loop variable declaration may not have an initializer (1:5)", {ecmaVersion: 17, sourceType: "module"})
3056+
3057+
testFail("for (using of == x;;) {}", "Unexpected token (1:14)", {ecmaVersion: 17, sourceType: "module"})
3058+
3059+
testFail("for (using of => x;;) {}", "Unexpected token (1:14)", {ecmaVersion: 17, sourceType: "module"})
3060+
3061+
testFail("for (using of of []) {}", "Unexpected token (1:18)", {ecmaVersion: 17, sourceType: "module"})
3062+
3063+
test("for (await using of of []) {}", {
3064+
"type": "Program",
3065+
"start": 0,
3066+
"end": 29,
3067+
"body": [
3068+
{
3069+
"type": "ForOfStatement",
3070+
"start": 0,
3071+
"end": 29,
3072+
"await": false,
3073+
"left": {
3074+
"type": "VariableDeclaration",
3075+
"start": 5,
3076+
"end": 19,
3077+
"declarations": [
3078+
{
3079+
"type": "VariableDeclarator",
3080+
"start": 17,
3081+
"end": 19,
3082+
"id": {
3083+
"type": "Identifier",
3084+
"start": 17,
3085+
"end": 19,
3086+
"name": "of"
3087+
},
3088+
"init": null
3089+
}
3090+
],
3091+
"kind": "await using"
3092+
},
3093+
"right": {
3094+
"type": "ArrayExpression",
3095+
"start": 23,
3096+
"end": 25,
3097+
"elements": []
3098+
},
3099+
"body": {
3100+
"type": "BlockStatement",
3101+
"start": 27,
3102+
"end": 29,
3103+
"body": []
3104+
}
3105+
}
3106+
],
3107+
"sourceType": "module"
3108+
}, {ecmaVersion: 17, sourceType: "module"})
3109+
3110+
testFail("for (;;) using x = resource", "Using declaration is not allowed in single-statement positions (1:9)", {ecmaVersion: 17, sourceType: "module"})
3111+
3112+
testFail("if (true) await using x = resource", "Using declaration is not allowed in single-statement positions (1:10)", {ecmaVersion: 17, sourceType: "module"})

0 commit comments

Comments
 (0)