about summary refs log tree commit diff
diff options
context:
space:
mode:
authorNathan Whitaker <nathan.whitaker01@gmail.com>2022-10-12 15:29:08 -0700
committerNathan Whitaker <nathan.whitaker01@gmail.com>2022-10-12 17:57:32 -0700
commitad8b24272428b28770471f222c19fa3154a65819 (patch)
treef38880c0d0afd13e96210f9ff3dfbdf027b68107
parent0938e1680daf66ca6aad428aedf9a920a0dab5ad (diff)
downloadrust-ad8b24272428b28770471f222c19fa3154a65819.tar.gz
rust-ad8b24272428b28770471f222c19fa3154a65819.zip
Let chains should still drop temporaries
by the end of the condition's execution
-rw-r--r--compiler/rustc_ast_lowering/src/expr.rs45
-rw-r--r--src/test/ui/drop/drop_order.rs63
2 files changed, 97 insertions, 11 deletions
diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs
index c55b4906302..104010f8d43 100644
--- a/compiler/rustc_ast_lowering/src/expr.rs
+++ b/compiler/rustc_ast_lowering/src/expr.rs
@@ -388,7 +388,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
         else_opt: Option<&Expr>,
     ) -> hir::ExprKind<'hir> {
         let lowered_cond = self.lower_expr(cond);
-        let new_cond = self.manage_let_cond(lowered_cond);
+        let new_cond = self.wrap_cond_in_drop_scope(lowered_cond);
         let then_expr = self.lower_block_expr(then);
         if let Some(rslt) = else_opt {
             hir::ExprKind::If(new_cond, self.arena.alloc(then_expr), Some(self.lower_expr(rslt)))
@@ -397,9 +397,9 @@ impl<'hir> LoweringContext<'_, 'hir> {
         }
     }
 
-    // If `cond` kind is `let`, returns `let`. Otherwise, wraps and returns `cond`
-    // in a temporary block.
-    fn manage_let_cond(&mut self, cond: &'hir hir::Expr<'hir>) -> &'hir hir::Expr<'hir> {
+    // Wraps a condition (i.e. `cond` in `if cond` or `while cond`) in a terminating scope
+    // so that temporaries created in the condition don't live beyond it.
+    fn wrap_cond_in_drop_scope(&mut self, cond: &'hir hir::Expr<'hir>) -> &'hir hir::Expr<'hir> {
         fn has_let_expr<'hir>(expr: &'hir hir::Expr<'hir>) -> bool {
             match expr.kind {
                 hir::ExprKind::Binary(_, lhs, rhs) => has_let_expr(lhs) || has_let_expr(rhs),
@@ -407,12 +407,35 @@ impl<'hir> LoweringContext<'_, 'hir> {
                 _ => false,
             }
         }
-        if has_let_expr(cond) {
-            cond
-        } else {
-            let reason = DesugaringKind::CondTemporary;
-            let span_block = self.mark_span_with_reason(reason, cond.span, None);
-            self.expr_drop_temps(span_block, cond, AttrVec::new())
+
+        // We have to take special care for `let` exprs in the condition, e.g. in
+        // `if let pat = val` or `if foo && let pat = val`, as we _do_ want `val` to live beyond the
+        // condition in this case.
+        //
+        // In order to mantain the drop behavior for the non `let` parts of the condition,
+        // we still wrap them in terminating scopes, e.g. `if foo && let pat = val` essentially
+        // gets transformed into `if { let _t = foo; _t } && let pat = val`
+        match cond.kind {
+            hir::ExprKind::Binary(
+                op @ Spanned { node: hir::BinOpKind::And | hir::BinOpKind::Or, .. },
+                lhs,
+                rhs,
+            ) if has_let_expr(cond) => {
+                let lhs = self.wrap_cond_in_drop_scope(lhs);
+                let rhs = self.wrap_cond_in_drop_scope(rhs);
+
+                self.arena.alloc(self.expr(
+                    cond.span,
+                    hir::ExprKind::Binary(op, lhs, rhs),
+                    AttrVec::new(),
+                ))
+            }
+            hir::ExprKind::Let(_) => cond,
+            _ => {
+                let reason = DesugaringKind::CondTemporary;
+                let span_block = self.mark_span_with_reason(reason, cond.span, None);
+                self.expr_drop_temps(span_block, cond, AttrVec::new())
+            }
         }
     }
 
@@ -440,7 +463,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
         opt_label: Option<Label>,
     ) -> hir::ExprKind<'hir> {
         let lowered_cond = self.with_loop_condition_scope(|t| t.lower_expr(cond));
