fix(match_like_matches_macro): FP with guards containing if let (#15876)

Fixes https://github.com/rust-lang/rust-clippy/issues/15841

changelog: [`match_like_matches_macro`]: FP with guards containing `if
let`
This commit is contained in:
Alex Macleod
2025-12-10 15:35:51 +00:00
committed by GitHub
5 changed files with 243 additions and 33 deletions
+33 -19
View File
@@ -1,7 +1,8 @@
//! Lint a `match` or `if let .. { .. } else { .. }` expr that could be replaced by `matches!`
use super::REDUNDANT_PATTERN_MATCHING;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::higher::has_let_expr;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{is_lint_allowed, is_wild, span_contains_comment};
use rustc_ast::LitKind;
@@ -43,18 +44,23 @@ pub(crate) fn check_if_let<'tcx>(
{
ex_new = ex_inner;
}
span_lint_and_sugg(
span_lint_and_then(
cx,
MATCH_LIKE_MATCHES_MACRO,
expr.span,
"if let .. else expression looks like `matches!` macro",
"try",
format!(
"{}matches!({}, {pat})",
if b0 { "" } else { "!" },
snippet_with_applicability(cx, ex_new.span, "..", &mut applicability),
),
applicability,
"`if let .. else` expression looks like `matches!` macro",
|diag| {
diag.span_suggestion_verbose(
expr.span,
"use `matches!` directly",
format!(
"{}matches!({}, {pat})",
if b0 { "" } else { "!" },
snippet_with_applicability(cx, ex_new.span, "..", &mut applicability),
),
applicability,
);
},
);
}
}
@@ -87,7 +93,10 @@ pub(super) fn check_match<'tcx>(
// ```rs
// matches!(e, Either::Left $(if $guard)|+)
// ```
middle_arms.is_empty()
//
// But if the guard _is_ present, it may not be an `if-let` guard, as `matches!` doesn't
// support these (currently?)
(middle_arms.is_empty() && first_arm.guard.is_none_or(|g| !has_let_expr(g)))
// - (added in #6216) There are middle arms
//
@@ -169,18 +178,23 @@ pub(super) fn check_match<'tcx>(
{
ex_new = ex_inner;
}
span_lint_and_sugg(
span_lint_and_then(
cx,
MATCH_LIKE_MATCHES_MACRO,
e.span,
"match expression looks like `matches!` macro",
"try",
format!(
"{}matches!({}, {pat_and_guard})",
if b0 { "" } else { "!" },
snippet_with_applicability(cx, ex_new.span, "..", &mut applicability),
),
applicability,
|diag| {
diag.span_suggestion_verbose(
e.span,
"use `matches!` directly",
format!(
"{}matches!({}, {pat_and_guard})",
if b0 { "" } else { "!" },
snippet_with_applicability(cx, ex_new.span, "..", &mut applicability),
),
applicability,
);
},
);
true
} else {
+7
View File
@@ -223,3 +223,10 @@ fn msrv_1_42() {
let _y = matches!(Some(5), Some(0));
//~^^^^ match_like_matches_macro
}
#[expect(clippy::option_option)]
fn issue15841(opt: Option<Option<Option<i32>>>, value: i32) {
// Lint: no if-let _in the guard_
let _ = matches!(opt, Some(first) if (if let Some(second) = first { true } else { todo!() }));
//~^^^^ match_like_matches_macro
}
+10
View File
@@ -267,3 +267,13 @@ fn msrv_1_42() {
};
//~^^^^ match_like_matches_macro
}
#[expect(clippy::option_option)]
fn issue15841(opt: Option<Option<Option<i32>>>, value: i32) {
// Lint: no if-let _in the guard_
let _ = match opt {
Some(first) if (if let Some(second) = first { true } else { todo!() }) => true,
_ => false,
};
//~^^^^ match_like_matches_macro
}
+142 -14
View File
@@ -6,10 +6,18 @@ LL | let _y = match x {
LL | | Some(0) => true,
LL | | _ => false,
LL | | };
| |_____^ help: try: `matches!(x, Some(0))`
| |_____^
|
= note: `-D clippy::match-like-matches-macro` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::match_like_matches_macro)]`
help: use `matches!` directly
|
LL - let _y = match x {
LL - Some(0) => true,
LL - _ => false,
LL - };
LL + let _y = matches!(x, Some(0));
|
error: redundant pattern matching, consider using `is_some()`
--> tests/ui/match_like_matches_macro.rs:20:14
@@ -42,13 +50,28 @@ LL | let _zz = match x {
LL | | Some(r) if r == 0 => false,
LL | | _ => true,
LL | | };
| |_____^ help: try: `!matches!(x, Some(r) if r == 0)`
| |_____^
|
help: use `matches!` directly
|
LL - let _zz = match x {
LL - Some(r) if r == 0 => false,
LL - _ => true,
LL - };
LL + let _zz = !matches!(x, Some(r) if r == 0);
|
error: if let .. else expression looks like `matches!` macro
error: `if let .. else` expression looks like `matches!` macro
--> tests/ui/match_like_matches_macro.rs:41:16
|
LL | let _zzz = if let Some(5) = x { true } else { false };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `matches!(x, Some(5))`
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: use `matches!` directly
|
LL - let _zzz = if let Some(5) = x { true } else { false };
LL + let _zzz = matches!(x, Some(5));
|
error: match expression looks like `matches!` macro
--> tests/ui/match_like_matches_macro.rs:66:20
@@ -59,7 +82,17 @@ LL | | E::A(_) => true,
LL | | E::B(_) => true,
LL | | _ => false,
LL | | };
| |_________^ help: try: `matches!(x, E::A(_) | E::B(_))`
| |_________^
|
help: use `matches!` directly
|
LL - let _ans = match x {
LL - E::A(_) => true,
LL - E::B(_) => true,
LL - _ => false,
LL - };
LL + let _ans = matches!(x, E::A(_) | E::B(_));
|
error: match expression looks like `matches!` macro
--> tests/ui/match_like_matches_macro.rs:77:20
@@ -71,7 +104,19 @@ LL | | true
... |
LL | | _ => false,
LL | | };
| |_________^ help: try: `matches!(x, E::A(_) | E::B(_))`
| |_________^
|
help: use `matches!` directly
|
LL - let _ans = match x {
LL - E::A(_) => {
LL - true
LL - }
LL - E::B(_) => true,
LL - _ => false,
LL - };
LL + let _ans = matches!(x, E::A(_) | E::B(_));
|
error: match expression looks like `matches!` macro
--> tests/ui/match_like_matches_macro.rs:88:20
@@ -82,7 +127,17 @@ LL | | E::B(_) => false,
LL | | E::C => false,
LL | | _ => true,
LL | | };
| |_________^ help: try: `!matches!(x, E::B(_) | E::C)`
| |_________^
|
help: use `matches!` directly
|
LL - let _ans = match x {
LL - E::B(_) => false,
LL - E::C => false,
LL - _ => true,
LL - };
LL + let _ans = !matches!(x, E::B(_) | E::C);
|
error: match expression looks like `matches!` macro
--> tests/ui/match_like_matches_macro.rs:149:18
@@ -92,7 +147,16 @@ LL | let _z = match &z {
LL | | Some(3) => true,
LL | | _ => false,
LL | | };
| |_________^ help: try: `matches!(z, Some(3))`
| |_________^
|
help: use `matches!` directly
|
LL - let _z = match &z {
LL - Some(3) => true,
LL - _ => false,
LL - };
LL + let _z = matches!(z, Some(3));
|
error: match expression looks like `matches!` macro
--> tests/ui/match_like_matches_macro.rs:159:18
@@ -102,7 +166,16 @@ LL | let _z = match &z {
LL | | Some(3) => true,
LL | | _ => false,
LL | | };
| |_________^ help: try: `matches!(&z, Some(3))`
| |_________^
|
help: use `matches!` directly
|
LL - let _z = match &z {
LL - Some(3) => true,
LL - _ => false,
LL - };
LL + let _z = matches!(&z, Some(3));
|
error: match expression looks like `matches!` macro
--> tests/ui/match_like_matches_macro.rs:177:21
@@ -112,7 +185,16 @@ LL | let _ = match &z {
LL | | AnEnum::X => true,
LL | | _ => false,
LL | | };
| |_____________^ help: try: `matches!(&z, AnEnum::X)`
| |_____________^
|
help: use `matches!` directly
|
LL - let _ = match &z {
LL - AnEnum::X => true,
LL - _ => false,
LL - };
LL + let _ = matches!(&z, AnEnum::X);
|
error: match expression looks like `matches!` macro
--> tests/ui/match_like_matches_macro.rs:192:20
@@ -122,7 +204,16 @@ LL | let _res = match &val {
LL | | &Some(ref _a) => true,
LL | | _ => false,
LL | | };
| |_________^ help: try: `matches!(&val, &Some(ref _a))`
| |_________^
|
help: use `matches!` directly
|
LL - let _res = match &val {
LL - &Some(ref _a) => true,
LL - _ => false,
LL - };
LL + let _res = matches!(&val, &Some(ref _a));
|
error: match expression looks like `matches!` macro
--> tests/ui/match_like_matches_macro.rs:205:20
@@ -132,7 +223,16 @@ LL | let _res = match &val {
LL | | &Some(ref _a) => true,
LL | | _ => false,
LL | | };
| |_________^ help: try: `matches!(&val, &Some(ref _a))`
| |_________^
|
help: use `matches!` directly
|
LL - let _res = match &val {
LL - &Some(ref _a) => true,
LL - _ => false,
LL - };
LL + let _res = matches!(&val, &Some(ref _a));
|
error: match expression looks like `matches!` macro
--> tests/ui/match_like_matches_macro.rs:264:14
@@ -142,7 +242,35 @@ LL | let _y = match Some(5) {
LL | | Some(0) => true,
LL | | _ => false,
LL | | };
| |_____^ help: try: `matches!(Some(5), Some(0))`
| |_____^
|
help: use `matches!` directly
|
LL - let _y = match Some(5) {
LL - Some(0) => true,
LL - _ => false,
LL - };
LL + let _y = matches!(Some(5), Some(0));
|
error: aborting due to 14 previous errors
error: match expression looks like `matches!` macro
--> tests/ui/match_like_matches_macro.rs:274:13
|
LL | let _ = match opt {
| _____________^
LL | | Some(first) if (if let Some(second) = first { true } else { todo!() }) => true,
LL | | _ => false,
LL | | };
| |_____^
|
help: use `matches!` directly
|
LL - let _ = match opt {
LL - Some(first) if (if let Some(second) = first { true } else { todo!() }) => true,
LL - _ => false,
LL - };
LL + let _ = matches!(opt, Some(first) if (if let Some(second) = first { true } else { todo!() }));
|
error: aborting due to 15 previous errors
@@ -0,0 +1,51 @@
//@check-pass
#![warn(clippy::match_like_matches_macro)]
#![feature(if_let_guard)]
#[expect(clippy::option_option)]
fn issue15841(opt: Option<Option<Option<i32>>>, value: i32) {
let _ = match opt {
Some(first)
if let Some(second) = first
&& let Some(third) = second
&& third == value =>
{
true
},
_ => false,
};
// if-let is the second if
let _ = match opt {
Some(first)
if first.is_some()
&& let Some(second) = first =>
{
true
},
_ => false,
};
// if-let is the third if
let _ = match opt {
Some(first)
if first.is_some()
&& first.is_none()
&& let Some(second) = first =>
{
true
},
_ => false,
};
// don't get confused by `or`s
let _ = match opt {
Some(first)
if (first.is_some() || first.is_none())
&& let Some(second) = first =>
{
true
},
_ => false,
};
}