about summary refs log tree commit diff
diff options
context:
space:
mode:
authorStuart Cook <Zalathar@users.noreply.github.com>2025-08-07 20:49:38 +1000
committerGitHub <noreply@github.com>2025-08-07 20:49:38 +1000
commit4529dd9192b5dccd3416e48f37c0263e55b852c0 (patch)
tree1fe70f093e121623ef5adbac97bf5671f753e322
parentb0fc38a36202e0e07c5770e3bab8169edb3c173e (diff)
parentc6a97d3c5939783c9df5ba876f0cc06d6fd0268c (diff)
downloadrust-4529dd9192b5dccd3416e48f37c0263e55b852c0.tar.gz
rust-4529dd9192b5dccd3416e48f37c0263e55b852c0.zip
Rollup merge of #143028 - dianne:let-else-storage, r=oli-obk,traviscross
emit `StorageLive` and schedule `StorageDead` for `let`-`else`'s bindings after matching

This PR removes special handling of `let`-`else`, so that `StorageLive`s are emitted and `StorageDead`s are scheduled only after pattern-matching has succeeded. This means `StorageDead`s will no longer appear for all of its bindings on the `else` branch (because they're not live yet) and its drops&`StorageDead`s will happen together like they do elsewhere, rather than having all drops first, then all `StorageDead`s.

This fixes rust-lang/rust#142056, and is therefore a breaking change. I believe it'll need a crater run and a T-lang nomination/fcp thereafter. Specifically, this makes drop-checking slightly more restrictive for `let`-`else` to match the behavior of other variable binding forms. An alternative approach could be to change the relative order of drops and `StorageDead`s for other binding forms to make drop-checking more permissive, but making that consistent would be a significantly more involved change.

r? mir
cc `````@dingxiangfei2009`````

`````@rustbot````` label +T-lang +needs-crater
-rw-r--r--compiler/rustc_mir_build/src/builder/block.rs12
-rw-r--r--compiler/rustc_mir_build/src/builder/matches/mod.rs52
-rw-r--r--tests/mir-opt/building/issue_101867.main.built.after.mir3
-rw-r--r--tests/mir-opt/building/user_type_annotations.let_else.built.after.mir11
-rw-r--r--tests/ui/dropck/let-else-more-permissive.rs7
-rw-r--r--tests/ui/dropck/let-else-more-permissive.stderr20
6 files changed, 36 insertions, 69 deletions
diff --git a/compiler/rustc_mir_build/src/builder/block.rs b/compiler/rustc_mir_build/src/builder/block.rs
index a71196f79d7..566d89f4269 100644
--- a/compiler/rustc_mir_build/src/builder/block.rs
+++ b/compiler/rustc_mir_build/src/builder/block.rs
@@ -6,7 +6,7 @@ use rustc_span::Span;
 use tracing::debug;
 
 use crate::builder::ForGuard::OutsideGuard;
-use crate::builder::matches::{DeclareLetBindings, EmitStorageLive, ScheduleDrops};
+use crate::builder::matches::{DeclareLetBindings, ScheduleDrops};
 use crate::builder::{BlockAnd, BlockAndExtension, BlockFrame, Builder};
 
 impl<'a, 'tcx> Builder<'a, 'tcx> {
@@ -199,15 +199,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                             None,
                             Some((Some(&destination), initializer_span)),
                         );
-                        this.visit_primary_bindings(pattern, &mut |this, node, span| {
-                            this.storage_live_binding(
-                                block,
-                                node,
-                                span,
-                                OutsideGuard,
-                                ScheduleDrops::Yes,
-                            );
-                        });
                         let else_block_span = this.thir[*else_block].span;
                         let (matching, failure) =
                             this.in_if_then_scope(last_remainder_scope, else_block_span, |this| {
@@ -218,7 +209,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                                     None,
                                     initializer_span,
                                     DeclareLetBindings::No,
-                                    EmitStorageLive::No,
                                 )
                             });
                         matching.and(failure)
