Skip to content

Commit d4a6634

Browse files
committed
Auto merge of #11175 - y21:issue11174, r=Manishearth
[`redundant_pattern_matching`]: include guard in suggestion Fixes #11174 changelog: [`redundant_pattern_matching`]: include guard in suggestion
2 parents 9f0cbfd + e7fd44f commit d4a6634

4 files changed

+123
-49
lines changed

clippy_lints/src/matches/redundant_pattern_match.rs

+52-18
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) {
@@ -202,30 +204,58 @@ pub(super) fn check_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, op
202204
if arms.len() == 2 {
203205
let node_pair = (&arms[0].pat.kind, &arms[1].pat.kind);
204206

205-
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) {
206208
let span = is_expn_of(expr.span, "matches").unwrap_or(expr.span.to(op.span));
207209
let result_expr = match &op.kind {
208210
ExprKind::AddrOf(_, _, borrowed) => borrowed,
209211
_ => op,
210212
};
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+
211241
span_lint_and_sugg(
212242
cx,
213243
REDUNDANT_PATTERN_MATCHING,
214244
span,
215245
&format!("redundant pattern matching, consider using `{good_method}`"),
216246
"try",
217-
format!("{}.{good_method}", snippet(cx, result_expr.span, "_")),
247+
sugg,
218248
Applicability::MachineApplicable,
219249
);
220250
}
221251
}
222252
}
223253

224-
fn found_good_method<'a>(
254+
fn found_good_method<'tcx>(
225255
cx: &LateContext<'_>,
226-
arms: &[Arm<'_>],
256+
arms: &'tcx [Arm<'tcx>],
227257
node: (&PatKind<'_>, &PatKind<'_>),
228-
) -> Option<&'a str> {
258+
) -> Option<(&'static str, Option<&'tcx Guard<'tcx>>)> {
229259
match node {
230260
(
231261
PatKind::TupleStruct(ref path_left, patterns_left, _),
@@ -311,7 +341,11 @@ fn get_ident(path: &QPath<'_>) -> Option<rustc_span::symbol::Ident> {
311341
}
312342
}
313343

314-
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>>)> {
315349
if let Some(name) = get_ident(path_left) {
316350
return match name.as_str() {
317351
"Ok" => {
@@ -377,16 +411,16 @@ fn is_pat_variant(cx: &LateContext<'_>, pat: &Pat<'_>, path: &QPath<'_>, expecte
377411
}
378412

379413
#[expect(clippy::too_many_arguments)]
380-
fn find_good_method_for_match<'a>(
414+
fn find_good_method_for_match<'a, 'tcx>(
381415
cx: &LateContext<'_>,
382-
arms: &[Arm<'_>],
416+
arms: &'tcx [Arm<'tcx>],
383417
path_left: &QPath<'_>,
384418
path_right: &QPath<'_>,
385419
expected_item_left: Item,
386420
expected_item_right: Item,
387421
should_be_left: &'a str,
388422
should_be_right: &'a str,
389-
) -> Option<&'a str> {
423+
) -> Option<(&'a str, Option<&'tcx Guard<'tcx>>)> {
390424
let first_pat = arms[0].pat;
391425
let second_pat = arms[1].pat;
392426

@@ -404,22 +438,22 @@ fn find_good_method_for_match<'a>(
404438

405439
match body_node_pair {
406440
(ExprKind::Lit(lit_left), ExprKind::Lit(lit_right)) => match (&lit_left.node, &lit_right.node) {
407-
(LitKind::Bool(true), LitKind::Bool(false)) => Some(should_be_left),
408-
(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())),
409443
_ => None,
410444
},
411445
_ => None,
412446
}
413447
}
414448

415-
fn find_good_method_for_matches_macro<'a>(
449+
fn find_good_method_for_matches_macro<'a, 'tcx>(
416450
cx: &LateContext<'_>,
417-
arms: &[Arm<'_>],
451+
arms: &'tcx [Arm<'tcx>],
418452
path_left: &QPath<'_>,
419453
expected_item_left: Item,
420454
should_be_left: &'a str,
421455
should_be_right: &'a str,
422-
) -> Option<&'a str> {
456+
) -> Option<(&'a str, Option<&'tcx Guard<'tcx>>)> {
423457
let first_pat = arms[0].pat;
424458

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

431465
match body_node_pair {
432466
(ExprKind::Lit(lit_left), ExprKind::Lit(lit_right)) => match (&lit_left.node, &lit_right.node) {
433-
(LitKind::Bool(true), LitKind::Bool(false)) => Some(should_be_left),
434-
(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())),
435469
_ => None,
436470
},
437471
_ => None,

tests/ui/redundant_pattern_matching_option.fixed

+14
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,20 @@
1010
clippy::equatable_if_let,
1111
clippy::if_same_then_else
1212
)]
13+
#![feature(let_chains, if_let_guard)]
14+
15+
fn issue_11174<T>(boolean: bool, maybe_some: Option<T>) -> bool {
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+
};
26+
}
1327

1428
fn main() {
1529
if None::<()>.is_none() {}

tests/ui/redundant_pattern_matching_option.rs

+14
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,20 @@
1010
clippy::equatable_if_let,
1111
clippy::if_same_then_else
1212
)]
13+
#![feature(let_chains, if_let_guard)]
14+
15+
fn issue_11174<T>(boolean: bool, maybe_some: Option<T>) -> bool {
16+
matches!(maybe_some, None if !boolean)
17+
}
18+
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+
}
1327

1428
fn main() {
1529
if let None = None::<()> {}

0 commit comments

Comments
 (0)