0
0
mirror of https://github.com/rust-lang/rust.git synced 2024-11-21 13:49:34 +01:00

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

@ -641,6 +641,7 @@ impl<'tcx> rustc_mir_dataflow::Analysis<'tcx> for Borrows<'_, 'tcx> {
| mir::StatementKind::Coverage(..)
| mir::StatementKind::Intrinsic(..)
| mir::StatementKind::ConstEvalCounter
| mir::StatementKind::BackwardIncompatibleDropHint { .. }
| mir::StatementKind::Nop => {}
}
}

View File

@ -652,6 +652,8 @@ impl<'a, 'tcx> ResultsVisitor<'a, 'tcx, Borrowck<'a, 'tcx>> for MirBorrowckCtxt<
| StatementKind::Coverage(..)
// These do not actually affect borrowck
| StatementKind::ConstEvalCounter
// This do not affect borrowck
| StatementKind::BackwardIncompatibleDropHint { .. }
| StatementKind::StorageLive(..) => {}
StatementKind::StorageDead(local) => {
self.access_place(

View File

@ -88,6 +88,7 @@ impl<'a, 'tcx> Visitor<'tcx> for LoanInvalidationsGenerator<'a, 'tcx> {
| StatementKind::Nop
| StatementKind::Retag { .. }
| StatementKind::Deinit(..)
| StatementKind::BackwardIncompatibleDropHint { .. }
| StatementKind::SetDiscriminant { .. } => {
bug!("Statement not allowed in this MIR phase")
}

View File

@ -1252,6 +1252,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
| StatementKind::Coverage(..)
| StatementKind::ConstEvalCounter
| StatementKind::PlaceMention(..)
| StatementKind::BackwardIncompatibleDropHint { .. }
| StatementKind::Nop => {}
StatementKind::Deinit(..) | StatementKind::SetDiscriminant { .. } => {
bug!("Statement not allowed in this MIR phase")

View File

@ -924,6 +924,7 @@ fn codegen_stmt<'tcx>(
| StatementKind::FakeRead(..)
| StatementKind::Retag { .. }
| StatementKind::PlaceMention(..)
| StatementKind::BackwardIncompatibleDropHint { .. }
| StatementKind::AscribeUserType(..) => {}
StatementKind::Coverage { .. } => unreachable!(),

View File

@ -583,6 +583,7 @@ pub(crate) fn mir_operand_get_const_val<'tcx>(
| StatementKind::PlaceMention(..)
| StatementKind::Coverage(_)
| StatementKind::ConstEvalCounter
| StatementKind::BackwardIncompatibleDropHint { .. }
| StatementKind::Nop => {}
}
}

View File

@ -92,6 +92,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
| mir::StatementKind::AscribeUserType(..)
| mir::StatementKind::ConstEvalCounter
| mir::StatementKind::PlaceMention(..)
| mir::StatementKind::BackwardIncompatibleDropHint { .. }
| mir::StatementKind::Nop => {}
}
}

View File

@ -609,6 +609,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
| StatementKind::Coverage(..)
| StatementKind::Intrinsic(..)
| StatementKind::ConstEvalCounter
| StatementKind::BackwardIncompatibleDropHint { .. }
| StatementKind::Nop => {}
}
}

View File

@ -143,6 +143,9 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
// Defined to do nothing. These are added by optimization passes, to avoid changing the
// size of MIR constantly.
Nop => {}
// Only used for temporary lifetime lints
BackwardIncompatibleDropHint { .. } => {}
}
interp_ok(())

View File

@ -17,6 +17,7 @@ use rustc_index::Idx;
use rustc_middle::bug;
use rustc_middle::middle::region::*;
use rustc_middle::ty::TyCtxt;
use rustc_session::lint;
use rustc_span::source_map;
use tracing::debug;
@ -167,8 +168,23 @@ fn resolve_block<'tcx>(visitor: &mut RegionResolutionVisitor<'tcx>, blk: &'tcx h
}
}
if let Some(tail_expr) = blk.expr {
if blk.span.edition().at_least_rust_2024() {
visitor.terminating_scopes.insert(tail_expr.hir_id.local_id);
let local_id = tail_expr.hir_id.local_id;
let edition = blk.span.edition();
if edition.at_least_rust_2024() {
visitor.terminating_scopes.insert(local_id);
} else if !visitor
.tcx
.lints_that_dont_need_to_run(())
.contains(&lint::LintId::of(lint::builtin::TAIL_EXPR_DROP_ORDER))
{
// If this temporary scope will be changing once the codebase adopts Rust 2024,
// and we are linting about possible semantic changes that would result,
// then record this node-id in the field `backwards_incompatible_scope`
// for future reference.
visitor
.scope_tree
.backwards_incompatible_scope
.insert(local_id, Scope { id: local_id, data: ScopeData::Node });
}
visitor.visit_expr(tail_expr);
}

View File

@ -460,6 +460,10 @@ impl<T: Idx> ChunkedBitSet<T> {
self.chunks.iter().map(|chunk| chunk.count()).sum()
}
pub fn is_empty(&self) -> bool {
self.chunks.iter().all(|chunk| matches!(chunk, Chunk::Zeros(..) | Chunk::Ones(0)))
}
/// Returns `true` if `self` contains `elem`.
#[inline]
pub fn contains(&self, elem: T) -> bool {
@ -668,12 +672,140 @@ impl<T: Idx> BitRelations<ChunkedBitSet<T>> for ChunkedBitSet<T> {
changed
}
fn subtract(&mut self, _other: &ChunkedBitSet<T>) -> bool {
unimplemented!("implement if/when necessary");
fn subtract(&mut self, other: &ChunkedBitSet<T>) -> bool {
assert_eq!(self.domain_size, other.domain_size);
debug_assert_eq!(self.chunks.len(), other.chunks.len());
let mut changed = false;
for (mut self_chunk, other_chunk) in self.chunks.iter_mut().zip(other.chunks.iter()) {
match (&mut self_chunk, &other_chunk) {
(Zeros(..), _) | (_, Zeros(..)) => {}
(
Ones(self_chunk_domain_size) | Mixed(self_chunk_domain_size, _, _),
Ones(other_chunk_domain_size),
) => {
debug_assert_eq!(self_chunk_domain_size, other_chunk_domain_size);
changed = true;
*self_chunk = Zeros(*self_chunk_domain_size);
}
(
Ones(self_chunk_domain_size),
Mixed(other_chunk_domain_size, other_chunk_count, other_chunk_words),
) => {
debug_assert_eq!(self_chunk_domain_size, other_chunk_domain_size);
changed = true;
let num_words = num_words(*self_chunk_domain_size as usize);
debug_assert!(num_words > 0 && num_words <= CHUNK_WORDS);
let mut tail_mask =
1 << (*other_chunk_domain_size - ((num_words - 1) * WORD_BITS) as u16) - 1;
let mut self_chunk_words = **other_chunk_words;
for word in self_chunk_words[0..num_words].iter_mut().rev() {
*word = !*word & tail_mask;
tail_mask = u64::MAX;
}
let self_chunk_count = *self_chunk_domain_size - *other_chunk_count;
debug_assert_eq!(
self_chunk_count,
self_chunk_words[0..num_words]
.iter()
.map(|w| w.count_ones() as ChunkSize)
.sum()
);
*self_chunk =
Mixed(*self_chunk_domain_size, self_chunk_count, Rc::new(self_chunk_words));
}
(
Mixed(
self_chunk_domain_size,
ref mut self_chunk_count,
ref mut self_chunk_words,
),
Mixed(_other_chunk_domain_size, _other_chunk_count, other_chunk_words),
) => {
// See [`<Self as BitRelations<ChunkedBitSet<T>>>::union`] for the explanation
let op = |a: u64, b: u64| a & !b;
let num_words = num_words(*self_chunk_domain_size as usize);
if bitwise_changes(
&self_chunk_words[0..num_words],
&other_chunk_words[0..num_words],
op,
) {
let self_chunk_words = Rc::make_mut(self_chunk_words);
let has_changed = bitwise(
&mut self_chunk_words[0..num_words],
&other_chunk_words[0..num_words],
op,
);
debug_assert!(has_changed);
*self_chunk_count = self_chunk_words[0..num_words]
.iter()
.map(|w| w.count_ones() as ChunkSize)
.sum();
if *self_chunk_count == 0 {
*self_chunk = Zeros(*self_chunk_domain_size);
}
changed = true;
}
}
}
}
changed
}
fn intersect(&mut self, _other: &ChunkedBitSet<T>) -> bool {
unimplemented!("implement if/when necessary");
fn intersect(&mut self, other: &ChunkedBitSet<T>) -> bool {
assert_eq!(self.domain_size, other.domain_size);
debug_assert_eq!(self.chunks.len(), other.chunks.len());
let mut changed = false;
for (mut self_chunk, other_chunk) in self.chunks.iter_mut().zip(other.chunks.iter()) {
match (&mut self_chunk, &other_chunk) {
(Zeros(..), _) | (_, Ones(..)) => {}
(
Ones(self_chunk_domain_size),
Zeros(other_chunk_domain_size) | Mixed(other_chunk_domain_size, ..),
)
| (Mixed(self_chunk_domain_size, ..), Zeros(other_chunk_domain_size)) => {
debug_assert_eq!(self_chunk_domain_size, other_chunk_domain_size);
changed = true;
*self_chunk = other_chunk.clone();
}
(
Mixed(
self_chunk_domain_size,
ref mut self_chunk_count,
ref mut self_chunk_words,
),
Mixed(_other_chunk_domain_size, _other_chunk_count, other_chunk_words),
) => {
// See [`<Self as BitRelations<ChunkedBitSet<T>>>::union`] for the explanation
let op = |a, b| a & b;
let num_words = num_words(*self_chunk_domain_size as usize);
if bitwise_changes(
&self_chunk_words[0..num_words],
&other_chunk_words[0..num_words],
op,
) {
let self_chunk_words = Rc::make_mut(self_chunk_words);
let has_changed = bitwise(
&mut self_chunk_words[0..num_words],
&other_chunk_words[0..num_words],
op,
);
debug_assert!(has_changed);
*self_chunk_count = self_chunk_words[0..num_words]
.iter()
.map(|w| w.count_ones() as ChunkSize)
.sum();
if *self_chunk_count == 0 {
*self_chunk = Zeros(*self_chunk_domain_size);
}
changed = true;
}
}
}
}
changed
}
}

View File

@ -772,9 +772,6 @@ lint_suspicious_double_ref_clone =
lint_suspicious_double_ref_deref =
using `.deref()` on a double reference, which returns `{$ty}` instead of dereferencing the inner type
lint_tail_expr_drop_order = these values and local bindings have significant drop implementation that will have a different drop order from that of Edition 2021
.label = these values have significant drop implementation and will observe changes in drop order under Edition 2024
lint_trailing_semi_macro = trailing semicolon in macro used in expression position
.note1 = macro invocations at the end of a block are treated as expressions
.note2 = to ignore the value produced by the macro, add a semicolon after the invocation of `{$name}`

View File

@ -75,7 +75,6 @@ mod redundant_semicolon;
mod reference_casting;
mod shadowed_into_iter;
mod static_mut_refs;
mod tail_expr_drop_order;
mod traits;
mod types;
mod unit_bindings;
@ -116,7 +115,6 @@ use rustc_middle::ty::TyCtxt;
use shadowed_into_iter::ShadowedIntoIter;
pub use shadowed_into_iter::{ARRAY_INTO_ITER, BOXED_SLICE_INTO_ITER};
use static_mut_refs::*;
use tail_expr_drop_order::TailExprDropOrder;
use traits::*;
use types::*;
use unit_bindings::*;
@ -240,7 +238,6 @@ late_lint_methods!(
AsyncFnInTrait: AsyncFnInTrait,
NonLocalDefinitions: NonLocalDefinitions::default(),
ImplTraitOvercaptures: ImplTraitOvercaptures,
TailExprDropOrder: TailExprDropOrder,
IfLetRescope: IfLetRescope::default(),
StaticMutRefs: StaticMutRefs,
UnqualifiedLocalImports: UnqualifiedLocalImports,

View File

@ -1,308 +0,0 @@
use std::mem::swap;
use rustc_ast::UnOp;
use rustc_hir::def::Res;
use rustc_hir::intravisit::{self, Visitor};
use rustc_hir::{self as hir, Block, Expr, ExprKind, LetStmt, Pat, PatKind, QPath, StmtKind};
use rustc_macros::LintDiagnostic;
use rustc_middle::ty;
use rustc_session::lint::FutureIncompatibilityReason;
use rustc_session::{declare_lint, declare_lint_pass};
use rustc_span::Span;
use rustc_span::edition::Edition;
use crate::{LateContext, LateLintPass};
declare_lint! {
/// The `tail_expr_drop_order` lint looks for those values generated at the tail expression location,
/// that runs a custom `Drop` destructor.
/// Some of them may be dropped earlier in Edition 2024 that they used to in Edition 2021 and prior.
/// This lint detects those cases and provides you information on those values and their custom destructor implementations.
/// Your discretion on this information is required.
///
/// ### Example
/// ```rust,edition2021
/// #![warn(tail_expr_drop_order)]
/// struct Droppy(i32);
/// impl Droppy {
/// fn get(&self) -> i32 {
/// self.0
/// }
/// }
/// impl Drop for Droppy {
/// fn drop(&mut self) {
/// // This is a custom destructor and it induces side-effects that is observable
/// // especially when the drop order at a tail expression changes.
/// println!("loud drop {}", self.0);
/// }
/// }
/// fn edition_2021() -> i32 {
/// let another_droppy = Droppy(0);
/// Droppy(1).get()
/// }
/// fn main() {
/// edition_2021();
/// }
/// ```
///
/// {{produces}}
///
/// ### Explanation
///
/// In tail expression of blocks or function bodies,
/// values of type with significant `Drop` implementation has an ill-specified drop order
/// before Edition 2024 so that they are dropped only after dropping local variables.
/// Edition 2024 introduces a new rule with drop orders for them,
/// so that they are dropped first before dropping local variables.
///
/// A significant `Drop::drop` destructor here refers to an explicit, arbitrary
/// implementation of the `Drop` trait on the type, with exceptions including `Vec`,
/// `Box`, `Rc`, `BTreeMap` and `HashMap` that are marked by the compiler otherwise
/// so long that the generic types have no significant destructor recursively.
/// In other words, a type has a significant drop destructor when it has a `Drop` implementation
/// or its destructor invokes a significant destructor on a type.
/// Since we cannot completely reason about the change by just inspecting the existence of
/// a significant destructor, this lint remains only a suggestion and is set to `allow` by default.
///
/// This lint only points out the issue with `Droppy`, which will be dropped before `another_droppy`
/// does in Edition 2024.
/// No fix will be proposed by this lint.
/// However, the most probable fix is to hoist `Droppy` into its own local variable binding.
/// ```rust
/// struct Droppy(i32);
/// impl Droppy {
/// fn get(&self) -> i32 {
/// self.0
/// }
/// }
/// fn edition_2024() -> i32 {
/// let value = Droppy(0);
/// let another_droppy = Droppy(1);
/// value.get()
/// }
/// ```
pub TAIL_EXPR_DROP_ORDER,
Allow,
"Detect and warn on significant change in drop order in tail expression location",
@future_incompatible = FutureIncompatibleInfo {
reason: FutureIncompatibilityReason::EditionSemanticsChange(Edition::Edition2024),
reference: "issue #123739 <https://github.com/rust-lang/rust/issues/123739>",
};
}
declare_lint_pass!(TailExprDropOrder => [TAIL_EXPR_DROP_ORDER]);
impl TailExprDropOrder {
fn check_fn_or_closure<'tcx>(
cx: &LateContext<'tcx>,
fn_kind: hir::intravisit::FnKind<'tcx>,
body: &'tcx hir::Body<'tcx>,
def_id: rustc_span::def_id::LocalDefId,
) {
let mut locals = vec![];
if matches!(fn_kind, hir::intravisit::FnKind::Closure) {
for &capture in cx.tcx.closure_captures(def_id) {
if matches!(capture.info.capture_kind, ty::UpvarCapture::ByValue)
&& capture.place.ty().has_significant_drop(cx.tcx, cx.typing_env())
{
locals.push(capture.var_ident.span);
}
}
}
for param in body.params {
if cx
.typeck_results()
.node_type(param.hir_id)
.has_significant_drop(cx.tcx, cx.typing_env())
{
locals.push(param.span);
}
}
if let hir::ExprKind::Block(block, _) = body.value.kind {
LintVisitor { cx, locals }.check_block_inner(block);
} else {
LintTailExpr { cx, locals: &locals, is_root_tail_expr: true }.visit_expr(body.value);
}
}
}
impl<'tcx> LateLintPass<'tcx> for TailExprDropOrder {
fn check_fn(
&mut self,
cx: &LateContext<'tcx>,
fn_kind: hir::intravisit::FnKind<'tcx>,
_: &'tcx hir::FnDecl<'tcx>,
body: &'tcx hir::Body<'tcx>,
_: Span,
def_id: rustc_span::def_id::LocalDefId,
) {
if !body.value.span.edition().at_least_rust_2024() {
Self::check_fn_or_closure(cx, fn_kind, body, def_id);
}
}
}
struct LintVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
// We only record locals that have significant drops
locals: Vec<Span>,
}
struct LocalCollector<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
locals: &'a mut Vec<Span>,
}
impl<'a, 'tcx> Visitor<'tcx> for LocalCollector<'a, 'tcx> {
type Result = ();
fn visit_pat(&mut self, pat: &'tcx Pat<'tcx>) {
if let PatKind::Binding(_binding_mode, id, ident, pat) = pat.kind {
let ty = self.cx.typeck_results().node_type(id);
if ty.has_significant_drop(self.cx.tcx, self.cx.typing_env()) {
self.locals.push(ident.span);
}
if let Some(pat) = pat {
self.visit_pat(pat);
}
} else {
intravisit::walk_pat(self, pat);
}
}
}
impl<'a, 'tcx> Visitor<'tcx> for LintVisitor<'a, 'tcx> {
fn visit_block(&mut self, block: &'tcx Block<'tcx>) {
let mut locals = <_>::default();
swap(&mut locals, &mut self.locals);
self.check_block_inner(block);
swap(&mut locals, &mut self.locals);
}
fn visit_local(&mut self, local: &'tcx LetStmt<'tcx>) {
LocalCollector { cx: self.cx, locals: &mut self.locals }.visit_local(local);
}
}
impl<'a, 'tcx> LintVisitor<'a, 'tcx> {
fn check_block_inner(&mut self, block: &Block<'tcx>) {
if block.span.at_least_rust_2024() {
// We only lint up to Edition 2021
return;
}
let Some(tail_expr) = block.expr else { return };
for stmt in block.stmts {
match stmt.kind {
StmtKind::Let(let_stmt) => self.visit_local(let_stmt),
StmtKind::Item(_) => {}
StmtKind::Expr(e) | StmtKind::Semi(e) => self.visit_expr(e),
}
}
if self.locals.is_empty() {
return;
}
LintTailExpr { cx: self.cx, locals: &self.locals, is_root_tail_expr: true }
.visit_expr(tail_expr);
}
}
struct LintTailExpr<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
is_root_tail_expr: bool,
locals: &'a [Span],
}
impl<'a, 'tcx> LintTailExpr<'a, 'tcx> {
fn expr_eventually_point_into_local(mut expr: &Expr<'tcx>) -> bool {
loop {
match expr.kind {
ExprKind::Index(access, _, _) | ExprKind::Field(access, _) => expr = access,
ExprKind::AddrOf(_, _, referee) | ExprKind::Unary(UnOp::Deref, referee) => {
expr = referee
}
ExprKind::Path(_)
if let ExprKind::Path(QPath::Resolved(_, path)) = expr.kind
&& let [local, ..] = path.segments
&& let Res::Local(_) = local.res =>
{
return true;
}
_ => return false,
}
}
}
fn expr_generates_nonlocal_droppy_value(&self, expr: &Expr<'tcx>) -> bool {
if Self::expr_eventually_point_into_local(expr) {
return false;
}
self.cx
.typeck_results()
.expr_ty(expr)
.has_significant_drop(self.cx.tcx, self.cx.typing_env())
}
}
impl<'a, 'tcx> Visitor<'tcx> for LintTailExpr<'a, 'tcx> {
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
if self.is_root_tail_expr {
self.is_root_tail_expr = false;
} else if self.expr_generates_nonlocal_droppy_value(expr) {
self.cx.tcx.emit_node_span_lint(
TAIL_EXPR_DROP_ORDER,
expr.hir_id,
expr.span,
TailExprDropOrderLint { spans: self.locals.to_vec() },
);
return;
}
match expr.kind {
ExprKind::Match(scrutinee, _, _) => self.visit_expr(scrutinee),
ExprKind::ConstBlock(_)
| ExprKind::Array(_)
| ExprKind::Break(_, _)
| ExprKind::Continue(_)
| ExprKind::Ret(_)
| ExprKind::Become(_)
| ExprKind::Yield(_, _)
| ExprKind::InlineAsm(_)
| ExprKind::If(_, _, _)
| ExprKind::Loop(_, _, _, _)
| ExprKind::Closure(_)
| ExprKind::DropTemps(_)
| ExprKind::OffsetOf(_, _)
| ExprKind::Assign(_, _, _)
| ExprKind::AssignOp(_, _, _)
| ExprKind::Lit(_)
| ExprKind::Err(_) => {}
ExprKind::MethodCall(_, _, _, _)
| ExprKind::Call(_, _)
| ExprKind::Type(_, _)
| ExprKind::Tup(_)
| ExprKind::Binary(_, _, _)
| ExprKind::Unary(_, _)
| ExprKind::Path(_)
| ExprKind::Let(_)
| ExprKind::Cast(_, _)
| ExprKind::Field(_, _)
| ExprKind::Index(_, _, _)
| ExprKind::AddrOf(_, _, _)
| ExprKind::Struct(_, _, _)
| ExprKind::Repeat(_, _) => intravisit::walk_expr(self, expr),
ExprKind::Block(_, _) => {
// We do not lint further because the drop order stays the same inside the block
}
}
}
fn visit_block(&mut self, block: &'tcx Block<'tcx>) {
LintVisitor { cx: self.cx, locals: <_>::default() }.check_block_inner(block);
}
}
#[derive(LintDiagnostic)]
#[diag(lint_tail_expr_drop_order)]
struct TailExprDropOrderLint {
#[label]
pub spans: Vec<Span>,
}

