Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit e41a6fc

Browse files
committed
Split out match_like_matches_macro
1 parent f7be956 commit e41a6fc

File tree

2 files changed

+175
-163
lines changed

2 files changed

+175
-163
lines changed
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
use clippy_utils::diagnostics::span_lint_and_sugg;
2+
use clippy_utils::source::snippet_with_applicability;
3+
use clippy_utils::{higher, is_wild};
4+
use rustc_ast::{Attribute, LitKind};
5+
use rustc_errors::Applicability;
6+
use rustc_hir::{BorrowKind, Expr, ExprKind, Guard, MatchSource, Pat};
7+
use rustc_lint::LateContext;
8+
use rustc_middle::ty;
9+
use rustc_span::source_map::Spanned;
10+
11+
use super::MATCH_LIKE_MATCHES_MACRO;
12+
13+
/// Lint a `match` or `if let .. { .. } else { .. }` expr that could be replaced by `matches!`
14+
pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
15+
if let Some(higher::IfLet {
16+
let_pat,
17+
let_expr,
18+
if_then,
19+
if_else: Some(if_else),
20+
}) = higher::IfLet::hir(cx, expr)
21+
{
22+
return find_matches_sugg(
23+
cx,
24+
let_expr,
25+
IntoIterator::into_iter([(&[][..], Some(let_pat), if_then, None), (&[][..], None, if_else, None)]),
26+
expr,
27+
true,
28+
);
29+
}
30+
31+
if let ExprKind::Match(scrut, arms, MatchSource::Normal) = expr.kind {
32+
return find_matches_sugg(
33+
cx,
34+
scrut,
35+
arms.iter().map(|arm| {
36+
(
37+
cx.tcx.hir().attrs(arm.hir_id),
38+
Some(arm.pat),
39+
arm.body,
40+
arm.guard.as_ref(),
41+
)
42+
}),
43+
expr,
44+
false,
45+
);
46+
}
47+
48+
false
49+
}
50+
51+
/// Lint a `match` or `if let` for replacement by `matches!`
52+
fn find_matches_sugg<'a, 'b, I>(
53+
cx: &LateContext<'_>,
54+
ex: &Expr<'_>,
55+
mut iter: I,
56+
expr: &Expr<'_>,
57+
is_if_let: bool,
58+
) -> bool
59+
where
60+
'b: 'a,
61+
I: Clone
62+
+ DoubleEndedIterator
63+
+ ExactSizeIterator
64+
+ Iterator<
65+
Item = (
66+
&'a [Attribute],
67+
Option<&'a Pat<'b>>,
68+
&'a Expr<'b>,
69+
Option<&'a Guard<'b>>,
70+
),
71+
>,
72+
{
73+
if_chain! {
74+
if iter.len() >= 2;
75+
if cx.typeck_results().expr_ty(expr).is_bool();
76+
if let Some((_, last_pat_opt, last_expr, _)) = iter.next_back();
77+
let iter_without_last = iter.clone();
78+
if let Some((first_attrs, _, first_expr, first_guard)) = iter.next();
79+
if let Some(b0) = find_bool_lit(&first_expr.kind, is_if_let);
80+
if let Some(b1) = find_bool_lit(&last_expr.kind, is_if_let);
81+
if b0 != b1;
82+
if first_guard.is_none() || iter.len() == 0;
83+
if first_attrs.is_empty();
84+
if iter
85+
.all(|arm| {
86+
find_bool_lit(&arm.2.kind, is_if_let).map_or(false, |b| b == b0) && arm.3.is_none() && arm.0.is_empty()
87+
});
88+
then {
89+
if let Some(last_pat) = last_pat_opt {
90+
if !is_wild(last_pat) {
91+
return false;
92+
}
93+
}
94+
95+
// The suggestion may be incorrect, because some arms can have `cfg` attributes
96+
// evaluated into `false` and so such arms will be stripped before.
97+
let mut applicability = Applicability::MaybeIncorrect;
98+
let pat = {
99+
use itertools::Itertools as _;
100+
iter_without_last
101+
.filter_map(|arm| {
102+
let pat_span = arm.1?.span;
103+
Some(snippet_with_applicability(cx, pat_span, "..", &mut applicability))
104+
})
105+
.join(" | ")
106+
};
107+
let pat_and_guard = if let Some(Guard::If(g)) = first_guard {
108+
format!("{} if {}", pat, snippet_with_applicability(cx, g.span, "..", &mut applicability))
109+
} else {
110+
pat
111+
};
112+
113+
// strip potential borrows (#6503), but only if the type is a reference
114+
let mut ex_new = ex;
115+
if let ExprKind::AddrOf(BorrowKind::Ref, .., ex_inner) = ex.kind {
116+
if let ty::Ref(..) = cx.typeck_results().expr_ty(ex_inner).kind() {
117+
ex_new = ex_inner;
118+
}
119+
};
120+
span_lint_and_sugg(
121+
cx,
122+
MATCH_LIKE_MATCHES_MACRO,
123+
expr.span,
124+
&format!("{} expression looks like `matches!` macro", if is_if_let { "if let .. else" } else { "match" }),
125+
"try this",
126+
format!(
127+
"{}matches!({}, {})",
128+
if b0 { "" } else { "!" },
129+
snippet_with_applicability(cx, ex_new.span, "..", &mut applicability),
130+
pat_and_guard,
131+
),
132+
applicability,
133+
);
134+
true
135+
} else {
136+
false
137+
}
138+
}
139+
}
140+
141+
/// Extract a `bool` or `{ bool }`
142+
fn find_bool_lit(ex: &ExprKind<'_>, is_if_let: bool) -> Option<bool> {
143+
match ex {
144+
ExprKind::Lit(Spanned {
145+
node: LitKind::Bool(b), ..
146+
}) => Some(*b),
147+
ExprKind::Block(
148+
rustc_hir::Block {
149+
stmts: &[],
150+
expr: Some(exp),
151+
..
152+
},
153+
_,
154+
) if is_if_let => {
155+
if let ExprKind::Lit(Spanned {
156+
node: LitKind::Bool(b), ..
157+
}) = exp.kind
158+
{
159+
Some(b)
160+
} else {
161+
None
162+
}
163+
},
164+
_ => None,
165+
}
166+
}

