1 use clippy_utils
::diagnostics
::span_lint_and_then
;
2 use clippy_utils
::visitors
::LocalUsedVisitor
;
3 use clippy_utils
::{is_lang_ctor, path_to_local, peel_ref_operators, SpanlessEq}
;
4 use if_chain
::if_chain
;
5 use rustc_hir
::LangItem
::OptionNone
;
6 use rustc_hir
::{Arm, Expr, ExprKind, Guard, HirId, Pat, PatKind, StmtKind}
;
7 use rustc_lint
::{LateContext, LateLintPass}
;
8 use rustc_session
::{declare_lint_pass, declare_tool_lint}
;
9 use rustc_span
::{MultiSpan, Span}
;
11 declare_clippy_lint
! {
12 /// **What it does:** Finds nested `match` or `if let` expressions where the patterns may be "collapsed" together
13 /// without adding any branches.
15 /// Note that this lint is not intended to find _all_ cases where nested match patterns can be merged, but only
16 /// cases where merging would most likely make the code more readable.
18 /// **Why is this bad?** It is unnecessarily verbose and complex.
20 /// **Known problems:** None.
25 /// fn func(opt: Option<Result<u64, String>>) {
26 /// let n = match opt {
27 /// Some(n) => match n {
37 /// fn func(opt: Option<Result<u64, String>>) {
38 /// let n = match opt {
44 pub COLLAPSIBLE_MATCH
,
46 "Nested `match` or `if let` expressions where the patterns may be \"collapsed\" together."
49 declare_lint_pass
!(CollapsibleMatch
=> [COLLAPSIBLE_MATCH
]);
51 impl<'tcx
> LateLintPass
<'tcx
> for CollapsibleMatch
{
52 fn check_expr(&mut self, cx
: &LateContext
<'tcx
>, expr
: &Expr
<'tcx
>) {
53 if let ExprKind
::Match(_expr
, arms
, _source
) = expr
.kind
{
54 if let Some(wild_arm
) = arms
.iter().rfind(|arm
| arm_is_wild_like(cx
, arm
)) {
56 check_arm(arm
, wild_arm
, cx
);
63 fn check_arm
<'tcx
>(arm
: &Arm
<'tcx
>, wild_outer_arm
: &Arm
<'tcx
>, cx
: &LateContext
<'tcx
>) {
64 let expr
= strip_singleton_blocks(arm
.body
);
66 if let ExprKind
::Match(expr_in
, arms_inner
, _
) = expr
.kind
;
67 // the outer arm pattern and the inner match
68 if expr_in
.span
.ctxt() == arm
.pat
.span
.ctxt();
69 // there must be no more than two arms in the inner match for this lint
70 if arms_inner
.len() == 2;
71 // no if guards on the inner match
72 if arms_inner
.iter().all(|arm
| arm
.guard
.is_none());
73 // match expression must be a local binding
74 // match <local> { .. }
75 if let Some(binding_id
) = path_to_local(peel_ref_operators(cx
, expr_in
));
76 // one of the branches must be "wild-like"
77 if let Some(wild_inner_arm_idx
) = arms_inner
.iter().rposition(|arm_inner
| arm_is_wild_like(cx
, arm_inner
));
78 let (wild_inner_arm
, non_wild_inner_arm
) =
79 (&arms_inner
[wild_inner_arm_idx
], &arms_inner
[1 - wild_inner_arm_idx
]);
80 if !pat_contains_or(non_wild_inner_arm
.pat
);
81 // the binding must come from the pattern of the containing match arm
82 // ..<local>.. => match <local> { .. }
83 if let Some(binding_span
) = find_pat_binding(arm
.pat
, binding_id
);
84 // the "wild-like" branches must be equal
85 if SpanlessEq
::new(cx
).eq_expr(wild_inner_arm
.body
, wild_outer_arm
.body
);
86 // the binding must not be used in the if guard
87 let mut used_visitor
= LocalUsedVisitor
::new(cx
, binding_id
);
90 Some(Guard
::If(expr
) | Guard
::IfLet(_
, expr
)) => !used_visitor
.check_expr(expr
),
92 // ...or anywhere in the inner match
93 if !arms_inner
.iter().any(|arm
| used_visitor
.check_arm(arm
));
99 "unnecessary nested match",
101 let mut help_span
= MultiSpan
::from_spans(vec
![binding_span
, non_wild_inner_arm
.pat
.span
]);
102 help_span
.push_span_label(binding_span
, "replace this binding".into());
103 help_span
.push_span_label(non_wild_inner_arm
.pat
.span
, "with this pattern".into());
104 diag
.span_help(help_span
, "the outer pattern can be modified to include the inner pattern");
111 fn strip_singleton_blocks
<'hir
>(mut expr
: &'hir Expr
<'hir
>) -> &'hir Expr
<'hir
> {
112 while let ExprKind
::Block(block
, _
) = expr
.kind
{
113 match (block
.stmts
, block
.expr
) {
114 ([stmt
], None
) => match stmt
.kind
{
115 StmtKind
::Expr(e
) | StmtKind
::Semi(e
) => expr
= e
,
118 ([], Some(e
)) => expr
= e
,
125 /// A "wild-like" pattern is wild ("_") or `None`.
126 /// For this lint to apply, both the outer and inner match expressions
127 /// must have "wild-like" branches that can be combined.
128 fn arm_is_wild_like(cx
: &LateContext
<'_
>, arm
: &Arm
<'_
>) -> bool
{
129 if arm
.guard
.is_some() {
133 PatKind
::Binding(..) | PatKind
::Wild
=> true,
134 PatKind
::Path(ref qpath
) => is_lang_ctor(cx
, qpath
, OptionNone
),
139 fn find_pat_binding(pat
: &Pat
<'_
>, hir_id
: HirId
) -> Option
<Span
> {
141 pat
.walk_short(|p
| match &p
.kind
{
142 // ignore OR patterns
143 PatKind
::Or(_
) => false,
144 PatKind
::Binding(_bm
, _
, _ident
, _
) => {
145 let found
= p
.hir_id
== hir_id
;
156 fn pat_contains_or(pat
: &Pat
<'_
>) -> bool
{
157 let mut result
= false;
159 let is_or
= matches
!(p
.kind
, PatKind
::Or(_
));