View File

@ -101,6 +101,7 @@ declare_lint_pass! {
SINGLE_USE_LIFETIMES,
SOFT_UNSTABLE,
STABLE_FEATURES,
TAIL_EXPR_DROP_ORDER,
TEST_UNSTABLE_LINT,
TEXT_DIRECTION_CODEPOINT_IN_COMMENT,
TRIVIAL_CASTS,
@ -4994,6 +4995,83 @@ declare_lint! {
"detects pointer to integer transmutes in const functions and associated constants",
}
declare_lint! {
/// The `tail_expr_drop_order` lint looks for those values generated at the tail expression location,
/// that runs a custom `Drop` destructor.
/// Some of them may be dropped earlier in Edition 2024 that they used to in Edition 2021 and prior.
/// This lint detects those cases and provides you information on those values and their custom destructor implementations.
/// Your discretion on this information is required.
///
/// ### Example
/// ```rust,edition2021
/// #![warn(tail_expr_drop_order)]
/// struct Droppy(i32);
/// impl Droppy {
/// fn get(&self) -> i32 {
/// self.0
/// }
/// }
/// impl Drop for Droppy {
/// fn drop(&mut self) {
/// // This is a custom destructor and it induces side-effects that is observable
/// // especially when the drop order at a tail expression changes.
/// println!("loud drop {}", self.0);
/// }
/// }
/// fn edition_2021() -> i32 {
/// let another_droppy = Droppy(0);
/// Droppy(1).get()
/// }
/// fn main() {
/// edition_2021();
/// }
/// ```
///
/// {{produces}}
///
/// ### Explanation
///
/// In tail expression of blocks or function bodies,
/// values of type with significant `Drop` implementation has an ill-specified drop order
/// before Edition 2024 so that they are dropped only after dropping local variables.
/// Edition 2024 introduces a new rule with drop orders for them,
/// so that they are dropped first before dropping local variables.
///
/// A significant `Drop::drop` destructor here refers to an explicit, arbitrary
/// implementation of the `Drop` trait on the type, with exceptions including `Vec`,
/// `Box`, `Rc`, `BTreeMap` and `HashMap` that are marked by the compiler otherwise
/// so long that the generic types have no significant destructor recursively.
/// In other words, a type has a significant drop destructor when it has a `Drop` implementation
/// or its destructor invokes a significant destructor on a type.
/// Since we cannot completely reason about the change by just inspecting the existence of
/// a significant destructor, this lint remains only a suggestion and is set to `allow` by default.
///
/// This lint only points out the issue with `Droppy`, which will be dropped before `another_droppy`
/// does in Edition 2024.
/// No fix will be proposed by this lint.
/// However, the most probable fix is to hoist `Droppy` into its own local variable binding.
/// ```rust
/// struct Droppy(i32);
/// impl Droppy {
/// fn get(&self) -> i32 {
/// self.0
/// }
/// }
/// fn edition_2024() -> i32 {
/// let value = Droppy(0);
/// let another_droppy = Droppy(1);
/// value.get()
/// }
/// ```
pub TAIL_EXPR_DROP_ORDER,
Allow,
"Detect and warn on significant change in drop order in tail expression location",
@future_incompatible = FutureIncompatibleInfo {
reason: FutureIncompatibilityReason::EditionSemanticsChange(Edition::Edition2024),
reference: "issue #123739 <https://github.com/rust-lang/rust/issues/123739>",
};
}
declare_lint! {
/// The `rust_2024_guarded_string_incompatible_syntax` lint detects `#` tokens
/// that will be parsed as part of a guarded string literal in Rust 2024.

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

View File

@ -1,6 +1,5 @@
//! See docs in build/expr/mod.rs
use rustc_middle::middle::region;
use rustc_middle::mir::*;
use rustc_middle::thir::*;
use tracing::{debug, instrument};
@ -9,6 +8,12 @@ use crate::build::expr::category::Category;
use crate::build::{BlockAnd, BlockAndExtension, Builder, NeedsTemporary};
impl<'a, 'tcx> Builder<'a, 'tcx> {
/// Construct a temporary lifetime restricted to just the local scope
pub(crate) fn local_temp_lifetime(&self) -> TempLifetime {
let local_scope = self.local_scope();
TempLifetime { temp_lifetime: Some(local_scope), backwards_incompatible: None }
}
/// Returns an operand suitable for use until the end of the current
/// scope expression.
///
@ -21,8 +26,13 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
block: BasicBlock,
expr_id: ExprId,
) -> BlockAnd<Operand<'tcx>> {
let local_scope = self.local_scope();
self.as_operand(block, Some(local_scope), expr_id, LocalInfo::Boring, NeedsTemporary::Maybe)
self.as_operand(
block,
self.local_temp_lifetime(),
expr_id,
LocalInfo::Boring,
NeedsTemporary::Maybe,
)
}
/// Returns an operand suitable for use until the end of the current scope expression and
@ -80,8 +90,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
block: BasicBlock,
expr: ExprId,
) -> BlockAnd<Operand<'tcx>> {
let local_scope = self.local_scope();
self.as_call_operand(block, Some(local_scope), expr)
self.as_call_operand(block, self.local_temp_lifetime(), expr)
}
/// Compile `expr` into a value that can be used as an operand.
@ -102,7 +111,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
pub(crate) fn as_operand(
&mut self,
mut block: BasicBlock,
scope: Option<region::Scope>,
scope: TempLifetime,
expr_id: ExprId,
local_info: LocalInfo<'tcx>,
needs_temporary: NeedsTemporary,
@ -146,7 +155,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
pub(crate) fn as_call_operand(
&mut self,
mut block: BasicBlock,
scope: Option<region::Scope>,
scope: TempLifetime,
expr_id: ExprId,
) -> BlockAnd<Operand<'tcx>> {
let this = self;

View File

@ -6,7 +6,6 @@ use std::iter;
use rustc_abi::{FIRST_VARIANT, FieldIdx, VariantIdx};
use rustc_hir::def_id::LocalDefId;
use rustc_middle::hir::place::{Projection as HirProjection, ProjectionKind as HirProjectionKind};
use rustc_middle::middle::region;
use rustc_middle::mir::AssertKind::BoundsCheck;
use rustc_middle::mir::*;
use rustc_middle::thir::*;
@ -598,7 +597,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
index: ExprId,
mutability: Mutability,
fake_borrow_temps: Option<&mut Vec<Local>>,
temp_lifetime: Option<region::Scope>,
temp_lifetime: TempLifetime,
expr_span: Span,
source_info: SourceInfo,
) -> BlockAnd<PlaceBuilder<'tcx>> {

View File

@ -33,14 +33,18 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
expr_id: ExprId,
) -> BlockAnd<Rvalue<'tcx>> {
let local_scope = self.local_scope();
self.as_rvalue(block, Some(local_scope), expr_id)
self.as_rvalue(
block,
TempLifetime { temp_lifetime: Some(local_scope), backwards_incompatible: None },
expr_id,
)
}
/// Compile `expr`, yielding an rvalue.
pub(crate) fn as_rvalue(
&mut self,
mut block: BasicBlock,
scope: Option<region::Scope>,
scope: TempLifetime,
expr_id: ExprId,
) -> BlockAnd<Rvalue<'tcx>> {
let this = self;
@ -171,7 +175,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
source_info,
kind: StatementKind::StorageLive(result),
});
if let Some(scope) = scope {
if let Some(scope) = scope.temp_lifetime {
// schedule a shallow free of that memory, lest we unwind:
this.schedule_drop_storage_and_value(expr_span, scope, result);
}
@ -445,7 +449,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
block = this.limit_capture_mutability(
upvar_expr.span,
upvar_expr.ty,
scope,
scope.temp_lifetime,
block,
arg,
)
@ -705,7 +709,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
&mut self,
mut block: BasicBlock,
value: ExprId,
scope: Option<region::Scope>,
scope: TempLifetime,
outer_source_info: SourceInfo,
) -> BlockAnd<Rvalue<'tcx>> {
let this = self;

View File

