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