From 0f65bcd9204c7d4753f1747cd3559f0bc4d8150a Mon Sep 17 00:00:00 2001
From: Jakob Degen <jakob.e.degen@gmail.com>
Date: Wed, 13 Apr 2022 07:08:58 -0400
Subject: [PATCH] Modify MIR building to drop `foo` in `[foo; 0]`

---
 .../src/build/expr/as_rvalue.rs               |  50 +++++++-
 compiler/rustc_mir_build/src/build/scope.rs   |   1 +
 src/test/ui/drop/repeat-drop-2.rs             |  15 +++
 src/test/ui/drop/repeat-drop-2.stderr         |  29 +++++
 src/test/ui/drop/repeat-drop.rs               | 120 ++++++++++++++++++
 5 files changed, 210 insertions(+), 5 deletions(-)
 create mode 100644 src/test/ui/drop/repeat-drop-2.rs
 create mode 100644 src/test/ui/drop/repeat-drop-2.stderr
 create mode 100644 src/test/ui/drop/repeat-drop.rs

diff --git a/compiler/rustc_mir_build/src/build/expr/as_rvalue.rs b/compiler/rustc_mir_build/src/build/expr/as_rvalue.rs
index 0fd67f15b75..2f9834df9d3 100644
--- a/compiler/rustc_mir_build/src/build/expr/as_rvalue.rs
+++ b/compiler/rustc_mir_build/src/build/expr/as_rvalue.rs
@@ -52,11 +52,20 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                 })
             }
             ExprKind::Repeat { value, count } => {
-                let value_operand = unpack!(
-                    block =
-                        this.as_operand(block, scope, &this.thir[value], None, NeedsTemporary::No)
-                );
-                block.and(Rvalue::Repeat(value_operand, count))
+                if Some(0) == count.try_eval_usize(this.tcx, this.param_env) {
+                    this.build_zero_repeat(block, value, scope, source_info)
+                } else {
+                    let value_operand = unpack!(
+                        block = this.as_operand(
+                            block,
+                            scope,
+                            &this.thir[value],
+                            None,
+                            NeedsTemporary::No
+                        )
+                    );
+                    block.and(Rvalue::Repeat(value_operand, count))
+                }
             }
             ExprKind::Binary { op, lhs, rhs } => {
                 let lhs = unpack!(
@@ -515,6 +524,37 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
         }
     }
 
+    fn build_zero_repeat(
+        &mut self,
+        mut block: BasicBlock,
+        value: ExprId,
+        scope: Option<region::Scope>,
+        outer_source_info: SourceInfo,
+    ) -> BlockAnd<Rvalue<'tcx>> {
+        let this = self;
+        let value = &this.thir[value];
+        let elem_ty = value.ty;
+        if let Some(Category::Constant) = Category::of(&value.kind) {
+            // Repeating a const does nothing
+        } else {
+            // For a non-const, we may need to generate an appropriate `Drop`
+            let value_operand =
+                unpack!(block = this.as_operand(block, scope, value, None, NeedsTemporary::No));
+            if let Operand::Move(to_drop) = value_operand {
+                let success = this.cfg.start_new_block();
+                this.cfg.terminate(
+                    block,
+                    outer_source_info,
+                    TerminatorKind::Drop { place: to_drop, target: success, unwind: None },
+                );
+                this.diverge_from(block);
+                block = success;
+            }
+            this.record_operands_moved(&[value_operand]);
+        }
+        block.and(Rvalue::Aggregate(Box::new(AggregateKind::Array(elem_ty)), Vec::new()))
+    }
+
     fn limit_capture_mutability(
         &mut self,
         upvar_span: Span,
diff --git a/compiler/rustc_mir_build/src/build/scope.rs b/compiler/rustc_mir_build/src/build/scope.rs
index 53f9706f021..a5f8a5847c2 100644
--- a/compiler/rustc_mir_build/src/build/scope.rs
+++ b/compiler/rustc_mir_build/src/build/scope.rs
@@ -1033,6 +1033,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                 self.cfg.block_data(start).terminator().kind,
                 TerminatorKind::Assert { .. }
                     | TerminatorKind::Call { .. }
+                    | TerminatorKind::Drop { .. }
                     | TerminatorKind::DropAndReplace { .. }
                     | TerminatorKind::FalseUnwind { .. }
                     | TerminatorKind::InlineAsm { .. }
diff --git a/src/test/ui/drop/repeat-drop-2.rs b/src/test/ui/drop/repeat-drop-2.rs
new file mode 100644
index 00000000000..2e7855328ec
--- /dev/null
+++ b/src/test/ui/drop/repeat-drop-2.rs
@@ -0,0 +1,15 @@
+fn borrowck_catch() {
+    let foo = String::new();
+    let _bar = foo;
+    let _baz = [foo; 0]; //~ ERROR use of moved value: `foo` [E0382]
+}
+
+const _: [String; 0] = [String::new(); 0];
+//~^ ERROR destructors cannot be evaluated at compile-time [E0493]
+
+fn must_be_init() {
+    let x: u8;
+    let _ = [x; 0]; //~ ERROR: use of possibly-uninitialized variable: `x`
+}
+
+fn main() {}
diff --git a/src/test/ui/drop/repeat-drop-2.stderr b/src/test/ui/drop/repeat-drop-2.stderr
new file mode 100644
index 00000000000..cdc58180c37
--- /dev/null
+++ b/src/test/ui/drop/repeat-drop-2.stderr
@@ -0,0 +1,29 @@
+error[E0382]: use of moved value: `foo`
+  --> $DIR/repeat-drop-2.rs:4:17
+   |
+LL |     let foo = String::new();
+   |         --- move occurs because `foo` has type `String`, which does not implement the `Copy` trait
+LL |     let _bar = foo;
+   |                --- value moved here
+LL |     let _baz = [foo; 0];
+   |                 ^^^ value used here after move
+
+error[E0493]: destructors cannot be evaluated at compile-time
+  --> $DIR/repeat-drop-2.rs:7:25
+   |
+LL | const _: [String; 0] = [String::new(); 0];
+   |                        -^^^^^^^^^^^^^----
+   |                        ||
+   |                        |constants cannot evaluate destructors
+   |                        value is dropped here
+
+error[E0381]: use of possibly-uninitialized variable: `x`
+  --> $DIR/repeat-drop-2.rs:12:14
+   |
+LL |     let _ = [x; 0];
+   |              ^ use of possibly-uninitialized `x`
+
+error: aborting due to 3 previous errors
+
+Some errors have detailed explanations: E0381, E0382, E0493.
+For more information about an error, try `rustc --explain E0381`.
diff --git a/src/test/ui/drop/repeat-drop.rs b/src/test/ui/drop/repeat-drop.rs
new file mode 100644
index 00000000000..03e832adb3b
--- /dev/null
+++ b/src/test/ui/drop/repeat-drop.rs
@@ -0,0 +1,120 @@
+// run-pass
+// ignore-wasm32-bare no unwinding panic
+// ignore-avr no unwinding panic
+// ignore-nvptx64 no unwinding panic
+
+static mut CHECK: usize = 0;
+
+struct DropChecker(usize);
+
+impl Drop for DropChecker {
+    fn drop(&mut self) {
+        unsafe {
+            if CHECK != self.0 - 1 {
+                panic!("Found {}, should have found {}", CHECK, self.0 - 1);
+            }
+            CHECK = self.0;
+        }
+    }
+}
+
+macro_rules! check_drops {
+    ($l:literal) => {
+        unsafe { assert_eq!(CHECK, $l) }
+    };
+}
+
+struct DropPanic;
+
+impl Drop for DropPanic {
+    fn drop(&mut self) {
+        panic!()
+    }
+}
+
+fn value_zero() {
+    unsafe { CHECK = 0 };
+    let foo = DropChecker(1);
+    let v: [DropChecker; 0] = [foo; 0];
+    check_drops!(1);
+    std::mem::drop(v);
+    check_drops!(1);
+}
+
+fn value_one() {
+    unsafe { CHECK = 0 };
+    let foo = DropChecker(1);
+    let v: [DropChecker; 1] = [foo; 1];
+    check_drops!(0);
+    std::mem::drop(v);
+    check_drops!(1);
+}
+
+const DROP_CHECKER: DropChecker = DropChecker(1);
+
+fn const_zero() {
+    unsafe { CHECK = 0 };
+    let v: [DropChecker; 0] = [DROP_CHECKER; 0];
+    check_drops!(0);
+    std::mem::drop(v);
+    check_drops!(0);
+}
+
+fn const_one() {
+    unsafe { CHECK = 0 };
+    let v: [DropChecker; 1] = [DROP_CHECKER; 1];
+    check_drops!(0);
+    std::mem::drop(v);
+    check_drops!(1);
+}
+
+fn const_generic_zero<const N: usize>() {
+    unsafe { CHECK = 0 };
+    let v: [DropChecker; N] = [DROP_CHECKER; N];
+    check_drops!(0);
+    std::mem::drop(v);
+    check_drops!(0);
+}
+
+fn const_generic_one<const N: usize>() {
+    unsafe { CHECK = 0 };
+    let v: [DropChecker; N] = [DROP_CHECKER; N];
+    check_drops!(0);
+    std::mem::drop(v);
+    check_drops!(1);
+}
+
+// Make sure that things are allowed to promote as expected
+
+fn allow_promote() {
+    unsafe { CHECK = 0 };
+    let foo = DropChecker(1);
+    let v: &'static [DropChecker; 0] = &[foo; 0];
+    check_drops!(1);
+    std::mem::drop(v);
+    check_drops!(1);
+}
+
+// Verify that unwinding in the drop causes the right things to drop in the right order
+fn on_unwind() {
+    unsafe { CHECK = 0 };
+    std::panic::catch_unwind(|| {
+        let panic = DropPanic;
+        let _local = DropChecker(2);
+        let _v = (DropChecker(1), [panic; 0]);
+        std::process::abort();
+    })
+    .unwrap_err();
+    check_drops!(2);
+}
+
+fn main() {
+    value_zero();
+    value_one();
+    const_zero();
+    const_one();
+    const_generic_zero::<0>();
+    const_generic_one::<1>();
+    allow_promote();
+    on_unwind();
+}