Skip to content

Commit 53d339b

Browse files
committed
add tests for pattern binding drop order edge cases
I couldn't find existing tests that for this behavior, so this should make sure it doesn't accidentally change.
1 parent 868bf2d commit 53d339b

File tree

5 files changed

+233
-0
lines changed

5 files changed

+233
-0
lines changed
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
//@ run-pass
2+
//! Test drop order for different ways of declaring pattern bindings involving or-patterns.
3+
//! Currently, it's inconsistent between language constructs (#142163).
4+
5+
use std::cell::RefCell;
6+
use std::ops::Drop;
7+
8+
// For more informative failures, we collect drops in a `Vec` before checking their order.
9+
struct DropOrder(RefCell<Vec<u32>>);
10+
struct LogDrop<'o>(&'o DropOrder, u32);
11+
12+
impl<'o> Drop for LogDrop<'o> {
13+
fn drop(&mut self) {
14+
self.0.0.borrow_mut().push(self.1);
15+
}
16+
}
17+
18+
#[track_caller]
19+
fn assert_drop_order(expected_drops: impl IntoIterator<Item = u32>, f: impl Fn(&DropOrder)) {
20+
let order = DropOrder(RefCell::new(Vec::new()));
21+
f(&order);
22+
let order = order.0.into_inner();
23+
let correct_order: Vec<u32> = expected_drops.into_iter().collect();
24+
assert_eq!(order, correct_order);
25+
}
26+
27+
#[expect(unused_variables, unused_assignments, irrefutable_let_patterns)]
28+
fn main() {
29+
// When bindings are declared with `let pat;`, they're visited in left-to-right order, using the
30+
// order given by the first occurrence of each variable. They're later dropped in reverse.
31+
assert_drop_order(1..=3, |o| {
32+
// Drops are right-to-left: `z`, `y`, `x`.
33+
let (x, Ok(y) | Err(y), z);
34+
// Assignment order doesn't matter.
35+
z = LogDrop(o, 1);
36+
y = LogDrop(o, 2);
37+
x = LogDrop(o, 3);
38+
});
39+
assert_drop_order(1..=2, |o| {
40+
// The first or-pattern alternative determines the bindings' drop order: `y`, `x`.
41+
let ((true, x, y) | (false, y, x));
42+
x = LogDrop(o, 2);
43+
y = LogDrop(o, 1);
44+
});
45+
46+
// When bindings are declared with `let pat = expr;`, bindings within or-patterns are seen last,
47+
// thus they're dropped first.
48+
assert_drop_order(1..=3, |o| {
49+
// Drops are right-to-left, treating `y` as rightmost: `y`, `z`, `x`.
50+
let (x, Ok(y) | Err(y), z) = (LogDrop(o, 3), Ok(LogDrop(o, 1)), LogDrop(o, 2));
51+
});
52+
assert_drop_order(1..=2, |o| {
53+
// The first or-pattern alternative determines the bindings' drop order: `y`, `x`.
54+
let ((true, x, y) | (false, y, x)) = (true, LogDrop(o, 2), LogDrop(o, 1));
55+
});
56+
assert_drop_order(1..=2, |o| {
57+
// That drop order is used regardless of which or-pattern alternative matches: `y`, `x`.
58+
let ((true, x, y) | (false, y, x)) = (false, LogDrop(o, 1), LogDrop(o, 2));
59+
});
60+
61+
// `match` treats or-patterns as last like `let pat = expr;`, but also determines drop order
62+
// using the order of the bindings in the *last* or-pattern alternative.
63+
assert_drop_order(1..=3, |o| {
64+
// Drops are right-to-left, treating `y` as rightmost: `y`, `z`, `x`.
65+
match (LogDrop(o, 3), Ok(LogDrop(o, 1)), LogDrop(o, 2)) { (x, Ok(y) | Err(y), z) => {} }
66+
});
67+
assert_drop_order(1..=2, |o| {
68+
// The last or-pattern alternative determines the bindings' drop order: `x`, `y`.
69+
match (true, LogDrop(o, 1), LogDrop(o, 2)) { (true, x, y) | (false, y, x) => {} }
70+
});
71+
assert_drop_order(1..=2, |o| {
72+
// That drop order is used regardless of which or-pattern alternative matches: `x`, `y`.
73+
match (false, LogDrop(o, 2), LogDrop(o, 1)) { (true, x, y) | (false, y, x) => {} }
74+
});
75+
76+
// Function params are visited one-by-one, and the order of bindings within a param's pattern is
77+
// the same as `let pat = expr`;
78+
assert_drop_order(1..=3, |o| {
79+
// Among separate params, the drop order is right-to-left: `z`, `y`, `x`.
80+
(|x, (Ok(y) | Err(y)), z| {})(LogDrop(o, 3), Ok(LogDrop(o, 2)), LogDrop(o, 1));
81+
});
82+
assert_drop_order(1..=3, |o| {
83+
// Within a param's pattern, or-patterns are treated as rightmost: `y`, `z`, `x`.
84+
(|(x, Ok(y) | Err(y), z)| {})((LogDrop(o, 3), Ok(LogDrop(o, 1)), LogDrop(o, 2)));
85+
});
86+
assert_drop_order(1..=2, |o| {
87+
// The first or-pattern alternative determines the bindings' drop order: `y`, `x`.
88+
(|((true, x, y) | (false, y, x))| {})((true, LogDrop(o, 2), LogDrop(o, 1)));
89+
});
90+
91+
// `if let` and `let`-`else` see bindings in the same order as `let pat = expr;`.
92+
// Vars in or-patterns are seen last (dropped first), and the first alternative's order is used.
93+
assert_drop_order(1..=3, |o| {
94+
if let (x, Ok(y) | Err(y), z) = (LogDrop(o, 3), Ok(LogDrop(o, 1)), LogDrop(o, 2)) {}
95+
});
96+
assert_drop_order(1..=3, |o| {
97+
let (x, Ok(y) | Err(y), z) = (LogDrop(o, 3), Ok(LogDrop(o, 1)), LogDrop(o, 2)) else {
98+
unreachable!();
99+
};
100+
});
101+
assert_drop_order(1..=2, |o| {
102+
if let (true, x, y) | (false, y, x) = (true, LogDrop(o, 2), LogDrop(o, 1)) {}
103+
});
104+
assert_drop_order(1..=2, |o| {
105+
let ((true, x, y) | (false, y, x)) = (true, LogDrop(o, 2), LogDrop(o, 1)) else {
106+
unreachable!();
107+
};
108+
});
109+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//! The drop check is currently more permissive when match arms have guards, due to eagerly creating
2+
//! by-ref bindings for the guard (#142057).
3+
4+
struct Struct<T>(T);
5+
impl<T> Drop for Struct<T> {
6+
fn drop(&mut self) {}
7+
}
8+
9+
fn main() {
10+
// This is an error: `short1` is dead before `long1` is dropped.
11+
match (Struct(&&0), 1) {
12+
(mut long1, ref short1) => long1.0 = &short1,
13+
//~^ ERROR `short1` does not live long enough
14+
}
15+
// This is OK: `short2`'s storage is live until after `long2`'s drop runs.
16+
match (Struct(&&0), 1) {
17+
(mut long2, ref short2) if true => long2.0 = &short2,
18+
_ => unreachable!(),
19+
}
20+
// This depends on the binding modes of the final or-pattern alternatives (see #142163):
21+
let res: &Result<u8, &u8> = &Ok(1);
22+
match (Struct(&&0), res) {
23+
(mut long3, Ok(short3) | &Err(short3)) if true => long3.0 = &short3,
24+
//~^ ERROR `short3` does not live long enough
25+
_ => unreachable!(),
26+
}
27+
match (Struct(&&0), res) {
28+
(mut long4, &Err(short4) | Ok(short4)) if true => long4.0 = &short4,
29+
_ => unreachable!(),
30+
}
31+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
error[E0597]: `short1` does not live long enough
2+
--> $DIR/eager-by-ref-binding-for-guards.rs:12:46
3+
|
4+
LL | (mut long1, ref short1) => long1.0 = &short1,
5+
| ---------- ^^^^^^-
6+
| | | |
7+
| | | `short1` dropped here while still borrowed
8+
| | | borrow might be used here, when `long1` is dropped and runs the `Drop` code for type `Struct`
9+
| | borrowed value does not live long enough
10+
| binding `short1` declared here
11+
|
12+
= note: values in a scope are dropped in the opposite order they are defined
13+
14+
error[E0597]: `short3` does not live long enough
15+
--> $DIR/eager-by-ref-binding-for-guards.rs:23:69
16+
|
17+
LL | (mut long3, Ok(short3) | &Err(short3)) if true => long3.0 = &short3,
18+
| ------ ^^^^^^-
19+
| | | |
20+
| | | `short3` dropped here while still borrowed
21+
| | | borrow might be used here, when `long3` is dropped and runs the `Drop` code for type `Struct`
22+
| binding `short3` declared here borrowed value does not live long enough
23+
|
24+
= note: values in a scope are dropped in the opposite order they are defined
25+
26+
error: aborting due to 2 previous errors
27+
28+
For more information about this error, try `rustc --explain E0597`.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//! The drop check is currently more permissive when `let` statements have an `else` block, due to
2+
//! scheduling drops for bindings' storage before pattern-matching (#142056).
3+
4+
struct Struct<T>(T);
5+
impl<T> Drop for Struct<T> {
6+
fn drop(&mut self) {}
7+
}
8+
9+
fn main() {
10+
{
11+
// This is an error: `short1` is dead before `long1` is dropped.
12+
let (mut long1, short1) = (Struct(&0), 1);
13+
long1.0 = &short1;
14+
//~^ ERROR `short1` does not live long enough
15+
}
16+
{
17+
// This is OK: `short2`'s storage is live until after `long2`'s drop runs.
18+
#[expect(irrefutable_let_patterns)]
19+
let (mut long2, short2) = (Struct(&0), 1) else { unreachable!() };
20+
long2.0 = &short2;
21+
}
22+
{
23+
// Sanity check: `short3`'s drop is significant; it's dropped before `long3`:
24+
let tmp = Box::new(0);
25+
#[expect(irrefutable_let_patterns)]
26+
let (mut long3, short3) = (Struct(&tmp), Box::new(1)) else { unreachable!() };
27+
long3.0 = &short3;
28+
//~^ ERROR `short3` does not live long enough
29+
}
30+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
error[E0597]: `short1` does not live long enough
2+
--> $DIR/let-else-more-permissive.rs:13:19
3+
|
4+
LL | let (mut long1, short1) = (Struct(&0), 1);
5+
| ------ binding `short1` declared here
6+
LL | long1.0 = &short1;
7+
| ^^^^^^^ borrowed value does not live long enough
8+
LL |
9+
LL | }
10+
| -
11+
| |
12+
| `short1` dropped here while still borrowed
13+
| borrow might be used here, when `long1` is dropped and runs the `Drop` code for type `Struct`
14+
|
15+
= note: values in a scope are dropped in the opposite order they are defined
16+
17+
error[E0597]: `short3` does not live long enough
18+
--> $DIR/let-else-more-permissive.rs:27:19
19+
|
20+
LL | let (mut long3, short3) = (Struct(&tmp), Box::new(1)) else { unreachable!() };
21+
| ------ binding `short3` declared here
22+
LL | long3.0 = &short3;
23+
| ^^^^^^^ borrowed value does not live long enough
24+
LL |
25+
LL | }
26+
| -
27+
| |
28+
| `short3` dropped here while still borrowed
29+
| borrow might be used here, when `long3` is dropped and runs the `Drop` code for type `Struct`
30+
|
31+
= note: values in a scope are dropped in the opposite order they are defined
32+
33+
error: aborting due to 2 previous errors
34+
35+
For more information about this error, try `rustc --explain E0597`.

0 commit comments

Comments
 (0)