@ -2,7 +2,6 @@
use rustc_data_structures::stack::ensure_sufficient_stack;
use rustc_hir::HirId;
use rustc_middle::middle::region;
use rustc_middle::middle::region::{Scope, ScopeData};
use rustc_middle::mir::*;
use rustc_middle::thir::*;
@ -17,7 +16,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
pub(crate) fn as_temp(
&mut self,
block: BasicBlock,
temp_lifetime: Option<region::Scope>,
temp_lifetime: TempLifetime,
expr_id: ExprId,
mutability: Mutability,
) -> BlockAnd<Local> {
@ -31,7 +30,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
fn as_temp_inner(
&mut self,
mut block: BasicBlock,
temp_lifetime: Option<region::Scope>,
temp_lifetime: TempLifetime,
expr_id: ExprId,
mutability: Mutability,
) -> BlockAnd<Local> {
@ -47,8 +46,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
}
let expr_ty = expr.ty;
let deduplicate_temps =
this.fixed_temps_scope.is_some() && this.fixed_temps_scope == temp_lifetime;
let deduplicate_temps = this.fixed_temps_scope.is_some()
&& this.fixed_temps_scope == temp_lifetime.temp_lifetime;
let temp = if deduplicate_temps && let Some(temp_index) = this.fixed_temps.get(&expr_id) {
*temp_index
} else {
@ -76,7 +75,9 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
LocalInfo::BlockTailTemp(tail_info)
}
_ if let Some(Scope { data: ScopeData::IfThenRescope, id }) = temp_lifetime => {
_ if let Some(Scope { data: ScopeData::IfThenRescope, id }) =
temp_lifetime.temp_lifetime =>
{
LocalInfo::IfThenRescopeTemp {
if_then: HirId { owner: this.hir_id.owner, local_id: id },
}
@ -117,7 +118,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
// Anything with a shorter lifetime (e.g the `&foo()` in
// `bar(&foo())` or anything within a block will keep the
// regular drops just like runtime code.
if let Some(temp_lifetime) = temp_lifetime {
if let Some(temp_lifetime) = temp_lifetime.temp_lifetime {
this.schedule_drop(expr_span, temp_lifetime, temp, DropKind::Storage);
}
}
@ -125,10 +126,14 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
block = this.expr_into_dest(temp_place, block, expr_id).into_block();
if let Some(temp_lifetime) = temp_lifetime {
if let Some(temp_lifetime) = temp_lifetime.temp_lifetime {
this.schedule_drop(expr_span, temp_lifetime, temp, DropKind::Value);
}
if let Some(backwards_incompatible) = temp_lifetime.backwards_incompatible {
this.schedule_backwards_incompatible_drop(expr_span, backwards_incompatible, temp);
}
block.and(temp)
}
}

View File

@ -139,7 +139,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
// (#66975) Source could be a const of type `!`, so has to
// exist in the generated MIR.
unpack!(
block = this.as_temp(block, Some(this.local_scope()), source, Mutability::Mut)
block =
this.as_temp(block, this.local_temp_lifetime(), source, Mutability::Mut)
);
// This is an optimization. If the expression was a call then we already have an
@ -321,7 +322,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
let is_union = adt_def.is_union();
let active_field_index = is_union.then(|| fields[0].name);
let scope = this.local_scope();
let scope = this.local_temp_lifetime();
// first process the set of fields that were provided
// (evaluating them in order given by user)
@ -333,7 +334,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
unpack!(
block = this.as_operand(
block,
Some(scope),
scope,
f.expr,
LocalInfo::AggregateTemp,
NeedsTemporary::Maybe,
@ -548,15 +549,10 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
}
ExprKind::Yield { value } => {
let scope = this.local_scope();
let scope = this.local_temp_lifetime();
let value = unpack!(
block = this.as_operand(
block,
Some(scope),
value,
LocalInfo::Boring,
NeedsTemporary::No
)
block =
this.as_operand(block, scope, value, LocalInfo::Boring, NeedsTemporary::No)
);
let resume = this.cfg.start_new_block();
this.cfg.terminate(block, source_info, TerminatorKind::Yield {

View File

@ -172,8 +172,17 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
None
};
let temp =
unpack!(block = this.as_temp(block, statement_scope, expr_id, Mutability::Not));
let temp = unpack!(
block = this.as_temp(
block,
TempLifetime {
temp_lifetime: statement_scope,
backwards_incompatible: None
},
expr_id,
Mutability::Not
)
);
if let Some(span) = adjusted_span {
this.local_decls[temp].source_info.span = span;

View File

@ -202,8 +202,17 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
// Increment the decision depth, in case we encounter boolean expressions
// further down.
this.mcdc_increment_depth_if_enabled();
let place =
unpack!(block = this.as_temp(block, Some(temp_scope), expr_id, mutability));
let place = unpack!(
block = this.as_temp(
block,
TempLifetime {
temp_lifetime: Some(temp_scope),
backwards_incompatible: None
},
expr_id,
mutability
)
);
this.mcdc_decrement_depth_if_enabled();
let operand = Operand::Move(Place::from(place));

View File

@ -89,7 +89,7 @@ use rustc_index::{IndexSlice, IndexVec};
use rustc_middle::middle::region;
use rustc_middle::mir::*;
use rustc_middle::thir::{ExprId, LintLevel};
use rustc_middle::{bug, span_bug};
use rustc_middle::{bug, span_bug, ty};
use rustc_session::lint::Level;
use rustc_span::source_map::Spanned;
use rustc_span::{DUMMY_SP, Span};
@ -151,6 +151,9 @@ struct DropData {
/// Whether this is a value Drop or a StorageDead.
kind: DropKind,
/// Whether this is a backwards-incompatible drop lint
backwards_incompatible_lint: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@ -274,8 +277,12 @@ impl DropTree {
// represents the block in the tree that should be jumped to once all
// of the required drops have been performed.
let fake_source_info = SourceInfo::outermost(DUMMY_SP);
let fake_data =
DropData { source_info: fake_source_info, local: Local::MAX, kind: DropKind::Storage };
let fake_data = DropData {
source_info: fake_source_info,
local: Local::MAX,
kind: DropKind::Storage,
backwards_incompatible_lint: false,
};
let drops = IndexVec::from_raw(vec![DropNode { data: fake_data, next: DropIdx::MAX }]);
Self { drops, entry_points: Vec::new(), existing_drops_map: FxHashMap::default() }
}
@ -763,7 +770,12 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
let local =
place.as_local().unwrap_or_else(|| bug!("projection in tail call args"));
Some(DropData { source_info, local, kind: DropKind::Value })
Some(DropData {
source_info,
local,
kind: DropKind::Value,
backwards_incompatible_lint: false,
})
}
Operand::Constant(_) => None,
})
@ -1019,9 +1031,9 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
if local.index() <= self.arg_count {
span_bug!(
span,
"`schedule_drop` called with local {:?} and arg_count {}",
"`schedule_drop` called with body argument {:?} \
but its storage does not require a drop",
local,
self.arg_count,
)
}
false
@ -1089,6 +1101,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
source_info: SourceInfo { span: scope_end, scope: scope.source_scope },
local,
kind: drop_kind,
backwards_incompatible_lint: false,
});
return;
@ -1098,6 +1111,45 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
span_bug!(span, "region scope {:?} not in scope to drop {:?}", region_scope, local);
}
/// Schedule emission of a backwards incompatible drop lint hint.
/// Applicable only to temporary values for now.
pub(crate) fn schedule_backwards_incompatible_drop(
&mut self,
span: Span,
region_scope: region::Scope,
local: Local,
) {
if !self.local_decls[local].ty.has_significant_drop(self.tcx, ty::TypingEnv {
typing_mode: ty::TypingMode::non_body_analysis(),
param_env: self.param_env,
}) {
return;
}
for scope in self.scopes.scopes.iter_mut().rev() {
// Since we are inserting linting MIR statement, we have to invalidate the caches
scope.invalidate_cache();
if scope.region_scope == region_scope {
let region_scope_span = region_scope.span(self.tcx, self.region_scope_tree);
let scope_end = self.tcx.sess.source_map().end_point(region_scope_span);
scope.drops.push(DropData {
source_info: SourceInfo { span: scope_end, scope: scope.source_scope },
local,
kind: DropKind::Value,
backwards_incompatible_lint: true,
});
return;
}
}
span_bug!(
span,
"region scope {:?} not in scope to drop {:?} for linting",
region_scope,
local
);
}
/// Indicates that the "local operand" stored in `local` is
/// *moved* at some point during execution (see `local_scope` for
/// more information about what a "local operand" is -- in short,
@ -1378,16 +1430,25 @@ fn build_scope_drops<'tcx>(
continue;
}
unwind_drops.add_entry_point(block, unwind_to);
let next = cfg.start_new_block();
cfg.terminate(block, source_info, TerminatorKind::Drop {
place: local.into(),
target: next,
unwind: UnwindAction::Continue,
replace: false,
});
block = next;
if drop_data.backwards_incompatible_lint {
cfg.push(block, Statement {
source_info,
kind: StatementKind::BackwardIncompatibleDropHint {
place: Box::new(local.into()),
reason: BackwardIncompatibleDropReason::Edition2024,
},
});
} else {
unwind_drops.add_entry_point(block, unwind_to);
let next = cfg.start_new_block();
cfg.terminate(block, source_info, TerminatorKind::Drop {
place: local.into(),
target: next,
unwind: UnwindAction::Continue,
replace: false,
});
block = next;
}
}
DropKind::Storage => {
if storage_dead_on_unwind {

View File

@ -23,7 +23,6 @@ use tracing::{debug, info, instrument, trace};
use crate::errors;
use crate::thir::cx::Cx;
use crate::thir::cx::region::Scope;
use crate::thir::util::UserAnnotatedTyHelpers;
impl<'tcx> Cx<'tcx> {
@ -240,7 +239,7 @@ impl<'tcx> Cx<'tcx> {
fn mirror_expr_cast(
&mut self,
source: &'tcx hir::Expr<'tcx>,
temp_lifetime: Option<Scope>,
temp_lifetime: TempLifetime,
span: Span,
) -> ExprKind<'tcx> {
let tcx = self.tcx;
@ -325,7 +324,7 @@ impl<'tcx> Cx<'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 =
let (temp_lifetime, backwards_incompatible) =
self.rvalue_scopes.temporary_scope(self.region_scope_tree, expr.hir_id.local_id);
let kind = match expr.kind {
@ -361,7 +360,7 @@ impl<'tcx> Cx<'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,
temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible },
span: expr.span,
kind: ExprKind::Tuple { fields: self.mirror_exprs(args) },
};
@ -391,7 +390,10 @@ impl<'tcx> Cx<'tcx> {
&& let [value] = args
{
return Expr {
temp_lifetime,
temp_lifetime: TempLifetime {
temp_lifetime,
backwards_incompatible,
},
ty: expr_ty,
span: expr.span,
kind: ExprKind::Box { value: self.mirror_expr(value) },
@ -811,13 +813,13 @@ impl<'tcx> Cx<'tcx> {
},
hir::ExprKind::Loop(body, ..) => {
let block_ty = self.typeck_results().node_type(body.hir_id);
let temp_lifetime = self
let (temp_lifetime, backwards_incompatible) = self
.rvalue_scopes
.temporary_scope(self.region_scope_tree, body.hir_id.local_id);
let block = self.mirror_block(body);
let body = self.thir.exprs.push(Expr {
ty: block_ty,
temp_lifetime,
temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible },
span: self.thir[block].span,
kind: ExprKind::Block { block },
});
@ -838,13 +840,17 @@ impl<'tcx> Cx<'tcx> {
expr, cast_ty.hir_id, user_ty,
);
let cast = self.mirror_expr_cast(source, temp_lifetime, expr.span);
let cast = self.mirror_expr_cast(
source,
TempLifetime { temp_lifetime, backwards_incompatible },
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,
temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible },
ty: expr_ty,
span: expr.span,
kind: cast,
@ -887,7 +893,12 @@ impl<'tcx> Cx<'tcx> {
hir::ExprKind::Err(_) => unreachable!("cannot lower a `hir::ExprKind::Err` to THIR"),
};
Expr { temp_lifetime, ty: expr_ty, span: expr.span, kind }
Expr {
temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible },
ty: expr_ty,
span: expr.span,
kind,
}
}
fn user_args_applied_to_res(
@ -931,7 +942,7 @@ impl<'tcx> Cx<'tcx> {
span: Span,
overloaded_callee: Option<Ty<'tcx>>,
) -> Expr<'tcx> {
let temp_lifetime =
let (temp_lifetime, backwards_incompatible) =
self.rvalue_scopes.temporary_scope(self.region_scope_tree, expr.hir_id.local_id);
let (ty, user_ty) = match overloaded_callee {
Some(fn_def) => (fn_def, None),
@ -952,7 +963,12 @@ impl<'tcx> Cx<'tcx> {
)
}
};
Expr { temp_lifetime, ty, span, kind: ExprKind::ZstLiteral { user_ty } }
Expr {
temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible },
ty,
span,
kind: ExprKind::ZstLiteral { user_ty },
}
}
fn convert_arm(&mut self, arm: &'tcx hir::Arm<'tcx>) -> ArmId {
@ -1025,7 +1041,7 @@ impl<'tcx> Cx<'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 = self
let (temp_lifetime, backwards_incompatible) = self
.rvalue_scopes
.temporary_scope(self.region_scope_tree, expr.hir_id.local_id);
let kind = if self.tcx.is_thread_local_static(id) {
@ -1035,7 +1051,12 @@ impl<'tcx> Cx<'tcx> {
ExprKind::StaticRef { alloc_id, ty, def_id: id }
};
ExprKind::Deref {
arg: self.thir.exprs.push(Expr { ty, temp_lifetime, span: expr.span, kind }),
arg: self.thir.exprs.push(Expr {
ty,
temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible },
span: expr.span,
kind,
}),
}
}
@ -1106,13 +1127,13 @@ impl<'tcx> Cx<'tcx> {
// construct the complete expression `foo()` for the overloaded call,
// which will yield the &T type
let temp_lifetime =
let (temp_lifetime, backwards_incompatible) =
self.rvalue_scopes.temporary_scope(self.region_scope_tree, 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,
temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible },
ty: ref_ty,
span,
kind: ExprKind::Call { ty: fun_ty, fun, args, from_hir_call: false, fn_span: span },
@ -1127,7 +1148,7 @@ impl<'tcx> Cx<'tcx> {
closure_expr: &'tcx hir::Expr<'tcx>,
place: HirPlace<'tcx>,
) -> Expr<'tcx> {
let temp_lifetime = self
let (temp_lifetime, backwards_incompatible) = self
.rvalue_scopes
.temporary_scope(self.region_scope_tree, closure_expr.hir_id.local_id);
let var_ty = place.base_ty;
@ -1143,7 +1164,7 @@ impl<'tcx> Cx<'tcx> {
};
let mut captured_place_expr = Expr {
temp_lifetime,
temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible },
ty: var_ty,
span: closure_expr.span,
kind: self.convert_var(var_hir_id),
@ -1168,8 +1189,12 @@ impl<'tcx> Cx<'tcx> {
}
};
captured_place_expr =
Expr { temp_lifetime, ty: proj.ty, span: closure_expr.span, kind };
captured_place_expr = Expr {
temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible },
ty: proj.ty,
span: closure_expr.span,
kind,
};
}
captured_place_expr
@ -1184,7 +1209,7 @@ impl<'tcx> Cx<'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 = self
let (temp_lifetime, backwards_incompatible) = self
.rvalue_scopes
.temporary_scope(self.region_scope_tree, closure_expr.hir_id.local_id);
@ -1201,7 +1226,7 @@ impl<'tcx> Cx<'tcx> {
}
};
Expr {
temp_lifetime,
temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible },
ty: upvar_ty,
span: closure_expr.span,
kind: ExprKind::Borrow {

View File

@ -253,6 +253,7 @@ impl<'a, 'tcx> Analysis<'tcx> for MaybeTransitiveLiveLocals<'a> {
| StatementKind::Coverage(..)
| StatementKind::Intrinsic(..)
| StatementKind::ConstEvalCounter
| StatementKind::BackwardIncompatibleDropHint { .. }
| StatementKind::Nop => None,
};
if let Some(destination) = destination {

View File

@ -157,6 +157,7 @@ impl<'tcx> Analysis<'tcx> for MaybeRequiresStorage<'_, 'tcx> {
| StatementKind::Nop
| StatementKind::Retag(..)
| StatementKind::Intrinsic(..)
| StatementKind::BackwardIncompatibleDropHint { .. }
| StatementKind::StorageLive(..) => {}
}
}

View File

@ -385,6 +385,7 @@ impl<'a, 'tcx, F: Fn(Ty<'tcx>) -> bool> MoveDataBuilder<'a, 'tcx, F> {
| StatementKind::Coverage(..)
| StatementKind::Intrinsic(..)
| StatementKind::ConstEvalCounter
| StatementKind::BackwardIncompatibleDropHint { .. }
| StatementKind::Nop => {}
}
}

