Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add let else drop order tests #99291

Merged
merged 1 commit into from
Sep 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
270 changes: 270 additions & 0 deletions src/test/ui/let-else/let-else-drop-order.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 51 additions & 0 deletions src/test/ui/let-else/let-else-drop-order.run.stdout
Original file line number Diff line number Diff line change
@@ -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 |