1
Fork 0

Auto merge of #131326 - dingxiangfei2009:issue-130836-attempt-2, r=nikomatsakis

Reduce false positives of tail-expr-drop-order from consumed values (attempt #2)

r? `@nikomatsakis`

Tracked by #123739.

Related to #129864 but not replacing, yet.

Related to #130836.

This is an implementation of the approach suggested in the [Zulip stream](https://rust-lang.zulipchat.com/#narrow/stream/213817-t-lang/topic/temporary.20drop.20order.20changes). A new MIR statement `BackwardsIncompatibleDrop` is added to the MIR syntax. The lint now works by inspecting possibly live move paths before at the `BackwardsIncompatibleDrop` location and the actual drop under the current edition, which should be one before Edition 2024 in practice.
This commit is contained in:
bors 2024-11-20 18:51:54 +00:00
commit 3fee0f12e4
58 changed files with 2015 additions and 538 deletions

View file

@ -236,6 +236,11 @@ pub struct ScopeTree {
/// during type check based on a traversal of the AST.
pub rvalue_candidates: HirIdMap<RvalueCandidateType>,
/// Backwards incompatible scoping that will be introduced in future editions.
/// This information is used later for linting to identify locals and
/// temporary values that will receive backwards-incompatible drop orders.
pub backwards_incompatible_scope: UnordMap<hir::ItemLocalId, Scope>,
/// If there are any `yield` nested within a scope, this map
/// stores the `Span` of the last one and its index in the
/// postorder of the Visitor traversal on the HIR.

View file

@ -834,6 +834,11 @@ impl Debug for Statement<'_> {
Intrinsic(box ref intrinsic) => write!(fmt, "{intrinsic}"),
ConstEvalCounter => write!(fmt, "ConstEvalCounter"),
Nop => write!(fmt, "nop"),
BackwardIncompatibleDropHint { ref place, reason: _ } => {
// For now, we don't record the reason because there is only one use case,
// which is to report breaking change in drop order by Edition 2024
write!(fmt, "backward incompatible drop({place:?})")
}
}
}
}

View file

@ -432,6 +432,18 @@ pub enum StatementKind<'tcx> {
/// No-op. Useful for deleting instructions without affecting statement indices.
Nop,
/// Marker statement indicating where `place` would be dropped.
/// This is semantically equivalent to `Nop`, so codegen and MIRI should interpret this
/// statement as such.
/// The only use case of this statement is for linting in MIR to detect temporary lifetime
/// changes.
BackwardIncompatibleDropHint {
/// Place to drop
place: Box<Place<'tcx>>,
/// Reason for backward incompatibility
reason: BackwardIncompatibleDropReason,
},
}
impl StatementKind<'_> {
@ -452,6 +464,7 @@ impl StatementKind<'_> {
StatementKind::Intrinsic(..) => "Intrinsic",
StatementKind::ConstEvalCounter => "ConstEvalCounter",
StatementKind::Nop => "Nop",
StatementKind::BackwardIncompatibleDropHint { .. } => "BackwardIncompatibleDropHint",
}
}
}
@ -897,6 +910,21 @@ pub enum TerminatorKind<'tcx> {
},
}
#[derive(
Clone,
Debug,
TyEncodable,
TyDecodable,
Hash,
HashStable,
PartialEq,
TypeFoldable,
TypeVisitable
)]
pub enum BackwardIncompatibleDropReason {
Edition2024,
}
impl TerminatorKind<'_> {
/// Returns a simple string representation of a `TerminatorKind` variant, independent of any
/// values it might hold (e.g. `TerminatorKind::Call` always returns `"Call"`).

View file

@ -452,6 +452,7 @@ macro_rules! make_mir_visitor {
}
StatementKind::ConstEvalCounter => {}
StatementKind::Nop => {}
StatementKind::BackwardIncompatibleDropHint { .. } => {}
}
}

View file

