@@ -7,6 +7,7 @@ use rustc_middle::mir::*;
77use rustc_middle:: thir:: { self , * } ;
88use rustc_middle:: ty:: TypeVisitableExt ;
99use rustc_middle:: ty:: { self , Ty } ;
10+ use rustc_span:: Span ;
1011
1112impl < ' a , ' tcx > Builder < ' a , ' tcx > {
1213 pub ( crate ) fn field_match_pairs < ' pat > (
@@ -268,19 +269,99 @@ impl<'pat, 'tcx> MatchPair<'pat, 'tcx> {
268269
269270pub ( super ) struct FakeBorrowCollector < ' a , ' b , ' tcx > {
270271 cx : & ' a mut Builder < ' b , ' tcx > ,
272+ /// Base of the scrutinee place. Used to distinguish bindings inside the scrutinee place from
273+ /// bindings inside deref patterns.
274+ scrutinee_base : PlaceBase ,
271275 fake_borrows : FxIndexSet < Place < ' tcx > > ,
272276}
273277
278+ /// Determine the set of places that have to be stable across match guards.
279+ ///
280+ /// Returns a list of places that need a fake borrow along with a local to store it.
281+ ///
282+ /// Match exhaustiveness checking is not able to handle the case where the place being matched on is
283+ /// mutated in the guards. We add "fake borrows" to the guards that prevent any mutation of the
284+ /// place being matched. There are a some subtleties:
285+ ///
286+ /// 1. Borrowing `*x` doesn't prevent assigning to `x`. If `x` is a shared reference, the borrow
287+ /// isn't even tracked. As such we have to add fake borrows of any prefixes of a place.
288+ /// 2. We don't want `match x { (Some(_), _) => (), .. }` to conflict with mutable borrows of `x.1`, so we
289+ /// only add fake borrows for places which are bound or tested by the match.
290+ /// 3. We don't want `match x { Some(_) => (), .. }` to conflict with mutable borrows of `(x as
291+ /// Some).0`, so the borrows are a special shallow borrow that only affects the place and not its
292+ /// projections.
293+ /// ```rust
294+ /// let mut x = (Some(0), true);
295+ /// match x {
296+ /// (Some(_), false) => {}
297+ /// _ if { if let Some(ref mut y) = x.0 { *y += 1 }; true } => {}
298+ /// _ => {}
299+ /// }
300+ /// ```
301+ /// 4. The fake borrows may be of places in inactive variants, e.g. here we need to fake borrow `x`
302+ /// and `(x as Some).0`, but when we reach the guard `x` may not be `Some`.
303+ /// ```rust
304+ /// let mut x = (Some(Some(0)), true);
305+ /// match x {
306+ /// (Some(Some(_)), false) => {}
307+ /// _ if { if let Some(Some(ref mut y)) = x.0 { *y += 1 }; true } => {}
308+ /// _ => {}
309+ /// }
310+ /// ```
311+ /// So it would be UB to generate code for the fake borrows. They therefore have to be removed by
312+ /// a MIR pass run after borrow checking.
313+ pub ( super ) fn collect_fake_borrows < ' tcx > (
314+ cx : & mut Builder < ' _ , ' tcx > ,
315+ candidates : & [ & mut Candidate < ' _ , ' tcx > ] ,
316+ temp_span : Span ,
317+ scrutinee_base : PlaceBase ,
318+ ) -> Vec < ( Place < ' tcx > , Local ) > {
319+ let mut collector =
320+ FakeBorrowCollector { cx, scrutinee_base, fake_borrows : FxIndexSet :: default ( ) } ;
321+ for candidate in candidates. iter ( ) {
322+ collector. visit_candidate ( candidate) ;
323+ }
324+ let fake_borrows = collector. fake_borrows ;
325+ debug ! ( "add_fake_borrows fake_borrows = {:?}" , fake_borrows) ;
326+ let tcx = cx. tcx ;
327+ fake_borrows
328+ . iter ( )
329+ . copied ( )
330+ . map ( |matched_place| {
331+ let fake_borrow_deref_ty = matched_place. ty ( & cx. local_decls , tcx) . ty ;
332+ let fake_borrow_ty =
333+ Ty :: new_imm_ref ( tcx, tcx. lifetimes . re_erased , fake_borrow_deref_ty) ;
334+ let mut fake_borrow_temp = LocalDecl :: new ( fake_borrow_ty, temp_span) ;
335+ fake_borrow_temp. local_info = ClearCrossCrate :: Set ( Box :: new ( LocalInfo :: FakeBorrow ) ) ;
336+ let fake_borrow_temp = cx. local_decls . push ( fake_borrow_temp) ;
337+ ( matched_place, fake_borrow_temp)
338+ } )
339+ . collect ( )
340+ }
341+
274342impl < ' a , ' b , ' tcx > FakeBorrowCollector < ' a , ' b , ' tcx > {
275- pub ( super ) fn collect_fake_borrows (
276- cx : & ' a mut Builder < ' b , ' tcx > ,
277- candidates : & [ & mut Candidate < ' _ , ' tcx > ] ,
278- ) -> FxIndexSet < Place < ' tcx > > {
279- let mut collector = Self { cx, fake_borrows : FxIndexSet :: default ( ) } ;
280- for candidate in candidates. iter ( ) {
281- collector. visit_candidate ( candidate) ;
343+ // Fake borrow this place and its dereference prefixes.
344+ fn fake_borrow ( & mut self , place : Place < ' tcx > ) {
345+ let new = self . fake_borrows . insert ( place) ;
346+ if !new {
347+ return ;
348+ }
349+ // Also fake borrow the prefixes of any fake borrow.
350+ self . fake_borrow_deref_prefixes ( place) ;
351+ }
352+
353+ // Fake borrow the prefixes of this place that are dereferences.
354+ fn fake_borrow_deref_prefixes ( & mut self , place : Place < ' tcx > ) {
355+ for ( place_ref, elem) in place. as_ref ( ) . iter_projections ( ) . rev ( ) {
356+ if let ProjectionElem :: Deref = elem {
357+ // Insert a shallow borrow after a deref. For other projections the borrow of
358+ // `place_ref` will conflict with any mutation of `place.base`.
359+ let new = self . fake_borrows . insert ( place_ref. to_place ( self . cx . tcx ) ) ;
360+ if !new {
361+ return ;
362+ }
363+ }
282364 }
283- collector. fake_borrows
284365 }
285366
286367 fn visit_candidate ( & mut self , candidate : & Candidate < ' _ , ' tcx > ) {
@@ -306,10 +387,28 @@ impl<'a, 'b, 'tcx> FakeBorrowCollector<'a, 'b, 'tcx> {
306387 for flat_pat in pats. iter ( ) {
307388 self . visit_flat_pat ( flat_pat)
308389 }
390+ } else if matches ! ( match_pair. test_case, TestCase :: Deref { .. } ) {
391+ // The subpairs of a deref pattern are all places relative to the deref temporary, so we
392+ // don't fake borrow them. Problem is, if we only shallowly fake-borrowed
393+ // `match_pair.place`, this would allow:
394+ // ```
395+ // let mut b = Box::new(false);
396+ // match b {
397+ // deref!(true) => {} // not reached because `*b == false`
398+ // _ if { *b = true; false } => {} // not reached because the guard is `false`
399+ // deref!(false) => {} // not reached because the guard changed it
400+ // // UB because we reached the unreachable.
401+ // }
402+ // ```
403+ // FIXME(deref_patterns): Hence we fake borrow using a non-shallow borrow.
404+ if let Some ( place) = match_pair. place {
405+ // FIXME(deref_patterns): use a non-shallow borrow.
406+ self . fake_borrow ( place) ;
407+ }
309408 } else {
310409 // Insert a Shallow borrow of any place that is switched on.
311410 if let Some ( place) = match_pair. place {
312- self . fake_borrows . insert ( place) ;
411+ self . fake_borrow ( place) ;
313412 }
314413
315414 for subpair in & match_pair. subpairs {
@@ -319,6 +418,14 @@ impl<'a, 'b, 'tcx> FakeBorrowCollector<'a, 'b, 'tcx> {
319418 }
320419
321420 fn visit_binding ( & mut self , Binding { source, .. } : & Binding < ' tcx > ) {
421+ if let PlaceBase :: Local ( l) = self . scrutinee_base
422+ && l != source. local
423+ {
424+ // The base of this place is a temporary created for deref patterns. We don't emit fake
425+ // borrows for these as they are not initialized in all branches.
426+ return ;
427+ }
428+
322429 // Insert a borrows of prefixes of places that are bound and are
323430 // behind a dereference projection.
324431 //
@@ -335,13 +442,13 @@ impl<'a, 'b, 'tcx> FakeBorrowCollector<'a, 'b, 'tcx> {
335442 // y if { y == 1 && (x = &2) == () } => y,
336443 // _ => 3,
337444 // }
338- if let Some ( i ) = source . projection . iter ( ) . rposition ( |elem| elem == ProjectionElem :: Deref ) {
339- let proj_base = & source . projection [ ..i ] ;
340- self . fake_borrows . insert ( Place {
341- local : source . local ,
342- projection : self . cx . tcx . mk_place_elems ( proj_base ) ,
343- } ) ;
344- }
445+ //
446+ // We don't just fake borrow the whole place because this is allowed:
447+ // match u {
448+ // _ if { u = true; false } => () ,
449+ // x => ( ),
450+ // }
451+ self . fake_borrow_deref_prefixes ( * source ) ;
345452 }
346453}
347454
0 commit comments