Skip to content

Fix initialization of nested for loop head declarations - #18088

Merged
nicolo-ribaudo merged 2 commits into
babel:mainfrom
journey-ad:fix/block-scoping-loop-head-init
Jul 15, 2026
Merged

nicolo-ribaudo merged 2 commits into
babel:mainfrom
journey-ad:fix/block-scoping-loop-head-init

Conversation

@journey-ad

Copy link
Copy Markdown
Contributor
Q                       A
Fixed Issues? Fixes #18087
Patch: Bug Fix? Yes
Major: Breaking Change? No
Minor: New Feature? No
Tests Added + Pass? Yes
Documentation PR Link N/A
Any Dependency Changes? No
License MIT

This PR fixes an issue in @babel/plugin-transform-block-scoping where an uninitialized let declaration in a nested for statement initializer could retain its value across outer loop iterations after being transformed to var.

For example:

let count = 0;

for (let outer = 0; outer < 2; outer++) {
  for (let done; !done; done = true) {
    count++;
  }
}

expect(count).toBe(2);

Before this change, the inner done declaration was transformed without an initializer:

for (var done; !done; done = true) {}

Because var is function-scoped, done could keep the value from the previous outer iteration. This PR initializes nested for statement initializers with void 0 when needed:

for (var done = void 0; !done; done = true) {}

The existing loop-body declaration initialization behavior is preserved, while top-level loop-head declarations are not unnecessarily initialized.

Added a regression fixture for #18087 covering both output and runtime behavior.

@babel-bot

babel-bot commented Jun 20, 2026 •

Copy link
Copy Markdown
Collaborator

Build successful! You can test your changes in the REPL here: https://babeljs.io/repl/build/61819

@pkg-pr-new

pkg-pr-new Bot commented Jun 20, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

commit: 10bb892

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes a semantic bug in @babel/plugin-transform-block-scoping where let declarations without initializers inside a nested for-statement initializer could retain their previous value after being lowered to var, changing runtime behavior across outer-loop iterations.

Changes:

  • Initialize uninitialized block-scoped declarators in nested ForStatement.init positions to void 0 when lowering to var.
  • Preserve existing behavior for loop-body declarations, while avoiding unnecessary initialization for top-level loop-head declarations.
  • Add regression fixture covering both expected output and runtime behavior for #18087.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.

Show a summary per file
File Description
packages/babel-plugin-transform-block-scoping/src/index.ts Extends loop-related initialization logic to also cover nested for initializer declarations.
packages/babel-plugin-transform-block-scoping/src/loop.ts Adds helper to detect VariableDeclaration nodes in ForStatement.init.
packages/babel-plugin-transform-block-scoping/test/fixtures/general/issue-18087/input.js Adds a minimal reproduction input for the nested for initializer case.
packages/babel-plugin-transform-block-scoping/test/fixtures/general/issue-18087/output.js Asserts the transformed output includes = void 0 in the nested for initializer.
packages/babel-plugin-transform-block-scoping/test/fixtures/general/issue-18087/exec.js Adds a runtime regression check ensuring the loop runs the expected number of times.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

for (let done; !done; done = true) {
count++;
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could you add a new test case where a for statement is indirectly nested within another for statement? For example,

for (let outer = 0; outer < 2; outer++) {
 do {
    for (let done; !done; done = true) {
      count++;
    }
  } while (false)
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added the indirect nested case.

@JLHwung JLHwung left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks.

@nicolo-ribaudo nicolo-ribaudo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks!

@nicolo-ribaudo nicolo-ribaudo changed the title Fix nested for loop declaration initialization Fix initialization of nested for...of loop declaration Jul 15, 2026
@nicolo-ribaudo nicolo-ribaudo added the PR: Bug Fix 🐛 A type of pull request used for our changelog categories label Jul 15, 2026
@nicolo-ribaudo nicolo-ribaudo changed the title Fix initialization of nested for...of loop declaration Fix initialization of nested for loop head declarations Jul 15, 2026
@nicolo-ribaudo
nicolo-ribaudo merged commit 7c7be05 into babel:main Jul 15, 2026
57 checks passed
@journey-ad
journey-ad deleted the fix/block-scoping-loop-head-init branch August 10, 2026 09:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

PR: Bug Fix 🐛 A type of pull request used for our changelog categories

Projects

None yet

Development

Successfully merging this pull request may close these issues.

transform-block-scoping does not reset uninitialized let in nested for

5 participants

Sponsor
SponsoredKunjungi sekarang
Promo