@ -360,6 +360,14 @@ impl<'tcx> Key for (ty::ParamEnv<'tcx>, ty::TraitRef<'tcx>) {
}
}
impl<'tcx> Key for ty::ParamEnvAnd<'tcx, Ty<'tcx>> {
type Cache<V> = DefaultCache<Self, V>;
fn default_span(&self, _tcx: TyCtxt<'_>) -> Span {
DUMMY_SP
}
}
impl<'tcx> Key for ty::TraitRef<'tcx> {
type Cache<V> = DefaultCache<Self, V>;

View file

@ -1448,6 +1448,28 @@ rustc_queries! {
cache_on_disk_if { false }
}
/// Returns a list of types which (a) have a potentially significant destructor
/// and (b) may be dropped as a result of dropping a value of some type `ty`
/// (in the given environment).
///
/// The idea of "significant" drop is somewhat informal and is used only for
/// diagnostics and edition migrations. The idea is that a significant drop may have
/// some visible side-effect on execution; freeing memory is NOT considered a side-effect.
/// The rules are as follows:
/// * Type with no explicit drop impl do not have significant drop.
/// * Types with a drop impl are assumed to have significant drop unless they have a `#[rustc_insignificant_dtor]` annotation.
///
/// Note that insignificant drop is a "shallow" property. A type like `Vec<LockGuard>` does not
/// have significant drop but the type `LockGuard` does, and so if `ty = Vec<LockGuard>`
/// then the return value would be `&[LockGuard]`.
/// *IMPORTANT*: *DO NOT* run this query before promoted MIR body is constructed,
/// because this query partially depends on that query.
/// Otherwise, there is a risk of query cycles.
query list_significant_drop_tys(ty: ty::ParamEnvAnd<'tcx, Ty<'tcx>>) -> &'tcx ty::List<Ty<'tcx>> {
desc { |tcx| "computing when `{}` has a significant destructor", ty.value }
cache_on_disk_if { false }
}
/// Computes the layout of a type. Note that this implicitly
/// executes in "reveal all" mode, and will normalize the input type.
query layout_of(

View file

@ -247,13 +247,24 @@ pub struct Expr<'tcx> {
pub ty: Ty<'tcx>,
/// The lifetime of this expression if it should be spilled into a
/// temporary; should be `None` only if in a constant context
pub temp_lifetime: Option<region::Scope>,
/// temporary
pub temp_lifetime: TempLifetime,
/// span of the expression in the source
pub span: Span,
}
/// Temporary lifetime information for THIR expressions
#[derive(Clone, Copy, Debug, HashStable)]
pub struct TempLifetime {
/// Lifetime for temporaries as expected.
/// This should be `None` in a constant context.
pub temp_lifetime: Option<region::Scope>,
/// If `Some(lt)`, indicates that the lifetime of this temporary will change to `lt` in a future edition.
/// If `None`, then no changes are expected, or lints are disabled.
pub backwards_incompatible: Option<region::Scope>,
}
#[derive(Clone, Debug, HashStable)]
pub enum ExprKind<'tcx> {
/// `Scope`s are used to explicitly mark destruction scopes,
@ -1087,7 +1098,7 @@ mod size_asserts {
use super::*;
// tidy-alphabetical-start
static_assert_size!(Block, 48);
static_assert_size!(Expr<'_>, 64);
static_assert_size!(Expr<'_>, 72);
static_assert_size!(ExprKind<'_>, 40);
static_assert_size!(Pat<'_>, 64);
static_assert_size!(PatKind<'_>, 48);

View file

@ -18,15 +18,17 @@ impl RvalueScopes {
}
/// 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>, 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;
return (s, None);
}
// Otherwise, locate the innermost terminating scope
@ -34,27 +36,40 @@ impl RvalueScopes {
// have an enclosing scope, hence no scope will be
// returned.
let mut id = Scope { id: expr_id, data: ScopeData::Node };
let mut backwards_incompatible = None;
while let Some(&(p, _)) = region_scope_tree.parent_map.get(&id) {
match p.data {
ScopeData::Destruction => {
debug!("temporary_scope({expr_id:?}) = {id:?} [enclosing]");
return Some(id);
return (Some(id), backwards_incompatible);
}
ScopeData::IfThenRescope => {
debug!("temporary_scope({expr_id:?}) = {p:?} [enclosing]");
return Some(p);
return (Some(p), backwards_incompatible);
}
ScopeData::Node
| ScopeData::CallSite
| ScopeData::Arguments
| ScopeData::IfThen
| ScopeData::Remainder(_) => id = p,
| ScopeData::Remainder(_) => {
// If we haven't already passed through a backwards-incompatible node,
// then check if we are passing through one now and record it if so.
// This is for now only working for cases where a temporary lifetime is
// *shortened*.
if backwards_incompatible.is_none() {
backwards_incompatible = region_scope_tree
.backwards_incompatible_scope
.get(&p.item_local_id())
.copied();
}
id = p
}
}
}
debug!("temporary_scope({expr_id:?}) = None");
None
(None, backwards_incompatible)
}
/// Make an association between a sub-expression and an extended lifetime