Auto merge of #148658 - dianne:cleanup-rvalue-scopes, r=cjgillot

cleanup: merge `RvalueScopes` into `ScopeTree`

This gets rid of `RvalueCandidate`, inlines the definition of `RvalueScopes` into `ScopeTree`, and removes two `RvalueScopes`-specific modules, consolidating the scoping logic a bit. Removing the extra step of going from `RvalueCandidate`s to `RvalueScopes` and removing the duplication between them should also hopefully improve perf.

I've also taken the liberty of doing a bit of renaming and comment updates, changing some "rvalue scope"s to "extended temporary scope"s. This is a bit closer to the Reference's terminology and makes it clearer that it's specific to temporary lifetime extension. This isn't comprehensive. In particular, I've left `record_rvalue_scope_if_borrow_expr` untouched since rust-lang/rust#146098 gets rid of it.

Pulled out from rust-lang/rust#146098.

r? BoxyUwU as the reviewer of rust-lang/rust#146098 (though feel free to reassign/claim! this is just cleanup)

cc `@dingxiangfei2009`
This commit is contained in:
bors 2025-11-11 17:23:11 +00:00
commit 25d319a0f6
12 changed files with 99 additions and 207 deletions

View file

@ -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
/// `<rvalue>` as `lifetime`:
///
/// ```text
/// ET = *ET
/// | ET[...]
/// | ET.f
/// | (ET)
/// | <rvalue>
/// ```
///
/// 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<Scope>,
) {
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) {

View file

@ -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) {

View file

@ -37,7 +37,6 @@ mod op;
mod opaque_types;
mod pat;
mod place_op;
mod rvalue_scopes;
mod typeck_root_ctxt;
mod upvar;
mod writeback;
@ -237,9 +236,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);

View file

@ -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
/// `<rvalue>` as `blk_id`:
///
/// ```text
/// ET = *ET
/// | ET[...]
/// | ET.f
/// | (ET)
/// | <rvalue>
/// ```
///
/// 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<Scope>,
) {
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
}

View file

@ -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);

View file

@ -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<hir::ItemLocalId, Scope>,
/// 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<RvalueCandidate>,
/// 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<Option<Scope>>,
/// 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<hir::ItemLocalId, Scope>,
}
/// 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<Scope>,
}
impl ScopeTree {
pub fn record_scope_parent(&mut self, child: Scope, parent: Option<Scope>) {
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<Scope>) {
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<Scope>, Option<Scope>) {
// 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)
}
}

View file

@ -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;

View file

@ -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<Option<Scope>>,
}
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<Scope>, Option<Scope>) {
// 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<Scope>) {
debug!("record_rvalue_scope(var={var:?}, lifetime={lifetime:?})");
if let Some(lifetime) = lifetime {
assert!(var != lifetime.local_id);
}
self.map.insert(var, lifetime);
}
}

View file

@ -18,7 +18,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;
@ -198,11 +197,6 @@ pub struct TypeckResults<'tcx> {
/// issue by fake reading `t`.
pub closure_fake_reads: LocalDefIdMap<Vec<(HirPlace<'tcx>, 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>)>,
@ -254,7 +248,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(),

View file

@ -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);
}

View file

@ -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<Ty<'tcx>>,
) -> 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,

View file

@ -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(),