@@ -257,7 +257,9 @@ struct DropNodeKey {
257257impl Scope {
258258 /// Whether there's anything to do for the cleanup path, that is,
259259 /// when unwinding through this scope. This includes destructors
260- /// and StorageDead statements to maintain proper drop ordering.
260+ /// (Value and ForLint drops). StorageDead drops are not included
261+ /// here because they don't require cleanup blocks - they're only
262+ /// needed for borrow-checking and are removed before codegen.
261263 fn needs_cleanup ( & self ) -> bool {
262264 self . drops . iter ( ) . any ( |drop| match drop. kind {
263265 DropKind :: Value | DropKind :: ForLint => true ,
@@ -1655,17 +1657,36 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
16551657
16561658 let is_coroutine = self . coroutine . is_some ( ) ;
16571659 // Check if there's a cleanup path (i.e., Value or ForLint drops that require cleanup)
1658- let has_cleanup_path = self . scopes . scopes [ uncached_scope..=target]
1659- . iter ( )
1660- . any ( |scope| scope . drops . iter ( ) . any ( |d| d . kind == DropKind :: Value || d . kind == DropKind :: ForLint ) ) ;
1660+ let has_cleanup_path = self . scopes . scopes [ uncached_scope..=target] . iter ( ) . any ( |scope| {
1661+ scope . drops . iter ( ) . any ( |d| d . kind == DropKind :: Value || d . kind == DropKind :: ForLint )
1662+ } ) ;
16611663 for scope in & mut self . scopes . scopes [ uncached_scope..=target] {
16621664 for drop in & scope. drops {
1663- // Add all drops to unwind_drops for all functions (not just coroutines)
1664- // to maintain proper drop ordering for borrow-checking. This matches
1665- // the behavior in build_exit_tree.
1666- // For coroutines, we always add all drops. For other functions, we now
1667- // also add StorageDead and ForLint drops, but only when there's a cleanup path.
1668- if is_coroutine || drop. kind == DropKind :: Value || drop. kind == DropKind :: ForLint || ( drop. kind == DropKind :: Storage && has_cleanup_path) {
1665+ // We add drops to unwind_drops to ensure the borrow-checker treats locals as dead
1666+ // at the same point on all paths (normal and unwind). This is for static semantics
1667+ // (borrow-checking), not runtime drop order.
1668+ //
1669+ // StorageDead drops are only needed when there are Value or ForLint drops present,
1670+ // because:
1671+ // 1. StorageDead is only relevant for borrow-checking when there are destructors
1672+ // that might reference the dead variable
1673+ // 2. If there are no drops, there's no unwind path to emit StorageDead on
1674+ // 3. StorageDead on unwind paths ensures the borrow-checker correctly tracks
1675+ // when variables are dead for the purpose of checking whether they might be
1676+ // referenced by destructors
1677+ //
1678+ // These StorageDead statements are removed by the `RemoveStorageMarkers` MIR
1679+ // transform pass before codegen, so they don't affect LLVM output. They only
1680+ // affect static analysis (borrow-checking).
1681+ //
1682+ // For coroutines, we always add all drops. For other functions, we add:
1683+ // - Value and ForLint drops (always, as they require cleanup)
1684+ // - StorageDead drops (only when there's a cleanup path, for borrow-checking)
1685+ if is_coroutine
1686+ || drop. kind == DropKind :: Value
1687+ || drop. kind == DropKind :: ForLint
1688+ || ( drop. kind == DropKind :: Storage && has_cleanup_path)
1689+ {
16691690 cached_drop = self . scopes . unwind_drops . add_drop ( * drop, cached_drop) ;
16701691 }
16711692 }
@@ -1885,9 +1906,13 @@ where
18851906 // another set of arrows).
18861907 //
18871908 // We unwind from a drop on a local to its StorageDead statement for all
1888- // functions (not just coroutines) to maintain proper drop ordering for
1889- // borrow-checking. The drops for the unwind path should have already been
1890- // generated by `diverge_cleanup_gen`.
1909+ // functions (not just coroutines) to ensure the borrow-checker treats locals
1910+ // as dead at the same point on all paths. This is for static semantics
1911+ // (borrow-checking), not runtime drop order. The drops for the unwind path
1912+ // should have already been generated by `diverge_cleanup_gen`.
1913+ //
1914+ // Note: StorageDead statements are removed by the `RemoveStorageMarkers` MIR
1915+ // transform pass before codegen, so they don't affect LLVM output.
18911916
18921917 // `unwind_to` indicates what needs to be dropped should unwinding occur.
18931918 // This is a subset of what needs to be dropped when exiting the scope.
@@ -1917,7 +1942,10 @@ where
19171942 // We adjust this BEFORE we create the drop (e.g., `drops[n]`)
19181943 // because `drops[n]` should unwind to `drops[n-1]`.
19191944 if unwind_to != DropIdx :: MAX {
1920- debug_assert_eq ! ( unwind_drops. drop_nodes[ unwind_to] . data. local, drop_data. local) ;
1945+ debug_assert_eq ! (
1946+ unwind_drops. drop_nodes[ unwind_to] . data. local,
1947+ drop_data. local
1948+ ) ;
19211949 debug_assert_eq ! ( unwind_drops. drop_nodes[ unwind_to] . data. kind, drop_data. kind) ;
19221950 unwind_to = unwind_drops. drop_nodes [ unwind_to] . next ;
19231951 }
@@ -1993,10 +2021,20 @@ where
19932021 ) ;
19942022 }
19952023 DropKind :: Storage => {
1996- // We emit storage-dead nodes on the unwind path for borrow-checking
1997- // purposes. When `storage_dead_on_unwind` is true, we need to adjust
1998- // the `unwind_to` pointer now that the storage-dead has completed, so
1999- // that any future drops we emit will not register storage-dead.
2024+ // We emit StorageDead statements on the unwind path to ensure the borrow-checker
2025+ // treats locals as dead at the same point on all paths. This is for static semantics
2026+ // (borrow-checking), not runtime drop order. StorageDead is only needed when there
2027+ // are Value or ForLint drops present, because:
2028+ // 1. StorageDead is only relevant for borrow-checking when there are destructors
2029+ // that might reference the dead variable
2030+ // 2. If there are no drops, there's no unwind path to emit StorageDead on
2031+ //
2032+ // These StorageDead statements are removed by the `RemoveStorageMarkers` MIR
2033+ // transform pass before codegen, so they don't affect LLVM output.
2034+ //
2035+ // When `storage_dead_on_unwind` is true, we need to adjust the `unwind_to` pointer
2036+ // now that the storage-dead has completed, so that any future drops we emit will
2037+ // not register storage-dead.
20002038 if storage_dead_on_unwind && unwind_to != DropIdx :: MAX {
20012039 debug_assert_eq ! (
20022040 unwind_drops. drop_nodes[ unwind_to] . data. local,
0 commit comments