View File

@ -25,6 +25,31 @@ mir_transform_must_not_suspend = {$pre}`{$def_path}`{$post} held across a suspen
.help = consider using a block (`{"{ ... }"}`) to shrink the value's scope, ending before the suspend point
mir_transform_operation_will_panic = this operation will panic at runtime
mir_transform_tail_expr_drop_order = relative drop order changing in Rust 2024
.temporaries = in Rust 2024, this temporary value will be dropped first
.observers = in Rust 2024, this local variable or temporary value will be dropped second
.note_dtors =
dropping the temporary value runs this custom `Drop` impl, which we could not prove to be side-effect free
.note_observer_dtors =
dropping the local runs this custom `Drop` impl, which we could not prove to be side-effect free
.drop_location =
now the temporary value is dropped here, before the local variables in the block or statement
.note_epilogue = most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects like releasing locks or sending messages
.label_local_epilogue = {$is_dropped_first_edition_2024 ->
[true] up until Edition 2021 `{$name}` is dropped last but will be dropped earlier in Edition 2024
*[false] `{$name}` will be dropped later as of Edition 2024
}
mir_transform_tail_expr_dtor = {$dtor_kind ->
[dyn] `{$name}` may invoke a custom destructor because it contains a trait object
*[concrete] `{$name}` invokes this custom destructor
}
mir_transform_tail_expr_local = {$is_generated_name ->
[true] this value will be stored in a temporary; let us call it `{$name}`
*[false] `{$name}` calls a custom destructor
}
mir_transform_unaligned_packed_ref = reference to packed field is unaligned
.note = packed structs are only aligned by one byte, and many modern architectures penalize unaligned field accesses
.note_ub = creating a misaligned reference is undefined behavior (even if that reference is never dereferenced)

View File

@ -1772,6 +1772,7 @@ impl<'tcx> Visitor<'tcx> for EnsureCoroutineFieldAssignmentsNeverAlias<'_> {
| StatementKind::Coverage(..)
| StatementKind::Intrinsic(..)
| StatementKind::ConstEvalCounter
| StatementKind::BackwardIncompatibleDropHint { .. }
| StatementKind::Nop => {}
}
}

View File

@ -97,6 +97,7 @@ fn filtered_statement_span(statement: &Statement<'_>) -> Option<Span> {
StatementKind::StorageLive(_)
| StatementKind::StorageDead(_)
| StatementKind::ConstEvalCounter
| StatementKind::BackwardIncompatibleDropHint { .. }
| StatementKind::Nop => None,
// FIXME(#78546): MIR InstrumentCoverage - Can the source_info.span for `FakeRead`

View File

@ -186,7 +186,8 @@ impl<'a, 'tcx> ConstAnalysis<'a, 'tcx> {
| StatementKind::FakeRead(..)
| StatementKind::PlaceMention(..)
| StatementKind::Coverage(..)
| StatementKind::AscribeUserType(..) => (),
| StatementKind::BackwardIncompatibleDropHint { .. }
| StatementKind::AscribeUserType(..) => {}
}
}

View File

@ -99,7 +99,8 @@ fn eliminate<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
| StatementKind::Intrinsic(_)
| StatementKind::ConstEvalCounter
| StatementKind::PlaceMention(_)
| StatementKind::Nop => (),
| StatementKind::BackwardIncompatibleDropHint { .. }
| StatementKind::Nop => {}
StatementKind::FakeRead(_) | StatementKind::AscribeUserType(_, _) => {
bug!("{:?} not found in this MIR phase!", statement.kind)

View File

@ -581,7 +581,7 @@ impl WriteInfo {
| Rvalue::RawPtr(_, _)
| Rvalue::Len(_)
| Rvalue::Discriminant(_)
| Rvalue::CopyForDeref(_) => (),
| Rvalue::CopyForDeref(_) => {}
}
}
// Retags are technically also reads, but reporting them as a write suffices
@ -596,7 +596,8 @@ impl WriteInfo {
| StatementKind::Coverage(_)
| StatementKind::StorageLive(_)
| StatementKind::StorageDead(_)
| StatementKind::PlaceMention(_) => (),
| StatementKind::BackwardIncompatibleDropHint { .. }
| StatementKind::PlaceMention(_) => {}
StatementKind::FakeRead(_) | StatementKind::AscribeUserType(_, _) => {
bug!("{:?} not found in this MIR phase", statement)
}

View File

@ -352,6 +352,7 @@ impl<'a, 'tcx> TOFinder<'a, 'tcx> {
| StatementKind::FakeRead(..)
| StatementKind::ConstEvalCounter
| StatementKind::PlaceMention(..)
| StatementKind::BackwardIncompatibleDropHint { .. }
| StatementKind::Nop => None,
}
}

View File

