Skip to content

Commit e7fd44f

Browse files
committed
add guard to suggestion instead of not linting
1 parent c26801e commit e7fd44f

File tree

4 files changed

+118
-55
lines changed

4 files changed

+118
-55
lines changed

clippy_lints/src/matches/redundant_pattern_match.rs

Lines changed: 54 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,20 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
33
use clippy_utils::source::{snippet, walk_span_to_context};
44
use clippy_utils::sugg::Sugg;
55
use clippy_utils::ty::{is_type_diagnostic_item, needs_ordered_drop};
6-
use clippy_utils::visitors::any_temporaries_need_ordered_drop;
6+
use clippy_utils::visitors::{any_temporaries_need_ordered_drop, for_each_expr};
77
use clippy_utils::{higher, is_expn_of, is_trait_method};
88
use if_chain::if_chain;
99
use rustc_ast::ast::LitKind;
1010
use rustc_errors::Applicability;
1111
use rustc_hir::def::{DefKind, Res};
1212
use rustc_hir::LangItem::{self, OptionNone, OptionSome, PollPending, PollReady, ResultErr, ResultOk};
13-
use rustc_hir::{Arm, Expr, ExprKind, Node, Pat, PatKind, QPath, UnOp};
13+
use rustc_hir::{Arm, Expr, ExprKind, Guard, Node, Pat, PatKind, QPath, UnOp};
1414
use rustc_lint::LateContext;
1515
use rustc_middle::ty::subst::GenericArgKind;
1616
use rustc_middle::ty::{self, Ty};
1717
use rustc_span::{sym, Symbol};
18+
use std::fmt::Write;
19+
use std::ops::ControlFlow;
1820

1921
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
2022
if let Some(higher::WhileLet { let_pat, let_expr, .. }) = higher::WhileLet::hir(expr) {
@@ -199,36 +201,61 @@ fn find_sugg_for_if_let<'tcx>(
199201
}
200202

