From e672bb9e306f5571654a3db2f46f16cdb7e34735 Mon Sep 17 00:00:00 2001
From: Ding Xiang Fei <dingxiangfei2009@protonmail.ch>
Date: Thu, 5 Dec 2024 02:58:59 +0800
Subject: [PATCH 1/6] run borrowck tests on BIDs and emit tail-expr-drop-order
 lints for potential violations

---
 compiler/rustc_borrowck/messages.ftl          |  3 +
 compiler/rustc_borrowck/src/lib.rs            | 79 +++++++++++++++----
 .../rustc_borrowck/src/session_diagnostics.rs |  7 ++
 compiler/rustc_mir_build/src/builder/scope.rs |  6 +-
 .../lint-tail-expr-drop-order-borrowck.rs     | 37 +++++++++
 .../lint-tail-expr-drop-order-borrowck.stderr | 40 ++++++++++
 6 files changed, 155 insertions(+), 17 deletions(-)
 create mode 100644 tests/ui/drop/lint-tail-expr-drop-order-borrowck.rs
 create mode 100644 tests/ui/drop/lint-tail-expr-drop-order-borrowck.stderr

diff --git a/compiler/rustc_borrowck/messages.ftl b/compiler/rustc_borrowck/messages.ftl
index ee4b2f95cb151..c1d5af59b1b5b 100644
--- a/compiler/rustc_borrowck/messages.ftl
+++ b/compiler/rustc_borrowck/messages.ftl
@@ -213,6 +213,9 @@ borrowck_suggest_create_fresh_reborrow =
 borrowck_suggest_iterate_over_slice =
     consider iterating over a slice of the `{$ty}`'s content to avoid moving into the `for` loop
 
