From 6d0fafd8493f4250e7394b832391d6cc5c507fcf Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Thu, 9 Oct 2025 11:15:05 +0200 Subject: [PATCH 01/10] clean-up --- .../src/matches/match_like_matches.rs | 43 ++++++++++--------- ...o.fixed => match_like_matches_macro.fixed} | 5 +-- ...s_macro.rs => match_like_matches_macro.rs} | 5 +-- ...stderr => match_like_matches_macro.stderr} | 28 ++++++------ 4 files changed, 41 insertions(+), 40 deletions(-) rename tests/ui/{match_expr_like_matches_macro.fixed => match_like_matches_macro.fixed} (97%) rename tests/ui/{match_expr_like_matches_macro.rs => match_like_matches_macro.rs} (97%) rename tests/ui/{match_expr_like_matches_macro.stderr => match_like_matches_macro.stderr} (84%) diff --git a/clippy_lints/src/matches/match_like_matches.rs b/clippy_lints/src/matches/match_like_matches.rs index 5816da5695eb..8257c39c90f0 100644 --- a/clippy_lints/src/matches/match_like_matches.rs +++ b/clippy_lints/src/matches/match_like_matches.rs @@ -1,3 +1,5 @@ +//! 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::source::snippet_with_applicability; @@ -11,7 +13,6 @@ use rustc_span::source_map::Spanned; use super::MATCH_LIKE_MATCHES_MACRO; -/// Lint a `match` or `if let .. { .. } else { .. }` expr that could be replaced by `matches!` pub(crate) fn check_if_let<'tcx>( cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, @@ -23,10 +24,11 @@ pub(crate) fn check_if_let<'tcx>( find_matches_sugg( cx, let_expr, - IntoIterator::into_iter([ + [ (&[][..], Some(let_pat), then_expr, None), (&[][..], None, else_expr, None), - ]), + ] + .into_iter(), expr, true, ); @@ -52,7 +54,7 @@ pub(super) fn check_match<'tcx>( fn find_matches_sugg<'a, 'b, I>( cx: &LateContext<'_>, ex: &Expr<'_>, - mut iter: I, + mut arms: I, expr: &Expr<'_>, is_if_let: bool, ) -> bool @@ -64,17 +66,17 @@ where + Iterator>, &'a Expr<'b>, Option<&'a Expr<'b>>)>, { if !span_contains_comment(cx.sess().source_map(), expr.span) - && iter.len() >= 2 + && arms.len() >= 2 && cx.typeck_results().expr_ty(expr).is_bool() - && let Some((_, last_pat_opt, last_expr, _)) = iter.next_back() - && let iter_without_last = iter.clone() - && let Some((first_attrs, _, first_expr, first_guard)) = iter.next() - && let Some(b0) = find_bool_lit(&first_expr.kind) - && let Some(b1) = find_bool_lit(&last_expr.kind) + && let Some((_, last_pat_opt, last_expr, _)) = arms.next_back() + && let arms_without_last = arms.clone() + && let Some((first_attrs, _, first_expr, first_guard)) = arms.next() + && let Some(b0) = find_bool_lit(first_expr) + && let Some(b1) = find_bool_lit(last_expr) && b0 != b1 - && (first_guard.is_none() || iter.len() == 0) + && (first_guard.is_none() || arms.len() == 0) && first_attrs.is_empty() - && iter.all(|arm| find_bool_lit(&arm.2.kind).is_some_and(|b| b == b0) && arm.3.is_none() && arm.0.is_empty()) + && arms.all(|(attrs, _, expr, guard)| attrs.is_empty() && guard.is_none() && find_bool_lit(expr) == Some(b0)) { if let Some(last_pat) = last_pat_opt && !is_wild(last_pat) @@ -82,10 +84,10 @@ where return false; } - for arm in iter_without_last.clone() { + for arm in arms_without_last.clone() { if let Some(pat) = arm.1 && !is_lint_allowed(cx, REDUNDANT_PATTERN_MATCHING, pat.hir_id) - && is_some(pat.kind) + && is_some_wild(pat.kind) { return false; } @@ -96,7 +98,7 @@ where let mut applicability = Applicability::MaybeIncorrect; let pat = { use itertools::Itertools as _; - iter_without_last + arms_without_last .filter_map(|arm| { let pat_span = arm.1?.span; Some(snippet_with_applicability(cx, pat_span, "..", &mut applicability)) @@ -142,11 +144,11 @@ where } /// Extract a `bool` or `{ bool }` -fn find_bool_lit(ex: &ExprKind<'_>) -> Option { - match ex { +fn find_bool_lit(ex: &Expr<'_>) -> Option { + match ex.kind { ExprKind::Lit(Spanned { node: LitKind::Bool(b), .. - }) => Some(*b), + }) => Some(b), ExprKind::Block( rustc_hir::Block { stmts: [], @@ -168,8 +170,9 @@ fn find_bool_lit(ex: &ExprKind<'_>) -> Option { } } -fn is_some(path_kind: PatKind<'_>) -> bool { - match path_kind { +/// Checks whether a pattern is `Some(_)` +fn is_some_wild(pat_kind: PatKind<'_>) -> bool { + match pat_kind { PatKind::TupleStruct(QPath::Resolved(_, path), [first, ..], _) if is_wild(first) => { let name = path.segments[0].ident; name.name == rustc_span::sym::Some diff --git a/tests/ui/match_expr_like_matches_macro.fixed b/tests/ui/match_like_matches_macro.fixed similarity index 97% rename from tests/ui/match_expr_like_matches_macro.fixed rename to tests/ui/match_like_matches_macro.fixed index 8530ab16bfd7..a1c95e8a94f1 100644 --- a/tests/ui/match_expr_like_matches_macro.fixed +++ b/tests/ui/match_like_matches_macro.fixed @@ -1,7 +1,6 @@ #![warn(clippy::match_like_matches_macro)] #![allow( unreachable_patterns, - dead_code, clippy::equatable_if_let, clippy::needless_borrowed_reference, clippy::redundant_guards @@ -14,11 +13,11 @@ fn main() { let _y = matches!(x, Some(0)); //~^^^^ match_like_matches_macro - // Lint + // No lint: covered by `redundant_pattern_matching` let _w = x.is_some(); //~^^^^ redundant_pattern_matching - // Turn into is_none + // No lint: covered by `redundant_pattern_matching` let _z = x.is_none(); //~^^^^ redundant_pattern_matching diff --git a/tests/ui/match_expr_like_matches_macro.rs b/tests/ui/match_like_matches_macro.rs similarity index 97% rename from tests/ui/match_expr_like_matches_macro.rs rename to tests/ui/match_like_matches_macro.rs index 81017936889e..eb419ba5bf8d 100644 --- a/tests/ui/match_expr_like_matches_macro.rs +++ b/tests/ui/match_like_matches_macro.rs @@ -1,7 +1,6 @@ #![warn(clippy::match_like_matches_macro)] #![allow( unreachable_patterns, - dead_code, clippy::equatable_if_let, clippy::needless_borrowed_reference, clippy::redundant_guards @@ -17,14 +16,14 @@ fn main() { }; //~^^^^ match_like_matches_macro - // Lint + // No lint: covered by `redundant_pattern_matching` let _w = match x { Some(_) => true, _ => false, }; //~^^^^ redundant_pattern_matching - // Turn into is_none + // No lint: covered by `redundant_pattern_matching` let _z = match x { Some(_) => false, None => true, diff --git a/tests/ui/match_expr_like_matches_macro.stderr b/tests/ui/match_like_matches_macro.stderr similarity index 84% rename from tests/ui/match_expr_like_matches_macro.stderr rename to tests/ui/match_like_matches_macro.stderr index 8fceb05bc6e8..ae277ce4dca6 100644 --- a/tests/ui/match_expr_like_matches_macro.stderr +++ b/tests/ui/match_like_matches_macro.stderr @@ -1,5 +1,5 @@ error: match expression looks like `matches!` macro - --> tests/ui/match_expr_like_matches_macro.rs:14:14 + --> tests/ui/match_like_matches_macro.rs:13:14 | LL | let _y = match x { | ______________^ @@ -12,7 +12,7 @@ LL | | }; = help: to override `-D warnings` add `#[allow(clippy::match_like_matches_macro)]` error: redundant pattern matching, consider using `is_some()` - --> tests/ui/match_expr_like_matches_macro.rs:21:14 + --> tests/ui/match_like_matches_macro.rs:20:14 | LL | let _w = match x { | ______________^ @@ -25,7 +25,7 @@ LL | | }; = help: to override `-D warnings` add `#[allow(clippy::redundant_pattern_matching)]` error: redundant pattern matching, consider using `is_none()` - --> tests/ui/match_expr_like_matches_macro.rs:28:14 + --> tests/ui/match_like_matches_macro.rs:27:14 | LL | let _z = match x { | ______________^ @@ -35,7 +35,7 @@ LL | | }; | |_____^ help: try: `x.is_none()` error: match expression looks like `matches!` macro - --> tests/ui/match_expr_like_matches_macro.rs:35:15 + --> tests/ui/match_like_matches_macro.rs:34:15 | LL | let _zz = match x { | _______________^ @@ -45,13 +45,13 @@ LL | | }; | |_____^ help: try: `!matches!(x, Some(r) if r == 0)` error: if let .. else expression looks like `matches!` macro - --> tests/ui/match_expr_like_matches_macro.rs:42:16 + --> 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))` error: match expression looks like `matches!` macro - --> tests/ui/match_expr_like_matches_macro.rs:67:20 + --> tests/ui/match_like_matches_macro.rs:66:20 | LL | let _ans = match x { | ____________________^ @@ -62,7 +62,7 @@ LL | | }; | |_________^ help: try: `matches!(x, E::A(_) | E::B(_))` error: match expression looks like `matches!` macro - --> tests/ui/match_expr_like_matches_macro.rs:78:20 + --> tests/ui/match_like_matches_macro.rs:77:20 | LL | let _ans = match x { | ____________________^ @@ -74,7 +74,7 @@ LL | | }; | |_________^ help: try: `matches!(x, E::A(_) | E::B(_))` error: match expression looks like `matches!` macro - --> tests/ui/match_expr_like_matches_macro.rs:89:20 + --> tests/ui/match_like_matches_macro.rs:88:20 | LL | let _ans = match x { | ____________________^ @@ -85,7 +85,7 @@ LL | | }; | |_________^ help: try: `!matches!(x, E::B(_) | E::C)` error: match expression looks like `matches!` macro - --> tests/ui/match_expr_like_matches_macro.rs:150:18 + --> tests/ui/match_like_matches_macro.rs:149:18 | LL | let _z = match &z { | __________________^ @@ -95,7 +95,7 @@ LL | | }; | |_________^ help: try: `matches!(z, Some(3))` error: match expression looks like `matches!` macro - --> tests/ui/match_expr_like_matches_macro.rs:160:18 + --> tests/ui/match_like_matches_macro.rs:159:18 | LL | let _z = match &z { | __________________^ @@ -105,7 +105,7 @@ LL | | }; | |_________^ help: try: `matches!(&z, Some(3))` error: match expression looks like `matches!` macro - --> tests/ui/match_expr_like_matches_macro.rs:178:21 + --> tests/ui/match_like_matches_macro.rs:177:21 | LL | let _ = match &z { | _____________________^ @@ -115,7 +115,7 @@ LL | | }; | |_____________^ help: try: `matches!(&z, AnEnum::X)` error: match expression looks like `matches!` macro - --> tests/ui/match_expr_like_matches_macro.rs:193:20 + --> tests/ui/match_like_matches_macro.rs:192:20 | LL | let _res = match &val { | ____________________^ @@ -125,7 +125,7 @@ LL | | }; | |_________^ help: try: `matches!(&val, &Some(ref _a))` error: match expression looks like `matches!` macro - --> tests/ui/match_expr_like_matches_macro.rs:206:20 + --> tests/ui/match_like_matches_macro.rs:205:20 | LL | let _res = match &val { | ____________________^ @@ -135,7 +135,7 @@ LL | | }; | |_________^ help: try: `matches!(&val, &Some(ref _a))` error: match expression looks like `matches!` macro - --> tests/ui/match_expr_like_matches_macro.rs:265:14 + --> tests/ui/match_like_matches_macro.rs:264:14 | LL | let _y = match Some(5) { | ______________^ From 62e1225c87852d9650e6d34ffc6c263ba41fab8e Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Thu, 9 Oct 2025 18:56:08 +0200 Subject: [PATCH 02/10] inline `find_matches_sugg` into `check_if_let` --- .../src/matches/match_like_matches.rs | 69 ++++++++++++++++--- 1 file changed, 58 insertions(+), 11 deletions(-) diff --git a/clippy_lints/src/matches/match_like_matches.rs b/clippy_lints/src/matches/match_like_matches.rs index 8257c39c90f0..3df23a635794 100644 --- a/clippy_lints/src/matches/match_like_matches.rs +++ b/clippy_lints/src/matches/match_like_matches.rs @@ -21,17 +21,64 @@ pub(crate) fn check_if_let<'tcx>( then_expr: &'tcx Expr<'_>, else_expr: &'tcx Expr<'_>, ) { - find_matches_sugg( - cx, - let_expr, - [ - (&[][..], Some(let_pat), then_expr, None), - (&[][..], None, else_expr, None), - ] - .into_iter(), - expr, - true, - ); + let mut arms = [(Some(let_pat), then_expr), (None, else_expr)].into_iter(); + let is_if_let = true; + if !span_contains_comment(cx.sess().source_map(), expr.span) + && cx.typeck_results().expr_ty(expr).is_bool() + && let Some((None, else_expr)) = arms.next_back() + && let arms_without_last = [(Some(let_pat), then_expr)] + && let Some((_, first_expr)) = arms.next() + && let Some(b0) = find_bool_lit(first_expr) + && let Some(b1) = find_bool_lit(else_expr) + && b0 != b1 + && arms.all(|(_, expr)| find_bool_lit(expr) == Some(b0)) + { + for arm in &arms_without_last { + if let Some(pat) = arm.0 + && !is_lint_allowed(cx, REDUNDANT_PATTERN_MATCHING, pat.hir_id) + && is_some_wild(pat.kind) + { + return; + } + } + + // The suggestion may be incorrect, because some arms can have `cfg` attributes + // evaluated into `false` and so such arms will be stripped before. + let mut applicability = Applicability::MaybeIncorrect; + let pat = { + use itertools::Itertools as _; + arms_without_last + .into_iter() + .filter_map(|arm| arm.0) + .map(|pat| snippet_with_applicability(cx, pat.span, "..", &mut applicability)) + .join(" | ") + }; + let pat_and_guard = pat; + + // strip potential borrows (#6503), but only if the type is a reference + let mut ex_new = let_expr; + if let ExprKind::AddrOf(BorrowKind::Ref, .., ex_inner) = let_expr.kind + && let ty::Ref(..) = cx.typeck_results().expr_ty(ex_inner).kind() + { + ex_new = ex_inner; + } + span_lint_and_sugg( + cx, + MATCH_LIKE_MATCHES_MACRO, + expr.span, + format!( + "{} expression looks like `matches!` macro", + if is_if_let { "if let .. else" } else { "match" } + ), + "try", + format!( + "{}matches!({}, {pat_and_guard})", + if b0 { "" } else { "!" }, + snippet_with_applicability(cx, ex_new.span, "..", &mut applicability), + ), + applicability, + ); + } } pub(super) fn check_match<'tcx>( From 27d5f5c7a61aca226eb33c8fa1e46b9ad4aa1f03 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Thu, 9 Oct 2025 19:01:33 +0200 Subject: [PATCH 03/10] inline `is_if_let` into all callers --- .../src/matches/match_like_matches.rs | 20 +++---------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/clippy_lints/src/matches/match_like_matches.rs b/clippy_lints/src/matches/match_like_matches.rs index 3df23a635794..7e9a14c81fc3 100644 --- a/clippy_lints/src/matches/match_like_matches.rs +++ b/clippy_lints/src/matches/match_like_matches.rs @@ -22,7 +22,6 @@ pub(crate) fn check_if_let<'tcx>( else_expr: &'tcx Expr<'_>, ) { let mut arms = [(Some(let_pat), then_expr), (None, else_expr)].into_iter(); - let is_if_let = true; if !span_contains_comment(cx.sess().source_map(), expr.span) && cx.typeck_results().expr_ty(expr).is_bool() && let Some((None, else_expr)) = arms.next_back() @@ -66,10 +65,7 @@ pub(crate) fn check_if_let<'tcx>( cx, MATCH_LIKE_MATCHES_MACRO, expr.span, - format!( - "{} expression looks like `matches!` macro", - if is_if_let { "if let .. else" } else { "match" } - ), + "if let .. else expression looks like `matches!` macro", "try", format!( "{}matches!({}, {pat_and_guard})", @@ -93,18 +89,11 @@ pub(super) fn check_match<'tcx>( arms.iter() .map(|arm| (cx.tcx.hir_attrs(arm.hir_id), Some(arm.pat), arm.body, arm.guard)), e, - false, ) } /// Lint a `match` or `if let` for replacement by `matches!` -fn find_matches_sugg<'a, 'b, I>( - cx: &LateContext<'_>, - ex: &Expr<'_>, - mut arms: I, - expr: &Expr<'_>, - is_if_let: bool, -) -> bool +fn find_matches_sugg<'a, 'b, I>(cx: &LateContext<'_>, ex: &Expr<'_>, mut arms: I, expr: &Expr<'_>) -> bool where 'b: 'a, I: Clone @@ -172,10 +161,7 @@ where cx, MATCH_LIKE_MATCHES_MACRO, expr.span, - format!( - "{} expression looks like `matches!` macro", - if is_if_let { "if let .. else" } else { "match" } - ), + "match expression looks like `matches!` macro", "try", format!( "{}matches!({}, {pat_and_guard})", From 8519d49d6f5eabed9ddd0a241436cc1d80e46022 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Thu, 9 Oct 2025 19:04:02 +0200 Subject: [PATCH 04/10] inline `arms_without_last` into all callers it's now just the first arm --- .../src/matches/match_like_matches.rs | 22 ++++--------------- 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/clippy_lints/src/matches/match_like_matches.rs b/clippy_lints/src/matches/match_like_matches.rs index 7e9a14c81fc3..7b5412d29736 100644 --- a/clippy_lints/src/matches/match_like_matches.rs +++ b/clippy_lints/src/matches/match_like_matches.rs @@ -25,34 +25,20 @@ pub(crate) fn check_if_let<'tcx>( if !span_contains_comment(cx.sess().source_map(), expr.span) && cx.typeck_results().expr_ty(expr).is_bool() && let Some((None, else_expr)) = arms.next_back() - && let arms_without_last = [(Some(let_pat), then_expr)] && let Some((_, first_expr)) = arms.next() && let Some(b0) = find_bool_lit(first_expr) && let Some(b1) = find_bool_lit(else_expr) && b0 != b1 && arms.all(|(_, expr)| find_bool_lit(expr) == Some(b0)) { - for arm in &arms_without_last { - if let Some(pat) = arm.0 - && !is_lint_allowed(cx, REDUNDANT_PATTERN_MATCHING, pat.hir_id) - && is_some_wild(pat.kind) - { - return; - } + if !is_lint_allowed(cx, REDUNDANT_PATTERN_MATCHING, let_pat.hir_id) && is_some_wild(let_pat.kind) { + return; } // The suggestion may be incorrect, because some arms can have `cfg` attributes // evaluated into `false` and so such arms will be stripped before. let mut applicability = Applicability::MaybeIncorrect; - let pat = { - use itertools::Itertools as _; - arms_without_last - .into_iter() - .filter_map(|arm| arm.0) - .map(|pat| snippet_with_applicability(cx, pat.span, "..", &mut applicability)) - .join(" | ") - }; - let pat_and_guard = pat; + let pat = snippet_with_applicability(cx, let_pat.span, "..", &mut applicability); // strip potential borrows (#6503), but only if the type is a reference let mut ex_new = let_expr; @@ -68,7 +54,7 @@ pub(crate) fn check_if_let<'tcx>( "if let .. else expression looks like `matches!` macro", "try", format!( - "{}matches!({}, {pat_and_guard})", + "{}matches!({}, {pat})", if b0 { "" } else { "!" }, snippet_with_applicability(cx, ex_new.span, "..", &mut applicability), ), From d1d5f0c7ae6944ff9d47aa97b0a3fb0a1469d2b0 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Thu, 9 Oct 2025 19:11:02 +0200 Subject: [PATCH 05/10] inline `arms` into all callers --- clippy_lints/src/matches/match_like_matches.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/clippy_lints/src/matches/match_like_matches.rs b/clippy_lints/src/matches/match_like_matches.rs index 7b5412d29736..3bee0b7894f5 100644 --- a/clippy_lints/src/matches/match_like_matches.rs +++ b/clippy_lints/src/matches/match_like_matches.rs @@ -21,15 +21,11 @@ pub(crate) fn check_if_let<'tcx>( then_expr: &'tcx Expr<'_>, else_expr: &'tcx Expr<'_>, ) { - let mut arms = [(Some(let_pat), then_expr), (None, else_expr)].into_iter(); if !span_contains_comment(cx.sess().source_map(), expr.span) && cx.typeck_results().expr_ty(expr).is_bool() - && let Some((None, else_expr)) = arms.next_back() - && let Some((_, first_expr)) = arms.next() - && let Some(b0) = find_bool_lit(first_expr) + && let Some(b0) = find_bool_lit(then_expr) && let Some(b1) = find_bool_lit(else_expr) && b0 != b1 - && arms.all(|(_, expr)| find_bool_lit(expr) == Some(b0)) { if !is_lint_allowed(cx, REDUNDANT_PATTERN_MATCHING, let_pat.hir_id) && is_some_wild(let_pat.kind) { return; From 83e2b3df5481ce50c898327f37ba57450fa009c1 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Thu, 9 Oct 2025 19:14:10 +0200 Subject: [PATCH 06/10] inline `find_matches_sugg` into `check_match` --- .../src/matches/match_like_matches.rs | 33 +++++-------------- 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/clippy_lints/src/matches/match_like_matches.rs b/clippy_lints/src/matches/match_like_matches.rs index 3bee0b7894f5..61b964a19f32 100644 --- a/clippy_lints/src/matches/match_like_matches.rs +++ b/clippy_lints/src/matches/match_like_matches.rs @@ -6,7 +6,7 @@ use clippy_utils::source::snippet_with_applicability; use clippy_utils::{is_lint_allowed, is_wild, span_contains_comment}; use rustc_ast::LitKind; use rustc_errors::Applicability; -use rustc_hir::{Arm, Attribute, BorrowKind, Expr, ExprKind, Pat, PatKind, QPath}; +use rustc_hir::{Arm, BorrowKind, Expr, ExprKind, Pat, PatKind, QPath}; use rustc_lint::{LateContext, LintContext}; use rustc_middle::ty; use rustc_span::source_map::Spanned; @@ -65,27 +65,12 @@ pub(super) fn check_match<'tcx>( scrutinee: &'tcx Expr<'_>, arms: &'tcx [Arm<'tcx>], ) -> bool { - find_matches_sugg( - cx, - scrutinee, - arms.iter() - .map(|arm| (cx.tcx.hir_attrs(arm.hir_id), Some(arm.pat), arm.body, arm.guard)), - e, - ) -} - -/// Lint a `match` or `if let` for replacement by `matches!` -fn find_matches_sugg<'a, 'b, I>(cx: &LateContext<'_>, ex: &Expr<'_>, mut arms: I, expr: &Expr<'_>) -> bool -where - 'b: 'a, - I: Clone - + DoubleEndedIterator - + ExactSizeIterator - + Iterator>, &'a Expr<'b>, Option<&'a Expr<'b>>)>, -{ - if !span_contains_comment(cx.sess().source_map(), expr.span) + let mut arms = arms + .iter() + .map(|arm| (cx.tcx.hir_attrs(arm.hir_id), Some(arm.pat), arm.body, arm.guard)); + if !span_contains_comment(cx.sess().source_map(), e.span) && arms.len() >= 2 - && cx.typeck_results().expr_ty(expr).is_bool() + && cx.typeck_results().expr_ty(e).is_bool() && let Some((_, last_pat_opt, last_expr, _)) = arms.next_back() && let arms_without_last = arms.clone() && let Some((first_attrs, _, first_expr, first_guard)) = arms.next() @@ -133,8 +118,8 @@ where }; // strip potential borrows (#6503), but only if the type is a reference - let mut ex_new = ex; - if let ExprKind::AddrOf(BorrowKind::Ref, .., ex_inner) = ex.kind + let mut ex_new = scrutinee; + if let ExprKind::AddrOf(BorrowKind::Ref, .., ex_inner) = scrutinee.kind && let ty::Ref(..) = cx.typeck_results().expr_ty(ex_inner).kind() { ex_new = ex_inner; @@ -142,7 +127,7 @@ where span_lint_and_sugg( cx, MATCH_LIKE_MATCHES_MACRO, - expr.span, + e.span, "match expression looks like `matches!` macro", "try", format!( From bf5170a54967664489161b90dfc75ba352a1cdc6 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Thu, 9 Oct 2025 19:16:58 +0200 Subject: [PATCH 07/10] match arm pats aren't `Option`al anymore --- .../src/matches/match_like_matches.rs | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/clippy_lints/src/matches/match_like_matches.rs b/clippy_lints/src/matches/match_like_matches.rs index 61b964a19f32..9a2610364fef 100644 --- a/clippy_lints/src/matches/match_like_matches.rs +++ b/clippy_lints/src/matches/match_like_matches.rs @@ -67,11 +67,11 @@ pub(super) fn check_match<'tcx>( ) -> bool { let mut arms = arms .iter() - .map(|arm| (cx.tcx.hir_attrs(arm.hir_id), Some(arm.pat), arm.body, arm.guard)); + .map(|arm| (cx.tcx.hir_attrs(arm.hir_id), arm.pat, arm.body, arm.guard)); if !span_contains_comment(cx.sess().source_map(), e.span) && arms.len() >= 2 && cx.typeck_results().expr_ty(e).is_bool() - && let Some((_, last_pat_opt, last_expr, _)) = arms.next_back() + && let Some((_, last_pat, last_expr, _)) = arms.next_back() && let arms_without_last = arms.clone() && let Some((first_attrs, _, first_expr, first_guard)) = arms.next() && let Some(b0) = find_bool_lit(first_expr) @@ -81,17 +81,13 @@ pub(super) fn check_match<'tcx>( && first_attrs.is_empty() && arms.all(|(attrs, _, expr, guard)| attrs.is_empty() && guard.is_none() && find_bool_lit(expr) == Some(b0)) { - if let Some(last_pat) = last_pat_opt - && !is_wild(last_pat) - { + if !is_wild(last_pat) { return false; } for arm in arms_without_last.clone() { - if let Some(pat) = arm.1 - && !is_lint_allowed(cx, REDUNDANT_PATTERN_MATCHING, pat.hir_id) - && is_some_wild(pat.kind) - { + let pat = arm.1; + if !is_lint_allowed(cx, REDUNDANT_PATTERN_MATCHING, pat.hir_id) && is_some_wild(pat.kind) { return false; } } @@ -102,9 +98,9 @@ pub(super) fn check_match<'tcx>( let pat = { use itertools::Itertools as _; arms_without_last - .filter_map(|arm| { - let pat_span = arm.1?.span; - Some(snippet_with_applicability(cx, pat_span, "..", &mut applicability)) + .map(|arm| { + let pat_span = arm.1.span; + snippet_with_applicability(cx, pat_span, "..", &mut applicability) }) .join(" | ") }; From 0efe3cfca56972c2117b1df7f109114264f4ff4d Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Thu, 9 Oct 2025 19:27:02 +0200 Subject: [PATCH 08/10] split `arms` into first, last, and middle pats as slice --- .../src/matches/match_like_matches.rs | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/clippy_lints/src/matches/match_like_matches.rs b/clippy_lints/src/matches/match_like_matches.rs index 9a2610364fef..a91970f82290 100644 --- a/clippy_lints/src/matches/match_like_matches.rs +++ b/clippy_lints/src/matches/match_like_matches.rs @@ -65,28 +65,28 @@ pub(super) fn check_match<'tcx>( scrutinee: &'tcx Expr<'_>, arms: &'tcx [Arm<'tcx>], ) -> bool { - let mut arms = arms - .iter() - .map(|arm| (cx.tcx.hir_attrs(arm.hir_id), arm.pat, arm.body, arm.guard)); - if !span_contains_comment(cx.sess().source_map(), e.span) - && arms.len() >= 2 + if let Some((last_arm, arms_without_last)) = arms.split_last() + && let Some((first_arm, middle_arms)) = arms_without_last.split_first() + && !span_contains_comment(cx.sess().source_map(), e.span) && cx.typeck_results().expr_ty(e).is_bool() - && let Some((_, last_pat, last_expr, _)) = arms.next_back() - && let arms_without_last = arms.clone() - && let Some((first_attrs, _, first_expr, first_guard)) = arms.next() + && let (last_pat, last_expr) = (last_arm.pat, last_arm.body) + && let (first_attrs, first_expr, first_guard) = + (cx.tcx.hir_attrs(first_arm.hir_id), first_arm.body, first_arm.guard) && let Some(b0) = find_bool_lit(first_expr) && let Some(b1) = find_bool_lit(last_expr) && b0 != b1 - && (first_guard.is_none() || arms.len() == 0) + && (first_guard.is_none() || middle_arms.is_empty()) && first_attrs.is_empty() - && arms.all(|(attrs, _, expr, guard)| attrs.is_empty() && guard.is_none() && find_bool_lit(expr) == Some(b0)) + && middle_arms.iter().all(|arm| { + cx.tcx.hir_attrs(arm.hir_id).is_empty() && arm.guard.is_none() && find_bool_lit(arm.body) == Some(b0) + }) { if !is_wild(last_pat) { return false; } - for arm in arms_without_last.clone() { - let pat = arm.1; + for arm in arms_without_last { + let pat = arm.pat; if !is_lint_allowed(cx, REDUNDANT_PATTERN_MATCHING, pat.hir_id) && is_some_wild(pat.kind) { return false; } @@ -98,10 +98,8 @@ pub(super) fn check_match<'tcx>( let pat = { use itertools::Itertools as _; arms_without_last - .map(|arm| { - let pat_span = arm.1.span; - snippet_with_applicability(cx, pat_span, "..", &mut applicability) - }) + .iter() + .map(|arm| snippet_with_applicability(cx, arm.pat.span, "..", &mut applicability)) .join(" | ") }; let pat_and_guard = if let Some(g) = first_guard { From dd7f6058bf221dbd18eb8c47b283b3aaaf0bf4a4 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Thu, 9 Oct 2025 19:28:49 +0200 Subject: [PATCH 09/10] inline `{first,last}_{attrs,expr,pat,guard}` into all callers --- clippy_lints/src/matches/match_like_matches.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/clippy_lints/src/matches/match_like_matches.rs b/clippy_lints/src/matches/match_like_matches.rs index a91970f82290..89da55d4fe74 100644 --- a/clippy_lints/src/matches/match_like_matches.rs +++ b/clippy_lints/src/matches/match_like_matches.rs @@ -69,19 +69,16 @@ pub(super) fn check_match<'tcx>( && let Some((first_arm, middle_arms)) = arms_without_last.split_first() && !span_contains_comment(cx.sess().source_map(), e.span) && cx.typeck_results().expr_ty(e).is_bool() - && let (last_pat, last_expr) = (last_arm.pat, last_arm.body) - && let (first_attrs, first_expr, first_guard) = - (cx.tcx.hir_attrs(first_arm.hir_id), first_arm.body, first_arm.guard) - && let Some(b0) = find_bool_lit(first_expr) - && let Some(b1) = find_bool_lit(last_expr) + && let Some(b0) = find_bool_lit(first_arm.body) + && let Some(b1) = find_bool_lit(last_arm.body) && b0 != b1 - && (first_guard.is_none() || middle_arms.is_empty()) - && first_attrs.is_empty() + && (first_arm.guard.is_none() || middle_arms.is_empty()) + && cx.tcx.hir_attrs(first_arm.hir_id).is_empty() && middle_arms.iter().all(|arm| { cx.tcx.hir_attrs(arm.hir_id).is_empty() && arm.guard.is_none() && find_bool_lit(arm.body) == Some(b0) }) { - if !is_wild(last_pat) { + if !is_wild(last_arm.pat) { return false; } @@ -102,7 +99,7 @@ pub(super) fn check_match<'tcx>( .map(|arm| snippet_with_applicability(cx, arm.pat.span, "..", &mut applicability)) .join(" | ") }; - let pat_and_guard = if let Some(g) = first_guard { + let pat_and_guard = if let Some(g) = first_arm.guard { format!( "{pat} if {}", snippet_with_applicability(cx, g.span, "..", &mut applicability) From 05359084991fc484539db87cf9e70665d1ccb5dd Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Thu, 9 Oct 2025 21:33:24 +0200 Subject: [PATCH 10/10] final refactor and docs --- .../src/matches/match_like_matches.rs | 64 +++++++++++++++++-- 1 file changed, 59 insertions(+), 5 deletions(-) diff --git a/clippy_lints/src/matches/match_like_matches.rs b/clippy_lints/src/matches/match_like_matches.rs index 89da55d4fe74..b5f631e8fea3 100644 --- a/clippy_lints/src/matches/match_like_matches.rs +++ b/clippy_lints/src/matches/match_like_matches.rs @@ -72,11 +72,65 @@ pub(super) fn check_match<'tcx>( && let Some(b0) = find_bool_lit(first_arm.body) && let Some(b1) = find_bool_lit(last_arm.body) && b0 != b1 - && (first_arm.guard.is_none() || middle_arms.is_empty()) - && cx.tcx.hir_attrs(first_arm.hir_id).is_empty() - && middle_arms.iter().all(|arm| { - cx.tcx.hir_attrs(arm.hir_id).is_empty() && arm.guard.is_none() && find_bool_lit(arm.body) == Some(b0) - }) + // We handle two cases: + && ( + // - There are no middle arms, i.e., 2 arms in total + // + // In that case, the first arm may or may not have a guard, because this: + // ```rs + // match e { + // Either::Left $(if $guard)|+ => true, // or `false`, but then we'll need `!matches!(..)` + // _ => false, + // } + // ``` + // can always become this: + // ```rs + // matches!(e, Either::Left $(if $guard)|+) + // ``` + middle_arms.is_empty() + + // - (added in #6216) There are middle arms + // + // In that case, neither they nor the first arm may have guards + // -- otherwise, they couldn't be combined into an or-pattern in `matches!` + // + // This: + // ```rs + // match e { + // Either3::First => true, + // Either3::Second => true, + // _ /* matches `Either3::Third` */ => false, + // } + // ``` + // can become this: + // ```rs + // matches!(e, Either3::First | Either3::Second) + // ``` + // + // But this: + // ```rs + // match e { + // Either3::First if X => true, + // Either3::Second => true, + // _ => false, + // } + // ``` + // cannot be transformed. + // + // We set an additional constraint of all of them needing to return the same bool, + // so we don't lint things like: + // ```rs + // match e { + // Either3::First => true, + // Either3::Second => false, + // _ => false, + // } + // ``` + // This is not *strictly* necessary, but it simplifies the logic a bit + || arms_without_last.iter().all(|arm| { + cx.tcx.hir_attrs(arm.hir_id).is_empty() && arm.guard.is_none() && find_bool_lit(arm.body) == Some(b0) + }) + ) { if !is_wild(last_arm.pat) { return false;