]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/matches/single_match.rs
Refactored the candidate type checking
[rust.git] / clippy_lints / src / matches / single_match.rs
1 use clippy_utils::diagnostics::span_lint_and_sugg;
2 use clippy_utils::source::{expr_block, snippet};
3 use clippy_utils::ty::{implements_trait, match_type, peel_mid_ty_refs};
4 use clippy_utils::{
5     is_lint_allowed, is_unit_expr, is_wild, paths, peel_blocks, peel_hir_pat_refs, peel_n_hir_expr_refs,
6 };
7 use core::cmp::max;
8 use rustc_errors::Applicability;
9 use rustc_hir::{Arm, BindingAnnotation, Block, Expr, ExprKind, Pat, PatKind};
10 use rustc_lint::LateContext;
11 use rustc_middle::ty::{self, Ty};
12
13 use super::{MATCH_BOOL, SINGLE_MATCH, SINGLE_MATCH_ELSE};
14
15 #[rustfmt::skip]
16 pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) {
17     if arms.len() == 2 && arms[0].guard.is_none() && arms[1].guard.is_none() {
18         if expr.span.from_expansion() {
19             // Don't lint match expressions present in
20             // macro_rules! block
21             return;
22         }
23         if let PatKind::Or(..) = arms[0].pat.kind {
24             // don't lint for or patterns for now, this makes
25             // the lint noisy in unnecessary situations
26             return;
27         }
28         let els = arms[1].body;
29         let els = if is_unit_expr(peel_blocks(els)) {
30             None
31         } else if let ExprKind::Block(Block { stmts, expr: block_expr, .. }, _) = els.kind {
32             if stmts.len() == 1 && block_expr.is_none() || stmts.is_empty() && block_expr.is_some() {
33                 // single statement/expr "else" block, don't lint
34                 return;
35             }
36             // block with 2+ statements or 1 expr and 1+ statement
37             Some(els)
38         } else {
39             // not a block, don't lint
40             return;
41         };
42
43         let ty = cx.typeck_results().expr_ty(ex);
44         if *ty.kind() != ty::Bool || is_lint_allowed(cx, MATCH_BOOL, ex.hir_id) {
45             check_single_pattern(cx, ex, arms, expr, els);
46             check_opt_like(cx, ex, arms, expr, ty, els);
47         }
48     }
49 }
50
51 fn check_single_pattern(
52     cx: &LateContext<'_>,
53     ex: &Expr<'_>,
54     arms: &[Arm<'_>],
55     expr: &Expr<'_>,
56     els: Option<&Expr<'_>>,
57 ) {
58     if is_wild(arms[1].pat) {
59         report_single_pattern(cx, ex, arms, expr, els);
60     }
61 }
62
63 fn report_single_pattern(
64     cx: &LateContext<'_>,
65     ex: &Expr<'_>,
66     arms: &[Arm<'_>],
67     expr: &Expr<'_>,
68     els: Option<&Expr<'_>>,
69 ) {
70     let lint = if els.is_some() { SINGLE_MATCH_ELSE } else { SINGLE_MATCH };
71     let els_str = els.map_or(String::new(), |els| {
72         format!(" else {}", expr_block(cx, els, None, "..", Some(expr.span)))
73     });
74
75     let (pat, pat_ref_count) = peel_hir_pat_refs(arms[0].pat);
76     let (msg, sugg) = if_chain! {
77         if let PatKind::Path(_) | PatKind::Lit(_) = pat.kind;
78         let (ty, ty_ref_count) = peel_mid_ty_refs(cx.typeck_results().expr_ty(ex));
79         if let Some(spe_trait_id) = cx.tcx.lang_items().structural_peq_trait();
80         if let Some(pe_trait_id) = cx.tcx.lang_items().eq_trait();
81         if ty.is_integral() || ty.is_char() || ty.is_str()
82             || (implements_trait(cx, ty, spe_trait_id, &[])
83                 && implements_trait(cx, ty, pe_trait_id, &[ty.into()]));
84         then {
85             // scrutinee derives PartialEq and the pattern is a constant.
86             let pat_ref_count = match pat.kind {
87                 // string literals are already a reference.
88                 PatKind::Lit(Expr { kind: ExprKind::Lit(lit), .. }) if lit.node.is_str() => pat_ref_count + 1,
89                 _ => pat_ref_count,
90             };
91             // References are only implicitly added to the pattern, so no overflow here.
92             // e.g. will work: match &Some(_) { Some(_) => () }
93             // will not: match Some(_) { &Some(_) => () }
94             let ref_count_diff = ty_ref_count - pat_ref_count;
95
96             // Try to remove address of expressions first.
97             let (ex, removed) = peel_n_hir_expr_refs(ex, ref_count_diff);
98             let ref_count_diff = ref_count_diff - removed;
99
100             let msg = "you seem to be trying to use `match` for an equality check. Consider using `if`";
101             let sugg = format!(
102                 "if {} == {}{} {}{}",
103                 snippet(cx, ex.span, ".."),
104                 // PartialEq for different reference counts may not exist.
105                 "&".repeat(ref_count_diff),
106                 snippet(cx, arms[0].pat.span, ".."),
107                 expr_block(cx, arms[0].body, None, "..", Some(expr.span)),
108                 els_str,
109             );
110             (msg, sugg)
111         } else {
112             let msg = "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`";
113             let sugg = format!(
114                 "if let {} = {} {}{}",
115                 snippet(cx, arms[0].pat.span, ".."),
116                 snippet(cx, ex.span, ".."),
117                 expr_block(cx, arms[0].body, None, "..", Some(expr.span)),
118                 els_str,
119             );
120             (msg, sugg)
121         }
122     };
123
124     span_lint_and_sugg(
125         cx,
126         lint,
127         expr.span,
128         msg,
129         "try this",
130         sugg,
131         Applicability::HasPlaceholders,
132     );
133 }
134
135 fn check_opt_like<'a>(
136     cx: &LateContext<'a>,
137     ex: &Expr<'_>,
138     arms: &[Arm<'_>],
139     expr: &Expr<'_>,
140     ty: Ty<'a>,
141     els: Option<&Expr<'_>>,
142 ) {
143     // We want to suggest to exclude an arm that contains only wildcards or forms the exhaustive
144     // match with the second branch, without enum variants in matches.
145     if !contains_only_wilds(arms[1].pat) && !form_exhaustive_matches(cx, ty, arms[0].pat, arms[1].pat) {
146         return;
147     }
148
149     let mut paths_and_types = Vec::new();
150     if !collect_pat_paths(&mut paths_and_types, cx, arms[1].pat, ty) {
151         return;
152     }
153
154     if paths_and_types.iter().all(|ty| in_candidate_enum(cx, *ty)) {
155         report_single_pattern(cx, ex, arms, expr, els);
156     }
157 }
158
159 fn in_candidate_enum<'a>(cx: &LateContext<'a>, ty: Ty<'_>) -> bool {
160     // list of candidate `Enum`s we know will never get any more members
161     let candidates = [&paths::COW, &paths::OPTION, &paths::RESULT];
162
163     for candidate_ty in candidates {
164         if match_type(cx, ty, candidate_ty) {
165             return true;
166         }
167     }
168     false
169 }
170
171 /// Collects paths and their types from the given patterns. Returns true if the given pattern could
172 /// be simplified, false otherwise.
173 fn collect_pat_paths<'a>(acc: &mut Vec<Ty<'a>>, cx: &LateContext<'a>, pat: &Pat<'_>, ty: Ty<'a>) -> bool {
174     match pat.kind {
175         PatKind::Wild => true,
176         PatKind::Tuple(inner, _) => inner.iter().all(|p| {
177             let p_ty = cx.typeck_results().pat_ty(p);
178             collect_pat_paths(acc, cx, p, p_ty)
179         }),
180         PatKind::TupleStruct(..) | PatKind::Binding(BindingAnnotation::Unannotated, .., None) | PatKind::Path(_) => {
181             acc.push(ty);
182             true
183         },
184         _ => false,
185     }
186 }
187
188 /// Returns true if the given arm of pattern matching contains wildcard patterns.
189 fn contains_only_wilds(pat: &Pat<'_>) -> bool {
190     match pat.kind {
191         PatKind::Wild => true,
192         PatKind::Tuple(inner, _) | PatKind::TupleStruct(_, inner, ..) => inner.iter().all(contains_only_wilds),
193         _ => false,
194     }
195 }
196
197 /// Returns true if the given patterns forms only exhaustive matches that don't contain enum
198 /// patterns without a wildcard.
199 fn form_exhaustive_matches<'a>(cx: &LateContext<'a>, ty: Ty<'a>, left: &Pat<'_>, right: &Pat<'_>) -> bool {
200     match (&left.kind, &right.kind) {
201         (PatKind::Wild, _) | (_, PatKind::Wild) => true,
202         (PatKind::Tuple(left_in, left_pos), PatKind::Tuple(right_in, right_pos)) => {
203             // We don't actually know the position and the presence of the `..` (dotdot) operator
204             // in the arms, so we need to evaluate the correct offsets here in order to iterate in
205             // both arms at the same time.
206             let len = max(
207                 left_in.len() + {
208                     if left_pos.is_some() { 1 } else { 0 }
209                 },
210                 right_in.len() + {
211                     if right_pos.is_some() { 1 } else { 0 }
212                 },
213             );
214             let mut left_pos = left_pos.unwrap_or(usize::MAX);
215             let mut right_pos = right_pos.unwrap_or(usize::MAX);
216             let mut left_dot_space = 0;
217             let mut right_dot_space = 0;
218             for i in 0..len {
219                 let mut found_dotdot = false;
220                 if i == left_pos {
221                     left_dot_space += 1;
222                     if left_dot_space < len - left_in.len() {
223                         left_pos += 1;
224                     }
225                     found_dotdot = true;
226                 }
227                 if i == right_pos {
228                     right_dot_space += 1;
229                     if right_dot_space < len - right_in.len() {
230                         right_pos += 1;
231                     }
232                     found_dotdot = true;
233                 }
234                 if found_dotdot {
235                     continue;
236                 }
237                 if !contains_only_wilds(&left_in[i - left_dot_space])
238                     && !contains_only_wilds(&right_in[i - right_dot_space])
239                 {
240                     return false;
241                 }
242             }
243             true
244         },
245         (PatKind::TupleStruct(..), PatKind::Path(_) | PatKind::TupleStruct(..)) => in_candidate_enum(cx, ty),
246         _ => false,
247     }
248 }