]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/matches/match_like_matches.rs
Split out `overlapping_arms`
[rust.git] / 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::{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 }