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<T: Idx> ChunkedBitSet<T> {
         self.chunks.iter().map(|chunk| chunk.count()).sum()
     }
 
+    pub fn is_empty(&self) -> bool {
+        self.chunks.iter().all(|chunk| matches!(chunk, Chunk::Zeros(..) | Chunk::Ones(0)))
+    }
+
     /// Returns `true` if `self` contains `elem`.
     #[inline]
     pub fn contains(&self, elem: T) -> bool {
@@ -668,12 +672,140 @@ impl<T: Idx> BitRelations<ChunkedBitSet<T>> for ChunkedBitSet<T> {
         changed
     }
 
-    fn subtract(&mut self, _other: &ChunkedBitSet<T>) -> bool {
-        unimplemented!("implement if/when necessary");
+    fn subtract(&mut self, other: &ChunkedBitSet<T>) -> bool {
+        assert_eq!(self.domain_size, other.domain_size);
+        debug_assert_eq!(self.chunks.len(), other.chunks.len());
+
+        let mut changed = false;
+        for (mut self_chunk, other_chunk) in self.chunks.iter_mut().zip(other.chunks.iter()) {
+            match (&mut self_chunk, &other_chunk) {
+                (Zeros(..), _) | (_, Zeros(..)) => {}
+                (
+                    Ones(self_chunk_domain_size) | Mixed(self_chunk_domain_size, _, _),
+                    Ones(other_chunk_domain_size),
+                ) => {
+                    debug_assert_eq!(self_chunk_domain_size, other_chunk_domain_size);
+                    changed = true;
+                    *self_chunk = Zeros(*self_chunk_domain_size);
+                }
+                (
+                    Ones(self_chunk_domain_size),
+                    Mixed(other_chunk_domain_size, other_chunk_count, other_chunk_words),
+                ) => {
+                    debug_assert_eq!(self_chunk_domain_size, other_chunk_domain_size);
+                    changed = true;
+                    let num_words = num_words(*self_chunk_domain_size as usize);
+                    debug_assert!(num_words > 0 && num_words <= CHUNK_WORDS);
+                    let mut tail_mask =
+                        1 << (*other_chunk_domain_size - ((num_words - 1) * WORD_BITS) as u16) - 1;
+                    let mut self_chunk_words = **other_chunk_words;
+                    for word in self_chunk_words[0..num_words].iter_mut().rev() {
+                        *word = !*word & tail_mask;
+                        tail_mask = u64::MAX;
+                    }
+                    let self_chunk_count = *self_chunk_domain_size - *other_chunk_count;
+                    debug_assert_eq!(
+                        self_chunk_count,
+                        self_chunk_words[0..num_words]
+                            .iter()
+                            .map(|w| w.count_ones() as ChunkSize)
+                            .sum()
+                    );
+                    *self_chunk =
+                        Mixed(*self_chunk_domain_size, self_chunk_count, Rc::new(self_chunk_words));
+                }
+                (
+                    Mixed(
+                        self_chunk_domain_size,
+                        ref mut self_chunk_count,
+                        ref mut self_chunk_words,
+                    ),
+                    Mixed(_other_chunk_domain_size, _other_chunk_count, other_chunk_words),
+                ) => {
+                    // See [`<Self as BitRelations<ChunkedBitSet<T>>>::union`] for the explanation
+                    let op = |a: u64, b: u64| a & !b;
+                    let num_words = num_words(*self_chunk_domain_size as usize);
+                    if bitwise_changes(
+                        &self_chunk_words[0..num_words],
+                        &other_chunk_words[0..num_words],
+                        op,
+                    ) {
+                        let self_chunk_words = Rc::make_mut(self_chunk_words);
+                        let has_changed = bitwise(
+                            &mut self_chunk_words[0..num_words],
+                            &other_chunk_words[0..num_words],
+                            op,
+                        );
+                        debug_assert!(has_changed);
+                        *self_chunk_count = self_chunk_words[0..num_words]
+                            .iter()
+                            .map(|w| w.count_ones() as ChunkSize)
+                            .sum();
+                        if *self_chunk_count == 0 {
+                            *self_chunk = Zeros(*self_chunk_domain_size);
+                        }
+                        changed = true;
+                    }
+                }
+            }
+        }
+        changed
     }
 
-    fn intersect(&mut self, _other: &ChunkedBitSet<T>) -> bool {
-        unimplemented!("implement if/when necessary");
+    fn intersect(&mut self, other: &ChunkedBitSet<T>) -> bool {
+        assert_eq!(self.domain_size, other.domain_size);
+        debug_assert_eq!(self.chunks.len(), other.chunks.len());
+
+        let mut changed = false;
+        for (mut self_chunk, other_chunk) in self.chunks.iter_mut().zip(other.chunks.iter()) {
+            match (&mut self_chunk, &other_chunk) {
+                (Zeros(..), _) | (_, Ones(..)) => {}
+                (
+                    Ones(self_chunk_domain_size),
+                    Zeros(other_chunk_domain_size) | Mixed(other_chunk_domain_size, ..),
+                )
+                | (Mixed(self_chunk_domain_size, ..), Zeros(other_chunk_domain_size)) => {
+                    debug_assert_eq!(self_chunk_domain_size, other_chunk_domain_size);
+                    changed = true;
+                    *self_chunk = other_chunk.clone();
+                }
+                (
+                    Mixed(
+                        self_chunk_domain_size,
+                        ref mut self_chunk_count,
+                        ref mut self_chunk_words,
+                    ),
+                    Mixed(_other_chunk_domain_size, _other_chunk_count, other_chunk_words),
+                ) => {
+                    // See [`<Self as BitRelations<ChunkedBitSet<T>>>::union`] for the explanation
+                    let op = |a, b| a & b;
+                    let num_words = num_words(*self_chunk_domain_size as usize);
+                    if bitwise_changes(
+                        &self_chunk_words[0..num_words],
+                        &other_chunk_words[0..num_words],
+                        op,
+                    ) {
+                        let self_chunk_words = Rc::make_mut(self_chunk_words);
+                        let has_changed = bitwise(
+                            &mut self_chunk_words[0..num_words],
+                            &other_chunk_words[0..num_words],
+                            op,
+                        );
+                        debug_assert!(has_changed);
+                        *self_chunk_count = self_chunk_words[0..num_words]
+                            .iter()
+                            .map(|w| w.count_ones() as ChunkSize)
+                            .sum();
+                        if *self_chunk_count == 0 {
+                            *self_chunk = Zeros(*self_chunk_domain_size);
+                        }
+                        changed = true;
+                    }
+                }
+            }
+        }
+
+        changed
     }
 }
 
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 <https://github.com/rust-lang/rust/issues/123739>",
-    };
-}
-
-declare_lint_pass!(TailExprDropOrder => [TAIL_EXPR_DROP_ORDER]);
-
-impl TailExprDropOrder {
-    fn check_fn_or_closure<'tcx>(
-        cx: &LateContext<'tcx>,
-        fn_kind: hir::intravisit::FnKind<'tcx>,
-        body: &'tcx hir::Body<'tcx>,
-        def_id: rustc_span::def_id::LocalDefId,
-    ) {
-        let mut locals = vec![];
-        if matches!(fn_kind, hir::intravisit::FnKind::Closure) {
-            for &capture in cx.tcx.closure_captures(def_id) {
-                if matches!(capture.info.capture_kind, ty::UpvarCapture::ByValue)
-                    && capture.place.ty().has_significant_drop(cx.tcx, cx.typing_env())
-                {
-                    locals.push(capture.var_ident.span);
-                }
-            }
-        }
-        for param in body.params {
-            if cx
-                .typeck_results()
-                .node_type(param.hir_id)
-                .has_significant_drop(cx.tcx, cx.typing_env())
-            {
-                locals.push(param.span);
-            }
-        }
-        if let hir::ExprKind::Block(block, _) = body.value.kind {
-            LintVisitor { cx, locals }.check_block_inner(block);
-        } else {
-            LintTailExpr { cx, locals: &locals, is_root_tail_expr: true }.visit_expr(body.value);
-        }
-    }
-}
-
-impl<'tcx> LateLintPass<'tcx> for TailExprDropOrder {
-    fn check_fn(
-        &mut self,
-        cx: &LateContext<'tcx>,
-        fn_kind: hir::intravisit::FnKind<'tcx>,
-        _: &'tcx hir::FnDecl<'tcx>,
-        body: &'tcx hir::Body<'tcx>,
-        _: Span,
-        def_id: rustc_span::def_id::LocalDefId,
-    ) {
-        if !body.value.span.edition().at_least_rust_2024() {
-            Self::check_fn_or_closure(cx, fn_kind, body, def_id);
-        }
-    }
-}
-
-struct LintVisitor<'a, 'tcx> {
-    cx: &'a LateContext<'tcx>,
-    // We only record locals that have significant drops
-    locals: Vec<Span>,
-}
-
-struct LocalCollector<'a, 'tcx> {
-    cx: &'a LateContext<'tcx>,
-    locals: &'a mut Vec<Span>,
-}
-
-impl<'a, 'tcx> Visitor<'tcx> for LocalCollector<'a, 'tcx> {
-    type Result = ();
-    fn visit_pat(&mut self, pat: &'tcx Pat<'tcx>) {
-        if let PatKind::Binding(_binding_mode, id, ident, pat) = pat.kind {
-            let ty = self.cx.typeck_results().node_type(id);
-            if ty.has_significant_drop(self.cx.tcx, self.cx.typing_env()) {
-                self.locals.push(ident.span);
-            }
-            if let Some(pat) = pat {
-                self.visit_pat(pat);
-            }
-        } else {
-            intravisit::walk_pat(self, pat);
-        }
-    }
-}
-
-impl<'a, 'tcx> Visitor<'tcx> for LintVisitor<'a, 'tcx> {
-    fn visit_block(&mut self, block: &'tcx Block<'tcx>) {
-        let mut locals = <_>::default();
-        swap(&mut locals, &mut self.locals);
-        self.check_block_inner(block);
-        swap(&mut locals, &mut self.locals);
-    }
-    fn visit_local(&mut self, local: &'tcx LetStmt<'tcx>) {
-        LocalCollector { cx: self.cx, locals: &mut self.locals }.visit_local(local);
-    }
-}
-
-impl<'a, 'tcx> LintVisitor<'a, 'tcx> {
-    fn check_block_inner(&mut self, block: &Block<'tcx>) {
-        if block.span.at_least_rust_2024() {
-            // We only lint up to Edition 2021
-            return;
-        }
-        let Some(tail_expr) = block.expr else { return };
-        for stmt in block.stmts {
-            match stmt.kind {
-                StmtKind::Let(let_stmt) => self.visit_local(let_stmt),
-                StmtKind::Item(_) => {}
-                StmtKind::Expr(e) | StmtKind::Semi(e) => self.visit_expr(e),
-            }
-        }
-        if self.locals.is_empty() {
-            return;
-        }
-        LintTailExpr { cx: self.cx, locals: &self.locals, is_root_tail_expr: true }
-            .visit_expr(tail_expr);
-    }
-}
-
-struct LintTailExpr<'a, 'tcx> {
-    cx: &'a LateContext<'tcx>,
-    is_root_tail_expr: bool,
-    locals: &'a [Span],
-}
-
-impl<'a, 'tcx> LintTailExpr<'a, 'tcx> {
-    fn expr_eventually_point_into_local(mut expr: &Expr<'tcx>) -> bool {
-        loop {
-            match expr.kind {
-                ExprKind::Index(access, _, _) | ExprKind::Field(access, _) => expr = access,
-                ExprKind::AddrOf(_, _, referee) | ExprKind::Unary(UnOp::Deref, referee) => {
-                    expr = referee
-                }
-                ExprKind::Path(_)
-                    if let ExprKind::Path(QPath::Resolved(_, path)) = expr.kind
-                        && let [local, ..] = path.segments
-                        && let Res::Local(_) = local.res =>
-                {
-                    return true;
-                }
-                _ => return false,
-            }
-        }
-    }
-
-    fn expr_generates_nonlocal_droppy_value(&self, expr: &Expr<'tcx>) -> bool {
-        if Self::expr_eventually_point_into_local(expr) {
-            return false;
-        }
-        self.cx
-            .typeck_results()
-            .expr_ty(expr)
-            .has_significant_drop(self.cx.tcx, self.cx.typing_env())
-    }
-}
-
-impl<'a, 'tcx> Visitor<'tcx> for LintTailExpr<'a, 'tcx> {
-    fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
-        if self.is_root_tail_expr {
-            self.is_root_tail_expr = false;
-        } else if self.expr_generates_nonlocal_droppy_value(expr) {
-            self.cx.tcx.emit_node_span_lint(
-                TAIL_EXPR_DROP_ORDER,
-                expr.hir_id,
-                expr.span,
-                TailExprDropOrderLint { spans: self.locals.to_vec() },
-            );
-            return;
-        }
-        match expr.kind {
-            ExprKind::Match(scrutinee, _, _) => self.visit_expr(scrutinee),
-
-            ExprKind::ConstBlock(_)
-            | ExprKind::Array(_)
-            | ExprKind::Break(_, _)
-            | ExprKind::Continue(_)
-            | ExprKind::Ret(_)
-            | ExprKind::Become(_)
-            | ExprKind::Yield(_, _)
-            | ExprKind::InlineAsm(_)
-            | ExprKind::If(_, _, _)
-            | ExprKind::Loop(_, _, _, _)
-            | ExprKind::Closure(_)
-            | ExprKind::DropTemps(_)
-            | ExprKind::OffsetOf(_, _)
-            | ExprKind::Assign(_, _, _)
-            | ExprKind::AssignOp(_, _, _)
-            | ExprKind::Lit(_)
-            | ExprKind::Err(_) => {}
-
-            ExprKind::MethodCall(_, _, _, _)
-            | ExprKind::Call(_, _)
-            | ExprKind::Type(_, _)
-            | ExprKind::Tup(_)
-            | ExprKind::Binary(_, _, _)
-            | ExprKind::Unary(_, _)
-            | ExprKind::Path(_)
-            | ExprKind::Let(_)
-            | ExprKind::Cast(_, _)
-            | ExprKind::Field(_, _)
-            | ExprKind::Index(_, _, _)
-            | ExprKind::AddrOf(_, _, _)
-            | ExprKind::Struct(_, _, _)
-            | ExprKind::Repeat(_, _) => intravisit::walk_expr(self, expr),
-
-            ExprKind::Block(_, _) => {
-                // We do not lint further because the drop order stays the same inside the block
-            }
-        }
-    }
-    fn visit_block(&mut self, block: &'tcx Block<'tcx>) {
-        LintVisitor { cx: self.cx, locals: <_>::default() }.check_block_inner(block);
-    }
-}
-
-#[derive(LintDiagnostic)]
-#[diag(lint_tail_expr_drop_order)]
-struct TailExprDropOrderLint {
-    #[label]
-    pub spans: Vec<Span>,
-}
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 <https://github.com/rust-lang/rust/issues/123739>",
+    };
+}
+
 declare_lint! {
     /// The `rust_2024_guarded_string_incompatible_syntax` lint detects `#` tokens
     /// that will be parsed as part of a guarded string literal in Rust 2024.
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<RvalueCandidateType>,
 
+    /// Backwards incompatible scoping that will be introduced in future editions.
+    /// This information is used later for linting to identify locals and
+    /// temporary values that will receive backwards-incompatible drop orders.
+    pub backwards_incompatible_scope: UnordMap<hir::ItemLocalId, Scope>,
+
     /// If there are any `yield` nested within a scope, this map
     /// stores the `Span` of the last one and its index in the
     /// postorder of the Visitor traversal on the HIR.
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<Place<'tcx>>,
+        /// Reason for backward incompatibility
+        reason: BackwardIncompatibleDropReason,
+    },
 }
 
 impl StatementKind<'_> {