clippy_lints/src/matches/mod.rs

Lines changed: 9 additions & 163 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use clippy_utils::diagnostics::{
33
multispan_sugg, span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then,
44
};
55
use clippy_utils::macros::{is_panic, root_macro_call};
6+
use clippy_utils::peel_blocks_with_stmt;
67
use clippy_utils::source::{expr_block, indent_of, snippet, snippet_block, snippet_opt, snippet_with_applicability};
78
use clippy_utils::sugg::Sugg;
89
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item, match_type, peel_mid_ty_refs};
@@ -12,28 +13,28 @@ use clippy_utils::{
1213
path_to_local, path_to_local_id, peel_blocks, peel_hir_pat_refs, peel_n_hir_expr_refs, recurse_or_patterns,
1314
strip_pat_refs,
1415
};
15-
use clippy_utils::{higher, peel_blocks_with_stmt};
1616
use clippy_utils::{paths, search_same, SpanlessEq, SpanlessHash};
17-
use core::iter::{once, ExactSizeIterator};
17+
use core::iter::once;
1818
use if_chain::if_chain;
19-
use rustc_ast::ast::{Attribute, LitKind};
19+
use rustc_ast::ast::LitKind;
2020
use rustc_errors::Applicability;
2121
use rustc_hir::def::{CtorKind, DefKind, Res};
2222
use rustc_hir::LangItem::{OptionNone, OptionSome};
2323
use rustc_hir::{
24-
self as hir, Arm, BindingAnnotation, Block, BorrowKind, Expr, ExprKind, Guard, HirId, Local, MatchSource,
25-
Mutability, Node, Pat, PatKind, PathSegment, QPath, RangeEnd, TyKind,
24+
self as hir, Arm, BindingAnnotation, Block, BorrowKind, Expr, ExprKind, HirId, Local, MatchSource, Mutability,
25+
Node, Pat, PatKind, PathSegment, QPath, RangeEnd, TyKind,
2626
};
2727
use rustc_hir::{HirIdMap, HirIdSet};
2828
use rustc_lint::{LateContext, LateLintPass};
2929
use rustc_middle::ty::{self, Ty, TyS, VariantDef};
3030
use rustc_semver::RustcVersion;
3131
use rustc_session::{declare_tool_lint, impl_lint_pass};
32-
use rustc_span::source_map::{Span, Spanned};
33-
use rustc_span::{sym, symbol::kw};
32+
use rustc_span::{sym, symbol::kw, Span};
3433
use std::cmp::{max, Ordering};
3534
use std::collections::hash_map::Entry;
3635

