mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-05 16:38:05 -06:00
Implement variable renaming for using declaration shadowing in for-of loops
Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com>
This commit is contained in:
parent
34e17b3bb8
commit
237579e33f
@ -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<Node> {
|
||||
const renamingMap = new Map<string, Identifier>();
|
||||
|
||||
return function renameShadowingVariables(node: Node): VisitResult<Node> {
|
||||
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) {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user