summaryrefslogtreecommitdiffstats
path: root/src/test/ui/let-else/let-else-drop-order.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/test/ui/let-else/let-else-drop-order.rs')
-rw-r--r--src/test/ui/let-else/let-else-drop-order.rs270
1 files changed, 270 insertions, 0 deletions
diff --git a/src/test/ui/let-else/let-else-drop-order.rs b/src/test/ui/let-else/let-else-drop-order.rs
new file mode 100644
index 000000000..e91e5de84
--- /dev/null
+++ b/src/test/ui/let-else/let-else-drop-order.rs
@@ -0,0 +1,270 @@
+// run-pass
+// edition:2021
+// check-run-results
+//
+// Drop order tests for let else
+//
+// Mostly this ensures two things:
+// 1. That let and let else temporary drop order is the same.
+// This is a specific design request: https://github.com/rust-lang/rust/pull/93628#issuecomment-1047140316
+// 2. That the else block truly only runs after the
+// temporaries have dropped.
+//
+// We also print some nice tables for an overview by humans.
+// Changes in those tables are considered breakages, but the
+// important properties 1 and 2 are also enforced by the code.
+// This is important as it's easy to update the stdout file
+// with a --bless and miss the impact of that change.
+
+
+#![allow(irrefutable_let_patterns)]
+
+use std::cell::RefCell;
+use std::rc::Rc;
+
+#[derive(Clone)]
+struct DropAccountant(Rc<RefCell<Vec<Vec<String>>>>);
+
+impl DropAccountant {
+ fn new() -> Self {
+ Self(Default::default())
+ }
+ fn build_droppy(&self, v: u32) -> Droppy<u32> {
+ Droppy(self.clone(), v)
+ }
+ fn build_droppy_enum_none(&self, _v: u32) -> ((), DroppyEnum<u32>) {
+ ((), DroppyEnum::None(self.clone()))
+ }
+ fn new_list(&self, s: impl ToString) {
+ self.0.borrow_mut().push(vec![s.to_string()]);
+ }
+ fn push(&self, s: impl ToString) {
+ let s = s.to_string();
+ let mut accounts = self.0.borrow_mut();
+ accounts.last_mut().unwrap().push(s);
+ }
+ fn print_table(&self) {
+ println!();
+
+ let accounts = self.0.borrow();
+ let before_last = &accounts[accounts.len() - 2];
+ let last = &accounts[accounts.len() - 1];
+ let before_last = get_comma_list(before_last);
+ let last = get_comma_list(last);
+ const LINES: &[&str] = &[
+ "vanilla",
+ "&",
+ "&mut",
+ "move",
+ "fn(this)",
+ "tuple",
+ "array",
+ "ref &",
+ "ref mut &mut",
+ ];
+ let max_len = LINES.iter().map(|v| v.len()).max().unwrap();
+ let max_len_before = before_last.iter().map(|v| v.len()).max().unwrap();
+ let max_len_last = last.iter().map(|v| v.len()).max().unwrap();
+
+ println!(
+ "| {: <max_len$} | {: <max_len_before$} | {: <max_len_last$} |",
+ "construct", before_last[0], last[0]
+ );
+ println!("| {:-<max_len$} | {:-<max_len_before$} | {:-<max_len_last$} |", "", "", "");
+
+ for ((l, l_before), l_last) in
+ LINES.iter().zip(before_last[1..].iter()).zip(last[1..].iter())
+ {
+ println!(
+ "| {: <max_len$} | {: <max_len_before$} | {: <max_len_last$} |",
+ l, l_before, l_last,
+ );
+ }
+ }
+ #[track_caller]
+ fn assert_all_equal_to(&self, st: &str) {
+ let accounts = self.0.borrow();
+ let last = &accounts[accounts.len() - 1];
+ let last = get_comma_list(last);
+ for line in last[1..].iter() {
+ assert_eq!(line.trim(), st.trim());
+ }
+ }
+ #[track_caller]
+ fn assert_equality_last_two_lists(&self) {
+ let accounts = self.0.borrow();
+ let last = &accounts[accounts.len() - 1];
+ let before_last = &accounts[accounts.len() - 2];
+ for (l, b) in last[1..].iter().zip(before_last[1..].iter()) {
+ if !(l == b || l == "n/a" || b == "n/a") {
+ panic!("not equal: '{last:?}' != '{before_last:?}'");
+ }
+ }
+ }
+}
+
+fn get_comma_list(sl: &[String]) -> Vec<String> {
+ std::iter::once(sl[0].clone())
+ .chain(sl[1..].chunks(2).map(|c| c.join(",")))
+ .collect::<Vec<String>>()
+}
+
+struct Droppy<T>(DropAccountant, T);
+
+impl<T> Drop for Droppy<T> {
+ fn drop(&mut self) {
+ self.0.push("drop");
+ }
+}
+
+#[allow(dead_code)]
+enum DroppyEnum<T> {
+ Some(DropAccountant, T),
+ None(DropAccountant),
+}
+
+impl<T> Drop for DroppyEnum<T> {
+ fn drop(&mut self) {
+ match self {
+ DroppyEnum::Some(acc, _inner) => acc,
+ DroppyEnum::None(acc) => acc,
+ }
+ .push("drop");
+ }
+}
+
+macro_rules! nestings_with {
+ ($construct:ident, $binding:pat, $exp:expr) => {
+ // vanilla:
+ $construct!($binding, $exp.1);
+
+ // &:
+ $construct!(&$binding, &$exp.1);
+
+ // &mut:
+ $construct!(&mut $binding, &mut ($exp.1));
+
+ {
+ // move:
+ let w = $exp;
+ $construct!(
+ $binding,
+ {
+ let w = w;
+ w
+ }
+ .1
+ );
+ }
+
+ // fn(this):
+ $construct!($binding, std::convert::identity($exp).1);
+ };
+}
+
+macro_rules! nestings {
+ ($construct:ident, $binding:pat, $exp:expr) => {
+ nestings_with!($construct, $binding, $exp);
+
+ // tuple:
+ $construct!(($binding, 77), ($exp.1, 77));
+
+ // array:
+ $construct!([$binding], [$exp.1]);
+ };
+}
+
+macro_rules! let_else {
+ ($acc:expr, $v:expr, $binding:pat, $build:ident) => {
+ let acc = $acc;
+ let v = $v;
+
+ macro_rules! let_else_construct {
+ ($arg:pat, $exp:expr) => {
+ loop {
+ let $arg = $exp else {
+ acc.push("else");
+ break;
+ };
+ acc.push("body");
+ break;
+ }
+ };
+ }
+ nestings!(let_else_construct, $binding, acc.$build(v));
+ // ref &:
+ let_else_construct!($binding, &acc.$build(v).1);
+
+ // ref mut &mut:
+ let_else_construct!($binding, &mut acc.$build(v).1);
+ };
+}
+
+macro_rules! let_ {
+ ($acc:expr, $binding:tt) => {
+ let acc = $acc;
+
+ macro_rules! let_construct {
+ ($arg:pat, $exp:expr) => {{
+ let $arg = $exp;
+ acc.push("body");
+ }};
+ }
+ let v = 0;
+ {
+ nestings_with!(let_construct, $binding, acc.build_droppy(v));
+ }
+ acc.push("n/a");
+ acc.push("n/a");
+ acc.push("n/a");
+ acc.push("n/a");
+
+ // ref &:
+ let_construct!($binding, &acc.build_droppy(v).1);
+
+ // ref mut &mut:
+ let_construct!($binding, &mut acc.build_droppy(v).1);
+ };
+}
+
+fn main() {
+ let acc = DropAccountant::new();
+
+ println!(" --- matching cases ---");
+
+ // Ensure that let and let else have the same behaviour
+ acc.new_list("let _");
+ let_!(&acc, _);
+ acc.new_list("let else _");
+ let_else!(&acc, 0, _, build_droppy);
+ acc.assert_equality_last_two_lists();
+ acc.print_table();
+
+ // Ensure that let and let else have the same behaviour
+ acc.new_list("let _v");
+ let_!(&acc, _v);
+ acc.new_list("let else _v");
+ let_else!(&acc, 0, _v, build_droppy);
+ acc.assert_equality_last_two_lists();
+ acc.print_table();
+
+ println!();
+
+ println!(" --- mismatching cases ---");
+
+ acc.new_list("let else _ mismatch");
+ let_else!(&acc, 1, DroppyEnum::Some(_, _), build_droppy_enum_none);
+ acc.new_list("let else _v mismatch");
+ let_else!(&acc, 1, DroppyEnum::Some(_, _v), build_droppy_enum_none);
+ acc.print_table();
+ // This ensures that we always drop before visiting the else case
+ acc.assert_all_equal_to("drop,else");
+
+ acc.new_list("let else 0 mismatch");
+ let_else!(&acc, 1, 0, build_droppy);
+ acc.new_list("let else 0 mismatch");
+ let_else!(&acc, 1, 0, build_droppy);
+ acc.print_table();
+ // This ensures that we always drop before visiting the else case
+ acc.assert_all_equal_to("drop,else");
+}