diff --git a/compiler/rustc_borrowck/src/dataflow.rs b/compiler/rustc_borrowck/src/dataflow.rs index 7adc7a8863e42..452038bc328df 100644 --- a/compiler/rustc_borrowck/src/dataflow.rs +++ b/compiler/rustc_borrowck/src/dataflow.rs @@ -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 => {} } } diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs index 03f7b05d1e3be..16a4f69917746 100644 --- a/compiler/rustc_borrowck/src/lib.rs +++ b/compiler/rustc_borrowck/src/lib.rs @@ -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( diff --git a/compiler/rustc_borrowck/src/polonius/loan_invalidations.rs b/compiler/rustc_borrowck/src/polonius/loan_invalidations.rs index d1b65943199c4..f646beeecf7b3 100644 --- a/compiler/rustc_borrowck/src/polonius/loan_invalidations.rs +++ b/compiler/rustc_borrowck/src/polonius/loan_invalidations.rs @@ -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") } diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs index c6b34d5bf1dae..3a7ed711f681e 100644 --- a/compiler/rustc_borrowck/src/type_check/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/mod.rs @@ -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") diff --git a/compiler/rustc_codegen_cranelift/src/base.rs b/compiler/rustc_codegen_cranelift/src/base.rs index 77ee977394056..70b7d92ce15bf 100644 --- a/compiler/rustc_codegen_cranelift/src/base.rs +++ b/compiler/rustc_codegen_cranelift/src/base.rs @@ -924,6 +924,7 @@ fn codegen_stmt<'tcx>( | StatementKind::FakeRead(..) | StatementKind::Retag { .. } | StatementKind::PlaceMention(..) + | StatementKind::BackwardIncompatibleDropHint { .. } | StatementKind::AscribeUserType(..) => {} StatementKind::Coverage { .. } => unreachable!(), diff --git a/compiler/rustc_codegen_cranelift/src/constant.rs b/compiler/rustc_codegen_cranelift/src/constant.rs index 5311547309c5e..abe6085b04f44 100644 --- a/compiler/rustc_codegen_cranelift/src/constant.rs +++ b/compiler/rustc_codegen_cranelift/src/constant.rs @@ -583,6 +583,7 @@ pub(crate) fn mir_operand_get_const_val<'tcx>( | StatementKind::PlaceMention(..) | StatementKind::Coverage(_) | StatementKind::ConstEvalCounter + | StatementKind::BackwardIncompatibleDropHint { .. } | StatementKind::Nop => {} } } diff --git a/compiler/rustc_codegen_ssa/src/mir/statement.rs b/compiler/rustc_codegen_ssa/src/mir/statement.rs index 1681ea1de5fb6..cd55a838a7561 100644 --- a/compiler/rustc_codegen_ssa/src/mir/statement.rs +++ b/compiler/rustc_codegen_ssa/src/mir/statement.rs @@ -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 => {} } } diff --git a/compiler/rustc_const_eval/src/check_consts/check.rs b/compiler/rustc_const_eval/src/check_consts/check.rs index 8e96d365bebec..1129b5caec54e 100644 --- a/compiler/rustc_const_eval/src/check_consts/check.rs +++ b/compiler/rustc_const_eval/src/check_consts/check.rs @@ -609,6 +609,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { | StatementKind::Coverage(..) | StatementKind::Intrinsic(..) | StatementKind::ConstEvalCounter + | StatementKind::BackwardIncompatibleDropHint { .. } | StatementKind::Nop => {} } } diff --git a/compiler/rustc_const_eval/src/interpret/step.rs b/compiler/rustc_const_eval/src/interpret/step.rs index 98aca37e73e2e..b61865be66788 100644 --- a/compiler/rustc_const_eval/src/interpret/step.rs +++ b/compiler/rustc_const_eval/src/interpret/step.rs @@ -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(()) diff --git a/compiler/rustc_hir_analysis/src/check/region.rs b/compiler/rustc_hir_analysis/src/check/region.rs index 679f6ccb8165d..b9cb48cafdcd2 100644 --- a/compiler/rustc_hir_analysis/src/check/region.rs +++ b/compiler/rustc_hir_analysis/src/check/region.rs @@ -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); } diff --git a/compiler/rustc_index/src/bit_set.rs b/compiler/rustc_index/src/bit_set.rs index c2b9cae680bf1..a923948922241 100644 --- a/compiler/rustc_index/src/bit_set.rs +++ b/compiler/rustc_index/src/bit_set.rs @@ -460,6 +460,10 @@ impl ChunkedBitSet { 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 BitRelations> for ChunkedBitSet { changed } - fn subtract(&mut self, _other: &ChunkedBitSet) -> bool { - unimplemented!("implement if/when necessary"); + fn subtract(&mut self, other: &ChunkedBitSet) -> 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 [`>>::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) -> bool { - unimplemented!("implement if/when necessary"); + fn intersect(&mut self, other: &ChunkedBitSet) -> 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 [`>>::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 } } diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index 6e35d89b48800..69fd7f2d8b25f 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -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}` diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index 86112277504a8..4cf5c7b4ff964 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -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, diff --git a/compiler/rustc_lint/src/tail_expr_drop_order.rs b/compiler/rustc_lint/src/tail_expr_drop_order.rs deleted file mode 100644 index 19763ce1ec590..0000000000000 --- a/compiler/rustc_lint/src/tail_expr_drop_order.rs +++ /dev/null @@ -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 ", - }; -} - -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, -} - -struct LocalCollector<'a, 'tcx> { - cx: &'a LateContext<'tcx>, - locals: &'a mut Vec, -} - -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, -} diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index 313a7badf191d..9036741e07877 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -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 ", + }; +} + 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. diff --git a/compiler/rustc_middle/src/middle/region.rs b/compiler/rustc_middle/src/middle/region.rs index 4e44de33611b9..114211b27c179 100644 --- a/compiler/rustc_middle/src/middle/region.rs +++ b/compiler/rustc_middle/src/middle/region.rs @@ -236,6 +236,11 @@ pub struct ScopeTree { /// during type check based on a traversal of the AST. pub rvalue_candidates: HirIdMap, + /// 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, + /// 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. diff --git a/compiler/rustc_middle/src/mir/pretty.rs b/compiler/rustc_middle/src/mir/pretty.rs index f0e2b7a376cd3..2bfcd0a62274d 100644 --- a/compiler/rustc_middle/src/mir/pretty.rs +++ b/compiler/rustc_middle/src/mir/pretty.rs @@ -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:?})") + } } } } diff --git a/compiler/rustc_middle/src/mir/syntax.rs b/compiler/rustc_middle/src/mir/syntax.rs index 2083279e12861..fea940ea47c47 100644 --- a/compiler/rustc_middle/src/mir/syntax.rs +++ b/compiler/rustc_middle/src/mir/syntax.rs @@ -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>, + /// 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"`). diff --git a/compiler/rustc_middle/src/mir/visit.rs b/compiler/rustc_middle/src/mir/visit.rs index 9f9ee8497b60b..62c340d99e38e 100644 --- a/compiler/rustc_middle/src/mir/visit.rs +++ b/compiler/rustc_middle/src/mir/visit.rs @@ -452,6 +452,7 @@ macro_rules! make_mir_visitor { } StatementKind::ConstEvalCounter => {} StatementKind::Nop => {} + StatementKind::BackwardIncompatibleDropHint { .. } => {} } } diff --git a/compiler/rustc_middle/src/query/keys.rs b/compiler/rustc_middle/src/query/keys.rs index 8fd9930362220..970dc72e1ff61 100644 --- a/compiler/rustc_middle/src/query/keys.rs +++ b/compiler/rustc_middle/src/query/keys.rs @@ -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 = DefaultCache; + + fn default_span(&self, _tcx: TyCtxt<'_>) -> Span { + DUMMY_SP + } +} + impl<'tcx> Key for ty::TraitRef<'tcx> { type Cache = DefaultCache; diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs index 9ae519dfe7e6a..3fdb38a433e97 100644 --- a/compiler/rustc_middle/src/query/mod.rs +++ b/compiler/rustc_middle/src/query/mod.rs @@ -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` does not + /// have significant drop but the type `LockGuard` does, and so if `ty = Vec` + /// 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> { + 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( diff --git a/compiler/rustc_middle/src/thir.rs b/compiler/rustc_middle/src/thir.rs index 8f26e05cb7206..06336d57704f8 100644 --- a/compiler/rustc_middle/src/thir.rs +++ b/compiler/rustc_middle/src/thir.rs @@ -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, + /// 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, + /// 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, +} + #[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); diff --git a/compiler/rustc_middle/src/ty/rvalue_scopes.rs b/compiler/rustc_middle/src/ty/rvalue_scopes.rs index 37dcf7c0d649f..57c2d7623d2d7 100644 --- a/compiler/rustc_middle/src/ty/rvalue_scopes.rs +++ b/compiler/rustc_middle/src/ty/rvalue_scopes.rs @@ -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 { + ) -> (Option, Option) { // Check for a designated rvalue scope. if let Some(&s) = self.map.get(&expr_id) { debug!("temporary_scope({expr_id:?}) = {s:?} [custom]"); - return s; + 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 diff --git a/compiler/rustc_mir_build/src/build/expr/as_operand.rs b/compiler/rustc_mir_build/src/build/expr/as_operand.rs index 159959d4f1f77..0cab853196b8e 100644 --- a/compiler/rustc_mir_build/src/build/expr/as_operand.rs +++ b/compiler/rustc_mir_build/src/build/expr/as_operand.rs @@ -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> { - 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> { - 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, + 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, + scope: TempLifetime, expr_id: ExprId, ) -> BlockAnd> { let this = self; diff --git a/compiler/rustc_mir_build/src/build/expr/as_place.rs b/compiler/rustc_mir_build/src/build/expr/as_place.rs index 9f6e0735b4822..6ce88cdc39d0c 100644 --- a/compiler/rustc_mir_build/src/build/expr/as_place.rs +++ b/compiler/rustc_mir_build/src/build/expr/as_place.rs @@ -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>, - temp_lifetime: Option, + temp_lifetime: TempLifetime, expr_span: Span, source_info: SourceInfo, ) -> BlockAnd> { diff --git a/compiler/rustc_mir_build/src/build/expr/as_rvalue.rs b/compiler/rustc_mir_build/src/build/expr/as_rvalue.rs index a3fee38908b4b..3f89e337781e5 100644 --- a/compiler/rustc_mir_build/src/build/expr/as_rvalue.rs +++ b/compiler/rustc_mir_build/src/build/expr/as_rvalue.rs @@ -33,14 +33,18 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { expr_id: ExprId, ) -> BlockAnd> { 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, + scope: TempLifetime, expr_id: ExprId, ) -> BlockAnd> { 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, + scope: TempLifetime, outer_source_info: SourceInfo, ) -> BlockAnd> { let this = self; diff --git a/compiler/rustc_mir_build/src/build/expr/as_temp.rs b/compiler/rustc_mir_build/src/build/expr/as_temp.rs index b8b5e4634ed89..466f67b1ba4d8 100644 --- a/compiler/rustc_mir_build/src/build/expr/as_temp.rs +++ b/compiler/rustc_mir_build/src/build/expr/as_temp.rs @@ -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, + temp_lifetime: TempLifetime, expr_id: ExprId, mutability: Mutability, ) -> BlockAnd { @@ -31,7 +30,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { fn as_temp_inner( &mut self, mut block: BasicBlock, - temp_lifetime: Option, + temp_lifetime: TempLifetime, expr_id: ExprId, mutability: Mutability, ) -> BlockAnd { @@ -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) } } diff --git a/compiler/rustc_mir_build/src/build/expr/into.rs b/compiler/rustc_mir_build/src/build/expr/into.rs index 0dec56d21ae1a..bebb44faba614 100644 --- a/compiler/rustc_mir_build/src/build/expr/into.rs +++ b/compiler/rustc_mir_build/src/build/expr/into.rs @@ -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 { diff --git a/compiler/rustc_mir_build/src/build/expr/stmt.rs b/compiler/rustc_mir_build/src/build/expr/stmt.rs index 02ca12028d304..15ee6dd014ce9 100644 --- a/compiler/rustc_mir_build/src/build/expr/stmt.rs +++ b/compiler/rustc_mir_build/src/build/expr/stmt.rs @@ -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; diff --git a/compiler/rustc_mir_build/src/build/matches/mod.rs b/compiler/rustc_mir_build/src/build/matches/mod.rs index a62d4e9d87373..a4d1629588094 100644 --- a/compiler/rustc_mir_build/src/build/matches/mod.rs +++ b/compiler/rustc_mir_build/src/build/matches/mod.rs @@ -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)); diff --git a/compiler/rustc_mir_build/src/build/scope.rs b/compiler/rustc_mir_build/src/build/scope.rs index e63fbeeac6620..636e47b7ad2f2 100644 --- a/compiler/rustc_mir_build/src/build/scope.rs +++ b/compiler/rustc_mir_build/src/build/scope.rs @@ -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 { diff --git a/compiler/rustc_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs index 198fa4ffb7a6d..47b7f33295185 100644 --- a/compiler/rustc_mir_build/src/thir/cx/expr.rs +++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs @@ -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, + 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>, ) -> 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 { diff --git a/compiler/rustc_mir_dataflow/src/impls/liveness.rs b/compiler/rustc_mir_dataflow/src/impls/liveness.rs index e06c1f2bb49e3..fd7254a0210dc 100644 --- a/compiler/rustc_mir_dataflow/src/impls/liveness.rs +++ b/compiler/rustc_mir_dataflow/src/impls/liveness.rs @@ -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 { diff --git a/compiler/rustc_mir_dataflow/src/impls/storage_liveness.rs b/compiler/rustc_mir_dataflow/src/impls/storage_liveness.rs index c5fd2a631ff74..576289e228ad0 100644 --- a/compiler/rustc_mir_dataflow/src/impls/storage_liveness.rs +++ b/compiler/rustc_mir_dataflow/src/impls/storage_liveness.rs @@ -157,6 +157,7 @@ impl<'tcx> Analysis<'tcx> for MaybeRequiresStorage<'_, 'tcx> { | StatementKind::Nop | StatementKind::Retag(..) | StatementKind::Intrinsic(..) + | StatementKind::BackwardIncompatibleDropHint { .. } | StatementKind::StorageLive(..) => {} } } diff --git a/compiler/rustc_mir_dataflow/src/move_paths/builder.rs b/compiler/rustc_mir_dataflow/src/move_paths/builder.rs index fd8e403ebc26e..0880364bfca00 100644 --- a/compiler/rustc_mir_dataflow/src/move_paths/builder.rs +++ b/compiler/rustc_mir_dataflow/src/move_paths/builder.rs @@ -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 => {} } } diff --git a/compiler/rustc_mir_transform/messages.ftl b/compiler/rustc_mir_transform/messages.ftl index 9bbfae17fd9e8..d00bfc66a6a57 100644 --- a/compiler/rustc_mir_transform/messages.ftl +++ b/compiler/rustc_mir_transform/messages.ftl @@ -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) diff --git a/compiler/rustc_mir_transform/src/coroutine.rs b/compiler/rustc_mir_transform/src/coroutine.rs index d38a1dd11dc29..8295a806d7125 100644 --- a/compiler/rustc_mir_transform/src/coroutine.rs +++ b/compiler/rustc_mir_transform/src/coroutine.rs @@ -1772,6 +1772,7 @@ impl<'tcx> Visitor<'tcx> for EnsureCoroutineFieldAssignmentsNeverAlias<'_> { | StatementKind::Coverage(..) | StatementKind::Intrinsic(..) | StatementKind::ConstEvalCounter + | StatementKind::BackwardIncompatibleDropHint { .. } | StatementKind::Nop => {} } } diff --git a/compiler/rustc_mir_transform/src/coverage/spans/from_mir.rs b/compiler/rustc_mir_transform/src/coverage/spans/from_mir.rs index 875db23ce0969..824d657e1fc2d 100644 --- a/compiler/rustc_mir_transform/src/coverage/spans/from_mir.rs +++ b/compiler/rustc_mir_transform/src/coverage/spans/from_mir.rs @@ -97,6 +97,7 @@ fn filtered_statement_span(statement: &Statement<'_>) -> Option { StatementKind::StorageLive(_) | StatementKind::StorageDead(_) | StatementKind::ConstEvalCounter + | StatementKind::BackwardIncompatibleDropHint { .. } | StatementKind::Nop => None, // FIXME(#78546): MIR InstrumentCoverage - Can the source_info.span for `FakeRead` diff --git a/compiler/rustc_mir_transform/src/dataflow_const_prop.rs b/compiler/rustc_mir_transform/src/dataflow_const_prop.rs index 26480be29f318..d017202f48bb9 100644 --- a/compiler/rustc_mir_transform/src/dataflow_const_prop.rs +++ b/compiler/rustc_mir_transform/src/dataflow_const_prop.rs @@ -186,7 +186,8 @@ impl<'a, 'tcx> ConstAnalysis<'a, 'tcx> { | StatementKind::FakeRead(..) | StatementKind::PlaceMention(..) | StatementKind::Coverage(..) - | StatementKind::AscribeUserType(..) => (), + | StatementKind::BackwardIncompatibleDropHint { .. } + | StatementKind::AscribeUserType(..) => {} } } diff --git a/compiler/rustc_mir_transform/src/dead_store_elimination.rs b/compiler/rustc_mir_transform/src/dead_store_elimination.rs index 2898f82e25c38..0c75cdadc92d7 100644 --- a/compiler/rustc_mir_transform/src/dead_store_elimination.rs +++ b/compiler/rustc_mir_transform/src/dead_store_elimination.rs @@ -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) diff --git a/compiler/rustc_mir_transform/src/dest_prop.rs b/compiler/rustc_mir_transform/src/dest_prop.rs index beeab0d4a666b..9c74b2f083943 100644 --- a/compiler/rustc_mir_transform/src/dest_prop.rs +++ b/compiler/rustc_mir_transform/src/dest_prop.rs @@ -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) } diff --git a/compiler/rustc_mir_transform/src/jump_threading.rs b/compiler/rustc_mir_transform/src/jump_threading.rs index 5c2c36db0f784..beed007589bdf 100644 --- a/compiler/rustc_mir_transform/src/jump_threading.rs +++ b/compiler/rustc_mir_transform/src/jump_threading.rs @@ -352,6 +352,7 @@ impl<'a, 'tcx> TOFinder<'a, 'tcx> { | StatementKind::FakeRead(..) | StatementKind::ConstEvalCounter | StatementKind::PlaceMention(..) + | StatementKind::BackwardIncompatibleDropHint { .. } | StatementKind::Nop => None, } } diff --git a/compiler/rustc_mir_transform/src/lib.rs b/compiler/rustc_mir_transform/src/lib.rs index 5651bf469d54d..bfb842e448564 100644 --- a/compiler/rustc_mir_transform/src/lib.rs +++ b/compiler/rustc_mir_transform/src/lib.rs @@ -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 { diff --git a/compiler/rustc_mir_transform/src/lint_tail_expr_drop_order.rs b/compiler/rustc_mir_transform/src/lint_tail_expr_drop_order.rs new file mode 100644 index 0000000000000..b8502fcbafb1b --- /dev/null +++ b/compiler/rustc_mir_transform/src/lint_tail_expr_drop_order.rs @@ -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, + move_data: &'a MoveData<'tcx>, + maybe_init: &'a mut ResultsCursor<'mir, 'tcx, MaybeInitializedPlaces<'mir, 'tcx>>, + block_drop_value_info: &'a mut IndexSlice, + collected_drops: &'a mut ChunkedBitSet, + visited: FxHashMap>>>, +} + +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`, whose drop is delegated to `K` and `V`. +fn true_significant_drop_ty<'tcx>( + tcx: TyCtxt<'tcx>, + ty: Ty<'tcx>, +) -> Option; 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>, +) -> 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 { + 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 { + 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, + user_names: &IndexMap, +) -> IndexMap { + 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>, + #[label(mir_transform_drop_location)] + drop_span: Option, + #[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>, +} + +/// 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, + >( + 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, +} diff --git a/compiler/rustc_mir_transform/src/remove_noop_landing_pads.rs b/compiler/rustc_mir_transform/src/remove_noop_landing_pads.rs index 55394e93a5cb7..fd49e956f433b 100644 --- a/compiler/rustc_mir_transform/src/remove_noop_landing_pads.rs +++ b/compiler/rustc_mir_transform/src/remove_noop_landing_pads.rs @@ -92,6 +92,7 @@ impl RemoveNoopLandingPads { | StatementKind::AscribeUserType(..) | StatementKind::Coverage(..) | StatementKind::ConstEvalCounter + | StatementKind::BackwardIncompatibleDropHint { .. } | StatementKind::Nop => { // These are all noops in a landing pad } diff --git a/compiler/rustc_mir_transform/src/remove_zsts.rs b/compiler/rustc_mir_transform/src/remove_zsts.rs index 2f723bccc1913..6fd70fbe9b04d 100644 --- a/compiler/rustc_mir_transform/src/remove_zsts.rs +++ b/compiler/rustc_mir_transform/src/remove_zsts.rs @@ -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 diff --git a/compiler/rustc_mir_transform/src/simplify.rs b/compiler/rustc_mir_transform/src/simplify.rs index 7ed43547e1127..4f312ed2aaabc 100644 --- a/compiler/rustc_mir_transform/src/simplify.rs +++ b/compiler/rustc_mir_transform/src/simplify.rs @@ -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(); } diff --git a/compiler/rustc_mir_transform/src/validate.rs b/compiler/rustc_mir_transform/src/validate.rs index 724238ecfc93e..1739fdcc9af21 100644 --- a/compiler/rustc_mir_transform/src/validate.rs +++ b/compiler/rustc_mir_transform/src/validate.rs @@ -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 => {} } diff --git a/compiler/rustc_smir/src/rustc_smir/convert/mir.rs b/compiler/rustc_smir/src/rustc_smir/convert/mir.rs index 820d8a6be25ca..fcdf8703b14da 100644 --- a/compiler/rustc_smir/src/rustc_smir/convert/mir.rs +++ b/compiler/rustc_smir/src/rustc_smir/convert/mir.rs @@ -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, } } diff --git a/compiler/rustc_ty_utils/src/needs_drop.rs b/compiler/rustc_ty_utils/src/needs_drop.rs index 03ab3a5548684..469a4ac3e414d 100644 --- a/compiler/rustc_ty_utils/src/needs_drop.rs +++ b/compiler/rustc_ty_utils/src/needs_drop.rs @@ -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>, @@ -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> { + 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>; + #[instrument(level = "debug", skip(self), ret)] fn next(&mut self) -> Option>> { 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, only_significant: bool, + exhaustive: bool, ) -> impl Iterator>> { 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::, _>>() .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::, _>>() .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> { + 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::>(), + ) +} + 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 }; } diff --git a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs index abadca7140019..345c46f944a5d 100644 --- a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs +++ b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs @@ -233,6 +233,7 @@ fn check_statement<'tcx>( | StatementKind::PlaceMention(..) | StatementKind::Coverage(..) | StatementKind::ConstEvalCounter + | StatementKind::BackwardIncompatibleDropHint { .. } | StatementKind::Nop => Ok(()), } } diff --git a/tests/ui/drop/lint-tail-expr-drop-order-gated.rs b/tests/ui/drop/lint-tail-expr-drop-order-gated.rs index fde542c756f2e..508e7bdbf99ae 100644 --- a/tests/ui/drop/lint-tail-expr-drop-order-gated.rs +++ b/tests/ui/drop/lint-tail-expr-drop-order-gated.rs @@ -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)] diff --git a/tests/ui/drop/lint-tail-expr-drop-order.rs b/tests/ui/drop/lint-tail-expr-drop-order.rs index d61abae518707..0fabc1f085c02 100644 --- a/tests/ui/drop/lint-tail-expr-drop-order.rs +++ b/tests/ui/drop/lint-tail-expr-drop-order.rs @@ -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 +//@ 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_lint_closure() -> impl FnOnce() -> i32 { +fn should_not_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 + move || { + // Should not lint because ... + x.get() + LoudDropper.get() + } + // ^ closure captures like `x` are always dropped last by contract +} + +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() -> &'static str { + fn extract(_: &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 = 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() {} diff --git a/tests/ui/drop/lint-tail-expr-drop-order.stderr b/tests/ui/drop/lint-tail-expr-drop-order.stderr index 6775c4ce6d15a..f0da24605e64c 100644 --- a/tests/ui/drop/lint-tail-expr-drop-order.stderr +++ b/tests/ui/drop/lint-tail-expr-drop-order.stderr @@ -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 +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 +error: relative drop order changing in Rust 2024 + --> $DIR/lint-tail-expr-drop-order.rs:67:19 + | +LL | let x = LoudDropper; + | - + | | + | `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 +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:94:7 | 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() - | ^^^^^^^^^^^ + | - + | | + | `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 +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: 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:147:5 | -LL | let x = LoudDropper; - | - these values have significant drop implementation and will observe changes in drop order under Edition 2024 -LL | // Should lint -LL | x.get() + LoudDropper.get() - | ^^^^^^^^^^^ +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 +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 + = 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 = 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 +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 +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 +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: aborting due to 8 previous errors diff --git a/tests/ui/thir-print/thir-flat-const-variant.stdout b/tests/ui/thir-print/thir-flat-const-variant.stdout index 1840be7885bce..5588cfdfa5ce0 100644 --- a/tests/ui/thir-print/thir-flat-const-variant.stdout +++ b/tests/ui/thir-print/thir-flat-const-variant.stdout @@ -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), }, ], diff --git a/tests/ui/thir-print/thir-flat.stdout b/tests/ui/thir-print/thir-flat.stdout index a31d08adab67c..59cecfe511c24 100644 --- a/tests/ui/thir-print/thir-flat.stdout +++ b/tests/ui/thir-print/thir-flat.stdout @@ -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), }, ], diff --git a/tests/ui/thir-print/thir-tree-match.stdout b/tests/ui/thir-print/thir-tree-match.stdout index 8cff788766189..a9d6985928aad 100644 --- a/tests/ui/thir-print/thir-tree-match.stdout +++ b/tests/ui/thir-print/thir-tree-match.stdout @@ -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 { diff --git a/tests/ui/thir-print/thir-tree.stdout b/tests/ui/thir-print/thir-tree.stdout index ef6db368dbe3e..b39581ad84151 100644 --- a/tests/ui/thir-print/thir-tree.stdout +++ b/tests/ui/thir-print/thir-tree.stdout @@ -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 {