Repository navigation
fix(ts): keep a closure's write through a captured &mut - #185
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
return_assigned folded `x = v; return x` into `return v` for any name, but in a closure body `x` may be a capture: the caller's variable by `&mut`, or a `move` closure's own copy. It now folds only the body's own locals. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed commit: c04d447
No actionable bugs found. Restricting return-assignment folding to this body's own Slot::Var names is a sound, simpler boundary than enumerating capture kinds. It preserves the existing cleanup-read exclusion; closure capture snapshots and borrowed places remain outside the allowlist, and nested closures are lowered independently.
The differential fixture covers repeated calls, move captures, conditional writes, generic FnOnce/FnMut calls, fields, &mut parameters and nested closures across integer widths. I inspected the lowering change, surrounding capture/name allocation and return-folding logic, fixture registration and generated output. Static review only: I did not run the test suite or independently verify the reported test results.
…rite Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Summary: (written by Opus 5.5) |
Problem
A closure that writes a variable it captured and then returns it lost the write.
let mut n = 0; let mut f = || { n += 1; n }; f(); nread 0 in the translation. Found writing #147's stock fixture.Approach
The root cause is
return_assigned, the pass that foldsx = v; return x;intoreturn v;. It assumedxwas a local of the body it ran on. In a closure body,xcan be a capture: either the caller's variable, captured by&mut, or the closure's own copy of amovecapture, which its next call reads. Either way the write was dropped. With the write gone, the caller'sletalso becameconst.The pass now folds only the body's own locals, its
Slot::Varnames, and still skips any name a cleanup reads. Fixing it at that check covers every capture path: by reference ormove,FnMutorFnOnce, through a generic, and nested closures.Tests
captures::capturescase gave 530 differences against native Rust without the fix, and none with it.FnOncethat moves what it owns,once/twicegenerics, amoveFnMutcalled twice, a field captured alone, a&mutparameter, and a closure inside another.just checkandjust testpass.Impact
Alternatives
Follow-ups
#147report is a separate root cause and is not fixed here.for _ in r.by_ref() {}on aRangeInclusive, followed by usingr, still failstsc:const iter: rt.RangeInclusive<rt.usize> = rt.iterBorrow(r)givesType 'Iter<usize>' is missing ... lo, hi, exhausted, int. The cause is howIntoIteratorfor&mut Iis typed, not closure writes. It could be a quest under ts-range-fold or one of its own.(written by Opus 5.5)
🤖 Generated with Claude Code