Bisected from the #5917 behavioral matrix (test_edge_closures, and by shared root very likely test_edge_interfaces): the classic per-iteration loop capture returns the FIRST iteration's value from every closure.
Repro (12 lines)
const fns: Array<() => number> = [];
for (let i = 0; i < 5; i++) {
const captured = i;
fns.push(() => captured);
}
console.log(fns[0](), fns[1](), fns[2](), fns[3](), fns[4]());
node: 0 1 2 3 4 — perry (main, v0.5.1238): 0 0 0 0 0.
Bisect
git bisect run over v0.5.1150..main with a perry-dev build + the repro (PERRY_NO_AUTO_OPTIMIZE=1): first bad = ce0117a — feat(codegen+runtime): representation-aware type lowering + native-ABI material evidence gate (#5466). Eight good/bad datapoints on each side, deterministic output both ways.
PERRY_TYPED_FEEDBACK=0 does NOT change the result, so it's the static representation lowering rather than feedback-driven specialization.
Mechanism hypothesis (unverified): captured is const-initialized from the typed loop var i (i32 representation). The per-iteration capture materialization appears to snapshot the representation-lowered slot in a way that reuses one cell or re-reads iteration 0's value — all five closures return 0 (not the last value 4, so it is not a shared-mutable-binding bug; it looks like capture-at-creation reading a stale/constant lowered slot).
Also from the same matrix triage (#5917): test_generic_class regressed at the same window with TypeError: (string).getValue is not a function — previously attributed to #5466 by symptom; this bisect strengthens that attribution. test_edge_interfaces (run-exit 1, missing 42/hello/world/{a:1,b:2} block) should be re-checked once this is fixed — likely the same root.
Bisected from the #5917 behavioral matrix (
test_edge_closures, and by shared root very likelytest_edge_interfaces): the classic per-iteration loop capture returns the FIRST iteration's value from every closure.Repro (12 lines)
node:
0 1 2 3 4— perry (main, v0.5.1238):0 0 0 0 0.Bisect
git bisect runover v0.5.1150..main with a perry-dev build + the repro (PERRY_NO_AUTO_OPTIMIZE=1): first bad = ce0117a — feat(codegen+runtime): representation-aware type lowering + native-ABI material evidence gate (#5466). Eight good/bad datapoints on each side, deterministic output both ways.PERRY_TYPED_FEEDBACK=0does NOT change the result, so it's the static representation lowering rather than feedback-driven specialization.Mechanism hypothesis (unverified):
capturedisconst-initialized from the typed loop vari(i32 representation). The per-iteration capture materialization appears to snapshot the representation-lowered slot in a way that reuses one cell or re-reads iteration 0's value — all five closures return 0 (not the last value 4, so it is not a shared-mutable-binding bug; it looks like capture-at-creation reading a stale/constant lowered slot).Also from the same matrix triage (#5917):
test_generic_classregressed at the same window withTypeError: (string).getValue is not a function— previously attributed to #5466 by symptom; this bisect strengthens that attribution.test_edge_interfaces(run-exit 1, missing42/hello/world/{a:1,b:2}block) should be re-checked once this is fixed — likely the same root.