Skip to content

for-let closures should capture per-iteration binding#1713

Open
RealColdFry wants to merge 5 commits into
TypeScriptToLua:masterfrom
RealColdFry:fix/for-let-per-iteration-binding
Open

for-let closures should capture per-iteration binding#1713
RealColdFry wants to merge 5 commits into
TypeScriptToLua:masterfrom
RealColdFry:fix/for-let-per-iteration-binding

Conversation

@RealColdFry
Copy link
Copy Markdown
Contributor

@RealColdFry RealColdFry commented Apr 18, 2026

const fns: (() => number)[] = [];
for (let i = 0; i < 4; i++) {
  fns.push(() => i);
}
fns.map((f) => f()); // expected [0,1,2,3], got [4,4,4,4]

TSTL lowers for (let i) to one local i outside a while, so every closure shares that upvalue. ES spec gives each iteration a fresh binding.

Fix: when a let-declared loop var is captured in body/cond/incr, wrap the body in a per-iter do ... end with local i = i. Lua makes a fresh upvalue per block activation, so each iteration's closures get their own i. A sync slot carries body mutations back to the outer before the incrementor, matching spec semantics (closure at i=2 when body does if (i===2) i = 8 reads 8, not 9).

Sync is also injected before every continue-exit (goto, repeat-break, native), so continue still propagates mutations.

No-capture loops keep today's output.

Known gap (pre-existing, not regressed): closures placed in the loop's condition or incrementor still capture the outer binding, since those run outside the per-iter do. Out of scope here; tracked separately.

for (let i = 0; i < 3; i++, fns.push(() => i)) {}
// yields [3, 3, 3] on both master and this branch

Tests:

  • basic capture
  • sibling const
  • body reassignment
  • reassign + continue
  • destructuring assignment
  • synchronous IIFE write

@RealColdFry RealColdFry marked this pull request as ready for review April 22, 2026 02:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant