about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--tests/ui/drop/if-let-guards.rs89
1 files changed, 89 insertions, 0 deletions
diff --git a/tests/ui/drop/if-let-guards.rs b/tests/ui/drop/if-let-guards.rs
new file mode 100644
index 00000000000..918245e029c
--- /dev/null
+++ b/tests/ui/drop/if-let-guards.rs
@@ -0,0 +1,89 @@
+//! Tests drop order for `if let` guard bindings and temporaries. This is for behavior specific to
+//! `match` expressions, whereas `tests/ui/drop/drop-order-comparisons.rs` compares `let` chains in
+//! guards to `let` chains in `if` expressions. Drop order for `let` chains in guards shouldn't
+//! differ between Editions, so we test on both 2021 and 2024, expecting the same results.
+//@ revisions: e2021 e2024
+//@ [e2021] edition: 2021
+//@ [e2024] edition: 2024
+//@ run-pass
+
+#![feature(if_let_guard)]
+#![deny(rust_2024_compatibility)]
+
+use core::{cell::RefCell, ops::Drop};
+
+fn main() {
+    // Test that `let` guard bindings and temps are dropped before the arm's pattern's bindings.
+    // TODO: this is currently the old behavior (`let` bindings dropped after arm bindings).
+    assert_drop_order(1..=6, |o| {
+        // We move out of the scrutinee, so the drop order of the array's elements are based on
+        // binding declaration order, and they're dropped in the arm's scope.
+        match [o.log(3), o.log(2)] {
+            // Partially move from the guard temporary to test drops both for temps and the binding.
+            [_x, _y] if let [_, _z, _] = [o.log(6), o.log(4), o.log(5)]
+                && true => { let _a = o.log(1); }
+            _ => unreachable!(),
+        }
+    });
+
+    // Sanity check: we don't move out of the match scrutinee when the guard fails.
+    assert_drop_order(1..=4, |o| {
+        // The scrutinee uses the drop order for arrays since its elements aren't moved.
+        match [o.log(3), o.log(4)] {
+            [_x, _y] if let _z = o.log(1)
+                && false => unreachable!(),
+            _ => { let _a = o.log(2); }
+        }
+    });
+
+    // Test `let` guards' temporaries are dropped immediately when a guard fails, even if the guard
+    // is lowered and run multiple times on the same arm due to or-patterns.
+    assert_drop_order(1..=8, |o| {
+        let mut _x = 1;
+        // The match's scrutinee isn't bound by-move, so it outlives the match.
+        match o.log(8) {
+            // Failing a guard breaks out of the arm's scope, dropping the `let` guard's scrutinee.
+            _ | _ | _ if let _ = o.log(_x)
+                && { _x += 1; false } => unreachable!(),
+            // The temporaries from a failed guard are dropped before testing the next guard.
+            _ if let _ = o.log(5)
+                && { o.push(4); false } => unreachable!(),
+            // If the guard succeeds, we stay in the arm's scope to execute its body.
+            _ if let _ = o.log(7)
+                && true => { o.log(6); }
+            _ => unreachable!(),
+        }
+    });
+}
+
+// # Test scaffolding...
+
+struct DropOrder(RefCell<Vec<u64>>);
+struct LogDrop<'o>(&'o DropOrder, u64);
+
+impl DropOrder {
+    fn log(&self, n: u64) -> LogDrop<'_> {
+        LogDrop(self, n)
+    }
+    fn push(&self, n: u64) {
+        self.0.borrow_mut().push(n);
+    }
+}
+
+impl<'o> Drop for LogDrop<'o> {
+    fn drop(&mut self) {
+        self.0.push(self.1);
+    }
+}
+
+#[track_caller]
+fn assert_drop_order(
+    ex: impl IntoIterator<Item = u64>,
+    f: impl Fn(&DropOrder),
+) {
+    let order = DropOrder(RefCell::new(Vec::new()));
+    f(&order);
+    let order = order.0.into_inner();
+    let expected: Vec<u64> = ex.into_iter().collect();
+    assert_eq!(order, expected);
+}