@ -50,6 +50,7 @@ mod deduce_param_attrs;
mod errors;
mod ffi_unwind_calls;
mod lint;
mod lint_tail_expr_drop_order;
mod shim;
mod ssa;
@ -490,6 +491,7 @@ fn mir_drops_elaborated_and_const_checked(tcx: TyCtxt<'_>, def: LocalDefId) -> &
}
let (body, _) = tcx.mir_promoted(def);
lint_tail_expr_drop_order::run_lint(tcx, def, &body.borrow());
let mut body = body.steal();
if let Some(error_reported) = tainted_by_errors {

View File

@ -0,0 +1,701 @@
use std::cell::RefCell;
use std::collections::hash_map;
use std::rc::Rc;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_data_structures::unord::{UnordMap, UnordSet};
use rustc_errors::Subdiagnostic;
use rustc_hir::CRATE_HIR_ID;
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_index::bit_set::ChunkedBitSet;
use rustc_index::{IndexSlice, IndexVec};
use rustc_macros::{LintDiagnostic, Subdiagnostic};
use rustc_middle::bug;
use rustc_middle::mir::{
self, BasicBlock, Body, ClearCrossCrate, Local, Location, Place, StatementKind, TerminatorKind,
dump_mir,
};
use rustc_middle::ty::{self, Ty, TyCtxt};
use rustc_mir_dataflow::impls::MaybeInitializedPlaces;
use rustc_mir_dataflow::move_paths::{LookupResult, MoveData, MovePathIndex};
use rustc_mir_dataflow::{Analysis, MaybeReachable, ResultsCursor};
use rustc_session::lint::builtin::TAIL_EXPR_DROP_ORDER;
use rustc_session::lint::{self};
use rustc_span::{DUMMY_SP, Span, Symbol};
use rustc_type_ir::data_structures::IndexMap;
use smallvec::{SmallVec, smallvec};
use tracing::{debug, instrument};
fn place_has_common_prefix<'tcx>(left: &Place<'tcx>, right: &Place<'tcx>) -> bool {
left.local == right.local
&& left.projection.iter().zip(right.projection).all(|(left, right)| left == right)
}
/// Cache entry of `drop` at a `BasicBlock`
#[derive(Debug, Clone, Copy)]
enum MovePathIndexAtBlock {
/// We know nothing yet
Unknown,
/// We know that the `drop` here has no effect
None,
/// We know that the `drop` here will invoke a destructor
Some(MovePathIndex),
}
struct DropsReachable<'a, 'mir, 'tcx> {
body: &'a Body<'tcx>,
place: &'a Place<'tcx>,
drop_span: &'a mut Option<Span>,
move_data: &'a MoveData<'tcx>,
maybe_init: &'a mut ResultsCursor<'mir, 'tcx, MaybeInitializedPlaces<'mir, 'tcx>>,
block_drop_value_info: &'a mut IndexSlice<BasicBlock, MovePathIndexAtBlock>,
collected_drops: &'a mut ChunkedBitSet<MovePathIndex>,
visited: FxHashMap<BasicBlock, Rc<RefCell<ChunkedBitSet<MovePathIndex>>>>,
}
impl<'a, 'mir, 'tcx> DropsReachable<'a, 'mir, 'tcx> {
fn visit(&mut self, block: BasicBlock) {
let move_set_size = self.move_data.move_paths.len();
let make_new_path_set = || Rc::new(RefCell::new(ChunkedBitSet::new_empty(move_set_size)));
let data = &self.body.basic_blocks[block];
let Some(terminator) = &data.terminator else { return };
// Given that we observe these dropped locals here at `block` so far,
// we will try to update the successor blocks.
// An occupied entry at `block` in `self.visited` signals that we have visited `block` before.
let dropped_local_here =
Rc::clone(self.visited.entry(block).or_insert_with(make_new_path_set));
// We could have invoked reverse lookup for a `MovePathIndex` every time, but unfortunately it is expensive.
// Let's cache them in `self.block_drop_value_info`.
match self.block_drop_value_info[block] {
MovePathIndexAtBlock::Some(dropped) => {
dropped_local_here.borrow_mut().insert(dropped);
}
MovePathIndexAtBlock::Unknown => {
if let TerminatorKind::Drop { place, .. } = &terminator.kind
&& let LookupResult::Exact(idx) | LookupResult::Parent(Some(idx)) =
self.move_data.rev_lookup.find(place.as_ref())
{
// Since we are working with MIRs at a very early stage,
// observing a `drop` terminator is not indicative enough that
// the drop will definitely happen.
// That is decided in the drop elaboration pass instead.
// Therefore, we need to consult with the maybe-initialization information.
self.maybe_init.seek_before_primary_effect(Location {
block,
statement_index: data.statements.len(),
});
// Check if the drop of `place` under inspection is really in effect.
// This is true only when `place` may have been initialized along a control flow path from a BID to the drop program point today.
// In other words, this is where the drop of `place` will happen in the future instead.
if let MaybeReachable::Reachable(maybe_init) = self.maybe_init.get()
&& maybe_init.contains(idx)
{
// We also cache the drop information, so that we do not need to check on data-flow cursor again
self.block_drop_value_info[block] = MovePathIndexAtBlock::Some(idx);
dropped_local_here.borrow_mut().insert(idx);
} else {
self.block_drop_value_info[block] = MovePathIndexAtBlock::None;
}
}
}
MovePathIndexAtBlock::None => {}
}
for succ in terminator.successors() {
let target = &self.body.basic_blocks[succ];
if target.is_cleanup {
continue;
}
// As long as we are passing through a new block, or new dropped places to propagate,
// we will proceed with `succ`
let dropped_local_there = match self.visited.entry(succ) {
hash_map::Entry::Occupied(occupied_entry) => {
if succ == block
|| !occupied_entry.get().borrow_mut().union(&*dropped_local_here.borrow())
{
// `succ` has been visited but no new drops observed so far,
// so we can bail on `succ` until new drop information arrives
continue;
}
Rc::clone(occupied_entry.get())
}
hash_map::Entry::Vacant(vacant_entry) => Rc::clone(
vacant_entry.insert(Rc::new(RefCell::new(dropped_local_here.borrow().clone()))),
),
};
if let Some(terminator) = &target.terminator
&& let TerminatorKind::Drop {
place: dropped_place,
target: _,
unwind: _,
replace: _,
} = &terminator.kind
&& place_has_common_prefix(dropped_place, self.place)
{
// We have now reached the current drop of the `place`.
// Let's check the observed dropped places in.
self.collected_drops.union(&*dropped_local_there.borrow());
if self.drop_span.is_none() {
// FIXME(@dingxiangfei2009): it turns out that `self.body.source_scopes` are still a bit wonky.
// There is a high chance that this span still points to a block rather than a statement semicolon.
*self.drop_span = Some(terminator.source_info.span);
}
// Now we have discovered a simple control flow path from a future drop point
// to the current drop point.
// We will not continue from there.
} else {
self.visit(succ)
}
}
}
}
/// An additional filter to exclude well-known types from the ecosystem
/// because their drops are trivial.
/// This returns additional types to check if the drops are delegated to those.
/// A typical example is `hashbrown::HashMap<K, V>`, whose drop is delegated to `K` and `V`.
fn true_significant_drop_ty<'tcx>(
tcx: TyCtxt<'tcx>,
ty: Ty<'tcx>,
) -> Option<SmallVec<[Ty<'tcx>; 2]>> {
if let ty::Adt(def, args) = ty.kind() {
let mut did = def.did();
let mut name_rev = vec![];
loop {
let key = tcx.def_key(did);
match key.disambiguated_data.data {
rustc_hir::definitions::DefPathData::CrateRoot => {
name_rev.push(tcx.crate_name(did.krate))
}
rustc_hir::definitions::DefPathData::TypeNs(symbol) => name_rev.push(symbol),
_ => return None,
}
if let Some(parent) = key.parent {
did = DefId { krate: did.krate, index: parent };
} else {
break;
}
}
let name_str: Vec<_> = name_rev.iter().rev().map(|x| x.as_str()).collect();
debug!(?name_str);
match name_str[..] {
// These are the types from Rust core ecosystem
["sym" | "proc_macro2", ..]
| ["core" | "std", "task", "LocalWaker" | "Waker"]
| ["core" | "std", "task", "wake", "LocalWaker" | "Waker"] => Some(smallvec![]),
// These are important types from Rust ecosystem
["tracing", "instrument", "Instrumented"] | ["bytes", "Bytes"] => Some(smallvec![]),
["hashbrown", "raw", "RawTable" | "RawIntoIter"] => {
if let [ty, ..] = &***args
&& let Some(ty) = ty.as_type()
{
Some(smallvec![ty])
} else {
None
}
}
["hashbrown", "raw", "RawDrain"] => {
if let [_, ty, ..] = &***args
&& let Some(ty) = ty.as_type()
{
Some(smallvec![ty])
} else {
None
}
}
_ => None,
}
} else {
None
}
}
/// Returns the list of types with a "potentially sigificant" that may be dropped
/// by dropping a value of type `ty`.
#[instrument(level = "debug", skip(tcx, param_env))]
fn extract_component_raw<'tcx>(
tcx: TyCtxt<'tcx>,
param_env: ty::ParamEnv<'tcx>,
ty: Ty<'tcx>,
ty_seen: &mut UnordSet<Ty<'tcx>>,
) -> SmallVec<[Ty<'tcx>; 4]> {
// Droppiness does not depend on regions, so let us erase them.
let ty = tcx
.try_normalize_erasing_regions(
ty::TypingEnv { param_env, typing_mode: ty::TypingMode::PostAnalysis },
ty,
)
.unwrap_or(ty);
let tys = tcx.list_significant_drop_tys(param_env.and(ty));
debug!(?ty, "components");
let mut out_tys = smallvec![];
for ty in tys {
if let Some(tys) = true_significant_drop_ty(tcx, ty) {
// Some types can be further opened up because the drop is simply delegated
for ty in tys {
if ty_seen.insert(ty) {
out_tys.extend(extract_component_raw(tcx, param_env, ty, ty_seen));
}
}
} else {
if ty_seen.insert(ty) {
out_tys.push(ty);
}
}
}
out_tys
}
#[instrument(level = "debug", skip(tcx, param_env))]
fn extract_component_with_significant_dtor<'tcx>(
tcx: TyCtxt<'tcx>,
param_env: ty::ParamEnv<'tcx>,
ty: Ty<'tcx>,
) -> SmallVec<[Ty<'tcx>; 4]> {
let mut tys = extract_component_raw(tcx, param_env, ty, &mut Default::default());
let mut deduplicate = FxHashSet::default();
tys.retain(|oty| deduplicate.insert(*oty));
tys.into_iter().collect()
}
/// Extract the span of the custom destructor of a type
/// especially the span of the `impl Drop` header or its entire block
/// when we are working with current local crate.
#[instrument(level = "debug", skip(tcx))]
fn ty_dtor_span<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option<Span> {
match ty.kind() {
ty::Bool
| ty::Char
| ty::Int(_)
| ty::Uint(_)
| ty::Float(_)
| ty::Error(_)
| ty::Str
| ty::Never
| ty::RawPtr(_, _)
| ty::Ref(_, _, _)
| ty::FnPtr(_, _)
| ty::Tuple(_)
| ty::Dynamic(_, _, _)
| ty::Alias(_, _)
| ty::Bound(_, _)
| ty::Pat(_, _)
| ty::Placeholder(_)
| ty::Infer(_)
| ty::Slice(_)
| ty::Array(_, _) => None,
ty::Adt(adt_def, _) => {
let did = adt_def.did();
let try_local_did_span = |did: DefId| {
if let Some(local) = did.as_local() {
tcx.source_span(local)
} else {
tcx.def_span(did)
}
};
let dtor = if let Some(dtor) = tcx.adt_destructor(did) {
dtor.did
} else if let Some(dtor) = tcx.adt_async_destructor(did) {
dtor.future
} else {
return Some(try_local_did_span(did));
};
let def_key = tcx.def_key(dtor);
let Some(parent_index) = def_key.parent else { return Some(try_local_did_span(dtor)) };
let parent_did = DefId { index: parent_index, krate: dtor.krate };
Some(try_local_did_span(parent_did))
}
ty::Coroutine(did, _)
| ty::CoroutineWitness(did, _)
| ty::CoroutineClosure(did, _)
| ty::Closure(did, _)
| ty::FnDef(did, _)
| ty::Foreign(did) => Some(tcx.def_span(did)),
ty::Param(_) => None,
}
}
/// Check if a moved place at `idx` is a part of a BID.
/// The use of this check is that we will consider drops on these
/// as a drop of the overall BID and, thus, we can exclude it from the diagnosis.
fn place_descendent_of_bids<'tcx>(
mut idx: MovePathIndex,
move_data: &MoveData<'tcx>,
bids: &UnordSet<&Place<'tcx>>,
) -> bool {
loop {
let path = &move_data.move_paths[idx];
if bids.contains(&path.place) {
return true;
}
if let Some(parent) = path.parent {
idx = parent;
} else {
return false;
}
}
}
/// The core of the lint `tail-expr-drop-order`
pub(crate) fn run_lint<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId, body: &Body<'tcx>) {
if matches!(tcx.def_kind(def_id), rustc_hir::def::DefKind::SyntheticCoroutineBody) {
// A synthetic coroutine has no HIR body and it is enough to just analyse the original body
return;
}
if body.span.edition().at_least_rust_2024()
|| tcx.lints_that_dont_need_to_run(()).contains(&lint::LintId::of(TAIL_EXPR_DROP_ORDER))
{
return;
}
// ## About BIDs in blocks ##
// Track the set of blocks that contain a backwards-incompatible drop (BID)
// and, for each block, the vector of locations.
//
// We group them per-block because they tend to scheduled in the same drop ladder block.
let mut bid_per_block = IndexMap::default();
let mut bid_places = UnordSet::new();
let param_env = tcx.param_env(def_id).with_reveal_all_normalized(tcx);
let mut ty_dropped_components = UnordMap::default();
for (block, data) in body.basic_blocks.iter_enumerated() {
for (statement_index, stmt) in data.statements.iter().enumerate() {
if let StatementKind::BackwardIncompatibleDropHint { place, reason: _ } = &stmt.kind {
let ty = place.ty(body, tcx).ty;
if ty_dropped_components
.entry(ty)
.or_insert_with(|| extract_component_with_significant_dtor(tcx, param_env, ty))
.is_empty()
{
continue;
}
bid_per_block
.entry(block)
.or_insert(vec![])
.push((Location { block, statement_index }, &**place));
bid_places.insert(&**place);
}
}
}
if bid_per_block.is_empty() {
return;
}
dump_mir(tcx, false, "lint_tail_expr_drop_order", &0 as _, body, |_, _| Ok(()));
let locals_with_user_names = collect_user_names(body);
let is_closure_like = tcx.is_closure_like(def_id.to_def_id());
// Compute the "maybe initialized" information for this body.
// When we encounter a DROP of some place P we only care
// about the drop if `P` may be initialized.
let move_data = MoveData::gather_moves(body, tcx, |_| true);
let maybe_init = MaybeInitializedPlaces::new(tcx, body, &move_data);
let mut maybe_init = maybe_init.iterate_to_fixpoint(tcx, body, None).into_results_cursor(body);
let mut block_drop_value_info =
IndexVec::from_elem_n(MovePathIndexAtBlock::Unknown, body.basic_blocks.len());
for (&block, candidates) in &bid_per_block {
// We will collect drops on locals on paths between BID points to their actual drop locations
// into `all_locals_dropped`.
let mut all_locals_dropped = ChunkedBitSet::new_empty(move_data.move_paths.len());
let mut drop_span = None;
for &(_, place) in candidates.iter() {
let mut collected_drops = ChunkedBitSet::new_empty(move_data.move_paths.len());
// ## On detecting change in relative drop order ##
// Iterate through each BID-containing block `block`.
// If the place `P` targeted by the BID is "maybe initialized",
// then search forward to find the actual `DROP(P)` point.
// Everything dropped between the BID and the actual drop point
// is something whose relative drop order will change.
DropsReachable {
body,
place,
drop_span: &mut drop_span,
move_data: &move_data,
maybe_init: &mut maybe_init,
block_drop_value_info: &mut block_drop_value_info,
collected_drops: &mut collected_drops,
visited: Default::default(),
}
.visit(block);
// Compute the set `all_locals_dropped` of local variables that are dropped
// after the BID point but before the current drop point.
//
// These are the variables whose drop impls will be reordered with respect
// to `place`.
all_locals_dropped.union(&collected_drops);
}
// We shall now exclude some local bindings for the following cases.
{
let mut to_exclude = ChunkedBitSet::new_empty(all_locals_dropped.domain_size());
// We will now do subtraction from the candidate dropped locals, because of the following reasons.
for path_idx in all_locals_dropped.iter() {
let move_path = &move_data.move_paths[path_idx];
let dropped_local = move_path.place.local;
// a) A return value _0 will eventually be used
// Example:
// fn f() -> Droppy {
// let _x = Droppy;
// Droppy
// }
// _0 holds the literal `Droppy` and rightfully `_x` has to be dropped first
if dropped_local == Local::ZERO {
debug!(?dropped_local, "skip return value");
to_exclude.insert(path_idx);
continue;
}
// b) If we are analysing a closure, the captures are still dropped last.
// This is part of the closure capture lifetime contract.
// They are similar to the return value _0 with respect to lifetime rules.
if is_closure_like && matches!(dropped_local, ty::CAPTURE_STRUCT_LOCAL) {
debug!(?dropped_local, "skip closure captures");
to_exclude.insert(path_idx);
continue;
}
// c) Sometimes we collect places that are projections into the BID locals,
// so they are considered dropped now.
// Example:
// struct NotVeryDroppy(Droppy);
// impl Drop for Droppy {..}
// fn f() -> NotVeryDroppy {
// let x = NotVeryDroppy(droppy());
// {
// let y: Droppy = x.0;
// NotVeryDroppy(y)
// }
// }
// `y` takes `x.0`, which invalidates `x` as a complete `NotVeryDroppy`
// so there is no point in linting against `x` any more.
if place_descendent_of_bids(path_idx, &move_data, &bid_places) {
debug!(?dropped_local, "skip descendent of bids");
to_exclude.insert(path_idx);
continue;
}
let observer_ty = move_path.place.ty(body, tcx).ty;
// d) The collected local has no custom destructor that passes our ecosystem filter.
if ty_dropped_components
.entry(observer_ty)
.or_insert_with(|| {
extract_component_with_significant_dtor(tcx, param_env, observer_ty)
})
.is_empty()
{
debug!(?dropped_local, "skip non-droppy types");
to_exclude.insert(path_idx);
continue;
}
}
// Suppose that all BIDs point into the same local,
// we can remove the this local from the observed drops,
// so that we can focus our diagnosis more on the others.
if candidates.iter().all(|&(_, place)| candidates[0].1.local == place.local) {
for path_idx in all_locals_dropped.iter() {
if move_data.move_paths[path_idx].place.local == candidates[0].1.local {
to_exclude.insert(path_idx);
}
}
}
all_locals_dropped.subtract(&to_exclude);
}
if all_locals_dropped.is_empty() {
// No drop effect is observable, so let us move on.
continue;
}
// ## The final work to assemble the diagnosis ##
// First collect or generate fresh names for local variable bindings and temporary values.
let local_names = assign_observables_names(
all_locals_dropped
.iter()
.map(|path_idx| move_data.move_paths[path_idx].place.local)
.chain(candidates.iter().map(|(_, place)| place.local)),
&locals_with_user_names,
);
let mut lint_root = None;
let mut local_labels = vec![];
// We now collect the types with custom destructors.
for &(_, place) in candidates {
let linted_local_decl = &body.local_decls[place.local];
let Some(&(ref name, is_generated_name)) = local_names.get(&place.local) else {
bug!("a name should have been assigned")
};
let name = name.as_str();
if lint_root.is_none()
&& let ClearCrossCrate::Set(data) =
&body.source_scopes[linted_local_decl.source_info.scope].local_data
{
lint_root = Some(data.lint_root);
}
// Collect spans of the custom destructors.
let mut seen_dyn = false;
let destructors = ty_dropped_components
.get(&linted_local_decl.ty)
.unwrap()
.iter()
.filter_map(|&ty| {
if let Some(span) = ty_dtor_span(tcx, ty) {
Some(DestructorLabel { span, name, dtor_kind: "concrete" })
} else if matches!(ty.kind(), ty::Dynamic(..)) {
if seen_dyn {
None
} else {
seen_dyn = true;
Some(DestructorLabel { span: DUMMY_SP, name, dtor_kind: "dyn" })
}
} else {
None
}
})
.collect();
local_labels.push(LocalLabel {
span: linted_local_decl.source_info.span,
destructors,
name,
is_generated_name,
is_dropped_first_edition_2024: true,
});
}
// Similarly, custom destructors of the observed drops.
for path_idx in all_locals_dropped.iter() {
let place = &move_data.move_paths[path_idx].place;
// We are not using the type of the local because the drop may be partial.
let observer_ty = place.ty(body, tcx).ty;
let observer_local_decl = &body.local_decls[place.local];
let Some(&(ref name, is_generated_name)) = local_names.get(&place.local) else {
bug!("a name should have been assigned")
};
let name = name.as_str();
let mut seen_dyn = false;
let destructors = extract_component_with_significant_dtor(tcx, param_env, observer_ty)
.into_iter()
.filter_map(|ty| {
if let Some(span) = ty_dtor_span(tcx, ty) {
Some(DestructorLabel { span, name, dtor_kind: "concrete" })
} else if matches!(ty.kind(), ty::Dynamic(..)) {
if seen_dyn {
None
} else {
seen_dyn = true;
Some(DestructorLabel { span: DUMMY_SP, name, dtor_kind: "dyn" })
}
} else {
None
}
})
.collect();
local_labels.push(LocalLabel {
span: observer_local_decl.source_info.span,
destructors,
name,
is_generated_name,
is_dropped_first_edition_2024: false,
});
}
let span = local_labels[0].span;
tcx.emit_node_span_lint(
lint::builtin::TAIL_EXPR_DROP_ORDER,
lint_root.unwrap_or(CRATE_HIR_ID),
span,
TailExprDropOrderLint { local_labels, drop_span, _epilogue: () },
);
}
}
/// Extract binding names if available for diagnosis
fn collect_user_names(body: &Body<'_>) -> IndexMap<Local, Symbol> {
let mut names = IndexMap::default();
for var_debug_info in &body.var_debug_info {
if let mir::VarDebugInfoContents::Place(place) = &var_debug_info.value
&& let Some(local) = place.local_or_deref_local()
{
names.entry(local).or_insert(var_debug_info.name);
}
}
names
}
/// Assign names for anonymous or temporary values for diagnosis
fn assign_observables_names(
locals: impl IntoIterator<Item = Local>,
user_names: &IndexMap<Local, Symbol>,
) -> IndexMap<Local, (String, bool)> {
let mut names = IndexMap::default();
let mut assigned_names = FxHashSet::default();
let mut idx = 0u64;
let mut fresh_name = || {
idx += 1;
(format!("#{idx}"), true)
};
for local in locals {
let name = if let Some(name) = user_names.get(&local) {
let name = name.as_str();
if assigned_names.contains(name) { fresh_name() } else { (name.to_owned(), false) }
} else {
fresh_name()
};
assigned_names.insert(name.0.clone());
names.insert(local, name);
}
names
}
#[derive(LintDiagnostic)]
#[diag(mir_transform_tail_expr_drop_order)]
struct TailExprDropOrderLint<'a> {
#[subdiagnostic]
local_labels: Vec<LocalLabel<'a>>,
#[label(mir_transform_drop_location)]
drop_span: Option<Span>,
#[note(mir_transform_note_epilogue)]
_epilogue: (),
}
struct LocalLabel<'a> {
span: Span,
name: &'a str,
is_generated_name: bool,
is_dropped_first_edition_2024: bool,
destructors: Vec<DestructorLabel<'a>>,
}
/// A custom `Subdiagnostic` implementation so that the notes are delivered in a specific order
impl Subdiagnostic for LocalLabel<'_> {
fn add_to_diag_with<
G: rustc_errors::EmissionGuarantee,
F: rustc_errors::SubdiagMessageOp<G>,
>(
self,
diag: &mut rustc_errors::Diag<'_, G>,
f: &F,
) {
diag.arg("name", self.name);
diag.arg("is_generated_name", self.is_generated_name);
diag.arg("is_dropped_first_edition_2024", self.is_dropped_first_edition_2024);
let msg = f(diag, crate::fluent_generated::mir_transform_tail_expr_local.into());
diag.span_label(self.span, msg);
for dtor in self.destructors {
dtor.add_to_diag_with(diag, f);
}
let msg = f(diag, crate::fluent_generated::mir_transform_label_local_epilogue.into());
diag.span_label(self.span, msg);
}
}
#[derive(Subdiagnostic)]
#[note(mir_transform_tail_expr_dtor)]
struct DestructorLabel<'a> {
#[primary_span]
span: Span,
dtor_kind: &'static str,
name: &'a str,
}

