about summary refs log tree commit diff
diff options
context:
space:
mode:
authorJason Newcomb <jsnewcomb@pm.me>2023-11-09 17:29:01 -0500
committerJason Newcomb <jsnewcomb@pm.me>2023-11-09 17:57:06 -0500
commita44bb079005bac4dca0bdf7d94d5d87c52166674 (patch)
tree7028374e94b5075dbb8c12672ca2c3bee2c7e8ac
parent16d58a298232e026c05d69f4e8b5740f0f428bdb (diff)
downloadrust-a44bb079005bac4dca0bdf7d94d5d87c52166674.tar.gz
rust-a44bb079005bac4dca0bdf7d94d5d87c52166674.zip
Change divergence checking to match the compiler's type system based definition of divergence.
-rw-r--r--clippy_lints/src/manual_let_else.rs9
-rw-r--r--clippy_utils/src/lib.rs292
-rw-r--r--tests/ui/manual_let_else.rs113
-rw-r--r--tests/ui/manual_let_else.stderr172
4 files changed, 467 insertions, 119 deletions
diff --git a/clippy_lints/src/manual_let_else.rs b/clippy_lints/src/manual_let_else.rs
index 67a2a0ef8ba..01eccb56a0a 100644
--- a/clippy_lints/src/manual_let_else.rs
+++ b/clippy_lints/src/manual_let_else.rs
@@ -64,7 +64,7 @@ impl<'tcx> QuestionMark {
                 IfLetOrMatch::IfLet(if_let_expr, let_pat, if_then, if_else) => {
                     if let Some(ident_map) = expr_simple_identity_map(local.pat, let_pat, if_then)
                         && let Some(if_else) = if_else
-                        && is_never_expr(cx, if_else)
+                        && is_never_expr(cx, if_else).is_some()
                         && let qm_allowed = is_lint_allowed(cx, QUESTION_MARK, stmt.hir_id)
                         && (qm_allowed || pat_and_expr_can_be_question_mark(cx, let_pat, if_else).is_none())
                     {
@@ -88,10 +88,9 @@ impl<'tcx> QuestionMark {
                         return;
                     }
                     let check_types = self.matches_behaviour == MatchLintBehaviour::WellKnownTypes;
-                    let diverging_arm_opt = arms
-                        .iter()
-                        .enumerate()
-                        .find(|(_, arm)| is_never_expr(cx, arm.body) && pat_allowed_for_else(cx, arm.pat, check_types));
+                    let diverging_arm_opt = arms.iter().enumerate().find(|(_, arm)| {
+                        is_never_expr(cx, arm.body).is_some() && pat_allowed_for_else(cx, arm.pat, check_types)
+                    });
                     let Some((idx, diverging_arm)) = diverging_arm_opt else {
                         return;
                     };
diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs
index e11f7867ff0..19e4d4ad759 100644
--- a/clippy_utils/src/lib.rs
+++ b/clippy_utils/src/lib.rs
@@ -71,6 +71,7 @@ pub use self::hir_utils::{
     both, count_eq, eq_expr_value, hash_expr, hash_stmt, is_bool, over, HirEqInterExpr, SpanlessEq, SpanlessHash,
 };
 
+use core::mem;
 use core::ops::ControlFlow;
 use std::collections::hash_map::Entry;
 use std::hash::BuildHasherDefault;
@@ -88,7 +89,7 @@ use rustc_hir::intravisit::{walk_expr, FnKind, Visitor};
 use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk};
 use rustc_hir::{
     self as hir, def, Arm, ArrayLen, BindingAnnotation, Block, BlockCheckMode, Body, Closure, Destination, Expr,
-    ExprField, ExprKind, FnDecl, FnRetTy, GenericArgs, HirId, Impl, ImplItem, ImplItemKind, ImplItemRef, Item, ItemId,
+    ExprField, ExprKind, FnDecl, FnRetTy, GenericArgs, HirId, Impl, ImplItem, ImplItemKind, ImplItemRef, Item,
     ItemKind, LangItem, Local, MatchSource, Mutability, Node, OwnerId, Param, Pat, PatKind, Path, PathSegment, PrimTy,
     QPath, Stmt, StmtKind, TraitItem, TraitItemKind, TraitItemRef, TraitRef, TyKind, UnOp,
 };