-        let new_cond = self.manage_let_cond(lowered_cond);
+        let new_cond = self.wrap_cond_in_drop_scope(lowered_cond);
         let then = self.lower_block_expr(body);
         let expr_break = self.expr_break(span, AttrVec::new());
         let stmt_break = self.stmt_expr(span, expr_break);
diff --git a/src/test/ui/drop/drop_order.rs b/src/test/ui/drop/drop_order.rs
index e42150dcc09..bf740b6a9ab 100644
--- a/src/test/ui/drop/drop_order.rs
+++ b/src/test/ui/drop/drop_order.rs
@@ -1,4 +1,5 @@
 // run-pass
+#![feature(let_chains)]
 
 use std::cell::RefCell;
 use std::convert::TryInto;
@@ -116,6 +117,58 @@ impl DropOrderCollector {
         }
     }
 
+    fn let_chain(&self) {
+        // take the "then" branch
+        if self.option_loud_drop(2).is_some() // 2
+            && self.option_loud_drop(1).is_some() // 1
+            && let Some(_d) = self.option_loud_drop(4) { // 4
+            self.print(3); // 3
+        }
+
+        // take the "else" branch
+        if self.option_loud_drop(6).is_some() // 2
+            && self.option_loud_drop(5).is_some() // 1
+            && let None = self.option_loud_drop(7) { // 3
+            unreachable!();
+        } else {
+            self.print(8); // 4
+        }
+
+        // let exprs interspersed
+        if self.option_loud_drop(9).is_some() // 1
+            && let Some(_d) = self.option_loud_drop(13) // 5
+            && self.option_loud_drop(10).is_some() // 2
+            && let Some(_e) = self.option_loud_drop(12) { // 4
+            self.print(11); // 3
+        }
+
+        // let exprs first
+        if let Some(_d) = self.option_loud_drop(18) // 5
+            && let Some(_e) = self.option_loud_drop(17) // 4
+            && self.option_loud_drop(14).is_some() // 1
+            && self.option_loud_drop(15).is_some() { // 2
+                self.print(16); // 3
+            }
+
+        // let exprs last
+        if self.option_loud_drop(20).is_some() // 2
+            && self.option_loud_drop(19).is_some() // 1
+            && let Some(_d) = self.option_loud_drop(23) // 5
+            && let Some(_e) = self.option_loud_drop(22) { // 4
+                self.print(21); // 3
+        }
+    }
+
+    fn while_(&self) {
+        let mut v = self.option_loud_drop(4);
+        while let Some(_d) = v
+            && self.option_loud_drop(1).is_some()
+            && self.option_loud_drop(2).is_some() {
+            self.print(3);
+            v = None;
+        }
+    }
+
     fn assert_sorted(self) {
         assert!(
             self.0
@@ -142,4 +195,14 @@ fn main() {
     let collector = DropOrderCollector::default();
     collector.match_();
     collector.assert_sorted();
+
+    println!("-- let chain --");
+    let collector = DropOrderCollector::default();
+    collector.let_chain();
+    collector.assert_sorted();
+
+    println!("-- while --");
+    let collector = DropOrderCollector::default();
+    collector.while_();
+    collector.assert_sorted();
 }