View File

@ -92,6 +92,7 @@ impl RemoveNoopLandingPads {
| StatementKind::AscribeUserType(..)
| StatementKind::Coverage(..)
| StatementKind::ConstEvalCounter
| StatementKind::BackwardIncompatibleDropHint { .. }
| StatementKind::Nop => {
// These are all noops in a landing pad
}

View File

@ -125,6 +125,7 @@ impl<'tcx> MutVisitor<'tcx> for Replacer<'_, 'tcx> {
StatementKind::Coverage(_)
| StatementKind::Intrinsic(_)
| StatementKind::Nop
| StatementKind::BackwardIncompatibleDropHint { .. }
| StatementKind::ConstEvalCounter => None,
};
if let Some(place_for_ty) = place_for_ty

View File

@ -523,7 +523,8 @@ impl<'tcx> Visitor<'tcx> for UsedLocals {
}
StatementKind::SetDiscriminant { ref place, variant_index: _ }
| StatementKind::Deinit(ref place) => {
| StatementKind::Deinit(ref place)
| StatementKind::BackwardIncompatibleDropHint { ref place, reason: _ } => {
self.visit_lhs(place, location);
}
}
@ -560,6 +561,7 @@ fn remove_unused_definitions_helper(used_locals: &mut UsedLocals, body: &mut Bod
StatementKind::Assign(box (place, _)) => used_locals.is_used(place.local),
StatementKind::SetDiscriminant { ref place, .. }
| StatementKind::BackwardIncompatibleDropHint { ref place, reason: _ }
| StatementKind::Deinit(ref place) => used_locals.is_used(place.local),
StatementKind::Nop => false,
_ => true,
@ -587,6 +589,20 @@ impl<'tcx> MutVisitor<'tcx> for LocalUpdater<'tcx> {
self.tcx
}
fn visit_statement(&mut self, statement: &mut Statement<'tcx>, location: Location) {
if let StatementKind::BackwardIncompatibleDropHint { place, reason: _ } =
&mut statement.kind
{
self.visit_local(
&mut place.local,
PlaceContext::MutatingUse(MutatingUseContext::Store),
location,
);
} else {
self.super_statement(statement, location);
}
}
fn visit_local(&mut self, l: &mut Local, _: PlaceContext, _: Location) {
*l = self.map[*l].unwrap();
}

View File

@ -343,6 +343,7 @@ impl<'a, 'tcx> Visitor<'tcx> for CfgChecker<'a, 'tcx> {
| StatementKind::Intrinsic(_)
| StatementKind::ConstEvalCounter
| StatementKind::PlaceMention(..)
| StatementKind::BackwardIncompatibleDropHint { .. }
| StatementKind::Nop => {}
}
@ -1493,6 +1494,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
| StatementKind::Coverage(_)
| StatementKind::ConstEvalCounter
| StatementKind::PlaceMention(..)
| StatementKind::BackwardIncompatibleDropHint { .. }
| StatementKind::Nop => {}
}

View File

@ -151,6 +151,10 @@ impl<'tcx> Stable<'tcx> for mir::StatementKind<'tcx> {
mir::StatementKind::ConstEvalCounter => {
stable_mir::mir::StatementKind::ConstEvalCounter
}
// BackwardIncompatibleDropHint has no semantics, so it is translated to Nop.
mir::StatementKind::BackwardIncompatibleDropHint { .. } => {
stable_mir::mir::StatementKind::Nop
}
mir::StatementKind::Nop => stable_mir::mir::StatementKind::Nop,
}
}

View File

@ -8,7 +8,7 @@ use rustc_middle::ty::util::{AlwaysRequiresDrop, needs_drop_components};
use rustc_middle::ty::{self, EarlyBinder, GenericArgsRef, Ty, TyCtxt};
use rustc_session::Limit;
use rustc_span::sym;
use tracing::debug;
use tracing::{debug, instrument};
use crate::errors::NeedsDropOverflow;
@ -23,7 +23,7 @@ fn needs_drop_raw<'tcx>(
// needs drop.
let adt_has_dtor =
|adt_def: ty::AdtDef<'tcx>| adt_def.destructor(tcx).map(|_| DtorType::Significant);
let res = drop_tys_helper(tcx, query.value, query.typing_env, adt_has_dtor, false)
let res = drop_tys_helper(tcx, query.value, query.typing_env, adt_has_dtor, false, false)
.filter(filter_array_elements(tcx, query.typing_env))
.next()
.is_some();
@ -41,7 +41,7 @@ fn needs_async_drop_raw<'tcx>(
// it needs async drop.
let adt_has_async_dtor =
|adt_def: ty::AdtDef<'tcx>| adt_def.async_destructor(tcx).map(|_| DtorType::Significant);
let res = drop_tys_helper(tcx, query.value, query.typing_env, adt_has_async_dtor, false)
let res = drop_tys_helper(tcx, query.value, query.typing_env, adt_has_async_dtor, false, false)
.filter(filter_array_elements(tcx, query.typing_env))
.next()
.is_some();
@ -77,6 +77,7 @@ fn has_significant_drop_raw<'tcx>(
query.typing_env,
adt_consider_insignificant_dtor(tcx),
true,
false,
)
.filter(filter_array_elements(tcx, query.typing_env))
.next()
@ -88,8 +89,8 @@ fn has_significant_drop_raw<'tcx>(
struct NeedsDropTypes<'tcx, F> {
tcx: TyCtxt<'tcx>,
typing_env: ty::TypingEnv<'tcx>,
// Whether to reveal coroutine witnesses, this is set
// to `false` unless we compute `needs_drop` for a coroutine witness.
/// Whether to reveal coroutine witnesses, this is set
/// to `false` unless we compute `needs_drop` for a coroutine witness.
reveal_coroutine_witnesses: bool,
query_ty: Ty<'tcx>,
seen_tys: FxHashSet<Ty<'tcx>>,
@ -100,6 +101,9 @@ struct NeedsDropTypes<'tcx, F> {
unchecked_tys: Vec<(Ty<'tcx>, usize)>,
recursion_limit: Limit,
adt_components: F,
/// Set this to true if an exhaustive list of types involved in
/// drop obligation is requested.
exhaustive: bool,
}
impl<'tcx, F> NeedsDropTypes<'tcx, F> {
@ -107,6 +111,7 @@ impl<'tcx, F> NeedsDropTypes<'tcx, F> {
tcx: TyCtxt<'tcx>,
typing_env: ty::TypingEnv<'tcx>,
ty: Ty<'tcx>,
exhaustive: bool,
adt_components: F,
) -> Self {
let mut seen_tys = FxHashSet::default();
@ -114,14 +119,22 @@ impl<'tcx, F> NeedsDropTypes<'tcx, F> {
Self {
tcx,
typing_env,
reveal_coroutine_witnesses: false,
reveal_coroutine_witnesses: exhaustive,
seen_tys,
query_ty: ty,
unchecked_tys: vec![(ty, 0)],
recursion_limit: tcx.recursion_limit(),
adt_components,
exhaustive,
}
}
/// Called when `ty` is found to always require drop.
/// If the exhaustive flag is true, then `Ok(ty)` is returned like any other type.
/// Otherwise, `Err(AlwaysRequireDrop)` is returned, which will cause iteration to abort.
fn always_drop_component(&self, ty: Ty<'tcx>) -> NeedsDropResult<Ty<'tcx>> {
if self.exhaustive { Ok(ty) } else { Err(AlwaysRequiresDrop) }
}
}
impl<'tcx, F, I> Iterator for NeedsDropTypes<'tcx, F>
@ -131,19 +144,22 @@ where
{
type Item = NeedsDropResult<Ty<'tcx>>;
#[instrument(level = "debug", skip(self), ret)]
fn next(&mut self) -> Option<NeedsDropResult<Ty<'tcx>>> {
let tcx = self.tcx;
while let Some((ty, level)) = self.unchecked_tys.pop() {
debug!(?ty, "needs_drop_components: inspect");
if !self.recursion_limit.value_within_limit(level) {
// Not having a `Span` isn't great. But there's hopefully some other
// recursion limit error as well.
debug!("needs_drop_components: recursion limit exceeded");
tcx.dcx().emit_err(NeedsDropOverflow { query_ty: self.query_ty });
return Some(Err(AlwaysRequiresDrop));
return Some(self.always_drop_component(ty));
}
let components = match needs_drop_components(tcx, ty) {
Err(e) => return Some(Err(e)),
Err(AlwaysRequiresDrop) => return Some(self.always_drop_component(ty)),
Ok(components) => components,
};
debug!("needs_drop_components({:?}) = {:?}", ty, components);
@ -171,7 +187,7 @@ where
if self.reveal_coroutine_witnesses {
queue_type(self, args.as_coroutine().witness());
} else {
return Some(Err(AlwaysRequiresDrop));
return Some(self.always_drop_component(ty));
}
}
ty::CoroutineWitness(def_id, args) => {
@ -186,7 +202,7 @@ where
}
}
_ if component.is_copy_modulo_regions(tcx, self.typing_env) => (),
_ if component.is_copy_modulo_regions(tcx, self.typing_env) => {}
ty::Closure(_, args) => {
for upvar in args.as_closure().upvar_tys() {
@ -205,7 +221,9 @@ where
// impl then check whether the field types need `Drop`.
ty::Adt(adt_def, args) => {
let tys = match (self.adt_components)(adt_def, args) {
Err(e) => return Some(Err(e)),
Err(AlwaysRequiresDrop) => {
return Some(self.always_drop_component(ty));
}
Ok(tys) => tys,
};
for required_ty in tys {
@ -230,7 +248,8 @@ where
}
ty::Foreign(_) | ty::Dynamic(..) => {
return Some(Err(AlwaysRequiresDrop));
debug!("needs_drop_components: foreign or dynamic");
return Some(self.always_drop_component(ty));
}
ty::Bool
@ -280,6 +299,7 @@ fn drop_tys_helper<'tcx>(
typing_env: ty::TypingEnv<'tcx>,
adt_has_dtor: impl Fn(ty::AdtDef<'tcx>) -> Option<DtorType>,
only_significant: bool,
exhaustive: bool,
) -> impl Iterator<Item = NeedsDropResult<Ty<'tcx>>> {
fn with_query_cache<'tcx>(
tcx: TyCtxt<'tcx>,
@ -343,7 +363,7 @@ fn drop_tys_helper<'tcx>(
.map(|v| v.into_iter())
};
NeedsDropTypes::new(tcx, typing_env, ty, adt_components)
NeedsDropTypes::new(tcx, typing_env, ty, exhaustive, adt_components)
}
fn adt_consider_insignificant_dtor<'tcx>(
@ -384,6 +404,7 @@ fn adt_drop_tys<'tcx>(
ty::TypingEnv::non_body_analysis(tcx, def_id),
adt_has_dtor,
false,
false,
)
.collect::<Result<Vec<_>, _>>()
.map(|components| tcx.mk_type_list(&components))
@ -401,11 +422,31 @@ fn adt_significant_drop_tys(
ty::TypingEnv::non_body_analysis(tcx, def_id),
adt_consider_insignificant_dtor(tcx),
true,
false,
)
.collect::<Result<Vec<_>, _>>()
.map(|components| tcx.mk_type_list(&components))
}
#[instrument(level = "debug", skip(tcx), ret)]
fn list_significant_drop_tys<'tcx>(
tcx: TyCtxt<'tcx>,
ty: ty::ParamEnvAnd<'tcx, Ty<'tcx>>,
) -> &'tcx ty::List<Ty<'tcx>> {
tcx.mk_type_list(
&drop_tys_helper(
tcx,
ty.value,
ty::TypingEnv { typing_mode: ty::TypingMode::PostAnalysis, param_env: ty.param_env },
adt_consider_insignificant_dtor(tcx),
true,
true,
)
.filter_map(|res| res.ok())
.collect::<Vec<_>>(),
)
}
pub(crate) fn provide(providers: &mut Providers) {
*providers = Providers {
needs_drop_raw,
@ -413,6 +454,7 @@ pub(crate) fn provide(providers: &mut Providers) {
has_significant_drop_raw,
adt_drop_tys,
adt_significant_drop_tys,
list_significant_drop_tys,
..*providers
};
}

View File

@ -233,6 +233,7 @@ fn check_statement<'tcx>(
| StatementKind::PlaceMention(..)
| StatementKind::Coverage(..)
| StatementKind::ConstEvalCounter
| StatementKind::BackwardIncompatibleDropHint { .. }
| StatementKind::Nop => Ok(()),
}
}

View File

@ -1,9 +1,10 @@
// This test is to demonstrate that the lint is gated behind Edition and
// is triggered only for Edition 2021 and before.
// This test ensures that `tail_expr_drop_order` does not activate in case Edition 2024 is used
// because this is a migration lint.
// Only `cargo fix --edition 2024` shall activate this lint.
//@ check-pass
//@ edition: 2024
//@ compile-flags: -Z unstable-options
//@ edition: 2024
#![deny(tail_expr_drop_order)]

View File