36+
mod match_like_matches;
37+
3738
declare_clippy_lint! {
3839
/// ### What it does
3940
/// Checks for matches with a single arm where an `if let`
@@ -622,7 +623,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
622623
redundant_pattern_match::check(cx, expr);
623624

624625
if meets_msrv(self.msrv.as_ref(), &msrvs::MATCHES_MACRO) {
625-
if !check_match_like_matches(cx, expr) {
626+
if !match_like_matches::check(cx, expr) {
626627
lint_match_arms(cx, expr);
627628
}
628629
} else {
@@ -1382,161 +1383,6 @@ fn check_wild_in_or_pats(cx: &LateContext<'_>, arms: &[Arm<'_>]) {
13821383
}
13831384
}
13841385

1385-
/// Lint a `match` or `if let .. { .. } else { .. }` expr that could be replaced by `matches!`
1386-
fn check_match_like_matches<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
1387-
if let Some(higher::IfLet {
1388-
let_pat,
1389-
let_expr,
1390-
if_then,
1391-
if_else: Some(if_else),
1392-
}) = higher::IfLet::hir(cx, expr)
1393-
{
1394-
return find_matches_sugg(
1395-
cx,
1396-
let_expr,
1397-
IntoIterator::into_iter([(&[][..], Some(let_pat), if_then, None), (&[][..], None, if_else, None)]),
1398-
expr,
1399-
true,
1400-
);
1401-
}
1402-
1403-
if let ExprKind::Match(scrut, arms, MatchSource::Normal) = expr.kind {
1404-
return find_matches_sugg(
1405-
cx,
1406-
scrut,
1407-
arms.iter().map(|arm| {
1408-
(
1409-
cx.tcx.hir().attrs(arm.hir_id),
1410-
Some(arm.pat),
1411-
arm.body,
1412-
arm.guard.as_ref(),
1413-
)
1414-
}),
1415-
expr,
1416-
false,
1417-
);
1418-
}
1419-
1420-
false
1421-
}
1422-
1423-
/// Lint a `match` or `if let` for replacement by `matches!`
1424-
fn find_matches_sugg<'a, 'b, I>(
1425-
cx: &LateContext<'_>,
1426-
ex: &Expr<'_>,
1427-
mut iter: I,
1428-
expr: &Expr<'_>,
1429-
is_if_let: bool,
1430-
) -> bool
1431-
where
1432-
'b: 'a,
1433-
I: Clone
1434-
+ DoubleEndedIterator
1435-
+ ExactSizeIterator
1436-
+ Iterator<
1437-
Item = (
1438-
&'a [Attribute],
1439-
Option<&'a Pat<'b>>,
1440-
&'a Expr<'b>,
1441-
Option<&'a Guard<'b>>,
1442-
),
1443-
>,
1444-
{
1445-
if_chain! {
1446-
if iter.len() >= 2;
1447-
if cx.typeck_results().expr_ty(expr).is_bool();
1448-
if let Some((_, last_pat_opt, last_expr, _)) = iter.next_back();
1449-
let iter_without_last = iter.clone();
1450-
if let Some((first_attrs, _, first_expr, first_guard)) = iter.next();
1451-
if let Some(b0) = find_bool_lit(&first_expr.kind, is_if_let);
1452-
if let Some(b1) = find_bool_lit(&last_expr.kind, is_if_let);
1453-
if b0 != b1;
1454-
if first_guard.is_none() || iter.len() == 0;
1455-
if first_attrs.is_empty();
1456-
if iter
1457-
.all(|arm| {
1458-
find_bool_lit(&arm.2.kind, is_if_let).map_or(false, |b| b == b0) && arm.3.is_none() && arm.0.is_empty()
1459-
});
1460-
then {
1461-
if let Some(last_pat) = last_pat_opt {
1462-
if !is_wild(last_pat) {
1463-
return false;
1464-
}
1465-
}
1466-
1467-
// The suggestion may be incorrect, because some arms can have `cfg` attributes
1468-
// evaluated into `false` and so such arms will be stripped before.
1469-
let mut applicability = Applicability::MaybeIncorrect;
1470-
let pat = {
1471-
use itertools::Itertools as _;
1472-
iter_without_last
1473-
.filter_map(|arm| {
1474-
let pat_span = arm.1?.span;
1475-
Some(snippet_with_applicability(cx, pat_span, "..", &mut applicability))
1476-
})
1477-
.join(" | ")
1478-
};
1479-
let pat_and_guard = if let Some(Guard::If(g)) = first_guard {
1480-
format!("{} if {}", pat, snippet_with_applicability(cx, g.span, "..", &mut applicability))
1481-
} else {
1482-
pat
1483-
};
1484-
1485-
// strip potential borrows (#6503), but only if the type is a reference
1486-
let mut ex_new = ex;
1487-
if let ExprKind::AddrOf(BorrowKind::Ref, .., ex_inner) = ex.kind {
1488-
if let ty::Ref(..) = cx.typeck_results().expr_ty(ex_inner).kind() {
1489-
ex_new = ex_inner;
1490-
}
1491-
};
1492-
span_lint_and_sugg(
1493-
cx,
1494-
MATCH_LIKE_MATCHES_MACRO,
1495-
expr.span,
1496-
&format!("{} expression looks like `matches!` macro", if is_if_let { "if let .. else" } else { "match" }),
1497-
"try this",
1498-
format!(
1499-
"{}matches!({}, {})",
1500-
if b0 { "" } else { "!" },
1501-
snippet_with_applicability(cx, ex_new.span, "..", &mut applicability),
1502-
pat_and_guard,
1503-
),
1504-
applicability,
1505-
);
1506-
true
1507-
} else {
1508-
false
1509-
}
1510-
}
1511-
}
1512-
1513-
/// Extract a `bool` or `{ bool }`
1514-
fn find_bool_lit(ex: &ExprKind<'_>, is_if_let: bool) -> Option<bool> {
1515-
match ex {
1516-
ExprKind::Lit(Spanned {
1517-
node: LitKind::Bool(b), ..
1518-
}) => Some(*b),
1519-
ExprKind::Block(
1520-
rustc_hir::Block {
1521-
stmts: &[],
1522-
expr: Some(exp),
1523-
..
1524-
},
1525-
_,
1526-
) if is_if_let => {
1527-
if let ExprKind::Lit(Spanned {
1528-
node: LitKind::Bool(b), ..
1529-
}) = exp.kind
1530-
{
1531-
Some(b)
1532-
} else {
1533-
None
1534-
}
1535-
},
1536-
_ => None,
1537-
}
1538-
}
1539-
15401386
#[allow(clippy::too_many_lines)]
15411387
fn check_match_single_binding<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], expr: &Expr<'_>) {
15421388
if expr.span.from_expansion() || arms.len() != 1 || is_refutable(cx, arms[0].pat) {

0 commit comments

Comments
 (0)