@@ -452,6 +464,7 @@ impl StatementKind<'_> {
             StatementKind::Intrinsic(..) => "Intrinsic",
             StatementKind::ConstEvalCounter => "ConstEvalCounter",
             StatementKind::Nop => "Nop",
+            StatementKind::BackwardIncompatibleDropHint { .. } => "BackwardIncompatibleDropHint",
         }
     }
 }
@@ -897,6 +910,21 @@ pub enum TerminatorKind<'tcx> {
     },
 }
 
+#[derive(
+    Clone,
+    Debug,
+    TyEncodable,
+    TyDecodable,
+    Hash,
+    HashStable,
+    PartialEq,
+    TypeFoldable,
+    TypeVisitable
+)]
+pub enum BackwardIncompatibleDropReason {
+    Edition2024,
+}
+
 impl TerminatorKind<'_> {
     /// Returns a simple string representation of a `TerminatorKind` variant, independent of any
     /// values it might hold (e.g. `TerminatorKind::Call` always returns `"Call"`).
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<V> = DefaultCache<Self, V>;
+
+    fn default_span(&self, _tcx: TyCtxt<'_>) -> Span {
+        DUMMY_SP
+    }
+}
+
 impl<'tcx> Key for ty::TraitRef<'tcx> {
     type Cache<V> = DefaultCache<Self, V>;
 
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<LockGuard>` does not
+    /// have significant drop but the type `LockGuard` does, and so if `ty  = Vec<LockGuard>`
+    /// then the return value would be `&[LockGuard]`.
+    /// *IMPORTANT*: *DO NOT* run this query before promoted MIR body is constructed,
+    /// because this query partially depends on that query.
+    /// Otherwise, there is a risk of query cycles.
+    query list_significant_drop_tys(ty: ty::ParamEnvAnd<'tcx, Ty<'tcx>>) -> &'tcx ty::List<Ty<'tcx>> {
+        desc { |tcx| "computing when `{}` has a significant destructor", ty.value }
+        cache_on_disk_if { false }
+    }
+
     /// Computes the layout of a type. Note that this implicitly
     /// executes in "reveal all" mode, and will normalize the input type.
     query layout_of(
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<region::Scope>,
+    /// temporary
+    pub temp_lifetime: TempLifetime,
 
     /// span of the expression in the source
     pub span: Span,
 }
 
+/// Temporary lifetime information for THIR expressions
+#[derive(Clone, Copy, Debug, HashStable)]
+pub struct TempLifetime {
+    /// Lifetime for temporaries as expected.
+    /// This should be `None` in a constant context.
+    pub temp_lifetime: Option<region::Scope>,
+    /// If `Some(lt)`, indicates that the lifetime of this temporary will change to `lt` in a future edition.
+    /// If `None`, then no changes are expected, or lints are disabled.
+    pub backwards_incompatible: Option<region::Scope>,
+}
+
 #[derive(Clone, Debug, HashStable)]
 pub enum ExprKind<'tcx> {
     /// `Scope`s are used to explicitly mark destruction scopes,
@@ -1087,7 +1098,7 @@ mod size_asserts {
     use super::*;
     // tidy-alphabetical-start
     static_assert_size!(Block, 48);
-    static_assert_size!(Expr<'_>, 64);
+    static_assert_size!(Expr<'_>, 72);
     static_assert_size!(ExprKind<'_>, 40);
     static_assert_size!(Pat<'_>, 64);
     static_assert_size!(PatKind<'_>, 48);
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<Scope> {
+    ) -> (Option<Scope>, Option<Scope>) {
         // Check for a designated rvalue scope.
         if let Some(&s) = self.map.get(&expr_id) {
             debug!("temporary_scope({expr_id:?}) = {s:?} [custom]");
-            return s;
+            return (s, None);
         }
 
         // Otherwise, locate the innermost terminating scope
@@ -34,27 +36,40 @@ impl RvalueScopes {
         // have an enclosing scope, hence no scope will be
         // returned.
         let mut id = Scope { id: expr_id, data: ScopeData::Node };
+        let mut backwards_incompatible = None;
 
         while let Some(&(p, _)) = region_scope_tree.parent_map.get(&id) {
             match p.data {
                 ScopeData::Destruction => {
                     debug!("temporary_scope({expr_id:?}) = {id:?} [enclosing]");
-                    return Some(id);
+                    return (Some(id), backwards_incompatible);
                 }
                 ScopeData::IfThenRescope => {
                     debug!("temporary_scope({expr_id:?}) = {p:?} [enclosing]");
-                    return Some(p);
+                    return (Some(p), backwards_incompatible);
                 }
                 ScopeData::Node
                 | ScopeData::CallSite
                 | ScopeData::Arguments
                 | ScopeData::IfThen
-                | ScopeData::Remainder(_) => id = p,
+                | ScopeData::Remainder(_) => {
+                    // If we haven't already passed through a backwards-incompatible node,
+                    // then check if we are passing through one now and record it if so.
+                    // This is for now only working for cases where a temporary lifetime is
+                    // *shortened*.
+                    if backwards_incompatible.is_none() {
+                        backwards_incompatible = region_scope_tree
+                            .backwards_incompatible_scope
+                            .get(&p.item_local_id())
+                            .copied();
+                    }
+                    id = p
+                }
             }
         }
 
         debug!("temporary_scope({expr_id:?}) = None");
-        None
+        (None, backwards_incompatible)
     }
 
     /// Make an association between a sub-expression and an extended lifetime
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<Operand<'tcx>> {
-        let local_scope = self.local_scope();
-        self.as_operand(block, Some(local_scope), expr_id, LocalInfo::Boring, NeedsTemporary::Maybe)
+        self.as_operand(
+            block,
+            self.local_temp_lifetime(),
+            expr_id,
+            LocalInfo::Boring,
+            NeedsTemporary::Maybe,
+        )
     }
 
     /// Returns an operand suitable for use until the end of the current scope expression and
@@ -80,8 +90,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
         block: BasicBlock,
         expr: ExprId,
     ) -> BlockAnd<Operand<'tcx>> {
-        let local_scope = self.local_scope();
-        self.as_call_operand(block, Some(local_scope), expr)
+        self.as_call_operand(block, self.local_temp_lifetime(), expr)
     }
 
     /// Compile `expr` into a value that can be used as an operand.
@@ -102,7 +111,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
     pub(crate) fn as_operand(
         &mut self,
         mut block: BasicBlock,
-        scope: Option<region::Scope>,
+        scope: TempLifetime,
         expr_id: ExprId,
         local_info: LocalInfo<'tcx>,
         needs_temporary: NeedsTemporary,
@@ -146,7 +155,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
     pub(crate) fn as_call_operand(
         &mut self,
         mut block: BasicBlock,
-        scope: Option<region::Scope>,
+        scope: TempLifetime,
         expr_id: ExprId,
     ) -> BlockAnd<Operand<'tcx>> {
         let this = self;
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<Local>>,
-        temp_lifetime: Option<region::Scope>,
+        temp_lifetime: TempLifetime,
         expr_span: Span,
         source_info: SourceInfo,
     ) -> BlockAnd<PlaceBuilder<'tcx>> {
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<Rvalue<'tcx>> {
         let local_scope = self.local_scope();
-        self.as_rvalue(block, Some(local_scope), expr_id)
+        self.as_rvalue(
+            block,
+            TempLifetime { temp_lifetime: Some(local_scope), backwards_incompatible: None },
+            expr_id,
+        )
     }
 
     /// Compile `expr`, yielding an rvalue.
     pub(crate) fn as_rvalue(
         &mut self,
         mut block: BasicBlock,
-        scope: Option<region::Scope>,
+        scope: TempLifetime,
         expr_id: ExprId,
     ) -> BlockAnd<Rvalue<'tcx>> {
         let this = self;
@@ -171,7 +175,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                     source_info,
                     kind: StatementKind::StorageLive(result),
                 });
-                if let Some(scope) = scope {
+                if let Some(scope) = scope.temp_lifetime {
                     // schedule a shallow free of that memory, lest we unwind:
                     this.schedule_drop_storage_and_value(expr_span, scope, result);
                 }
@@ -445,7 +449,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                                         block = this.limit_capture_mutability(
                                             upvar_expr.span,
                                             upvar_expr.ty,
-                                            scope,
+                                            scope.temp_lifetime,
                                             block,
                                             arg,
                                         )
@@ -705,7 +709,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
         &mut self,
         mut block: BasicBlock,
         value: ExprId,
-        scope: Option<region::Scope>,
+        scope: TempLifetime,
         outer_source_info: SourceInfo,
     ) -> BlockAnd<Rvalue<'tcx>> {
         let this = self;
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<region::Scope>,
+        temp_lifetime: TempLifetime,
         expr_id: ExprId,
         mutability: Mutability,
     ) -> BlockAnd<Local> {
@@ -31,7 +30,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
     fn as_temp_inner(
         &mut self,
         mut block: BasicBlock,
-        temp_lifetime: Option<region::Scope>,
+        temp_lifetime: TempLifetime,
         expr_id: ExprId,
         mutability: Mutability,
     ) -> BlockAnd<Local> {
@@ -47,8 +46,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
         }
 
         let expr_ty = expr.ty;
-        let deduplicate_temps =
-            this.fixed_temps_scope.is_some() && this.fixed_temps_scope == temp_lifetime;
+        let deduplicate_temps = this.fixed_temps_scope.is_some()
+            && this.fixed_temps_scope == temp_lifetime.temp_lifetime;
         let temp = if deduplicate_temps && let Some(temp_index) = this.fixed_temps.get(&expr_id) {
             *temp_index
         } else {
@@ -76,7 +75,9 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                     LocalInfo::BlockTailTemp(tail_info)
                 }
 
-                _ if let Some(Scope { data: ScopeData::IfThenRescope, id }) = temp_lifetime => {
+                _ if let Some(Scope { data: ScopeData::IfThenRescope, id }) =
+                    temp_lifetime.temp_lifetime =>
+                {
                     LocalInfo::IfThenRescopeTemp {
                         if_then: HirId { owner: this.hir_id.owner, local_id: id },
                     }
@@ -117,7 +118,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                 // Anything with a shorter lifetime (e.g the `&foo()` in
                 // `bar(&foo())` or anything within a block will keep the
                 // regular drops just like runtime code.
-                if let Some(temp_lifetime) = temp_lifetime {
+                if let Some(temp_lifetime) = temp_lifetime.temp_lifetime {
                     this.schedule_drop(expr_span, temp_lifetime, temp, DropKind::Storage);
                 }
             }
@@ -125,10 +126,14 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
 
         block = this.expr_into_dest(temp_place, block, expr_id).into_block();
 
-        if let Some(temp_lifetime) = temp_lifetime {
+        if let Some(temp_lifetime) = temp_lifetime.temp_lifetime {
             this.schedule_drop(expr_span, temp_lifetime, temp, DropKind::Value);
         }
 
+        if let Some(backwards_incompatible) = temp_lifetime.backwards_incompatible {
+            this.schedule_backwards_incompatible_drop(expr_span, backwards_incompatible, temp);
+        }
+
         block.and(temp)
     }
 }
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<Scope>,
+        temp_lifetime: TempLifetime,
         span: Span,
     ) -> ExprKind<'tcx> {
         let tcx = self.tcx;
@@ -325,7 +324,7 @@ impl<'tcx> Cx<'tcx> {
     fn make_mirror_unadjusted(&mut self, expr: &'tcx hir::Expr<'tcx>) -> Expr<'tcx> {
         let tcx = self.tcx;
         let expr_ty = self.typeck_results().expr_ty(expr);
-        let temp_lifetime =
+        let (temp_lifetime, backwards_incompatible) =
             self.rvalue_scopes.temporary_scope(self.region_scope_tree, expr.hir_id.local_id);
 
         let kind = match expr.kind {
@@ -361,7 +360,7 @@ impl<'tcx> Cx<'tcx> {
                     let arg_tys = args.iter().map(|e| self.typeck_results().expr_ty_adjusted(e));
                     let tupled_args = Expr {
                         ty: Ty::new_tup_from_iter(tcx, arg_tys),
-                        temp_lifetime,
+                        temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible },
                         span: expr.span,
                         kind: ExprKind::Tuple { fields: self.mirror_exprs(args) },
                     };
@@ -391,7 +390,10 @@ impl<'tcx> Cx<'tcx> {
                                 && let [value] = args
                             {
                                 return Expr {
-                                    temp_lifetime,
+                                    temp_lifetime: TempLifetime {
+                                        temp_lifetime,
+                                        backwards_incompatible,
+                                    },
                                     ty: expr_ty,
                                     span: expr.span,
                                     kind: ExprKind::Box { value: self.mirror_expr(value) },
@@ -811,13 +813,13 @@ impl<'tcx> Cx<'tcx> {
             },
             hir::ExprKind::Loop(body, ..) => {
                 let block_ty = self.typeck_results().node_type(body.hir_id);
-                let temp_lifetime = self
+                let (temp_lifetime, backwards_incompatible) = self
                     .rvalue_scopes
                     .temporary_scope(self.region_scope_tree, body.hir_id.local_id);
                 let block = self.mirror_block(body);
                 let body = self.thir.exprs.push(Expr {
                     ty: block_ty,
-                    temp_lifetime,
+                    temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible },
                     span: self.thir[block].span,
                     kind: ExprKind::Block { block },
                 });
@@ -838,13 +840,17 @@ impl<'tcx> Cx<'tcx> {
                     expr, cast_ty.hir_id, user_ty,
                 );
 
-                let cast = self.mirror_expr_cast(source, temp_lifetime, expr.span);
+                let cast = self.mirror_expr_cast(
+                    source,
+                    TempLifetime { temp_lifetime, backwards_incompatible },
+                    expr.span,
+                );
 
                 if let Some(user_ty) = user_ty {
                     // NOTE: Creating a new Expr and wrapping a Cast inside of it may be
                     //       inefficient, revisit this when performance becomes an issue.
                     let cast_expr = self.thir.exprs.push(Expr {
-                        temp_lifetime,
+                        temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible },
                         ty: expr_ty,
                         span: expr.span,
                         kind: cast,
@@ -887,7 +893,12 @@ impl<'tcx> Cx<'tcx> {
             hir::ExprKind::Err(_) => unreachable!("cannot lower a `hir::ExprKind::Err` to THIR"),
         };
 
-        Expr { temp_lifetime, ty: expr_ty, span: expr.span, kind }
+        Expr {
+            temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible },
+            ty: expr_ty,
+            span: expr.span,
+            kind,
+        }
     }
 
     fn user_args_applied_to_res(
@@ -931,7 +942,7 @@ impl<'tcx> Cx<'tcx> {
         span: Span,
         overloaded_callee: Option<Ty<'tcx>>,
     ) -> Expr<'tcx> {
-        let temp_lifetime =
+        let (temp_lifetime, backwards_incompatible) =
             self.rvalue_scopes.temporary_scope(self.region_scope_tree, expr.hir_id.local_id);
         let (ty, user_ty) = match overloaded_callee {
             Some(fn_def) => (fn_def, None),
@@ -952,7 +963,12 @@ impl<'tcx> Cx<'tcx> {
                 )
             }
         };
-        Expr { temp_lifetime, ty, span, kind: ExprKind::ZstLiteral { user_ty } }
+        Expr {
+            temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible },
+            ty,
+            span,
+            kind: ExprKind::ZstLiteral { user_ty },
+        }
     }
 
     fn convert_arm(&mut self, arm: &'tcx hir::Arm<'tcx>) -> ArmId {
@@ -1025,7 +1041,7 @@ impl<'tcx> Cx<'tcx> {
             Res::Def(DefKind::Static { .. }, id) => {
                 // this is &raw for extern static or static mut, and & for other statics
                 let ty = self.tcx.static_ptr_ty(id, self.typing_env());
-                let temp_lifetime = self
+                let (temp_lifetime, backwards_incompatible) = self
                     .rvalue_scopes
                     .temporary_scope(self.region_scope_tree, expr.hir_id.local_id);
                 let kind = if self.tcx.is_thread_local_static(id) {
@@ -1035,7 +1051,12 @@ impl<'tcx> Cx<'tcx> {
                     ExprKind::StaticRef { alloc_id, ty, def_id: id }
                 };
                 ExprKind::Deref {
-                    arg: self.thir.exprs.push(Expr { ty, temp_lifetime, span: expr.span, kind }),
+                    arg: self.thir.exprs.push(Expr {
+                        ty,
+                        temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible },
+                        span: expr.span,
+                        kind,
+                    }),
                 }
             }
 
@@ -1106,13 +1127,13 @@ impl<'tcx> Cx<'tcx> {
 
         // construct the complete expression `foo()` for the overloaded call,
         // which will yield the &T type
-        let temp_lifetime =
+        let (temp_lifetime, backwards_incompatible) =
             self.rvalue_scopes.temporary_scope(self.region_scope_tree, expr.hir_id.local_id);
         let fun = self.method_callee(expr, span, overloaded_callee);
         let fun = self.thir.exprs.push(fun);
         let fun_ty = self.thir[fun].ty;
         let ref_expr = self.thir.exprs.push(Expr {
-            temp_lifetime,
+            temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible },
             ty: ref_ty,
             span,
             kind: ExprKind::Call { ty: fun_ty, fun, args, from_hir_call: false, fn_span: span },
@@ -1127,7 +1148,7 @@ impl<'tcx> Cx<'tcx> {
         closure_expr: &'tcx hir::Expr<'tcx>,
         place: HirPlace<'tcx>,
     ) -> Expr<'tcx> {
-        let temp_lifetime = self
+        let (temp_lifetime, backwards_incompatible) = self
             .rvalue_scopes
             .temporary_scope(self.region_scope_tree, closure_expr.hir_id.local_id);
         let var_ty = place.base_ty;
@@ -1143,7 +1164,7 @@ impl<'tcx> Cx<'tcx> {
         };
 
         let mut captured_place_expr = Expr {
-            temp_lifetime,
+            temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible },
             ty: var_ty,
             span: closure_expr.span,
             kind: self.convert_var(var_hir_id),
@@ -1168,8 +1189,12 @@ impl<'tcx> Cx<'tcx> {
                 }
             };
 
-            captured_place_expr =
-                Expr { temp_lifetime, ty: proj.ty, span: closure_expr.span, kind };
+            captured_place_expr = Expr {
+                temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible },
+                ty: proj.ty,
+                span: closure_expr.span,
+                kind,
+            };
         }
 
         captured_place_expr
@@ -1184,7 +1209,7 @@ impl<'tcx> Cx<'tcx> {
         let upvar_capture = captured_place.info.capture_kind;
         let captured_place_expr =
             self.convert_captured_hir_place(closure_expr, captured_place.place.clone());
-        let temp_lifetime = self
+        let (temp_lifetime, backwards_incompatible) = self
             .rvalue_scopes
             .temporary_scope(self.region_scope_tree, closure_expr.hir_id.local_id);
 
@@ -1201,7 +1226,7 @@ impl<'tcx> Cx<'tcx> {
                     }
                 };
                 Expr {
-                    temp_lifetime,
+                    temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible },
                     ty: upvar_ty,
                     span: closure_expr.span,
                     kind: ExprKind::Borrow {
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<Span> {
         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<Span>,
+    move_data: &'a MoveData<'tcx>,
+    maybe_init: &'a mut ResultsCursor<'mir, 'tcx, MaybeInitializedPlaces<'mir, 'tcx>>,
+    block_drop_value_info: &'a mut IndexSlice<BasicBlock, MovePathIndexAtBlock>,
+    collected_drops: &'a mut ChunkedBitSet<MovePathIndex>,
+    visited: FxHashMap<BasicBlock, Rc<RefCell<ChunkedBitSet<MovePathIndex>>>>,
+}
+
+impl<'a, 'mir, 'tcx> DropsReachable<'a, 'mir, 'tcx> {
+    fn visit(&mut self, block: BasicBlock) {
+        let move_set_size = self.move_data.move_paths.len();
+        let make_new_path_set = || Rc::new(RefCell::new(ChunkedBitSet::new_empty(move_set_size)));
+
+        let data = &self.body.basic_blocks[block];
+        let Some(terminator) = &data.terminator else { return };
+        // Given that we observe these dropped locals here at `block` so far,
+        // we will try to update the successor blocks.
+        // An occupied entry at `block` in `self.visited` signals that we have visited `block` before.
+        let dropped_local_here =
+            Rc::clone(self.visited.entry(block).or_insert_with(make_new_path_set));
+        // We could have invoked reverse lookup for a `MovePathIndex` every time, but unfortunately it is expensive.
+        // Let's cache them in `self.block_drop_value_info`.
+        match self.block_drop_value_info[block] {
+            MovePathIndexAtBlock::Some(dropped) => {
+                dropped_local_here.borrow_mut().insert(dropped);
+            }
+            MovePathIndexAtBlock::Unknown => {
+                if let TerminatorKind::Drop { place, .. } = &terminator.kind
+                    && let LookupResult::Exact(idx) | LookupResult::Parent(Some(idx)) =
+                        self.move_data.rev_lookup.find(place.as_ref())
+                {
+                    // Since we are working with MIRs at a very early stage,
+                    // observing a `drop` terminator is not indicative enough that
+                    // the drop will definitely happen.
+                    // That is decided in the drop elaboration pass instead.
+                    // Therefore, we need to consult with the maybe-initialization information.
+                    self.maybe_init.seek_before_primary_effect(Location {
+                        block,
+                        statement_index: data.statements.len(),
+                    });
+
+                    // Check if the drop of `place` under inspection is really in effect.
+                    // This is true only when `place` may have been initialized along a control flow path from a BID to the drop program point today.
+                    // In other words, this is where the drop of `place` will happen in the future instead.
+                    if let MaybeReachable::Reachable(maybe_init) = self.maybe_init.get()
+                        && maybe_init.contains(idx)
+                    {
+                        // We also cache the drop information, so that we do not need to check on data-flow cursor again
+                        self.block_drop_value_info[block] = MovePathIndexAtBlock::Some(idx);
+                        dropped_local_here.borrow_mut().insert(idx);
+                    } else {
+                        self.block_drop_value_info[block] = MovePathIndexAtBlock::None;
+                    }
+                }
+            }
+            MovePathIndexAtBlock::None => {}
+        }
+
+        for succ in terminator.successors() {
+            let target = &self.body.basic_blocks[succ];
+            if target.is_cleanup {
+                continue;
+            }
+
+            // As long as we are passing through a new block, or new dropped places to propagate,
+            // we will proceed with `succ`
+            let dropped_local_there = match self.visited.entry(succ) {
+                hash_map::Entry::Occupied(occupied_entry) => {
+                    if succ == block
+                        || !occupied_entry.get().borrow_mut().union(&*dropped_local_here.borrow())
+                    {
+                        // `succ` has been visited but no new drops observed so far,
+                        // so we can bail on `succ` until new drop information arrives
+                        continue;
+                    }
+                    Rc::clone(occupied_entry.get())
+                }
+                hash_map::Entry::Vacant(vacant_entry) => Rc::clone(
+                    vacant_entry.insert(Rc::new(RefCell::new(dropped_local_here.borrow().clone()))),
+                ),
+            };
+            if let Some(terminator) = &target.terminator
+                && let TerminatorKind::Drop {
+                    place: dropped_place,
+                    target: _,
+                    unwind: _,
+                    replace: _,
+                } = &terminator.kind
+                && place_has_common_prefix(dropped_place, self.place)
+            {
+                // We have now reached the current drop of the `place`.
+                // Let's check the observed dropped places in.
+                self.collected_drops.union(&*dropped_local_there.borrow());
+                if self.drop_span.is_none() {
+                    // FIXME(@dingxiangfei2009): it turns out that `self.body.source_scopes` are still a bit wonky.
+                    // There is a high chance that this span still points to a block rather than a statement semicolon.
+                    *self.drop_span = Some(terminator.source_info.span);
+                }
+                // Now we have discovered a simple control flow path from a future drop point
+                // to the current drop point.
+                // We will not continue from there.
+            } else {
+                self.visit(succ)
+            }
+        }
+    }
+}
+
+/// An additional filter to exclude well-known types from the ecosystem
+/// because their drops are trivial.
+/// This returns additional types to check if the drops are delegated to those.
+/// A typical example is `hashbrown::HashMap<K, V>`, whose drop is delegated to `K` and `V`.
+fn true_significant_drop_ty<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    ty: Ty<'tcx>,
+) -> Option<SmallVec<[Ty<'tcx>; 2]>> {
+    if let ty::Adt(def, args) = ty.kind() {
+        let mut did = def.did();
+        let mut name_rev = vec![];
+        loop {
+            let key = tcx.def_key(did);
+
+            match key.disambiguated_data.data {
+                rustc_hir::definitions::DefPathData::CrateRoot => {
+                    name_rev.push(tcx.crate_name(did.krate))
+                }
+                rustc_hir::definitions::DefPathData::TypeNs(symbol) => name_rev.push(symbol),
+                _ => return None,
+            }
+            if let Some(parent) = key.parent {
+                did = DefId { krate: did.krate, index: parent };
+            } else {
+                break;
+            }
+        }
+        let name_str: Vec<_> = name_rev.iter().rev().map(|x| x.as_str()).collect();
+        debug!(?name_str);
+        match name_str[..] {
+            // These are the types from Rust core ecosystem
+            ["sym" | "proc_macro2", ..]
+            | ["core" | "std", "task", "LocalWaker" | "Waker"]
+            | ["core" | "std", "task", "wake", "LocalWaker" | "Waker"] => Some(smallvec![]),
+            // These are important types from Rust ecosystem
+            ["tracing", "instrument", "Instrumented"] | ["bytes", "Bytes"] => Some(smallvec![]),
+            ["hashbrown", "raw", "RawTable" | "RawIntoIter"] => {
+                if let [ty, ..] = &***args
+                    && let Some(ty) = ty.as_type()
+                {
+                    Some(smallvec![ty])
+                } else {
+                    None
+                }
+            }
+            ["hashbrown", "raw", "RawDrain"] => {
+                if let [_, ty, ..] = &***args
+                    && let Some(ty) = ty.as_type()
+                {
+                    Some(smallvec![ty])
+                } else {
+                    None
+                }
+            }
+            _ => None,
+        }
+    } else {
+        None
+    }
+}
+
+/// Returns the list of types with a "potentially sigificant" that may be dropped
+/// by dropping a value of type `ty`.
+#[instrument(level = "debug", skip(tcx, param_env))]
+fn extract_component_raw<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    param_env: ty::ParamEnv<'tcx>,
+    ty: Ty<'tcx>,
+    ty_seen: &mut UnordSet<Ty<'tcx>>,
+) -> SmallVec<[Ty<'tcx>; 4]> {
+    // Droppiness does not depend on regions, so let us erase them.
+    let ty = tcx
+        .try_normalize_erasing_regions(
+            ty::TypingEnv { param_env, typing_mode: ty::TypingMode::PostAnalysis },
+            ty,
+        )
+        .unwrap_or(ty);
+
+    let tys = tcx.list_significant_drop_tys(param_env.and(ty));
+    debug!(?ty, "components");
+    let mut out_tys = smallvec![];
+    for ty in tys {
+        if let Some(tys) = true_significant_drop_ty(tcx, ty) {
+            // Some types can be further opened up because the drop is simply delegated
+            for ty in tys {
+                if ty_seen.insert(ty) {
+                    out_tys.extend(extract_component_raw(tcx, param_env, ty, ty_seen));
+                }
+            }
+        } else {
+            if ty_seen.insert(ty) {
+                out_tys.push(ty);
+            }
+        }
+    }
+    out_tys
+}
+
+#[instrument(level = "debug", skip(tcx, param_env))]
+fn extract_component_with_significant_dtor<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    param_env: ty::ParamEnv<'tcx>,
+    ty: Ty<'tcx>,
+) -> SmallVec<[Ty<'tcx>; 4]> {
+    let mut tys = extract_component_raw(tcx, param_env, ty, &mut Default::default());
+    let mut deduplicate = FxHashSet::default();
+    tys.retain(|oty| deduplicate.insert(*oty));
+    tys.into_iter().collect()
+}
+
+/// Extract the span of the custom destructor of a type
+/// especially the span of the `impl Drop` header or its entire block
+/// when we are working with current local crate.
+#[instrument(level = "debug", skip(tcx))]
+fn ty_dtor_span<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option<Span> {
+    match ty.kind() {
+        ty::Bool
+        | ty::Char
+        | ty::Int(_)
+        | ty::Uint(_)
+        | ty::Float(_)
+        | ty::Error(_)
+        | ty::Str
+        | ty::Never
+        | ty::RawPtr(_, _)
+        | ty::Ref(_, _, _)
+        | ty::FnPtr(_, _)
+        | ty::Tuple(_)
+        | ty::Dynamic(_, _, _)
+        | ty::Alias(_, _)
+        | ty::Bound(_, _)
+        | ty::Pat(_, _)
+        | ty::Placeholder(_)
+        | ty::Infer(_)
+        | ty::Slice(_)
+        | ty::Array(_, _) => None,
+        ty::Adt(adt_def, _) => {
+            let did = adt_def.did();
+            let try_local_did_span = |did: DefId| {
+                if let Some(local) = did.as_local() {
+                    tcx.source_span(local)
+                } else {
+                    tcx.def_span(did)
+                }
+            };
+            let dtor = if let Some(dtor) = tcx.adt_destructor(did) {
+                dtor.did
+            } else if let Some(dtor) = tcx.adt_async_destructor(did) {
+                dtor.future
+            } else {
+                return Some(try_local_did_span(did));
+            };
+            let def_key = tcx.def_key(dtor);
+            let Some(parent_index) = def_key.parent else { return Some(try_local_did_span(dtor)) };
+            let parent_did = DefId { index: parent_index, krate: dtor.krate };
+            Some(try_local_did_span(parent_did))
+        }
+        ty::Coroutine(did, _)
+        | ty::CoroutineWitness(did, _)
+        | ty::CoroutineClosure(did, _)
+        | ty::Closure(did, _)
+        | ty::FnDef(did, _)
+        | ty::Foreign(did) => Some(tcx.def_span(did)),
+        ty::Param(_) => None,
+    }
+}
+
+/// Check if a moved place at `idx` is a part of a BID.
+/// The use of this check is that we will consider drops on these
+/// as a drop of the overall BID and, thus, we can exclude it from the diagnosis.
+fn place_descendent_of_bids<'tcx>(
+    mut idx: MovePathIndex,
+    move_data: &MoveData<'tcx>,
+    bids: &UnordSet<&Place<'tcx>>,
+) -> bool {
+    loop {
+        let path = &move_data.move_paths[idx];
+        if bids.contains(&path.place) {
+            return true;
+        }
+        if let Some(parent) = path.parent {
+            idx = parent;
+        } else {
+            return false;
+        }
+    }
+}
+
+/// The core of the lint `tail-expr-drop-order`
+pub(crate) fn run_lint<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId, body: &Body<'tcx>) {
+    if matches!(tcx.def_kind(def_id), rustc_hir::def::DefKind::SyntheticCoroutineBody) {
+        // A synthetic coroutine has no HIR body and it is enough to just analyse the original body
+        return;
+    }
+    if body.span.edition().at_least_rust_2024()
+        || tcx.lints_that_dont_need_to_run(()).contains(&lint::LintId::of(TAIL_EXPR_DROP_ORDER))
+    {
+        return;
+    }
+    // ## About BIDs in blocks ##
+    // Track the set of blocks that contain a backwards-incompatible drop (BID)
+    // and, for each block, the vector of locations.
+    //
+    // We group them per-block because they tend to scheduled in the same drop ladder block.
+    let mut bid_per_block = IndexMap::default();
+    let mut bid_places = UnordSet::new();
+    let param_env = tcx.param_env(def_id).with_reveal_all_normalized(tcx);
+    let mut ty_dropped_components = UnordMap::default();
+    for (block, data) in body.basic_blocks.iter_enumerated() {
+        for (statement_index, stmt) in data.statements.iter().enumerate() {
+            if let StatementKind::BackwardIncompatibleDropHint { place, reason: _ } = &stmt.kind {
+                let ty = place.ty(body, tcx).ty;
+                if ty_dropped_components
+                    .entry(ty)
+                    .or_insert_with(|| extract_component_with_significant_dtor(tcx, param_env, ty))
+                    .is_empty()
+                {
+                    continue;
+                }
+                bid_per_block
+                    .entry(block)
+                    .or_insert(vec![])
+                    .push((Location { block, statement_index }, &**place));
+                bid_places.insert(&**place);
+            }
+        }
+    }
+    if bid_per_block.is_empty() {
+        return;
+    }
+
+    dump_mir(tcx, false, "lint_tail_expr_drop_order", &0 as _, body, |_, _| Ok(()));
+    let locals_with_user_names = collect_user_names(body);
+    let is_closure_like = tcx.is_closure_like(def_id.to_def_id());
+
+    // Compute the "maybe initialized" information for this body.
+    // When we encounter a DROP of some place P we only care
+    // about the drop if `P` may be initialized.
+    let move_data = MoveData::gather_moves(body, tcx, |_| true);
+    let maybe_init = MaybeInitializedPlaces::new(tcx, body, &move_data);
+    let mut maybe_init = maybe_init.iterate_to_fixpoint(tcx, body, None).into_results_cursor(body);
+    let mut block_drop_value_info =
+        IndexVec::from_elem_n(MovePathIndexAtBlock::Unknown, body.basic_blocks.len());
+    for (&block, candidates) in &bid_per_block {
+        // We will collect drops on locals on paths between BID points to their actual drop locations
+        // into `all_locals_dropped`.
+        let mut all_locals_dropped = ChunkedBitSet::new_empty(move_data.move_paths.len());
+        let mut drop_span = None;
+        for &(_, place) in candidates.iter() {
+            let mut collected_drops = ChunkedBitSet::new_empty(move_data.move_paths.len());
+            // ## On detecting change in relative drop order ##
+            // Iterate through each BID-containing block `block`.
+            // If the place `P` targeted by the BID is "maybe initialized",
+            // then search forward to find the actual `DROP(P)` point.
+            // Everything dropped between the BID and the actual drop point
+            // is something whose relative drop order will change.
+            DropsReachable {
+                body,
+                place,
+                drop_span: &mut drop_span,
+                move_data: &move_data,
+                maybe_init: &mut maybe_init,
+                block_drop_value_info: &mut block_drop_value_info,
+                collected_drops: &mut collected_drops,
+                visited: Default::default(),
+            }
+            .visit(block);
+            // Compute the set `all_locals_dropped` of local variables that are dropped
+            // after the BID point but before the current drop point.
+            //
+            // These are the variables whose drop impls will be reordered with respect
+            // to `place`.
+            all_locals_dropped.union(&collected_drops);
+        }
+
+        // We shall now exclude some local bindings for the following cases.
+        {
+            let mut to_exclude = ChunkedBitSet::new_empty(all_locals_dropped.domain_size());
+            // We will now do subtraction from the candidate dropped locals, because of the following reasons.
+            for path_idx in all_locals_dropped.iter() {
+                let move_path = &move_data.move_paths[path_idx];
+                let dropped_local = move_path.place.local;
+                // a) A return value _0 will eventually be used
+                // Example:
+                // fn f() -> Droppy {
+                //     let _x = Droppy;
+                //     Droppy
+                // }
+                // _0 holds the literal `Droppy` and rightfully `_x` has to be dropped first
+                if dropped_local == Local::ZERO {
+                    debug!(?dropped_local, "skip return value");
+                    to_exclude.insert(path_idx);
+                    continue;
+                }
+                // b) If we are analysing a closure, the captures are still dropped last.
+                // This is part of the closure capture lifetime contract.
+                // They are similar to the return value _0 with respect to lifetime rules.
+                if is_closure_like && matches!(dropped_local, ty::CAPTURE_STRUCT_LOCAL) {
+                    debug!(?dropped_local, "skip closure captures");
+                    to_exclude.insert(path_idx);
+                    continue;
+                }
+                // c) Sometimes we collect places that are projections into the BID locals,
+                // so they are considered dropped now.
+                // Example:
+                // struct NotVeryDroppy(Droppy);
+                // impl Drop for Droppy {..}
+                // fn f() -> NotVeryDroppy {
+                //    let x = NotVeryDroppy(droppy());
+                //    {
+                //        let y: Droppy = x.0;
+                //        NotVeryDroppy(y)
+                //    }
+                // }
+                // `y` takes `x.0`, which invalidates `x` as a complete `NotVeryDroppy`
+                // so there is no point in linting against `x` any more.
+                if place_descendent_of_bids(path_idx, &move_data, &bid_places) {
+                    debug!(?dropped_local, "skip descendent of bids");
+                    to_exclude.insert(path_idx);
+                    continue;
+                }
+                let observer_ty = move_path.place.ty(body, tcx).ty;
+                // d) The collected local has no custom destructor that passes our ecosystem filter.
+                if ty_dropped_components
+                    .entry(observer_ty)
+                    .or_insert_with(|| {
+                        extract_component_with_significant_dtor(tcx, param_env, observer_ty)
+                    })
+                    .is_empty()
+                {
+                    debug!(?dropped_local, "skip non-droppy types");
+                    to_exclude.insert(path_idx);
+                    continue;
+                }
+            }
+            // Suppose that all BIDs point into the same local,
+            // we can remove the this local from the observed drops,
+            // so that we can focus our diagnosis more on the others.
+            if candidates.iter().all(|&(_, place)| candidates[0].1.local == place.local) {
+                for path_idx in all_locals_dropped.iter() {
+                    if move_data.move_paths[path_idx].place.local == candidates[0].1.local {
+                        to_exclude.insert(path_idx);
+                    }
+                }
+            }
+            all_locals_dropped.subtract(&to_exclude);
+        }
+        if all_locals_dropped.is_empty() {
+            // No drop effect is observable, so let us move on.
+            continue;
+        }
+
+        // ## The final work to assemble the diagnosis ##
+        // First collect or generate fresh names for local variable bindings and temporary values.
+        let local_names = assign_observables_names(
+            all_locals_dropped
+                .iter()
+                .map(|path_idx| move_data.move_paths[path_idx].place.local)
+                .chain(candidates.iter().map(|(_, place)| place.local)),
+            &locals_with_user_names,
+        );
+
+        let mut lint_root = None;
+        let mut local_labels = vec![];
+        // We now collect the types with custom destructors.
+        for &(_, place) in candidates {
+            let linted_local_decl = &body.local_decls[place.local];
+            let Some(&(ref name, is_generated_name)) = local_names.get(&place.local) else {
+                bug!("a name should have been assigned")
+            };
+            let name = name.as_str();
+
+            if lint_root.is_none()
+                && let ClearCrossCrate::Set(data) =
+                    &body.source_scopes[linted_local_decl.source_info.scope].local_data
+            {
+                lint_root = Some(data.lint_root);
+            }
+
+            // Collect spans of the custom destructors.
+            let mut seen_dyn = false;
+            let destructors = ty_dropped_components
+                .get(&linted_local_decl.ty)
+                .unwrap()
+                .iter()
+                .filter_map(|&ty| {
+                    if let Some(span) = ty_dtor_span(tcx, ty) {
+                        Some(DestructorLabel { span, name, dtor_kind: "concrete" })
+                    } else if matches!(ty.kind(), ty::Dynamic(..)) {
+                        if seen_dyn {
+                            None
+                        } else {
+                            seen_dyn = true;
+                            Some(DestructorLabel { span: DUMMY_SP, name, dtor_kind: "dyn" })
+                        }
+                    } else {
+                        None
+                    }
+                })
+                .collect();
+            local_labels.push(LocalLabel {
+                span: linted_local_decl.source_info.span,
+                destructors,
+                name,
+                is_generated_name,
+                is_dropped_first_edition_2024: true,
+            });
+        }
+
+        // Similarly, custom destructors of the observed drops.
+        for path_idx in all_locals_dropped.iter() {
+            let place = &move_data.move_paths[path_idx].place;
+            // We are not using the type of the local because the drop may be partial.
+            let observer_ty = place.ty(body, tcx).ty;
+
+            let observer_local_decl = &body.local_decls[place.local];
+            let Some(&(ref name, is_generated_name)) = local_names.get(&place.local) else {
+                bug!("a name should have been assigned")
+            };
+            let name = name.as_str();
+
+            let mut seen_dyn = false;
+            let destructors = extract_component_with_significant_dtor(tcx, param_env, observer_ty)
+                .into_iter()
+                .filter_map(|ty| {
+                    if let Some(span) = ty_dtor_span(tcx, ty) {
+                        Some(DestructorLabel { span, name, dtor_kind: "concrete" })
+                    } else if matches!(ty.kind(), ty::Dynamic(..)) {
+                        if seen_dyn {
+                            None
+                        } else {
+                            seen_dyn = true;
+                            Some(DestructorLabel { span: DUMMY_SP, name, dtor_kind: "dyn" })
+                        }
+                    } else {
+                        None
+                    }
+                })
+                .collect();
+            local_labels.push(LocalLabel {
+                span: observer_local_decl.source_info.span,
+                destructors,
+                name,
+                is_generated_name,
+                is_dropped_first_edition_2024: false,
+            });
+        }
+
+        let span = local_labels[0].span;
+        tcx.emit_node_span_lint(
+            lint::builtin::TAIL_EXPR_DROP_ORDER,
+            lint_root.unwrap_or(CRATE_HIR_ID),
+            span,
+            TailExprDropOrderLint { local_labels, drop_span, _epilogue: () },
+        );
+    }
+}
+
+/// Extract binding names if available for diagnosis
+fn collect_user_names(body: &Body<'_>) -> IndexMap<Local, Symbol> {
+    let mut names = IndexMap::default();
+    for var_debug_info in &body.var_debug_info {
+        if let mir::VarDebugInfoContents::Place(place) = &var_debug_info.value
+            && let Some(local) = place.local_or_deref_local()
+        {
+            names.entry(local).or_insert(var_debug_info.name);
+        }
+    }
+    names
+}
+
+/// Assign names for anonymous or temporary values for diagnosis
+fn assign_observables_names(
+    locals: impl IntoIterator<Item = Local>,
+    user_names: &IndexMap<Local, Symbol>,
+) -> IndexMap<Local, (String, bool)> {
+    let mut names = IndexMap::default();
+    let mut assigned_names = FxHashSet::default();
+    let mut idx = 0u64;
+    let mut fresh_name = || {
+        idx += 1;
+        (format!("#{idx}"), true)
+    };
+    for local in locals {
+        let name = if let Some(name) = user_names.get(&local) {
+            let name = name.as_str();
+            if assigned_names.contains(name) { fresh_name() } else { (name.to_owned(), false) }
+        } else {
+            fresh_name()
+        };
+        assigned_names.insert(name.0.clone());
+        names.insert(local, name);
+    }
+    names
+}
+
+#[derive(LintDiagnostic)]
+#[diag(mir_transform_tail_expr_drop_order)]
+struct TailExprDropOrderLint<'a> {
+    #[subdiagnostic]
+    local_labels: Vec<LocalLabel<'a>>,
+    #[label(mir_transform_drop_location)]
+    drop_span: Option<Span>,
+    #[note(mir_transform_note_epilogue)]
+    _epilogue: (),
+}
+
+struct LocalLabel<'a> {
+    span: Span,
+    name: &'a str,
+    is_generated_name: bool,
+    is_dropped_first_edition_2024: bool,
+    destructors: Vec<DestructorLabel<'a>>,
+}
+
+/// A custom `Subdiagnostic` implementation so that the notes are delivered in a specific order
+impl Subdiagnostic for LocalLabel<'_> {
+    fn add_to_diag_with<
+        G: rustc_errors::EmissionGuarantee,
+        F: rustc_errors::SubdiagMessageOp<G>,
+    >(
+        self,
+        diag: &mut rustc_errors::Diag<'_, G>,
+        f: &F,
+    ) {
+        diag.arg("name", self.name);
+        diag.arg("is_generated_name", self.is_generated_name);
+        diag.arg("is_dropped_first_edition_2024", self.is_dropped_first_edition_2024);
+        let msg = f(diag, crate::fluent_generated::mir_transform_tail_expr_local.into());
+        diag.span_label(self.span, msg);
+        for dtor in self.destructors {
+            dtor.add_to_diag_with(diag, f);
+        }
+        let msg = f(diag, crate::fluent_generated::mir_transform_label_local_epilogue.into());
+        diag.span_label(self.span, msg);
+    }
+}
+
+#[derive(Subdiagnostic)]
+#[note(mir_transform_tail_expr_dtor)]
+struct DestructorLabel<'a> {
+    #[primary_span]
+    span: Span,
+    dtor_kind: &'static str,
+    name: &'a str,
+}
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<Ty<'tcx>>,
@@ -100,6 +101,9 @@ struct NeedsDropTypes<'tcx, F> {
     unchecked_tys: Vec<(Ty<'tcx>, usize)>,
     recursion_limit: Limit,
     adt_components: F,
+    /// Set this to true if an exhaustive list of types involved in
+    /// drop obligation is requested.
+    exhaustive: bool,
 }
 
 impl<'tcx, F> NeedsDropTypes<'tcx, F> {
@@ -107,6 +111,7 @@ impl<'tcx, F> NeedsDropTypes<'tcx, F> {
         tcx: TyCtxt<'tcx>,
         typing_env: ty::TypingEnv<'tcx>,
         ty: Ty<'tcx>,
+        exhaustive: bool,
         adt_components: F,
     ) -> Self {
         let mut seen_tys = FxHashSet::default();
@@ -114,14 +119,22 @@ impl<'tcx, F> NeedsDropTypes<'tcx, F> {
         Self {
             tcx,
             typing_env,
-            reveal_coroutine_witnesses: false,
+            reveal_coroutine_witnesses: exhaustive,
             seen_tys,
             query_ty: ty,
             unchecked_tys: vec![(ty, 0)],
             recursion_limit: tcx.recursion_limit(),
             adt_components,
+            exhaustive,
         }
     }
+
+    /// Called when `ty` is found to always require drop.
+    /// If the exhaustive flag is true, then `Ok(ty)` is returned like any other type.
+    /// Otherwise, `Err(AlwaysRequireDrop)` is returned, which will cause iteration to abort.
+    fn always_drop_component(&self, ty: Ty<'tcx>) -> NeedsDropResult<Ty<'tcx>> {
+        if self.exhaustive { Ok(ty) } else { Err(AlwaysRequiresDrop) }
+    }
 }
 
 impl<'tcx, F, I> Iterator for NeedsDropTypes<'tcx, F>
@@ -131,19 +144,22 @@ where
 {
     type Item = NeedsDropResult<Ty<'tcx>>;
 
+    #[instrument(level = "debug", skip(self), ret)]
     fn next(&mut self) -> Option<NeedsDropResult<Ty<'tcx>>> {
         let tcx = self.tcx;
 
         while let Some((ty, level)) = self.unchecked_tys.pop() {
+            debug!(?ty, "needs_drop_components: inspect");
             if !self.recursion_limit.value_within_limit(level) {
                 // Not having a `Span` isn't great. But there's hopefully some other
                 // recursion limit error as well.
+                debug!("needs_drop_components: recursion limit exceeded");
                 tcx.dcx().emit_err(NeedsDropOverflow { query_ty: self.query_ty });
-                return Some(Err(AlwaysRequiresDrop));
+                return Some(self.always_drop_component(ty));
             }
 
             let components = match needs_drop_components(tcx, ty) {
-                Err(e) => return Some(Err(e)),
+                Err(AlwaysRequiresDrop) => return Some(self.always_drop_component(ty)),
                 Ok(components) => components,
             };
             debug!("needs_drop_components({:?}) = {:?}", ty, components);
@@ -171,7 +187,7 @@ where
                         if self.reveal_coroutine_witnesses {
                             queue_type(self, args.as_coroutine().witness());
                         } else {
-                            return Some(Err(AlwaysRequiresDrop));
+                            return Some(self.always_drop_component(ty));
                         }
                     }
                     ty::CoroutineWitness(def_id, args) => {
@@ -186,7 +202,7 @@ where
                         }
                     }
 
-                    _ if component.is_copy_modulo_regions(tcx, self.typing_env) => (),
+                    _ if component.is_copy_modulo_regions(tcx, self.typing_env) => {}
 
                     ty::Closure(_, args) => {
                         for upvar in args.as_closure().upvar_tys() {
@@ -205,7 +221,9 @@ where
                     // impl then check whether the field types need `Drop`.
                     ty::Adt(adt_def, args) => {
                         let tys = match (self.adt_components)(adt_def, args) {
-                            Err(e) => return Some(Err(e)),
+                            Err(AlwaysRequiresDrop) => {
+                                return Some(self.always_drop_component(ty));
+                            }
                             Ok(tys) => tys,
                         };
                         for required_ty in tys {
@@ -230,7 +248,8 @@ where
                     }
 
                     ty::Foreign(_) | ty::Dynamic(..) => {
-                        return Some(Err(AlwaysRequiresDrop));
+                        debug!("needs_drop_components: foreign or dynamic");
+                        return Some(self.always_drop_component(ty));
                     }
 
                     ty::Bool
@@ -280,6 +299,7 @@ fn drop_tys_helper<'tcx>(
     typing_env: ty::TypingEnv<'tcx>,
     adt_has_dtor: impl Fn(ty::AdtDef<'tcx>) -> Option<DtorType>,
     only_significant: bool,
+    exhaustive: bool,
 ) -> impl Iterator<Item = NeedsDropResult<Ty<'tcx>>> {
     fn with_query_cache<'tcx>(
         tcx: TyCtxt<'tcx>,
@@ -343,7 +363,7 @@ fn drop_tys_helper<'tcx>(
         .map(|v| v.into_iter())
     };
 
-    NeedsDropTypes::new(tcx, typing_env, ty, adt_components)
+    NeedsDropTypes::new(tcx, typing_env, ty, exhaustive, adt_components)
 }
 
 fn adt_consider_insignificant_dtor<'tcx>(
@@ -384,6 +404,7 @@ fn adt_drop_tys<'tcx>(
         ty::TypingEnv::non_body_analysis(tcx, def_id),
         adt_has_dtor,
         false,
+        false,
     )
     .collect::<Result<Vec<_>, _>>()
     .map(|components| tcx.mk_type_list(&components))
@@ -401,11 +422,31 @@ fn adt_significant_drop_tys(
         ty::TypingEnv::non_body_analysis(tcx, def_id),
         adt_consider_insignificant_dtor(tcx),
         true,
+        false,
     )
     .collect::<Result<Vec<_>, _>>()
     .map(|components| tcx.mk_type_list(&components))
 }
 
+#[instrument(level = "debug", skip(tcx), ret)]
+fn list_significant_drop_tys<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    ty: ty::ParamEnvAnd<'tcx, Ty<'tcx>>,
+) -> &'tcx ty::List<Ty<'tcx>> {
+    tcx.mk_type_list(
+        &drop_tys_helper(
+            tcx,
+            ty.value,
+            ty::TypingEnv { typing_mode: ty::TypingMode::PostAnalysis, param_env: ty.param_env },
+            adt_consider_insignificant_dtor(tcx),
+            true,
+            true,
+        )
+        .filter_map(|res| res.ok())
+        .collect::<Vec<_>>(),
+    )
+}
+
 pub(crate) fn provide(providers: &mut Providers) {
     *providers = Providers {
         needs_drop_raw,
@@ -413,6 +454,7 @@ pub(crate) fn provide(providers: &mut Providers) {
         has_significant_drop_raw,
         adt_drop_tys,
         adt_significant_drop_tys,
+        list_significant_drop_tys,
         ..*providers
     };
 }
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 <https://github.com/rust-lang/rfcs/pull/3606>
+//@ edition: 2021
+//@ build-fail
 
-#![deny(tail_expr_drop_order)]
+#![deny(tail_expr_drop_order)] //~ NOTE: the lint level is defined here
+#![allow(dropping_copy_types)]
 
 struct LoudDropper;
 impl Drop for LoudDropper {
+    //~^ NOTE: `#1` invokes this custom destructor
+    //~| NOTE: `x` invokes this custom destructor
+    //~| NOTE: `#1` invokes this custom destructor
+    //~| NOTE: `x` invokes this custom destructor
+    //~| NOTE: `#1` invokes this custom destructor
+    //~| NOTE: `x` invokes this custom destructor
+    //~| NOTE: `#1` invokes this custom destructor
+    //~| NOTE: `x` invokes this custom destructor
+    //~| NOTE: `#1` invokes this custom destructor
+    //~| NOTE: `future` invokes this custom destructor
+    //~| NOTE: `_x` invokes this custom destructor
+    //~| NOTE: `#1` invokes this custom destructor
     fn drop(&mut self) {
         // This destructor should be considered significant because it is a custom destructor
         // and we will assume that the destructor can generate side effects arbitrarily so that
@@ -23,30 +36,70 @@ impl LoudDropper {
 
 fn should_lint() -> i32 {
     let x = LoudDropper;
+    //~^ NOTE: `x` calls a custom destructor
+    //~| NOTE: `x` will be dropped later as of Edition 2024
     // Should lint
     x.get() + LoudDropper.get()
-    //~^ ERROR: these values and local bindings have significant drop implementation that will have a different drop order from that of Edition 2021
+    //~^ ERROR: relative drop order changing in Rust 2024
+    //~| NOTE: this value will be stored in a temporary; let us call it `#1`
+    //~| NOTE: up until Edition 2021 `#1` is dropped last but will be dropped earlier in Edition 2024
     //~| WARN: this changes meaning in Rust 2024
+    //~| NOTE: most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects
+    //~| NOTE: for more information, see issue #123739
 }
+//~^ NOTE: now the temporary value is dropped here, before the local variables in the block or statement
 
-fn should_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<T: Default>() -> &'static str {
+    fn extract<T>(_: &T) -> &'static str {
+        todo!()
+    }
+    let x = T::default();
+    //~^ NOTE: `x` calls a custom destructor
+    //~| NOTE: `x` will be dropped later as of Edition 2024
+    extract(&T::default())
+    //~^ ERROR: relative drop order changing in Rust 2024
+    //~| WARN: this changes meaning in Rust 2024
+    //~| NOTE: this value will be stored in a temporary; let us call it `#1`
+    //~| NOTE: up until Edition 2021 `#1` is dropped last but will be dropped earlier in Edition 2024
+    //~| NOTE: most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects
+    //~| NOTE: for more information, see issue #123739
+}
+//~^ NOTE: now the temporary value is dropped here, before the local variables in the block or statement
+
+fn should_lint_adt() -> i32 {
+    let x: Result<LoudDropper, ()> = Ok(LoudDropper);
+    //~^ NOTE: `x` calls a custom destructor
+    //~| NOTE: `x` will be dropped later as of Edition 2024
+    LoudDropper.get()
+    //~^ ERROR: relative drop order changing in Rust 2024
+    //~| WARN: this changes meaning in Rust 2024
+    //~| NOTE: this value will be stored in a temporary; let us call it `#1`
+    //~| NOTE: up until Edition 2021 `#1` is dropped last but will be dropped earlier in Edition 2024
+    //~| NOTE: most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects
+    //~| NOTE: for more information, see issue #123739
+}
+//~^ NOTE: now the temporary value is dropped here, before the local variables in the block or statement
+
+fn should_not_lint_insign_dtor() -> i32 {
+    let x = String::new();
+    LoudDropper.get()
+}
+
+fn should_lint_with_dtor_span() -> i32 {
+    struct LoudDropper3;
+    impl Drop for LoudDropper3 {
+        //~^ NOTE: `#1` invokes this custom destructor
+        fn drop(&mut self) {
+            println!("loud drop");
+        }
     }
+    impl LoudDropper3 {
+        fn get(&self) -> i32 {
+            0
+        }
+    }
+    struct LoudDropper2;
+    impl Drop for LoudDropper2 {
+        //~^ NOTE: `x` invokes this custom destructor
+        fn drop(&mut self) {
+            println!("loud drop");
+        }
+    }
+    impl LoudDropper2 {
+        fn get(&self) -> i32 {
+            0
+        }
+    }
+
+    let x = LoudDropper2;
+    //~^ NOTE: `x` calls a custom destructor
+    //~| NOTE: `x` will be dropped later as of Edition 2024
+    LoudDropper3.get()
+    //~^ ERROR: relative drop order changing in Rust 2024
+    //~| NOTE: this value will be stored in a temporary; let us call it `#1`
+    //~| NOTE: up until Edition 2021 `#1` is dropped last but will be dropped earlier in Edition 2024
+    //~| WARN: this changes meaning in Rust 2024
+    //~| NOTE: most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects
+    //~| NOTE: for more information, see issue #123739
+}
+//~^ NOTE: now the temporary value is dropped here, before the local variables in the block or statement
+
+fn should_lint_with_transient_drops() {
+    drop((
+        {
+            LoudDropper.get()
+            //~^ ERROR: relative drop order changing in Rust 2024
+            //~| NOTE: this value will be stored in a temporary; let us call it `#1`
+            //~| NOTE: up until Edition 2021 `#1` is dropped last but will be dropped earlier in Edition 2024
+            //~| WARN: this changes meaning in Rust 2024
+            //~| NOTE: most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects
+            //~| NOTE: for more information, see issue #123739
+        },
+        {
+            let _x = LoudDropper;
+            //~^ NOTE: `_x` calls a custom destructor
+            //~| NOTE: `_x` will be dropped later as of Edition 2024
+        },
+    ));
+    //~^ NOTE: now the temporary value is dropped here, before the local variables in the block or statement
 }
 
 fn main() {}
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 <https://github.com/rust-lang/rust/issues/123739>
+note: `#1` invokes this custom destructor
+  --> $DIR/lint-tail-expr-drop-order.rs:11:1
+   |
+LL | / impl Drop for LoudDropper {
+LL | |
+LL | |
+LL | |
+...  |
+LL | |     }
+LL | | }
+   | |_^
+note: `x` invokes this custom destructor
+  --> $DIR/lint-tail-expr-drop-order.rs:11:1
+   |
+LL | / impl Drop for LoudDropper {
+LL | |
+LL | |
+LL | |
+...  |
+LL | |     }
+LL | | }
+   | |_^
+   = note: most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects like releasing locks or sending messages
 note: the lint level is defined here
   --> $DIR/lint-tail-expr-drop-order.rs:7:9
    |
 LL | #![deny(tail_expr_drop_order)]
    |         ^^^^^^^^^^^^^^^^^^^^
 