diff --git a/compiler/rustc_mir_build/src/builder/matches/mod.rs b/compiler/rustc_mir_build/src/builder/matches/mod.rs
index 2c29b862841..a17adc2a7cd 100644
--- a/compiler/rustc_mir_build/src/builder/matches/mod.rs
+++ b/compiler/rustc_mir_build/src/builder/matches/mod.rs
@@ -69,18 +69,6 @@ pub(crate) enum DeclareLetBindings {
     LetNotPermitted,
 }
 
-/// Used by [`Builder::bind_matched_candidate_for_arm_body`] to determine
-/// whether or not to call [`Builder::storage_live_binding`] to emit
-/// [`StatementKind::StorageLive`].
-#[derive(Clone, Copy)]
-pub(crate) enum EmitStorageLive {
-    /// Yes, emit `StorageLive` as normal.
-    Yes,
-    /// No, don't emit `StorageLive`. The caller has taken responsibility for
-    /// emitting `StorageLive` as appropriate.
-    No,
-}
-
 /// Used by [`Builder::storage_live_binding`] and [`Builder::bind_matched_candidate_for_arm_body`]
 /// to decide whether to schedule drops.
 #[derive(Clone, Copy, Debug)]
@@ -207,7 +195,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                 Some(args.variable_source_info.scope),
                 args.variable_source_info.span,
                 args.declare_let_bindings,
-                EmitStorageLive::Yes,
             ),
             _ => {
                 let mut block = block;
@@ -479,7 +466,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                         &built_match_tree.fake_borrow_temps,
                         scrutinee_span,
                         Some((arm, match_scope)),
-                        EmitStorageLive::Yes,
                     );
 
                     this.fixed_temps_scope = old_dedup_scope;
@@ -533,7 +519,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
         fake_borrow_temps: &[(Place<'tcx>, Local, FakeBorrowKind)],
         scrutinee_span: Span,
         arm_match_scope: Option<(&Arm<'tcx>, region::Scope)>,
-        emit_storage_live: EmitStorageLive,
     ) -> BasicBlock {
         if branch.sub_branches.len() == 1 {
             let [sub_branch] = branch.sub_branches.try_into().unwrap();
@@ -544,7 +529,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                 scrutinee_span,
                 arm_match_scope,
                 ScheduleDrops::Yes,
-                emit_storage_live,
             )
         } else {
             // It's helpful to avoid scheduling drops multiple times to save
@@ -577,7 +561,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                     scrutinee_span,
                     arm_match_scope,
                     schedule_drops,
-                    emit_storage_live,
                 );
                 if arm.is_none() {
                     schedule_drops = ScheduleDrops::No;
@@ -741,7 +724,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
             &[],
             irrefutable_pat.span,
             None,
-            EmitStorageLive::Yes,
         )
         .unit()
     }
@@ -2364,7 +2346,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
         source_scope: Option<SourceScope>,
         scope_span: Span,
         declare_let_bindings: DeclareLetBindings,
-        emit_storage_live: EmitStorageLive,
     ) -> BlockAnd<()> {
         let expr_span = self.thir[expr_id].span;
         let scrutinee = unpack!(block = self.lower_scrutinee(block, expr_id, expr_span));
@@ -2398,14 +2379,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
             }
         }
 
-        let success = self.bind_pattern(
-            self.source_info(pat.span),
-            branch,
-            &[],
-            expr_span,
-            None,
-            emit_storage_live,
-        );
+        let success = self.bind_pattern(self.source_info(pat.span), branch, &[], expr_span, None);
 
         // If branch coverage is enabled, record this branch.
         self.visit_coverage_conditional_let(pat, success, built_tree.otherwise_block);
