Skip to content

Weaken closure parameter for FnMut closures called via FnOnce#120

Draft
coord-e wants to merge 3 commits into
mainfrom
claude/issue-46-4zfjsz
Draft

Weaken closure parameter for FnMut closures called via FnOnce#120
coord-e wants to merge 3 commits into
mainfrom
claude/issue-46-4zfjsz

Conversation

@coord-e

@coord-e coord-e commented Jun 12, 2026

Copy link
Copy Markdown
Owner

Fixes #46 (the remaining reopened case: calling an FnMut closure via FnOnce::call_once).

Problem

Calling an FnMut closure through FnOnce::call_once resolves to the closure function taking &mut {closure}, while the caller's MIR passes the closure environment by value. This hit the unimplemented!() "case 3" in RustCallVisitor.

The draft patch in the issue comment fixed the panic but lost completeness: after rewriting the call to pass &mut {closure}, the environment local stays live in the caller, yet the drop-point analysis (computed on the original MIR, where the local was moved into the call) never schedules a drop for it. The prophecies of its captured mutable borrows were therefore never resolved (final = current), leaving the captures' final values unconstrained.

Fix

  • RustCallVisitor case 3: insert a mutable borrow of the closure environment and pass it as the argument. Since FnOnce::call_once consumes the closure, schedule both the borrow and the environment local to be dropped right after the call, which resolves the prophecies of the captured mutable borrows and restores completeness.
  • DropPoints: the scheduled drops are stored next to the liveness-derived sets, behind DropPoints::insert_after_terminator and an Analyzer::drop_after_terminator helper. They are kept as a Vec because the locals are created during elaboration and lie outside the bitset domains sized to the original body's locals.
  • reassign_local_mutabilities: mark the closure argument of a FnOnce::call_once call as mutable when the closure's kind is FnMut (implementing the TODO from the issue comment by checking the closure kind instead of the resolved signature), so the environment is boxed in the refinement environment and can be mutably borrowed.

Tests

  • New tests/ui/{pass,fail}/closure_param_weaken_3.rs with the example from the issue: assert!(result == 3 && x == 2) is now proven (the issue's draft patch could not), and the incorrect variant (x == 1) is rejected with Unsat.
  • Full cargo test passes locally (230/230 UI tests, including the pcsat/COAR-based ones), as do cargo fmt --check and cargo clippy.

https://claude.ai/code/session_012451PZJH2QqdqE5Zyxna5y

claude and others added 3 commits June 12, 2026 15:20
Calling an FnMut closure through FnOnce::call_once resolves to the
closure function taking &mut {closure}, while the caller passes the
closure environment by value, which hit the unimplemented case 3 in
RustCallVisitor.

Implement case 3 by mutably borrowing the closure argument, and restore
completeness by dropping the borrow and the environment right after the
call: FnOnce::call_once consumes the closure, so dropping the
environment at the call site resolves the prophecies of its captured
mutable borrows. Without these drops the captured borrows' final values
stay unconstrained and assertions about the mutated captures cannot be
proven.

To make the environment local borrowable, reassign_local_mutabilities
now also marks the closure argument of such calls as mutable, so it is
boxed in the refinement environment.

https://claude.ai/code/session_012451PZJH2QqdqE5Zyxna5y
Instead of a separate Vec field on basic_block::Analyzer, store the
extra after-terminator drops in DropPoints next to the liveness-derived
sets, behind DropPoints::insert_after_terminator and an Analyzer helper.
They are kept as a Vec because the locals are created during elaboration
and lie outside the bitset domains sized to the original body's locals.

https://claude.ai/code/session_012451PZJH2QqdqE5Zyxna5y
style fix
@coord-e coord-e force-pushed the claude/issue-46-4zfjsz branch from 421ec68 to 5a96655 Compare June 13, 2026 02:52
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.

Weaken the closure parameters in calls using rust-call

2 participants