Track drop points by MIR Place, splitting partially-moved locals (fix #121, #122)#155
Draft
coord-e wants to merge 2 commits into
Draft
Track drop points by MIR Place, splitting partially-moved locals (fix #121, #122)#155coord-e wants to merge 2 commits into
coord-e wants to merge 2 commits into
Conversation
This was referenced Jun 28, 2026
915afc5 to
12b6c5f
Compare
Replace the Local-only drop-point representation with MIR `Place` so implicit drops are tracked at the right granularity. Drop points are stored as `HashSet<Place>` (a whole-local drop is just a place with an empty projection), and the analyzer drops them through a single `drop_places` helper.
A local with a partial field move (e.g. `move (_2.0)`) was still dropped wholesale, so dropping it walked into the moved-out sub-place and resolved the `&mut` prophecy it owns a second time. The two resolutions contradict, making the clause body unsatisfiable, after which any assertion -- including false ones -- "verifies" (#121, #122). `Moves::collect` gathers all non-reference `move`d operands in one body traversal: whole-local moves (keyed by location, where the local also dies) and, keyed by parent local, the partial field moves. A dying local is dropped whole, but its partial-move sub-places are passed to the drop as `except`: the drop walk skips those subtrees (they are resolved at the move destination) and resolves the still-owned siblings, so the fix is both sound and complete. `Env::dropping_assumption` gains the `except` set and `Path::same_place`. The comparison peels `Deref`, because the drop walk inserts a `Deref` for every `own`-box the type elaboration introduces (mut locals, and every tuple field) while the moved-out places come straight from MIR without them; moved-out places never contain a real deref, so peeling cannot conflate distinct places. Adds regression tests: the two original shapes (#121, #122), plus a partially-moved local with a still-owned sibling (completeness) and a soundness guard where the moved part is used and the sibling is reborrowed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NU1g7aHMxcSEZgeKProDr1
12b6c5f to
43aaebb
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Supersedes #154 and #124. Fixes #121 and #122.
Motivation
Placerather thanLocal(the Track drop points by MIR Place #154 generalization), and on that basis fix the Unsoundness: partially-moved locals are still implicitly dropped, resolving prophecies of moved-out&mutborrows #121/Unsound: aggregate dropped wholesale after a partial field-move double-resolves the field's &mut prophecy #122 double-resolution bug.move (_2.0)) was still dropped wholesale, so the drop walked into the moved-out sub-place and resolved the&mutprophecy it owns a second time. The two resolutions contradict (final = 1∧final = 2), making the clause body unsatisfiable, after which any assertion — including false ones — "verifies".Approach
Fix it in the drop-point production, keeping whole-local drops intact and dropping the still-owned siblings of a partially-moved local (sound and complete), without a hand-rolled type walk:
drop_point.rs—Moves::collectgathers all non-referencemoved operands in one body traversal: whole-local moves (keyed by location, where the local also dies) and, keyed by the parent local, the partial field moves.DropSet { drops, except }carries, per drop point, the places to drop plus the moved-out sub-places to skip. A dying local always goes indrops; its partial-move sub-places go inexcept.env.rs—dropping_assumptionthreads theexceptset and returns early on any subtree whose path matches an excepted place, so the drop resolves the still-owned siblings while skipping the moved-out ones (which are resolved at the move destination).Path::same_placeperforms the match and peelsDeref: the drop walk inserts aDereffor everyown-box the type elaboration introduces (mut/reborrowed locals, and every tuple field), whereas the moved-out places come straight from MIR without them. Moved-out places never contain a real deref (reference moves are excluded upstream), so peeling cannot conflate distinct owned places. This is what makes the skip fire for partially-moved locals that get box-elaborated (e.g. when a sibling is mutated through).Vec, dropping the manual dedup bookkeeping; the analyzer drops each point through a singledrop_placeshelper.Testing
New pass/fail regression tests:
partial_move_drop.rs— Unsoundness: partially-moved locals are still implicitly dropped, resolving prophecies of moved-out&mutborrows #121's partial-move-into-local (let b = s.0;).partial_move_field_call.rs— Unsound: aggregate dropped wholesale after a partial field-move double-resolves the field's &mut prophecy #122's partial-move-into-call (owned(&mut i64,)field passed to a function).partial_move_sibling.rs— a partially-moved local with a still-owned sibling: the sibling's prophecy must be resolved by the parent's drop (completeness), and — when the moved-out part is used and the sibling is reborrowed — the moved-out sub-place must not be resolved twice (soundness; this false-assertion variant was vacuously accepted before theDeref-peeling fix).Each false-assertion variant reports
Unsat(rejected); the true companions verify. Full UI suite: 294 passed, 0 failed (pcsat via Docker, z3 4.15.4), plus doc-tests.cargo fmt --all -- --check,cargo clippy -- -D warnings, andgit diff --checkare clean.🤖 Generated with Claude Code