Fixes a collapsible_match false positive where the suggested if-into-guard collapse produces code that fails the borrow checker (E0502).#16912
Conversation
|
r? @dswij rustbot has assigned @dswij. Use Why was this reviewer chosen?The reviewer was selected based on:
|
|
this seems way too complex for a fix, and partial at that, the analysis currently has gaps around multi-level reborrow chains (e.g., |
| fn places_alias(a: &Place<'_>, b: &Place<'_>) -> bool { | ||
| match (a.base, b.base) { | ||
| (PlaceBase::Upvar(_), _) | (_, PlaceBase::Upvar(_)) => true, |
There was a problem hiding this comment.
Closures appear frequently near match arms. Treating any Upvar as aliasing will make the lint silently stop firing in closure heavy code.
When the inner
if cond { ... }lives in an arm body whose match scrutinee mutablyborrows some place P, moving the condition into a guard makes that guard run while
the scrutinee borrow is still live. If
condreads from P, the rewrite is unsound.NLL hides this in the original because the scrutinee borrow can end before the arm
body runs, but a guard executes during pattern matching, not after.
Closes #16903
What the lint catches
After the fix, the lint stays silent on this shape. It still fires when the inner
condition reads only places disjoint from the scrutinee's borrow.
How the gate works
A new helper,
inner_cond_conflicts_with_scrutinee_borrows, runs in front of theexisting Path-B suggestion. It returns
truewhen collapsing would introduce aborrow conflict, in which case the suggestion is suppressed.
ExprUseVisitoron the match scrutinee with a delegate that records everyplace taken under a mutable borrow (or written to). This catches direct cases
like
match v.iter_mut().next() { ... }and auto-deref ofmatch self.x { ... }when
self.x: &mut T.&mut Treferenced in the scrutinee, walk up to itsletinitializer once and merge its mutable borrows into the same set. Thiscatches the let-else pattern from the reproducer where the binding carries the
borrow into the scrutinee transparently.
ExprUseVisitoron the inner condition with a delegate that records everyplace read or borrowed (any kind).
projection list is a prefix of the other's.
Upvarbases are treatedconservatively as aliasing.
Conservative conditions (no false positives)
The lint is now skipped when:
one-level
letbinding of&mut T), AND the innerifcondition reads orborrows P or a sub-place of P.
Existing gates still apply on top of this:
_or bindingpattern (added in
collapsible_matchsuggestion change meaning #16875 / collapsible_match ignore "else" when fixing an if statement #16910).(
pat_bindings_moved_or_mutated).Out of scope
&mut Tbindings are not traced back through the callboundary; their borrow origin is unknown so we conservatively don't expand
them. If a real-world case surfaces this can be tightened in a follow-up.
letchains beyond one level (let a = &mut x; let b = a; match b { ... })are not followed. The dominant pattern in the wild is one-level let-else, which
the v1 covers.
Tests
Negative cases (must NOT lint) in
tests/ui/collapsible_match.rs:issue16903::handle-- the cackle reproducer verbatim, withlet-elsebinding&mut Modefromself.modes.last_mut()and a guard that would readself.modes.v.iter_mut().next()scrutinee with an innerif v.is_empty().Positive control (must STILL lint) in
tests/ui/collapsible_match_fixable.rs:issue16903_no_conflict-- scrutinee mutably borrowsv, but the innerifreads only an unrelated
local_flag. The lint fires; the resulting.fixedoutput compiles.
changelog: [
collapsible_match]: no longer suggests an if-into-guard collapsewhen the inner condition would conflict with a mutable borrow held by the match
scrutinee.