Skip to content

Commit 8f3df02

Browse files
Copilotweswigham
andcommitted
Implement block scoping approach for using declaration shadowing in for-of loops
Co-authored-by: weswigham <2932786+weswigham@users.noreply.github.com>
1 parent 2738f69 commit 8f3df02

29 files changed

Lines changed: 112 additions & 100 deletions

File tree

src/compiler/transformers/esnext.ts

Lines changed: 12 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import {
1515
ExportSpecifier,
1616
Expression,
1717
firstOrUndefined,
18-
forEachChild,
1918
ForOfStatement,
2019
ForStatement,
2120
GeneratedIdentifierFlags,
@@ -37,7 +36,6 @@ import {
3736
isPrologueDirective,
3837
isSourceFile,
3938
isStatement,
40-
isVariableDeclaration,
4139
isVariableDeclarationList,
4240
isVariableStatement,
4341
ModifierFlags,
@@ -297,55 +295,7 @@ export function transformESNext(context: TransformationContext): (x: SourceFile
297295
/**
298296
* Collects all variable declarations that shadow a given identifier name in a statement.
299297
*/
300-
function collectShadowingVariables(statement: Statement, shadowedName: string): VariableDeclaration[] {
301-
const shadowingVars: VariableDeclaration[] = [];
302-
303-
function visit(node: Node): void {
304-
if (isVariableStatement(node)) {
305-
for (const declaration of node.declarationList.declarations) {
306-
if (isIdentifier(declaration.name) && declaration.name.escapedText === shadowedName) {
307-
shadowingVars.push(declaration);
308-
}
309-
}
310-
}
311-
forEachChild(node, visit);
312-
}
313-
314-
visit(statement);
315-
return shadowingVars;
316-
}
317298

318-
/**
319-
* Creates a visitor that renames shadowing variables to avoid conflicts.
320-
*/
321-
function createShadowingVariableRenamer(shadowedName: string): (node: Node) => VisitResult<Node> {
322-
const renamingMap = new Map<string, Identifier>();
323-
324-
return function renameShadowingVariables(node: Node): VisitResult<Node> {
325-
if (isVariableDeclaration(node) && isIdentifier(node.name) && node.name.escapedText === shadowedName) {
326-
// Create a unique name for this shadowing variable
327-
const uniqueName = factory.createUniqueName(shadowedName as string, GeneratedIdentifierFlags.Optimistic);
328-
renamingMap.set(node.name.escapedText as string, uniqueName);
329-
330-
return factory.updateVariableDeclaration(
331-
node,
332-
uniqueName,
333-
node.exclamationToken,
334-
node.type,
335-
visitNode(node.initializer, renameShadowingVariables, isExpression)
336-
);
337-
}
338-
339-
if (isIdentifier(node)) {
340-
const renamed = renamingMap.get(node.escapedText as string);
341-
if (renamed) {
342-
return renamed;
343-
}
344-
}
345-
346-
return visitEachChild(node, renameShadowingVariables, context);
347-
};
348-
}
349299

350300
function visitForOfStatement(node: ForOfStatement) {
351301
if (isUsingVariableDeclarationList(node.initializer)) {
@@ -356,11 +306,12 @@ export function transformESNext(context: TransformationContext): (x: SourceFile
356306
// produces a shallow transformation to:
357307
//
358308
// for (const x_1 of y) {
359-
// using x = x;
360-
// ...
309+
// using x = x_1;
310+
// { ... }
361311
// }
362312
//
363-
// before handing the shallow transformation back to the visitor for an in-depth transformation.
313+
// where the original loop body is wrapped in an additional block scope
314+
// to handle shadowing variables naturally through block scoping.
364315
const forInitializer = node.initializer;
365316
const forDecl = firstOrUndefined(forInitializer.declarations) || factory.createVariableDeclaration(factory.createTempVariable(/*recordTempVariable*/ undefined));
366317

@@ -370,18 +321,10 @@ export function transformESNext(context: TransformationContext): (x: SourceFile
370321
const usingVarList = factory.createVariableDeclarationList([usingVar], isAwaitUsing ? NodeFlags.AwaitUsing : NodeFlags.Using);
371322
const usingVarStatement = factory.createVariableStatement(/*modifiers*/ undefined, usingVarList);
372323

373-
// Check if the loop body contains shadowing variables and rename them if necessary
374-
const shadowedName = isIdentifier(forDecl.name) ? forDecl.name.escapedText as string : undefined;
375-
let transformedStatement = node.statement;
376-
377-
if (shadowedName) {
378-
const shadowingVars = collectShadowingVariables(node.statement, shadowedName);
379-
if (shadowingVars.length > 0) {
380-
// Apply the renaming visitor to the loop body
381-
const renamer = createShadowingVariableRenamer(shadowedName);
382-
transformedStatement = visitNode(node.statement, renamer, isStatement);
383-
}
384-
}
324+
// Wrap the original loop body in an additional block scope to handle shadowing
325+
const wrappedStatement = isBlock(node.statement) ?
326+
node.statement :
327+
factory.createBlock([node.statement], /*multiLine*/ true);
385328

386329
return visitNode(
387330
factory.updateForOfStatement(
@@ -391,15 +334,10 @@ export function transformESNext(context: TransformationContext): (x: SourceFile
391334
factory.createVariableDeclaration(temp),
392335
], NodeFlags.Const),
393336
node.expression,
394-
isBlock(transformedStatement) ?
395-
factory.updateBlock(transformedStatement, [
396-
usingVarStatement,
397-
...transformedStatement.statements,
398-
]) :
399-
factory.createBlock([
400-
usingVarStatement,
401-
transformedStatement,
402-
], /*multiLine*/ true),
337+
factory.createBlock([
338+
usingVarStatement,
339+
wrappedStatement,
340+
], /*multiLine*/ true),
403341
),
404342
visitor,
405343
isStatement,

tests/baselines/reference/awaitUsingDeclarationsInForAwaitOf(target=es2015).js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ function main() {
8888
const env_1 = { stack: [], error: void 0, hasError: false };
8989
try {
9090
const d1 = __addDisposableResource(env_1, d1_1, true);
91+
{
92+
}
9193
}
9294
catch (e_2) {
9395
env_1.error = e_2;

tests/baselines/reference/awaitUsingDeclarationsInForAwaitOf(target=es2017).js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ async function main() {
7676
const env_1 = { stack: [], error: void 0, hasError: false };
7777
try {
7878
const d1 = __addDisposableResource(env_1, d1_1, true);
79+
{
80+
}
7981
}
8082
catch (e_2) {
8183
env_1.error = e_2;

tests/baselines/reference/awaitUsingDeclarationsInForAwaitOf(target=es2022).js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ async function main() {
6464
const env_1 = { stack: [], error: void 0, hasError: false };
6565
try {
6666
const d1 = __addDisposableResource(env_1, d1_1, true);
67+
{
68+
}
6769
}
6870
catch (e_1) {
6971
env_1.error = e_1;

tests/baselines/reference/awaitUsingDeclarationsInForAwaitOf(target=es5).js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,8 @@ function main() {
128128
case 3:
129129
_j.trys.push([3, 4, 5, 8]);
130130
d1 = __addDisposableResource(env_1, d1_1, true);
131+
{
132+
}
131133
return [3 /*break*/, 8];
132134
case 4:
133135
e_1 = _j.sent();

tests/baselines/reference/awaitUsingDeclarationsInForAwaitOf.3(target=es5).js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,9 @@ try {
118118
var env_1 = { stack: [], error: void 0, hasError: false };
119119
try {
120120
var _e = __addDisposableResource(env_1, _e_1, true);
121-
;
121+
{
122+
;
123+
}
122124
}
123125
catch (e_2) {
124126
env_1.error = e_2;
@@ -159,7 +161,9 @@ export function test() {
159161
case 3:
160162
_f.trys.push([3, 4, 5, 8]);
161163
_b = __addDisposableResource(env_2, _b_1, true);
162-
;
164+
{
165+
;
166+
}
163167
return [3 /*break*/, 8];
164168
case 4:
165169
e_3 = _f.sent();

tests/baselines/reference/awaitUsingDeclarationsInForOf.1(target=es2015).js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ function main() {
7777
const env_1 = { stack: [], error: void 0, hasError: false };
7878
try {
7979
const d1 = __addDisposableResource(env_1, d1_1, true);
80+
{
81+
}
8082
}
8183
catch (e_1) {
8284
env_1.error = e_1;

tests/baselines/reference/awaitUsingDeclarationsInForOf.1(target=es2017).js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ async function main() {
6565
const env_1 = { stack: [], error: void 0, hasError: false };
6666
try {
6767
const d1 = __addDisposableResource(env_1, d1_1, true);
68+
{
69+
}
6870
}
6971
catch (e_1) {
7072
env_1.error = e_1;

tests/baselines/reference/awaitUsingDeclarationsInForOf.1(target=es2022).js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ async function main() {
6565
const env_1 = { stack: [], error: void 0, hasError: false };
6666
try {
6767
const d1 = __addDisposableResource(env_1, d1_1, true);
68+
{
69+
}
6870
}
6971
catch (e_1) {
7072
env_1.error = e_1;

tests/baselines/reference/awaitUsingDeclarationsInForOf.1(target=es5).js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ function main() {
117117
case 2:
118118
_d.trys.push([2, 3, 4, 7]);
119119
d1 = __addDisposableResource(env_1, d1_1, true);
120+
{
121+
}
120122
return [3 /*break*/, 7];
121123
case 3:
122124
e_1 = _d.sent();

0 commit comments

Comments
 (0)