201203
pub(super) fn check_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, op: &Expr<'_>, arms: &[Arm<'_>]) {
202-
if let [arm1, arm2] = arms
203-
&& arm1.guard.is_none()
204-
&& arm2.guard.is_none()
205-
{
206-
let node_pair = (&arm1.pat.kind, &arm2.pat.kind);
204+
if arms.len() == 2 {
205+
let node_pair = (&arms[0].pat.kind, &arms[1].pat.kind);
207206

208-
if let Some(good_method) = found_good_method(cx, arms, node_pair) {
207+
if let Some((good_method, maybe_guard)) = found_good_method(cx, arms, node_pair) {
209208
let span = is_expn_of(expr.span, "matches").unwrap_or(expr.span.to(op.span));
210209
let result_expr = match &op.kind {
211210
ExprKind::AddrOf(_, _, borrowed) => borrowed,
212211
_ => op,
213212
};
213+
let mut sugg = format!("{}.{good_method}", snippet(cx, result_expr.span, "_"));
214+
215+
if let Some(guard) = maybe_guard {
216+
let Guard::If(guard) = *guard else { return }; // `...is_none() && let ...` is a syntax error
217+
218+
// wow, the HIR for match guards in `PAT if let PAT = expr && expr => ...` is annoying!
219+
// `guard` here is `Guard::If` with the let expression somewhere deep in the tree of exprs,
220+
// counter to the intuition that it should be `Guard::IfLet`, so we need another check
221+
// to see that there aren't any let chains anywhere in the guard, as that would break
222+
// if we suggest `t.is_none() && (let X = y && z)` for:
223+
// `match t { None if let X = y && z => true, _ => false }`
224+
let has_nested_let_chain = for_each_expr(guard, |expr| {
225+
if matches!(expr.kind, ExprKind::Let(..)) {
226+
ControlFlow::Break(())
227+
} else {
228+
ControlFlow::Continue(())
229+
}
230+
})
231+
.is_some();
232+
233+
if has_nested_let_chain {
234+
return;
235+
}
236+
237+
let guard = Sugg::hir(cx, guard, "..");
238+
let _ = write!(sugg, " && {}", guard.maybe_par());
239+
}
240+
214241
span_lint_and_sugg(
215242
cx,
216243
REDUNDANT_PATTERN_MATCHING,
217244
span,
218245
&format!("redundant pattern matching, consider using `{good_method}`"),
219246
"try",
220-
format!("{}.{good_method}", snippet(cx, result_expr.span, "_")),
247+
sugg,
221248
Applicability::MachineApplicable,
222249
);
223250
}
224251
}
225252
}
226253

227-
fn found_good_method<'a>(
254+
fn found_good_method<'tcx>(
228255
cx: &LateContext<'_>,
229-
arms: &[Arm<'_>],
256+
arms: &'tcx [Arm<'tcx>],
230257
node: (&PatKind<'_>, &PatKind<'_>),
231-
) -> Option<&'a str> {
258+
) -> Option<(&'static str, Option<&'tcx Guard<'tcx>>)> {
232259
match node {
233260
(
234261
PatKind::TupleStruct(ref path_left, patterns_left, _),
@@ -314,7 +341,11 @@ fn get_ident(path: &QPath<'_>) -> Option<rustc_span::symbol::Ident> {
314341
}
315342
}
316343

317-
fn get_good_method<'a>(cx: &LateContext<'_>, arms: &[Arm<'_>], path_left: &QPath<'_>) -> Option<&'a str> {
344+
fn get_good_method<'tcx>(
345+
cx: &LateContext<'_>,
346+
arms: &'tcx [Arm<'tcx>],
347+
path_left: &QPath<'_>,
348+
) -> Option<(&'static str, Option<&'tcx Guard<'tcx>>)> {
318349
if let Some(name) = get_ident(path_left) {
319350
return match name.as_str() {
320351
"Ok" => {
@@ -380,16 +411,16 @@ fn is_pat_variant(cx: &LateContext<'_>, pat: &Pat<'_>, path: &QPath<'_>, expecte
380411
}
381412

382413
#[expect(clippy::too_many_arguments)]
383-
fn find_good_method_for_match<'a>(
414+
fn find_good_method_for_match<'a, 'tcx>(
384415
cx: &LateContext<'_>,
385-
arms: &[Arm<'_>],
416+
arms: &'tcx [Arm<'tcx>],
386417
path_left: &QPath<'_>,
387418
path_right: &QPath<'_>,
388419
expected_item_left: Item,
389420
expected_item_right: Item,
390421
should_be_left: &'a str,
391422
should_be_right: &'a str,
392-
) -> Option<&'a str> {
423+
) -> Option<(&'a str, Option<&'tcx Guard<'tcx>>)> {
393424
let first_pat = arms[0].pat;
394425
let second_pat = arms[1].pat;
395426

@@ -407,22 +438,22 @@ fn find_good_method_for_match<'a>(
407438

408439
match body_node_pair {
409440
(ExprKind::Lit(lit_left), ExprKind::Lit(lit_right)) => match (&lit_left.node, &lit_right.node) {
410-
(LitKind::Bool(true), LitKind::Bool(false)) => Some(should_be_left),
411-
(LitKind::Bool(false), LitKind::Bool(true)) => Some(should_be_right),
441+
(LitKind::Bool(true), LitKind::Bool(false)) => Some((should_be_left, arms[0].guard.as_ref())),
442+
(LitKind::Bool(false), LitKind::Bool(true)) => Some((should_be_right, arms[1].guard.as_ref())),
412443
_ => None,
413444
},
414445
_ => None,
415446
}
416447
}
417448

418-
fn find_good_method_for_matches_macro<'a>(
449+
fn find_good_method_for_matches_macro<'a, 'tcx>(
419450
cx: &LateContext<'_>,
420-
arms: &[Arm<'_>],
451+
arms: &'tcx [Arm<'tcx>],
421452
path_left: &QPath<'_>,
422453
expected_item_left: Item,
423454
should_be_left: &'a str,
424455
should_be_right: &'a str,
425-
) -> Option<&'a str> {
456+
) -> Option<(&'a str, Option<&'tcx Guard<'tcx>>)> {
426457
let first_pat = arms[0].pat;
427458

428459
let body_node_pair = if is_pat_variant(cx, first_pat, path_left, expected_item_left) {
@@ -433,8 +464,8 @@ fn find_good_method_for_matches_macro<'a>(
433464

434465
match body_node_pair {
435466
(ExprKind::Lit(lit_left), ExprKind::Lit(lit_right)) => match (&lit_left.node, &lit_right.node) {
436-
(LitKind::Bool(true), LitKind::Bool(false)) => Some(should_be_left),
437-
(LitKind::Bool(false), LitKind::Bool(true)) => Some(should_be_right),
467+
(LitKind::Bool(true), LitKind::Bool(false)) => Some((should_be_left, arms[0].guard.as_ref())),
468+
(LitKind::Bool(false), LitKind::Bool(true)) => Some((should_be_right, arms[1].guard.as_ref())),
438469
_ => None,
439470
},
440471
_ => None,

tests/ui/redundant_pattern_matching_option.fixed

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,19 @@
1010
clippy::equatable_if_let,
1111
clippy::if_same_then_else
1212
)]
13+
#![feature(let_chains, if_let_guard)]
1314

1415
fn issue_11174<T>(boolean: bool, maybe_some: Option<T>) -> bool {
15-
matches!(maybe_some, None if !boolean)
16+
maybe_some.is_none() && (!boolean)
17+
}
18+
19+
fn issue_11174_edge_cases<T>(boolean: bool, boolean2: bool, maybe_some: Option<T>) {
20+
let _ = maybe_some.is_none() && (boolean || boolean2); // guard needs parentheses
21+
let _ = match maybe_some { // can't use `matches!` here
22+
// because `expr` metavars in macros don't allow let exprs
23+
None if let Some(x) = Some(0) && x > 5 => true,
24+
_ => false
25+
};
1626
}
1727

1828
fn main() {

tests/ui/redundant_pattern_matching_option.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,21 @@
1010
clippy::equatable_if_let,
1111
clippy::if_same_then_else
1212
)]
13+
#![feature(let_chains, if_let_guard)]
1314

1415
fn issue_11174<T>(boolean: bool, maybe_some: Option<T>) -> bool {
1516
matches!(maybe_some, None if !boolean)
1617
}
1718

19+
fn issue_11174_edge_cases<T>(boolean: bool, boolean2: bool, maybe_some: Option<T>) {
20+
let _ = matches!(maybe_some, None if boolean || boolean2); // guard needs parentheses
21+
let _ = match maybe_some { // can't use `matches!` here
22+
// because `expr` metavars in macros don't allow let exprs
23+
None if let Some(x) = Some(0) && x > 5 => true,
24+
_ => false
25+
};
26+
}
27+
1828
fn main() {
1929
if let None = None::<()> {}
2030

0 commit comments

Comments
 (0)