rust/tests/ui/drop/drop-order-comparisons.rs
Travis Cross c27a6bf042 Add extensive set of drop order tests
On lang, we've recently been discussing the drop order with respect to
`let` chains apropos of how we shortened temporary lifetimes in Rust
2024 and how we may shorten them further in the future.

Here we add an extensive set of tests that demonstrate the drop order
in the cases that interest us.
2025-01-23 22:48:03 +00:00

576 lines
16 KiB
Rust

// This tests various aspects of the drop order with a focus on:
//
// - The lifetime of temporaries with the `if let` construct (and with
// various similar constructs) and how these lifetimes were shortened
// for `if let` in Rust 2024.
//
// - The shortening of the lifetimes of temporaries in tail
// expressions in Rust 2024.
//
// - The behavior of `let` chains and how this behavior compares to
// nested `if let` expressions and chained `let .. else` statements.
//
// In the tests below, `Events` tracks a sequence of numbered events.
// Calling `e.mark(..)` logs a numbered event immediately. Calling
// `e.ok(..)` or `e.err(..)` returns an `Ok(_)` or `Err(_)` value,
// respectively, and logs the numbered event when that value is
// dropped. Calling `e.assert()` verifies that the correct number of
// events were logged and that they were logged in the correct order.
//@ revisions: e2021 e2024
//@ [e2021] edition: 2021
//@ [e2021] run-rustfix
//@ [e2021] rustfix-only-machine-applicable
//@ [e2024] edition: 2024
//@ run-pass
#![feature(let_chains)]
#![cfg_attr(e2021, warn(rust_2024_compatibility))]
fn t_bindings() {
let e = Events::new();
_ = {
e.mark(1);
let _v = e.ok(8);
let _v = e.ok(2).is_ok();
let _ = e.ok(3);
let Ok(_) = e.ok(4) else { unreachable!() };
let Ok(_) = e.ok(5).as_ref() else { unreachable!() };
let _v = e.ok(7);
e.mark(6);
};
e.assert(8);
}
fn t_tuples() {
let e = Events::new();
_ = (e.ok(1), e.ok(4).is_ok(), e.ok(2), e.ok(3).is_ok());
e.assert(4);
}
fn t_arrays() {
let e = Events::new();
trait Tr {}
impl<T> Tr for T {}
fn b<'a, T: 'a>(x: T) -> Box<dyn Tr + 'a> {
Box::new(x)
}
_ = [b(e.ok(1)), b(e.ok(4).is_ok()), b(e.ok(2)), b(e.ok(3).is_ok())];
e.assert(4);
}
fn t_fncalls() {
let e = Events::new();
let f = |_, _, _, _| {};
_ = f(e.ok(2), e.ok(4).is_ok(), e.ok(1), e.ok(3).is_ok());
e.assert(4);
}
#[cfg(e2021)]
#[rustfmt::skip]
fn t_tailexpr_bindings() {
let e = Events::new();
_ = ({
let _v = e.ok(2);
let _v = e.ok(1);
e.ok(5).is_ok()
//[e2021]~^ WARN relative drop order changing in Rust 2024
//[e2021]~| WARN this changes meaning in Rust 2024
}, e.mark(3), e.ok(4));
e.assert(5);
}
#[cfg(e2024)]
#[rustfmt::skip]
fn t_tailexpr_bindings() {
let e = Events::new();
_ = ({
let _v = e.ok(3);
let _v = e.ok(2);
e.ok(1).is_ok()
}, e.mark(4), e.ok(5));
e.assert(5);
}
#[cfg(e2021)]
#[rustfmt::skip]
fn t_tailexpr_tuples() {
let e = Events::new();
_ = ({
(e.ok(2), e.ok(6).is_ok(), e.ok(3), e.ok(5).is_ok())
//[e2021]~^ WARN relative drop order changing in Rust 2024
//[e2021]~| WARN this changes meaning in Rust 2024
//[e2021]~| WARN relative drop order changing in Rust 2024
//[e2021]~| WARN this changes meaning in Rust 2024
}, e.mark(1), e.ok(4));
e.assert(6);
}
#[cfg(e2024)]
#[rustfmt::skip]
fn t_tailexpr_tuples() {
let e = Events::new();
_ = ({
(e.ok(4), e.ok(2).is_ok(), e.ok(5), e.ok(1).is_ok())
}, e.mark(3), e.ok(6));
e.assert(6);
}
#[cfg(e2021)]
#[rustfmt::skip]
fn t_if_let_then() {
let e = Events::new();
_ = (if let Ok(_) = e.ok(4).as_ref() {
//[e2021]~^ WARN if let` assigns a shorter lifetime since Edition 2024
//[e2021]~| WARN this changes meaning in Rust 2024
e.mark(1);
}, e.mark(2), e.ok(3));
e.assert(4);
}
#[cfg(e2024)]
#[rustfmt::skip]
fn t_if_let_then() {
let e = Events::new();
_ = (if let Ok(_) = e.ok(2).as_ref() {
e.mark(1);
}, e.mark(3), e.ok(4));
e.assert(4);
}
#[cfg(e2021)]
#[rustfmt::skip]
fn t_if_let_else() {
let e = Events::new();
_ = (if let Ok(_) = e.err(4).as_ref() {} else {
//[e2021]~^ WARN if let` assigns a shorter lifetime since Edition 2024
//[e2021]~| WARN this changes meaning in Rust 2024
e.mark(1);
}, e.mark(2), e.ok(3));
e.assert(4);
}
#[cfg(e2024)]
#[rustfmt::skip]
fn t_if_let_else() {
let e = Events::new();
_ = (if let Ok(_) = e.err(1).as_ref() {} else {
e.mark(2);
}, e.mark(3), e.ok(4));
e.assert(4);
}
#[rustfmt::skip]
fn t_match_then() {
let e = Events::new();
_ = (match e.ok(4).as_ref() {
Ok(_) => e.mark(1),
_ => unreachable!(),
}, e.mark(2), e.ok(3));
e.assert(4);
}
#[rustfmt::skip]
fn t_match_else() {
let e = Events::new();
_ = (match e.err(4).as_ref() {
Ok(_) => unreachable!(),
_ => e.mark(1),
}, e.mark(2), e.ok(3));
e.assert(4);
}
#[rustfmt::skip]
fn t_let_else_then() {
let e = Events::new();
_ = ('top: {
'chain: {
let Ok(_) = e.ok(1).as_ref() else { break 'chain };
// The "then" branch:
e.mark(2);
break 'top;
}
// The "else" branch:
unreachable!()
}, e.mark(3), e.ok(4));
e.assert(4);
}
#[rustfmt::skip]
fn t_let_else_else() {
let e = Events::new();
_ = ('top: {
'chain: {
let Ok(_) = e.err(1).as_ref() else { break 'chain };
// The "then" branch:
unreachable!();
#[allow(unreachable_code)]
break 'top;
}
// The "else" branch:
e.mark(2);
}, e.mark(3), e.ok(4));
e.assert(4);
}
#[cfg(e2021)]
#[rustfmt::skip]
fn t_if_let_then_tailexpr() {
let e = Events::new();
_ = ({
if let Ok(_) = e.ok(4).as_ref() {
//[e2021]~^ WARN relative drop order changing in Rust 2024
//[e2021]~| WARN this changes meaning in Rust 2024
e.mark(1);
}
}, e.mark(2), e.ok(3));
e.assert(4);
}
#[cfg(e2024)]
#[rustfmt::skip]
fn t_if_let_then_tailexpr() {
let e = Events::new();
_ = ({
if let Ok(_) = e.ok(2).as_ref() {
e.mark(1);
}
}, e.mark(3), e.ok(4));
e.assert(4);
}
#[cfg(e2021)]
#[rustfmt::skip]
fn t_if_let_else_tailexpr() {
let e = Events::new();
_ = ({
if let Ok(_) = e.err(4).as_ref() {} else {
//[e2021]~^ WARN relative drop order changing in Rust 2024
//[e2021]~| WARN this changes meaning in Rust 2024
//[e2021]~| WARN if let` assigns a shorter lifetime since Edition 2024
//[e2021]~| WARN this changes meaning in Rust 2024
e.mark(1);
}
}, e.mark(2), e.ok(3));
e.assert(4);
}
#[cfg(e2024)]
#[rustfmt::skip]
fn t_if_let_else_tailexpr() {
let e = Events::new();
_ = ({
if let Ok(_) = e.err(1).as_ref() {} else {
e.mark(2);
}
}, e.mark(3), e.ok(4));
e.assert(4);
}
#[rustfmt::skip]
fn t_if_let_nested_then() {
let e = Events::new();
_ = {
// The unusual formatting, here and below, is to make the
// comparison with `let` chains more direct.
if e.ok(1).is_ok() {
if let true = e.ok(9).is_ok() {
if let Ok(_v) = e.ok(8) {
if let Ok(_) = e.ok(7) {
if let Ok(_) = e.ok(6).as_ref() {
if e.ok(2).is_ok() {
if let Ok(_v) = e.ok(5) {
if let Ok(_) = e.ok(4).as_ref() {
e.mark(3);
}}}}}}}}
};
e.assert(9);
}
#[rustfmt::skip]
fn t_let_else_chained_then() {
let e = Events::new();
_ = 'top: {
'chain: {
if e.ok(1).is_ok() {} else { break 'chain };
let true = e.ok(2).is_ok() else { break 'chain };
let Ok(_v) = e.ok(9) else { break 'chain };
let Ok(_) = e.ok(3) else { break 'chain };
let Ok(_) = e.ok(4).as_ref() else { break 'chain };
if e.ok(5).is_ok() {} else { break 'chain };
let Ok(_v) = e.ok(8) else { break 'chain };
let Ok(_) = e.ok(6).as_ref() else { break 'chain };
// The "then" branch:
e.mark(7);
break 'top;
}
// The "else" branch:
unreachable!()
};
e.assert(9);
}
#[cfg(e2021)]
#[rustfmt::skip]
fn t_if_let_chains_then() {
let e = Events::new();
_ = if e.ok(1).is_ok()
&& let true = e.ok(9).is_ok()
&& let Ok(_v) = e.ok(5)
&& let Ok(_) = e.ok(8)
&& let Ok(_) = e.ok(7).as_ref()
&& e.ok(2).is_ok()
&& let Ok(_v) = e.ok(4)
&& let Ok(_) = e.ok(6).as_ref() {
e.mark(3);
};
e.assert(9);
}
#[cfg(e2024)]
#[rustfmt::skip]
fn t_if_let_chains_then() {
let e = Events::new();
_ = if e.ok(1).is_ok()
&& let true = e.ok(9).is_ok()
&& let Ok(_v) = e.ok(8)
&& let Ok(_) = e.ok(7)
&& let Ok(_) = e.ok(6).as_ref()
&& e.ok(2).is_ok()
&& let Ok(_v) = e.ok(5)
&& let Ok(_) = e.ok(4).as_ref() {
e.mark(3);
};
e.assert(9);
}
#[cfg(e2021)]
#[rustfmt::skip]
fn t_if_let_nested_else() {
let e = Events::new();
_ = if e.err(1).is_ok() {} else {
if let true = e.err(9).is_ok() {} else {
//[e2021]~^ WARN if let` assigns a shorter lifetime since Edition 2024
//[e2021]~| WARN this changes meaning in Rust 2024
if let Ok(_v) = e.err(8) {} else {
//[e2021]~^ WARN if let` assigns a shorter lifetime since Edition 2024
//[e2021]~| WARN this changes meaning in Rust 2024
if let Ok(_) = e.err(7) {} else {
//[e2021]~^ WARN if let` assigns a shorter lifetime since Edition 2024
//[e2021]~| WARN this changes meaning in Rust 2024
if let Ok(_) = e.err(6).as_ref() {} else {
//[e2021]~^ WARN if let` assigns a shorter lifetime since Edition 2024
//[e2021]~| WARN this changes meaning in Rust 2024
if e.err(2).is_ok() {} else {
if let Ok(_v) = e.err(5) {} else {
//[e2021]~^ WARN if let` assigns a shorter lifetime since Edition 2024
//[e2021]~| WARN this changes meaning in Rust 2024
if let Ok(_) = e.err(4) {} else {
//[e2021]~^ WARN if let` assigns a shorter lifetime since Edition 2024
//[e2021]~| WARN this changes meaning in Rust 2024
e.mark(3);
}}}}}}}};
e.assert(9);
}
#[cfg(e2024)]
#[rustfmt::skip]
fn t_if_let_nested_else() {
let e = Events::new();
_ = if e.err(1).is_ok() {} else {
if let true = e.err(2).is_ok() {} else {
if let Ok(_v) = e.err(3) {} else {
if let Ok(_) = e.err(4) {} else {
if let Ok(_) = e.err(5).as_ref() {} else {
if e.err(6).is_ok() {} else {
if let Ok(_v) = e.err(7) {} else {
if let Ok(_) = e.err(8) {} else {
e.mark(9);
}}}}}}}};
e.assert(9);
}
#[cfg(e2021)]
#[rustfmt::skip]
fn t_if_let_nested_then_else() {
let e = Events::new();
_ = if e.ok(1).is_ok() {
if let true = e.ok(9).is_ok() {
if let Ok(_v) = e.ok(8) {
if let Ok(_) = e.ok(7) {
if let Ok(_) = e.ok(6).as_ref() {
if e.ok(2).is_ok() {
if let Ok(_v) = e.ok(5) {
if let Ok(_) = e.err(4).as_ref() {} else {
//[e2021]~^ WARN if let` assigns a shorter lifetime since Edition 2024
//[e2021]~| WARN this changes meaning in Rust 2024
e.mark(3);
}}}}}}}};
e.assert(9);
}
#[cfg(e2024)]
#[rustfmt::skip]
fn t_if_let_nested_then_else() {
let e = Events::new();
_ = if e.ok(1).is_ok() {
if let true = e.ok(9).is_ok() {
if let Ok(_v) = e.ok(8) {
if let Ok(_) = e.ok(7) {
if let Ok(_) = e.ok(6).as_ref() {
if e.ok(2).is_ok() {
if let Ok(_v) = e.ok(5) {
if let Ok(_) = e.err(3).as_ref() {} else {
e.mark(4);
}}}}}}}};
e.assert(9);
}
#[rustfmt::skip]
fn t_let_else_chained_then_else() {
let e = Events::new();
_ = 'top: {
'chain: {
if e.ok(1).is_ok() {} else { break 'chain };
let true = e.ok(2).is_ok() else { break 'chain };
let Ok(_v) = e.ok(8) else { break 'chain };
let Ok(_) = e.ok(3) else { break 'chain };
let Ok(_) = e.ok(4).as_ref() else { break 'chain };
if e.ok(5).is_ok() {} else { break 'chain };
let Ok(_v) = e.ok(7) else { break 'chain };
let Ok(_) = e.err(6).as_ref() else { break 'chain };
// The "then" branch:
unreachable!();
#[allow(unreachable_code)]
break 'top;
}
// The "else" branch:
e.mark(9);
};
e.assert(9);
}
#[cfg(e2021)]
#[rustfmt::skip]
fn t_if_let_chains_then_else() {
let e = Events::new();
_ = if e.ok(1).is_ok()
&& let true = e.ok(9).is_ok()
&& let Ok(_v) = e.ok(4)
&& let Ok(_) = e.ok(8)
&& let Ok(_) = e.ok(7).as_ref()
&& e.ok(2).is_ok()
&& let Ok(_v) = e.ok(3)
&& let Ok(_) = e.err(6) {} else {
e.mark(5);
};
e.assert(9);
}
#[cfg(e2024)]
#[rustfmt::skip]
fn t_if_let_chains_then_else() {
let e = Events::new();
_ = if e.ok(1).is_ok()
&& let true = e.ok(8).is_ok()
&& let Ok(_v) = e.ok(7)
&& let Ok(_) = e.ok(6)
&& let Ok(_) = e.ok(5).as_ref()
&& e.ok(2).is_ok()
&& let Ok(_v) = e.ok(4)
&& let Ok(_) = e.err(3) {} else {
e.mark(9);
};
e.assert(9);
}
fn main() {
t_bindings();
t_tuples();
t_arrays();
t_fncalls();
t_tailexpr_bindings();
t_tailexpr_tuples();
t_if_let_then();
t_if_let_else();
t_match_then();
t_match_else();
t_let_else_then();
t_let_else_else();
t_if_let_then_tailexpr();
t_if_let_else_tailexpr();
t_if_let_nested_then();
t_let_else_chained_then();
t_if_let_chains_then();
t_if_let_nested_else();
t_if_let_nested_then_else();
t_let_else_chained_then_else();
t_if_let_chains_then_else();
}
// # Test scaffolding
use core::cell::RefCell;
use std::collections::HashSet;
/// A buffer to track the order of events.
///
/// First, numbered events are logged into this buffer.
///
/// Then, `assert` is called to verify that the correct number of
/// events were logged, and that they were logged in the expected
/// order.
struct Events(RefCell<Option<Vec<u64>>>);
impl Events {
const fn new() -> Self {
Self(RefCell::new(Some(Vec::new())))
}
#[track_caller]
fn assert(&self, max: u64) {
let buf = &self.0;
let v1 = buf.borrow().as_ref().unwrap().clone();
let mut v2 = buf.borrow().as_ref().unwrap().clone();
*buf.borrow_mut() = None;
v2.sort();
let uniq_len = v2.iter().collect::<HashSet<_>>().len();
// Check that the sequence is sorted.
assert_eq!(v1, v2);
// Check that there are no duplicates.
assert_eq!(v2.len(), uniq_len);
// Check that the length is the expected one.
assert_eq!(max, uniq_len as u64);
// Check that the last marker is the expected one.
assert_eq!(v2.last().unwrap(), &max);
}
/// Return an `Ok` value that logs its drop.
fn ok(&self, m: u64) -> Result<LogDrop<'_>, LogDrop<'_>> {
Ok(LogDrop(self, m))
}
/// Return an `Err` value that logs its drop.
fn err(&self, m: u64) -> Result<LogDrop, LogDrop> {
Err(LogDrop(self, m))
}
/// Log an event.
fn mark(&self, m: u64) {
self.0.borrow_mut().as_mut().unwrap().push(m);
}
}
impl Drop for Events {
fn drop(&mut self) {
if self.0.borrow().is_some() {
panic!("failed to call `Events::assert()`");
}
}
}
/// A type that logs its drop events.
struct LogDrop<'b>(&'b Events, u64);
impl<'b> Drop for LogDrop<'b> {
fn drop(&mut self) {
self.0.mark(self.1);
}
}