Skip to content

add tests for pattern binding drop order edge cases #142193

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

Merged
merged 1 commit into from
Jun 9, 2025
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
109 changes: 109 additions & 0 deletions tests/ui/drop/or-pattern-drop-order.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
//@ run-pass
//! Test drop order for different ways of declaring pattern bindings involving or-patterns.
//! Currently, it's inconsistent between language constructs (#142163).

use std::cell::RefCell;
use std::ops::Drop;

// For more informative failures, we collect drops in a `Vec` before checking their order.
struct DropOrder(RefCell<Vec<u32>>);
struct LogDrop<'o>(&'o DropOrder, u32);

impl<'o> Drop for LogDrop<'o> {
fn drop(&mut self) {
self.0.0.borrow_mut().push(self.1);
}
}

#[track_caller]
fn assert_drop_order(expected_drops: impl IntoIterator<Item = u32>, f: impl Fn(&DropOrder)) {
let order = DropOrder(RefCell::new(Vec::new()));
f(&order);
let order = order.0.into_inner();
let correct_order: Vec<u32> = expected_drops.into_iter().collect();
assert_eq!(order, correct_order);
}

#[expect(unused_variables, unused_assignments, irrefutable_let_patterns)]
fn main() {
// When bindings are declared with `let pat;`, they're visited in left-to-right order, using the
// order given by the first occurrence of each variable. They're later dropped in reverse.
assert_drop_order(1..=3, |o| {
// Drops are right-to-left: `z`, `y`, `x`.
let (x, Ok(y) | Err(y), z);
// Assignment order doesn't matter.
z = LogDrop(o, 1);
y = LogDrop(o, 2);
x = LogDrop(o, 3);
});
assert_drop_order(1..=2, |o| {
// The first or-pattern alternative determines the bindings' drop order: `y`, `x`.
let ((true, x, y) | (false, y, x));
x = LogDrop(o, 2);
y = LogDrop(o, 1);
});

// When bindings are declared with `let pat = expr;`, bindings within or-patterns are seen last,
// thus they're dropped first.
assert_drop_order(1..=3, |o| {
// Drops are right-to-left, treating `y` as rightmost: `y`, `z`, `x`.
let (x, Ok(y) | Err(y), z) = (LogDrop(o, 3), Ok(LogDrop(o, 1)), LogDrop(o, 2));
});
assert_drop_order(1..=2, |o| {
// The first or-pattern alternative determines the bindings' drop order: `y`, `x`.
let ((true, x, y) | (false, y, x)) = (true, LogDrop(o, 2), LogDrop(o, 1));
});
assert_drop_order(1..=2, |o| {
// That drop order is used regardless of which or-pattern alternative matches: `y`, `x`.
let ((true, x, y) | (false, y, x)) = (false, LogDrop(o, 1), LogDrop(o, 2));
});

// `match` treats or-patterns as last like `let pat = expr;`, but also determines drop order
// using the order of the bindings in the *last* or-pattern alternative.
assert_drop_order(1..=3, |o| {
// Drops are right-to-left, treating `y` as rightmost: `y`, `z`, `x`.
match (LogDrop(o, 3), Ok(LogDrop(o, 1)), LogDrop(o, 2)) { (x, Ok(y) | Err(y), z) => {} }
});
assert_drop_order(1..=2, |o| {
// The last or-pattern alternative determines the bindings' drop order: `x`, `y`.
match (true, LogDrop(o, 1), LogDrop(o, 2)) { (true, x, y) | (false, y, x) => {} }
});
assert_drop_order(1..=2, |o| {
// That drop order is used regardless of which or-pattern alternative matches: `x`, `y`.
match (false, LogDrop(o, 2), LogDrop(o, 1)) { (true, x, y) | (false, y, x) => {} }
});

// Function params are visited one-by-one, and the order of bindings within a param's pattern is
// the same as `let pat = expr`;
assert_drop_order(1..=3, |o| {
// Among separate params, the drop order is right-to-left: `z`, `y`, `x`.
(|x, (Ok(y) | Err(y)), z| {})(LogDrop(o, 3), Ok(LogDrop(o, 2)), LogDrop(o, 1));
});
assert_drop_order(1..=3, |o| {
// Within a param's pattern, or-patterns are treated as rightmost: `y`, `z`, `x`.
(|(x, Ok(y) | Err(y), z)| {})((LogDrop(o, 3), Ok(LogDrop(o, 1)), LogDrop(o, 2)));
});
assert_drop_order(1..=2, |o| {
// The first or-pattern alternative determines the bindings' drop order: `y`, `x`.
(|((true, x, y) | (false, y, x))| {})((true, LogDrop(o, 2), LogDrop(o, 1)));
});

// `if let` and `let`-`else` see bindings in the same order as `let pat = expr;`.
// Vars in or-patterns are seen last (dropped first), and the first alternative's order is used.
assert_drop_order(1..=3, |o| {
if let (x, Ok(y) | Err(y), z) = (LogDrop(o, 3), Ok(LogDrop(o, 1)), LogDrop(o, 2)) {}
});
assert_drop_order(1..=3, |o| {
let (x, Ok(y) | Err(y), z) = (LogDrop(o, 3), Ok(LogDrop(o, 1)), LogDrop(o, 2)) else {
unreachable!();
};
});
assert_drop_order(1..=2, |o| {
if let (true, x, y) | (false, y, x) = (true, LogDrop(o, 2), LogDrop(o, 1)) {}
});
assert_drop_order(1..=2, |o| {
let ((true, x, y) | (false, y, x)) = (true, LogDrop(o, 2), LogDrop(o, 1)) else {
unreachable!();
};
});
}
31 changes: 31 additions & 0 deletions tests/ui/dropck/eager-by-ref-binding-for-guards.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//! The drop check is currently more permissive when match arms have guards, due to eagerly creating
//! by-ref bindings for the guard (#142057).

