Implement block scoping approach for using declaration shadowing in for-of loops

Co-authored-by: weswigham <2932786+weswigham@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2025-06-25 19:31:16 +00:00
co-authored by weswigham
parent 2738f69f57
commit 8f3df02544
29 changed files with 112 additions and 100 deletions
+12 -74
View File
@@ -15,7 +15,6 @@ import {
ExportSpecifier,
Expression,
firstOrUndefined,
forEachChild,
ForOfStatement,
ForStatement,
GeneratedIdentifierFlags,
@@ -37,7 +36,6 @@ import {
isPrologueDirective,
isSourceFile,
isStatement,
isVariableDeclaration,
isVariableDeclarationList,
isVariableStatement,
ModifierFlags,
@@ -297,55 +295,7 @@ export function transformESNext(context: TransformationContext): (x: SourceFile
/**
* 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)) {
@@ -356,11 +306,12 @@ export function transformESNext(context: TransformationContext): (x: SourceFile
// produces a shallow transformation to:
//
// for (const x_1 of y) {
// using x = x;
// ...
// using x = x_1;
// { ... }
// }
//
// before handing the shallow transformation back to the visitor for an in-depth transformation.
// where the original loop body is wrapped in an additional block scope
// to handle shadowing variables naturally through block scoping.
const forInitializer = node.initializer;
const forDecl = firstOrUndefined(forInitializer.declarations) || factory.createVariableDeclaration(factory.createTempVariable(/*recordTempVariable*/ undefined));
@@ -370,18 +321,10 @@ export function transformESNext(context: TransformationContext): (x: SourceFile
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);
}
}
// Wrap the original loop body in an additional block scope to handle shadowing
const wrappedStatement = isBlock(node.statement) ?
node.statement :
factory.createBlock([node.statement], /*multiLine*/ true);
return visitNode(
factory.updateForOfStatement(
@@ -391,15 +334,10 @@ export function transformESNext(context: TransformationContext): (x: SourceFile
factory.createVariableDeclaration(temp),
], NodeFlags.Const),
node.expression,
isBlock(transformedStatement) ?
factory.updateBlock(transformedStatement, [
usingVarStatement,
...transformedStatement.statements,
]) :
factory.createBlock([
usingVarStatement,
transformedStatement,
], /*multiLine*/ true),
factory.createBlock([
usingVarStatement,
wrappedStatement,
], /*multiLine*/ true),
),
visitor,
isStatement,
@@ -88,6 +88,8 @@ function main() {
const env_1 = { stack: [], error: void 0, hasError: false };
try {
const d1 = __addDisposableResource(env_1, d1_1, true);
{
}
}
catch (e_2) {
env_1.error = e_2;
@@ -76,6 +76,8 @@ async function main() {
const env_1 = { stack: [], error: void 0, hasError: false };
try {
const d1 = __addDisposableResource(env_1, d1_1, true);
{
}
}
catch (e_2) {
env_1.error = e_2;
@@ -64,6 +64,8 @@ async function main() {
const env_1 = { stack: [], error: void 0, hasError: false };
try {
const d1 = __addDisposableResource(env_1, d1_1, true);
{
}
}
catch (e_1) {
env_1.error = e_1;
@@ -128,6 +128,8 @@ function main() {
case 3:
_j.trys.push([3, 4, 5, 8]);
d1 = __addDisposableResource(env_1, d1_1, true);
{
}
return [3 /*break*/, 8];
case 4:
e_1 = _j.sent();
@@ -118,7 +118,9 @@ try {
var env_1 = { stack: [], error: void 0, hasError: false };
try {
var _e = __addDisposableResource(env_1, _e_1, true);
;
{
;
}
}
catch (e_2) {
env_1.error = e_2;
@@ -159,7 +161,9 @@ export function test() {
case 3:
_f.trys.push([3, 4, 5, 8]);
_b = __addDisposableResource(env_2, _b_1, true);
;
{
;
}
return [3 /*break*/, 8];
case 4:
e_3 = _f.sent();
@@ -77,6 +77,8 @@ function main() {
const env_1 = { stack: [], error: void 0, hasError: false };
try {
const d1 = __addDisposableResource(env_1, d1_1, true);
{
}
}
catch (e_1) {
env_1.error = e_1;
@@ -65,6 +65,8 @@ async function main() {
const env_1 = { stack: [], error: void 0, hasError: false };
try {
const d1 = __addDisposableResource(env_1, d1_1, true);
{
}
}
catch (e_1) {
env_1.error = e_1;
@@ -65,6 +65,8 @@ async function main() {
const env_1 = { stack: [], error: void 0, hasError: false };
try {
const d1 = __addDisposableResource(env_1, d1_1, true);
{
}
}
catch (e_1) {
env_1.error = e_1;
@@ -117,6 +117,8 @@ function main() {
case 2:
_d.trys.push([2, 3, 4, 7]);
d1 = __addDisposableResource(env_1, d1_1, true);
{
}
return [3 /*break*/, 7];
case 3:
e_1 = _d.sent();
@@ -107,7 +107,9 @@ for (var _i = 0, x_1 = x; _i < x_1.length; _i++) {
var env_1 = { stack: [], error: void 0, hasError: false };
try {
var _a = __addDisposableResource(env_1, _a_1, true);
;
{
;
}
}
catch (e_1) {
env_1.error = e_1;
@@ -135,7 +137,9 @@ export function test() {
case 2:
_b.trys.push([2, 3, 4, 7]);
_a = __addDisposableResource(env_2, _a_2, true);
;
{
;
}
return [3 /*break*/, 7];
case 3:
e_2 = _b.sent();
@@ -87,6 +87,8 @@ function main() {
const env_1 = { stack: [], error: void 0, hasError: false };
try {
const d1 = __addDisposableResource(env_1, d1_1, false);
{
}
}
catch (e_2) {
env_1.error = e_2;
@@ -77,6 +77,8 @@ async function main() {
const env_1 = { stack: [], error: void 0, hasError: false };
try {
const d1 = __addDisposableResource(env_1, d1_1, false);
{
}
}
catch (e_2) {
env_1.error = e_2;
@@ -65,6 +65,8 @@ async function main() {
const env_1 = { stack: [], error: void 0, hasError: false };
try {
const d1 = __addDisposableResource(env_1, d1_1, false);
{
}
}
catch (e_1) {
env_1.error = e_1;
@@ -123,6 +123,8 @@ function main() {
env_1 = { stack: [], error: void 0, hasError: false };
try {
d1 = __addDisposableResource(env_1, d1_1, false);
{
}
}
catch (e_2) {
env_1.error = e_2;
@@ -62,6 +62,8 @@ for (const d1_1 of [{ [Symbol.dispose]() { } }, null, undefined]) {
const env_1 = { stack: [], error: void 0, hasError: false };
try {
const d1 = __addDisposableResource(env_1, d1_1, false);
{
}
}
catch (e_1) {
env_1.error = e_1;
@@ -62,6 +62,8 @@ for (const d1_1 of [{ [Symbol.dispose]() { } }, null, undefined]) {
const env_1 = { stack: [], error: void 0, hasError: false };
try {
const d1 = __addDisposableResource(env_1, d1_1, false);
{
}
}
catch (e_1) {
env_1.error = e_1;
@@ -62,6 +62,8 @@ for (const d1_1 of [{ [Symbol.dispose]() { } }, null, undefined]) {
const env_1 = { stack: [], error: void 0, hasError: false };
try {
const d1 = __addDisposableResource(env_1, d1_1, false);
{
}
}
catch (e_1) {
env_1.error = e_1;
@@ -64,6 +64,8 @@ for (var _i = 0, _b = [(_a = {}, _a[Symbol.dispose] = function () { }, _a), null
var env_1 = { stack: [], error: void 0, hasError: false };
try {
var d1 = __addDisposableResource(env_1, d1_1, false);
{
}
}
catch (e_1) {
env_1.error = e_1;
@@ -20,4 +20,5 @@ main.ts(12,7): error TS2481: Cannot initialize outer scoped variable 'baz' in th
==== tslib.d.ts (0 errors) ====
export declare function __addDisposableResource<T>(env: any, value: T, async: boolean): T;
export declare function __disposeResources(env: any): void;
export declare function __disposeResources(env: any): void;
@@ -17,7 +17,8 @@ for (using baz of []) {
//// [tslib.d.ts]
export declare function __addDisposableResource<T>(env: any, value: T, async: boolean): T;
export declare function __disposeResources(env: any): void;
export declare function __disposeResources(env: any): void;
//// [main.js]
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
@@ -78,7 +79,9 @@ for (const foo_1 of []) {
const env_1 = { stack: [], error: void 0, hasError: false };
try {
const foo = __addDisposableResource(env_1, foo_1, false);
const foo_2 = new Foo();
{
const foo = new Foo();
}
}
catch (e_1) {
env_1.error = e_1;
@@ -92,7 +95,9 @@ for (const bar_1 of []) {
const env_2 = { stack: [], error: void 0, hasError: false };
try {
const bar = __addDisposableResource(env_2, bar_1, false);
let bar_2 = "test";
{
let bar = "test";
}
}
catch (e_2) {
env_2.error = e_2;
@@ -106,7 +111,9 @@ for (const baz_1 of []) {
const env_3 = { stack: [], error: void 0, hasError: false };
try {
const baz = __addDisposableResource(env_3, baz_1, false);
var baz_2 = 42;
{
var baz = 42;
}
}
catch (e_3) {
env_3.error = e_3;
@@ -20,4 +20,5 @@ main.ts(12,7): error TS2481: Cannot initialize outer scoped variable 'baz' in th
==== tslib.d.ts (0 errors) ====
export declare function __addDisposableResource<T>(env: any, value: T, async: boolean): T;
export declare function __disposeResources(env: any): void;
export declare function __disposeResources(env: any): void;
@@ -17,7 +17,8 @@ for (using baz of []) {
//// [tslib.d.ts]
export declare function __addDisposableResource<T>(env: any, value: T, async: boolean): T;
export declare function __disposeResources(env: any): void;
export declare function __disposeResources(env: any): void;
//// [main.js]
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
@@ -78,7 +79,9 @@ for (const foo_1 of []) {
const env_1 = { stack: [], error: void 0, hasError: false };
try {
const foo = __addDisposableResource(env_1, foo_1, false);
const foo_2 = new Foo();
{
const foo = new Foo();
}
}
catch (e_1) {
env_1.error = e_1;
@@ -92,7 +95,9 @@ for (const bar_1 of []) {
const env_2 = { stack: [], error: void 0, hasError: false };
try {
const bar = __addDisposableResource(env_2, bar_1, false);
let bar_2 = "test";
{
let bar = "test";
}
}
catch (e_2) {
env_2.error = e_2;
@@ -106,7 +111,9 @@ for (const baz_1 of []) {
const env_3 = { stack: [], error: void 0, hasError: false };
try {
const baz = __addDisposableResource(env_3, baz_1, false);
var baz_2 = 42;
{
var baz = 42;
}
}
catch (e_3) {
env_3.error = e_3;
@@ -20,4 +20,5 @@ main.ts(12,7): error TS2481: Cannot initialize outer scoped variable 'baz' in th
==== tslib.d.ts (0 errors) ====
export declare function __addDisposableResource<T>(env: any, value: T, async: boolean): T;
export declare function __disposeResources(env: any): void;
export declare function __disposeResources(env: any): void;
@@ -17,7 +17,8 @@ for (using baz of []) {
//// [tslib.d.ts]
export declare function __addDisposableResource<T>(env: any, value: T, async: boolean): T;
export declare function __disposeResources(env: any): void;
export declare function __disposeResources(env: any): void;
//// [main.js]
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
@@ -78,7 +79,9 @@ for (const foo_1 of []) {
const env_1 = { stack: [], error: void 0, hasError: false };
try {
const foo = __addDisposableResource(env_1, foo_1, false);
const foo_2 = new Foo();
{
const foo = new Foo();
}
}
catch (e_1) {
env_1.error = e_1;
@@ -92,7 +95,9 @@ for (const bar_1 of []) {
const env_2 = { stack: [], error: void 0, hasError: false };
try {
const bar = __addDisposableResource(env_2, bar_1, false);
let bar_2 = "test";
{
let bar = "test";
}
}
catch (e_2) {
env_2.error = e_2;
@@ -106,7 +111,9 @@ for (const baz_1 of []) {
const env_3 = { stack: [], error: void 0, hasError: false };
try {
const baz = __addDisposableResource(env_3, baz_1, false);
var baz_2 = 42;
{
var baz = 42;
}
}
catch (e_3) {
env_3.error = e_3;
@@ -20,4 +20,5 @@ main.ts(12,7): error TS2481: Cannot initialize outer scoped variable 'baz' in th
==== tslib.d.ts (0 errors) ====
export declare function __addDisposableResource<T>(env: any, value: T, async: boolean): T;
export declare function __disposeResources(env: any): void;
export declare function __disposeResources(env: any): void;
@@ -17,7 +17,8 @@ for (using baz of []) {
//// [tslib.d.ts]
export declare function __addDisposableResource<T>(env: any, value: T, async: boolean): T;
export declare function __disposeResources(env: any): void;
export declare function __disposeResources(env: any): void;
//// [main.js]
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
@@ -82,7 +83,9 @@ for (var _i = 0, _a = []; _i < _a.length; _i++) {
var env_1 = { stack: [], error: void 0, hasError: false };
try {
var foo = __addDisposableResource(env_1, foo_1, false);
var foo_2 = new Foo();
{
var foo_2 = new Foo();
}
}
catch (e_1) {
env_1.error = e_1;
@@ -97,7 +100,9 @@ for (var _b = 0, _c = []; _b < _c.length; _b++) {
var env_2 = { stack: [], error: void 0, hasError: false };
try {
var bar = __addDisposableResource(env_2, bar_1, false);
var bar_2 = "test";
{
var bar_2 = "test";
}
}
catch (e_2) {
env_2.error = e_2;
@@ -112,7 +117,9 @@ for (var _d = 0, _e = []; _d < _e.length; _d++) {
var env_3 = { stack: [], error: void 0, hasError: false };
try {
var baz_1 = __addDisposableResource(env_3, baz_1, false);
var baz_2 = 42;
{
var baz = 42;
}
}
catch (e_3) {
env_3.error = e_3;
@@ -20,4 +20,5 @@ main.ts(12,7): error TS2481: Cannot initialize outer scoped variable 'baz' in th
==== tslib.d.ts (0 errors) ====
export declare function __addDisposableResource<T>(env: any, value: T, async: boolean): T;
export declare function __disposeResources(env: any): void;
export declare function __disposeResources(env: any): void;
@@ -17,7 +17,8 @@ for (using baz of []) {
//// [tslib.d.ts]
export declare function __addDisposableResource<T>(env: any, value: T, async: boolean): T;
export declare function __disposeResources(env: any): void;
export declare function __disposeResources(env: any): void;
//// [main.js]
class Foo {