5 // Drop order tests for let else
7 // Mostly this ensures two things:
8 // 1. That let and let else temporary drop order is the same.
9 // This is a specific design request: https://github.com/rust-lang/rust/pull/93628#issuecomment-1047140316
10 // 2. That the else block truly only runs after the
11 // temporaries have dropped.
13 // We also print some nice tables for an overview by humans.
14 // Changes in those tables are considered breakages, but the
15 // important properties 1 and 2 are also enforced by the code.
16 // This is important as it's easy to update the stdout file
17 // with a --bless and miss the impact of that change.
20 #![allow(irrefutable_let_patterns)]
22 use std::cell::RefCell;
26 struct DropAccountant(Rc<RefCell<Vec<Vec<String>>>>);
30 Self(Default::default())
32 fn build_droppy(&self, v: u32) -> Droppy<u32> {
33 Droppy(self.clone(), v)
35 fn build_droppy_enum_none(&self, _v: u32) -> ((), DroppyEnum<u32>) {
36 ((), DroppyEnum::None(self.clone()))
38 fn new_list(&self, s: impl ToString) {
39 self.0.borrow_mut().push(vec![s.to_string()]);
41 fn push(&self, s: impl ToString) {
42 let s = s.to_string();
43 let mut accounts = self.0.borrow_mut();
44 accounts.last_mut().unwrap().push(s);
46 fn print_table(&self) {
49 let accounts = self.0.borrow();
50 let before_last = &accounts[accounts.len() - 2];
51 let last = &accounts[accounts.len() - 1];
52 let before_last = get_comma_list(before_last);
53 let last = get_comma_list(last);
54 const LINES: &[&str] = &[
65 let max_len = LINES.iter().map(|v| v.len()).max().unwrap();
66 let max_len_before = before_last.iter().map(|v| v.len()).max().unwrap();
67 let max_len_last = last.iter().map(|v| v.len()).max().unwrap();
70 "| {: <max_len$} | {: <max_len_before$} | {: <max_len_last$} |",
71 "construct", before_last[0], last[0]
73 println!("| {:-<max_len$} | {:-<max_len_before$} | {:-<max_len_last$} |", "", "", "");
75 for ((l, l_before), l_last) in
76 LINES.iter().zip(before_last[1..].iter()).zip(last[1..].iter())
79 "| {: <max_len$} | {: <max_len_before$} | {: <max_len_last$} |",
85 fn assert_all_equal_to(&self, st: &str) {
86 let accounts = self.0.borrow();
87 let last = &accounts[accounts.len() - 1];
88 let last = get_comma_list(last);
89 for line in last[1..].iter() {
90 assert_eq!(line.trim(), st.trim());
94 fn assert_equality_last_two_lists(&self) {
95 let accounts = self.0.borrow();
96 let last = &accounts[accounts.len() - 1];
97 let before_last = &accounts[accounts.len() - 2];
98 for (l, b) in last[1..].iter().zip(before_last[1..].iter()) {
99 if !(l == b || l == "n/a" || b == "n/a") {
100 panic!("not equal: '{last:?}' != '{before_last:?}'");
106 fn get_comma_list(sl: &[String]) -> Vec<String> {
107 std::iter::once(sl[0].clone())
108 .chain(sl[1..].chunks(2).map(|c| c.join(",")))
109 .collect::<Vec<String>>()
112 struct Droppy<T>(DropAccountant, T);
114 impl<T> Drop for Droppy<T> {
122 Some(DropAccountant, T),
123 None(DropAccountant),
126 impl<T> Drop for DroppyEnum<T> {
129 DroppyEnum::Some(acc, _inner) => acc,
130 DroppyEnum::None(acc) => acc,
136 macro_rules! nestings_with {
137 ($construct:ident, $binding:pat, $exp:expr) => {
139 $construct!($binding, $exp.1);
142 $construct!(&$binding, &$exp.1);
145 $construct!(&mut $binding, &mut ($exp.1));
161 $construct!($binding, std::convert::identity($exp).1);
165 macro_rules! nestings {
166 ($construct:ident, $binding:pat, $exp:expr) => {
167 nestings_with!($construct, $binding, $exp);
170 $construct!(($binding, 77), ($exp.1, 77));
173 $construct!([$binding], [$exp.1]);
177 macro_rules! let_else {
178 ($acc:expr, $v:expr, $binding:pat, $build:ident) => {
182 macro_rules! let_else_construct {
183 ($arg:pat, $exp:expr) => {
185 let $arg = $exp else {
194 nestings!(let_else_construct, $binding, acc.$build(v));
196 let_else_construct!($binding, &acc.$build(v).1);
199 let_else_construct!($binding, &mut acc.$build(v).1);
204 ($acc:expr, $binding:tt) => {
207 macro_rules! let_construct {
208 ($arg:pat, $exp:expr) => {{
215 nestings_with!(let_construct, $binding, acc.build_droppy(v));
223 let_construct!($binding, &acc.build_droppy(v).1);
226 let_construct!($binding, &mut acc.build_droppy(v).1);
231 let acc = DropAccountant::new();
233 println!(" --- matching cases ---");
235 // Ensure that let and let else have the same behaviour
236 acc.new_list("let _");
238 acc.new_list("let else _");
239 let_else!(&acc, 0, _, build_droppy);
240 acc.assert_equality_last_two_lists();
243 // Ensure that let and let else have the same behaviour
244 acc.new_list("let _v");
246 acc.new_list("let else _v");
247 let_else!(&acc, 0, _v, build_droppy);
248 acc.assert_equality_last_two_lists();
253 println!(" --- mismatching cases ---");
255 acc.new_list("let else _ mismatch");
256 let_else!(&acc, 1, DroppyEnum::Some(_, _), build_droppy_enum_none);
257 acc.new_list("let else _v mismatch");
258 let_else!(&acc, 1, DroppyEnum::Some(_, _v), build_droppy_enum_none);
260 // This ensures that we always drop before visiting the else case
261 acc.assert_all_equal_to("drop,else");
263 acc.new_list("let else 0 mismatch");
264 let_else!(&acc, 1, 0, build_droppy);
265 acc.new_list("let else 0 mismatch");
266 let_else!(&acc, 1, 0, build_droppy);
268 // This ensures that we always drop before visiting the else case
269 acc.assert_all_equal_to("drop,else");