Rollup merge of #147471 - dianne:assert-temporary-scope, r=nnethercote
Assert that non-extended temporaries and `super let` bindings have scopes This PR clarifies a point of confusion in the compiler: all bodies have an outer temporary drop scope, including `static` and `const` item bodies[^1]. Whenever a temporary should be dropped in its enclosing temporary scope, it should have a temporary scope to be dropped in so that its drop can be scheduled[^2]. As such, I've updated some relevant comments and made `ScopeTree::default_temporary_scope` and `RvalueScopes::temporary_scope` panic when an enclosing temporary scope isn't found instead of allowing potential bugs where potentially-drop-sensitive temporaries are effectively given static lifetimes. Since non-extended `super let` bindings are dropped in their block's enclosing temporary scope, this applies to them as well: the enclosing temporary scope should exist. [^1]: See https://github.com/rust-lang/rust/blob/master/compiler/rustc_hir_analysis/src/check/region.rs#L773-L778 for non-`fn`/closure bodies. The `this.cx.var_parent = None;` enables [lifetime extension to `'static` lifetimes](https://doc.rust-lang.org/stable/reference/destructors.html#r-destructors.scope.lifetime-extension.static) and the `ScopeData::Destruction` scope ensures non-extended temporaries are dropped in the body expression's scope. [^2]: For certain borrowed temporaries, drops that don't require running destructors may later be removed by constant promotion. That is unrelated to this PR.
This commit is contained in:
@@ -498,7 +498,8 @@ fn resolve_local<'tcx>(
|
||||
// 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, _) = visitor.scope_tree.default_temporary_scope(inner_scope)
|
||||
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`
|
||||
@@ -752,10 +753,10 @@ impl<'tcx> Visitor<'tcx> for ScopeResolutionVisitor<'tcx> {
|
||||
// The body of the every fn is a root scope.
|
||||
resolve_expr(this, body.value, true);
|
||||
} else {
|
||||
// Only functions have an outer terminating (drop) scope, while
|
||||
// temporaries in constant initializers may be 'static, but only
|
||||
// according to rvalue lifetime semantics, using the same
|
||||
// syntactical rules used for let initializers.
|
||||
// All bodies have an outer temporary drop scope, but temporaries
|
||||
// and `super let` bindings in constant initializers may be extended
|
||||
// to have 'static lifetimes, using the same syntactical rules used
|
||||
// for `let` initializers.
|
||||
//
|
||||
// e.g., in `let x = &f();`, the temporary holding the result from
|
||||
// the `f()` call lives for the entirety of the surrounding block.
|
||||
|
||||
@@ -16,7 +16,7 @@ use rustc_macros::{HashStable, TyDecodable, TyEncodable};
|
||||
use rustc_span::{DUMMY_SP, Span};
|
||||
use tracing::debug;
|
||||
|
||||
use crate::ty::TyCtxt;
|
||||
use crate::ty::{self, TyCtxt};
|
||||
|
||||
/// Represents a statically-describable scope that can be used to
|
||||
/// bound the lifetime/region for values.
|
||||
@@ -302,8 +302,8 @@ impl ScopeTree {
|
||||
|
||||
/// Returns the scope of non-lifetime-extended temporaries within a given scope, as well as
|
||||
/// whether we've recorded a potential backwards-incompatible change to lint on.
|
||||
/// Returns `None` when no enclosing temporary scope is found, such as for static items.
|
||||
pub fn default_temporary_scope(&self, inner: Scope) -> (Option<Scope>, Option<Scope>) {
|
||||
/// Panics if no enclosing temporary scope is found.
|
||||
pub fn default_temporary_scope(&self, inner: Scope) -> (Scope, Option<Scope>) {
|
||||
let mut id = inner;
|
||||
let mut backwards_incompatible = None;
|
||||
|
||||
@@ -311,11 +311,11 @@ impl ScopeTree {
|
||||
match p.data {
|
||||
ScopeData::Destruction => {
|
||||
debug!("temporary_scope({inner:?}) = {id:?} [enclosing]");
|
||||
return (Some(id), backwards_incompatible);
|
||||
return (id, backwards_incompatible);
|
||||
}
|
||||
ScopeData::IfThenRescope | ScopeData::MatchGuard => {
|
||||
debug!("temporary_scope({inner:?}) = {p:?} [enclosing]");
|
||||
return (Some(p), backwards_incompatible);
|
||||
return (p, backwards_incompatible);
|
||||
}
|
||||
ScopeData::Node
|
||||
| ScopeData::CallSite
|
||||
@@ -335,7 +335,6 @@ impl ScopeTree {
|
||||
}
|
||||
}
|
||||
|
||||
debug!("temporary_scope({inner:?}) = None");
|
||||
(None, backwards_incompatible)
|
||||
span_bug!(ty::tls::with(|tcx| inner.span(tcx, self)), "no enclosing temporary scope")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,12 +31,10 @@ impl RvalueScopes {
|
||||
return (s, None);
|
||||
}
|
||||
|
||||
// Otherwise, locate the innermost terminating scope
|
||||
// if there's one. Static items, for instance, won't
|
||||
// have an enclosing scope, hence no scope will be
|
||||
// returned.
|
||||
region_scope_tree
|
||||
.default_temporary_scope(Scope { local_id: expr_id, data: ScopeData::Node })
|
||||
// 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
|
||||
|
||||
Reference in New Issue
Block a user