Skip to content

Fix using transform variable shadowing in for-of loops #61934

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

Copilot
Copy link
Contributor

@Copilot Copilot AI commented Jun 24, 2025

The using declaration transform was throwing "redeclaration of const" errors when the for-of loop body contained variables that shadowed the using variable name.

Problem:

class Foo {}
for (using foo of []) {
  const foo = new Foo(); // Error: redeclaration of const foo
}

The transformed output would generate:

for (const foo_1 of []) {
    try {
        const foo = __addDisposableResource(env_1, foo_1, false);
        const foo = new Foo(); // ❌ Duplicate declaration
    }
    // ...
}

Solution:
This PR implements variable renaming for shadowing variables in using for-of loops, similar to how ES5 for-of transforms handle variable conflicts. The fix:

  1. Detects variables in the loop body that shadow the using variable name
  2. Renames them with unique identifiers (e.g., foofoo_2)
  3. Updates all references to use the renamed variables

After the fix:

for (const foo_1 of []) {
    try {
        const foo = __addDisposableResource(env_1, foo_1, false);
        const foo_2 = new Foo(); // ✅ Renamed to avoid conflict
    }
    // ...
}

Features:

  • Works with all variable declaration types (const, let, var)
  • Handles nested scopes and multiple shadowing variables
  • Preserves legitimate scoping errors (e.g., var vs block-scoped conflicts)
  • Only affects using for-of loops, regular for-of loops unchanged

Fixes #61714.


💬 Share your feedback on Copilot coding agent for the chance to win a $200 gift card! Click here to start the survey.

@typescript-bot typescript-bot added the For Milestone Bug PRs that fix a bug with a specific milestone label Jun 24, 2025
@Copilot Copilot AI changed the title [WIP] using transform throws when a for body binding shadows the for head binding Fix using transform variable shadowing in for-of loops Jun 24, 2025
@Copilot Copilot AI requested a review from RyanCavanaugh June 24, 2025 17:07
Copilot finished work on behalf of RyanCavanaugh June 24, 2025 17:07
Copy link
Member

@RyanCavanaugh RyanCavanaugh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Run hereby format to fix your line endings

Add importHelpers: true to your testcase so the emit is tidier

Copy link
Contributor Author

Copilot AI commented Jun 24, 2025

Run hereby format to fix your line endings

Add importHelpers: true to your testcase so the emit is tidier

Fixed line endings by adding missing newline at end of file. Added importHelpers: true directive and restructured test with multi-file format for cleaner organization. Commit 2738f69.

Warning

Firewall rules blocked me from connecting to one or more addresses

I tried to connect to the following addresses, but was blocked by firewall rules:

  • plugins.dprint.dev
    • Triggering command: /home/REDACTED/work/TypeScript/TypeScript/node_modules/dprint/dprint fmt (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

@Copilot Copilot AI requested a review from RyanCavanaugh June 24, 2025 17:25
Copilot finished work on behalf of RyanCavanaugh June 24, 2025 17:25
@RyanCavanaugh RyanCavanaugh requested a review from weswigham June 24, 2025 18:09
@RyanCavanaugh RyanCavanaugh marked this pull request as ready for review June 24, 2025 18:09
@weswigham
Copy link
Member

@copilot I'd prefer a different approach. Instead of renaming anything, inject an extra block scope between the moved and downleveled using declaration and the actual loop body. So the output should look like

for (const foo_1 of []) {
    try {
        const foo = __addDisposableResource(env_1, foo_1, false);
        {
            const foo = new Foo();
        }
    }
    // ...
}

instead.

Copy link
Contributor Author

Copilot AI commented Jun 25, 2025

@copilot I'd prefer a different approach. Instead of renaming anything, inject an extra block scope between the moved and downleveled using declaration and the actual loop body. So the output should...

Implemented the block scoping approach as suggested. Instead of renaming variables, the transformation now wraps the loop body in an additional block scope to handle shadowing naturally. The output now matches your specification exactly. Commit 8f3df02.

Warning

Firewall rules blocked me from connecting to one or more addresses

I tried to connect to the following addresses, but was blocked by firewall rules:

  • plugins.dprint.dev
    • Triggering command: /home/REDACTED/work/TypeScript/TypeScript/node_modules/dprint/dprint fmt (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot finished work on behalf of weswigham June 25, 2025 19:31
@@ -88,6 +88,8 @@ function main() {
const env_1 = { stack: [], error: void 0, hasError: false };
try {
const d1 = __addDisposableResource(env_1, d1_1, true);
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the existing block is empty, we shouldn't bother adding a new, empty block.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in commit 0b3bf33. The implementation now checks if the loop body is empty and skips adding the extra wrapping block in those cases. Empty using for-of loops no longer generate redundant empty blocks.

return visitEachChild(node, visitor, context);
}

/**
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delete this comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deleted the comment in commit 0b3bf33. The obsolete comment about collecting variable declarations has been removed from esnext.ts.

@Copilot Copilot AI requested a review from weswigham June 25, 2025 20:47
Copilot finished work on behalf of weswigham June 25, 2025 20:47
Copy link
Member

@weswigham weswigham left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also don't change the line endings that are already in the edited code.

@@ -118,7 +118,9 @@ try {
var env_1 = { stack: [], error: void 0, hasError: false };
try {
var _e = __addDisposableResource(env_1, _e_1, true);
;
{
;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should consider a block with just an empty statement the same as an empty block and avoid emitting the new block.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in commit 957cf79. The implementation now considers blocks containing only empty statements as effectively empty and avoids generating the extra wrapping block in those cases.

Copy link
Contributor Author

Copilot AI commented Jun 25, 2025

Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details.

Warning

Firewall rules blocked me from connecting to one or more addresses

I tried to connect to the following addresses, but was blocked by firewall rules:

  • plugins.dprint.dev
    • Triggering command: /home/REDACTED/work/TypeScript/TypeScript/node_modules/dprint/dprint fmt (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

@Copilot Copilot AI requested a review from weswigham June 25, 2025 21:10
Copilot finished work on behalf of weswigham June 25, 2025 21:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
For Milestone Bug PRs that fix a bug with a specific milestone
Projects
None yet
Development

Successfully merging this pull request may close these issues.

using transform throws when a for body binding shadows the for head binding
4 participants