about summary refs log tree commit diff
path: root/compiler/rustc_mir_build/src
diff options
context:
space:
mode:
authorMatthew Jasper <mjjasper1@gmail.com>2021-09-01 22:52:17 +0100
committerMatthew Jasper <mjjasper1@gmail.com>2021-09-01 23:47:41 +0100
commitff8c0ef0e47639b32fdba062851a1367a9cc9ae2 (patch)
tree65030b964b24d8c0ccaf02bd00f475bb09dfd179 /compiler/rustc_mir_build/src
parent50171c310cd15e1b2d3723766ce64e2e4d6696fc (diff)
downloadrust-ff8c0ef0e47639b32fdba062851a1367a9cc9ae2.tar.gz
rust-ff8c0ef0e47639b32fdba062851a1367a9cc9ae2.zip
Fix drop handling for `if let` expressions
MIR lowering for `if let` expressions is now more complicated now that
`if let` exists in HIR. This PR adds a scope for the variables bound in
an `if let` expression and then uses an approach similar to how we
handle loops to ensure that we reliably drop the correct variables.
Diffstat (limited to 'compiler/rustc_mir_build/src')
-rw-r--r--compiler/rustc_mir_build/src/build/expr/into.rs40
-rw-r--r--compiler/rustc_mir_build/src/build/matches/mod.rs97
-rw-r--r--compiler/rustc_mir_build/src/build/scope.rs150
-rw-r--r--compiler/rustc_mir_build/src/thir/cx/expr.rs4
-rw-r--r--compiler/rustc_mir_build/src/thir/visit.rs2
5 files changed, 183 insertions, 110 deletions
diff --git a/compiler/rustc_mir_build/src/build/expr/into.rs b/compiler/rustc_mir_build/src/build/expr/into.rs
index 21c5a64f823..1a7bb602392 100644
--- a/compiler/rustc_mir_build/src/build/expr/into.rs
+++ b/compiler/rustc_mir_build/src/build/expr/into.rs
@@ -52,11 +52,33 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
             ExprKind::Match { scrutinee, ref arms } => {
                 this.match_expr(destination, expr_span, block, &this.thir[scrutinee], arms)
             }
