diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index eace2e2c280..d7519127ed9 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -1,67 +1,69 @@ -import { - addEmitHelpers, - addRange, - append, - arrayFrom, - BindingElement, - Block, - Bundle, - CaseOrDefaultClause, - chainBundle, - ClassDeclaration, - Debug, - EmitFlags, - ExportAssignment, - ExportSpecifier, - Expression, - firstOrUndefined, - ForOfStatement, - ForStatement, - GeneratedIdentifierFlags, - getEmitFlags, - hasSyntacticModifier, - Identifier, - IdentifierNameMap, - isArray, - isBindingPattern, - isBlock, - isCaseClause, - isCustomPrologue, - isExpression, - isGeneratedIdentifier, - isIdentifier, - isLocalName, - isNamedEvaluation, - isOmittedExpression, - isPrologueDirective, - isSourceFile, - isStatement, - isVariableDeclarationList, - isVariableStatement, - ModifierFlags, - Node, - NodeFlags, - setCommentRange, - setEmitFlags, - setOriginalNode, - setSourceMapRange, - setTextRange, - skipOuterExpressions, - SourceFile, - Statement, - SwitchStatement, - SyntaxKind, - TransformationContext, - TransformFlags, - transformNamedEvaluation, - VariableDeclaration, - VariableDeclarationList, - VariableStatement, - visitArray, - visitEachChild, - visitNode, - visitNodes, - VisitResult, +import { + addEmitHelpers, + addRange, + append, + arrayFrom, + BindingElement, + Block, + Bundle, + CaseOrDefaultClause, + chainBundle, + ClassDeclaration, + Debug, + EmitFlags, + ExportAssignment, + ExportSpecifier, + Expression, + firstOrUndefined, + forEachChild, + ForOfStatement, + ForStatement, + GeneratedIdentifierFlags, + getEmitFlags, + hasSyntacticModifier, + Identifier, + IdentifierNameMap, + isArray, + isBindingPattern, + isBlock, + isCaseClause, + isCustomPrologue, + isExpression, + isGeneratedIdentifier, + isIdentifier, + isLocalName, + isNamedEvaluation, + isOmittedExpression, + isPrologueDirective, + isSourceFile, + isStatement, + isVariableDeclaration, + isVariableDeclarationList, + isVariableStatement, + ModifierFlags, + Node, + NodeFlags, + setCommentRange, + setEmitFlags, + setOriginalNode, + setSourceMapRange, + setTextRange, + skipOuterExpressions, + SourceFile, + Statement, + SwitchStatement, + SyntaxKind, + TransformationContext, + TransformFlags, + transformNamedEvaluation, + VariableDeclaration, + VariableDeclarationList, + VariableStatement, + visitArray, + visitEachChild, + visitNode, + visitNodes, + VisitResult, } from "../_namespaces/ts.js"; const enum UsingKind { @@ -289,54 +291,121 @@ export function transformESNext(context: TransformationContext): (x: SourceFile ); } - return visitEachChild(node, visitor, context); - } - - function visitForOfStatement(node: ForOfStatement) { - if (isUsingVariableDeclarationList(node.initializer)) { - // given: - // - // for (using x of y) { ... } - // - // produces a shallow transformation to: - // - // for (const x_1 of y) { - // using x = x; - // ... - // } - // - // before handing the shallow transformation back to the visitor for an in-depth transformation. - const forInitializer = node.initializer; - const forDecl = firstOrUndefined(forInitializer.declarations) || factory.createVariableDeclaration(factory.createTempVariable(/*recordTempVariable*/ undefined)); - - const isAwaitUsing = getUsingKindOfVariableDeclarationList(forInitializer) === UsingKind.Async; - const temp = factory.getGeneratedNameForNode(forDecl.name); - const usingVar = factory.updateVariableDeclaration(forDecl, forDecl.name, /*exclamationToken*/ undefined, /*type*/ undefined, temp); - const usingVarList = factory.createVariableDeclarationList([usingVar], isAwaitUsing ? NodeFlags.AwaitUsing : NodeFlags.Using); - const usingVarStatement = factory.createVariableStatement(/*modifiers*/ undefined, usingVarList); - return visitNode( - factory.updateForOfStatement( - node, - node.awaitModifier, - factory.createVariableDeclarationList([ - factory.createVariableDeclaration(temp), - ], NodeFlags.Const), - node.expression, - isBlock(node.statement) ? - factory.updateBlock(node.statement, [ - usingVarStatement, - ...node.statement.statements, - ]) : - factory.createBlock([ - usingVarStatement, - node.statement, - ], /*multiLine*/ true), - ), - visitor, - isStatement, - ); - } - return visitEachChild(node, visitor, context); + return visitEachChild(node, visitor, context); + } + + /** + * Collects all variable declarations that shadow a given identifier name in a statement. + */ + function collectShadowingVariables(statement: Statement, shadowedName: string): VariableDeclaration[] { + const shadowingVars: VariableDeclaration[] = []; + + function visit(node: Node): void { + if (isVariableStatement(node)) { + for (const declaration of node.declarationList.declarations) { + if (isIdentifier(declaration.name) && declaration.name.escapedText === shadowedName) { + shadowingVars.push(declaration); + } + } + } + forEachChild(node, visit); + } + + visit(statement); + return shadowingVars; + } + + /** + * Creates a visitor that renames shadowing variables to avoid conflicts. + */ + function createShadowingVariableRenamer(shadowedName: string): (node: Node) => VisitResult { + const renamingMap = new Map(); + + return function renameShadowingVariables(node: Node): VisitResult { + if (isVariableDeclaration(node) && isIdentifier(node.name) && node.name.escapedText === shadowedName) { + // Create a unique name for this shadowing variable + const uniqueName = factory.createUniqueName(shadowedName as string, GeneratedIdentifierFlags.Optimistic); + renamingMap.set(node.name.escapedText as string, uniqueName); + + return factory.updateVariableDeclaration( + node, + uniqueName, + node.exclamationToken, + node.type, + visitNode(node.initializer, renameShadowingVariables, isExpression) + ); + } + + if (isIdentifier(node)) { + const renamed = renamingMap.get(node.escapedText as string); + if (renamed) { + return renamed; + } + } + + return visitEachChild(node, renameShadowingVariables, context); + }; + } + + function visitForOfStatement(node: ForOfStatement) { + if (isUsingVariableDeclarationList(node.initializer)) { + // given: + // + // for (using x of y) { ... } + // + // produces a shallow transformation to: + // + // for (const x_1 of y) { + // using x = x; + // ... + // } + // + // before handing the shallow transformation back to the visitor for an in-depth transformation. + const forInitializer = node.initializer; + const forDecl = firstOrUndefined(forInitializer.declarations) || factory.createVariableDeclaration(factory.createTempVariable(/*recordTempVariable*/ undefined)); + + const isAwaitUsing = getUsingKindOfVariableDeclarationList(forInitializer) === UsingKind.Async; + const temp = factory.getGeneratedNameForNode(forDecl.name); + const usingVar = factory.updateVariableDeclaration(forDecl, forDecl.name, /*exclamationToken*/ undefined, /*type*/ undefined, temp); + const usingVarList = factory.createVariableDeclarationList([usingVar], isAwaitUsing ? NodeFlags.AwaitUsing : NodeFlags.Using); + const usingVarStatement = factory.createVariableStatement(/*modifiers*/ undefined, usingVarList); + + // Check if the loop body contains shadowing variables and rename them if necessary + const shadowedName = isIdentifier(forDecl.name) ? forDecl.name.escapedText as string : undefined; + let transformedStatement = node.statement; + + if (shadowedName) { + const shadowingVars = collectShadowingVariables(node.statement, shadowedName); + if (shadowingVars.length > 0) { + // Apply the renaming visitor to the loop body + const renamer = createShadowingVariableRenamer(shadowedName); + transformedStatement = visitNode(node.statement, renamer, isStatement); + } + } + + return visitNode( + factory.updateForOfStatement( + node, + node.awaitModifier, + factory.createVariableDeclarationList([ + factory.createVariableDeclaration(temp), + ], NodeFlags.Const), + node.expression, + isBlock(transformedStatement) ? + factory.updateBlock(transformedStatement, [ + usingVarStatement, + ...transformedStatement.statements, + ]) : + factory.createBlock([ + usingVarStatement, + transformedStatement, + ], /*multiLine*/ true), + ), + visitor, + isStatement, + ); + } + return visitEachChild(node, visitor, context); } function visitCaseOrDefaultClause(node: CaseOrDefaultClause, envBinding: Identifier) { diff --git a/tests/cases/conformance/statements/VariableStatements/usingDeclarations/usingDeclarationsInForOfShadowing.ts b/tests/cases/conformance/statements/VariableStatements/usingDeclarations/usingDeclarationsInForOfShadowing.ts new file mode 100644 index 00000000000..2eafdebadd9 --- /dev/null +++ b/tests/cases/conformance/statements/VariableStatements/usingDeclarations/usingDeclarationsInForOfShadowing.ts @@ -0,0 +1,18 @@ +// @target: esnext,es2022,es2017,es2015,es5 +// @module: esnext +// @lib: esnext +// @noTypesAndSymbols: true + +class Foo {} + +for (using foo of []) { + const foo = new Foo(); +} + +for (using bar of []) { + let bar = "test"; +} + +for (using baz of []) { + var baz = 42; +} \ No newline at end of file