+borrowck_tail_expr_drop_order = a temporary value will be dropped here before the execution exits the block in Edition 2024, which will raise borrow checking error
+    .label = consider using a `let` binding to create a longer lived value; or replacing the `{"{"} .. {"}"}` block with curly brackets `( .. )`; or folding the rest of the expression into the surrounding `unsafe {"{"} .. {"}"}`
+
 borrowck_ty_no_impl_copy =
     {$is_partial_move ->
         [true] partial move
diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs
index b061a450c83f7..bb1cb7f3de031 100644
--- a/compiler/rustc_borrowck/src/lib.rs
+++ b/compiler/rustc_borrowck/src/lib.rs
@@ -15,6 +15,7 @@
 #![warn(unreachable_pub)]
 // tidy-alphabetical-end
 
+use std::borrow::Cow;
 use std::cell::RefCell;
 use std::marker::PhantomData;
 use std::ops::Deref;
@@ -23,6 +24,7 @@ use rustc_abi::FieldIdx;
 use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
 use rustc_data_structures::graph::dominators::Dominators;
 use rustc_hir as hir;
+use rustc_hir::CRATE_HIR_ID;
 use rustc_hir::def_id::LocalDefId;
 use rustc_index::bit_set::{BitSet, MixedBitSet};
 use rustc_index::{IndexSlice, IndexVec};
@@ -42,7 +44,7 @@ use rustc_mir_dataflow::move_paths::{
     InitIndex, InitLocation, LookupResult, MoveData, MovePathIndex,
 };
 use rustc_mir_dataflow::{Analysis, EntryStates, Results, ResultsVisitor, visit_results};
-use rustc_session::lint::builtin::UNUSED_MUT;
+use rustc_session::lint::builtin::{TAIL_EXPR_DROP_ORDER, UNUSED_MUT};
 use rustc_span::{Span, Symbol};
 use smallvec::SmallVec;
 use tracing::{debug, instrument};
@@ -636,9 +638,11 @@ 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(..) => {}
+            // This does not affect borrowck
+            StatementKind::BackwardIncompatibleDropHint { place, reason: BackwardIncompatibleDropReason::Edition2024 } => {
+                self.check_backward_incompatible_drop(location, (**place, span), state);
+            }
             StatementKind::StorageDead(local) => {
                 self.access_place(
                     location,
@@ -1007,6 +1011,23 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> {
         }
     }
 
+    fn maybe_polonius_borrows_in_scope<'s>(
+        &self,
+        location: Location,
+        state: &'s BorrowckDomain,
+    ) -> Cow<'s, BitSet<BorrowIndex>> {
+        if let Some(polonius) = &self.polonius_output {
+            let location = self.location_table.start_index(location);
+            let mut polonius_output = BitSet::new_empty(self.borrow_set.len());
+            for &idx in polonius.errors_at(location) {
+                polonius_output.insert(idx);
+            }
+            Cow::Owned(polonius_output)
+        } else {
+            Cow::Borrowed(&state.borrows)
+        }
+    }
+
     #[instrument(level = "debug", skip(self, state))]
     fn check_access_for_conflict(
         &mut self,
@@ -1019,17 +1040,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> {
         let mut error_reported = false;
 
         // Use polonius output if it has been enabled.
-        let mut polonius_output;
-        let borrows_in_scope = if let Some(polonius) = &self.polonius_output {
-            let location = self.location_table.start_index(location);
-            polonius_output = BitSet::new_empty(self.borrow_set.len());
-            for &idx in polonius.errors_at(location) {
-                polonius_output.insert(idx);
-            }
-            &polonius_output
-        } else {
-            &state.borrows
-        };
+        let borrows_in_scope = self.maybe_polonius_borrows_in_scope(location, state);
 
         each_borrow_involving_path(
             self,
@@ -1149,6 +1160,46 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> {
         error_reported
     }
 
+    /// Through #123739, backward incompatible drops (BIDs) are introduced.
+    /// We would like to emit lints whether borrow checking fails at these future drop locations.
+    #[instrument(level = "debug", skip(self, state))]
+    fn check_backward_incompatible_drop(
+        &mut self,
+        location: Location,
+        place_span: (Place<'tcx>, Span),
+        state: &BorrowckDomain,
+    ) {
+        let sd = AccessDepth::Drop;
+
+        // Use polonius output if it has been enabled.
+        let borrows_in_scope = self.maybe_polonius_borrows_in_scope(location, state);
+
+        // This is a very simplified version of `Self::check_access_for_conflict`.
+        // We are here checking on BIDs and specifically still-live borrows of data involving the BIDs.
+        each_borrow_involving_path(
+            self,
+            self.infcx.tcx,
+            self.body,
+            (sd, place_span.0),
+            self.borrow_set,
+            |borrow_index| borrows_in_scope.contains(borrow_index),
+            |this, _borrow_index, borrow| {
+                if matches!(borrow.kind, BorrowKind::Fake(_)) {
+                    return Control::Continue;
+                }
+                let borrowed = this.retrieve_borrow_spans(borrow).var_or_use_path_span();
+                this.infcx.tcx.emit_node_span_lint(
+                    TAIL_EXPR_DROP_ORDER,
+                    CRATE_HIR_ID,
+                    place_span.1,
+                    session_diagnostics::TailExprDropOrder { borrowed },
+                );
+                // We may stop at the first case
+                Control::Break
+            },
+        );
+    }
+
     fn mutate_place(
         &mut self,
         location: Location,
diff --git a/compiler/rustc_borrowck/src/session_diagnostics.rs b/compiler/rustc_borrowck/src/session_diagnostics.rs
index 627444a4ce5b8..4be5d0dbf4284 100644
--- a/compiler/rustc_borrowck/src/session_diagnostics.rs
+++ b/compiler/rustc_borrowck/src/session_diagnostics.rs
@@ -480,3 +480,10 @@ pub(crate) struct SimdIntrinsicArgConst {
     pub arg: usize,
     pub intrinsic: String,
 }
+
+#[derive(LintDiagnostic)]
+#[diag(borrowck_tail_expr_drop_order)]
+pub(crate) struct TailExprDropOrder {
+    #[label]
+    pub borrowed: Span,
+}
diff --git a/compiler/rustc_mir_build/src/builder/scope.rs b/compiler/rustc_mir_build/src/builder/scope.rs
index 35c98037827af..20441530a4790 100644
--- a/compiler/rustc_mir_build/src/builder/scope.rs
+++ b/compiler/rustc_mir_build/src/builder/scope.rs
@@ -1131,15 +1131,15 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
 
     /// Schedule emission of a backwards incompatible drop lint hint.
     /// Applicable only to temporary values for now.
+    #[instrument(level = "debug", skip(self))]
     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, self.typing_env()) {
-            return;
-        }
+        // Note that we are *not* gating BIDs here on whether they have significant destructor.
+        // We need to know all of them so that we can capture potential borrow-checking errors.
         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();
diff --git a/tests/ui/drop/lint-tail-expr-drop-order-borrowck.rs b/tests/ui/drop/lint-tail-expr-drop-order-borrowck.rs
new file mode 100644
index 0000000000000..1bd5655d7fe1d
--- /dev/null
+++ b/tests/ui/drop/lint-tail-expr-drop-order-borrowck.rs
@@ -0,0 +1,37 @@
+// Edition 2024 lint for change in drop order at tail expression
+// This lint is to capture potential borrow-checking errors
+// due to implementation of RFC 3606 <https://github.com/rust-lang/rfcs/pull/3606>
+//@ edition: 2021
+
+#![deny(tail_expr_drop_order)] //~ NOTE: the lint level is defined here
+
+fn should_lint_with_potential_borrowck_err() {
+    let _ = { String::new().as_str() }.len();
+    //~^ ERROR: a temporary value will be dropped here
+    //~| WARN: this changes meaning in Rust 2024
+    //~| NOTE: consider using a `let` binding
+    //~| NOTE: for more information, see
+}
+
+fn should_lint_with_unsafe_block() {
+    fn f(_: usize) {}
+    f(unsafe { String::new().as_str() }.len());
+    //~^ ERROR: a temporary value will be dropped here
+    //~| WARN: this changes meaning in Rust 2024
+    //~| NOTE: consider using a `let` binding
+    //~| NOTE: for more information, see
+}
+
+#[rustfmt::skip]
+fn should_lint_with_big_block() {
+    fn f<T>(_: T) {}
+    f({
+        &mut || 0
+        //~^ ERROR: a temporary value will be dropped here
+        //~| WARN: this changes meaning in Rust 2024
+        //~| NOTE: consider using a `let` binding
+        //~| NOTE: for more information, see
+    })
+}
+
+fn main() {}
diff --git a/tests/ui/drop/lint-tail-expr-drop-order-borrowck.stderr b/tests/ui/drop/lint-tail-expr-drop-order-borrowck.stderr
new file mode 100644
index 0000000000000..98ef0547c9075
--- /dev/null
+++ b/tests/ui/drop/lint-tail-expr-drop-order-borrowck.stderr
@@ -0,0 +1,40 @@
+error: a temporary value will be dropped here before the execution exits the block in Edition 2024, which will raise borrow checking error
+  --> $DIR/lint-tail-expr-drop-order-borrowck.rs:9:36
+   |
+LL |     let _ = { String::new().as_str() }.len();
+   |               -------------        ^
+   |               |
+   |               consider using a `let` binding to create a longer lived value; or replacing the `{ .. }` block with curly brackets `( .. )`; or folding the rest of the expression into the surrounding `unsafe { .. }`
+   |
+   = warning: this changes meaning in Rust 2024
+   = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/temporary-tail-expr-scope.html>
+note: the lint level is defined here
+  --> $DIR/lint-tail-expr-drop-order-borrowck.rs:6:9
+   |
+LL | #![deny(tail_expr_drop_order)]
+   |         ^^^^^^^^^^^^^^^^^^^^
+
+error: a temporary value will be dropped here before the execution exits the block in Edition 2024, which will raise borrow checking error
+  --> $DIR/lint-tail-expr-drop-order-borrowck.rs:18:37
+   |
+LL |     f(unsafe { String::new().as_str() }.len());
+   |                -------------        ^
+   |                |
+   |                consider using a `let` binding to create a longer lived value; or replacing the `{ .. }` block with curly brackets `( .. )`; or folding the rest of the expression into the surrounding `unsafe { .. }`
+   |
+   = warning: this changes meaning in Rust 2024
+   = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/temporary-tail-expr-scope.html>
+
+error: a temporary value will be dropped here before the execution exits the block in Edition 2024, which will raise borrow checking error
+  --> $DIR/lint-tail-expr-drop-order-borrowck.rs:29:17
+   |
+LL |         &mut || 0
+   |         --------^
+   |         |
+   |         consider using a `let` binding to create a longer lived value; or replacing the `{ .. }` block with curly brackets `( .. )`; or folding the rest of the expression into the surrounding `unsafe { .. }`
+   |
+   = warning: this changes meaning in Rust 2024
+   = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/temporary-tail-expr-scope.html>
+
+error: aborting due to 3 previous errors
+

From 92d8d6cf711720358003153b9bd9dee70fa4f92c Mon Sep 17 00:00:00 2001
From: Ding Xiang Fei <dingxiangfei2009@protonmail.ch>
Date: Fri, 20 Dec 2024 01:21:51 +0800
Subject: [PATCH 2/6] apply suggestions on fn name

---
 compiler/rustc_borrowck/src/lib.rs | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs
index bb1cb7f3de031..5a1f1ea745130 100644
--- a/compiler/rustc_borrowck/src/lib.rs
+++ b/compiler/rustc_borrowck/src/lib.rs
@@ -1011,12 +1011,13 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> {
         }
     }
 
-    fn maybe_polonius_borrows_in_scope<'s>(
+    fn borrows_in_scope<'s>(
         &self,
         location: Location,
         state: &'s BorrowckDomain,
     ) -> Cow<'s, BitSet<BorrowIndex>> {
         if let Some(polonius) = &self.polonius_output {
+            // Use polonius output if it has been enabled.
             let location = self.location_table.start_index(location);
             let mut polonius_output = BitSet::new_empty(self.borrow_set.len());
             for &idx in polonius.errors_at(location) {
@@ -1039,8 +1040,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> {
     ) -> bool {
         let mut error_reported = false;
 
-        // Use polonius output if it has been enabled.
-        let borrows_in_scope = self.maybe_polonius_borrows_in_scope(location, state);
+        let borrows_in_scope = self.borrows_in_scope(location, state);
 
         each_borrow_involving_path(
             self,
@@ -1172,7 +1172,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> {
         let sd = AccessDepth::Drop;
 
         // Use polonius output if it has been enabled.
-        let borrows_in_scope = self.maybe_polonius_borrows_in_scope(location, state);
+        let borrows_in_scope = self.borrows_in_scope(location, state);
 
         // This is a very simplified version of `Self::check_access_for_conflict`.
         // We are here checking on BIDs and specifically still-live borrows of data involving the BIDs.

From 63762c9cb72879815ac7fd6f81c3235c65942c52 Mon Sep 17 00:00:00 2001
From: wieDasDing <6884440+dingxiangfei2009@users.noreply.github.com>
Date: Fri, 20 Dec 2024 02:08:36 +0800
Subject: [PATCH 3/6] remove an extraneous comment
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Co-authored-by: Rémy Rakic <remy.rakic+github@gmail.com>
---
 compiler/rustc_borrowck/src/lib.rs | 1 -
 1 file changed, 1 deletion(-)

diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs
index 5a1f1ea745130..a3911642ee65a 100644
--- a/compiler/rustc_borrowck/src/lib.rs
+++ b/compiler/rustc_borrowck/src/lib.rs
@@ -1171,7 +1171,6 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> {
     ) {
         let sd = AccessDepth::Drop;
 
-        // Use polonius output if it has been enabled.
         let borrows_in_scope = self.borrows_in_scope(location, state);
 
         // This is a very simplified version of `Self::check_access_for_conflict`.

From 1e17822b2ba39d9f7bb1a84ba786cff3997669da Mon Sep 17 00:00:00 2001
From: Michael Goulet <michael@errs.io>
Date: Sun, 5 Jan 2025 18:07:16 +0000
Subject: [PATCH 4/6] Don't create cycles by normalizing opaques defined in the
 body we're checking

---
 .../src/lint_tail_expr_drop_order.rs          |  7 ++++-
 tests/ui/drop/lint-tail-expr-drop-order.rs    |  1 -
 .../ui/drop/lint-tail-expr-drop-order.stderr  | 27 +++++++------------
 ...expr_drop_order-on-coroutine-unwind.stderr |  4 +++
 ..._expr_drop_order-on-recursive-boxed-fut.rs | 13 +++++++++
 5 files changed, 33 insertions(+), 19 deletions(-)
 create mode 100644 tests/ui/drop/tail_expr_drop_order-on-recursive-boxed-fut.rs

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
index e5a183bc75ce5..6590702118c7f 100644
--- a/compiler/rustc_mir_transform/src/lint_tail_expr_drop_order.rs
+++ b/compiler/rustc_mir_transform/src/lint_tail_expr_drop_order.rs
@@ -351,6 +351,11 @@ pub(crate) fn run_lint<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId, body: &Body<
     {
         return;
     }
+
+    // FIXME(typing_env): This should be able to reveal the opaques local to the
+    // body using the typeck results.
+    let typing_env = ty::TypingEnv::non_body_analysis(tcx, def_id);
+
     // ## About BIDs in blocks ##
     // Track the set of blocks that contain a backwards-incompatible drop (BID)
     // and, for each block, the vector of locations.
@@ -358,7 +363,7 @@ pub(crate) fn run_lint<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId, body: &Body<
     // 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 typing_env = ty::TypingEnv::post_analysis(tcx, def_id);
+
     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() {
diff --git a/tests/ui/drop/lint-tail-expr-drop-order.rs b/tests/ui/drop/lint-tail-expr-drop-order.rs
index b2a5db0d87133..55a2d1d3b7543 100644
--- a/tests/ui/drop/lint-tail-expr-drop-order.rs
+++ b/tests/ui/drop/lint-tail-expr-drop-order.rs
@@ -17,7 +17,6 @@ impl Drop for LoudDropper {
     //~| 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) {
diff --git a/tests/ui/drop/lint-tail-expr-drop-order.stderr b/tests/ui/drop/lint-tail-expr-drop-order.stderr
index 92afae5af6766..6ff9b7c12681d 100644
--- a/tests/ui/drop/lint-tail-expr-drop-order.stderr
+++ b/tests/ui/drop/lint-tail-expr-drop-order.stderr
@@ -1,5 +1,5 @@
 error: relative drop order changing in Rust 2024
-  --> $DIR/lint-tail-expr-drop-order.rs:41:15
+  --> $DIR/lint-tail-expr-drop-order.rs:40:15
    |
 LL |     let x = LoudDropper;
    |         -
@@ -40,7 +40,7 @@ LL | #![deny(tail_expr_drop_order)]
    |         ^^^^^^^^^^^^^^^^^^^^
 
 error: relative drop order changing in Rust 2024
-  --> $DIR/lint-tail-expr-drop-order.rs:66:19
+  --> $DIR/lint-tail-expr-drop-order.rs:65:19
    |
 LL |         let x = LoudDropper;
    |             -
@@ -76,7 +76,7 @@ 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:93:7
+  --> $DIR/lint-tail-expr-drop-order.rs:92:7
    |
 LL |     let x = LoudDropper;
    |         -
@@ -112,7 +112,7 @@ 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:146:5
+  --> $DIR/lint-tail-expr-drop-order.rs:145:5
    |
 LL |     let future = f();
    |         ------
@@ -136,19 +136,12 @@ note: `#1` invokes this custom destructor
    |
 LL | / impl Drop for LoudDropper {
 ...  |
-LL | | }
-   | |_^
-note: `future` invokes this custom destructor
-  --> $DIR/lint-tail-expr-drop-order.rs:10:1
-   |
-LL | / impl Drop for LoudDropper {
-...  |
 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:163:14
+  --> $DIR/lint-tail-expr-drop-order.rs:162:14
    |
 LL |     let x = T::default();
    |         -
@@ -170,7 +163,7 @@ 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:177:5
+  --> $DIR/lint-tail-expr-drop-order.rs:176:5
    |
 LL |     let x: Result<LoudDropper, ()> = Ok(LoudDropper);
    |         -
@@ -206,7 +199,7 @@ 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:221:5
+  --> $DIR/lint-tail-expr-drop-order.rs:220:5
    |
 LL |     let x = LoudDropper2;
    |         -
@@ -226,7 +219,7 @@ LL | }
    = warning: this changes meaning in Rust 2024
    = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/temporary-tail-expr-scope.html>
 note: `#1` invokes this custom destructor
-  --> $DIR/lint-tail-expr-drop-order.rs:194:5
+  --> $DIR/lint-tail-expr-drop-order.rs:193:5
    |
 LL | /     impl Drop for LoudDropper3 {
 LL | |
@@ -236,7 +229,7 @@ LL | |         }
 LL | |     }
    | |_____^
 note: `x` invokes this custom destructor
-  --> $DIR/lint-tail-expr-drop-order.rs:206:5
+  --> $DIR/lint-tail-expr-drop-order.rs:205:5
    |
 LL | /     impl Drop for LoudDropper2 {
 LL | |
@@ -248,7 +241,7 @@ 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:234:13
+  --> $DIR/lint-tail-expr-drop-order.rs:233:13
    |
 LL |             LoudDropper.get()
    |             ^^^^^^^^^^^
diff --git a/tests/ui/drop/tail_expr_drop_order-on-coroutine-unwind.stderr b/tests/ui/drop/tail_expr_drop_order-on-coroutine-unwind.stderr
index d98100bc1b04c..b0f971dd5cec6 100644
--- a/tests/ui/drop/tail_expr_drop_order-on-coroutine-unwind.stderr
+++ b/tests/ui/drop/tail_expr_drop_order-on-coroutine-unwind.stderr
@@ -4,10 +4,14 @@ error: relative drop order changing in Rust 2024
 LL |         match func().await {
    |               ^^^^^^^-----
    |               |      |
+   |               |      this value will be stored in a temporary; let us call it `#3`
+   |               |      up until Edition 2021 `#3` is dropped last but will be dropped earlier in Edition 2024
    |               |      this value will be stored in a temporary; let us call it `#1`
    |               |      `#1` will be dropped later as of Edition 2024
    |               this value will be stored in a temporary; let us call it `#2`
    |               up until Edition 2021 `#2` is dropped last but will be dropped earlier in Edition 2024
+   |               `__awaitee` calls a custom destructor
+   |               `__awaitee` will be dropped later as of Edition 2024
 ...
 LL |             Err(e) => {}
    |                 -
diff --git a/tests/ui/drop/tail_expr_drop_order-on-recursive-boxed-fut.rs b/tests/ui/drop/tail_expr_drop_order-on-recursive-boxed-fut.rs
new file mode 100644
index 0000000000000..4a72f224d9436
--- /dev/null
+++ b/tests/ui/drop/tail_expr_drop_order-on-recursive-boxed-fut.rs
@@ -0,0 +1,13 @@
+//@ edition: 2021
+//@ check-pass
+
+// Make sure we don't cycle error when normalizing types for tail expr drop order lint.
+
+#![deny(tail_expr_drop_order)]
+
+async fn test() -> Result<(), Box<dyn std::error::Error>> {
+    Box::pin(test()).await?;
+    Ok(())
+}
+
+fn main() {}

From 16d16b00518e945a1657fee65648df7bd0905c0c Mon Sep 17 00:00:00 2001
From: Michael Goulet <michael@errs.io>
Date: Sun, 5 Jan 2025 19:11:00 +0000
Subject: [PATCH 5/6] Don't do AccessDepth::Drop for types with no drop impl

---
 compiler/rustc_borrowck/src/lib.rs            | 16 ++++--
 .../tail_expr_drop_order-on-thread-local.rs   | 56 +++++++++++++++++++
 2 files changed, 68 insertions(+), 4 deletions(-)
 create mode 100644 tests/ui/drop/tail_expr_drop_order-on-thread-local.rs

diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs
index a3911642ee65a..a35dff99669a9 100644
--- a/compiler/rustc_borrowck/src/lib.rs
+++ b/compiler/rustc_borrowck/src/lib.rs
@@ -1166,10 +1166,18 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> {
     fn check_backward_incompatible_drop(
         &mut self,
         location: Location,
-        place_span: (Place<'tcx>, Span),
+        (place, place_span): (Place<'tcx>, Span),
         state: &BorrowckDomain,
     ) {
-        let sd = AccessDepth::Drop;
+        let tcx = self.infcx.tcx;
+        // If this type does not need `Drop`, then treat it like a `StorageDead`.
+        // This is needed because we track the borrows of refs to thread locals,
+        // and we'll ICE because we don't track borrows behind shared references.
+        let sd = if place.ty(self.body, tcx).ty.needs_drop(tcx, self.body.typing_env(tcx)) {
+            AccessDepth::Drop
+        } else {
+            AccessDepth::Shallow(None)
+        };
 
         let borrows_in_scope = self.borrows_in_scope(location, state);
 
@@ -1179,7 +1187,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> {
             self,
             self.infcx.tcx,
             self.body,
-            (sd, place_span.0),
+            (sd, place),
             self.borrow_set,
             |borrow_index| borrows_in_scope.contains(borrow_index),
             |this, _borrow_index, borrow| {
@@ -1190,7 +1198,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> {
                 this.infcx.tcx.emit_node_span_lint(
                     TAIL_EXPR_DROP_ORDER,
                     CRATE_HIR_ID,
-                    place_span.1,
+                    place_span,
                     session_diagnostics::TailExprDropOrder { borrowed },
                 );
                 // We may stop at the first case
diff --git a/tests/ui/drop/tail_expr_drop_order-on-thread-local.rs b/tests/ui/drop/tail_expr_drop_order-on-thread-local.rs
new file mode 100644
index 0000000000000..e38175fd1b653
--- /dev/null
+++ b/tests/ui/drop/tail_expr_drop_order-on-thread-local.rs
@@ -0,0 +1,56 @@
+//@ check-pass
+
+#![feature(thread_local)]
+#![deny(tail_expr_drop_order)]
+
+use std::marker::PhantomData;
+use std::ops::{Deref, DerefMut};
+
+pub struct Global;
+
+#[thread_local]
+static REENTRANCY_STATE: State<Global> = State { marker: PhantomData, controller: Global };
+
+pub struct Token(PhantomData<*mut ()>);
+
+pub fn with_mut<T>(f: impl FnOnce(&mut Token) -> T) -> T {
+    f(&mut REENTRANCY_STATE.borrow_mut())
+}
+
+pub struct State<T: ?Sized = Global> {
+    marker: PhantomData<*mut ()>,
+    controller: T,
+}
+
+impl<T: ?Sized> State<T> {
+    pub fn borrow_mut(&self) -> TokenMut<'_, T> {
+        todo!()
+    }
+}
+
+pub struct TokenMut<'a, T: ?Sized = Global> {
+    state: &'a State<T>,
+    token: Token,
+}
+
+impl<T> Deref for TokenMut<'_, T> {
+    type Target = Token;
+
+    fn deref(&self) -> &Self::Target {
+        todo!()
+    }
+}
+
+impl<T> DerefMut for TokenMut<'_, T> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        todo!()
+    }
+}
+
+impl<T: ?Sized> Drop for TokenMut<'_, T> {
+    fn drop(&mut self) {
+        todo!()
+    }
+}
+
+fn main() {}

From 1c69947d1c40850c969c9deda73067d18ac6a2c9 Mon Sep 17 00:00:00 2001
From: Michael Goulet <michael@errs.io>
Date: Mon, 6 Jan 2025 03:25:45 +0000
Subject: [PATCH 6/6] Try to explain borrow for tail expr temporary drop order
 change in 2024

---
 compiler/rustc_borrowck/messages.ftl          |  5 ++-
 .../src/diagnostics/explain_borrow.rs         | 18 ++++-----
 .../rustc_borrowck/src/diagnostics/mod.rs     |  1 +
 .../src/diagnostics/region_name.rs            |  4 +-
 compiler/rustc_borrowck/src/lib.rs            | 23 +++++++++--
 ...hod_1.ElaborateDrops.after.panic-abort.mir |  1 +
 ...od_1.ElaborateDrops.after.panic-unwind.mir |  1 +
 .../lint-tail-expr-drop-order-borrowck.rs     | 26 ++++++++++---
 .../lint-tail-expr-drop-order-borrowck.stderr | 38 ++++++++++++-------
 9 files changed, 82 insertions(+), 35 deletions(-)

diff --git a/compiler/rustc_borrowck/messages.ftl b/compiler/rustc_borrowck/messages.ftl
index c1d5af59b1b5b..ada20e5c614f8 100644
--- a/compiler/rustc_borrowck/messages.ftl
+++ b/compiler/rustc_borrowck/messages.ftl
@@ -213,8 +213,9 @@ borrowck_suggest_create_fresh_reborrow =
 borrowck_suggest_iterate_over_slice =
     consider iterating over a slice of the `{$ty}`'s content to avoid moving into the `for` loop
 
-borrowck_tail_expr_drop_order = a temporary value will be dropped here before the execution exits the block in Edition 2024, which will raise borrow checking error
-    .label = consider using a `let` binding to create a longer lived value; or replacing the `{"{"} .. {"}"}` block with curly brackets `( .. )`; or folding the rest of the expression into the surrounding `unsafe {"{"} .. {"}"}`
+borrowck_tail_expr_drop_order = relative drop order changing in Rust 2024
+    .label = this temporary value will be dropped at the end of the block
+    .note = consider using a `let` binding to ensure the value will live long enough
 
 borrowck_ty_no_impl_copy =
     {$is_partial_move ->
diff --git a/compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs b/compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs
index 87017460e8ec3..1e0c1f3fed7a4 100644
--- a/compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs
+++ b/compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs
@@ -5,7 +5,7 @@
 
 use std::assert_matches::assert_matches;
 
-use rustc_errors::{Applicability, Diag};
+use rustc_errors::{Applicability, Diag, EmissionGuarantee};
 use rustc_hir as hir;
 use rustc_hir::intravisit::Visitor;
 use rustc_index::IndexSlice;
@@ -61,12 +61,12 @@ impl<'tcx> BorrowExplanation<'tcx> {
     pub(crate) fn is_explained(&self) -> bool {
         !matches!(self, BorrowExplanation::Unexplained)
     }
-    pub(crate) fn add_explanation_to_diagnostic(
+    pub(crate) fn add_explanation_to_diagnostic<G: EmissionGuarantee>(
         &self,
         tcx: TyCtxt<'tcx>,
         body: &Body<'tcx>,
         local_names: &IndexSlice<Local, Option<Symbol>>,
-        err: &mut Diag<'_>,
+        err: &mut Diag<'_, G>,
         borrow_desc: &str,
         borrow_span: Option<Span>,
         multiple_borrow_span: Option<(Span, Span)>,
@@ -349,10 +349,10 @@ impl<'tcx> BorrowExplanation<'tcx> {
         }
     }
 
-    fn add_object_lifetime_default_note(
+    fn add_object_lifetime_default_note<G: EmissionGuarantee>(
         &self,
         tcx: TyCtxt<'tcx>,
-        err: &mut Diag<'_>,
+        err: &mut Diag<'_, G>,
         unsize_ty: Ty<'tcx>,
     ) {
         if let ty::Adt(def, args) = unsize_ty.kind() {
@@ -406,9 +406,9 @@ impl<'tcx> BorrowExplanation<'tcx> {
         }
     }
 
-    fn add_lifetime_bound_suggestion_to_diagnostic(
+    fn add_lifetime_bound_suggestion_to_diagnostic<G: EmissionGuarantee>(
         &self,
-        err: &mut Diag<'_>,
+        err: &mut Diag<'_, G>,
         category: &ConstraintCategory<'tcx>,
         span: Span,
         region_name: &RegionName,
@@ -435,14 +435,14 @@ impl<'tcx> BorrowExplanation<'tcx> {
     }
 }
 
-fn suggest_rewrite_if_let(
+fn suggest_rewrite_if_let<G: EmissionGuarantee>(
     tcx: TyCtxt<'_>,
     expr: &hir::Expr<'_>,
     pat: &str,
     init: &hir::Expr<'_>,
     conseq: &hir::Expr<'_>,
     alt: Option<&hir::Expr<'_>>,
-    err: &mut Diag<'_>,
+    err: &mut Diag<'_, G>,
 ) {
     let source_map = tcx.sess.source_map();
     err.span_note(
diff --git a/compiler/rustc_borrowck/src/diagnostics/mod.rs b/compiler/rustc_borrowck/src/diagnostics/mod.rs
index ebbdfea302cd3..b9b140a3f6b32 100644
--- a/compiler/rustc_borrowck/src/diagnostics/mod.rs
+++ b/compiler/rustc_borrowck/src/diagnostics/mod.rs
@@ -992,6 +992,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
                 kind,
             };
         }
+
         normal_ret
     }
 
diff --git a/compiler/rustc_borrowck/src/diagnostics/region_name.rs b/compiler/rustc_borrowck/src/diagnostics/region_name.rs
index bdb880b2bced3..9349b46ec5b0c 100644
--- a/compiler/rustc_borrowck/src/diagnostics/region_name.rs
+++ b/compiler/rustc_borrowck/src/diagnostics/region_name.rs
@@ -5,7 +5,7 @@ use std::fmt::{self, Display};
 use std::iter;
 
 use rustc_data_structures::fx::IndexEntry;
-use rustc_errors::Diag;
+use rustc_errors::{Diag, EmissionGuarantee};
 use rustc_hir as hir;
 use rustc_hir::def::{DefKind, Res};
 use rustc_middle::ty::print::RegionHighlightMode;
@@ -108,7 +108,7 @@ impl RegionName {
         }
     }
 
-    pub(crate) fn highlight_region_name(&self, diag: &mut Diag<'_>) {
+    pub(crate) fn highlight_region_name<G: EmissionGuarantee>(&self, diag: &mut Diag<'_, G>) {
         match &self.source {
             RegionNameSource::NamedLateParamRegion(span)
             | RegionNameSource::NamedEarlyParamRegion(span) => {
diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs
index a35dff99669a9..400bd51d87dfa 100644
--- a/compiler/rustc_borrowck/src/lib.rs
+++ b/compiler/rustc_borrowck/src/lib.rs
@@ -23,6 +23,7 @@ use std::ops::Deref;
 use rustc_abi::FieldIdx;
 use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
 use rustc_data_structures::graph::dominators::Dominators;
+use rustc_errors::LintDiagnostic;
 use rustc_hir as hir;
 use rustc_hir::CRATE_HIR_ID;
 use rustc_hir::def_id::LocalDefId;
@@ -1195,11 +1196,27 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> {
                     return Control::Continue;
                 }
                 let borrowed = this.retrieve_borrow_spans(borrow).var_or_use_path_span();
-                this.infcx.tcx.emit_node_span_lint(
+                let explain = this.explain_why_borrow_contains_point(
+                    location,
+                    borrow,
+                    Some((WriteKind::StorageDeadOrDrop, place)),
+                );
+                this.infcx.tcx.node_span_lint(
                     TAIL_EXPR_DROP_ORDER,
                     CRATE_HIR_ID,
-                    place_span,
-                    session_diagnostics::TailExprDropOrder { borrowed },
+                    borrowed,
+                    |diag| {
+                        session_diagnostics::TailExprDropOrder { borrowed }.decorate_lint(diag);
+                        explain.add_explanation_to_diagnostic(
+                            tcx,
+                            this.body,
+                            &this.local_names,
+                            diag,
+                            "",
+                            None,
+                            None,
+                        );
+                    },
                 );
                 // We may stop at the first case
                 Control::Break
diff --git a/tests/mir-opt/tail_expr_drop_order_unwind.method_1.ElaborateDrops.after.panic-abort.mir b/tests/mir-opt/tail_expr_drop_order_unwind.method_1.ElaborateDrops.after.panic-abort.mir
index e9bbe30bd774f..ee6e16d20fd8d 100644
--- a/tests/mir-opt/tail_expr_drop_order_unwind.method_1.ElaborateDrops.after.panic-abort.mir
+++ b/tests/mir-opt/tail_expr_drop_order_unwind.method_1.ElaborateDrops.after.panic-abort.mir
@@ -74,6 +74,7 @@ fn method_1(_1: Guard) -> () {
 
     bb7: {
         backward incompatible drop(_2);
+        backward incompatible drop(_4);
         backward incompatible drop(_5);
         goto -> bb21;
     }
diff --git a/tests/mir-opt/tail_expr_drop_order_unwind.method_1.ElaborateDrops.after.panic-unwind.mir b/tests/mir-opt/tail_expr_drop_order_unwind.method_1.ElaborateDrops.after.panic-unwind.mir
index e9bbe30bd774f..ee6e16d20fd8d 100644
--- a/tests/mir-opt/tail_expr_drop_order_unwind.method_1.ElaborateDrops.after.panic-unwind.mir
+++ b/tests/mir-opt/tail_expr_drop_order_unwind.method_1.ElaborateDrops.after.panic-unwind.mir
@@ -74,6 +74,7 @@ fn method_1(_1: Guard) -> () {
 
     bb7: {
         backward incompatible drop(_2);
+        backward incompatible drop(_4);
         backward incompatible drop(_5);
         goto -> bb21;
     }
diff --git a/tests/ui/drop/lint-tail-expr-drop-order-borrowck.rs b/tests/ui/drop/lint-tail-expr-drop-order-borrowck.rs
index 1bd5655d7fe1d..6f64d83f8a0c3 100644
--- a/tests/ui/drop/lint-tail-expr-drop-order-borrowck.rs
+++ b/tests/ui/drop/lint-tail-expr-drop-order-borrowck.rs
@@ -7,18 +7,20 @@
 
 fn should_lint_with_potential_borrowck_err() {
     let _ = { String::new().as_str() }.len();
-    //~^ ERROR: a temporary value will be dropped here
+    //~^ ERROR: relative drop order changing
     //~| WARN: this changes meaning in Rust 2024
-    //~| NOTE: consider using a `let` binding
+    //~| NOTE: this temporary value will be dropped at the end of the block
+    //~| borrow later used by call
     //~| NOTE: for more information, see
 }
 
 fn should_lint_with_unsafe_block() {
     fn f(_: usize) {}
     f(unsafe { String::new().as_str() }.len());
-    //~^ ERROR: a temporary value will be dropped here
+    //~^ ERROR: relative drop order changing
     //~| WARN: this changes meaning in Rust 2024
-    //~| NOTE: consider using a `let` binding
+    //~| NOTE: this temporary value will be dropped at the end of the block
+    //~| borrow later used by call
     //~| NOTE: for more information, see
 }
 
@@ -27,11 +29,23 @@ fn should_lint_with_big_block() {
     fn f<T>(_: T) {}
     f({
         &mut || 0
-        //~^ ERROR: a temporary value will be dropped here
+        //~^ ERROR: relative drop order changing
         //~| WARN: this changes meaning in Rust 2024
-        //~| NOTE: consider using a `let` binding
+        //~| NOTE: this temporary value will be dropped at the end of the block
+        //~| borrow later used here
         //~| NOTE: for more information, see
     })
 }
 
+fn another_temp_that_is_copy_in_arg() {
+    fn f() {}
+    fn g(_: &()) {}
+    g({ &f() });
+    //~^ ERROR: relative drop order changing
+    //~| WARN: this changes meaning in Rust 2024
+    //~| NOTE: this temporary value will be dropped at the end of the block
+    //~| borrow later used by call
+    //~| NOTE: for more information, see
+}
+
 fn main() {}
diff --git a/tests/ui/drop/lint-tail-expr-drop-order-borrowck.stderr b/tests/ui/drop/lint-tail-expr-drop-order-borrowck.stderr
index 98ef0547c9075..a55e366dd0be1 100644
--- a/tests/ui/drop/lint-tail-expr-drop-order-borrowck.stderr
+++ b/tests/ui/drop/lint-tail-expr-drop-order-borrowck.stderr
@@ -1,10 +1,10 @@
-error: a temporary value will be dropped here before the execution exits the block in Edition 2024, which will raise borrow checking error
-  --> $DIR/lint-tail-expr-drop-order-borrowck.rs:9:36
+error: relative drop order changing in Rust 2024
+  --> $DIR/lint-tail-expr-drop-order-borrowck.rs:9:15
    |
 LL |     let _ = { String::new().as_str() }.len();
-   |               -------------        ^
+   |               ^^^^^^^^^^^^^            --- borrow later used by call
    |               |
-   |               consider using a `let` binding to create a longer lived value; or replacing the `{ .. }` block with curly brackets `( .. )`; or folding the rest of the expression into the surrounding `unsafe { .. }`
+   |               this temporary value will be dropped at the end of the block
    |
    = warning: this changes meaning in Rust 2024
    = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/temporary-tail-expr-scope.html>
@@ -14,27 +14,39 @@ note: the lint level is defined here
 LL | #![deny(tail_expr_drop_order)]
    |         ^^^^^^^^^^^^^^^^^^^^
 
-error: a temporary value will be dropped here before the execution exits the block in Edition 2024, which will raise borrow checking error
-  --> $DIR/lint-tail-expr-drop-order-borrowck.rs:18:37
+error: relative drop order changing in Rust 2024
+  --> $DIR/lint-tail-expr-drop-order-borrowck.rs:19:16
    |
 LL |     f(unsafe { String::new().as_str() }.len());
-   |                -------------        ^
+   |                ^^^^^^^^^^^^^            --- borrow later used by call
    |                |
-   |                consider using a `let` binding to create a longer lived value; or replacing the `{ .. }` block with curly brackets `( .. )`; or folding the rest of the expression into the surrounding `unsafe { .. }`
+   |                this temporary value will be dropped at the end of the block
    |
    = warning: this changes meaning in Rust 2024
    = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/temporary-tail-expr-scope.html>
 
-error: a temporary value will be dropped here before the execution exits the block in Edition 2024, which will raise borrow checking error
-  --> $DIR/lint-tail-expr-drop-order-borrowck.rs:29:17
+error: relative drop order changing in Rust 2024
+  --> $DIR/lint-tail-expr-drop-order-borrowck.rs:31:9
    |
 LL |         &mut || 0
-   |         --------^
+   |         ^^^^^^^^^
    |         |
-   |         consider using a `let` binding to create a longer lived value; or replacing the `{ .. }` block with curly brackets `( .. )`; or folding the rest of the expression into the surrounding `unsafe { .. }`
+   |         this temporary value will be dropped at the end of the block
+   |         borrow later used here
    |
    = warning: this changes meaning in Rust 2024
    = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/temporary-tail-expr-scope.html>
 
-error: aborting due to 3 previous errors
+error: relative drop order changing in Rust 2024
+  --> $DIR/lint-tail-expr-drop-order-borrowck.rs:43:9
+   |
+LL |     g({ &f() });
+   |     -   ^^^^ this temporary value will be dropped at the end of the block
+   |     |
+   |     borrow later used by call
+   |
+   = warning: this changes meaning in Rust 2024
+   = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/temporary-tail-expr-scope.html>
+
+error: aborting due to 4 previous errors