//@ 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>>>); impl DropAccountant { fn new() -> Self { Self(Default::default()) } fn build_droppy(&self, v: u32) -> Droppy { Droppy(self.clone(), v) } fn build_droppy_enum_none(&self, _v: u32) -> ((), DroppyEnum) { ((), 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!( "| {: Vec { std::iter::once(sl[0].clone()) .chain(sl[1..].chunks(2).map(|c| c.join(","))) .collect::>() } struct Droppy(DropAccountant, T); impl Drop for Droppy { fn drop(&mut self) { self.0.push("drop"); } } #[allow(dead_code)] enum DroppyEnum { Some(DropAccountant, T), None(DropAccountant), } impl Drop for DroppyEnum { 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"); }