@ -1,13 +1,26 @@
//@ edition: 2021
// Edition 2024 lint for change in drop order at tail expression
// This lint is to capture potential change in program semantics
// due to implementation of RFC 3606 <https://github.com/rust-lang/rfcs/pull/3606>
//@ edition: 2021
//@ build-fail
#![deny(tail_expr_drop_order)]
#![deny(tail_expr_drop_order)] //~ NOTE: the lint level is defined here
#![allow(dropping_copy_types)]
struct LoudDropper;
impl Drop for LoudDropper {
//~^ NOTE: `#1` invokes this custom destructor
//~| NOTE: `x` invokes this custom destructor
//~| NOTE: `#1` invokes this custom destructor
//~| NOTE: `x` invokes this custom destructor
//~| NOTE: `#1` invokes this custom destructor
//~| NOTE: `x` invokes this custom destructor
//~| NOTE: `#1` invokes this custom destructor
//~| NOTE: `x` invokes this custom destructor
//~| NOTE: `#1` invokes this custom destructor
//~| NOTE: `future` invokes this custom destructor
//~| NOTE: `_x` invokes this custom destructor
//~| NOTE: `#1` invokes this custom destructor
fn drop(&mut self) {
// This destructor should be considered significant because it is a custom destructor
// and we will assume that the destructor can generate side effects arbitrarily so that
@ -23,30 +36,70 @@ impl LoudDropper {
fn should_lint() -> i32 {
let x = LoudDropper;
//~^ NOTE: `x` calls a custom destructor
//~| NOTE: `x` will be dropped later as of Edition 2024
// Should lint
x.get() + LoudDropper.get()
//~^ ERROR: these values and local bindings have significant drop implementation that will have a different drop order from that of Edition 2021
//~^ ERROR: relative drop order changing in Rust 2024
//~| NOTE: this value will be stored in a temporary; let us call it `#1`
//~| NOTE: up until Edition 2021 `#1` is dropped last but will be dropped earlier in Edition 2024
//~| WARN: this changes meaning in Rust 2024
//~| NOTE: most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects
//~| NOTE: for more information, see issue #123739
}
//~^ NOTE: now the temporary value is dropped here, before the local variables in the block or statement
fn should_not_lint_closure() -> impl FnOnce() -> i32 {
let x = LoudDropper;
move || {
// Should not lint because ...
x.get() + LoudDropper.get()
}
// ^ closure captures like `x` are always dropped last by contract
}
fn should_lint_closure() -> impl FnOnce() -> i32 {
let x = LoudDropper;
move || x.get() + LoudDropper.get()
//~^ ERROR: these values and local bindings have significant drop implementation that will have a different drop order from that of Edition 2021
//~| WARN: this changes meaning in Rust 2024
fn should_lint_in_nested_items() {
fn should_lint_me() -> i32 {
let x = LoudDropper;
//~^ NOTE: `x` calls a custom destructor
//~| NOTE: `x` will be dropped later as of Edition 2024
// Should lint
x.get() + LoudDropper.get()
//~^ ERROR: relative drop order changing in Rust 2024
//~| NOTE: this value will be stored in a temporary; let us call it `#1`
//~| NOTE: up until Edition 2021 `#1` is dropped last but will be dropped earlier in Edition 2024
//~| WARN: this changes meaning in Rust 2024
//~| NOTE: most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects
//~| NOTE: for more information, see issue #123739
}
//~^ NOTE: now the temporary value is dropped here, before the local variables in the block or statement
}
fn should_not_lint_params(x: LoudDropper) -> i32 {
// Should not lint because ...
x.get() + LoudDropper.get()
}
// ^ function parameters like `x` are always dropped last
fn should_not_lint() -> i32 {
let x = LoudDropper;
// Should not lint
x.get()
}
fn should_not_lint_in_nested_block() -> i32 {
fn should_lint_in_nested_block() -> i32 {
let x = LoudDropper;
// Should not lint because Edition 2021 drops temporaries in blocks earlier already
//~^ NOTE: `x` calls a custom destructor
//~| NOTE: `x` will be dropped later as of Edition 2024
{ LoudDropper.get() }
//~^ ERROR: relative drop order changing in Rust 2024
//~| NOTE: this value will be stored in a temporary; let us call it `#1`
//~| NOTE: up until Edition 2021 `#1` is dropped last but will be dropped earlier in Edition 2024
//~| WARN: this changes meaning in Rust 2024
//~| NOTE: most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects
//~| NOTE: for more information, see issue #123739
}
//~^ NOTE: now the temporary value is dropped here, before the local variables in the block or statement
fn should_not_lint_in_match_arm() -> i32 {
let x = LoudDropper;
@ -56,14 +109,144 @@ fn should_not_lint_in_match_arm() -> i32 {
}
}
fn should_lint_in_nested_items() {
fn should_lint_me() -> i32 {
fn should_not_lint_when_consumed() -> (LoudDropper, i32) {
let x = LoudDropper;
// Should not lint because `LoudDropper` is consumed by the return value
(LoudDropper, x.get())
}
struct MyAdt {
a: LoudDropper,
b: LoudDropper,
}
fn should_not_lint_when_consumed_in_ctor() -> MyAdt {
let a = LoudDropper;
// Should not lint
MyAdt { a, b: LoudDropper }
}
fn should_not_lint_when_moved() -> i32 {
let x = LoudDropper;
drop(x);
// Should not lint because `x` is not live
LoudDropper.get()
}
fn should_lint_into_async_body() -> i32 {
async fn f() {
async fn f() {}
let x = LoudDropper;
// Should lint
x.get() + LoudDropper.get()
//~^ ERROR: these values and local bindings have significant drop implementation that will have a different drop order from that of Edition 2021
//~| WARN: this changes meaning in Rust 2024
f().await;
drop(x);
}
let future = f();
//~^ NOTE: `future` calls a custom destructor
//~| NOTE: `future` will be dropped later as of Edition 2024
LoudDropper.get()
//~^ ERROR: relative drop order changing in Rust 2024
//~| WARN: this changes meaning in Rust 2024
//~| NOTE: this value will be stored in a temporary; let us call it `#1`
//~| NOTE: up until Edition 2021 `#1` is dropped last but will be dropped earlier in Edition 2024
//~| NOTE: most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects
//~| NOTE: for more information, see issue #123739
}
//~^ NOTE: now the temporary value is dropped here, before the local variables in the block or statement
fn should_lint_generics<T: Default>() -> &'static str {
fn extract<T>(_: &T) -> &'static str {
todo!()
}
let x = T::default();
//~^ NOTE: `x` calls a custom destructor
//~| NOTE: `x` will be dropped later as of Edition 2024
extract(&T::default())
//~^ ERROR: relative drop order changing in Rust 2024
//~| WARN: this changes meaning in Rust 2024
//~| NOTE: this value will be stored in a temporary; let us call it `#1`
//~| NOTE: up until Edition 2021 `#1` is dropped last but will be dropped earlier in Edition 2024
//~| NOTE: most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects
//~| NOTE: for more information, see issue #123739
}
//~^ NOTE: now the temporary value is dropped here, before the local variables in the block or statement
fn should_lint_adt() -> i32 {
let x: Result<LoudDropper, ()> = Ok(LoudDropper);
//~^ NOTE: `x` calls a custom destructor
//~| NOTE: `x` will be dropped later as of Edition 2024
LoudDropper.get()
//~^ ERROR: relative drop order changing in Rust 2024
//~| WARN: this changes meaning in Rust 2024
//~| NOTE: this value will be stored in a temporary; let us call it `#1`
//~| NOTE: up until Edition 2021 `#1` is dropped last but will be dropped earlier in Edition 2024
//~| NOTE: most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects
//~| NOTE: for more information, see issue #123739
}
//~^ NOTE: now the temporary value is dropped here, before the local variables in the block or statement
fn should_not_lint_insign_dtor() -> i32 {
let x = String::new();
LoudDropper.get()
}
fn should_lint_with_dtor_span() -> i32 {
struct LoudDropper3;
impl Drop for LoudDropper3 {
//~^ NOTE: `#1` invokes this custom destructor
fn drop(&mut self) {
println!("loud drop");
}
}
impl LoudDropper3 {
fn get(&self) -> i32 {
0
}
}
struct LoudDropper2;
impl Drop for LoudDropper2 {
//~^ NOTE: `x` invokes this custom destructor
fn drop(&mut self) {
println!("loud drop");
}
}
impl LoudDropper2 {
fn get(&self) -> i32 {
0
}
}
let x = LoudDropper2;
//~^ NOTE: `x` calls a custom destructor
//~| NOTE: `x` will be dropped later as of Edition 2024
LoudDropper3.get()
//~^ ERROR: relative drop order changing in Rust 2024
//~| NOTE: this value will be stored in a temporary; let us call it `#1`
//~| NOTE: up until Edition 2021 `#1` is dropped last but will be dropped earlier in Edition 2024
//~| WARN: this changes meaning in Rust 2024
//~| NOTE: most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects
//~| NOTE: for more information, see issue #123739
}
//~^ NOTE: now the temporary value is dropped here, before the local variables in the block or statement
fn should_lint_with_transient_drops() {
drop((
{
LoudDropper.get()
//~^ ERROR: relative drop order changing in Rust 2024
//~| NOTE: this value will be stored in a temporary; let us call it `#1`
//~| NOTE: up until Edition 2021 `#1` is dropped last but will be dropped earlier in Edition 2024
//~| WARN: this changes meaning in Rust 2024
//~| NOTE: most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects
//~| NOTE: for more information, see issue #123739
},
{
let _x = LoudDropper;
//~^ NOTE: `_x` calls a custom destructor
//~| NOTE: `_x` will be dropped later as of Edition 2024
},
));
//~^ NOTE: now the temporary value is dropped here, before the local variables in the block or statement
}
fn main() {}

View File

@ -1,42 +1,335 @@
error: these values and local bindings have significant drop implementation that will have a different drop order from that of Edition 2021
--> $DIR/lint-tail-expr-drop-order.rs:27:15
error: relative drop order changing in Rust 2024
--> $DIR/lint-tail-expr-drop-order.rs:42:15
|
LL | let x = LoudDropper;
| - these values have significant drop implementation and will observe changes in drop order under Edition 2024
LL | // Should lint
| -
| |
| `x` calls a custom destructor
| `x` will be dropped later as of Edition 2024
...
LL | x.get() + LoudDropper.get()
| ^^^^^^^^^^^
| |
| this value will be stored in a temporary; let us call it `#1`
| up until Edition 2021 `#1` is dropped last but will be dropped earlier in Edition 2024
...
LL | }
| - now the temporary value is dropped here, before the local variables in the block or statement
|
= warning: this changes meaning in Rust 2024
= note: for more information, see issue #123739 <https://github.com/rust-lang/rust/issues/123739>
note: `#1` invokes this custom destructor
--> $DIR/lint-tail-expr-drop-order.rs:11:1
|
LL | / impl Drop for LoudDropper {
LL | |
LL | |
LL | |
... |
LL | | }
LL | | }
| |_^
note: `x` invokes this custom destructor
--> $DIR/lint-tail-expr-drop-order.rs:11:1
|
LL | / impl Drop for LoudDropper {
LL | |
LL | |
LL | |
... |
LL | | }
LL | | }
| |_^
= note: most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects like releasing locks or sending messages
note: the lint level is defined here
--> $DIR/lint-tail-expr-drop-order.rs:7:9
|
LL | #![deny(tail_expr_drop_order)]
| ^^^^^^^^^^^^^^^^^^^^
error: these values and local bindings have significant drop implementation that will have a different drop order from that of Edition 2021
--> $DIR/lint-tail-expr-drop-order.rs:34:23
|
LL | let x = LoudDropper;
| - these values have significant drop implementation and will observe changes in drop order under Edition 2024
LL | move || x.get() + LoudDropper.get()
| ^^^^^^^^^^^
|
= warning: this changes meaning in Rust 2024
= note: for more information, see issue #123739 <https://github.com/rust-lang/rust/issues/123739>
error: these values and local bindings have significant drop implementation that will have a different drop order from that of Edition 2021
--> $DIR/lint-tail-expr-drop-order.rs:63:19
error: relative drop order changing in Rust 2024
--> $DIR/lint-tail-expr-drop-order.rs:67:19
|
LL | let x = LoudDropper;
| - these values have significant drop implementation and will observe changes in drop order under Edition 2024
LL | // Should lint
| -
| |
| `x` calls a custom destructor
| `x` will be dropped later as of Edition 2024
...
LL | x.get() + LoudDropper.get()
| ^^^^^^^^^^^
| |
| this value will be stored in a temporary; let us call it `#1`
| up until Edition 2021 `#1` is dropped last but will be dropped earlier in Edition 2024
...
LL | }
| - now the temporary value is dropped here, before the local variables in the block or statement
|
= warning: this changes meaning in Rust 2024
= note: for more information, see issue #123739 <https://github.com/rust-lang/rust/issues/123739>
note: `#1` invokes this custom destructor
--> $DIR/lint-tail-expr-drop-order.rs:11:1
|
LL | / impl Drop for LoudDropper {
LL | |
LL | |
LL | |
... |
LL | | }
LL | | }
| |_^
note: `x` invokes this custom destructor
--> $DIR/lint-tail-expr-drop-order.rs:11:1
|
LL | / impl Drop for LoudDropper {
LL | |
LL | |
LL | |
... |
LL | | }
LL | | }
| |_^
= note: most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects like releasing locks or sending messages
error: aborting due to 3 previous errors
error: relative drop order changing in Rust 2024
--> $DIR/lint-tail-expr-drop-order.rs:94:7
|
LL | let x = LoudDropper;
| -
| |
| `x` calls a custom destructor
| `x` will be dropped later as of Edition 2024
...
LL | { LoudDropper.get() }
| ^^^^^^^^^^^
| |
| this value will be stored in a temporary; let us call it `#1`
| up until Edition 2021 `#1` is dropped last but will be dropped earlier in Edition 2024
...
LL | }
| - now the temporary value is dropped here, before the local variables in the block or statement
|
= warning: this changes meaning in Rust 2024
= note: for more information, see issue #123739 <https://github.com/rust-lang/rust/issues/123739>
note: `#1` invokes this custom destructor
--> $DIR/lint-tail-expr-drop-order.rs:11:1
|
LL | / impl Drop for LoudDropper {
LL | |
LL | |
LL | |
... |
LL | | }
LL | | }
| |_^
note: `x` invokes this custom destructor
--> $DIR/lint-tail-expr-drop-order.rs:11:1
|
LL | / impl Drop for LoudDropper {
LL | |
LL | |
LL | |
... |
LL | | }
LL | | }
| |_^
= note: most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects like releasing locks or sending messages
error: relative drop order changing in Rust 2024
--> $DIR/lint-tail-expr-drop-order.rs:147:5
|
LL | let future = f();
| ------
| |
| `future` calls a custom destructor
| `future` will be dropped later as of Edition 2024
...
LL | LoudDropper.get()
| ^^^^^^^^^^^
| |
| this value will be stored in a temporary; let us call it `#1`
| up until Edition 2021 `#1` is dropped last but will be dropped earlier in Edition 2024
...
LL | }
| - now the temporary value is dropped here, before the local variables in the block or statement
|
= warning: this changes meaning in Rust 2024
= note: for more information, see issue #123739 <https://github.com/rust-lang/rust/issues/123739>
note: `#1` invokes this custom destructor
--> $DIR/lint-tail-expr-drop-order.rs:11:1
|
LL | / impl Drop for LoudDropper {
LL | |
LL | |
LL | |
... |
LL | | }
LL | | }
| |_^
note: `future` invokes this custom destructor
--> $DIR/lint-tail-expr-drop-order.rs:11:1
|
LL | / impl Drop for LoudDropper {
LL | |
LL | |
LL | |
... |
LL | | }
LL | | }
| |_^
= note: most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects like releasing locks or sending messages
error: relative drop order changing in Rust 2024
--> $DIR/lint-tail-expr-drop-order.rs:164:14
|
LL | let x = T::default();
| -
| |
| `x` calls a custom destructor
| `x` will be dropped later as of Edition 2024
...
LL | extract(&T::default())
| ^^^^^^^^^^^^
| |
| this value will be stored in a temporary; let us call it `#1`
| up until Edition 2021 `#1` is dropped last but will be dropped earlier in Edition 2024
...
LL | }
| - now the temporary value is dropped here, before the local variables in the block or statement
|
= warning: this changes meaning in Rust 2024
= note: for more information, see issue #123739 <https://github.com/rust-lang/rust/issues/123739>
= note: most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects like releasing locks or sending messages
error: relative drop order changing in Rust 2024
--> $DIR/lint-tail-expr-drop-order.rs:178:5
|
LL | let x: Result<LoudDropper, ()> = Ok(LoudDropper);
| -
| |
| `x` calls a custom destructor
| `x` will be dropped later as of Edition 2024
...
LL | LoudDropper.get()
| ^^^^^^^^^^^
| |
| this value will be stored in a temporary; let us call it `#1`
| up until Edition 2021 `#1` is dropped last but will be dropped earlier in Edition 2024
...
LL | }
| - now the temporary value is dropped here, before the local variables in the block or statement
|
= warning: this changes meaning in Rust 2024
= note: for more information, see issue #123739 <https://github.com/rust-lang/rust/issues/123739>
note: `#1` invokes this custom destructor
--> $DIR/lint-tail-expr-drop-order.rs:11:1
|
LL | / impl Drop for LoudDropper {
LL | |
LL | |
LL | |
... |
LL | | }
LL | | }
| |_^
note: `x` invokes this custom destructor
--> $DIR/lint-tail-expr-drop-order.rs:11:1
|
LL | / impl Drop for LoudDropper {
LL | |
LL | |
LL | |
... |
LL | | }
LL | | }
| |_^
= note: most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects like releasing locks or sending messages
error: relative drop order changing in Rust 2024
--> $DIR/lint-tail-expr-drop-order.rs:222:5
|
LL | let x = LoudDropper2;
| -
| |
| `x` calls a custom destructor
| `x` will be dropped later as of Edition 2024
...
LL | LoudDropper3.get()
| ^^^^^^^^^^^^
| |
| this value will be stored in a temporary; let us call it `#1`
| up until Edition 2021 `#1` is dropped last but will be dropped earlier in Edition 2024
...
LL | }
| - now the temporary value is dropped here, before the local variables in the block or statement
|
= warning: this changes meaning in Rust 2024
= note: for more information, see issue #123739 <https://github.com/rust-lang/rust/issues/123739>
note: `#1` invokes this custom destructor
--> $DIR/lint-tail-expr-drop-order.rs:195:5
|
LL | / impl Drop for LoudDropper3 {
LL | |
LL | | fn drop(&mut self) {
LL | | println!("loud drop");
LL | | }
LL | | }
| |_____^
note: `x` invokes this custom destructor
--> $DIR/lint-tail-expr-drop-order.rs:207:5
|
LL | / impl Drop for LoudDropper2 {
LL | |
LL | | fn drop(&mut self) {
LL | | println!("loud drop");
LL | | }
LL | | }
| |_____^
= note: most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects like releasing locks or sending messages
error: relative drop order changing in Rust 2024
--> $DIR/lint-tail-expr-drop-order.rs:235:13
|
LL | LoudDropper.get()
| ^^^^^^^^^^^
| |
| this value will be stored in a temporary; let us call it `#1`
| up until Edition 2021 `#1` is dropped last but will be dropped earlier in Edition 2024
...
LL | let _x = LoudDropper;
| --
| |
| `_x` calls a custom destructor
| `_x` will be dropped later as of Edition 2024
...
LL | ));
| - now the temporary value is dropped here, before the local variables in the block or statement
|
= warning: this changes meaning in Rust 2024
= note: for more information, see issue #123739 <https://github.com/rust-lang/rust/issues/123739>
note: `#1` invokes this custom destructor
--> $DIR/lint-tail-expr-drop-order.rs:11:1
|
LL | / impl Drop for LoudDropper {
LL | |
LL | |
LL | |
... |
LL | | }
LL | | }
| |_^
note: `_x` invokes this custom destructor
--> $DIR/lint-tail-expr-drop-order.rs:11:1
|
LL | / impl Drop for LoudDropper {
LL | |
LL | |
LL | |
... |
LL | | }
LL | | }
| |_^
= note: most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects like releasing locks or sending messages
error: aborting due to 8 previous errors

