Skip to content

Commit 237579e

Browse files
Implement variable renaming for using declaration shadowing in for-of loops
Co-authored-by: RyanCavanaugh <[email protected]>
1 parent 34e17b3 commit 237579e

File tree

2 files changed

+199
-112
lines changed

2 files changed

+199
-112
lines changed

src/compiler/transformers/esnext.ts

Lines changed: 181 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,69 @@
1-
import {
2-
addEmitHelpers,
3-
addRange,
4-
append,
5-
arrayFrom,
6-
BindingElement,
7-
Block,
8-
Bundle,
9-
CaseOrDefaultClause,
10-
chainBundle,
11-
ClassDeclaration,
12-
Debug,
13-
EmitFlags,
14-
ExportAssignment,
15-
ExportSpecifier,
16-
Expression,
17-
firstOrUndefined,
18-
ForOfStatement,
19-
ForStatement,
20-
GeneratedIdentifierFlags,
21-
getEmitFlags,
22-
hasSyntacticModifier,
23-
Identifier,
24-
IdentifierNameMap,
25-
isArray,
26-
isBindingPattern,
27-
isBlock,
28-
isCaseClause,
29-
isCustomPrologue,
30-
isExpression,
31-
isGeneratedIdentifier,
32-
isIdentifier,
33-
isLocalName,
34-
isNamedEvaluation,
35-
isOmittedExpression,
36-
isPrologueDirective,
37-
isSourceFile,
38-
isStatement,
39-
isVariableDeclarationList,
40-
isVariableStatement,
41-
ModifierFlags,
42-
Node,
43-
NodeFlags,
44-
setCommentRange,
45-
setEmitFlags,
46-
setOriginalNode,
47-
setSourceMapRange,
48-
setTextRange,
49-
skipOuterExpressions,
50-
SourceFile,
51-
Statement,
52-
SwitchStatement,
53-
SyntaxKind,
54-
TransformationContext,
55-
TransformFlags,
56-
transformNamedEvaluation,
57-
VariableDeclaration,
58-
VariableDeclarationList,
59-
VariableStatement,
60-
visitArray,
61-
visitEachChild,
62-
visitNode,
63-
visitNodes,
64-
VisitResult,
1+
import {
2+
addEmitHelpers,
3+
addRange,
4+
append,
5+
arrayFrom,
6+
BindingElement,
7+
Block,
8+
Bundle,
9+
CaseOrDefaultClause,
10+
chainBundle,
11+
ClassDeclaration,
12+
Debug,
13+
EmitFlags,
14+
ExportAssignment,
15+
ExportSpecifier,
16+
Expression,
17+
firstOrUndefined,
18+
forEachChild,
19+
ForOfStatement,
20+
ForStatement,
21+
GeneratedIdentifierFlags,
22+
getEmitFlags,
23+
hasSyntacticModifier,
24+
Identifier,
25+
IdentifierNameMap,
26+
isArray,
27+
isBindingPattern,
28+
isBlock,
29+
isCaseClause,
30+
isCustomPrologue,
31+
isExpression,
32+
isGeneratedIdentifier,
33+
isIdentifier,
34+
isLocalName,
35+
isNamedEvaluation,
36+
isOmittedExpression,
37+
isPrologueDirective,
38+
isSourceFile,
39+
isStatement,
40+
isVariableDeclaration,
41+
isVariableDeclarationList,
42+
isVariableStatement,
43+
ModifierFlags,
44+
Node,
45+
NodeFlags,
46+
setCommentRange,
47+
setEmitFlags,
48+
setOriginalNode,
49+
setSourceMapRange,
50+
setTextRange,
51+
skipOuterExpressions,
52+
SourceFile,
53+
Statement,
54+
SwitchStatement,
55+
SyntaxKind,
56+
TransformationContext,
57+
TransformFlags,
58+
transformNamedEvaluation,
59+
VariableDeclaration,
60+
VariableDeclarationList,
61+
VariableStatement,
62+
visitArray,
63+
visitEachChild,
64+
visitNode,
65+
visitNodes,
66+
VisitResult,
6567
} from "../_namespaces/ts.js";
6668

6769
const enum UsingKind {
@@ -289,54 +291,121 @@ export function transformESNext(context: TransformationContext): (x: SourceFile
289291
);
290292
}
291293

292-
return visitEachChild(node, visitor, context);
293-
}
294-
295-
function visitForOfStatement(node: ForOfStatement) {
296-
if (isUsingVariableDeclarationList(node.initializer)) {
297-
// given:
298-
//
299-
// for (using x of y) { ... }
300-
//
301-
// produces a shallow transformation to:
302-
//
303-
// for (const x_1 of y) {
304-
// using x = x;
305-
// ...
306-
// }
307-
//
308-
// before handing the shallow transformation back to the visitor for an in-depth transformation.
309-
const forInitializer = node.initializer;
310-
const forDecl = firstOrUndefined(forInitializer.declarations) || factory.createVariableDeclaration(factory.createTempVariable(/*recordTempVariable*/ undefined));
311-
312-
const isAwaitUsing = getUsingKindOfVariableDeclarationList(forInitializer) === UsingKind.Async;
313-
const temp = factory.getGeneratedNameForNode(forDecl.name);
314-
const usingVar = factory.updateVariableDeclaration(forDecl, forDecl.name, /*exclamationToken*/ undefined, /*type*/ undefined, temp);
315-
const usingVarList = factory.createVariableDeclarationList([usingVar], isAwaitUsing ? NodeFlags.AwaitUsing : NodeFlags.Using);
316-
const usingVarStatement = factory.createVariableStatement(/*modifiers*/ undefined, usingVarList);
317-
return visitNode(
318-
factory.updateForOfStatement(
319-
node,
320-
node.awaitModifier,
321-
factory.createVariableDeclarationList([
322-
factory.createVariableDeclaration(temp),
323-
], NodeFlags.Const),
324-
node.expression,
325-
isBlock(node.statement) ?
326-
factory.updateBlock(node.statement, [
327-
usingVarStatement,
328-
...node.statement.statements,
329-
]) :
330-
factory.createBlock([
331-
usingVarStatement,
332-
node.statement,
333-
], /*multiLine*/ true),
334-
),
335-
visitor,
336-
isStatement,
337-
);
338-
}
339-
return visitEachChild(node, visitor, context);
294+
return visitEachChild(node, visitor, context);
295+
}
296+
297+
/**
298+
* Collects all variable declarations that shadow a given identifier name in a statement.
299+
*/
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+
}
317+
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+
}
349+
350+
function visitForOfStatement(node: ForOfStatement) {
351+
if (isUsingVariableDeclarationList(node.initializer)) {
352+
// given:
353+
//
354+
// for (using x of y) { ... }
355+
//
356+
// produces a shallow transformation to:
357+
//
358+
// for (const x_1 of y) {
359+
// using x = x;
360+
// ...
361+
// }
362+
//
363+
// before handing the shallow transformation back to the visitor for an in-depth transformation.
364+
const forInitializer = node.initializer;
365+
const forDecl = firstOrUndefined(forInitializer.declarations) || factory.createVariableDeclaration(factory.createTempVariable(/*recordTempVariable*/ undefined));
366+
367+
const isAwaitUsing = getUsingKindOfVariableDeclarationList(forInitializer) === UsingKind.Async;
368+
const temp = factory.getGeneratedNameForNode(forDecl.name);
369+
const usingVar = factory.updateVariableDeclaration(forDecl, forDecl.name, /*exclamationToken*/ undefined, /*type*/ undefined, temp);
370+
const usingVarList = factory.createVariableDeclarationList([usingVar], isAwaitUsing ? NodeFlags.AwaitUsing : NodeFlags.Using);
371+
const usingVarStatement = factory.createVariableStatement(/*modifiers*/ undefined, usingVarList);
372+
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+
}
385+
386+
return visitNode(
387+
factory.updateForOfStatement(
388+
node,
389+
node.awaitModifier,
390+
factory.createVariableDeclarationList([
391+
factory.createVariableDeclaration(temp),
392+
], NodeFlags.Const),
393+
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),
403+
),
404+
visitor,
405+
isStatement,
406+
);
407+
}
408+
return visitEachChild(node, visitor, context);
340409
}
341410

342411
function visitCaseOrDefaultClause(node: CaseOrDefaultClause, envBinding: Identifier) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// @target: esnext,es2022,es2017,es2015,es5
2+
// @module: esnext
3+
// @lib: esnext
4+
// @noTypesAndSymbols: true
5+
6+
class Foo {}
7+
8+
for (using foo of []) {
9+
const foo = new Foo();
10+
}
11+
12+
for (using bar of []) {
13+
let bar = "test";
14+
}
15+
16+
for (using baz of []) {
17+
var baz = 42;
18+
}

0 commit comments

Comments
 (0)