Auto merge of #11787 - Jarcho:divergence_check, r=dswij

Fixes to `manual_let_else`'s divergence check

A few changes to the divergence check in `manual_let_else` and moves it the implementation to `clippy_utils` since it's generally useful:
* Handle internal `break` and `continue` expressions.
    e.g. The first loop is divergent, but the second is not.
    ```rust
    {
        loop {
            break 'outer;
        };
    }
    {
        loop {
            break;
        };
    }
    ```
* Match rust's definition of divergence which is defined via the type system.
    e.g. The following is not considered divergent by rustc as the inner block has a result type of `()`:
    ```rust
    {
        'a: {
            panic!();
            break 'a;
        };
    }
    ```
* Handle when adding a single semicolon would make the expression divergent.
    e.g. The following would be a divergent if a semicolon were added after the `if` expression:
    ```rust
    { if panic!() { 0 } else { 1 } }
    ```

changelog: None
This commit is contained in:
bors 2023-11-12 15:44:13 +00:00
commit 6a15f3bd49
4 changed files with 503 additions and 158 deletions

View file

@ -5,18 +5,15 @@ use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::higher::IfLetOrMatch;
use clippy_utils::source::snippet_with_context;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::visitors::{Descend, Visitable};
use clippy_utils::{is_lint_allowed, pat_and_expr_can_be_question_mark, peel_blocks};
use clippy_utils::{is_lint_allowed, is_never_expr, pat_and_expr_can_be_question_mark, peel_blocks};
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_expr, Visitor};
use rustc_hir::{Expr, ExprKind, HirId, ItemId, Local, MatchSource, Pat, PatKind, QPath, Stmt, StmtKind, Ty};
use rustc_hir::{Expr, ExprKind, MatchSource, Pat, PatKind, QPath, Stmt, StmtKind};
use rustc_lint::{LateContext, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_session::declare_tool_lint;
use rustc_span::symbol::{sym, Symbol};
use rustc_span::Span;
use std::ops::ControlFlow;
use std::slice;
declare_clippy_lint! {
@ -51,7 +48,7 @@ declare_clippy_lint! {
}
impl<'tcx> QuestionMark {
pub(crate) fn check_manual_let_else(&mut self, cx: &LateContext<'_>, stmt: &'tcx Stmt<'tcx>) {
pub(crate) fn check_manual_let_else(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'tcx>) {
if !self.msrv.meets(msrvs::LET_ELSE) || in_external_macro(cx.sess(), stmt.span) {
return;
}
@ -67,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
&& expr_diverges(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())
{
@ -91,10 +88,9 @@ impl<'tcx> QuestionMark {
return;
}
let check_types = self.matches_behaviour == MatchLintBehaviour::WellKnownTypes;
let diverging_arm_opt = arms
.iter()
.enumerate()
.find(|(_, arm)| expr_diverges(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;
};
@ -272,104 +268,6 @@ fn replace_in_pattern(
sn_pat.into_owned()
}
/// Check whether an expression is divergent. May give false negatives.
fn expr_diverges(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
struct V<'cx, 'tcx> {
cx: &'cx LateContext<'tcx>,
res: ControlFlow<(), Descend>,
}
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() {
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)
}
},
ExprKind::MethodCall(..) => {
if is_never(self.cx, e) {
ControlFlow::Break(())
} else {
ControlFlow::Continue(Descend::Yes)
}
},
ExprKind::If(if_expr, if_then, if_else) => {
let else_diverges = if_else.map_or(false, |ex| expr_diverges(self.cx, ex));
let diverges =
expr_diverges(self.cx, if_expr) || (else_diverges && expr_diverges(self.cx, if_then));
if diverges {
ControlFlow::Break(())
} else {
ControlFlow::Continue(Descend::No)
}
},
ExprKind::Match(match_expr, match_arms, _) => {
let diverges = expr_diverges(self.cx, match_expr)
|| match_arms.iter().all(|arm| {
let guard_diverges = arm.guard.as_ref().map_or(false, |g| expr_diverges(self.cx, g.body()));
guard_diverges || expr_diverges(self.cx, arm.body)
});
if diverges {
ControlFlow::Break(())
} else {
ControlFlow::Continue(Descend::No)
}
},
// 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_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);
}
}
// Avoid unnecessary `walk_*` calls.
fn visit_ty(&mut self, _: &'tcx 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) {}
}
let mut v = V {
cx,
res: ControlFlow::Continue(Descend::Yes),
};
expr.visit(&mut v);
v.res.is_break()
}
fn pat_allowed_for_else(cx: &LateContext<'_>, pat: &'_ Pat<'_>, check_types: bool) -> bool {
// Check whether the pattern contains any bindings, as the
// binding might potentially be used in the body.

View file

@ -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;
@ -2973,3 +2974,248 @@ pub fn pat_is_wild<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx PatKind<'_>, body: i
_ => false,
}
}
#[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>,
break_targets: Vec<BreakTarget>,
break_targets_for_result_ty: u32,
in_final_expr: bool,
requires_semi: bool,
is_never: bool,
}
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;
}
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::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 {
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::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 {
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::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 {
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();
},
}
}
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, 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;
}
}
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);
}
}
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
})
}
}

View file

@ -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!();
};
// 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.
// 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 {
match () {
_ if panic!() => {},
_ => panic!(),
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:
let v = if let Some(v_some) = g() {
//~^ ERROR: this could be rewritten as `let...else`
v_some
} else {
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> {

View file

@ -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,29 +127,21 @@ LL | / let v = if let Some(v_some) = g() {
LL | |
LL | | v_some
LL | | } else {
... |
LL | | }
LL | | panic!();
LL | | ()
LL | | };
| |______^
|
help: consider writing
|
LL ~ let Some(v) = g() else {
LL + match () {
LL + _ if panic!() => {},
LL + _ => panic!(),
LL + }
LL + panic!();
LL + ()
LL + };
|
error: this could be rewritten as `let...else`
--> $DIR/manual_let_else.rs:96:5
|
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:94:5
|
LL | / let v = if let Some(v_some) = g() {
LL | |
@ -163,8 +155,14 @@ LL | | };
help: consider writing
|
LL ~ let Some(v) = g() else {
LL + match panic!() {
LL + _ => {},
LL + panic!();
LL + if true {
LL + match 0 {
LL + 0 => (),
LL + _ => (),
LL + }
LL + } else {
LL + panic!()
LL + }
LL + };
|
@ -175,6 +173,116 @@ error: this could be rewritten as `let...else`
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 + };
|
error: this could be rewritten as `let...else`
--> $DIR/manual_let_else.rs:121: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 + '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 + };
|
error: this could be rewritten as `let...else`
--> $DIR/manual_let_else.rs:166:5
|
LL | / let v = if let Some(v_some) = g() {
LL | |
LL | | v_some
LL | | } else if true {
... |
LL | | panic!("diverge");
@ -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