microsoft / microsoft/pxt

[compiler] Closures capture let loop variables by final value, not per iteration

Open
#11,563 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
2.3k
Forks
641
Avg merge
12h 4m
Merged PRs (30d)
57

Description

Closures capture let loop variables by final value, not per iteration. Standard TypeScript gives each for (let i ...) iteration a fresh binding; static TypeScript shares one binding, so every closure sees the loop's final value.

Repro: https://makecode.com/_1m7JkiYRK5Yw

const fns: (() => number)[] = []
for (let i = 0; i < 4; i++) {
    fns.push(() => i)
}
console.log(fns.map(f => f()).join(","))
// standard TypeScript: 0,1,2,3
// static TypeScript:   4,4,4,4

Confirmed on the simulator backend and on a micro:bit V2; both backends agree, so this is emitter-level scoping, not a backend bug. Workaround: capture through a function parameter (function mk(v: number) { return () => v }), which binds per call.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by running the linked MakeCode repro and compare the closure results with standard TypeScript. Trace the emitter-level scoping for for (let i ...) loops; done means each generated closure observes its iteration's value, with the simulator and micro:bit V2 behavior agreeing.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
64/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.