Skip to content

Commit 81e4450

Browse files
committed
Merge CollapsibleMatch into Matches lint pass
1 parent 7c0d649 commit 81e4450

File tree

9 files changed

+139
-113
lines changed

9 files changed

+139
-113
lines changed

clippy_lints/src/lib.register_all.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
3838
LintId::of(casts::UNNECESSARY_CAST),
3939
LintId::of(collapsible_if::COLLAPSIBLE_ELSE_IF),
4040
LintId::of(collapsible_if::COLLAPSIBLE_IF),
41-
LintId::of(collapsible_match::COLLAPSIBLE_MATCH),
4241
LintId::of(comparison_chain::COMPARISON_CHAIN),
4342
LintId::of(copies::IFS_SAME_COND),
4443
LintId::of(copies::IF_SAME_THEN_ELSE),
@@ -143,6 +142,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
143142
LintId::of(map_unit_fn::RESULT_MAP_UNIT_FN),
144143
LintId::of(match_result_ok::MATCH_RESULT_OK),
145144
LintId::of(match_str_case_mismatch::MATCH_STR_CASE_MISMATCH),
145+
LintId::of(matches::COLLAPSIBLE_MATCH),
146146
LintId::of(matches::INFALLIBLE_DESTRUCTURING_MATCH),
147147
LintId::of(matches::MATCH_AS_REF),
148148
LintId::of(matches::MATCH_LIKE_MATCHES_MACRO),

