From f39e2a64348588031786c77bc1e2dab3e6406820 Mon Sep 17 00:00:00 2001 From: dianne Date: Mon, 1 Sep 2025 10:15:37 -0700 Subject: [PATCH 1/6] add drop order test for destructuring assignments Co-authored-by: Travis Cross --- tests/ui/drop/destructuring-assignments.rs | 68 ++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 tests/ui/drop/destructuring-assignments.rs diff --git a/tests/ui/drop/destructuring-assignments.rs b/tests/ui/drop/destructuring-assignments.rs new file mode 100644 index 0000000000000..52d787b2b3ede --- /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(1) }, drop(e.log(2))); + drop(e.log(3)); + }); + assert_drop_order(1..=3, |e| { + { let _x; _x = &({ &raw const *&e.log(1) }, drop(e.log(2))); } + 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); +} From 3eef21ecb7ec51f2848d72af63e6b51991b70688 Mon Sep 17 00:00:00 2001 From: dianne Date: Fri, 19 Sep 2025 14:15:19 -0700 Subject: [PATCH 2/6] additional tests --- .../format-args-temporary-scopes.e2021.stderr | 41 +++++++++++ .../format-args-temporary-scopes.e2024.stderr | 68 ++++++++++++++++++- .../borrowck/format-args-temporary-scopes.rs | 23 ++++++- tests/ui/borrowck/super-let-in-if-block.rs | 29 ++++++++ .../ui/borrowck/super-let-in-if-block.stderr | 30 ++++++++ .../super-let-projection-extension.rs | 25 +++++++ .../super-let-projection-extension.stderr | 30 ++++++++ 7 files changed, 242 insertions(+), 4 deletions(-) create mode 100644 tests/ui/borrowck/format-args-temporary-scopes.e2021.stderr create mode 100644 tests/ui/borrowck/super-let-in-if-block.rs create mode 100644 tests/ui/borrowck/super-let-in-if-block.stderr create mode 100644 tests/ui/borrowck/super-let-projection-extension.rs create mode 100644 tests/ui/borrowck/super-let-projection-extension.stderr 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..1bc0d0785eceb --- /dev/null +++ b/tests/ui/borrowck/format-args-temporary-scopes.e2021.stderr @@ -0,0 +1,41 @@ +error[E0716]: temporary value dropped while borrowed + --> $DIR/format-args-temporary-scopes.rs:40:90 + | +LL | println!("{:?}{:?}", (), match true { true => &"" as &dyn std::fmt::Debug, false => &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:33:41 + | +LL | println!("{:?}{:?}", (), if true { &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[E0716]: temporary value dropped while borrowed + --> $DIR/format-args-temporary-scopes.rs:36: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.e2024.stderr b/tests/ui/borrowck/format-args-temporary-scopes.e2024.stderr index 506fc6e0965f7..ceaeb07af26e4 100644 --- a/tests/ui/borrowck/format-args-temporary-scopes.e2024.stderr +++ b/tests/ui/borrowck/format-args-temporary-scopes.e2024.stderr @@ -1,5 +1,5 @@ error[E0716]: temporary value dropped while borrowed - --> $DIR/format-args-temporary-scopes.rs:13:25 + --> $DIR/format-args-temporary-scopes.rs:12:25 | LL | println!("{:?}", { &temp() }); | ---^^^^^--- @@ -11,7 +11,19 @@ LL | println!("{:?}", { &temp() }); = 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:17:48 + | +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:23:29 | LL | println!("{:?}{:?}", { &temp() }, ()); | ---^^^^^--- @@ -22,6 +34,56 @@ LL | println!("{:?}{:?}", { &temp() }, ()); | = 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:26:52 + | +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:40:90 + | +LL | println!("{:?}{:?}", (), match true { true => &"" as &dyn std::fmt::Debug, false => &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:33:41 + | +LL | println!("{:?}{:?}", (), if true { &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[E0716]: temporary value dropped while borrowed + --> $DIR/format-args-temporary-scopes.rs:36: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 7 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..6f9a7a22e2de8 100644 --- a/tests/ui/borrowck/format-args-temporary-scopes.rs +++ b/tests/ui/borrowck/format-args-temporary-scopes.rs @@ -1,7 +1,6 @@ //! 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 @@ -13,9 +12,31 @@ fn main() { println!("{:?}", { &temp() }); //[e2024]~^ ERROR: temporary value dropped while borrowed [E0716] + // 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. println!("{:?}{:?}", { &temp() }, ()); //[e2024]~^ ERROR: temporary value dropped while borrowed [E0716] + + 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 { "" }); + //~^ ERROR: temporary value dropped while borrowed [E0716] + + println!("{:?}{:?}", (), if true { std::convert::identity(&format!("")) } else { "" }); + //~^ ERROR: temporary value dropped while borrowed [E0716] + + // This has likewise occurred with `match`, affecting all editions. + println!("{:?}{:?}", (), match true { true => &"" as &dyn std::fmt::Debug, false => &temp() }); + //~^ 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..d35075047fdad --- /dev/null +++ b/tests/ui/borrowck/super-let-in-if-block.rs @@ -0,0 +1,29 @@ +//! Test that `super let` bindings in `if` expressions' blocks have the same scope as the result +//! of the block. +#![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. + // TODO: make this not an error + std::convert::identity(if true { + super let temp = (); + &temp + //~^ ERROR `temp` does not live long enough + } else { + super let temp = (); + &temp + //~^ ERROR `temp` does not live long enough + }); +} diff --git a/tests/ui/borrowck/super-let-in-if-block.stderr b/tests/ui/borrowck/super-let-in-if-block.stderr new file mode 100644 index 0000000000000..d2c351b5ea1dc --- /dev/null +++ b/tests/ui/borrowck/super-let-in-if-block.stderr @@ -0,0 +1,30 @@ +error[E0597]: `temp` does not live long enough + --> $DIR/super-let-in-if-block.rs:22:9 + | +LL | std::convert::identity(if true { + | ---------------------- borrow later used by call +LL | super let temp = (); + | ---- binding `temp` declared here +LL | &temp + | ^^^^^ borrowed value does not live long enough +LL | +LL | } else { + | - `temp` dropped here while still borrowed + +error[E0597]: `temp` does not live long enough + --> $DIR/super-let-in-if-block.rs:26:9 + | +LL | std::convert::identity(if true { + | ---------------------- borrow later used by call +... +LL | super let temp = (); + | ---- binding `temp` declared here +LL | &temp + | ^^^^^ borrowed value does not live long enough +LL | +LL | }); + | - `temp` dropped here while still borrowed + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0597`. diff --git a/tests/ui/borrowck/super-let-projection-extension.rs b/tests/ui/borrowck/super-let-projection-extension.rs new file mode 100644 index 0000000000000..5d3a7f98c8c37 --- /dev/null +++ b/tests/ui/borrowck/super-let-projection-extension.rs @@ -0,0 +1,25 @@ +//! Demonstrates a case where `{ super let x = temp(); &x }` =/= `&temp()`, a variant of which is +//! observable on stable Rust via the `pin!` macro: `pin!($EXPR)` and `&mut $EXPR` may use different +//! scopes for their temporaries. + +#![feature(super_let)] + +use std::pin::pin; + +fn temp() {} + +fn main() { + // This is fine, since the temporary is extended to the end of the block: + let a = &*&temp(); + a; + let b = &mut *&mut temp(); + b; + + // The temporary is dropped at the end of the outer `let` initializer: + let c = &*{ super let x = temp(); &x }; + //~^ ERROR `x` does not live long enough + c; + let d = &mut *pin!(temp()); + //~^ ERROR temporary value dropped while borrowed + d; +} diff --git a/tests/ui/borrowck/super-let-projection-extension.stderr b/tests/ui/borrowck/super-let-projection-extension.stderr new file mode 100644 index 0000000000000..329a9f5846513 --- /dev/null +++ b/tests/ui/borrowck/super-let-projection-extension.stderr @@ -0,0 +1,30 @@ +error[E0597]: `x` does not live long enough + --> $DIR/super-let-projection-extension.rs:19:39 + | +LL | let c = &*{ super let x = temp(); &x }; + | - ^^ - `x` dropped here while still borrowed + | | | + | | borrowed value does not live long enough + | binding `x` declared here +LL | +LL | c; + | - borrow later used here + +error[E0716]: temporary value dropped while borrowed + --> $DIR/super-let-projection-extension.rs:22:19 + | +LL | let d = &mut *pin!(temp()); + | ^^^^^^^^^^^^- temporary value is freed at the end of this statement + | | + | creates a temporary value which is freed while still in use +LL | +LL | d; + | - borrow later used here + | + = note: consider using a `let` binding to create a longer lived value + = note: this error originates in the macro `pin` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 2 previous errors + +Some errors have detailed explanations: E0597, E0716. +For more information about an error, try `rustc --explain E0597`. From 07d6b6483efdb291adf35c1aa00a056967c05d52 Mon Sep 17 00:00:00 2001 From: dianne Date: Wed, 5 Nov 2025 05:59:28 -0800 Subject: [PATCH 3/6] merge `RvalueScopes` into `ScopeTree` This removes some unneeded indirection and consolidates the logic for scope resolution. --- .../rustc_hir_analysis/src/check/region.rs | 64 ++++++++++++--- .../rustc_hir_typeck/src/fn_ctxt/_impl.rs | 9 +-- compiler/rustc_hir_typeck/src/lib.rs | 4 - .../rustc_hir_typeck/src/rvalue_scopes.rs | 79 ------------------- compiler/rustc_hir_typeck/src/writeback.rs | 3 - compiler/rustc_middle/src/middle/region.rs | 51 ++++++------ compiler/rustc_middle/src/ty/mod.rs | 2 - compiler/rustc_middle/src/ty/rvalue_scopes.rs | 48 ----------- .../rustc_middle/src/ty/typeck_results.rs | 7 -- .../src/builder/expr/as_rvalue.rs | 4 +- compiler/rustc_mir_build/src/thir/cx/expr.rs | 31 +++----- compiler/rustc_mir_build/src/thir/cx/mod.rs | 4 +- 12 files changed, 99 insertions(+), 207 deletions(-) delete mode 100644 compiler/rustc_hir_typeck/src/rvalue_scopes.rs delete mode 100644 compiler/rustc_middle/src/ty/rvalue_scopes.rs diff --git a/compiler/rustc_hir_analysis/src/check/region.rs b/compiler/rustc_hir_analysis/src/check/region.rs index f99fefcf56ace..550f6fe8195a7 100644 --- a/compiler/rustc_hir_analysis/src/check/region.rs +++ b/compiler/rustc_hir_analysis/src/check/region.rs @@ -516,12 +516,10 @@ fn resolve_local<'tcx>( if let Some(pat) = pat { if is_binding_pat(pat) { - visitor.scope_tree.record_rvalue_candidate( - expr.hir_id, - RvalueCandidate { - target: expr.hir_id.local_id, - lifetime: visitor.cx.var_parent, - }, + record_subexpr_extended_temp_scopes( + &mut visitor.scope_tree, + expr, + visitor.cx.var_parent, ); } } @@ -604,7 +602,7 @@ fn resolve_local<'tcx>( } } - /// If `expr` matches the `E&` grammar, then records an extended rvalue scope as appropriate: + /// If `expr` matches the `E&` grammar, then records an extended temporary scope as appropriate: /// /// ```text /// E& = & ET @@ -627,10 +625,7 @@ fn resolve_local<'tcx>( 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 }, - ); + record_subexpr_extended_temp_scopes(&mut visitor.scope_tree, subexpr, blk_id); } hir::ExprKind::Struct(_, fields, _) => { for field in fields { @@ -687,6 +682,53 @@ fn resolve_local<'tcx>( } } +/// Applied to an expression `expr` if `expr` -- or something owned or partially owned by +/// `expr` -- is going to be indirectly referenced by a variable in a let statement. In that +/// case, the "temporary lifetime" of `expr` is extended to be the block enclosing the `let` +/// statement. +/// +/// More formally, if `expr` matches the grammar `ET`, record the temporary scope of the matching +/// `` as `lifetime`: +/// +/// ```text +/// ET = *ET +/// | ET[...] +/// | ET.f +/// | (ET) +/// | +/// ``` +/// +/// Note: ET is intended to match "rvalues or places based on rvalues". +fn record_subexpr_extended_temp_scopes( + scope_tree: &mut ScopeTree, + mut expr: &hir::Expr<'_>, + lifetime: Option, +) { + debug!(?expr, ?lifetime); + + loop { + // Note: give all the expressions matching `ET` with the + // extended temporary lifetime, not just the innermost rvalue, + // because in MIR building if we must compile e.g., `*rvalue()` + // into a temporary, we request the temporary scope of the + // outer expression. + + scope_tree.record_extended_temp_scope(expr.hir_id.local_id, lifetime); + + match expr.kind { + hir::ExprKind::AddrOf(_, _, subexpr) + | hir::ExprKind::Unary(hir::UnOp::Deref, subexpr) + | hir::ExprKind::Field(subexpr, _) + | hir::ExprKind::Index(subexpr, _, _) => { + expr = subexpr; + } + _ => { + return; + } + } + } +} + impl<'tcx> ScopeResolutionVisitor<'tcx> { /// Records the current parent (if any) as the parent of `child_scope`. fn record_child_scope(&mut self, child_scope: Scope) { diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs index f4e65b42cd423..320de021a81d4 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs @@ -40,7 +40,7 @@ use tracing::{debug, instrument}; use crate::callee::{self, DeferredCallResolution}; use crate::errors::{self, CtorIsPrivate}; use crate::method::{self, MethodCallee}; -use crate::{BreakableCtxt, Diverges, Expectation, FnCtxt, LoweredTy, rvalue_scopes}; +use crate::{BreakableCtxt, Diverges, Expectation, FnCtxt, LoweredTy}; impl<'a, 'tcx> FnCtxt<'a, 'tcx> { /// Produces warning on the given node, if the current point in the @@ -604,13 +604,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.normalize(span, field.ty(self.tcx, args)) } - pub(crate) fn resolve_rvalue_scopes(&self, def_id: DefId) { - let scope_tree = self.tcx.region_scope_tree(def_id); - let rvalue_scopes = { rvalue_scopes::resolve_rvalue_scopes(self, scope_tree, def_id) }; - let mut typeck_results = self.typeck_results.borrow_mut(); - typeck_results.rvalue_scopes = rvalue_scopes; - } - /// Drain all obligations that are stalled on coroutines defined in this body. #[instrument(level = "debug", skip(self))] pub(crate) fn drain_stalled_coroutine_obligations(&self) { diff --git a/compiler/rustc_hir_typeck/src/lib.rs b/compiler/rustc_hir_typeck/src/lib.rs index 99a9566c74cef..aee38801f4b4b 100644 --- a/compiler/rustc_hir_typeck/src/lib.rs +++ b/compiler/rustc_hir_typeck/src/lib.rs @@ -36,7 +36,6 @@ mod op; mod opaque_types; mod pat; mod place_op; -mod rvalue_scopes; mod typeck_root_ctxt; mod upvar; mod writeback; @@ -236,9 +235,6 @@ fn typeck_with_inspect<'tcx>( // because they don't constrain other type variables. fcx.closure_analyze(body); assert!(fcx.deferred_call_resolutions.borrow().is_empty()); - // Before the coroutine analysis, temporary scopes shall be marked to provide more - // precise information on types to be captured. - fcx.resolve_rvalue_scopes(def_id.to_def_id()); for (ty, span, code) in fcx.deferred_sized_obligations.borrow_mut().drain(..) { let ty = fcx.normalize(span, ty); diff --git a/compiler/rustc_hir_typeck/src/rvalue_scopes.rs b/compiler/rustc_hir_typeck/src/rvalue_scopes.rs deleted file mode 100644 index 973dc7141e64d..0000000000000 --- a/compiler/rustc_hir_typeck/src/rvalue_scopes.rs +++ /dev/null @@ -1,79 +0,0 @@ -use hir::Node; -use hir::def_id::DefId; -use rustc_hir as hir; -use rustc_middle::bug; -use rustc_middle::middle::region::{RvalueCandidate, Scope, ScopeTree}; -use rustc_middle::ty::RvalueScopes; -use tracing::debug; - -use super::FnCtxt; - -/// Applied to an expression `expr` if `expr` -- or something owned or partially owned by -/// `expr` -- is going to be indirectly referenced by a variable in a let statement. In that -/// case, the "temporary lifetime" or `expr` is extended to be the block enclosing the `let` -/// statement. -/// -/// More formally, if `expr` matches the grammar `ET`, record the rvalue scope of the matching -/// `` as `blk_id`: -/// -/// ```text -/// ET = *ET -/// | ET[...] -/// | ET.f -/// | (ET) -/// | -/// ``` -/// -/// Note: ET is intended to match "rvalues or places based on rvalues". -fn record_rvalue_scope_rec( - rvalue_scopes: &mut RvalueScopes, - mut expr: &hir::Expr<'_>, - lifetime: Option, -) { - loop { - // Note: give all the expressions matching `ET` with the - // extended temporary lifetime, not just the innermost rvalue, - // because in codegen if we must compile e.g., `*rvalue()` - // into a temporary, we request the temporary scope of the - // outer expression. - - rvalue_scopes.record_rvalue_scope(expr.hir_id.local_id, lifetime); - - match expr.kind { - hir::ExprKind::AddrOf(_, _, subexpr) - | hir::ExprKind::Unary(hir::UnOp::Deref, subexpr) - | hir::ExprKind::Field(subexpr, _) - | hir::ExprKind::Index(subexpr, _, _) => { - expr = subexpr; - } - _ => { - return; - } - } - } -} -fn record_rvalue_scope( - rvalue_scopes: &mut RvalueScopes, - expr: &hir::Expr<'_>, - candidate: &RvalueCandidate, -) { - debug!("resolve_rvalue_scope(expr={expr:?}, candidate={candidate:?})"); - record_rvalue_scope_rec(rvalue_scopes, expr, candidate.lifetime) - // FIXME(@dingxiangfei2009): handle the candidates in the function call arguments -} - -pub(crate) fn resolve_rvalue_scopes<'a, 'tcx>( - fcx: &'a FnCtxt<'a, 'tcx>, - scope_tree: &'a ScopeTree, - def_id: DefId, -) -> RvalueScopes { - let tcx = &fcx.tcx; - let mut rvalue_scopes = RvalueScopes::new(); - debug!("start resolving rvalue scopes, def_id={def_id:?}"); - debug!("rvalue_scope: rvalue_candidates={:?}", scope_tree.rvalue_candidates); - for (&hir_id, candidate) in &scope_tree.rvalue_candidates { - let Node::Expr(expr) = tcx.hir_node(hir_id) else { bug!("hir node does not exist") }; - record_rvalue_scope(&mut rvalue_scopes, expr, candidate); - } - rvalue_scopes -} diff --git a/compiler/rustc_hir_typeck/src/writeback.rs b/compiler/rustc_hir_typeck/src/writeback.rs index 50b6fe1ad5ecb..49f2f3f5de6e0 100644 --- a/compiler/rustc_hir_typeck/src/writeback.rs +++ b/compiler/rustc_hir_typeck/src/writeback.rs @@ -79,9 +79,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { wbcx.visit_offset_of_container_types(); wbcx.visit_potentially_region_dependent_goals(); - wbcx.typeck_results.rvalue_scopes = - mem::take(&mut self.typeck_results.borrow_mut().rvalue_scopes); - let used_trait_imports = mem::take(&mut self.typeck_results.borrow_mut().used_trait_imports); debug!("used_trait_imports({:?}) = {:?}", item_def_id, used_trait_imports); diff --git a/compiler/rustc_middle/src/middle/region.rs b/compiler/rustc_middle/src/middle/region.rs index 3ed8d9a36e101..c0e443b5ead87 100644 --- a/compiler/rustc_middle/src/middle/region.rs +++ b/compiler/rustc_middle/src/middle/region.rs @@ -11,7 +11,7 @@ use std::fmt; use rustc_data_structures::fx::FxIndexMap; use rustc_data_structures::unord::UnordMap; use rustc_hir as hir; -use rustc_hir::{HirId, HirIdMap, Node}; +use rustc_hir::{HirId, ItemLocalMap, Node}; use rustc_macros::{HashStable, TyDecodable, TyEncodable}; use rustc_span::{DUMMY_SP, Span}; use tracing::debug; @@ -221,12 +221,12 @@ pub struct ScopeTree { /// variable is declared. var_map: FxIndexMap, - /// Identifies expressions which, if captured into a temporary, ought to - /// have a temporary whose lifetime extends to the end of the enclosing *block*, - /// and not the enclosing *statement*. Expressions that are not present in this - /// table are not rvalue candidates. The set of rvalue candidates is computed - /// during type check based on a traversal of the AST. - pub rvalue_candidates: HirIdMap, + /// Tracks expressions with extended temporary scopes, based on the syntactic rules for + /// temporary lifetime extension. Further details may be found in + /// `rustc_hir_analysis::check::region` and in the [Reference]. + /// + /// [Reference]: https://doc.rust-lang.org/nightly/reference/destructors.html#temporary-lifetime-extension + extended_temp_scopes: ItemLocalMap>, /// Backwards incompatible scoping that will be introduced in future editions. /// This information is used later for linting to identify locals and @@ -234,16 +234,6 @@ pub struct ScopeTree { pub backwards_incompatible_scope: UnordMap, } -/// See the `rvalue_candidates` field for more information on rvalue -/// candidates in general. -/// The `lifetime` field is None to indicate that certain expressions escape -/// into 'static and should have no local cleanup scope. -#[derive(Debug, Copy, Clone, HashStable)] -pub struct RvalueCandidate { - pub target: hir::ItemLocalId, - pub lifetime: Option, -} - impl ScopeTree { pub fn record_scope_parent(&mut self, child: Scope, parent: Option) { debug!("{:?}.parent = {:?}", child, parent); @@ -260,12 +250,13 @@ impl ScopeTree { self.var_map.insert(var, lifetime); } - pub fn record_rvalue_candidate(&mut self, var: HirId, candidate: RvalueCandidate) { - debug!("record_rvalue_candidate(var={var:?}, candidate={candidate:?})"); - if let Some(lifetime) = &candidate.lifetime { - assert!(var.local_id != lifetime.local_id) + /// Make an association between a sub-expression and an extended lifetime + pub fn record_extended_temp_scope(&mut self, var: hir::ItemLocalId, lifetime: Option) { + debug!(?var, ?lifetime); + if let Some(lifetime) = lifetime { + assert!(var != lifetime.local_id); } - self.rvalue_candidates.insert(var, candidate); + self.extended_temp_scopes.insert(var, lifetime); } /// Returns the narrowest scope that encloses `id`, if any. @@ -337,4 +328,20 @@ impl ScopeTree { span_bug!(ty::tls::with(|tcx| inner.span(tcx, self)), "no enclosing temporary scope") } + + /// Returns the scope when the temp created by `expr_id` will be cleaned up. + /// It also emits a lint on potential backwards incompatible change to the temporary scope + /// which is *for now* always shortening. + pub fn temporary_scope(&self, expr_id: hir::ItemLocalId) -> (Option, Option) { + // Check for a designated extended temporary scope. + if let Some(&s) = self.extended_temp_scopes.get(&expr_id) { + debug!("temporary_scope({expr_id:?}) = {s:?} [custom]"); + return (s, None); + } + + // Otherwise, locate the innermost terminating scope. + let (scope, backward_incompatible) = + self.default_temporary_scope(Scope { local_id: expr_id, data: ScopeData::Node }); + (Some(scope), backward_incompatible) + } } diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs index 905874f05628f..8fd3c9cb61d6e 100644 --- a/compiler/rustc_middle/src/ty/mod.rs +++ b/compiler/rustc_middle/src/ty/mod.rs @@ -97,7 +97,6 @@ pub use self::region::{ BoundRegion, BoundRegionKind, EarlyParamRegion, LateParamRegion, LateParamRegionKind, Region, RegionKind, RegionVid, }; -pub use self::rvalue_scopes::RvalueScopes; pub use self::sty::{ AliasTy, Article, Binder, BoundTy, BoundTyKind, BoundVariableKind, CanonicalPolyFnSig, CoroutineArgsExt, EarlyBinder, FnSig, InlineConstArgs, InlineConstArgsParts, ParamConst, @@ -156,7 +155,6 @@ mod list; mod opaque_types; mod predicate; mod region; -mod rvalue_scopes; mod structural_impls; #[allow(hidden_glob_reexports)] mod sty; diff --git a/compiler/rustc_middle/src/ty/rvalue_scopes.rs b/compiler/rustc_middle/src/ty/rvalue_scopes.rs deleted file mode 100644 index df4e29d457548..0000000000000 --- a/compiler/rustc_middle/src/ty/rvalue_scopes.rs +++ /dev/null @@ -1,48 +0,0 @@ -use rustc_hir as hir; -use rustc_hir::ItemLocalMap; -use rustc_macros::{HashStable, TyDecodable, TyEncodable}; -use tracing::debug; - -use crate::middle::region::{Scope, ScopeData, ScopeTree}; - -/// `RvalueScopes` is a mapping from sub-expressions to _extended_ lifetime as determined by -/// rules laid out in `rustc_hir_analysis::check::rvalue_scopes`. -#[derive(TyEncodable, TyDecodable, Clone, Debug, Default, Eq, PartialEq, HashStable)] -pub struct RvalueScopes { - map: ItemLocalMap>, -} - -impl RvalueScopes { - pub fn new() -> Self { - Self { map: <_>::default() } - } - - /// Returns the scope when the temp created by `expr_id` will be cleaned up. - /// It also emits a lint on potential backwards incompatible change to the temporary scope - /// which is *for now* always shortening. - pub fn temporary_scope( - &self, - region_scope_tree: &ScopeTree, - expr_id: hir::ItemLocalId, - ) -> (Option, Option) { - // Check for a designated rvalue scope. - if let Some(&s) = self.map.get(&expr_id) { - debug!("temporary_scope({expr_id:?}) = {s:?} [custom]"); - return (s, None); - } - - // Otherwise, locate the innermost terminating scope. - let (scope, backward_incompatible) = region_scope_tree - .default_temporary_scope(Scope { local_id: expr_id, data: ScopeData::Node }); - (Some(scope), backward_incompatible) - } - - /// Make an association between a sub-expression and an extended lifetime - pub fn record_rvalue_scope(&mut self, var: hir::ItemLocalId, lifetime: Option) { - debug!("record_rvalue_scope(var={var:?}, lifetime={lifetime:?})"); - if let Some(lifetime) = lifetime { - assert!(var != lifetime.local_id); - } - self.map.insert(var, lifetime); - } -} diff --git a/compiler/rustc_middle/src/ty/typeck_results.rs b/compiler/rustc_middle/src/ty/typeck_results.rs index 20657ba8c726c..d292d30fd9873 100644 --- a/compiler/rustc_middle/src/ty/typeck_results.rs +++ b/compiler/rustc_middle/src/ty/typeck_results.rs @@ -17,7 +17,6 @@ use rustc_macros::{HashStable, TyDecodable, TyEncodable, TypeFoldable, TypeVisit use rustc_session::Session; use rustc_span::Span; -use super::RvalueScopes; use crate::hir::place::Place as HirPlace; use crate::infer::canonical::Canonical; use crate::mir::FakeReadCause; @@ -197,11 +196,6 @@ pub struct TypeckResults<'tcx> { /// issue by fake reading `t`. pub closure_fake_reads: LocalDefIdMap, FakeReadCause, HirId)>>, - /// Tracks the rvalue scoping rules which defines finer scoping for rvalue expressions - /// by applying extended parameter rules. - /// Details may be found in `rustc_hir_analysis::check::rvalue_scopes`. - pub rvalue_scopes: RvalueScopes, - /// Stores the predicates that apply on coroutine witness types. /// formatting modified file tests/ui/coroutine/retain-resume-ref.rs pub coroutine_stalled_predicates: FxIndexSet<(ty::Predicate<'tcx>, ObligationCause<'tcx>)>, @@ -253,7 +247,6 @@ impl<'tcx> TypeckResults<'tcx> { hidden_types: Default::default(), closure_min_captures: Default::default(), closure_fake_reads: Default::default(), - rvalue_scopes: Default::default(), coroutine_stalled_predicates: Default::default(), potentially_region_dependent_goals: Default::default(), closure_size_eval: Default::default(), diff --git a/compiler/rustc_mir_build/src/builder/expr/as_rvalue.rs b/compiler/rustc_mir_build/src/builder/expr/as_rvalue.rs index 552f8c66784ed..546bfc8ea547a 100644 --- a/compiler/rustc_mir_build/src/builder/expr/as_rvalue.rs +++ b/compiler/rustc_mir_build/src/builder/expr/as_rvalue.rs @@ -779,8 +779,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { Rvalue::Ref(this.tcx.lifetimes.re_erased, borrow_kind, arg_place), ); - // See the comment in `expr_as_temp` and on the `rvalue_scopes` field for why - // this can be `None`. + // This can be `None` if the expression's temporary scope was extended so that it can be + // borrowed by a `const` or `static`. In that case, it's never dropped. if let Some(temp_lifetime) = temp_lifetime { this.schedule_drop_storage_and_value(upvar_span, temp_lifetime, temp); } diff --git a/compiler/rustc_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs index 8ce0b73e47e36..6ac935d3901ed 100644 --- a/compiler/rustc_mir_build/src/thir/cx/expr.rs +++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs @@ -337,7 +337,7 @@ impl<'tcx> ThirBuildCx<'tcx> { let tcx = self.tcx; let expr_ty = self.typeck_results.expr_ty(expr); let (temp_lifetime, backwards_incompatible) = - self.rvalue_scopes.temporary_scope(self.region_scope_tree, expr.hir_id.local_id); + self.region_scope_tree.temporary_scope(expr.hir_id.local_id); let kind = match expr.kind { // Here comes the interesting stuff: @@ -502,9 +502,8 @@ impl<'tcx> ThirBuildCx<'tcx> { expr: Some(arg), safety_mode: BlockSafety::Safe, }); - let (temp_lifetime, backwards_incompatible) = self - .rvalue_scopes - .temporary_scope(self.region_scope_tree, arg_expr.hir_id.local_id); + let (temp_lifetime, backwards_incompatible) = + self.region_scope_tree.temporary_scope(arg_expr.hir_id.local_id); arg = self.thir.exprs.push(Expr { temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible }, ty: arg_ty, @@ -996,9 +995,8 @@ impl<'tcx> ThirBuildCx<'tcx> { } } else { let block_ty = self.typeck_results.node_type(body.hir_id); - let (temp_lifetime, backwards_incompatible) = self - .rvalue_scopes - .temporary_scope(self.region_scope_tree, body.hir_id.local_id); + let (temp_lifetime, backwards_incompatible) = + self.region_scope_tree.temporary_scope(body.hir_id.local_id); let block = self.mirror_block(body); let body = self.thir.exprs.push(Expr { ty: block_ty, @@ -1143,7 +1141,7 @@ impl<'tcx> ThirBuildCx<'tcx> { overloaded_callee: Option>, ) -> Expr<'tcx> { let (temp_lifetime, backwards_incompatible) = - self.rvalue_scopes.temporary_scope(self.region_scope_tree, expr.hir_id.local_id); + self.region_scope_tree.temporary_scope(expr.hir_id.local_id); let (ty, user_ty) = match overloaded_callee { Some(fn_def) => (fn_def, None), None => { @@ -1237,9 +1235,8 @@ impl<'tcx> ThirBuildCx<'tcx> { Res::Def(DefKind::Static { .. }, id) => { // this is &raw for extern static or static mut, and & for other statics let ty = self.tcx.static_ptr_ty(id, self.typing_env); - let (temp_lifetime, backwards_incompatible) = self - .rvalue_scopes - .temporary_scope(self.region_scope_tree, expr.hir_id.local_id); + let (temp_lifetime, backwards_incompatible) = + self.region_scope_tree.temporary_scope(expr.hir_id.local_id); let kind = if self.tcx.is_thread_local_static(id) { ExprKind::ThreadLocalRef(id) } else { @@ -1321,7 +1318,7 @@ impl<'tcx> ThirBuildCx<'tcx> { // construct the complete expression `foo()` for the overloaded call, // which will yield the &T type let (temp_lifetime, backwards_incompatible) = - self.rvalue_scopes.temporary_scope(self.region_scope_tree, expr.hir_id.local_id); + self.region_scope_tree.temporary_scope(expr.hir_id.local_id); let fun = self.method_callee(expr, span, overloaded_callee); let fun = self.thir.exprs.push(fun); let fun_ty = self.thir[fun].ty; @@ -1341,9 +1338,8 @@ impl<'tcx> ThirBuildCx<'tcx> { closure_expr: &'tcx hir::Expr<'tcx>, place: HirPlace<'tcx>, ) -> Expr<'tcx> { - let (temp_lifetime, backwards_incompatible) = self - .rvalue_scopes - .temporary_scope(self.region_scope_tree, closure_expr.hir_id.local_id); + let (temp_lifetime, backwards_incompatible) = + self.region_scope_tree.temporary_scope(closure_expr.hir_id.local_id); let var_ty = place.base_ty; // The result of capture analysis in `rustc_hir_typeck/src/upvar.rs` represents a captured path @@ -1405,9 +1401,8 @@ impl<'tcx> ThirBuildCx<'tcx> { let upvar_capture = captured_place.info.capture_kind; let captured_place_expr = self.convert_captured_hir_place(closure_expr, captured_place.place.clone()); - let (temp_lifetime, backwards_incompatible) = self - .rvalue_scopes - .temporary_scope(self.region_scope_tree, closure_expr.hir_id.local_id); + let (temp_lifetime, backwards_incompatible) = + self.region_scope_tree.temporary_scope(closure_expr.hir_id.local_id); match upvar_capture { ty::UpvarCapture::ByValue => captured_place_expr, diff --git a/compiler/rustc_mir_build/src/thir/cx/mod.rs b/compiler/rustc_mir_build/src/thir/cx/mod.rs index 9657c4dc839d6..65b05a0e6a985 100644 --- a/compiler/rustc_mir_build/src/thir/cx/mod.rs +++ b/compiler/rustc_mir_build/src/thir/cx/mod.rs @@ -12,7 +12,7 @@ use rustc_hir::{self as hir, HirId, find_attr}; use rustc_middle::bug; use rustc_middle::middle::region; use rustc_middle::thir::*; -use rustc_middle::ty::{self, RvalueScopes, TyCtxt}; +use rustc_middle::ty::{self, TyCtxt}; use tracing::instrument; use crate::thir::pattern::pat_from_hir; @@ -62,7 +62,6 @@ struct ThirBuildCx<'tcx> { region_scope_tree: &'tcx region::ScopeTree, typeck_results: &'tcx ty::TypeckResults<'tcx>, - rvalue_scopes: &'tcx RvalueScopes, /// False to indicate that adjustments should not be applied. Only used for `custom_mir` apply_adjustments: bool, @@ -109,7 +108,6 @@ impl<'tcx> ThirBuildCx<'tcx> { typing_env: ty::TypingEnv::non_body_analysis(tcx, def), region_scope_tree: tcx.region_scope_tree(def), typeck_results, - rvalue_scopes: &typeck_results.rvalue_scopes, body_owner: def.to_def_id(), apply_adjustments: !find_attr!(tcx.hir_attrs(hir_id), AttributeKind::CustomMir(..) => ()).is_some(), From 6b8121d908e9aff9dd48fa55e6b5454df02cbf96 Mon Sep 17 00:00:00 2001 From: dianne Date: Wed, 5 Nov 2025 07:50:15 -0800 Subject: [PATCH 4/6] return a `TempLifetime` from `ScopeTree::temporary_scope` This means less boilerplate when building the THIR and less possibility for confusion about what to do with the returned scopes from `ScopeTree::temporary_scope`. Possibly migrating `TempLifetime` to `rustc_middle::middle::region` and tweaking its doc comments is left for future work. --- compiler/rustc_middle/src/middle/region.rs | 11 +-- compiler/rustc_mir_build/src/thir/cx/expr.rs | 79 ++++++-------------- 2 files changed, 30 insertions(+), 60 deletions(-) diff --git a/compiler/rustc_middle/src/middle/region.rs b/compiler/rustc_middle/src/middle/region.rs index c0e443b5ead87..3fe1075d64c18 100644 --- a/compiler/rustc_middle/src/middle/region.rs +++ b/compiler/rustc_middle/src/middle/region.rs @@ -16,6 +16,7 @@ use rustc_macros::{HashStable, TyDecodable, TyEncodable}; use rustc_span::{DUMMY_SP, Span}; use tracing::debug; +use crate::thir::TempLifetime; use crate::ty::{self, TyCtxt}; /// Represents a statically-describable scope that can be used to @@ -332,16 +333,16 @@ impl ScopeTree { /// Returns the scope when the temp created by `expr_id` will be cleaned up. /// It also emits a lint on potential backwards incompatible change to the temporary scope /// which is *for now* always shortening. - pub fn temporary_scope(&self, expr_id: hir::ItemLocalId) -> (Option, Option) { + pub fn temporary_scope(&self, expr_id: hir::ItemLocalId) -> TempLifetime { // Check for a designated extended temporary scope. - if let Some(&s) = self.extended_temp_scopes.get(&expr_id) { + if let Some(&s) = self.rvalue_scopes.get(&expr_id) { debug!("temporary_scope({expr_id:?}) = {s:?} [custom]"); - return (s, None); + return TempLifetime { temp_lifetime: s, backwards_incompatible: None }; } // Otherwise, locate the innermost terminating scope. - let (scope, backward_incompatible) = + let (scope, backwards_incompatible) = self.default_temporary_scope(Scope { local_id: expr_id, data: ScopeData::Node }); - (Some(scope), backward_incompatible) + TempLifetime { temp_lifetime: Some(scope), backwards_incompatible } } } diff --git a/compiler/rustc_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs index 6ac935d3901ed..09fa0e802ed67 100644 --- a/compiler/rustc_mir_build/src/thir/cx/expr.rs +++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs @@ -336,8 +336,7 @@ impl<'tcx> ThirBuildCx<'tcx> { fn make_mirror_unadjusted(&mut self, expr: &'tcx hir::Expr<'tcx>) -> Expr<'tcx> { let tcx = self.tcx; let expr_ty = self.typeck_results.expr_ty(expr); - let (temp_lifetime, backwards_incompatible) = - self.region_scope_tree.temporary_scope(expr.hir_id.local_id); + let temp_lifetime = self.region_scope_tree.temporary_scope(expr.hir_id.local_id); let kind = match expr.kind { // Here comes the interesting stuff: @@ -372,7 +371,7 @@ impl<'tcx> ThirBuildCx<'tcx> { let arg_tys = args.iter().map(|e| self.typeck_results.expr_ty_adjusted(e)); let tupled_args = Expr { ty: Ty::new_tup_from_iter(tcx, arg_tys), - temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible }, + temp_lifetime, span: expr.span, kind: ExprKind::Tuple { fields: self.mirror_exprs(args) }, }; @@ -398,7 +397,7 @@ impl<'tcx> ThirBuildCx<'tcx> { } let value = &args[0]; return Expr { - temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible }, + temp_lifetime, ty: expr_ty, span: expr.span, kind: ExprKind::Box { value: self.mirror_expr(value) }, @@ -502,17 +501,17 @@ impl<'tcx> ThirBuildCx<'tcx> { expr: Some(arg), safety_mode: BlockSafety::Safe, }); - let (temp_lifetime, backwards_incompatible) = - self.region_scope_tree.temporary_scope(arg_expr.hir_id.local_id); arg = self.thir.exprs.push(Expr { - temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible }, + temp_lifetime: self + .region_scope_tree + .temporary_scope(arg_expr.hir_id.local_id), ty: arg_ty, span: arg_expr.span, kind: ExprKind::Block { block }, }); } let expr = self.thir.exprs.push(Expr { - temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible }, + temp_lifetime, ty, span: expr.span, kind: ExprKind::Borrow { borrow_kind: mutbl.to_borrow_kind(), arg }, @@ -995,12 +994,10 @@ impl<'tcx> ThirBuildCx<'tcx> { } } else { let block_ty = self.typeck_results.node_type(body.hir_id); - let (temp_lifetime, backwards_incompatible) = - self.region_scope_tree.temporary_scope(body.hir_id.local_id); let block = self.mirror_block(body); let body = self.thir.exprs.push(Expr { ty: block_ty, - temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible }, + temp_lifetime: self.region_scope_tree.temporary_scope(body.hir_id.local_id), span: self.thir[block].span, kind: ExprKind::Block { block }, }); @@ -1022,17 +1019,13 @@ impl<'tcx> ThirBuildCx<'tcx> { expr, cast_ty.hir_id, user_ty, ); - let cast = self.mirror_expr_cast( - source, - TempLifetime { temp_lifetime, backwards_incompatible }, - expr.span, - ); + let cast = self.mirror_expr_cast(source, temp_lifetime, expr.span); if let Some(user_ty) = user_ty { // NOTE: Creating a new Expr and wrapping a Cast inside of it may be // inefficient, revisit this when performance becomes an issue. let cast_expr = self.thir.exprs.push(Expr { - temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible }, + temp_lifetime, ty: expr_ty, span: expr.span, kind: cast, @@ -1091,12 +1084,7 @@ impl<'tcx> ThirBuildCx<'tcx> { hir::ExprKind::Err(_) => unreachable!("cannot lower a `hir::ExprKind::Err` to THIR"), }; - Expr { - temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible }, - ty: expr_ty, - span: expr.span, - kind, - } + Expr { temp_lifetime, ty: expr_ty, span: expr.span, kind } } fn user_args_applied_to_res( @@ -1140,8 +1128,7 @@ impl<'tcx> ThirBuildCx<'tcx> { span: Span, overloaded_callee: Option>, ) -> Expr<'tcx> { - let (temp_lifetime, backwards_incompatible) = - self.region_scope_tree.temporary_scope(expr.hir_id.local_id); + let temp_lifetime = self.region_scope_tree.temporary_scope(expr.hir_id.local_id); let (ty, user_ty) = match overloaded_callee { Some(fn_def) => (fn_def, None), None => { @@ -1157,12 +1144,7 @@ impl<'tcx> ThirBuildCx<'tcx> { ) } }; - Expr { - temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible }, - ty, - span, - kind: ExprKind::ZstLiteral { user_ty }, - } + Expr { temp_lifetime, ty, span, kind: ExprKind::ZstLiteral { user_ty } } } fn convert_arm(&mut self, arm: &'tcx hir::Arm<'tcx>) -> ArmId { @@ -1235,8 +1217,7 @@ impl<'tcx> ThirBuildCx<'tcx> { Res::Def(DefKind::Static { .. }, id) => { // this is &raw for extern static or static mut, and & for other statics let ty = self.tcx.static_ptr_ty(id, self.typing_env); - let (temp_lifetime, backwards_incompatible) = - self.region_scope_tree.temporary_scope(expr.hir_id.local_id); + let temp_lifetime = self.region_scope_tree.temporary_scope(expr.hir_id.local_id); let kind = if self.tcx.is_thread_local_static(id) { ExprKind::ThreadLocalRef(id) } else { @@ -1244,12 +1225,7 @@ impl<'tcx> ThirBuildCx<'tcx> { ExprKind::StaticRef { alloc_id, ty, def_id: id } }; ExprKind::Deref { - arg: self.thir.exprs.push(Expr { - ty, - temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible }, - span: expr.span, - kind, - }), + arg: self.thir.exprs.push(Expr { ty, temp_lifetime, span: expr.span, kind }), } } @@ -1317,13 +1293,12 @@ impl<'tcx> ThirBuildCx<'tcx> { // construct the complete expression `foo()` for the overloaded call, // which will yield the &T type - let (temp_lifetime, backwards_incompatible) = - self.region_scope_tree.temporary_scope(expr.hir_id.local_id); + let temp_lifetime = self.region_scope_tree.temporary_scope(expr.hir_id.local_id); let fun = self.method_callee(expr, span, overloaded_callee); let fun = self.thir.exprs.push(fun); let fun_ty = self.thir[fun].ty; let ref_expr = self.thir.exprs.push(Expr { - temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible }, + temp_lifetime, ty: ref_ty, span, kind: ExprKind::Call { ty: fun_ty, fun, args, from_hir_call: false, fn_span: span }, @@ -1338,8 +1313,7 @@ impl<'tcx> ThirBuildCx<'tcx> { closure_expr: &'tcx hir::Expr<'tcx>, place: HirPlace<'tcx>, ) -> Expr<'tcx> { - let (temp_lifetime, backwards_incompatible) = - self.region_scope_tree.temporary_scope(closure_expr.hir_id.local_id); + let temp_lifetime = self.region_scope_tree.temporary_scope(closure_expr.hir_id.local_id); let var_ty = place.base_ty; // The result of capture analysis in `rustc_hir_typeck/src/upvar.rs` represents a captured path @@ -1353,7 +1327,7 @@ impl<'tcx> ThirBuildCx<'tcx> { }; let mut captured_place_expr = Expr { - temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible }, + temp_lifetime, ty: var_ty, span: closure_expr.span, kind: self.convert_var(var_hir_id), @@ -1381,12 +1355,8 @@ impl<'tcx> ThirBuildCx<'tcx> { } }; - captured_place_expr = Expr { - temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible }, - ty: proj.ty, - span: closure_expr.span, - kind, - }; + captured_place_expr = + Expr { temp_lifetime, ty: proj.ty, span: closure_expr.span, kind }; } captured_place_expr @@ -1401,8 +1371,7 @@ impl<'tcx> ThirBuildCx<'tcx> { let upvar_capture = captured_place.info.capture_kind; let captured_place_expr = self.convert_captured_hir_place(closure_expr, captured_place.place.clone()); - let (temp_lifetime, backwards_incompatible) = - self.region_scope_tree.temporary_scope(closure_expr.hir_id.local_id); + let temp_lifetime = self.region_scope_tree.temporary_scope(closure_expr.hir_id.local_id); match upvar_capture { ty::UpvarCapture::ByValue => captured_place_expr, @@ -1411,7 +1380,7 @@ impl<'tcx> ThirBuildCx<'tcx> { let expr_id = self.thir.exprs.push(captured_place_expr); Expr { - temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible }, + temp_lifetime, ty: upvar_ty, span: closure_expr.span, kind: ExprKind::ByUse { expr: expr_id, span }, @@ -1428,7 +1397,7 @@ impl<'tcx> ThirBuildCx<'tcx> { } }; Expr { - temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible }, + temp_lifetime, ty: upvar_ty, span: closure_expr.span, kind: ExprKind::Borrow { From f2bd9a0961d457b409c380d7be3be20321a92433 Mon Sep 17 00:00:00 2001 From: dianne Date: Wed, 5 Nov 2025 06:50:44 -0800 Subject: [PATCH 5/6] compute extended temporary scopes in a single pass --- .../rustc_hir_analysis/src/check/region.rs | 330 +++++++++--------- compiler/rustc_middle/src/middle/region.rs | 2 +- 2 files changed, 164 insertions(+), 168 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/check/region.rs b/compiler/rustc_hir_analysis/src/check/region.rs index 550f6fe8195a7..2dc49babf41e2 100644 --- a/compiler/rustc_hir_analysis/src/check/region.rs +++ b/compiler/rustc_hir_analysis/src/check/region.rs @@ -8,7 +8,6 @@ use std::mem; -use rustc_data_structures::fx::FxHashMap; use rustc_hir as hir; use rustc_hir::def::{CtorKind, DefKind, Res}; use rustc_hir::def_id::DefId; @@ -28,8 +27,30 @@ struct Context { /// Region parent of expressions, etc. parent: Option, + + /// Scope of lifetime-extended temporaries. If `None`, extendable expressions have their usual + /// temporary scopes. + extended_parent: Option, +} + +/// Determines the scopes of subexpressions' temporaries. +#[derive(Debug, Copy, Clone)] +struct NodeInfo { + /// If `true`, this node is a temporary scope; non-extended temporaries do not live past it. + /// If `false`, temporaries live past its evaluation to the enclosing temporary scope. + drop_temps: bool, + /// If `true`, borrow operators' operands and `super let` bindings in extending sub-expressions + /// are subject to [lifetime extension]. + /// If `false`, sub-expressions' temporary lifetimes will not be extended. + /// + /// [lifetime extension]: https://doc.rust-lang.org/nightly/reference/destructors.html#temporary-lifetime-extension + extending: bool, } +/// Scope of lifetime-extended temporaries. If the field is `None`, no drop is scheduled for them. +#[derive(Debug, Copy, Clone)] +struct ExtendedScope(Option); + struct ScopeResolutionVisitor<'tcx> { tcx: TyCtxt<'tcx>, @@ -37,8 +58,6 @@ struct ScopeResolutionVisitor<'tcx> { scope_tree: ScopeTree, cx: Context, - - extended_super_lets: FxHashMap>, } /// Records the lifetime of a local variable as `cx.var_parent` @@ -56,7 +75,7 @@ fn record_var_lifetime(visitor: &mut ScopeResolutionVisitor<'_>, var_id: hir::It fn resolve_block<'tcx>( visitor: &mut ScopeResolutionVisitor<'tcx>, blk: &'tcx hir::Block<'tcx>, - terminating: bool, + node_info: NodeInfo, ) { debug!("resolve_block(blk.hir_id={:?})", blk.hir_id); @@ -87,7 +106,7 @@ fn resolve_block<'tcx>( // `other_argument()` has run and also the call to `quux(..)` // itself has returned. - visitor.enter_node_scope_with_dtor(blk.hir_id.local_id, terminating); + visitor.enter_node_scope(blk.hir_id.local_id, node_info); visitor.cx.var_parent = visitor.cx.parent; { @@ -116,7 +135,7 @@ fn resolve_block<'tcx>( // the sequence of visits agree with the order in the default // `hir::intravisit` visitor. mem::swap(&mut prev_cx, &mut visitor.cx); - resolve_block(visitor, els, true); + resolve_block(visitor, els, NodeInfo { drop_temps: true, extending: false }); // From now on, we continue normally. visitor.cx = prev_cx; } @@ -144,8 +163,8 @@ fn resolve_block<'tcx>( if let Some(tail_expr) = blk.expr { let local_id = tail_expr.hir_id.local_id; let edition = blk.span.edition(); - let terminating = edition.at_least_rust_2024(); - if !terminating + let drop_temps = edition.at_least_rust_2024(); + if !drop_temps && !visitor .tcx .lints_that_dont_need_to_run(()) @@ -160,7 +179,7 @@ fn resolve_block<'tcx>( .backwards_incompatible_scope .insert(local_id, Scope { local_id, data: ScopeData::Node }); } - resolve_expr(visitor, tail_expr, terminating); + resolve_expr(visitor, tail_expr, NodeInfo { drop_temps, extending: true }); } } @@ -170,7 +189,7 @@ fn resolve_block<'tcx>( /// Resolve a condition from an `if` expression or match guard so that it is a terminating scope /// if it doesn't contain `let` expressions. fn resolve_cond<'tcx>(visitor: &mut ScopeResolutionVisitor<'tcx>, cond: &'tcx hir::Expr<'tcx>) { - let terminate = match cond.kind { + let drop_temps = match cond.kind { // Temporaries for `let` expressions must live into the success branch. hir::ExprKind::Let(_) => false, // Logical operator chains are handled in `resolve_expr`. Since logical operator chains in @@ -188,13 +207,13 @@ fn resolve_cond<'tcx>(visitor: &mut ScopeResolutionVisitor<'tcx>, cond: &'tcx hi // Otherwise, conditions should always drop their temporaries. _ => true, }; - resolve_expr(visitor, cond, terminate); + resolve_expr(visitor, cond, NodeInfo { drop_temps, extending: false }); } fn resolve_arm<'tcx>(visitor: &mut ScopeResolutionVisitor<'tcx>, arm: &'tcx hir::Arm<'tcx>) { let prev_cx = visitor.cx; - visitor.enter_node_scope_with_dtor(arm.hir_id.local_id, true); + visitor.enter_node_scope(arm.hir_id.local_id, NodeInfo { drop_temps: true, extending: true }); visitor.cx.var_parent = visitor.cx.parent; resolve_pat(visitor, arm.pat); @@ -206,7 +225,7 @@ fn resolve_arm<'tcx>(visitor: &mut ScopeResolutionVisitor<'tcx>, arm: &'tcx hir: visitor.cx.var_parent = visitor.cx.parent; resolve_cond(visitor, guard); } - resolve_expr(visitor, arm.body, false); + resolve_expr(visitor, arm.body, NodeInfo { drop_temps: false, extending: true }); visitor.cx = prev_cx; } @@ -242,11 +261,13 @@ fn resolve_stmt<'tcx>(visitor: &mut ScopeResolutionVisitor<'tcx>, stmt: &'tcx hi // regions referenced by the destructors need to survive. let prev_parent = visitor.cx.parent; - visitor.enter_node_scope_with_dtor(stmt_id, true); + let prev_extended_parent = visitor.cx.extended_parent; + visitor.enter_node_scope(stmt_id, NodeInfo { drop_temps: true, extending: false }); intravisit::walk_stmt(visitor, stmt); visitor.cx.parent = prev_parent; + visitor.cx.extended_parent = prev_extended_parent; } } @@ -254,12 +275,34 @@ fn resolve_stmt<'tcx>(visitor: &mut ScopeResolutionVisitor<'tcx>, stmt: &'tcx hi fn resolve_expr<'tcx>( visitor: &mut ScopeResolutionVisitor<'tcx>, expr: &'tcx hir::Expr<'tcx>, - terminating: bool, + node_info: NodeInfo, ) { let prev_cx = visitor.cx; - visitor.enter_node_scope_with_dtor(expr.hir_id.local_id, terminating); + visitor.enter_node_scope(expr.hir_id.local_id, node_info); + // Expressions matching the `E&` grammar are marked as "extending". Temporaries borrowed within + // extending expressions have their lifetimes extended. See + // + // + // E& = & E& + // | StructName { ..., f: E&, ... } + // | StructName(..., E&, ...) + // | [ ..., E&, ... ] + // | ( ..., E&, ... ) + // | {...; E&} + // | { super let ... = E&; ... } + // | if _ { ...; E& } else { ...; E& } + // | match _ { ..., _ => E&, ... } + // | E& as ... match expr.kind { + hir::ExprKind::AddrOf(_, _, subexpr) => { + // TODO: generalize + if let Some(ExtendedScope(lifetime)) = visitor.cx.extended_parent { + record_subexpr_extended_temp_scopes(&mut visitor.scope_tree, subexpr, lifetime); + } + resolve_expr(visitor, subexpr, NodeInfo { drop_temps: false, extending: true }); + } + // Conditional or repeating scopes are always terminating // scopes, meaning that temporaries cannot outlive them. // This ensures fixed size stacks. @@ -304,8 +347,8 @@ fn resolve_expr<'tcx>( // should live beyond the immediate expression let terminate_rhs = !matches!(right.kind, hir::ExprKind::Let(_)); - resolve_expr(visitor, left, terminate_lhs); - resolve_expr(visitor, right, terminate_rhs); + resolve_expr(visitor, left, NodeInfo { drop_temps: terminate_lhs, extending: false }); + resolve_expr(visitor, right, NodeInfo { drop_temps: terminate_rhs, extending: false }); } // Manually recurse over closures, because they are nested bodies // that share the parent environment. We handle const blocks in @@ -352,6 +395,40 @@ fn resolve_expr<'tcx>( visitor.visit_expr(left_expr); } + hir::ExprKind::Struct(_, fields, opt_base) => { + for field in fields { + resolve_expr(visitor, field.expr, NodeInfo { drop_temps: false, extending: true }); + } + if let hir::StructTailExpr::Base(base) = opt_base { + visitor.visit_expr(base); + } + } + + // Lifetime-extend tuple constructors' arguments, such as `Some(&temp())`. + // + // That way, there is no difference between `Some(..)` and `Some { 0: .. }`, + // even though the former is syntactically a function call. + hir::ExprKind::Call(func, args) + 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 { + resolve_expr(visitor, arg, NodeInfo { drop_temps: false, extending: true }); + } + } + + hir::ExprKind::Array(subexprs) | hir::ExprKind::Tup(subexprs) => { + for subexpr in subexprs { + resolve_expr(visitor, subexpr, NodeInfo { drop_temps: false, extending: true }); + } + } + + hir::ExprKind::Cast(subexpr, _) => { + resolve_expr(visitor, subexpr, NodeInfo { drop_temps: false, extending: true }); + } + hir::ExprKind::If(cond, then, Some(otherwise)) => { let expr_cx = visitor.cx; let data = if expr.span.at_least_rust_2024() { @@ -362,9 +439,9 @@ fn resolve_expr<'tcx>( visitor.enter_scope(Scope { local_id: then.hir_id.local_id, data }); visitor.cx.var_parent = visitor.cx.parent; resolve_cond(visitor, cond); - resolve_expr(visitor, then, true); + resolve_expr(visitor, then, NodeInfo { drop_temps: true, extending: true }); visitor.cx = expr_cx; - resolve_expr(visitor, otherwise, true); + resolve_expr(visitor, otherwise, NodeInfo { drop_temps: true, extending: true }); } hir::ExprKind::If(cond, then, None) => { @@ -377,18 +454,18 @@ fn resolve_expr<'tcx>( visitor.enter_scope(Scope { local_id: then.hir_id.local_id, data }); visitor.cx.var_parent = visitor.cx.parent; resolve_cond(visitor, cond); - resolve_expr(visitor, then, true); + resolve_expr(visitor, then, NodeInfo { drop_temps: true, extending: true }); visitor.cx = expr_cx; } hir::ExprKind::Loop(body, _, _, _) => { - resolve_block(visitor, body, true); + resolve_block(visitor, body, NodeInfo { drop_temps: true, extending: false }); } hir::ExprKind::DropTemps(expr) => { // `DropTemps(expr)` does not denote a conditional scope. // Rather, we want to achieve the same behavior as `{ let _t = expr; _t }`. - resolve_expr(visitor, expr, true); + resolve_expr(visitor, expr, NodeInfo { drop_temps: true, extending: false }); } _ => intravisit::walk_expr(visitor, expr), @@ -467,69 +544,63 @@ fn resolve_local<'tcx>( // A, but the inner rvalues `a()` and `b()` have an extended lifetime // due to rule C. - let extend_initializer = match let_kind { - LetKind::Regular => true, - LetKind::Super - if let Some(scope) = - visitor.extended_super_lets.remove(&pat.unwrap().hir_id.local_id) => + if let_kind == LetKind::Super { + // Use the extended parent scope (normally used for borrow expressions' operands) as the + // scope of bindings for `super let`. This allows for e.g. + // + // `let a = { super let b = temp(); &b };` === `let a = &temp();` + // + // and + // + // `identity({ super let x = temp(); &x }).method()` === `identity(&temp()).method()` + // + // to both hold. + // + // NB(super_let): This is not sufficient for `{ super let x = &$EXPR; x } === &$EXPR` to + // always hold; e.g. `let _ = &*{ super let x = &temp(); x };` =/= `let _ = &*&temp();`. + // See + // + // FIXME(super_let): This ignores backward-incompatible drop hints. Implementing BIDs for + // `super let` bindings could improve `tail_expr_drop_order` with regard to `pin!`, etc. + + // TODO: generalize + visitor.cx.var_parent = match visitor.cx.extended_parent { + // If the extended parent scope was set, use it. + Some(ExtendedScope(lifetime)) => lifetime, + // Otherwise, like a temporaries, bindings are dropped in the enclosing temporary scope. + None => visitor + .cx + .var_parent + .map(|block| visitor.scope_tree.default_temporary_scope(block).0), + }; + } + + if let Some(expr) = init { + if let Some(pat) = pat + && is_binding_pat(pat) { - // This expression was lifetime-extended by a parent let binding. E.g. - // - // let a = { - // super let b = temp(); - // &b - // }; - // - // (Which needs to behave exactly as: let a = &temp();) - // - // Processing of `let a` will have already decided to extend the lifetime of this - // `super let` to its own var_scope. We use that scope. - visitor.cx.var_parent = scope; - // Extend temporaries to live in the same scope as the parent `let`'s bindings. - true - } - LetKind::Super => { - // This `super let` is not subject to lifetime extension from a parent let binding. E.g. - // - // identity({ super let x = temp(); &x }).method(); - // - // (Which needs to behave exactly as: identity(&temp()).method();) - // - // Iterate up to the enclosing destruction scope to find the same scope that will also - // be used for the result of the block itself. - if let Some(inner_scope) = visitor.cx.var_parent { - visitor.cx.var_parent = - Some(visitor.scope_tree.default_temporary_scope(inner_scope).0) - } - // 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() }))`. - false + record_subexpr_extended_temp_scopes( + &mut visitor.scope_tree, + expr, + visitor.cx.var_parent, + ); } - }; - if let Some(expr) = init - && extend_initializer - { - record_rvalue_scope_if_borrow_expr(visitor, expr, visitor.cx.var_parent); - - if let Some(pat) = pat { - if is_binding_pat(pat) { - record_subexpr_extended_temp_scopes( - &mut visitor.scope_tree, - expr, - visitor.cx.var_parent, - ); - } + let prev_extended_parent = visitor.cx.extended_parent; + + if let_kind == LetKind::Regular { + // When visiting the initializer, extend borrows and `super let`s accessible through + // extending subexpressions to live in the current variable scope (or in the case of + // statics and consts, for the whole program). + visitor.cx.extended_parent = Some(ExtendedScope(visitor.cx.var_parent)); } - } - // Make sure we visit the initializer first. - // The correct order, as shared between drop_ranges and intravisitor, - // is to walk initializer, followed by pattern bindings, finally followed by the `else` block. - if let Some(expr) = init { - visitor.visit_expr(expr); + // Make sure we visit the initializer first. + // The correct order, as shared between drop_ranges and intravisitor, is + // to walk initializer, followed by pattern bindings, finally followed by the `else` block. + resolve_expr(visitor, expr, NodeInfo { drop_temps: false, extending: true }); + + visitor.cx.extended_parent = prev_extended_parent; } if let Some(pat) = pat { @@ -601,85 +672,6 @@ fn resolve_local<'tcx>( | PatKind::Err(_) => false, } } - - /// If `expr` matches the `E&` grammar, then records an extended temporary 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); - record_subexpr_extended_temp_scopes(&mut visitor.scope_tree, subexpr, 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::Cast(subexpr, _) => { - 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); - } - } - } - 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::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); - } - } - } - _ => {} - } - } } /// Applied to an expression `expr` if `expr` -- or something owned or partially owned by @@ -743,15 +735,20 @@ impl<'tcx> ScopeResolutionVisitor<'tcx> { self.cx.parent = Some(child_scope); } - fn enter_node_scope_with_dtor(&mut self, id: hir::ItemLocalId, terminating: bool) { + fn enter_node_scope(&mut self, id: hir::ItemLocalId, node_info: NodeInfo) { // If node was previously marked as a terminating scope during the // recursive visit of its parent node in the HIR, then we need to // account for the destruction scope representing the scope of // the destructors that run immediately after it completes. - if terminating { + if node_info.drop_temps { self.enter_scope(Scope { local_id: id, data: ScopeData::Destruction }); } self.enter_scope(Scope { local_id: id, data: ScopeData::Node }); + // If this scope corresponds to a non-extending subexpression, limit the scopes of + // temporaries to the enclosing temporary scope. + if !node_info.extending { + self.cx.extended_parent = None; + } } fn enter_body(&mut self, hir_id: hir::HirId, f: impl FnOnce(&mut Self)) { @@ -769,7 +766,7 @@ impl<'tcx> ScopeResolutionVisitor<'tcx> { impl<'tcx> Visitor<'tcx> for ScopeResolutionVisitor<'tcx> { fn visit_block(&mut self, b: &'tcx Block<'tcx>) { - resolve_block(self, b, false); + resolve_block(self, b, NodeInfo { drop_temps: false, extending: true }); } fn visit_body(&mut self, body: &hir::Body<'tcx>) { @@ -793,7 +790,7 @@ impl<'tcx> Visitor<'tcx> for ScopeResolutionVisitor<'tcx> { } // The body of the every fn is a root scope. - resolve_expr(this, body.value, true); + resolve_expr(this, body.value, NodeInfo { drop_temps: true, extending: false }); } else { // All bodies have an outer temporary drop scope, but temporaries // and `super let` bindings in constant initializers may be extended @@ -833,7 +830,7 @@ impl<'tcx> Visitor<'tcx> for ScopeResolutionVisitor<'tcx> { resolve_stmt(self, s); } fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) { - resolve_expr(self, ex, false); + resolve_expr(self, ex, NodeInfo { drop_temps: false, extending: false }); } fn visit_local(&mut self, l: &'tcx LetStmt<'tcx>) { let let_kind = match l.super_ { @@ -865,8 +862,7 @@ pub(crate) fn region_scope_tree(tcx: TyCtxt<'_>, def_id: DefId) -> &ScopeTree { let mut visitor = ScopeResolutionVisitor { tcx, scope_tree: ScopeTree::default(), - cx: Context { parent: None, var_parent: None }, - extended_super_lets: Default::default(), + cx: Context { parent: None, var_parent: None, extended_parent: None }, }; visitor.scope_tree.root_body = Some(body.value.hir_id); diff --git a/compiler/rustc_middle/src/middle/region.rs b/compiler/rustc_middle/src/middle/region.rs index 3fe1075d64c18..6b1b99ba24ef6 100644 --- a/compiler/rustc_middle/src/middle/region.rs +++ b/compiler/rustc_middle/src/middle/region.rs @@ -335,7 +335,7 @@ impl ScopeTree { /// which is *for now* always shortening. pub fn temporary_scope(&self, expr_id: hir::ItemLocalId) -> TempLifetime { // Check for a designated extended temporary scope. - if let Some(&s) = self.rvalue_scopes.get(&expr_id) { + if let Some(&s) = self.extended_temp_scopes.get(&expr_id) { debug!("temporary_scope({expr_id:?}) = {s:?} [custom]"); return TempLifetime { temp_lifetime: s, backwards_incompatible: None }; } From d77f9caafe0fa00e889cba92896efc802eb55508 Mon Sep 17 00:00:00 2001 From: dianne Date: Wed, 5 Nov 2025 09:21:53 -0800 Subject: [PATCH 6/6] extend borrow operators' operands' temporary scopes --- .../rustc_hir_analysis/src/check/region.rs | 61 ++++++++++-- compiler/rustc_middle/src/middle/region.rs | 11 ++- compiler/rustc_middle/src/thir.rs | 2 +- ...dex_range.PreCodegen.after.panic-abort.mir | 2 +- ...ex_range.PreCodegen.after.panic-unwind.mir | 2 +- .../format-args-temporary-scopes.e2021.stderr | 29 +----- .../format-args-temporary-scopes.e2024.stderr | 55 +---------- .../borrowck/format-args-temporary-scopes.rs | 14 +-- tests/ui/borrowck/super-let-in-if-block.rs | 4 +- .../ui/borrowck/super-let-in-if-block.stderr | 30 ------ tests/ui/drop/destructuring-assignments.rs | 7 +- .../lint-tail-expr-drop-order-borrowck.rs | 19 +++- .../lint-tail-expr-drop-order-borrowck.stderr | 30 ++++-- .../ui/drop/super-let-tail-expr-drop-order.rs | 95 ++++++------------- 14 files changed, 143 insertions(+), 218 deletions(-) delete mode 100644 tests/ui/borrowck/super-let-in-if-block.stderr diff --git a/compiler/rustc_hir_analysis/src/check/region.rs b/compiler/rustc_hir_analysis/src/check/region.rs index 2dc49babf41e2..fd7562201c6e5 100644 --- a/compiler/rustc_hir_analysis/src/check/region.rs +++ b/compiler/rustc_hir_analysis/src/check/region.rs @@ -15,6 +15,7 @@ use rustc_hir::intravisit::{self, Visitor}; use rustc_hir::{Arm, Block, Expr, LetStmt, Pat, PatKind, Stmt}; use rustc_index::Idx; use rustc_middle::middle::region::*; +use rustc_middle::thir::TempLifetime; use rustc_middle::ty::TyCtxt; use rustc_session::lint; use rustc_span::source_map; @@ -29,7 +30,8 @@ struct Context { parent: Option, /// Scope of lifetime-extended temporaries. If `None`, extendable expressions have their usual - /// temporary scopes. + /// temporary scopes. Distinguishing the case of non-extended temporaries helps minimize the + /// number of extended lifetimes we record in the [`ScopeTree`]'s `rvalue_scopes` map. extended_parent: Option, } @@ -47,9 +49,32 @@ struct NodeInfo { extending: bool, } -/// Scope of lifetime-extended temporaries. If the field is `None`, no drop is scheduled for them. +/// Scope of lifetime-extended temporaries. #[derive(Debug, Copy, Clone)] -struct ExtendedScope(Option); +enum ExtendedScope { + /// Extendable temporaries' scopes will be extended to match the scope of a `let` statement's + /// bindings, a `const`/`static` item, or a `const` block result. In the case of temporaries + /// extended by `const`s and `static`s, the field is `None`, meaning no drop is scheduled. + ThroughDeclaration(Option), + /// Extendable temporaries will be dropped in the temporary scope enclosing the given scope. + /// This is a separate variant to minimize calls to [`ScopeTree::default_temporary_scope`]. + ThroughExpression(Scope), +} + +impl ExtendedScope { + fn to_scope(self, scope_tree: &ScopeTree) -> TempLifetime { + match self { + ExtendedScope::ThroughDeclaration(temp_lifetime) => { + TempLifetime { temp_lifetime, backwards_incompatible: None } + } + ExtendedScope::ThroughExpression(non_extending_parent) => { + let (temp_scope, backwards_incompatible) = + scope_tree.default_temporary_scope(non_extending_parent); + TempLifetime { temp_lifetime: Some(temp_scope), backwards_incompatible } + } + } + } +} struct ScopeResolutionVisitor<'tcx> { tcx: TyCtxt<'tcx>, @@ -178,6 +203,14 @@ fn resolve_block<'tcx>( .scope_tree .backwards_incompatible_scope .insert(local_id, Scope { local_id, data: ScopeData::Node }); + + // To avoid false positives in `tail_expr_drop_order`, make sure extendable + // temporaries are extended past the block tail even if that doesn't change their + // scopes in the current edition. + if visitor.cx.extended_parent.is_none() { + visitor.cx.extended_parent = + Some(ExtendedScope::ThroughExpression(visitor.cx.parent.unwrap())); + } } resolve_expr(visitor, tail_expr, NodeInfo { drop_temps, extending: true }); } @@ -296,8 +329,9 @@ fn resolve_expr<'tcx>( // | E& as ... match expr.kind { hir::ExprKind::AddrOf(_, _, subexpr) => { - // TODO: generalize - if let Some(ExtendedScope(lifetime)) = visitor.cx.extended_parent { + // Record an extended lifetime for the operand if needed. + if let Some(extended_scope) = visitor.cx.extended_parent { + let lifetime = extended_scope.to_scope(&visitor.scope_tree); record_subexpr_extended_temp_scopes(&mut visitor.scope_tree, subexpr, lifetime); } resolve_expr(visitor, subexpr, NodeInfo { drop_temps: false, extending: true }); @@ -563,10 +597,9 @@ fn resolve_local<'tcx>( // FIXME(super_let): This ignores backward-incompatible drop hints. Implementing BIDs for // `super let` bindings could improve `tail_expr_drop_order` with regard to `pin!`, etc. - // TODO: generalize visitor.cx.var_parent = match visitor.cx.extended_parent { // If the extended parent scope was set, use it. - Some(ExtendedScope(lifetime)) => lifetime, + Some(extended_parent) => extended_parent.to_scope(&visitor.scope_tree).temp_lifetime, // Otherwise, like a temporaries, bindings are dropped in the enclosing temporary scope. None => visitor .cx @@ -582,7 +615,7 @@ fn resolve_local<'tcx>( record_subexpr_extended_temp_scopes( &mut visitor.scope_tree, expr, - visitor.cx.var_parent, + TempLifetime { temp_lifetime: visitor.cx.var_parent, backwards_incompatible: None }, ); } @@ -592,7 +625,8 @@ fn resolve_local<'tcx>( // When visiting the initializer, extend borrows and `super let`s accessible through // extending subexpressions to live in the current variable scope (or in the case of // statics and consts, for the whole program). - visitor.cx.extended_parent = Some(ExtendedScope(visitor.cx.var_parent)); + visitor.cx.extended_parent = + Some(ExtendedScope::ThroughDeclaration(visitor.cx.var_parent)); } // Make sure we visit the initializer first. @@ -694,7 +728,7 @@ fn resolve_local<'tcx>( fn record_subexpr_extended_temp_scopes( scope_tree: &mut ScopeTree, mut expr: &hir::Expr<'_>, - lifetime: Option, + lifetime: TempLifetime, ) { debug!(?expr, ?lifetime); @@ -741,6 +775,13 @@ impl<'tcx> ScopeResolutionVisitor<'tcx> { // account for the destruction scope representing the scope of // the destructors that run immediately after it completes. if node_info.drop_temps { + // If this scope corresponds to an extending subexpression, we can extend certain + // temporaries' scopes through it. + if node_info.extending && self.cx.extended_parent.is_none() { + self.cx.extended_parent = Some(ExtendedScope::ThroughExpression( + self.cx.parent.expect("extending subexpressions should have parent scopes"), + )); + } self.enter_scope(Scope { local_id: id, data: ScopeData::Destruction }); } self.enter_scope(Scope { local_id: id, data: ScopeData::Node }); diff --git a/compiler/rustc_middle/src/middle/region.rs b/compiler/rustc_middle/src/middle/region.rs index 6b1b99ba24ef6..e5e7552006dd1 100644 --- a/compiler/rustc_middle/src/middle/region.rs +++ b/compiler/rustc_middle/src/middle/region.rs @@ -227,7 +227,7 @@ pub struct ScopeTree { /// `rustc_hir_analysis::check::region` and in the [Reference]. /// /// [Reference]: https://doc.rust-lang.org/nightly/reference/destructors.html#temporary-lifetime-extension - extended_temp_scopes: ItemLocalMap>, + extended_temp_scopes: ItemLocalMap, /// Backwards incompatible scoping that will be introduced in future editions. /// This information is used later for linting to identify locals and @@ -252,12 +252,13 @@ impl ScopeTree { } /// Make an association between a sub-expression and an extended lifetime - pub fn record_extended_temp_scope(&mut self, var: hir::ItemLocalId, lifetime: Option) { + pub fn record_extended_temp_scope(&mut self, var: hir::ItemLocalId, lifetime: TempLifetime) { debug!(?var, ?lifetime); - if let Some(lifetime) = lifetime { + if let Some(lifetime) = lifetime.temp_lifetime { assert!(var != lifetime.local_id); } - self.extended_temp_scopes.insert(var, lifetime); + let old_lifetime = self.extended_temp_scopes.insert(var, lifetime); + assert!(old_lifetime.is_none_or(|old| old == lifetime)); } /// Returns the narrowest scope that encloses `id`, if any. @@ -337,7 +338,7 @@ impl ScopeTree { // Check for a designated extended temporary scope. if let Some(&s) = self.extended_temp_scopes.get(&expr_id) { debug!("temporary_scope({expr_id:?}) = {s:?} [custom]"); - return TempLifetime { temp_lifetime: s, backwards_incompatible: None }; + return s; } // Otherwise, locate the innermost terminating scope. diff --git a/compiler/rustc_middle/src/thir.rs b/compiler/rustc_middle/src/thir.rs index e72ed78d07e6a..4ec9e43d07363 100644 --- a/compiler/rustc_middle/src/thir.rs +++ b/compiler/rustc_middle/src/thir.rs @@ -265,7 +265,7 @@ pub struct Expr<'tcx> { } /// Temporary lifetime information for THIR expressions -#[derive(Clone, Copy, Debug, HashStable)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, HashStable)] pub struct TempLifetime { /// Lifetime for temporaries as expected. /// This should be `None` in a constant context. diff --git a/tests/mir-opt/pre-codegen/slice_index.slice_index_range.PreCodegen.after.panic-abort.mir b/tests/mir-opt/pre-codegen/slice_index.slice_index_range.PreCodegen.after.panic-abort.mir index 2df2c4b85b8fa..e63f5939b918a 100644 --- a/tests/mir-opt/pre-codegen/slice_index.slice_index_range.PreCodegen.after.panic-abort.mir +++ b/tests/mir-opt/pre-codegen/slice_index.slice_index_range.PreCodegen.after.panic-abort.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/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 index 1bc0d0785eceb..a9ebf7b8f0130 100644 --- a/tests/ui/borrowck/format-args-temporary-scopes.e2021.stderr +++ b/tests/ui/borrowck/format-args-temporary-scopes.e2021.stderr @@ -1,30 +1,5 @@ error[E0716]: temporary value dropped while borrowed - --> $DIR/format-args-temporary-scopes.rs:40:90 - | -LL | println!("{:?}{:?}", (), match true { true => &"" as &dyn std::fmt::Debug, false => &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:33:41 - | -LL | println!("{:?}{:?}", (), if true { &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[E0716]: temporary value dropped while borrowed - --> $DIR/format-args-temporary-scopes.rs:36:64 + --> $DIR/format-args-temporary-scopes.rs:33:64 | LL | println!("{:?}{:?}", (), if true { std::convert::identity(&format!("")) } else { "" }); | ----------------------------------^^^^^^^^^^^--------------- @@ -36,6 +11,6 @@ LL | println!("{:?}{:?}", (), if true { std::convert::identity(&format!("")) = 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 +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 ceaeb07af26e4..40a3b1174ec85 100644 --- a/tests/ui/borrowck/format-args-temporary-scopes.e2024.stderr +++ b/tests/ui/borrowck/format-args-temporary-scopes.e2024.stderr @@ -1,15 +1,3 @@ -error[E0716]: temporary value dropped while borrowed - --> $DIR/format-args-temporary-scopes.rs:12: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:17:48 | @@ -23,19 +11,7 @@ LL | println!("{:?}", { std::convert::identity(&temp()) }); = 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:23: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[E0716]: temporary value dropped while borrowed - --> $DIR/format-args-temporary-scopes.rs:26:52 + --> $DIR/format-args-temporary-scopes.rs:24:52 | LL | println!("{:?}{:?}", { std::convert::identity(&temp()) }, ()); | --------------------------^^^^^^--- @@ -47,32 +23,7 @@ LL | println!("{:?}{:?}", { std::convert::identity(&temp()) }, ()); = 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:40:90 - | -LL | println!("{:?}{:?}", (), match true { true => &"" as &dyn std::fmt::Debug, false => &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:33:41 - | -LL | println!("{:?}{:?}", (), if true { &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[E0716]: temporary value dropped while borrowed - --> $DIR/format-args-temporary-scopes.rs:36:64 + --> $DIR/format-args-temporary-scopes.rs:33:64 | LL | println!("{:?}{:?}", (), if true { std::convert::identity(&format!("")) } else { "" }); | ------------------------^^^^^^^^^^^- @@ -84,6 +35,6 @@ LL | println!("{:?}{:?}", (), if true { std::convert::identity(&format!("")) = 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 7 previous errors +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 6f9a7a22e2de8..e32637c156df6 100644 --- a/tests/ui/borrowck/format-args-temporary-scopes.rs +++ b/tests/ui/borrowck/format-args-temporary-scopes.rs @@ -7,21 +7,19 @@ 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() }); - //[e2024]~^ ERROR: temporary value dropped while borrowed [E0716] // 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() }, ()); - //[e2024]~^ ERROR: temporary value dropped while borrowed [E0716] println!("{:?}{:?}", { std::convert::identity(&temp()) }, ()); //[e2024]~^ ERROR: temporary value dropped while borrowed [E0716] @@ -31,12 +29,10 @@ fn main() { // blocks of `if` expressions are temporary scopes in all editions, this affects Rust 2021 and // earlier as well. println!("{:?}{:?}", (), if true { &format!("") } else { "" }); - //~^ ERROR: temporary value dropped while borrowed [E0716] println!("{:?}{:?}", (), if true { std::convert::identity(&format!("")) } else { "" }); //~^ ERROR: temporary value dropped while borrowed [E0716] // This has likewise occurred with `match`, affecting all editions. println!("{:?}{:?}", (), match true { true => &"" as &dyn std::fmt::Debug, false => &temp() }); - //~^ 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 index d35075047fdad..e7fe634b8e23b 100644 --- a/tests/ui/borrowck/super-let-in-if-block.rs +++ b/tests/ui/borrowck/super-let-in-if-block.rs @@ -1,5 +1,6 @@ //! 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() { @@ -16,14 +17,11 @@ fn main() { // For `super let` in non-extending `if`, the binding `temp` should live in the temporary scope // the `if` expression is in. - // TODO: make this not an error std::convert::identity(if true { super let temp = (); &temp - //~^ ERROR `temp` does not live long enough } else { super let temp = (); &temp - //~^ ERROR `temp` does not live long enough }); } diff --git a/tests/ui/borrowck/super-let-in-if-block.stderr b/tests/ui/borrowck/super-let-in-if-block.stderr deleted file mode 100644 index d2c351b5ea1dc..0000000000000 --- a/tests/ui/borrowck/super-let-in-if-block.stderr +++ /dev/null @@ -1,30 +0,0 @@ -error[E0597]: `temp` does not live long enough - --> $DIR/super-let-in-if-block.rs:22:9 - | -LL | std::convert::identity(if true { - | ---------------------- borrow later used by call -LL | super let temp = (); - | ---- binding `temp` declared here -LL | &temp - | ^^^^^ borrowed value does not live long enough -LL | -LL | } else { - | - `temp` dropped here while still borrowed - -error[E0597]: `temp` does not live long enough - --> $DIR/super-let-in-if-block.rs:26:9 - | -LL | std::convert::identity(if true { - | ---------------------- borrow later used by call -... -LL | super let temp = (); - | ---- binding `temp` declared here -LL | &temp - | ^^^^^ borrowed value does not live long enough -LL | -LL | }); - | - `temp` dropped here while still borrowed - -error: aborting due to 2 previous errors - -For more information about this error, try `rustc --explain E0597`. diff --git a/tests/ui/drop/destructuring-assignments.rs b/tests/ui/drop/destructuring-assignments.rs index 52d787b2b3ede..98aeead165f16 100644 --- a/tests/ui/drop/destructuring-assignments.rs +++ b/tests/ui/drop/destructuring-assignments.rs @@ -14,11 +14,13 @@ fn main() { assert_drop_order(1..=3, |e| { - &({ &raw const *&e.log(1) }, drop(e.log(2))); + &({ &*&e.log(2) }, drop(e.log(1))); + // &({ &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(1) }, drop(e.log(2))); } + { let _x; _x = &({ &raw const *&e.log(2) }, drop(e.log(1))); } drop(e.log(3)); }); assert_drop_order(1..=3, |e| { @@ -34,6 +36,7 @@ fn main() { (_x, _y) = ({ &raw const *&e.log(2) }, drop(e.log(1))); drop(e.log(3)); }); + */ } // # Test scaffolding... 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..b86190a7390c7 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,19 @@ fn another_temp_that_is_copy_in_arg() { //~| NOTE: for more information, see } +fn do_not_lint_borrow_extended_past_all_blocks() { + fn f(_: T) {} + f({ &mut || 0 }); +} + +fn should_lint_borrow_extended_past_only_some_blocks() { + fn f(_: T) {} + f({ 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 + //~| NOTE: borrow later used here + //~| NOTE: for more information, see +} + 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..d014d1cb83c4a 100644 --- a/tests/ui/drop/lint-tail-expr-drop-order-borrowck.stderr +++ b/tests/ui/drop/lint-tail-expr-drop-order-borrowck.stderr @@ -26,27 +26,39 @@ 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 | = warning: this changes meaning in Rust 2024 = note: for more information, see -error: aborting due to 4 previous errors +error: relative drop order changing in Rust 2024 + --> $DIR/lint-tail-expr-drop-order-borrowck.rs:58:34 + | +LL | f({ 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: aborting due to 5 previous errors 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..91d258ba7a41a 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. +//! - Since temporary lifetime extension applies to extending subexpressions in all contexts, it +//! works through Rust 2024 block tail expressions: 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.