diff options
| author | Stuart Cook <Zalathar@users.noreply.github.com> | 2025-09-17 14:56:45 +1000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-09-17 14:56:45 +1000 |
| commit | c2e8264b342b8e3547bfdbb6d4a7c11972affd94 (patch) | |
| tree | 32e246f7c825baf6a66890b939c4173f74e94706 /tests | |
| parent | 6ad98750e0d2261aa123806c69a133b2fbd18d88 (diff) | |
| parent | d5b5a4a0e11d92a963578d15a9f693e1538caf3f (diff) | |
| download | rust-c2e8264b342b8e3547bfdbb6d4a7c11972affd94.tar.gz rust-c2e8264b342b8e3547bfdbb6d4a7c11972affd94.zip | |
Rollup merge of #145838 - dianne:non-extending-super-let, r=jackh726,traviscross
don't apply temporary lifetime extension rules to non-extended `super let`
Reference PR: rust-lang/reference#1980
This changes the semantics for `super let` (and macros implemented in terms of it, such as `pin!`, `format_args!`, `write!`, and `println!`) as suggested by ````@theemathas```` in https://github.com/rust-lang/rust/issues/145784#issuecomment-3218658335, making `super let` initializers only count as [extending expressions](https://doc.rust-lang.org/nightly/reference/destructors.html#extending-based-on-expressions) when the `super let` itself is within an extending block. Since `super let` initializers aren't temporary drop scopes, their temporaries outside of inner temporary scopes are effectively always extended, even when not in extending positions; this only affects two cases as far as I can tell:
- Block tail expressions in Rust 2024. This PR makes `f(pin!({ &temp() }))` drop `temp()` at the end of the block in Rust 2024, whereas previously it would live until after the call to `f` because syntactically the `temp()` was in an extending position as a result of `super let` in `pin!`'s expansion.
- `super let` nested within a non-extended `super let` is no longer extended. i.e. a normal `let` is required to treat `super let`s as extending (in which case nested `super let`s will also be extending).
Closes rust-lang/rust#145784
This is a breaking change. Both static and dynamic semantics are affected. The most likely breakage is for programs to stop compiling, but it's technically possible for drop order to silently change as well (as in rust-lang/rust#145784). Since this affects stable macros, it probably would need a crater run.
Nominating for discussion alongside rust-lang/rust#145784: ````@rustbot```` label +I-lang-nominated +I-libs-api-nominated
Tracking issue for `super let`: rust-lang/rust#139076
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/ui/borrowck/format-args-temporary-scopes.e2024.stderr | 27 | ||||
| -rw-r--r-- | tests/ui/borrowck/format-args-temporary-scopes.rs | 21 | ||||
| -rw-r--r-- | tests/ui/drop/super-let-tail-expr-drop-order.rs | 192 |
3 files changed, 240 insertions, 0 deletions
diff --git a/tests/ui/borrowck/format-args-temporary-scopes.e2024.stderr b/tests/ui/borrowck/format-args-temporary-scopes.e2024.stderr new file mode 100644 index 00000000000..506fc6e0965 --- /dev/null +++ b/tests/ui/borrowck/format-args-temporary-scopes.e2024.stderr @@ -0,0 +1,27 @@ +error[E0716]: temporary value dropped while borrowed + --> $DIR/format-args-temporary-scopes.rs:13:25 + | +LL | println!("{:?}", { &temp() }); + | ---^^^^^--- + | | | | + | | | temporary value is freed at the end of this statement + | | creates a temporary value which is freed while still in use + | borrow later used here + | + = note: consider using a `let` binding to create a longer lived value + +error[E0716]: temporary value dropped while borrowed + --> $DIR/format-args-temporary-scopes.rs:19:29 + | +LL | println!("{:?}{:?}", { &temp() }, ()); + | ---^^^^^--- + | | | | + | | | temporary value is freed at the end of this statement + | | creates a temporary value which is freed while still in use + | borrow later used here + | + = note: consider using a `let` binding to create a longer lived value + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0716`. diff --git a/tests/ui/borrowck/format-args-temporary-scopes.rs b/tests/ui/borrowck/format-args-temporary-scopes.rs new file mode 100644 index 00000000000..2641058accb --- /dev/null +++ b/tests/ui/borrowck/format-args-temporary-scopes.rs @@ -0,0 +1,21 @@ +//! Test for #145784 as it relates to format arguments: arguments to macros such as `println!` +//! should obey normal temporary scoping rules. +//@ revisions: e2021 e2024 +//@ [e2021] check-pass +//@ [e2021] edition: 2021 +//@ [e2024] edition: 2024 + +fn temp() {} + +fn main() { + // In Rust 2024, block tail expressions are temporary scopes, so the result of `temp()` is + // dropped after evaluating `&temp()`. + println!("{:?}", { &temp() }); + //[e2024]~^ ERROR: temporary value dropped while borrowed [E0716] + + // In Rust 1.89, `format_args!` extended the lifetime of all extending expressions in its + // arguments when provided with two or more arguments. This caused the result of `temp()` to + // outlive the result of the block, making this compile. + println!("{:?}{:?}", { &temp() }, ()); + //[e2024]~^ ERROR: temporary value dropped while borrowed [E0716] +} diff --git a/tests/ui/drop/super-let-tail-expr-drop-order.rs b/tests/ui/drop/super-let-tail-expr-drop-order.rs new file mode 100644 index 00000000000..5b2ecfbb320 --- /dev/null +++ b/tests/ui/drop/super-let-tail-expr-drop-order.rs @@ -0,0 +1,192 @@ +//! Test for #145784: the argument to `pin!` should be treated as an extending expression if and +//! only if the whole `pin!` invocation is an extending expression. Likewise, since `pin!` is +//! implemented in terms of `super let`, test the same for `super let` initializers. Since the +//! argument to `pin!` and the initializer of `super let` are not temporary drop scopes, this only +//! affects lifetimes in two cases: +//! +//! - Block tail expressions in Rust 2024, which are both extending expressions and temporary drop +//! scopes; treating them as extending expressions within a non-extending `pin!` resulted in borrow +//! expression operands living past the end of the block. +//! +//! - Nested `super let` statements, which can have their binding and temporary lifetimes extended +//! when the block they're in is an extending expression. +//! +//! For more information on extending expressions, see +//! https://doc.rust-lang.org/reference/destructors.html#extending-based-on-expressions +//! +//! For tests that `super let` initializers aren't temporary drop scopes, and tests for +//! lifetime-extended `super let`, see tests/ui/borrowck/super-let-lifetime-and-drop.rs +//@ run-pass +//@ revisions: e2021 e2024 +//@ [e2021] edition: 2021 +//@ [e2024] edition: 2024 + +#![feature(super_let)] +#![allow(unused_braces)] + +use std::cell::RefCell; +use std::pin::pin; + +fn f<T>(_: LogDrop<'_>, x: T) -> T { x } + +fn main() { + // Test block arguments to `pin!` in non-extending expressions. + // In Rust 2021, block tail expressions aren't temporary drop scopes, so their temporaries + // should outlive the `pin!` invocation. + // In Rust 2024, block tail expressions are temporary drop scopes, so their temporaries should + // be dropped after evaluating the tail expression within the `pin!` invocation. + // By nesting two `pin!` calls, this ensures non-extended `pin!` doesn't extend an inner `pin!`. + assert_drop_order(1..=3, |o| { + #[cfg(e2021)] + ( + pin!(( + pin!({ &o.log(3) as *const LogDrop<'_> }), + drop(o.log(1)), + )), + drop(o.log(2)), + ); + #[cfg(e2024)] + ( + pin!(( + pin!({ &o.log(1) as *const LogDrop<'_> }), + drop(o.log(2)), + )), + drop(o.log(3)), + ); + }); + + // The same holds for `super let` initializers in non-extending expressions. + assert_drop_order(1..=4, |o| { + #[cfg(e2021)] + ( + { + super let _ = { + super let _ = { &o.log(4) as *const LogDrop<'_> }; + drop(o.log(1)) + }; + drop(o.log(2)) + }, + drop(o.log(3)), + ); + #[cfg(e2024)] + ( + { + super let _ = { + super let _ = { &o.log(1) as *const LogDrop<'_> }; + drop(o.log(2)) + }; + drop(o.log(3)) + }, + drop(o.log(4)), + ); + }); + + // Within an extending expression, the argument to `pin!` is also an extending expression, + // allowing borrow operands in block tail expressions to have extended lifetimes. + assert_drop_order(1..=2, |o| { + let _ = pin!({ &o.log(2) as *const LogDrop<'_> }); + drop(o.log(1)); + }); + + // The same holds for `super let` initializers in extending expressions. + assert_drop_order(1..=2, |o| { + let _ = { super let _ = { &o.log(2) as *const LogDrop<'_> }; }; + drop(o.log(1)); + }); + + // We have extending borrow expressions within an extending block + // expression (within an extending borrow expression) within a + // non-extending expresion within the initializer expression. + #[cfg(e2021)] + { + // These two should be the same. + assert_drop_order(1..=3, |e| { + let _v = f(e.log(1), &{ &raw const *&e.log(2) }); + drop(e.log(3)); + }); + assert_drop_order(1..=3, |e| { + let _v = f(e.log(1), { + super let v = &{ &raw const *&e.log(2) }; + v + }); + drop(e.log(3)); + }); + } + #[cfg(e2024)] + { + // These two should be the same. + assert_drop_order(1..=3, |e| { + let _v = f(e.log(2), &{ &raw const *&e.log(1) }); + drop(e.log(3)); + }); + assert_drop_order(1..=3, |e| { + let _v = f(e.log(2), { + super let v = &{ &raw const *&e.log(1) }; + v + }); + drop(e.log(3)); + }); + } + + // We have extending borrow expressions within a non-extending + // expression within the initializer expression. + // + // These two should be the same. + assert_drop_order(1..=3, |e| { + let _v = f(e.log(1), &&raw const *&e.log(2)); + drop(e.log(3)); + }); + assert_drop_order(1..=3, |e| { + let _v = f(e.log(1), { + super let v = &&raw const *&e.log(2); + v + }); + drop(e.log(3)); + }); + + // We have extending borrow expressions within an extending block + // expression (within an extending borrow expression) within the + // initializer expression. + // + // These two should be the same. + assert_drop_order(1..=2, |e| { + let _v = &{ &raw const *&e.log(2) }; + drop(e.log(1)); + }); + assert_drop_order(1..=2, |e| { + let _v = { + super let v = &{ &raw const *&e.log(2) }; + v + }; + drop(e.log(1)); + }); +} + +// # Test scaffolding... + +struct DropOrder(RefCell<Vec<u64>>); +struct LogDrop<'o>(&'o DropOrder, u64); + +impl DropOrder { + fn log(&self, n: u64) -> LogDrop<'_> { + LogDrop(self, n) + } +} + +impl<'o> Drop for LogDrop<'o> { + fn drop(&mut self) { + self.0.0.borrow_mut().push(self.1); + } +} + +#[track_caller] +fn assert_drop_order( + ex: impl IntoIterator<Item = u64>, + f: impl Fn(&DropOrder), +) { + let order = DropOrder(RefCell::new(Vec::new())); + f(&order); + let order = order.0.into_inner(); + let expected: Vec<u64> = ex.into_iter().collect(); + assert_eq!(order, expected); +} |