@@ -117,7 +118,7 @@ use crate::ty::{
     adt_and_variant_of_res, can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type,
     ty_is_fn_once_param,
 };
-use crate::visitors::{for_each_expr, Descend};
+use crate::visitors::for_each_expr;
 
 use rustc_middle::hir::nested_filter;
 
@@ -2975,100 +2976,247 @@ pub fn pat_is_wild<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx PatKind<'_>, body: i
     }
 }
 
-/// Check if the expression either returns, or could be coerced into returning, `!`.
-pub fn is_never_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+#[derive(Clone, Copy)]
+pub enum RequiresSemi {
+    Yes,
+    No,
+}
+impl RequiresSemi {
+    pub fn requires_semi(self) -> bool {
+        matches!(self, Self::Yes)
+    }
+}
+
+/// Check if the expression return `!`, a type coerced from `!`, or could return `!` if the final
+/// expression were turned into a statement.
+#[expect(clippy::too_many_lines)]
+pub fn is_never_expr<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> Option<RequiresSemi> {
+    struct BreakTarget {
+        id: HirId,
+        unused: bool,
+    }
+
     struct V<'cx, 'tcx> {
         cx: &'cx LateContext<'tcx>,
-        res: ControlFlow<(), Descend>,
+        break_targets: Vec<BreakTarget>,
+        break_targets_for_result_ty: u32,
+        in_final_expr: bool,
+        requires_semi: bool,
+        is_never: bool,
     }
-    impl<'tcx> Visitor<'tcx> for V<'_, '_> {
-        fn visit_expr(&mut self, e: &'tcx Expr<'tcx>) {
-            fn is_never(cx: &LateContext<'_>, expr: &'_ Expr<'_>) -> bool {
-                if let Some(ty) = cx.typeck_results().expr_ty_opt(expr) {
-                    return ty.is_never();
-                }
-                false
-            }
 
-            if self.res.is_break() {
+    impl<'tcx> V<'_, 'tcx> {
+        fn push_break_target(&mut self, id: HirId) {
+            self.break_targets.push(BreakTarget { id, unused: true });
+            self.break_targets_for_result_ty += u32::from(self.in_final_expr);
+        }
+    }
+
+    impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> {
+        fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
+            // Note: Part of the complexity here comes from the fact that
+            // coercions are applied to the innermost expression.
+            // e.g. In `let x: u32 = { break () };` the never-to-any coercion
+            // is applied to the break expression. This means we can't just
+            // check the block's type as it will be `u32` despite the fact
+            // that the block always diverges.
+
+            // The rest of the complexity comes from checking blocks which
+            // syntactically return a value, but will always diverge before
+            // reaching that point.
+            // e.g. In `let x = { foo(panic!()) };` the block's type will be the
+            // return type of `foo` even though it will never actually run. This
+            // can be trivially fixed by adding a semicolon after the call, but
+            // we must first detect that a semicolon is needed to make that
+            // suggestion.
+
+            if self.is_never && self.break_targets.is_empty() {
+                if self.in_final_expr && !self.requires_semi {
+                    // This expression won't ever run, but we still need to check
+                    // if it can affect the type of the final expression.
+                    match e.kind {
+                        ExprKind::DropTemps(e) => self.visit_expr(e),
+                        ExprKind::If(_, then, Some(else_)) => {
+                            self.visit_expr(then);
+                            self.visit_expr(else_);
+                        },
+                        ExprKind::Match(_, arms, _) => {
+                            for arm in arms {
+                                self.visit_expr(arm.body);
+                            }
+                        },
+                        ExprKind::Loop(b, ..) => {
+                            self.push_break_target(e.hir_id);
+                            self.in_final_expr = false;
+                            self.visit_block(b);
+                            self.break_targets.pop();
+                        },
+                        ExprKind::Block(b, _) => {
+                            if b.targeted_by_break {
+                                self.push_break_target(b.hir_id);
+                                self.visit_block(b);
+                                self.break_targets.pop();
+                            } else {
+                                self.visit_block(b);
+                            }
+                        },
+                        _ => {
+                            self.requires_semi = !self.cx.typeck_results().expr_ty(e).is_never();
+                        },
+                    }
+                }
                 return;
             }
-
-            // We can't just call is_never on expr and be done, because the type system
-            // sometimes coerces the ! type to something different before we can get
-            // our hands on it. So instead, we do a manual search. We do fall back to
-            // is_never in some places when there is no better alternative.
-            self.res = match e.kind {
-                ExprKind::Continue(_) | ExprKind::Break(_, _) | ExprKind::Ret(_) => ControlFlow::Break(()),
-                ExprKind::Call(call, _) => {
-                    if is_never(self.cx, e) || is_never(self.cx, call) {
-                        ControlFlow::Break(())
-                    } else {
-                        ControlFlow::Continue(Descend::Yes)
+            match e.kind {
+                ExprKind::DropTemps(e) => self.visit_expr(e),
+                ExprKind::Ret(None) | ExprKind::Continue(_) => self.is_never = true,
+                ExprKind::Ret(Some(e)) | ExprKind::Become(e) => {
+                    self.in_final_expr = false;
+                    self.visit_expr(e);
+                    self.is_never = true;
+                },
+                ExprKind::Break(dest, e) => {
+                    if let Some(e) = e {
+                        self.in_final_expr = false;
+                        self.visit_expr(e);
+                    }
+                    if let Ok(id) = dest.target_id
+                        && let Some((i, target)) = self
+                            .break_targets
+                            .iter_mut()
+                            .enumerate()
+                            .find(|(_, target)| target.id == id)
+                    {
+                        target.unused &= self.is_never;
+                        if i < self.break_targets_for_result_ty as usize {
+                            self.requires_semi = true;
+                        }
                     }
+                    self.is_never = true;
                 },
-                ExprKind::MethodCall(..) => {
-                    if is_never(self.cx, e) {
-                        ControlFlow::Break(())
+                ExprKind::If(cond, then, else_) => {
+                    let in_final_expr = mem::replace(&mut self.in_final_expr, false);
+                    self.visit_expr(cond);
+                    self.in_final_expr = in_final_expr;
+
+                    if self.is_never {
+                        self.visit_expr(then);
+                        if let Some(else_) = else_ {
+                            self.visit_expr(else_);
+                        }
                     } else {
-                        ControlFlow::Continue(Descend::Yes)
+                        self.visit_expr(then);
+                        let is_never = mem::replace(&mut self.is_never, false);
+                        if let Some(else_) = else_ {
+                            self.visit_expr(else_);
+                            self.is_never &= is_never;
+                        }
                     }
                 },
-                ExprKind::If(if_expr, if_then, if_else) => {
-                    let else_diverges = if_else.map_or(false, |ex| is_never_expr(self.cx, ex));
-                    let diverges =
-                        is_never_expr(self.cx, if_expr) || (else_diverges && is_never_expr(self.cx, if_then));
-                    if diverges {
-                        ControlFlow::Break(())
+                ExprKind::Match(scrutinee, arms, _) => {
+                    let in_final_expr = mem::replace(&mut self.in_final_expr, false);
+                    self.visit_expr(scrutinee);
+                    self.in_final_expr = in_final_expr;
+
+                    if self.is_never {
+                        for arm in arms {
+                            self.visit_arm(arm);
+                        }
                     } else {
-                        ControlFlow::Continue(Descend::No)
+                        let mut is_never = true;
+                        for arm in arms {
+                            self.is_never = false;
+                            if let Some(guard) = arm.guard {
+                                let in_final_expr = mem::replace(&mut self.in_final_expr, false);
+                                self.visit_expr(guard.body());
+                                self.in_final_expr = in_final_expr;
+                                // The compiler doesn't consider diverging guards as causing the arm to diverge.
+                                self.is_never = false;
+                            }
+                            self.visit_expr(arm.body);
+                            is_never &= self.is_never;
+                        }
+                        self.is_never = is_never;
                     }
                 },
-                ExprKind::Match(match_expr, match_arms, _) => {
-                    let diverges = is_never_expr(self.cx, match_expr)
-                        || match_arms.iter().all(|arm| {
-                            let guard_diverges = arm.guard.as_ref().map_or(false, |g| is_never_expr(self.cx, g.body()));
-                            guard_diverges || is_never_expr(self.cx, arm.body)
-                        });
-                    if diverges {
-                        ControlFlow::Break(())
+                ExprKind::Loop(b, _, _, _) => {
+                    self.push_break_target(e.hir_id);
+                    self.in_final_expr = false;
+                    self.visit_block(b);
+                    self.is_never = self.break_targets.pop().unwrap().unused;
+                },
+                ExprKind::Block(b, _) => {
+                    if b.targeted_by_break {
+                        self.push_break_target(b.hir_id);
+                        self.visit_block(b);
+                        self.is_never &= self.break_targets.pop().unwrap().unused;
                     } else {
-                        ControlFlow::Continue(Descend::No)
+                        self.visit_block(b);
                     }
                 },
+                _ => {
+                    self.in_final_expr = false;
+                    walk_expr(self, e);
+                    self.is_never |= self.cx.typeck_results().expr_ty(e).is_never();
+                },
+            }
+        }
 
-                // Don't continue into loops or labeled blocks, as they are breakable,
-                // and we'd have to start checking labels.
-                ExprKind::Block(_, Some(_)) | ExprKind::Loop(..) => ControlFlow::Continue(Descend::No),
-
-                // Default: descend
-                _ => ControlFlow::Continue(Descend::Yes),
-            };
-            if let ControlFlow::Continue(Descend::Yes) = self.res {
-                walk_expr(self, e);
+        fn visit_block(&mut self, b: &'tcx Block<'_>) {
+            let in_final_expr = mem::replace(&mut self.in_final_expr, false);
+            for s in b.stmts {
+                self.visit_stmt(s);
+            }
+            self.in_final_expr = in_final_expr;
+            if let Some(e) = b.expr {
+                self.visit_expr(e);
             }
         }
 
-        fn visit_local(&mut self, local: &'tcx Local<'_>) {
-            // Don't visit the else block of a let/else statement as it will not make
-            // the statement divergent even though the else block is divergent.
-            if let Some(init) = local.init {
-                self.visit_expr(init);
+        fn visit_local(&mut self, l: &'tcx Local<'_>) {
+            if let Some(e) = l.init {
+                self.visit_expr(e);
+            }
+            if let Some(else_) = l.els {
+                let is_never = self.is_never;
+                self.visit_block(else_);
+                self.is_never = is_never;
             }
         }
 
-        // Avoid unnecessary `walk_*` calls.
-        fn visit_ty(&mut self, _: &'tcx hir::Ty<'tcx>) {}
-        fn visit_pat(&mut self, _: &'tcx Pat<'tcx>) {}
-        fn visit_qpath(&mut self, _: &'tcx QPath<'tcx>, _: HirId, _: Span) {}
-        // Avoid monomorphising all `visit_*` functions.
-        fn visit_nested_item(&mut self, _: ItemId) {}
+        fn visit_arm(&mut self, arm: &Arm<'tcx>) {
+            if let Some(guard) = arm.guard {
+                let in_final_expr = mem::replace(&mut self.in_final_expr, false);
+                self.visit_expr(guard.body());
+                self.in_final_expr = in_final_expr;
+            }
+            self.visit_expr(arm.body);
+        }
     }
 
-    let mut v = V {
-        cx,
-        res: ControlFlow::Continue(Descend::Yes),
-    };
-    expr.visit(&mut v);
-    v.res.is_break()
+    if cx.typeck_results().expr_ty(e).is_never() {
+        Some(RequiresSemi::No)
+    } else if let ExprKind::Block(b, _) = e.kind
+        && !b.targeted_by_break
+        && b.expr.is_none()
+    {
+        // If a block diverges without a final expression then it's type is `!`.
+        None
+    } else {
+        let mut v = V {
+            cx,
+            break_targets: Vec::new(),
+            break_targets_for_result_ty: 0,
+            in_final_expr: true,
+            requires_semi: false,
+            is_never: false,
+        };
+        v.visit_expr(e);
+        v.is_never
+            .then_some(if v.requires_semi && matches!(e.kind, ExprKind::Block(..)) {
+                RequiresSemi::Yes
+            } else {
+                RequiresSemi::No
+            })
+    }
 }
diff --git a/tests/ui/manual_let_else.rs b/tests/ui/manual_let_else.rs
index 27717ab3a73..5d94660ec89 100644
--- a/tests/ui/manual_let_else.rs
+++ b/tests/ui/manual_let_else.rs
@@ -5,7 +5,9 @@
     clippy::let_unit_value,
     clippy::match_single_binding,
     clippy::never_loop,
-    clippy::needless_if
+    clippy::needless_if,
+    clippy::diverging_sub_expression,
+    clippy::single_match
 )]
 #![warn(clippy::manual_let_else)]
 //@no-rustfix
@@ -24,7 +26,7 @@ fn main() {}
 fn fire() {
     let v = if let Some(v_some) = g() { v_some } else { return };
     //~^ ERROR: this could be rewritten as `let...else`
-    //~| NOTE: `-D clippy::manual-let-else` implied by `-D warnings`
+
     let v = if let Some(v_some) = g() {
         //~^ ERROR: this could be rewritten as `let...else`
         v_some
@@ -79,22 +81,76 @@ fn fire() {
         panic!();
     };
 
+    // The final expression will need to be turned into a statement.
+    let v = if let Some(v_some) = g() {
+        //~^ ERROR: this could be rewritten as `let...else`
+        v_some
+    } else {
+        panic!();
+        ()
+    };
+
+    // Even if the result is buried multiple expressions deep.
+    let v = if let Some(v_some) = g() {
+        //~^ ERROR: this could be rewritten as `let...else`
+        v_some
+    } else {
+        panic!();
+        if true {
+            match 0 {
+                0 => (),
+                _ => (),
+            }
+        } else {
+            panic!()
+        }
+    };
+
+    // Or if a break gives the value.
+    let v = if let Some(v_some) = g() {
+        //~^ ERROR: this could be rewritten as `let...else`
+        v_some
+    } else {
+        loop {
+            panic!();
+            break ();
+        }
+    };
+
+    // Even if the break is in a weird position.
+    let v = if let Some(v_some) = g() {
+        //~^ ERROR: this could be rewritten as `let...else`
+        v_some
+    } else {
+        'a: loop {
+            panic!();
+            loop {
+                match 0 {
+                    0 if (return break 'a ()) => {},
+                    _ => {},
+                }
+            }
+        }
+    };
+
     // A match diverges if all branches diverge:
-    // Note: the corresponding let-else requires a ; at the end of the match
-    // as otherwise the type checker does not turn it into a ! type.
     let v = if let Some(v_some) = g() {
         //~^ ERROR: this could be rewritten as `let...else`
         v_some
     } else {
-        match () {
-            _ if panic!() => {},
+        match 0 {
+            0 if true => panic!(),
             _ => panic!(),
-        }
+        };
     };
 
     // An if's expression can cause divergence:
-    let v = if let Some(v_some) = g() { v_some } else { if panic!() {} };
-    //~^ ERROR: this could be rewritten as `let...else`
+    let v = if let Some(v_some) = g() {
+        //~^ ERROR: this could be rewritten as `let...else`
+        v_some
+    } else {
+        if panic!() {};
+    };
 
     // An expression of a match can cause divergence:
     let v = if let Some(v_some) = g() {
@@ -103,7 +159,7 @@ fn fire() {
     } else {
         match panic!() {
             _ => {},
-        }
+        };
     };
 
     // Top level else if
@@ -342,6 +398,43 @@ fn not_fire() {
     } else {
         return;
     };
+
+    // A break that skips the divergent statement will cause the expression to be non-divergent.
+    let _x = if let Some(x) = Some(0) {
+        x
+    } else {
+        'foo: loop {
+            break 'foo 0;
+            panic!();
+        }
+    };
+
+    // Even in inner loops.
+    let _x = if let Some(x) = Some(0) {
+        x
+    } else {
+        'foo: {
+            loop {
+                break 'foo 0;
+            }
+            panic!();
+        }
+    };
+
+    // But a break that can't ever be reached still affects divergence checking.
+    let _x = if let Some(x) = g() {
+        x
+    } else {
+        'foo: {
+            'bar: loop {
+                loop {
+                    break 'bar ();
+                }
+                break 'foo ();
+            }
+            panic!();
+        };
+    };
 }
 
 struct S<T> {
diff --git a/tests/ui/manual_let_else.stderr b/tests/ui/manual_let_else.stderr
index 2b6504a1827..3beaf766efb 100644
--- a/tests/ui/manual_let_else.stderr
+++ b/tests/ui/manual_let_else.stderr
@@ -1,5 +1,5 @@
 error: this could be rewritten as `let...else`
-  --> $DIR/manual_let_else.rs:25:5
+  --> $DIR/manual_let_else.rs:27:5
    |
 LL |     let v = if let Some(v_some) = g() { v_some } else { return };
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider writing: `let Some(v) = g() else { return };`
@@ -8,7 +8,7 @@ LL |     let v = if let Some(v_some) = g() { v_some } else { return };
    = help: to override `-D warnings` add `#[allow(clippy::manual_let_else)]`
 
 error: this could be rewritten as `let...else`
-  --> $DIR/manual_let_else.rs:28:5
+  --> $DIR/manual_let_else.rs:30:5
    |
 LL | /     let v = if let Some(v_some) = g() {
 LL | |
@@ -26,7 +26,7 @@ LL +     };
    |
 
 error: this could be rewritten as `let...else`
-  --> $DIR/manual_let_else.rs:35:5
+  --> $DIR/manual_let_else.rs:37:5
    |
 LL | /     let v = if let Some(v) = g() {
 LL | |
@@ -47,25 +47,25 @@ LL +     };
    |
 
 error: this could be rewritten as `let...else`
-  --> $DIR/manual_let_else.rs:47:9
+  --> $DIR/manual_let_else.rs:49:9
    |
 LL |         let v = if let Some(v_some) = g() { v_some } else { continue };
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider writing: `let Some(v) = g() else { continue };`
 
 error: this could be rewritten as `let...else`
-  --> $DIR/manual_let_else.rs:49:9
+  --> $DIR/manual_let_else.rs:51:9
    |
 LL |         let v = if let Some(v_some) = g() { v_some } else { break };
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider writing: `let Some(v) = g() else { break };`
 
 error: this could be rewritten as `let...else`
-  --> $DIR/manual_let_else.rs:54:5
+  --> $DIR/manual_let_else.rs:56:5
    |
 LL |     let v = if let Some(v_some) = g() { v_some } else { panic!() };
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider writing: `let Some(v) = g() else { panic!() };`
 
 error: this could be rewritten as `let...else`
-  --> $DIR/manual_let_else.rs:58:5
+  --> $DIR/manual_let_else.rs:60:5
    |
 LL | /     let v = if let Some(v_some) = g() {
 LL | |
@@ -83,7 +83,7 @@ LL +     };
    |
 
 error: this could be rewritten as `let...else`
-  --> $DIR/manual_let_else.rs:66:5
+  --> $DIR/manual_let_else.rs:68:5
    |
 LL | /     let v = if let Some(v_some) = g() {
 LL | |
@@ -101,7 +101,7 @@ LL +     };
    |
 
 error: this could be rewritten as `let...else`
-  --> $DIR/manual_let_else.rs:74:5
+  --> $DIR/manual_let_else.rs:76:5
    |
 LL | /     let v = if let Some(v_some) = g() {
 LL | |
@@ -127,6 +127,26 @@ LL | /     let v = if let Some(v_some) = g() {
 LL | |
 LL | |         v_some
 LL | |     } else {
+LL | |         panic!();
+LL | |         ()
+LL | |     };
+   | |______^
+   |
+help: consider writing
+   |
+LL ~     let Some(v) = g() else {
+LL +         panic!();
+LL +         ()
+LL +     };
+   |
+
+error: this could be rewritten as `let...else`
+  --> $DIR/manual_let_else.rs:94:5
+   |
+LL | /     let v = if let Some(v_some) = g() {
+LL | |
+LL | |         v_some
+LL | |     } else {
 ...  |
 LL | |         }
 LL | |     };
@@ -135,21 +155,42 @@ LL | |     };
 help: consider writing
    |
 LL ~     let Some(v) = g() else {
-LL +         match () {
-LL +             _ if panic!() => {},
-LL +             _ => panic!(),
+LL +         panic!();
+LL +         if true {
+LL +             match 0 {
+LL +                 0 => (),
+LL +                 _ => (),
+LL +             }
+LL +         } else {
+LL +             panic!()
 LL +         }
 LL +     };
    |
 
 error: this could be rewritten as `let...else`
-  --> $DIR/manual_let_else.rs:96:5
+  --> $DIR/manual_let_else.rs:110:5
+   |
+LL | /     let v = if let Some(v_some) = g() {
+LL | |
+LL | |         v_some
+LL | |     } else {
+...  |
+LL | |         }
+LL | |     };
+   | |______^
+   |
+help: consider writing
+   |
+LL ~     let Some(v) = g() else {
+LL +         loop {
+LL +             panic!();
+LL +             break ();
+LL +         }
+LL +     };
    |
-LL |     let v = if let Some(v_some) = g() { v_some } else { if panic!() {} };
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider writing: `let Some(v) = g() else { if panic!() {} };`
 
 error: this could be rewritten as `let...else`
-  --> $DIR/manual_let_else.rs:100:5
+  --> $DIR/manual_let_else.rs:121:5
    |
 LL | /     let v = if let Some(v_some) = g() {
 LL | |
@@ -163,14 +204,81 @@ LL | |     };
 help: consider writing
    |
 LL ~     let Some(v) = g() else {
+LL +         'a: loop {
+LL +             panic!();
+LL +             loop {
+LL +                 match 0 {
+LL +                     0 if (return break 'a ()) => {},
+LL +                     _ => {},
+LL +                 }
+LL +             }
+LL +         }
+LL +     };
+   |
+
+error: this could be rewritten as `let...else`
+  --> $DIR/manual_let_else.rs:137:5
+   |
+LL | /     let v = if let Some(v_some) = g() {
+LL | |
+LL | |         v_some
+LL | |     } else {
+...  |
+LL | |         };
+LL | |     };
+   | |______^
+   |
+help: consider writing
+   |
+LL ~     let Some(v) = g() else {
+LL +         match 0 {
+LL +             0 if true => panic!(),
+LL +             _ => panic!(),
+LL +         };
+LL +     };
+   |
+
+error: this could be rewritten as `let...else`
+  --> $DIR/manual_let_else.rs:148:5
+   |
+LL | /     let v = if let Some(v_some) = g() {
+LL | |
+LL | |         v_some
+LL | |     } else {
+LL | |         if panic!() {};
+LL | |     };
+   | |______^
+   |
+help: consider writing
+   |
+LL ~     let Some(v) = g() else {
+LL +         if panic!() {};
+LL +     };
+   |
+
+error: this could be rewritten as `let...else`
+  --> $DIR/manual_let_else.rs:156:5
+   |
+LL | /     let v = if let Some(v_some) = g() {
+LL | |
+LL | |         v_some
+LL | |     } else {
+...  |
+LL | |         };
+LL | |     };
+   | |______^
+   |
+help: consider writing
+   |
+LL ~     let Some(v) = g() else {
 LL +         match panic!() {
 LL +             _ => {},
-LL +         }
+LL +         };
 LL +     };
    |
 
 error: this could be rewritten as `let...else`
-  --> $DIR/manual_let_else.rs:110:5
+  --> $DIR/manual_let_else.rs:166:5
    |
 LL | /     let v = if let Some(v_some) = g() {
 LL | |
@@ -191,7 +299,7 @@ LL +     } };
    |
 
 error: this could be rewritten as `let...else`
-  --> $DIR/manual_let_else.rs:120:5
+  --> $DIR/manual_let_else.rs:176:5
    |
 LL | /     let v = if let Some(v_some) = g() {
 LL | |
@@ -220,7 +328,7 @@ LL +     };
    |
 
 error: this could be rewritten as `let...else`
-  --> $DIR/manual_let_else.rs:138:5
+  --> $DIR/manual_let_else.rs:194:5
    |
 LL | /     let (v, w) = if let Some(v_some) = g().map(|v| (v, 42)) {
 LL | |
@@ -238,7 +346,7 @@ LL +     };
    |
 
 error: this could be rewritten as `let...else`
-  --> $DIR/manual_let_else.rs:146:5
+  --> $DIR/manual_let_else.rs:202:5
    |
 LL | /     let (w, S { v }) = if let (Some(v_some), w_some) = (g().map(|_| S { v: 0 }), 0) {
 LL | |
@@ -256,7 +364,7 @@ LL +     };
    |
 
 error: this could be rewritten as `let...else`
-  --> $DIR/manual_let_else.rs:156:13
+  --> $DIR/manual_let_else.rs:212:13
    |
 LL |             let $n = if let Some(v) = $e { v } else { return };
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider writing: `let Some($n) = g() else { return };`
@@ -267,19 +375,19 @@ LL |     create_binding_if_some!(w, g());
    = note: this error originates in the macro `create_binding_if_some` (in Nightly builds, run with -Z macro-backtrace for more info)
 
 error: this could be rewritten as `let...else`
-  --> $DIR/manual_let_else.rs:165:5
+  --> $DIR/manual_let_else.rs:221:5
    |
 LL |     let v = if let Variant::A(a, 0) = e() { a } else { return };
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider writing: `let Variant::A(v, 0) = e() else { return };`
 
 error: this could be rewritten as `let...else`
-  --> $DIR/manual_let_else.rs:169:5
+  --> $DIR/manual_let_else.rs:225:5
    |
 LL |     let mut v = if let Variant::B(b) = e() { b } else { return };
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider writing: `let Variant::B(mut v) = e() else { return };`
 
 error: this could be rewritten as `let...else`
-  --> $DIR/manual_let_else.rs:174:5
+  --> $DIR/manual_let_else.rs:230:5
    |
 LL | /     let v = if let Ok(Some(Variant::B(b))) | Err(Some(Variant::A(b, _))) = nested {
 LL | |
@@ -297,19 +405,19 @@ LL +     };
    |
 
 error: this could be rewritten as `let...else`
-  --> $DIR/manual_let_else.rs:181:5
+  --> $DIR/manual_let_else.rs:237:5
    |
 LL |     let v = if let Variant::A(.., a) = e() { a } else { return };
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider writing: `let Variant::A(.., v) = e() else { return };`
 
 error: this could be rewritten as `let...else`
-  --> $DIR/manual_let_else.rs:185:5
+  --> $DIR/manual_let_else.rs:241:5
    |
 LL |     let w = if let (Some(v), ()) = (g(), ()) { v } else { return };
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider writing: `let (Some(w), ()) = (g(), ()) else { return };`
 
 error: this could be rewritten as `let...else`
-  --> $DIR/manual_let_else.rs:189:5
+  --> $DIR/manual_let_else.rs:245:5
    |
 LL | /     let w = if let Some(S { v: x }) = Some(S { v: 0 }) {
 LL | |
@@ -327,7 +435,7 @@ LL +     };
    |
 
 error: this could be rewritten as `let...else`
-  --> $DIR/manual_let_else.rs:197:5
+  --> $DIR/manual_let_else.rs:253:5
    |
 LL | /     let v = if let Some(S { v: x }) = Some(S { v: 0 }) {
 LL | |
@@ -345,7 +453,7 @@ LL +     };
    |
 
 error: this could be rewritten as `let...else`
-  --> $DIR/manual_let_else.rs:205:5
+  --> $DIR/manual_let_else.rs:261:5
    |
 LL | /     let (x, S { v }, w) = if let Some(U { v, w, x }) = None::<U<S<()>>> {
 LL | |
@@ -363,7 +471,7 @@ LL +     };
    |
 
 error: this could be rewritten as `let...else`
-  --> $DIR/manual_let_else.rs:322:5
+  --> $DIR/manual_let_else.rs:378:5
    |
 LL | /     let _ = match ff {
 LL | |
@@ -372,5 +480,5 @@ LL | |         _ => macro_call!(),
 LL | |     };
    | |______^ help: consider writing: `let Some(_) = ff else { macro_call!() };`
 
-error: aborting due to 26 previous errors
+error: aborting due to 30 previous errors