diff --git a/compiler/rustc_hir_analysis/src/check/region.rs b/compiler/rustc_hir_analysis/src/check/region.rs index 43e6f5fe10471..d5f42201e0157 100644 --- a/compiler/rustc_hir_analysis/src/check/region.rs +++ b/compiler/rustc_hir_analysis/src/check/region.rs @@ -8,7 +8,7 @@ use std::mem; -use rustc_data_structures::fx::FxHashMap; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_hir as hir; use rustc_hir::def::{CtorKind, DefKind, Res}; use rustc_hir::def_id::DefId; @@ -38,6 +38,14 @@ struct ScopeResolutionVisitor<'tcx> { cx: Context, + /// Tracks [extending] blocks and `if` expressions. This is used in performing lifetime + /// extension on block tail expressions: if we've already extended the temporary scopes of + /// extending borrows within a block's tail when checking a parent `let` statement or block, we + /// don't want to re-extend them to be shorter when checking the block itself. + /// + /// [extending]: https://doc.rust-lang.org/nightly/reference/destructors.html#extending-based-on-expressions + extended_blocks: FxHashSet, + extended_super_lets: FxHashMap>, } @@ -160,6 +168,17 @@ fn resolve_block<'tcx>( .backwards_incompatible_scope .insert(local_id, Scope { local_id, data: ScopeData::Node }); } + // If we haven't already checked for temporary lifetime extension due to a parent `let` + // statement initializer or block, do so. This, e.g., allows `temp()` in `{ &temp() }` + // to outlive the block even when the block itself is not in a `let` statement + // initializer. The same rules for `let` are used here, so non-extending borrows are + // unaffected: `{ f(&temp()) }` drops `temp()` at the end of the block in Rust 2024. + if !visitor.extended_blocks.contains(&blk.hir_id.local_id) { + let blk_result_scope = prev_cx.parent.and_then(|blk_parent| { + visitor.scope_tree.default_temporary_scope(blk_parent).0 + }); + record_rvalue_scope_if_borrow_expr(visitor, tail_expr, blk_result_scope); + } resolve_expr(visitor, tail_expr, terminating); } } @@ -354,6 +373,22 @@ fn resolve_expr<'tcx>( hir::ExprKind::If(cond, then, Some(otherwise)) => { let expr_cx = visitor.cx; + // If we haven't already checked for temporary lifetime extension due to a parent `let` + // statement initializer or block, do so. This, e.g., allows `format!("temp")` in + // `if cond { &format!("temp") } else { "" }` to outlive the block even when the `if` + // expression itself is not in a `let` statement initializer. The same rules for `let` + // are used here, so non-extending borrows are unaffected: + // `if cond { f(&format!("temp")) } else { "" }` + // drops `format!("temp")` at the end of the block in all editions. + // This also allows `super let` in the then and else blocks to have the scope of the + // result of the block, as expected. + if !visitor.extended_blocks.contains(&expr.hir_id.local_id) { + let blk_result_scope = expr_cx + .parent + .and_then(|if_parent| visitor.scope_tree.default_temporary_scope(if_parent).0); + record_rvalue_scope_if_borrow_expr(visitor, then, blk_result_scope); + record_rvalue_scope_if_borrow_expr(visitor, otherwise, blk_result_scope); + } let data = if expr.span.at_least_rust_2024() { ScopeData::IfThenRescope } else { @@ -369,6 +404,13 @@ fn resolve_expr<'tcx>( hir::ExprKind::If(cond, then, None) => { let expr_cx = visitor.cx; + // As above, perform lifetime extension on the consequent block. + if !visitor.extended_blocks.contains(&expr.hir_id.local_id) { + let blk_result_scope = expr_cx + .parent + .and_then(|if_parent| visitor.scope_tree.default_temporary_scope(if_parent).0); + record_rvalue_scope_if_borrow_expr(visitor, then, blk_result_scope); + } let data = if expr.span.at_least_rust_2024() { ScopeData::IfThenRescope } else { @@ -473,7 +515,7 @@ fn resolve_local<'tcx>( if let Some(scope) = visitor.extended_super_lets.remove(&pat.unwrap().hir_id.local_id) => { - // This expression was lifetime-extended by a parent let binding. E.g. + // This expression was lifetime-extended by a parent let binding or block. E.g. // // let a = { // super let b = temp(); @@ -489,7 +531,8 @@ fn resolve_local<'tcx>( true } LetKind::Super => { - // This `super let` is not subject to lifetime extension from a parent let binding. E.g. + // This `super let` is not subject to lifetime extension from a parent let binding or + // block. E.g. // // identity({ super let x = temp(); &x }).method(); // @@ -500,10 +543,16 @@ fn resolve_local<'tcx>( if let Some(inner_scope) = visitor.cx.var_parent { (visitor.cx.var_parent, _) = visitor.scope_tree.default_temporary_scope(inner_scope) } - // Don't lifetime-extend child `super let`s or block tail expressions' temporaries in - // the initializer when this `super let` is not itself extended by a parent `let` - // (#145784). Block tail expressions are temporary drop scopes in Editions 2024 and - // later, their temps shouldn't outlive the block in e.g. `f(pin!({ &temp() }))`. + // Don't apply lifetime extension to the initializer of non-extended `super let`. + // This helps ensure that `{ super let x = &$EXPR; x }` is equivalent to `&$EXPR` in + // non-extending contexts: we want to avoid extending temporaries in `$EXPR` past what + // their temporary scopes would otherwise be (#145784). + // Currently, this shouldn't do anything. The discrepancy in #145784 was due to + // `{ super let x = &{ &temp() }; x }` extending `temp()` to outlive its immediately + // enclosing temporary scope (the block tail expression in Rust 2024), whereas in a + // non-extending context, `&{ &temp() }` would drop `temp()` at the end of the block. + // This particular quirk no longer exists: lifetime extension rules are applied to block + // tail expressions, so `temp()` is extended past the block in the latter case as well. false } }; @@ -602,87 +651,92 @@ fn resolve_local<'tcx>( | PatKind::Err(_) => false, } } +} - /// If `expr` matches the `E&` grammar, then records an extended rvalue scope as appropriate: - /// - /// ```text - /// E& = & ET - /// | StructName { ..., f: E&, ... } - /// | [ ..., E&, ... ] - /// | ( ..., E&, ... ) - /// | {...; E&} - /// | { super let ... = E&; ... } - /// | if _ { ...; E& } else { ...; E& } - /// | match _ { ..., _ => E&, ... } - /// | box E& - /// | E& as ... - /// | ( E& ) - /// ``` - fn record_rvalue_scope_if_borrow_expr<'tcx>( - visitor: &mut ScopeResolutionVisitor<'tcx>, - expr: &hir::Expr<'_>, - blk_id: Option, - ) { - match expr.kind { - hir::ExprKind::AddrOf(_, _, subexpr) => { - record_rvalue_scope_if_borrow_expr(visitor, subexpr, blk_id); - visitor.scope_tree.record_rvalue_candidate( - subexpr.hir_id, - RvalueCandidate { target: subexpr.hir_id.local_id, lifetime: blk_id }, - ); - } - hir::ExprKind::Struct(_, fields, _) => { - for field in fields { - record_rvalue_scope_if_borrow_expr(visitor, field.expr, blk_id); - } +/// If `expr` matches the `E&` grammar, then records an extended rvalue scope as appropriate: +/// +/// ```text +/// E& = & ET +/// | StructName { ..., f: E&, ... } +/// | StructName(..., E&, ...) +/// | [ ..., E&, ... ] +/// | ( ..., E&, ... ) +/// | {...; E&} +/// | { super let ... = E&; ... } +/// | if _ { ...; E& } else { ...; E& } +/// | match _ { ..., _ => E&, ... } +/// | E& as ... +/// ``` +fn record_rvalue_scope_if_borrow_expr<'tcx>( + visitor: &mut ScopeResolutionVisitor<'tcx>, + expr: &hir::Expr<'_>, + blk_id: Option, +) { + match expr.kind { + hir::ExprKind::AddrOf(_, _, subexpr) => { + record_rvalue_scope_if_borrow_expr(visitor, subexpr, blk_id); + visitor.scope_tree.record_rvalue_candidate( + subexpr.hir_id, + RvalueCandidate { target: subexpr.hir_id.local_id, lifetime: blk_id }, + ); + } + hir::ExprKind::Struct(_, fields, _) => { + for field in fields { + record_rvalue_scope_if_borrow_expr(visitor, field.expr, blk_id); } - hir::ExprKind::Array(subexprs) | hir::ExprKind::Tup(subexprs) => { - for subexpr in subexprs { - record_rvalue_scope_if_borrow_expr(visitor, subexpr, blk_id); - } + } + hir::ExprKind::Array(subexprs) | hir::ExprKind::Tup(subexprs) => { + for subexpr in subexprs { + record_rvalue_scope_if_borrow_expr(visitor, subexpr, blk_id); } - hir::ExprKind::Cast(subexpr, _) => { - record_rvalue_scope_if_borrow_expr(visitor, subexpr, blk_id) + } + hir::ExprKind::Cast(subexpr, _) => { + record_rvalue_scope_if_borrow_expr(visitor, subexpr, blk_id) + } + hir::ExprKind::Block(block, _) => { + // Mark the block as extending, so we know its extending borrows and `super let`s have + // extended scopes when checking the block itself. + visitor.extended_blocks.insert(block.hir_id.local_id); + if let Some(subexpr) = block.expr { + record_rvalue_scope_if_borrow_expr(visitor, subexpr, blk_id); } - hir::ExprKind::Block(block, _) => { - if let Some(subexpr) = block.expr { - record_rvalue_scope_if_borrow_expr(visitor, subexpr, blk_id); - } - for stmt in block.stmts { - if let hir::StmtKind::Let(local) = stmt.kind - && let Some(_) = local.super_ - { - visitor.extended_super_lets.insert(local.pat.hir_id.local_id, blk_id); - } + for stmt in block.stmts { + if let hir::StmtKind::Let(local) = stmt.kind + && let Some(_) = local.super_ + { + visitor.extended_super_lets.insert(local.pat.hir_id.local_id, blk_id); } } - hir::ExprKind::If(_, then_block, else_block) => { - record_rvalue_scope_if_borrow_expr(visitor, then_block, blk_id); - if let Some(else_block) = else_block { - record_rvalue_scope_if_borrow_expr(visitor, else_block, blk_id); - } + } + hir::ExprKind::If(_, then_block, else_block) => { + // Mark the expression as extending, so we know its extending borrows and `super let`s + // have extended scopes when checking the `if` expression's blocks. + visitor.extended_blocks.insert(expr.hir_id.local_id); + record_rvalue_scope_if_borrow_expr(visitor, then_block, blk_id); + if let Some(else_block) = else_block { + record_rvalue_scope_if_borrow_expr(visitor, else_block, blk_id); } - hir::ExprKind::Match(_, arms, _) => { - for arm in arms { - record_rvalue_scope_if_borrow_expr(visitor, arm.body, blk_id); - } + } + hir::ExprKind::Match(_, arms, _) => { + for arm in arms { + record_rvalue_scope_if_borrow_expr(visitor, arm.body, blk_id); } - hir::ExprKind::Call(func, args) => { - // Recurse into tuple constructors, such as `Some(&temp())`. - // - // That way, there is no difference between `Some(..)` and `Some { 0: .. }`, - // even though the former is syntactically a function call. - if let hir::ExprKind::Path(path) = &func.kind - && let hir::QPath::Resolved(None, path) = path - && let Res::SelfCtor(_) | Res::Def(DefKind::Ctor(_, CtorKind::Fn), _) = path.res - { - for arg in args { - record_rvalue_scope_if_borrow_expr(visitor, arg, blk_id); - } + } + hir::ExprKind::Call(func, args) => { + // Recurse into tuple constructors, such as `Some(&temp())`. + // + // That way, there is no difference between `Some(..)` and `Some { 0: .. }`, + // even though the former is syntactically a function call. + if let hir::ExprKind::Path(path) = &func.kind + && let hir::QPath::Resolved(None, path) = path + && let Res::SelfCtor(_) | Res::Def(DefKind::Ctor(_, CtorKind::Fn), _) = path.res + { + for arg in args { + record_rvalue_scope_if_borrow_expr(visitor, arg, blk_id); } } - _ => {} } + _ => {} } } @@ -823,6 +877,7 @@ pub(crate) fn region_scope_tree(tcx: TyCtxt<'_>, def_id: DefId) -> &ScopeTree { tcx, scope_tree: ScopeTree::default(), cx: Context { parent: None, var_parent: None }, + extended_blocks: Default::default(), extended_super_lets: Default::default(), }; diff --git a/tests/mir-opt/pre-codegen/slice_index.slice_index_range.PreCodegen.after.panic-unwind.mir b/tests/mir-opt/pre-codegen/slice_index.slice_index_range.PreCodegen.after.panic-unwind.mir index d4b86b9633acd..044e9e6ad5575 100644 --- a/tests/mir-opt/pre-codegen/slice_index.slice_index_range.PreCodegen.after.panic-unwind.mir +++ b/tests/mir-opt/pre-codegen/slice_index.slice_index_range.PreCodegen.after.panic-unwind.mir @@ -66,8 +66,8 @@ fn slice_index_range(_1: &[u32], _2: std::ops::Range) -> &[u32] { StorageDead(_10); StorageDead(_9); _0 = &(*_12); - StorageDead(_12); StorageDead(_8); + StorageDead(_12); return; } diff --git a/tests/ui/borrowck/format-args-temporary-scopes.e2021.stderr b/tests/ui/borrowck/format-args-temporary-scopes.e2021.stderr new file mode 100644 index 0000000000000..a9ebf7b8f0130 --- /dev/null +++ b/tests/ui/borrowck/format-args-temporary-scopes.e2021.stderr @@ -0,0 +1,16 @@ +error[E0716]: temporary value dropped while borrowed + --> $DIR/format-args-temporary-scopes.rs:33:64 + | +LL | println!("{:?}{:?}", (), if true { std::convert::identity(&format!("")) } else { "" }); + | ----------------------------------^^^^^^^^^^^--------------- + | | | | + | | | 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 + = note: this error originates in the macro `format` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0716`. diff --git a/tests/ui/borrowck/format-args-temporary-scopes.e2024.stderr b/tests/ui/borrowck/format-args-temporary-scopes.e2024.stderr index 506fc6e0965f7..40a3b1174ec85 100644 --- a/tests/ui/borrowck/format-args-temporary-scopes.e2024.stderr +++ b/tests/ui/borrowck/format-args-temporary-scopes.e2024.stderr @@ -1,27 +1,40 @@ error[E0716]: temporary value dropped while borrowed - --> $DIR/format-args-temporary-scopes.rs:13:25 + --> $DIR/format-args-temporary-scopes.rs:17:48 | -LL | println!("{:?}", { &temp() }); - | ---^^^^^--- - | | | | - | | | temporary value is freed at the end of this statement - | | creates a temporary value which is freed while still in use +LL | println!("{:?}", { std::convert::identity(&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 + --> $DIR/format-args-temporary-scopes.rs:24:52 | -LL | println!("{:?}{:?}", { &temp() }, ()); - | ---^^^^^--- - | | | | - | | | temporary value is freed at the end of this statement - | | creates a temporary value which is freed while still in use +LL | println!("{:?}{:?}", { std::convert::identity(&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 +error[E0716]: temporary value dropped while borrowed + --> $DIR/format-args-temporary-scopes.rs:33:64 + | +LL | println!("{:?}{:?}", (), if true { std::convert::identity(&format!("")) } else { "" }); + | ------------------------^^^^^^^^^^^- + | | | | + | | | 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 + = note: this error originates in the macro `format` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 3 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 index 2641058accb31..985f44e87fb08 100644 --- a/tests/ui/borrowck/format-args-temporary-scopes.rs +++ b/tests/ui/borrowck/format-args-temporary-scopes.rs @@ -1,21 +1,35 @@ //! 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()`. + // In Rust 2024, block tail expressions are temporary scopes, but temporary lifetime extension + // rules apply: `&temp()` here is an extending borrow expression, so `temp()`'s lifetime is + // extended past the block. println!("{:?}", { &temp() }); + + // Arguments to function calls aren't extending expressions, so `temp()` is dropped at the end + // of the block in Rust 2024. + println!("{:?}", { std::convert::identity(&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. + // In Rust 1.89, `format_args!` had different lifetime extension behavior dependent on how many + // formatting arguments it had (#145880), so let's test that too. println!("{:?}{:?}", { &temp() }, ()); + + println!("{:?}{:?}", { std::convert::identity(&temp()) }, ()); //[e2024]~^ ERROR: temporary value dropped while borrowed [E0716] + + // In real-world projects, this typically appeared in `if` expressions with a `&str` in one + // branch and a reference to a `String` temporary in the other. Since the consequent and `else` + // blocks of `if` expressions are temporary scopes in all editions, this affects Rust 2021 and + // earlier as well. + println!("{:?}{:?}", (), if true { &format!("") } else { "" }); + + println!("{:?}{:?}", (), if true { std::convert::identity(&format!("")) } else { "" }); + //~^ ERROR: temporary value dropped while borrowed [E0716] } diff --git a/tests/ui/borrowck/super-let-in-if-block.rs b/tests/ui/borrowck/super-let-in-if-block.rs new file mode 100644 index 0000000000000..e7fe634b8e23b --- /dev/null +++ b/tests/ui/borrowck/super-let-in-if-block.rs @@ -0,0 +1,27 @@ +//! Test that `super let` bindings in `if` expressions' blocks have the same scope as the result +//! of the block. +//@ check-pass +#![feature(super_let)] + +fn main() { + // For `super let` in an extending `if`, the binding `temp` should live in the scope of the + // outer `let` statement. + let x = if true { + super let temp = (); + &temp + } else { + super let temp = (); + &temp + }; + x; + + // For `super let` in non-extending `if`, the binding `temp` should live in the temporary scope + // the `if` expression is in. + std::convert::identity(if true { + super let temp = (); + &temp + } else { + super let temp = (); + &temp + }); +} diff --git a/tests/ui/drop/destructuring-assignments.rs b/tests/ui/drop/destructuring-assignments.rs new file mode 100644 index 0000000000000..c90dba1855055 --- /dev/null +++ b/tests/ui/drop/destructuring-assignments.rs @@ -0,0 +1,68 @@ +// Test drop order for destructuring assignments against +// other expressions they should be consistent with. +// +// See: +// +// - https://github.com/rust-lang/rust/pull/145838 +// +// Original author: TC +// Date: 2025-08-30 +//@ edition: 2024 +//@ run-pass + +#![allow(unused_must_use)] + +fn main() { + assert_drop_order(1..=3, |e| { + &({ &raw const *&e.log(2) }, drop(e.log(1))); + drop(e.log(3)); + }); + assert_drop_order(1..=3, |e| { + { let _x; _x = &({ &raw const *&e.log(2) }, drop(e.log(1))); } + drop(e.log(3)); + }); + assert_drop_order(1..=3, |e| { + _ = &({ &raw const *&e.log(2) }, drop(e.log(1))); + drop(e.log(3)); + }); + assert_drop_order(1..=3, |e| { + { let _ = &({ &raw const *&e.log(2) }, drop(e.log(1))); } + drop(e.log(3)); + }); + assert_drop_order(1..=3, |e| { + let _x; let _y; + (_x, _y) = ({ &raw const *&e.log(2) }, drop(e.log(1))); + drop(e.log(3)); + }); +} + +// # Test scaffolding... + +use core::cell::RefCell; + +struct DropOrder(RefCell>); +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, + f: impl Fn(&DropOrder), +) { + let order = DropOrder(RefCell::new(Vec::new())); + f(&order); + let order = order.0.into_inner(); + let expected: Vec = ex.into_iter().collect(); + assert_eq!(order, expected); +} diff --git a/tests/ui/drop/lint-tail-expr-drop-order-borrowck.rs b/tests/ui/drop/lint-tail-expr-drop-order-borrowck.rs index e8368b0a369d8..1ff3a78aa86b0 100644 --- a/tests/ui/drop/lint-tail-expr-drop-order-borrowck.rs +++ b/tests/ui/drop/lint-tail-expr-drop-order-borrowck.rs @@ -28,7 +28,7 @@ fn should_lint_with_unsafe_block() { fn should_lint_with_big_block() { fn f(_: T) {} f({ - &mut || 0 + std::convert::identity(&mut || 0) //~^ ERROR: relative drop order changing //~| WARN: this changes meaning in Rust 2024 //~| NOTE: this temporary value will be dropped at the end of the block @@ -40,7 +40,7 @@ fn should_lint_with_big_block() { fn another_temp_that_is_copy_in_arg() { fn f() {} fn g(_: &()) {} - g({ &f() }); + g({ std::convert::identity(&f()) }); //~^ ERROR: relative drop order changing //~| WARN: this changes meaning in Rust 2024 //~| NOTE: this temporary value will be dropped at the end of the block @@ -48,4 +48,9 @@ fn another_temp_that_is_copy_in_arg() { //~| NOTE: for more information, see } +fn do_not_lint_extending_borrow() { + fn f(_: T) {} + f({ &mut || 0 }); +} + fn main() {} diff --git a/tests/ui/drop/lint-tail-expr-drop-order-borrowck.stderr b/tests/ui/drop/lint-tail-expr-drop-order-borrowck.stderr index 2eeda8ac387fd..106080007784e 100644 --- a/tests/ui/drop/lint-tail-expr-drop-order-borrowck.stderr +++ b/tests/ui/drop/lint-tail-expr-drop-order-borrowck.stderr @@ -26,22 +26,22 @@ LL | f(unsafe { String::new().as_str() }.len()); = note: for more information, see error: relative drop order changing in Rust 2024 - --> $DIR/lint-tail-expr-drop-order-borrowck.rs:31:9 + --> $DIR/lint-tail-expr-drop-order-borrowck.rs:31:32 | -LL | &mut || 0 - | ^^^^^^^^^ - | | - | this temporary value will be dropped at the end of the block +LL | std::convert::identity(&mut || 0) + | -----------------------^^^^^^^^^- + | | | + | | this temporary value will be dropped at the end of the block | borrow later used here | = warning: this changes meaning in Rust 2024 = note: for more information, see error: relative drop order changing in Rust 2024 - --> $DIR/lint-tail-expr-drop-order-borrowck.rs:43:9 + --> $DIR/lint-tail-expr-drop-order-borrowck.rs:43:32 | -LL | g({ &f() }); - | - ^^^^ this temporary value will be dropped at the end of the block +LL | g({ std::convert::identity(&f()) }); + | - ^^^^ this temporary value will be dropped at the end of the block | | | borrow later used by call | diff --git a/tests/ui/drop/super-let-tail-expr-drop-order.rs b/tests/ui/drop/super-let-tail-expr-drop-order.rs index 5b2ecfbb3200c..3504bde887067 100644 --- a/tests/ui/drop/super-let-tail-expr-drop-order.rs +++ b/tests/ui/drop/super-let-tail-expr-drop-order.rs @@ -1,18 +1,19 @@ -//! 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: +//! Test for #145784. This tests three things: //! -//! - 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. +//! - In Rust 2024, temporary lifetime extension applies to block tail expressions, such that +//! extending borrows and `super let`s in block tails are extended to outlive the result 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. +//! - Since `super let`'s initializer has the same temporary scope as the variable scope of its +//! bindings, this means that lifetime extension can effectively see through `super let`. //! -//! For more information on extending expressions, see -//! https://doc.rust-lang.org/reference/destructors.html#extending-based-on-expressions +//! - In particular, the argument to `pin!` is an extending expression, and the argument of an +//! extending `pin!` has an extended temporary scope. The lifetime of the argument, as well those +//! of extending borrows and `super lets` within it, should match the result of the `pin!`, +//! regardless of whether it itself is extended by a parent expression. +//! +//! For more information on temporary lifetime extension, see +//! https://doc.rust-lang.org/nightly/reference/destructors.html#temporary-lifetime-extension //! //! 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 @@ -30,14 +31,14 @@ use std::pin::pin; fn f(_: LogDrop<'_>, x: T) -> T { x } fn main() { - // Test block arguments to `pin!` in non-extending expressions. + // Test block arguments to non-extending `pin!`. // 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!`. + // In Rust 2024, extending borrows within block tail expressions have extended lifetimes to + // outlive result of the block, so the end result is the same in this case. + // By nesting two `pin!` calls, this ensures extending borrows in the inner `pin!` outlive the + // outer `pin!`. assert_drop_order(1..=3, |o| { - #[cfg(e2021)] ( pin!(( pin!({ &o.log(3) as *const LogDrop<'_> }), @@ -45,19 +46,10 @@ fn main() { )), 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 _ = { @@ -68,17 +60,6 @@ fn main() { }, 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, @@ -97,36 +78,18 @@ fn main() { // 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)); + // 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 a non-extending // expression within the initializer expression.