-error: these values and local bindings have significant drop implementation that will have a different drop order from that of Edition 2021
-  --> $DIR/lint-tail-expr-drop-order.rs:34:23
+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 <https://github.com/rust-lang/rust/issues/123739>
+note: `#1` invokes this custom destructor
+  --> $DIR/lint-tail-expr-drop-order.rs:11:1
+   |
+LL | / impl Drop for LoudDropper {
+LL | |
+LL | |
+LL | |
+...  |
+LL | |     }
+LL | | }
+   | |_^
+note: `x` invokes this custom destructor
+  --> $DIR/lint-tail-expr-drop-order.rs:11:1
+   |
+LL | / impl Drop for LoudDropper {
+LL | |
+LL | |
+LL | |
+...  |
+LL | |     }
+LL | | }
+   | |_^
+   = note: most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects like releasing locks or sending messages
+
+error: relative drop order changing in Rust 2024
+  --> $DIR/lint-tail-expr-drop-order.rs: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 <https://github.com/rust-lang/rust/issues/123739>
+note: `#1` invokes this custom destructor
+  --> $DIR/lint-tail-expr-drop-order.rs:11:1
+   |
+LL | / impl Drop for LoudDropper {
+LL | |
+LL | |
+LL | |
+...  |
+LL | |     }
+LL | | }
+   | |_^
+note: `x` invokes this custom destructor
+  --> $DIR/lint-tail-expr-drop-order.rs:11:1
+   |
+LL | / impl Drop for LoudDropper {
+LL | |
+LL | |
+LL | |
+...  |
+LL | |     }
+LL | | }
+   | |_^
+   = note: most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects like releasing locks or sending messages
 
