]>
Commit | Line | Data |
---|---|---|
f2b60f7d FG |
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 | ||
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 | } |