]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/matches/match_like_matches.rs
2e1f7646eb400b3924ff813738c90091205e139f
[rust.git] / src / tools / clippy / clippy_lints / src / matches / match_like_matches.rs
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::{Arm, BorrowKind, Expr, ExprKind, Guard, 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<'_>) {
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         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
32 pub(super) fn check_match<'tcx>(
33     cx: &LateContext<'tcx>,
34     e: &'tcx Expr<'_>,
35     scrutinee: &'tcx Expr<'_>,
36     arms: &'tcx [Arm<'tcx>],
37 ) -> bool {
38     find_matches_sugg(
39         cx,
40         scrutinee,
41         arms.iter().map(|arm| {
42             (
43                 cx.tcx.hir().attrs(arm.hir_id),
44                 Some(arm.pat),
45                 arm.body,
46                 arm.guard.as_ref(),
47             )
48         }),
49         e,
50         false,
51     )
52 }
53
54 /// Lint a `match` or `if let` for replacement by `matches!`
55 fn find_matches_sugg<'a, 'b, I>(
56     cx: &LateContext<'_>,
57     ex: &Expr<'_>,
58     mut iter: I,
59     expr: &Expr<'_>,
60     is_if_let: bool,
61 ) -> bool
62 where
63     'b: 'a,
64     I: Clone
65         + DoubleEndedIterator
66         + ExactSizeIterator
67         + Iterator<
68             Item = (
69                 &'a [Attribute],
70                 Option<&'a Pat<'b>>,
71                 &'a Expr<'b>,
72                 Option<&'a Guard<'b>>,
73             ),
74         >,
75 {
76     if_chain! {
77         if iter.len() >= 2;
78         if cx.typeck_results().expr_ty(expr).is_bool();
79         if let Some((_, last_pat_opt, last_expr, _)) = iter.next_back();
80         let iter_without_last = iter.clone();
81         if let Some((first_attrs, _, first_expr, first_guard)) = iter.next();
82         if let Some(b0) = find_bool_lit(&first_expr.kind, is_if_let);
83         if let Some(b1) = find_bool_lit(&last_expr.kind, is_if_let);
84         if b0 != b1;
85         if first_guard.is_none() || iter.len() == 0;
86         if first_attrs.is_empty();
87         if iter
88             .all(|arm| {
89                 find_bool_lit(&arm.2.kind, is_if_let).map_or(false, |b| b == b0) && arm.3.is_none() && arm.0.is_empty()
90             });
91         then {
92             if let Some(last_pat) = last_pat_opt {
93                 if !is_wild(last_pat) {
94                     return false;
95                 }
96             }
97
98             // The suggestion may be incorrect, because some arms can have `cfg` attributes
99             // evaluated into `false` and so such arms will be stripped before.
100             let mut applicability = Applicability::MaybeIncorrect;
101             let pat = {
102                 use itertools::Itertools as _;
103                 iter_without_last
104                     .filter_map(|arm| {
105                         let pat_span = arm.1?.span;
106                         Some(snippet_with_applicability(cx, pat_span, "..", &mut applicability))
107                     })
108                     .join(" | ")
109             };
110             let pat_and_guard = if let Some(Guard::If(g)) = first_guard {
111                 format!("{} if {}", pat, snippet_with_applicability(cx, g.span, "..", &mut applicability))
112             } else {
113                 pat
114             };
115
116             // strip potential borrows (#6503), but only if the type is a reference
117             let mut ex_new = ex;
118             if let ExprKind::AddrOf(BorrowKind::Ref, .., ex_inner) = ex.kind {
119                 if let ty::Ref(..) = cx.typeck_results().expr_ty(ex_inner).kind() {
120                     ex_new = ex_inner;
121                 }
122             };
123             span_lint_and_sugg(
124                 cx,
125                 MATCH_LIKE_MATCHES_MACRO,
126                 expr.span,
127                 &format!("{} expression looks like `matches!` macro", if is_if_let { "if let .. else" } else { "match" }),
128                 "try this",
129                 format!(
130                     "{}matches!({}, {})",
131                     if b0 { "" } else { "!" },
132                     snippet_with_applicability(cx, ex_new.span, "..", &mut applicability),
133                     pat_and_guard,
134                 ),
135                 applicability,
136             );
137             true
138         } else {
139             false
140         }
141     }
142 }
143
144 /// Extract a `bool` or `{ bool }`
145 fn find_bool_lit(ex: &ExprKind<'_>, is_if_let: bool) -> Option<bool> {
146     match ex {
147         ExprKind::Lit(Spanned {
148             node: LitKind::Bool(b), ..
149         }) => Some(*b),
150         ExprKind::Block(
151             rustc_hir::Block {
152                 stmts: &[],
153                 expr: Some(exp),
154                 ..
155             },
156             _,
157         ) if is_if_let => {
158             if let ExprKind::Lit(Spanned {
159                 node: LitKind::Bool(b), ..
160             }) = exp.kind
161             {
162                 Some(b)
163             } else {
164                 None
165             }
166         },
167         _ => None,
168     }
169 }