Rollup merge of #99291 - est31:let_else_tests, r=joshtriplett
Add let else drop order tests Add a systematic matrix based test that checks temporary drop order in various settings, `let-else-drop-order.rs`, as requested [here](https://github.com/rust-lang/rust/pull/93628#issuecomment-1055738523). The drop order of let and let else is supposed to be the and in order to ensure this, the test checks that this holds for a number of cases. The test also ensures that we drop the temporaries of the condition before executing the else block. cc #87335 tracking issue for `let else`
This commit is contained in:
commit
0e6747fb25
2 changed files with 321 additions and 0 deletions
270
src/test/ui/let-else/let-else-drop-order.rs
Normal file
270
src/test/ui/let-else/let-else-drop-order.rs
Normal file
|
@ -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");
|
||||
}
|
51
src/test/ui/let-else/let-else-drop-order.run.stdout
Normal file
51
src/test/ui/let-else/let-else-drop-order.run.stdout
Normal file
|
@ -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 |
|
Loading…
Add table
Add a link
Reference in a new issue