-error: 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 <https://github.com/rust-lang/rust/issues/123739>
+note: `#1` invokes this custom destructor
+  --> $DIR/lint-tail-expr-drop-order.rs:11:1
+   |
+LL | / impl Drop for LoudDropper {
+LL | |
+LL | |
+LL | |
+...  |
+LL | |     }
+LL | | }
+   | |_^
+note: `future` invokes this custom destructor
+  --> $DIR/lint-tail-expr-drop-order.rs:11:1
+   |
+LL | / impl Drop for LoudDropper {
+LL | |
+LL | |
+LL | |
+...  |
+LL | |     }
+LL | | }
+   | |_^
+   = note: most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects like releasing locks or sending messages
+
+error: relative drop order changing in Rust 2024
+  --> $DIR/lint-tail-expr-drop-order.rs:164:14
+   |
+LL |     let x = T::default();
+   |         -
+   |         |
+   |         `x` calls a custom destructor
+   |         `x` will be dropped later as of Edition 2024
+...
+LL |     extract(&T::default())
+   |              ^^^^^^^^^^^^
+   |              |
+   |              this value will be stored in a temporary; let us call it `#1`
+   |              up until Edition 2021 `#1` is dropped last but will be dropped earlier in Edition 2024
+...
+LL | }
+   | - now the temporary value is dropped here, before the local variables in the block or statement
+   |
+   = warning: this changes meaning in Rust 2024
+   = note: for more information, see issue #123739 <https://github.com/rust-lang/rust/issues/123739>
+   = note: most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects like releasing locks or sending messages
+
+error: relative drop order changing in Rust 2024
+  --> $DIR/lint-tail-expr-drop-order.rs:178:5
+   |
+LL |     let x: Result<LoudDropper, ()> = Ok(LoudDropper);
+   |         -
+   |         |
+   |         `x` calls a custom destructor
+   |         `x` will be dropped later as of Edition 2024
+...
+LL |     LoudDropper.get()
+   |     ^^^^^^^^^^^
+   |     |
+   |     this value will be stored in a temporary; let us call it `#1`
+   |     up until Edition 2021 `#1` is dropped last but will be dropped earlier in Edition 2024
+...
+LL | }
+   | - now the temporary value is dropped here, before the local variables in the block or statement
+   |
+   = warning: this changes meaning in Rust 2024
+   = note: for more information, see issue #123739 <https://github.com/rust-lang/rust/issues/123739>
+note: `#1` invokes this custom destructor
+  --> $DIR/lint-tail-expr-drop-order.rs:11:1
+   |
+LL | / impl Drop for LoudDropper {
+LL | |
+LL | |
+LL | |
+...  |
+LL | |     }
+LL | | }
+   | |_^
+note: `x` invokes this custom destructor
+  --> $DIR/lint-tail-expr-drop-order.rs:11:1
+   |
+LL | / impl Drop for LoudDropper {
+LL | |
+LL | |
+LL | |
+...  |
+LL | |     }
+LL | | }
+   | |_^
+   = note: most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects like releasing locks or sending messages
+
+error: relative drop order changing in Rust 2024
+  --> $DIR/lint-tail-expr-drop-order.rs:222:5
+   |
+LL |     let x = LoudDropper2;
+   |         -
+   |         |
+   |         `x` calls a custom destructor
+   |         `x` will be dropped later as of Edition 2024
+...
+LL |     LoudDropper3.get()
+   |     ^^^^^^^^^^^^
+   |     |
+   |     this value will be stored in a temporary; let us call it `#1`
+   |     up until Edition 2021 `#1` is dropped last but will be dropped earlier in Edition 2024
+...
+LL | }
+   | - now the temporary value is dropped here, before the local variables in the block or statement
+   |
+   = warning: this changes meaning in Rust 2024
+   = note: for more information, see issue #123739 <https://github.com/rust-lang/rust/issues/123739>
+note: `#1` invokes this custom destructor
+  --> $DIR/lint-tail-expr-drop-order.rs:195:5
+   |
+LL | /     impl Drop for LoudDropper3 {
+LL | |
+LL | |         fn drop(&mut self) {
+LL | |             println!("loud drop");
+LL | |         }
+LL | |     }
+   | |_____^
+note: `x` invokes this custom destructor
+  --> $DIR/lint-tail-expr-drop-order.rs:207:5
+   |
+LL | /     impl Drop for LoudDropper2 {
+LL | |
+LL | |         fn drop(&mut self) {
+LL | |             println!("loud drop");
+LL | |         }
+LL | |     }
+   | |_____^
+   = note: most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects like releasing locks or sending messages
+
+error: relative drop order changing in Rust 2024
+  --> $DIR/lint-tail-expr-drop-order.rs:235:13
+   |
+LL |             LoudDropper.get()
+   |             ^^^^^^^^^^^
+   |             |
+   |             this value will be stored in a temporary; let us call it `#1`
+   |             up until Edition 2021 `#1` is dropped last but will be dropped earlier in Edition 2024
+...
+LL |             let _x = LoudDropper;
+   |                 --
+   |                 |
+   |                 `_x` calls a custom destructor
+   |                 `_x` will be dropped later as of Edition 2024
+...
+LL |     ));
+   |       - now the temporary value is dropped here, before the local variables in the block or statement
+   |
+   = warning: this changes meaning in Rust 2024
+   = note: for more information, see issue #123739 <https://github.com/rust-lang/rust/issues/123739>
+note: `#1` invokes this custom destructor
+  --> $DIR/lint-tail-expr-drop-order.rs:11:1
+   |
+LL | / impl Drop for LoudDropper {
+LL | |
+LL | |
+LL | |
+...  |
+LL | |     }
+LL | | }
+   | |_^
+note: `_x` invokes this custom destructor
+  --> $DIR/lint-tail-expr-drop-order.rs:11:1
+   |
+LL | / impl Drop for LoudDropper {
+LL | |
+LL | |
+LL | |
+...  |
+LL | |     }
+LL | | }
+   | |_^
+   = note: most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects like releasing locks or sending messages
 
-error: aborting due to 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 {