Skip to content

Bind the structure of the value a mutable borrow prophesies - #213

Draft
coord-e wants to merge 1 commit into
mainfrom
claude/admiring-cori-dpvmfe
Draft

Bind the structure of the value a mutable borrow prophesies#213
coord-e wants to merge 1 commit into
mainfrom
claude/admiring-cori-dpvmfe

Conversation

@coord-e

@coord-e coord-e commented Aug 13, 2026

Copy link
Copy Markdown
Owner

Fixes #176.

Cause

Env::borrow_var rebinds the borrowed variable to the borrow's prophecy variable, which stands for the value the place takes once the borrow ends. That variable comes from push_temp_var, so it is bound as a value (TempVarBinding::Type), not with the FlowBinding that locate_place and borrow_var walk to reach through a reference, a box, a tuple, or an enum. A place whose value has structure therefore lost that structure as soon as it was borrowed.

Writing to a mut local is elaborated into a borrow of the local's slot (ReborrowVisitor::visit_assign), which is why the issue's straight-line reassignment hit exactly that: r = &mut b left r's content standing for the prophecy, and elaborating *r = 20 into a reborrow of *r reached borrow_var with no binding — borrowing unbound var. The same reassignment of a tuple local panics with deref unbound var when a field is projected afterwards. Reading through the reassigned local was fine, since reads go through place_type, which needs no flow binding; and the cross-basic-block form was fine too, since a block entry re-binds its locals from the block type.

Fix

bind_prophesied_value binds a fresh variable refined to equal the prophecy through bind_impl, so the prophesied value carries the structure of the value it replaces. A value that had no flow binding to begin with keeps standing for the prophecy variable directly, so the constraints generated for a borrow of a scalar are unchanged.

Tests

tests/ui/{pass,fail}/reassign_mut_ref.rs write through a &mut local both before and after reassigning it, and check both referents afterwards; the failing side breaks the assertion on the reassigned referent.

Also checked by hand, all verifying as expected after the fix and all but the last panicking before it:

Program Before After
r = &mut b; *r = 20; assert!(b == 20) borrowing unbound var passes
r = &mut b; *r = 20; assert!(b == 21) borrowing unbound var Unsat
r = &mut b; *r = 20; assert!(a == 20) borrowing unbound var Unsat
r = &mut a; *r = 5; assert!(a == 5) (same referent) borrowing unbound var passes
t = (3, 4); t.0 = 5; assert!(t.0 == 5 && t.1 == 4) deref unbound var passes
x = Box::new(2); *x = 3; assert!(*x == 3) passes passes

Generated by Claude Code

A mutable borrow rebinds the borrowed variable to the borrow's prophecy variable,
which stands for the value the place takes once the borrow ends. That variable is
opaque: it is bound as a value, not with the `FlowBinding` that `locate_place` and
`borrow_var` walk to reach through a reference, a box, a tuple, or an enum. A place
whose value has structure therefore lost that structure as soon as it was borrowed.

Writing to a `mut` local is elaborated into a borrow of the local's slot, so a
straight-line reassignment of a `&mut` local followed by a write through it hit
exactly that: the reassignment left the local's content standing for the prophecy,
and elaborating the write into a reborrow of `*r` panicked with `borrowing unbound
var`. The same reassignment of a tuple local panicked with `deref unbound var` when
projecting a field afterwards. Reading through the reassigned local was fine, since
reads go through `place_type`, which needs no flow binding.

Bind a fresh variable equal to the prophecy through `bind_impl` instead, so that the
prophesied value carries the structure of the value it replaces. A value bound
without a flow binding keeps standing for the prophecy variable directly, leaving
the constraints generated for a borrow of a scalar unchanged.

Fixes #176

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gbkD8SESxdRSj7taHpFJs
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.

Panic: "borrowing unbound var" when writing through a reassigned &mut local

2 participants