Skip to content

Commit 0e6747f

Browse files
authored
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](#93628 (comment)). 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`
2 parents b44197a + 2d8460e commit 0e6747f

File tree

2 files changed

+321
-0
lines changed

2 files changed

+321
-0
lines changed
+270
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
// run-pass
2+
// edition:2021
3+
// check-run-results
4+
//
5+
// Drop order tests for let else
6+
//
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.
12+
//
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.
18+
19+
#![feature(let_else)]
20+
#![allow(irrefutable_let_patterns)]
21+
22+
use std::cell::RefCell;
23+
use std::rc::Rc;
24+
25+
#[derive(Clone)]
26+
struct DropAccountant(Rc<RefCell<Vec<Vec<String>>>>);
27+
28+
impl DropAccountant {
29+
fn new() -> Self {
30+
Self(Default::default())
31+
}
32+
fn build_droppy(&self, v: u32) -> Droppy<u32> {
33+
Droppy(self.clone(), v)
34+
}
35+
fn build_droppy_enum_none(&self, _v: u32) -> ((), DroppyEnum<u32>) {
36+
((), DroppyEnum::None(self.clone()))
37+
}
38+
fn new_list(&self, s: impl ToString) {
39+
self.0.borrow_mut().push(vec![s.to_string()]);
40+
}
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);
45+
}
46+
fn print_table(&self) {
47+
println!();
48+
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] = &[
55+
"vanilla",
56+
"&",
57+
"&mut",
58+
"move",
59+
"fn(this)",
60+
"tuple",
61+
"array",
62+
"ref &",
63+
"ref mut &mut",
64+
];
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();
68+
69+
println!(
70+
"| {: <max_len$} | {: <max_len_before$} | {: <max_len_last$} |",
71+
"construct", before_last[0], last[0]
72+
);
73+
println!("| {:-<max_len$} | {:-<max_len_before$} | {:-<max_len_last$} |", "", "", "");
74+
75+
for ((l, l_before), l_last) in
76+
LINES.iter().zip(before_last[1..].iter()).zip(last[1..].iter())
77+
{
78+
println!(
79+
"| {: <max_len$} | {: <max_len_before$} | {: <max_len_last$} |",
80+
l, l_before, l_last,
81+
);
82+
}
83+
}
84+
#[track_caller]
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());
91+
}
92+
}
93+
#[track_caller]
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:?}'");
101+
}
102+
}
103+
}
104+
}
105+
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>>()
110+
}
111+
112+
struct Droppy<T>(DropAccountant, T);
113+
114+
impl<T> Drop for Droppy<T> {
115+
fn drop(&mut self) {
116+
self.0.push("drop");
117+
}
118+
}
119+
120+
#[allow(dead_code)]
121+
enum DroppyEnum<T> {
122+
Some(DropAccountant, T),
123+
None(DropAccountant),
124+
}
125+
126+
impl<T> Drop for DroppyEnum<T> {
127+
fn drop(&mut self) {
128+
match self {
129+
DroppyEnum::Some(acc, _inner) => acc,
130+
DroppyEnum::None(acc) => acc,
131+
}
132+
.push("drop");
133+
}
134+
}
135+
136+
macro_rules! nestings_with {
137+
($construct:ident, $binding:pat, $exp:expr) => {
138+
// vanilla:
139+
$construct!($binding, $exp.1);
140+
141+
// &:
142+
$construct!(&$binding, &$exp.1);
143+
144+
// &mut:
145+
$construct!(&mut $binding, &mut ($exp.1));
146+
147+
{
148+
// move:
149+
let w = $exp;
150+
$construct!(
151+
$binding,
152+
{
153+
let w = w;
154+
w
155+
}
156+
.1
157+
);
158+
}
159+
160+
// fn(this):
161+
$construct!($binding, std::convert::identity($exp).1);
162+
};
163+
}
164+
165+
macro_rules! nestings {
166+
($construct:ident, $binding:pat, $exp:expr) => {
167+
nestings_with!($construct, $binding, $exp);
168+
169+
// tuple:
170+
$construct!(($binding, 77), ($exp.1, 77));
171+
172+
// array:
173+
$construct!([$binding], [$exp.1]);
174+
};
175+
}
176+
177+
macro_rules! let_else {
178+
($acc:expr, $v:expr, $binding:pat, $build:ident) => {
179+
let acc = $acc;
180+
let v = $v;
181+
182+
macro_rules! let_else_construct {
183+
($arg:pat, $exp:expr) => {
184+
loop {
185+
let $arg = $exp else {
186+
acc.push("else");
187+
break;
188+
};
189+
acc.push("body");
190+
break;
191+
}
192+
};
193+
}
194+
nestings!(let_else_construct, $binding, acc.$build(v));
195+
// ref &:
196+
let_else_construct!($binding, &acc.$build(v).1);
197+
198+
// ref mut &mut:
199+
let_else_construct!($binding, &mut acc.$build(v).1);
200+
};
201+
}
202+
203+
macro_rules! let_ {
204+
($acc:expr, $binding:tt) => {
205+
let acc = $acc;
206+
207+
macro_rules! let_construct {
208+
($arg:pat, $exp:expr) => {{
209+
let $arg = $exp;
210+
acc.push("body");
211+
}};
212+
}
213+
let v = 0;
214+
{
215+
nestings_with!(let_construct, $binding, acc.build_droppy(v));
216+
}
217+
acc.push("n/a");
218+
acc.push("n/a");
219+
acc.push("n/a");
220+
acc.push("n/a");
221+
222+
// ref &:
223+
let_construct!($binding, &acc.build_droppy(v).1);
224+
225+
// ref mut &mut:
226+
let_construct!($binding, &mut acc.build_droppy(v).1);
227+
};
228+
}
229+
230+
fn main() {
231+
let acc = DropAccountant::new();
232+
233+
println!(" --- matching cases ---");
234+
235+
// Ensure that let and let else have the same behaviour
236+
acc.new_list("let _");
237+
let_!(&acc, _);
238+
acc.new_list("let else _");
239+
let_else!(&acc, 0, _, build_droppy);
240+
acc.assert_equality_last_two_lists();
241+
acc.print_table();
242+
243+
// Ensure that let and let else have the same behaviour
244+
acc.new_list("let _v");
245+
let_!(&acc, _v);
246+
acc.new_list("let else _v");
247+
let_else!(&acc, 0, _v, build_droppy);
248+
acc.assert_equality_last_two_lists();
249+
acc.print_table();
250+
251+
println!();
252+
253+
println!(" --- mismatching cases ---");
254+
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);
259+
acc.print_table();
260+
// This ensures that we always drop before visiting the else case
261+
acc.assert_all_equal_to("drop,else");
262+
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);
267+
acc.print_table();
268+
// This ensures that we always drop before visiting the else case
269+
acc.assert_all_equal_to("drop,else");
270+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
--- matching cases ---
2+
3+
| construct | let _ | let else _ |
4+
| ------------ | --------- | ---------- |
5+
| vanilla | drop,body | drop,body |
6+
| & | body,drop | body,drop |
7+
| &mut | body,drop | body,drop |
8+
| move | drop,body | drop,body |
9+
| fn(this) | drop,body | drop,body |
10+
| tuple | n/a,n/a | drop,body |
11+
| array | n/a,n/a | drop,body |
12+
| ref & | body,drop | body,drop |
13+
| ref mut &mut | body,drop | body,drop |
14+
15+
| construct | let _v | let else _v |
16+
| ------------ | --------- | ----------- |
17+
| vanilla | drop,body | drop,body |
18+
| & | body,drop | body,drop |
19+
| &mut | body,drop | body,drop |
20+
| move | drop,body | drop,body |
21+
| fn(this) | drop,body | drop,body |
22+
| tuple | n/a,n/a | drop,body |
23+
| array | n/a,n/a | drop,body |
24+
| ref & | body,drop | body,drop |
25+
| ref mut &mut | body,drop | body,drop |
26+
27+
--- mismatching cases ---
28+
29+
| construct | let else _ mismatch | let else _v mismatch |
30+
| ------------ | ------------------- | -------------------- |
31+
| vanilla | drop,else | drop,else |
32+
| & | drop,else | drop,else |
33+
| &mut | drop,else | drop,else |
34+
| move | drop,else | drop,else |
35+
| fn(this) | drop,else | drop,else |
36+
| tuple | drop,else | drop,else |
37+
| array | drop,else | drop,else |
38+
| ref & | drop,else | drop,else |
39+
| ref mut &mut | drop,else | drop,else |
40+
41+
| construct | let else 0 mismatch | let else 0 mismatch |
42+
| ------------ | ------------------- | ------------------- |
43+
| vanilla | drop,else | drop,else |
44+
| & | drop,else | drop,else |
45+
| &mut | drop,else | drop,else |
46+
| move | drop,else | drop,else |
47+
| fn(this) | drop,else | drop,else |
48+
| tuple | drop,else | drop,else |
49+
| array | drop,else | drop,else |
50+
| ref & | drop,else | drop,else |
51+
| ref mut &mut | drop,else | drop,else |

0 commit comments

Comments
 (0)