-            ExprKind::If { cond, then, else_opt } => {
-                let local_scope = this.local_scope();
-                let (mut then_blk, mut else_blk) =
-                    this.then_else_blocks(block, &this.thir[cond], local_scope, source_info);
-                unpack!(then_blk = this.expr_into_dest(destination, then_blk, &this.thir[then]));
+            ExprKind::If { cond, then, else_opt, if_then_scope } => {
+                let then_blk;
+                let then_expr = &this.thir[then];
+                let then_source_info = this.source_info(then_expr.span);
+                let condition_scope = this.local_scope();
+
+                let mut else_blk = unpack!(
+                    then_blk = this.in_scope(
+                        (if_then_scope, then_source_info),
+                        LintLevel::Inherited,
+                        |this| {
+                            let (then_block, else_block) =
+                                this.in_if_then_scope(condition_scope, |this| {
+                                    let then_blk = unpack!(this.then_else_break(
+                                        block,
+                                        &this.thir[cond],
+                                        condition_scope,
+                                        condition_scope,
+                                        then_expr.span,
+                                    ));
+                                    this.expr_into_dest(destination, then_blk, then_expr)
+                                });
+                            then_block.and(else_block)
+                        },
+                    )
+                );
+
                 else_blk = if let Some(else_opt) = else_opt {
                     unpack!(this.expr_into_dest(destination, else_blk, &this.thir[else_opt]))
                 } else {
@@ -81,9 +103,11 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
 
                 join_block.unit()
             }
-            ExprKind::Let { ref pat, expr } => {
-                let (true_block, false_block) =
-                    this.lower_let(block, &this.thir[expr], pat, expr_span);
+            ExprKind::Let { expr, ref pat } => {
+                let scope = this.local_scope();
+                let (true_block, false_block) = this.in_if_then_scope(scope, |this| {
+                    this.lower_let_else(block, &this.thir[expr], pat, scope, expr_span)
+                });
 
                 let join_block = this.cfg.start_new_block();
 
diff --git a/compiler/rustc_mir_build/src/build/matches/mod.rs b/compiler/rustc_mir_build/src/build/matches/mod.rs
index 7c4bdf1066a..d13c4f5a5a0 100644
--- a/compiler/rustc_mir_build/src/build/matches/mod.rs
+++ b/compiler/rustc_mir_build/src/build/matches/mod.rs
@@ -35,42 +35,49 @@ use std::convert::TryFrom;
 use std::mem;
 
 impl<'a, 'tcx> Builder<'a, 'tcx> {
-    pub(crate) fn then_else_blocks(
+    pub(crate) fn then_else_break(
         &mut self,
         mut block: BasicBlock,
         expr: &Expr<'tcx>,
-        scope: region::Scope,
-        source_info: SourceInfo,
-    ) -> (BasicBlock, BasicBlock) {
+        temp_scope: region::Scope,
+        break_scope: region::Scope,
+        variable_scope_span: Span,
+    ) -> BlockAnd<()> {
         let this = self;
         let expr_span = expr.span;
 
         match expr.kind {
             ExprKind::Scope { region_scope, lint_level, value } => {
-                let region_scope = (region_scope, source_info);
-                let then_block;
-                let else_block = unpack!(
-                    then_block = this.in_scope(region_scope, lint_level, |this| {
-                        let (then_block, else_block) =
-                            this.then_else_blocks(block, &this.thir[value], scope, source_info);
-                        then_block.and(else_block)
-                    })
-                );
-                (then_block, else_block)
+                let region_scope = (region_scope, this.source_info(expr_span));
+                this.in_scope(region_scope, lint_level, |this| {
+                    this.then_else_break(
+                        block,
+                        &this.thir[value],
+                        temp_scope,
+                        break_scope,
+                        variable_scope_span,
+                    )
+                })
             }
             ExprKind::Let { expr, ref pat } => {
-                // FIXME: Use correct span.
-                this.lower_let(block, &this.thir[expr], pat, expr_span)
+                this.lower_let_else(block, &this.thir[expr], pat, break_scope, variable_scope_span)
             }
             _ => {
+                // TODO `as_temp`?
                 let mutability = Mutability::Mut;
-                let place = unpack!(block = this.as_temp(block, Some(scope), expr, mutability));
+                let place =
+                    unpack!(block = this.as_temp(block, Some(temp_scope), expr, mutability));
                 let operand = Operand::Move(Place::from(place));
+
                 let then_block = this.cfg.start_new_block();
                 let else_block = this.cfg.start_new_block();
                 let term = TerminatorKind::if_(this.tcx, operand, then_block, else_block);
+
+                let source_info = this.source_info(expr_span);
                 this.cfg.terminate(block, source_info, term);
-                (then_block, else_block)
+                this.break_for_else(else_block, break_scope, source_info);
+
+                then_block.unit()
             }
         }
     }
@@ -302,6 +309,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
 
                 let arm_source_info = self.source_info(arm.span);
                 let arm_scope = (arm.scope, arm_source_info);
+                let match_scope = self.local_scope();
                 self.in_scope(arm_scope, arm.lint_level, |this| {
                     // `try_upvars_resolved` may fail if it is unable to resolve the given
                     // `PlaceBuilder` inside a closure. In this case, we don't want to include
@@ -340,6 +348,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                         scrutinee_span,
                         Some(arm.span),
                         Some(arm.scope),
+                        Some(match_scope),
                     );
 
                     if let Some(source_scope) = scope {
@@ -384,6 +393,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
         scrutinee_span: Span,
         arm_span: Option<Span>,
         arm_scope: Option<region::Scope>,
+        match_scope: Option<region::Scope>,
     ) -> BasicBlock {
         if candidate.subcandidates.is_empty() {
             // Avoid generating another `BasicBlock` when we only have one
@@ -395,6 +405,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                 fake_borrow_temps,
                 scrutinee_span,
                 arm_span,
+                match_scope,
                 true,
             )
         } else {
@@ -431,6 +442,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                         &fake_borrow_temps,
                         scrutinee_span,
                         arm_span,
+                        match_scope,
                         schedule_drops,
                     );
                     if arm_scope.is_none() {
@@ -616,6 +628,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
             irrefutable_pat.span,
             None,
             None,
+            None,
         )
         .unit()
     }
@@ -1742,13 +1755,14 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
 // Pat binding - used for `let` and function parameters as well.
 
 impl<'a, 'tcx> Builder<'a, 'tcx> {
-    pub fn lower_let(
+    crate fn lower_let_else(
         &mut self,
         mut block: BasicBlock,
         expr: &Expr<'tcx>,
         pat: &Pat<'tcx>,
+        else_target: region::Scope,
         span: Span,
-    ) -> (BasicBlock, BasicBlock) {
+    ) -> BlockAnd<()> {
         let expr_span = expr.span;
         let expr_place_builder = unpack!(block = self.lower_scrutinee(block, expr, expr_span));
         let mut guard_candidate = Candidate::new(expr_place_builder.clone(), &pat, false);
@@ -1769,6 +1783,9 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
             expr_place = expr_builder.into_place(self.tcx, self.typeck_results);
             opt_expr_place = Some((Some(&expr_place), expr_span));
         }
+        let otherwise_post_guard_block = otherwise_candidate.pre_binding_block.unwrap();
+        self.break_for_else(otherwise_post_guard_block, else_target, self.source_info(expr_span));
+
         self.declare_bindings(None, pat.span.to(span), pat, ArmHasGuard(false), opt_expr_place);
         let post_guard_block = self.bind_pattern(
             self.source_info(pat.span),
@@ -1778,9 +1795,10 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
             expr.span,
             None,
             None,
+            None,
         );
-        let otherwise_post_guard_block = otherwise_candidate.pre_binding_block.unwrap();
-        (post_guard_block, otherwise_post_guard_block)
+
+        post_guard_block.unit()
     }
 
     /// Initializes each of the bindings from the candidate by
@@ -1799,6 +1817,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
         fake_borrows: &Vec<(Place<'tcx>, Local)>,
         scrutinee_span: Span,
         arm_span: Option<Span>,
+        match_scope: Option<region::Scope>,
         schedule_drops: bool,
     ) -> BasicBlock {
         debug!("bind_and_guard_matched_candidate(candidate={:?})", candidate);
@@ -1929,17 +1948,25 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                 self.cfg.push_assign(block, scrutinee_source_info, Place::from(temp), borrow);
             }
 
-            let (guard_span, (post_guard_block, otherwise_post_guard_block)) = match *guard {
-                Guard::If(e) => {
-                    let e = &self.thir[e];
-                    let source_info = self.source_info(e.span);
-                    (e.span, self.test_bool(block, e, source_info))
-                }
-                Guard::IfLet(ref pat, scrutinee) => {
-                    let s = &self.thir[scrutinee];
-                    (s.span, self.lower_let(block, s, pat, arm_span.unwrap()))
-                }
-            };
+            let arm_span = arm_span.unwrap();
+            let arm_scope = self.local_scope();
+            let match_scope = match_scope.unwrap();
+            let mut guard_span = rustc_span::DUMMY_SP;
+
+            let (post_guard_block, otherwise_post_guard_block) =
+                self.in_if_then_scope(match_scope, |this| match *guard {
+                    Guard::If(e) => {
+                        let e = &this.thir[e];
+                        guard_span = e.span;
+                        this.then_else_break(block, e, arm_scope, match_scope, arm_span)
+                    }
+                    Guard::IfLet(ref pat, scrutinee) => {
+                        let s = &this.thir[scrutinee];
+                        guard_span = s.span;
+                        this.lower_let_else(block, s, pat, match_scope, arm_span)
+                    }
+                });
+
             let source_info = self.source_info(guard_span);
             let guard_end = self.source_info(tcx.sess.source_map().end_point(guard_span));
             let guard_frame = self.guard_context.pop().unwrap();
@@ -1955,10 +1982,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                 self.cfg.terminate(unreachable, source_info, TerminatorKind::Unreachable);
                 unreachable
             });
-            let outside_scope = self.cfg.start_new_block();
-            self.exit_top_scope(otherwise_post_guard_block, outside_scope, source_info);
             self.false_edges(
-                outside_scope,
+                otherwise_post_guard_block,
                 otherwise_block,
                 candidate.next_candidate_pre_binding_block,
                 source_info,
diff --git a/compiler/rustc_mir_build/src/build/scope.rs b/compiler/rustc_mir_build/src/build/scope.rs
index 496db58758c..bd8d14fcd01 100644
--- a/compiler/rustc_mir_build/src/build/scope.rs
+++ b/compiler/rustc_mir_build/src/build/scope.rs
@@ -81,6 +81,8 @@ that contains only loops and breakable blocks. It tracks where a `break`,
 
 */
 
+use std::mem;
+
 use crate::build::{BlockAnd, BlockAndExtension, BlockFrame, Builder, CFG};
 use rustc_data_structures::fx::FxHashMap;
 use rustc_index::vec::IndexVec;
@@ -93,9 +95,13 @@ use rustc_span::{Span, DUMMY_SP};
 #[derive(Debug)]
 pub struct Scopes<'tcx> {
     scopes: Vec<Scope>,
+
     /// The current set of breakable scopes. See module comment for more details.
     breakable_scopes: Vec<BreakableScope<'tcx>>,
 
+    /// The scope of the innermost if-then currently being lowered.
+    if_then_scope: Option<IfThenScope>,
+
     /// Drops that need to be done on unwind paths. See the comment on
     /// [DropTree] for more details.
     unwind_drops: DropTree,
@@ -164,6 +170,14 @@ struct BreakableScope<'tcx> {
     continue_drops: Option<DropTree>,
 }
 
+#[derive(Debug)]
+struct IfThenScope {
+    /// The if-then scope or arm scope
+    region_scope: region::Scope,
+    /// Drops that happen on the `else` path.
+    else_drops: DropTree,
+}
+
 /// The target of an expression that breaks out of a scope
 #[derive(Clone, Copy, Debug)]
 crate enum BreakableTarget {
@@ -183,6 +197,7 @@ const ROOT_NODE: DropIdx = DropIdx::from_u32(0);
 /// * Drops on unwind paths
 /// * Drops on generator drop paths (when a suspended generator is dropped)
 /// * Drops on return and loop exit paths
+/// * Drops on the else path in an `if let` chain
 ///
 /// Once no more nodes could be added to the tree, we lower it to MIR in one go
 /// in `build_mir`.
@@ -394,6 +409,7 @@ impl<'tcx> Scopes<'tcx> {
         Self {
             scopes: Vec::new(),
             breakable_scopes: Vec::new(),
+            if_then_scope: None,
             unwind_drops: DropTree::new(),
             generator_drops: DropTree::new(),
         }
@@ -483,6 +499,45 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
         }
     }
 
+    /// Start an if-then scope which tracks drop for `if` expressions and `if`
+    /// guards.
+    ///
+    /// For an if-let chain:
+    ///
+    /// if let Some(x) = a && let Some(y) = b && let Some(z) = c { ... }
+    ///
+    /// there are three possible ways the condition can be false and we may have
+    /// to drop `x`, `x` and `y`, or neither depending on which binding fails.
+    /// To handle this correctly we use a `DropTree` in a similar way to a
+    /// `loop` expression and 'break' out on all of the 'else' paths.
+    ///
+    /// Notes:
+    /// - We don't need to keep a stack of scopes in the `Builder` because the
+    ///   'else' paths will only leave the innermost scope.
+    /// - This is also used for match guards.
+    crate fn in_if_then_scope<F>(
+        &mut self,
+        region_scope: region::Scope,
+        f: F,
+    ) -> (BasicBlock, BasicBlock)
+    where
+        F: FnOnce(&mut Builder<'a, 'tcx>) -> BlockAnd<()>,
+    {
+        let scope = IfThenScope { region_scope, else_drops: DropTree::new() };
+        let previous_scope = mem::replace(&mut self.scopes.if_then_scope, Some(scope));
+
+        let then_block = unpack!(f(self));
+
+        let if_then_scope = mem::replace(&mut self.scopes.if_then_scope, previous_scope).unwrap();
+        assert!(if_then_scope.region_scope == region_scope);
+
+        let else_block = self
+            .build_exit_tree(if_then_scope.else_drops, None)
+            .map_or_else(|| self.cfg.start_new_block(), |else_block_and| unpack!(else_block_and));
+
+        (then_block, else_block)
+    }
+
     crate fn in_opt_scope<F, R>(
         &mut self,
         opt_scope: Option<(region::Scope, SourceInfo)>,
@@ -651,6 +706,36 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
         self.cfg.start_new_block().unit()
     }
 
+    crate fn break_for_else(
+        &mut self,
+        block: BasicBlock,
+        target: region::Scope,
+        source_info: SourceInfo,
+    ) {
+        let scope_index = self.scopes.scope_index(target, source_info.span);
+        let if_then_scope = self
+            .scopes
+            .if_then_scope
+            .as_mut()
+            .unwrap_or_else(|| span_bug!(source_info.span, "no if-then scope found"));
+
+        assert_eq!(if_then_scope.region_scope, target, "breaking to incorrect scope");
+
+        let mut drop_idx = ROOT_NODE;
+        let drops = &mut if_then_scope.else_drops;
+        for scope in &self.scopes.scopes[scope_index + 1..] {
+            for drop in &scope.drops {
+                drop_idx = drops.add_drop(*drop, drop_idx);
+            }
+        }
+        drops.add_entry(block, drop_idx);
+
+        // `build_drop_tree` doesn't have access to our source_info, so we
+        // create a dummy terminator now. `TerminatorKind::Resume` is used
+        // because MIR type checking will panic if it hasn't been overwritten.
+        self.cfg.terminate(block, source_info, TerminatorKind::Resume);
+    }
+
     // Add a dummy `Assign` statement to the CFG, with the span for the source code's `continue`
     // statement.
     fn add_dummy_assignment(&mut self, span: &Span, block: BasicBlock, source_info: SourceInfo) {
@@ -659,16 +744,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
         self.cfg.push_assign_unit(block, source_info, temp_place, self.tcx);
     }
 
-    crate fn exit_top_scope(
-        &mut self,
-        mut block: BasicBlock,
-        target: BasicBlock,
-        source_info: SourceInfo,
-    ) {
-        block = self.leave_top_scope(block);
-        self.cfg.terminate(block, source_info, TerminatorKind::Goto { target });
-    }
-
     fn leave_top_scope(&mut self, block: BasicBlock) -> BasicBlock {
         // If we are emitting a `drop` statement, we need to have the cached
         // diverge cleanup pads ready in case that drop panics.
@@ -927,61 +1002,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
 
     // Other
     // =====
-    /// Branch based on a boolean condition.
-    ///
-    /// This is a special case because the temporary for the condition needs to
-    /// be dropped on both the true and the false arm.
-    crate fn test_bool(
-        &mut self,
-        mut block: BasicBlock,
-        condition: &Expr<'tcx>,
-        source_info: SourceInfo,
-    ) -> (BasicBlock, BasicBlock) {
-        let cond = unpack!(block = self.as_local_operand(block, condition));
-        let true_block = self.cfg.start_new_block();
-        let false_block = self.cfg.start_new_block();
-        let term = TerminatorKind::if_(self.tcx, cond.clone(), true_block, false_block);
-        self.cfg.terminate(block, source_info, term);
-
-        match cond {
-            // Don't try to drop a constant
-            Operand::Constant(_) => (),
-            Operand::Copy(place) | Operand::Move(place) => {
-                if let Some(cond_temp) = place.as_local() {
-                    // Manually drop the condition on both branches.
-                    let top_scope = self.scopes.scopes.last_mut().unwrap();
-                    let top_drop_data = top_scope.drops.pop().unwrap();
-                    if self.generator_kind.is_some() {
-                        top_scope.invalidate_cache();
-                    }
-
-                    match top_drop_data.kind {
-                        DropKind::Value { .. } => {
-                            bug!("Drop scheduled on top of condition variable")
-                        }
-                        DropKind::Storage => {
-                            let source_info = top_drop_data.source_info;
-                            let local = top_drop_data.local;
-                            assert_eq!(local, cond_temp, "Drop scheduled on top of condition");
-                            self.cfg.push(
-                                true_block,
-                                Statement { source_info, kind: StatementKind::StorageDead(local) },
-                            );
-                            self.cfg.push(
-                                false_block,
-                                Statement { source_info, kind: StatementKind::StorageDead(local) },
-                            );
-                        }
-                    }
-                } else {
-                    bug!("Expected as_local_operand to produce a temporary");
-                }
-            }
-        }
-
-        (true_block, false_block)
-    }
-
     /// Returns the [DropIdx] for the innermost drop if the function unwound at
     /// this point. The `DropIdx` will be created if it doesn't already exist.
     fn diverge_cleanup(&mut self) -> DropIdx {
diff --git a/compiler/rustc_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs
index 9106f1d2342..66005be05df 100644
--- a/compiler/rustc_mir_build/src/thir/cx/expr.rs
+++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs
@@ -594,6 +594,10 @@ impl<'tcx> Cx<'tcx> {
                 ExprKind::Let { expr: self.mirror_expr(expr), pat: self.pattern_from_hir(pat) }
             }
             hir::ExprKind::If(cond, then, else_opt) => ExprKind::If {
+                if_then_scope: region::Scope {
+                    id: then.hir_id.local_id,
+                    data: region::ScopeData::IfThen,
+                },
                 cond: self.mirror_expr(cond),
                 then: self.mirror_expr(then),
                 else_opt: else_opt.map(|el| self.mirror_expr(el)),
diff --git a/compiler/rustc_mir_build/src/thir/visit.rs b/compiler/rustc_mir_build/src/thir/visit.rs
index dda6c74d4de..51c371b8720 100644
--- a/compiler/rustc_mir_build/src/thir/visit.rs
+++ b/compiler/rustc_mir_build/src/thir/visit.rs
@@ -34,7 +34,7 @@ pub fn walk_expr<'a, 'tcx: 'a, V: Visitor<'a, 'tcx>>(visitor: &mut V, expr: &Exp
             visitor.visit_expr(&visitor.thir()[value])
         }
         Box { value } => visitor.visit_expr(&visitor.thir()[value]),
-        If { cond, then, else_opt } => {
+        If { cond, then, else_opt, if_then_scope: _ } => {
             visitor.visit_expr(&visitor.thir()[cond]);
             visitor.visit_expr(&visitor.thir()[then]);
             if let Some(else_expr) = else_opt {