struct Struct<T>(T);
impl<T> Drop for Struct<T> {
fn drop(&mut self) {}
}

fn main() {
// This is an error: `short1` is dead before `long1` is dropped.
match (Struct(&&0), 1) {
(mut long1, ref short1) => long1.0 = &short1,
//~^ ERROR `short1` does not live long enough
}
// This is OK: `short2`'s storage is live until after `long2`'s drop runs.
match (Struct(&&0), 1) {
(mut long2, ref short2) if true => long2.0 = &short2,
_ => unreachable!(),
}
// This depends on the binding modes of the final or-pattern alternatives (see #142163):
let res: &Result<u8, &u8> = &Ok(1);
match (Struct(&&0), res) {
(mut long3, Ok(short3) | &Err(short3)) if true => long3.0 = &short3,
//~^ ERROR `short3` does not live long enough
_ => unreachable!(),
}
match (Struct(&&0), res) {
(mut long4, &Err(short4) | Ok(short4)) if true => long4.0 = &short4,
_ => unreachable!(),
}
}
28 changes: 28 additions & 0 deletions tests/ui/dropck/eager-by-ref-binding-for-guards.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
error[E0597]: `short1` does not live long enough
--> $DIR/eager-by-ref-binding-for-guards.rs:12:46
|
LL | (mut long1, ref short1) => long1.0 = &short1,
| ---------- ^^^^^^-
| | | |
| | | `short1` dropped here while still borrowed
| | | borrow might be used here, when `long1` is dropped and runs the `Drop` code for type `Struct`
| | borrowed value does not live long enough
| binding `short1` declared here
|
= note: values in a scope are dropped in the opposite order they are defined

error[E0597]: `short3` does not live long enough
--> $DIR/eager-by-ref-binding-for-guards.rs:23:69
|
LL | (mut long3, Ok(short3) | &Err(short3)) if true => long3.0 = &short3,
| ------ ^^^^^^-
| | | |
| | | `short3` dropped here while still borrowed
| | | borrow might be used here, when `long3` is dropped and runs the `Drop` code for type `Struct`
| binding `short3` declared here borrowed value does not live long enough
|
= note: values in a scope are dropped in the opposite order they are defined

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0597`.
30 changes: 30 additions & 0 deletions tests/ui/dropck/let-else-more-permissive.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//! The drop check is currently more permissive when `let` statements have an `else` block, due to
//! scheduling drops for bindings' storage before pattern-matching (#142056).

struct Struct<T>(T);
impl<T> Drop for Struct<T> {
fn drop(&mut self) {}
}

fn main() {
{
// This is an error: `short1` is dead before `long1` is dropped.
let (mut long1, short1) = (Struct(&0), 1);
long1.0 = &short1;
//~^ ERROR `short1` does not live long enough
}
{
// This is OK: `short2`'s storage is live until after `long2`'s drop runs.
#[expect(irrefutable_let_patterns)]
let (mut long2, short2) = (Struct(&0), 1) else { unreachable!() };
long2.0 = &short2;
}
{
// Sanity check: `short3`'s drop is significant; it's dropped before `long3`:
let tmp = Box::new(0);
#[expect(irrefutable_let_patterns)]
let (mut long3, short3) = (Struct(&tmp), Box::new(1)) else { unreachable!() };
long3.0 = &short3;
//~^ ERROR `short3` does not live long enough
}
}
35 changes: 35 additions & 0 deletions tests/ui/dropck/let-else-more-permissive.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
error[E0597]: `short1` does not live long enough
--> $DIR/let-else-more-permissive.rs:13:19
|
LL | let (mut long1, short1) = (Struct(&0), 1);
| ------ binding `short1` declared here
LL | long1.0 = &short1;
| ^^^^^^^ borrowed value does not live long enough
LL |
LL | }
| -
| |
| `short1` dropped here while still borrowed
| borrow might be used here, when `long1` is dropped and runs the `Drop` code for type `Struct`
|
= note: values in a scope are dropped in the opposite order they are defined

error[E0597]: `short3` does not live long enough
--> $DIR/let-else-more-permissive.rs:27:19
|
LL | let (mut long3, short3) = (Struct(&tmp), Box::new(1)) else { unreachable!() };
| ------ binding `short3` declared here
LL | long3.0 = &short3;
| ^^^^^^^ borrowed value does not live long enough
LL |
LL | }
| -
| |
| `short3` dropped here while still borrowed
| borrow might be used here, when `long3` is dropped and runs the `Drop` code for type `Struct`
|
= note: values in a scope are dropped in the opposite order they are defined

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0597`.
Loading