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