about summary refs log tree commit diff
path: root/src/test/ui
diff options
context:
space:
mode:
authorest31 <MTest31@outlook.com>2022-07-15 19:17:10 +0200
committerest31 <MTest31@outlook.com>2022-09-05 13:06:23 +0200
commit2d8460ef43d902f34ba2133fe38f66ee8d2fdafc (patch)
tree34e1a0d47e87656d89d3dbe5e4cdfb8a22558fee /src/test/ui
parent289279de116707f28cf9c18e4bbb8c6ec84ad75b (diff)
downloadrust-2d8460ef43d902f34ba2133fe38f66ee8d2fdafc.tar.gz
rust-2d8460ef43d902f34ba2133fe38f66ee8d2fdafc.zip
Add matrix based test for documenting the let / let else temporary drop order
The drop order of let and let else is supposed to be the same,
and in order to ensure this, the test checks that this holds for
the given list of cases.

The test also ensures that we drop the temporaries of the
condition before executing the else block.

We made the test matrix based so it can check all the possible
combinations and find out possible edge cases.
Diffstat (limited to 'src/test/ui')
-rw-r--r--src/test/ui/let-else/let-else-drop-order.rs270
-rw-r--r--src/test/ui/let-else/let-else-drop-order.run.stdout51
2 files changed, 321 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 00000000000..0054f3d4182
--- /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.
+
+#![feature(let_else)]
+#![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");
+}
diff --git a/src/test/ui/let-else/let-else-drop-order.run.stdout b/src/test/ui/let-else/let-else-drop-order.run.stdout
new file mode 100644
index 00000000000..01cf2f73e17
--- /dev/null
+++ b/src/test/ui/let-else/let-else-drop-order.run.stdout
@@ -0,0 +1,51 @@
+ --- matching cases ---
+
+| construct    | let _     | let else _ |
+| ------------ | --------- | ---------- |
+| vanilla      | drop,body | drop,body  |
+| &            | body,drop | body,drop  |
+| &mut         | body,drop | body,drop  |
+| move         | drop,body | drop,body  |
+| fn(this)     | drop,body | drop,body  |
+| tuple        | n/a,n/a   | drop,body  |
+| array        | n/a,n/a   | drop,body  |
+| ref &        | body,drop | body,drop  |
+| ref mut &mut | body,drop | body,drop  |
+
+| construct    | let _v    | let else _v |
+| ------------ | --------- | ----------- |
+| vanilla      | drop,body | drop,body   |
+| &            | body,drop | body,drop   |
+| &mut         | body,drop | body,drop   |
+| move         | drop,body | drop,body   |
+| fn(this)     | drop,body | drop,body   |
+| tuple        | n/a,n/a   | drop,body   |
+| array        | n/a,n/a   | drop,body   |
+| ref &        | body,drop | body,drop   |
+| ref mut &mut | body,drop | body,drop   |
+
+ --- mismatching cases ---
+
+| construct    | let else _ mismatch | let else _v mismatch |
+| ------------ | ------------------- | -------------------- |
+| vanilla      | drop,else           | drop,else            |
+| &            | drop,else           | drop,else            |
+| &mut         | drop,else           | drop,else            |
+| move         | drop,else           | drop,else            |
+| fn(this)     | drop,else           | drop,else            |
+| tuple        | drop,else           | drop,else            |
+| array        | drop,else           | drop,else            |
+| ref &        | drop,else           | drop,else            |
+| ref mut &mut | drop,else           | drop,else            |
+
+| construct    | let else 0 mismatch | let else 0 mismatch |
+| ------------ | ------------------- | ------------------- |
+| vanilla      | drop,else           | drop,else           |
+| &            | drop,else           | drop,else           |
+| &mut         | drop,else           | drop,else           |
+| move         | drop,else           | drop,else           |
+| fn(this)     | drop,else           | drop,else           |
+| tuple        | drop,else           | drop,else           |
+| array        | drop,else           | drop,else           |
+| ref &        | drop,else           | drop,else           |
+| ref mut &mut | drop,else           | drop,else           |