clippy_lints/src/lib.register_lints.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,6 @@ store.register_lints(&[
9393
cognitive_complexity::COGNITIVE_COMPLEXITY,
9494
collapsible_if::COLLAPSIBLE_ELSE_IF,
9595
collapsible_if::COLLAPSIBLE_IF,
96-
collapsible_match::COLLAPSIBLE_MATCH,
9796
comparison_chain::COMPARISON_CHAIN,
9897
copies::BRANCHES_SHARING_CODE,
9998
copies::IFS_SAME_COND,
@@ -263,6 +262,7 @@ store.register_lints(&[
263262
match_on_vec_items::MATCH_ON_VEC_ITEMS,
264263
match_result_ok::MATCH_RESULT_OK,
265264
match_str_case_mismatch::MATCH_STR_CASE_MISMATCH,
265+
matches::COLLAPSIBLE_MATCH,
266266
matches::INFALLIBLE_DESTRUCTURING_MATCH,
267267
matches::MATCH_AS_REF,
268268
matches::MATCH_BOOL,

clippy_lints/src/lib.register_style.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ store.register_group(true, "clippy::style", Some("clippy_style"), vec![
1212
LintId::of(casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION),
1313
LintId::of(collapsible_if::COLLAPSIBLE_ELSE_IF),
1414
LintId::of(collapsible_if::COLLAPSIBLE_IF),
15-
LintId::of(collapsible_match::COLLAPSIBLE_MATCH),
1615
LintId::of(comparison_chain::COMPARISON_CHAIN),
1716
LintId::of(default::FIELD_REASSIGN_WITH_DEFAULT),
1817
LintId::of(dereference::NEEDLESS_BORROW),
@@ -50,6 +49,7 @@ store.register_group(true, "clippy::style", Some("clippy_style"), vec![
5049
LintId::of(manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE),
5150
LintId::of(map_clone::MAP_CLONE),
5251
LintId::of(match_result_ok::MATCH_RESULT_OK),
52+
LintId::of(matches::COLLAPSIBLE_MATCH),
5353
LintId::of(matches::INFALLIBLE_DESTRUCTURING_MATCH),
5454
LintId::of(matches::MATCH_LIKE_MATCHES_MACRO),
5555
LintId::of(matches::MATCH_OVERLAPPING_ARM),

clippy_lints/src/lib.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,6 @@ mod casts;
192192
mod checked_conversions;
193193
mod cognitive_complexity;
194194
mod collapsible_if;
195-
mod collapsible_match;
196195
mod comparison_chain;
197196
mod copies;
198197
mod copy_iterator;
@@ -568,7 +567,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
568567
store.register_late_pass(|| Box::new(len_zero::LenZero));
569568
store.register_late_pass(|| Box::new(attrs::Attributes));
570569
store.register_late_pass(|| Box::new(blocks_in_if_conditions::BlocksInIfConditions));
571-
store.register_late_pass(|| Box::new(collapsible_match::CollapsibleMatch));
572570
store.register_late_pass(|| Box::new(unicode::Unicode));
573571
store.register_late_pass(|| Box::new(uninit_vec::UninitVec));
574572
store.register_late_pass(|| Box::new(unit_hash::UnitHash));

clippy_lints/src/collapsible_match.rs renamed to clippy_lints/src/matches/collapsible_match.rs

Lines changed: 15 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -6,68 +6,28 @@ use if_chain::if_chain;
66
use rustc_errors::MultiSpan;
77
use rustc_hir::LangItem::OptionNone;
88
use rustc_hir::{Arm, Expr, Guard, HirId, Let, Pat, PatKind};
9-
use rustc_lint::{LateContext, LateLintPass};
10-
use rustc_session::{declare_lint_pass, declare_tool_lint};
9+
use rustc_lint::LateContext;
1110
use rustc_span::Span;
1211

13-
declare_clippy_lint! {
14-
/// ### What it does
15-
/// Finds nested `match` or `if let` expressions where the patterns may be "collapsed" together
16-
/// without adding any branches.
17-
///
18-
/// Note that this lint is not intended to find _all_ cases where nested match patterns can be merged, but only
19-
/// cases where merging would most likely make the code more readable.
20-
///
21-
/// ### Why is this bad?
22-
/// It is unnecessarily verbose and complex.
23-
///
24-
/// ### Example
25-
/// ```rust
26-
/// fn func(opt: Option<Result<u64, String>>) {
27-
/// let n = match opt {
28-
/// Some(n) => match n {
29-
/// Ok(n) => n,
30-
/// _ => return,
31-
/// }
32-
/// None => return,
33-
/// };
34-
/// }
35-
/// ```
36-
/// Use instead:
37-
/// ```rust
38-
/// fn func(opt: Option<Result<u64, String>>) {
39-
/// let n = match opt {
40-
/// Some(Ok(n)) => n,
41-
/// _ => return,
42-
/// };
43-
/// }
44-
/// ```
45-
#[clippy::version = "1.50.0"]
46-
pub COLLAPSIBLE_MATCH,
47-
style,
48-
"Nested `match` or `if let` expressions where the patterns may be \"collapsed\" together."
49-
}
50-
51-
declare_lint_pass!(CollapsibleMatch => [COLLAPSIBLE_MATCH]);
12+
use super::COLLAPSIBLE_MATCH;
5213

53-
impl<'tcx> LateLintPass<'tcx> for CollapsibleMatch {
54-
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
55-
match IfLetOrMatch::parse(cx, expr) {
56-
Some(IfLetOrMatch::Match(_, arms, _)) => {
57-
if let Some(els_arm) = arms.iter().rfind(|arm| arm_is_wild_like(cx, arm)) {
58-
for arm in arms {
59-
check_arm(cx, true, arm.pat, arm.body, arm.guard.as_ref(), Some(els_arm.body));
60-
}
61-
}
62-
},
63-
Some(IfLetOrMatch::IfLet(_, pat, body, els)) => {
64-
check_arm(cx, false, pat, body, None, els);
65-
},
66-
None => {},
14+
pub(super) fn check_match<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) {
15+
if let Some(els_arm) = arms.iter().rfind(|arm| arm_is_wild_like(cx, arm)) {
16+
for arm in arms {
17+
check_arm(cx, true, arm.pat, arm.body, arm.guard.as_ref(), Some(els_arm.body));
6718
}
6819
}
6920
}
7021

22+
pub(super) fn check_if_let<'tcx>(
23+
cx: &LateContext<'tcx>,
24+
pat: &'tcx Pat<'_>,
25+
body: &'tcx Expr<'_>,
26+
else_expr: Option<&'tcx Expr<'_>>,
27+
) {
28+
check_arm(cx, false, pat, body, None, else_expr);
29+
}
30+
7131
fn check_arm<'tcx>(
7232
cx: &LateContext<'tcx>,
7333
outer_is_match: bool,

clippy_lints/src/matches/match_like_matches.rs

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use clippy_utils::diagnostics::span_lint_and_sugg;
2+
use clippy_utils::is_wild;
23
use clippy_utils::source::snippet_with_applicability;
3-
use clippy_utils::{higher, is_wild};
44
use rustc_ast::{Attribute, LitKind};
55
use rustc_errors::Applicability;
66
use rustc_hir::{Arm, BorrowKind, Expr, ExprKind, Guard, Pat};
@@ -11,22 +11,24 @@ use rustc_span::source_map::Spanned;
1111
use super::MATCH_LIKE_MATCHES_MACRO;
1212

1313
/// 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<'_>) {
15-
if let Some(higher::IfLet {
16-
let_pat,
14+
pub(crate) fn check_if_let<'tcx>(
15+
cx: &LateContext<'tcx>,
16+
expr: &'tcx Expr<'_>,
17+
let_pat: &'tcx Pat<'_>,
18+
let_expr: &'tcx Expr<'_>,
19+
then_expr: &'tcx Expr<'_>,
20+
else_expr: &'tcx Expr<'_>,
21+
) {
22+
find_matches_sugg(
23+
cx,
1724
let_expr,
18-
if_then,
19-
if_else: Some(if_else),
20-
}) = higher::IfLet::hir(cx, expr)
21-
{
22-
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-
}
25+
IntoIterator::into_iter([
26+
(&[][..], Some(let_pat), then_expr, None),
27+
(&[][..], None, else_expr, None),
28+
]),
29+
expr,
30+
true,
31+
);
3032
}
3133

3234
pub(super) fn check_match<'tcx>(

clippy_lints/src/matches/mod.rs

Lines changed: 78 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
use clippy_utils::source::{snippet_opt, span_starts_with, walk_span_to_context};
2-
use clippy_utils::{meets_msrv, msrvs};
2+
use clippy_utils::{higher, meets_msrv, msrvs};
33
use rustc_hir::{Arm, Expr, ExprKind, Local, MatchSource, Pat};
44
use rustc_lexer::{tokenize, TokenKind};
5-
use rustc_lint::{LateContext, LateLintPass};
5+
use rustc_lint::{LateContext, LateLintPass, LintContext};
6+
use rustc_middle::lint::in_external_macro;
67
use rustc_semver::RustcVersion;
78
use rustc_session::{declare_tool_lint, impl_lint_pass};
89
use rustc_span::{Span, SpanData, SyntaxContext};
910

11+
mod collapsible_match;
1012
mod infallible_destructuring_match;
1113
mod match_as_ref;
1214
mod match_bool;
@@ -610,6 +612,44 @@ declare_clippy_lint! {
610612
"`match` or match-like `if let` that are unnecessary"
611613
}
612614

615+
declare_clippy_lint! {
616+
/// ### What it does
617+
/// Finds nested `match` or `if let` expressions where the patterns may be "collapsed" together
618+
/// without adding any branches.
619+
///
620+
/// Note that this lint is not intended to find _all_ cases where nested match patterns can be merged, but only
621+
/// cases where merging would most likely make the code more readable.
622+
///
623+
/// ### Why is this bad?
624+
/// It is unnecessarily verbose and complex.
625+
///
626+
/// ### Example
627+
/// ```rust
628+
/// fn func(opt: Option<Result<u64, String>>) {
629+
/// let n = match opt {
630+
/// Some(n) => match n {
631+
/// Ok(n) => n,
632+
/// _ => return,
633+
/// }
634+
/// None => return,
635+
/// };
636+
/// }
637+
/// ```
638+
/// Use instead:
639+
/// ```rust
640+
/// fn func(opt: Option<Result<u64, String>>) {
641+
/// let n = match opt {
642+
/// Some(Ok(n)) => n,
643+
/// _ => return,
644+
/// };
645+
/// }
646+
/// ```
647+
#[clippy::version = "1.50.0"]
648+
pub COLLAPSIBLE_MATCH,
649+
style,
650+
"Nested `match` or `if let` expressions where the patterns may be \"collapsed\" together."
651+
}
652+
613653
#[derive(Default)]
614654
pub struct Matches {
615655
msrv: Option<RustcVersion>,
@@ -644,19 +684,29 @@ impl_lint_pass!(Matches => [
644684
MATCH_LIKE_MATCHES_MACRO,
645685
MATCH_SAME_ARMS,
646686
NEEDLESS_MATCH,
687+
COLLAPSIBLE_MATCH,
647688
]);
648689

649690
impl<'tcx> LateLintPass<'tcx> for Matches {
650691
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
651-
if expr.span.from_expansion() {
692+
if in_external_macro(cx.sess(), expr.span) {
652693
return;
653694
}
695+
let from_expansion = expr.span.from_expansion();
654696

655697
if let ExprKind::Match(ex, arms, source) = expr.kind {
656698
if !span_starts_with(cx, expr.span, "match") {
657699
return;
658700
}
659-
if !contains_cfg_arm(cx, expr, ex, arms) {
701+
702+
collapsible_match::check_match(cx, arms);
703+
if !from_expansion {
704+
// These don't depend on a relationship between multiple arms
705+
match_wild_err_arm::check(cx, ex, arms);
706+
wild_in_or_pats::check(cx, arms);
707+
}
708+
709+
if !from_expansion && !contains_cfg_arm(cx, expr, ex, arms) {
660710
if source == MatchSource::Normal {
661711
if !(meets_msrv(self.msrv, msrvs::MATCHES_MACRO)
662712
&& match_like_matches::check_match(cx, expr, ex, arms))
@@ -680,16 +730,32 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
680730
}
681731
match_ref_pats::check(cx, ex, arms.iter().map(|el| el.pat), expr);
682732
}
683-
684-
// These don't depend on a relationship between multiple arms
685-
match_wild_err_arm::check(cx, ex, arms);
686-
wild_in_or_pats::check(cx, arms);
687-
} else {
688-
if meets_msrv(self.msrv, msrvs::MATCHES_MACRO) {
689-
match_like_matches::check(cx, expr);
733+
} else if let Some(if_let) = higher::IfLet::hir(cx, expr) {
734+
collapsible_match::check_if_let(cx, if_let.let_pat, if_let.if_then, if_let.if_else);
735+
if !from_expansion {
736+
if let Some(else_expr) = if_let.if_else {
737+
if meets_msrv(self.msrv, msrvs::MATCHES_MACRO) {
738+
match_like_matches::check_if_let(
739+
cx,
740+
expr,
741+
if_let.let_pat,
742+
if_let.let_expr,
743+
if_let.if_then,
744+
else_expr,
745+
);
746+
}
747+
}
748+
redundant_pattern_match::check_if_let(
749+
cx,
750+
expr,
751+
if_let.let_pat,
752+
if_let.let_expr,
753+
if_let.if_else.is_some(),
754+
);
755+
needless_match::check_if_let(cx, expr, &if_let);
690756
}
757+
} else if !from_expansion {
691758
redundant_pattern_match::check(cx, expr);
692-
needless_match::check(cx, expr);
693759
}
694760
}
695761

clippy_lints/src/matches/needless_match.rs

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -47,20 +47,18 @@ pub(crate) fn check_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>],
4747
/// some_enum
4848
/// }
4949
/// ```
50-
pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>) {
51-
if let Some(ref if_let) = higher::IfLet::hir(cx, ex) {
52-
if !is_else_clause(cx.tcx, ex) && expr_ty_matches_p_ty(cx, if_let.let_expr, ex) && check_if_let(cx, if_let) {
53-
let mut applicability = Applicability::MachineApplicable;
54-
span_lint_and_sugg(
55-
cx,
56-
NEEDLESS_MATCH,
57-
ex.span,
58-
"this if-let expression is unnecessary",
59-
"replace it with",
60-
snippet_with_applicability(cx, if_let.let_expr.span, "..", &mut applicability).to_string(),
61-
applicability,
62-
);
63-
}
50+
pub(crate) fn check_if_let<'tcx>(cx: &LateContext<'tcx>, ex: &Expr<'_>, if_let: &higher::IfLet<'tcx>) {
51+
if !is_else_clause(cx.tcx, ex) && expr_ty_matches_p_ty(cx, if_let.let_expr, ex) && check_if_let_inner(cx, if_let) {
52+
let mut applicability = Applicability::MachineApplicable;
53+
span_lint_and_sugg(
54+
cx,
55+
NEEDLESS_MATCH,
56+
ex.span,
57+
"this if-let expression is unnecessary",
58+
"replace it with",
59+
snippet_with_applicability(cx, if_let.let_expr.span, "..", &mut applicability).to_string(),
60+
applicability,
61+
);
6462
}
6563
}
6664

@@ -77,15 +75,15 @@ fn check_all_arms(cx: &LateContext<'_>, match_expr: &Expr<'_>, arms: &[Arm<'_>])
7775
true
7876
}
7977

80-
fn check_if_let(cx: &LateContext<'_>, if_let: &higher::IfLet<'_>) -> bool {
78+
fn check_if_let_inner(cx: &LateContext<'_>, if_let: &higher::IfLet<'_>) -> bool {
8179
if let Some(if_else) = if_let.if_else {
8280
if !pat_same_as_expr(if_let.let_pat, peel_blocks_with_stmt(if_let.if_then)) {
8381
return false;
8482
}
8583

8684
// Recursively check for each `else if let` phrase,
8785
if let Some(ref nested_if_let) = higher::IfLet::hir(cx, if_else) {
88-
return check_if_let(cx, nested_if_let);
86+
return check_if_let_inner(cx, nested_if_let);
8987
}
9088

9189
if matches!(if_else.kind, ExprKind::Block(..)) {

0 commit comments

Comments
 (0)