View File

@ -11,9 +11,12 @@ Thir {
fields: [],
},
ty: (),
temp_lifetime: Some(
Node(3),
),
temp_lifetime: TempLifetime {
temp_lifetime: Some(
Node(3),
),
backwards_incompatible: None,
},
span: $DIR/thir-flat-const-variant.rs:12:32: 12:34 (#0),
},
Expr {
@ -25,9 +28,12 @@ Thir {
value: e0,
},
ty: (),
temp_lifetime: Some(
Node(3),
),
temp_lifetime: TempLifetime {
temp_lifetime: Some(
Node(3),
),
backwards_incompatible: None,
},
span: $DIR/thir-flat-const-variant.rs:12:32: 12:34 (#0),
},
Expr {
@ -47,9 +53,12 @@ Thir {
},
),
ty: Foo,
temp_lifetime: Some(
Node(3),
),
temp_lifetime: TempLifetime {
temp_lifetime: Some(
Node(3),
),
backwards_incompatible: None,
},
span: $DIR/thir-flat-const-variant.rs:12:23: 12:35 (#0),
},
Expr {
@ -61,9 +70,12 @@ Thir {
value: e2,
},
ty: Foo,
temp_lifetime: Some(
Node(3),
),
temp_lifetime: TempLifetime {
temp_lifetime: Some(
Node(3),
),
backwards_incompatible: None,
},
span: $DIR/thir-flat-const-variant.rs:12:23: 12:35 (#0),
},
],
@ -84,9 +96,12 @@ Thir {
fields: [],
},
ty: (),
temp_lifetime: Some(
Node(3),
),
temp_lifetime: TempLifetime {
temp_lifetime: Some(
Node(3),
),
backwards_incompatible: None,
},
span: $DIR/thir-flat-const-variant.rs:13:33: 13:35 (#0),
},
Expr {
@ -98,9 +113,12 @@ Thir {
value: e0,
},
ty: (),
temp_lifetime: Some(
Node(3),
),
temp_lifetime: TempLifetime {
temp_lifetime: Some(
Node(3),
),
backwards_incompatible: None,
},
span: $DIR/thir-flat-const-variant.rs:13:33: 13:35 (#0),
},
Expr {
@ -120,9 +138,12 @@ Thir {
},
),
ty: Foo,
temp_lifetime: Some(
Node(3),
),
temp_lifetime: TempLifetime {
temp_lifetime: Some(
Node(3),
),
backwards_incompatible: None,
},
span: $DIR/thir-flat-const-variant.rs:13:23: 13:36 (#0),
},
Expr {
@ -134,9 +155,12 @@ Thir {
value: e2,
},
ty: Foo,
temp_lifetime: Some(
Node(3),
),
temp_lifetime: TempLifetime {
temp_lifetime: Some(
Node(3),
),
backwards_incompatible: None,
},
span: $DIR/thir-flat-const-variant.rs:13:23: 13:36 (#0),
},
],
@ -157,9 +181,12 @@ Thir {
fields: [],
},
ty: (),
temp_lifetime: Some(
Node(3),
),
temp_lifetime: TempLifetime {
temp_lifetime: Some(
Node(3),
),
backwards_incompatible: None,
},
span: $DIR/thir-flat-const-variant.rs:14:33: 14:35 (#0),
},
Expr {
@ -171,9 +198,12 @@ Thir {
value: e0,
},
ty: (),
temp_lifetime: Some(
Node(3),
),
temp_lifetime: TempLifetime {
temp_lifetime: Some(
Node(3),
),
backwards_incompatible: None,
},
span: $DIR/thir-flat-const-variant.rs:14:33: 14:35 (#0),
},
Expr {
@ -193,9 +223,12 @@ Thir {
},
),
ty: Foo,
temp_lifetime: Some(
Node(3),
),
temp_lifetime: TempLifetime {
temp_lifetime: Some(
Node(3),
),
backwards_incompatible: None,
},
span: $DIR/thir-flat-const-variant.rs:14:24: 14:36 (#0),
},
Expr {
@ -207,9 +240,12 @@ Thir {
value: e2,
},
ty: Foo,
temp_lifetime: Some(
Node(3),
),
temp_lifetime: TempLifetime {
temp_lifetime: Some(
Node(3),
),
backwards_incompatible: None,
},
span: $DIR/thir-flat-const-variant.rs:14:24: 14:36 (#0),
},
],
@ -230,9 +266,12 @@ Thir {
fields: [],
},
ty: (),
temp_lifetime: Some(
Node(3),
),
temp_lifetime: TempLifetime {
temp_lifetime: Some(
Node(3),
),
backwards_incompatible: None,
},
span: $DIR/thir-flat-const-variant.rs:15:34: 15:36 (#0),
},
Expr {
@ -244,9 +283,12 @@ Thir {
value: e0,
},
ty: (),
temp_lifetime: Some(
Node(3),
),
temp_lifetime: TempLifetime {
temp_lifetime: Some(
Node(3),
),
backwards_incompatible: None,
},
span: $DIR/thir-flat-const-variant.rs:15:34: 15:36 (#0),
},
Expr {
@ -266,9 +308,12 @@ Thir {
},
),
ty: Foo,
temp_lifetime: Some(
Node(3),
),
temp_lifetime: TempLifetime {
temp_lifetime: Some(
Node(3),
),
backwards_incompatible: None,
},
span: $DIR/thir-flat-const-variant.rs:15:24: 15:37 (#0),
},
Expr {
@ -280,9 +325,12 @@ Thir {
value: e2,
},
ty: Foo,
temp_lifetime: Some(
Node(3),
),
temp_lifetime: TempLifetime {
temp_lifetime: Some(
Node(3),
),
backwards_incompatible: None,
},
span: $DIR/thir-flat-const-variant.rs:15:24: 15:37 (#0),
},
],
@ -312,9 +360,12 @@ Thir {
block: b0,
},
ty: (),
temp_lifetime: Some(
Node(2),
),
temp_lifetime: TempLifetime {
temp_lifetime: Some(
Node(2),
),
backwards_incompatible: None,
},
span: $DIR/thir-flat-const-variant.rs:18:11: 18:13 (#0),
},
Expr {
@ -326,9 +377,12 @@ Thir {
value: e0,
},
ty: (),
temp_lifetime: Some(
Node(2),
),
temp_lifetime: TempLifetime {
temp_lifetime: Some(
Node(2),
),
backwards_incompatible: None,
},
span: $DIR/thir-flat-const-variant.rs:18:11: 18:13 (#0),
},
],

View File

@ -20,9 +20,12 @@ Thir {
block: b0,
},
ty: (),
temp_lifetime: Some(
Node(2),
),
temp_lifetime: TempLifetime {
temp_lifetime: Some(
Node(2),
),
backwards_incompatible: None,
},
span: $DIR/thir-flat.rs:4:15: 4:17 (#0),
},
Expr {
@ -34,9 +37,12 @@ Thir {
value: e0,
},
ty: (),
temp_lifetime: Some(
Node(2),
),
temp_lifetime: TempLifetime {
temp_lifetime: Some(
Node(2),
),
backwards_incompatible: None,
},
span: $DIR/thir-flat.rs:4:15: 4:17 (#0),
},
],

View File

@ -26,7 +26,7 @@ params: [
body:
Expr {
ty: bool
temp_lifetime: Some(Node(26))
temp_lifetime: TempLifetime { temp_lifetime: Some(Node(26)), backwards_incompatible: None }
span: $DIR/thir-tree-match.rs:15:32: 21:2 (#0)
kind:
Scope {
@ -35,7 +35,7 @@ body:
value:
Expr {
ty: bool
temp_lifetime: Some(Node(26))
temp_lifetime: TempLifetime { temp_lifetime: Some(Node(26)), backwards_incompatible: None }
span: $DIR/thir-tree-match.rs:15:32: 21:2 (#0)
kind:
Block {
@ -47,7 +47,7 @@ body:
expr:
Expr {
ty: bool
temp_lifetime: Some(Node(26))
temp_lifetime: TempLifetime { temp_lifetime: Some(Node(26)), backwards_incompatible: None }
span: $DIR/thir-tree-match.rs:16:5: 20:6 (#0)
kind:
Scope {
@ -56,14 +56,14 @@ body:
value:
Expr {
ty: bool
temp_lifetime: Some(Node(26))
temp_lifetime: TempLifetime { temp_lifetime: Some(Node(26)), backwards_incompatible: None }
span: $DIR/thir-tree-match.rs:16:5: 20:6 (#0)
kind:
Match {
scrutinee:
Expr {
ty: Foo
temp_lifetime: Some(Node(26))
temp_lifetime: TempLifetime { temp_lifetime: Some(Node(26)), backwards_incompatible: None }
span: $DIR/thir-tree-match.rs:16:11: 16:14 (#0)
kind:
Scope {
@ -72,7 +72,7 @@ body:
value:
Expr {
ty: Foo
temp_lifetime: Some(Node(26))
temp_lifetime: TempLifetime { temp_lifetime: Some(Node(26)), backwards_incompatible: None }
span: $DIR/thir-tree-match.rs:16:11: 16:14 (#0)
kind:
VarRef {
@ -123,7 +123,7 @@ body:
body:
Expr {
ty: bool
temp_lifetime: Some(Node(13))
temp_lifetime: TempLifetime { temp_lifetime: Some(Node(13)), backwards_incompatible: None }
span: $DIR/thir-tree-match.rs:17:36: 17:40 (#0)
kind:
Scope {
@ -132,7 +132,7 @@ body:
value:
Expr {
ty: bool
temp_lifetime: Some(Node(13))
temp_lifetime: TempLifetime { temp_lifetime: Some(Node(13)), backwards_incompatible: None }
span: $DIR/thir-tree-match.rs:17:36: 17:40 (#0)
kind:
Literal( lit: Spanned { node: Bool(true), span: $DIR/thir-tree-match.rs:17:36: 17:40 (#0) }, neg: false)
@ -175,7 +175,7 @@ body:
body:
Expr {
ty: bool
temp_lifetime: Some(Node(19))
temp_lifetime: TempLifetime { temp_lifetime: Some(Node(19)), backwards_incompatible: None }
span: $DIR/thir-tree-match.rs:18:27: 18:32 (#0)
kind:
Scope {
@ -184,7 +184,7 @@ body:
value:
Expr {
ty: bool
temp_lifetime: Some(Node(19))
temp_lifetime: TempLifetime { temp_lifetime: Some(Node(19)), backwards_incompatible: None }
span: $DIR/thir-tree-match.rs:18:27: 18:32 (#0)
kind:
Literal( lit: Spanned { node: Bool(false), span: $DIR/thir-tree-match.rs:18:27: 18:32 (#0) }, neg: false)
@ -219,7 +219,7 @@ body:
body:
Expr {
ty: bool
temp_lifetime: Some(Node(24))
temp_lifetime: TempLifetime { temp_lifetime: Some(Node(24)), backwards_incompatible: None }
span: $DIR/thir-tree-match.rs:19:24: 19:28 (#0)
kind:
Scope {
@ -228,7 +228,7 @@ body:
value:
Expr {
ty: bool
temp_lifetime: Some(Node(24))
temp_lifetime: TempLifetime { temp_lifetime: Some(Node(24)), backwards_incompatible: None }
span: $DIR/thir-tree-match.rs:19:24: 19:28 (#0)
kind:
Literal( lit: Spanned { node: Bool(true), span: $DIR/thir-tree-match.rs:19:24: 19:28 (#0) }, neg: false)
@ -257,7 +257,7 @@ params: [
body:
Expr {
ty: ()
temp_lifetime: Some(Node(2))
temp_lifetime: TempLifetime { temp_lifetime: Some(Node(2)), backwards_incompatible: None }
span: $DIR/thir-tree-match.rs:23:11: 23:13 (#0)
kind:
Scope {
@ -266,7 +266,7 @@ body:
value:
Expr {
ty: ()
temp_lifetime: Some(Node(2))
temp_lifetime: TempLifetime { temp_lifetime: Some(Node(2)), backwards_incompatible: None }
span: $DIR/thir-tree-match.rs:23:11: 23:13 (#0)
kind:
Block {

View File

@ -4,7 +4,7 @@ params: [
body:
Expr {
ty: ()
temp_lifetime: Some(Node(2))
temp_lifetime: TempLifetime { temp_lifetime: Some(Node(2)), backwards_incompatible: None }
span: $DIR/thir-tree.rs:4:15: 4:17 (#0)
kind:
Scope {
@ -13,7 +13,7 @@ body:
value:
Expr {
ty: ()
temp_lifetime: Some(Node(2))
temp_lifetime: TempLifetime { temp_lifetime: Some(Node(2)), backwards_incompatible: None }
span: $DIR/thir-tree.rs:4:15: 4:17 (#0)
kind:
Block {