@@ -2428,7 +2402,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
         scrutinee_span: Span,
         arm_match_scope: Option<(&Arm<'tcx>, region::Scope)>,
         schedule_drops: ScheduleDrops,
-        emit_storage_live: EmitStorageLive,
     ) -> BasicBlock {
         debug!("bind_and_guard_matched_candidate(subbranch={:?})", sub_branch);
 
@@ -2547,7 +2520,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                 post_guard_block,
                 ScheduleDrops::Yes,
                 by_value_bindings,
-                emit_storage_live,
             );
 
             post_guard_block
@@ -2559,7 +2531,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                 block,
                 schedule_drops,
                 sub_branch.bindings.iter(),
-                emit_storage_live,
             );
             block
         }
@@ -2730,7 +2701,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
         block: BasicBlock,
         schedule_drops: ScheduleDrops,
         bindings: impl IntoIterator<Item = &'b Binding<'tcx>>,
-        emit_storage_live: EmitStorageLive,
     ) where
         'tcx: 'b,
     {
@@ -2740,19 +2710,13 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
         // Assign each of the bindings. This may trigger moves out of the candidate.
         for binding in bindings {
             let source_info = self.source_info(binding.span);
-            let local = match emit_storage_live {
-                // Here storages are already alive, probably because this is a binding
-                // from let-else.
-                // We just need to schedule drop for the value.
-                EmitStorageLive::No => self.var_local_id(binding.var_id, OutsideGuard).into(),
-                EmitStorageLive::Yes => self.storage_live_binding(
-                    block,
-                    binding.var_id,
-                    binding.span,
-                    OutsideGuard,
-                    schedule_drops,
-                ),
-            };
+            let local = self.storage_live_binding(
+                block,
+                binding.var_id,
+                binding.span,
+                OutsideGuard,
+                schedule_drops,
+            );
             if matches!(schedule_drops, ScheduleDrops::Yes) {
                 self.schedule_drop_for_binding(binding.var_id, binding.span, OutsideGuard);
             }
diff --git a/tests/mir-opt/building/issue_101867.main.built.after.mir b/tests/mir-opt/building/issue_101867.main.built.after.mir
index e59b23fdd20..8a36c901eed 100644
--- a/tests/mir-opt/building/issue_101867.main.built.after.mir
+++ b/tests/mir-opt/building/issue_101867.main.built.after.mir
@@ -24,7 +24,6 @@ fn main() -> () {
         _1 = Option::<u8>::Some(const 1_u8);
         FakeRead(ForLet(None), _1);
         AscribeUserType(_1, o, UserTypeProjection { base: UserType(1), projs: [] });
-        StorageLive(_5);
         PlaceMention(_1);
         _6 = discriminant(_1);
         switchInt(move _6) -> [1: bb4, otherwise: bb3];
@@ -55,6 +54,7 @@ fn main() -> () {
     }
 
     bb6: {
+        StorageLive(_5);
         _5 = copy ((_1 as Some).0: u8);
         _0 = const ();
         StorageDead(_5);
@@ -63,7 +63,6 @@ fn main() -> () {
     }
 
     bb7: {
-        StorageDead(_5);
         goto -> bb1;
     }
 
diff --git a/tests/mir-opt/building/user_type_annotations.let_else.built.after.mir b/tests/mir-opt/building/user_type_annotations.let_else.built.after.mir
index 6369dbec750..3d26fe24ac9 100644
--- a/tests/mir-opt/building/user_type_annotations.let_else.built.after.mir
+++ b/tests/mir-opt/building/user_type_annotations.let_else.built.after.mir
@@ -21,9 +21,6 @@ fn let_else() -> () {
     }
 
     bb0: {
-        StorageLive(_2);
-        StorageLive(_3);
-        StorageLive(_4);
         StorageLive(_5);
         StorageLive(_6);
         StorageLive(_7);
@@ -51,16 +48,19 @@ fn let_else() -> () {
 
     bb4: {
         AscribeUserType(_5, +, UserTypeProjection { base: UserType(1), projs: [] });
+        StorageLive(_2);
         _2 = copy (_5.0: u32);
+        StorageLive(_3);
         _3 = copy (_5.1: u64);
+        StorageLive(_4);
         _4 = copy (_5.2: &char);
         StorageDead(_7);
         StorageDead(_5);
         _0 = const ();
-        StorageDead(_8);
         StorageDead(_4);
         StorageDead(_3);
         StorageDead(_2);
+        StorageDead(_8);
         return;
     }
 
@@ -68,9 +68,6 @@ fn let_else() -> () {
         StorageDead(_7);
         StorageDead(_5);
         StorageDead(_8);
-        StorageDead(_4);
-        StorageDead(_3);
-        StorageDead(_2);
         goto -> bb1;
     }
 
diff --git a/tests/ui/dropck/let-else-more-permissive.rs b/tests/ui/dropck/let-else-more-permissive.rs
index 0020814aa81..6247b0eb5e2 100644
--- a/tests/ui/dropck/let-else-more-permissive.rs
+++ b/tests/ui/dropck/let-else-more-permissive.rs
@@ -1,5 +1,5 @@
-//! The drop check is currently more permissive when `let` statements have an `else` block, due to
-//! scheduling drops for bindings' storage before pattern-matching (#142056).
+//! Regression test for #142056. The drop check used to be more permissive for `let` statements with
+//! `else` blocks, due to scheduling drops for bindings' storage before pattern-matching.
 
 struct Struct<T>(T);
 impl<T> Drop for Struct<T> {
@@ -14,10 +14,11 @@ fn main() {
         //~^ ERROR `short1` does not live long enough
     }
     {
-        // This is OK: `short2`'s storage is live until after `long2`'s drop runs.
+        // This was OK: `short2`'s storage was live until after `long2`'s drop ran.
         #[expect(irrefutable_let_patterns)]
         let (mut long2, short2) = (Struct(&0), 1) else { unreachable!() };
         long2.0 = &short2;
+        //~^ ERROR `short2` does not live long enough
     }
     {
         // Sanity check: `short3`'s drop is significant; it's dropped before `long3`:
diff --git a/tests/ui/dropck/let-else-more-permissive.stderr b/tests/ui/dropck/let-else-more-permissive.stderr
index 7c37e170afa..4f0c193a78d 100644
--- a/tests/ui/dropck/let-else-more-permissive.stderr
+++ b/tests/ui/dropck/let-else-more-permissive.stderr
@@ -14,8 +14,24 @@ LL |     }
    |
    = note: values in a scope are dropped in the opposite order they are defined
 
+error[E0597]: `short2` does not live long enough
+  --> $DIR/let-else-more-permissive.rs:20:19
+   |
+LL |         let (mut long2, short2) = (Struct(&0), 1) else { unreachable!() };
+   |                         ------ binding `short2` declared here
+LL |         long2.0 = &short2;
+   |                   ^^^^^^^ borrowed value does not live long enough
+LL |
+LL |     }
+   |     -
+   |     |
+   |     `short2` dropped here while still borrowed
+   |     borrow might be used here, when `long2` is dropped and runs the `Drop` code for type `Struct`
+   |
+   = note: values in a scope are dropped in the opposite order they are defined
+
 error[E0597]: `short3` does not live long enough
-  --> $DIR/let-else-more-permissive.rs:27:19
+  --> $DIR/let-else-more-permissive.rs:28:19
    |
 LL |         let (mut long3, short3) = (Struct(&tmp), Box::new(1)) else { unreachable!() };
    |                         ------ binding `short3` declared here
@@ -30,6 +46,6 @@ LL |     }
    |
    = note: values in a scope are dropped in the opposite order they are defined
 
-error: aborting due to 2 previous errors
+error: aborting due to 3 previous errors
 
 For more information about this error, try `rustc --explain E0597`.