]>
Commit | Line | Data |
---|---|---|
5099ac24 | 1 | use clippy_utils::diagnostics::span_lint_and_sugg; |
923072b8 | 2 | use clippy_utils::is_wild; |
5099ac24 | 3 | use clippy_utils::source::snippet_with_applicability; |
f2b60f7d | 4 | use clippy_utils::span_contains_comment; |
5099ac24 FG |
5 | use rustc_ast::{Attribute, LitKind}; |
6 | use rustc_errors::Applicability; | |
5e7ed085 | 7 | use rustc_hir::{Arm, BorrowKind, Expr, ExprKind, Guard, Pat}; |
f2b60f7d | 8 | use rustc_lint::{LateContext, LintContext}; |
5099ac24 FG |
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!` | |
923072b8 FG |
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, | |
5099ac24 | 25 | let_expr, |
923072b8 FG |
26 | IntoIterator::into_iter([ |
27 | (&[][..], Some(let_pat), then_expr, None), | |
28 | (&[][..], None, else_expr, None), | |
29 | ]), | |
30 | expr, | |
31 | true, | |
32 | ); | |
5e7ed085 | 33 | } |
5099ac24 | 34 | |
5e7ed085 FG |
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 | ) | |
5099ac24 FG |
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! { | |
f2b60f7d | 80 | if !span_contains_comment(cx.sess().source_map(), expr.span); |
5099ac24 FG |
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(); | |
064997fb FG |
86 | if let Some(b0) = find_bool_lit(&first_expr.kind); |
87 | if let Some(b1) = find_bool_lit(&last_expr.kind); | |
5099ac24 FG |
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| { | |
064997fb | 93 | find_bool_lit(&arm.2.kind).map_or(false, |b| b == b0) && arm.3.is_none() && arm.0.is_empty() |
5099ac24 FG |
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 }` | |
064997fb | 149 | fn find_bool_lit(ex: &ExprKind<'_>) -> Option<bool> { |
5099ac24 FG |
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 | _, | |
064997fb | 161 | ) => { |
5099ac24 FG |
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 | } |