]> git.proxmox.com Git - rustc.git/blob - src/tools/clippy/clippy_lints/src/matches/single_match.rs
New upstream version 1.60.0+dfsg1
[rustc.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, 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 // list of candidate `Enum`s we know will never get any more members
144 let candidates = &[
145 (&paths::COW, "Borrowed"),
146 (&paths::COW, "Cow::Borrowed"),
147 (&paths::COW, "Cow::Owned"),
148 (&paths::COW, "Owned"),
149 (&paths::OPTION, "None"),
150 (&paths::RESULT, "Err"),
151 (&paths::RESULT, "Ok"),
152 ];
153
154 // We want to suggest to exclude an arm that contains only wildcards or forms the exhaustive
155 // match with the second branch, without enum variants in matches.
156 if !contains_only_wilds(arms[1].pat) && !form_exhaustive_matches(arms[0].pat, arms[1].pat) {
157 return;
158 }
159
160 let mut paths_and_types = Vec::new();
161 if !collect_pat_paths(&mut paths_and_types, cx, arms[1].pat, ty) {
162 return;
163 }
164
165 let in_candidate_enum = |path_info: &(String, Ty<'_>)| -> bool {
166 let (path, ty) = path_info;
167 for &(ty_path, pat_path) in candidates {
168 if path == pat_path && match_type(cx, *ty, ty_path) {
169 return true;
170 }
171 }
172 false
173 };
174 if paths_and_types.iter().all(in_candidate_enum) {
175 report_single_pattern(cx, ex, arms, expr, els);
176 }
177 }
178
179 /// Collects paths and their types from the given patterns. Returns true if the given pattern could
180 /// be simplified, false otherwise.
181 fn collect_pat_paths<'a>(acc: &mut Vec<(String, Ty<'a>)>, cx: &LateContext<'a>, pat: &Pat<'_>, ty: Ty<'a>) -> bool {
182 match pat.kind {
183 PatKind::Wild => true,
184 PatKind::Tuple(inner, _) => inner.iter().all(|p| {
185 let p_ty = cx.typeck_results().pat_ty(p);
186 collect_pat_paths(acc, cx, p, p_ty)
187 }),
188 PatKind::TupleStruct(ref path, ..) => {
189 let path = rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| {
190 s.print_qpath(path, false);
191 });
192 acc.push((path, ty));
193 true
194 },
195 PatKind::Binding(BindingAnnotation::Unannotated, .., ident, None) => {
196 acc.push((ident.to_string(), ty));
197 true
198 },
199 PatKind::Path(ref path) => {
200 let path = rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| {
201 s.print_qpath(path, false);
202 });
203 acc.push((path, ty));
204 true
205 },
206 _ => false,
207 }
208 }
209
210 /// Returns true if the given arm of pattern matching contains wildcard patterns.
211 fn contains_only_wilds(pat: &Pat<'_>) -> bool {
212 match pat.kind {
213 PatKind::Wild => true,
214 PatKind::Tuple(inner, _) | PatKind::TupleStruct(_, inner, ..) => inner.iter().all(contains_only_wilds),
215 _ => false,
216 }
217 }
218
219 /// Returns true if the given patterns forms only exhaustive matches that don't contain enum
220 /// patterns without a wildcard.
221 fn form_exhaustive_matches(left: &Pat<'_>, right: &Pat<'_>) -> bool {
222 match (&left.kind, &right.kind) {
223 (PatKind::Wild, _) | (_, PatKind::Wild) => true,
224 (PatKind::Tuple(left_in, left_pos), PatKind::Tuple(right_in, right_pos)) => {
225 // We don't actually know the position and the presence of the `..` (dotdot) operator
226 // in the arms, so we need to evaluate the correct offsets here in order to iterate in
227 // both arms at the same time.
228 let len = max(
229 left_in.len() + {
230 if left_pos.is_some() { 1 } else { 0 }
231 },
232 right_in.len() + {
233 if right_pos.is_some() { 1 } else { 0 }
234 },
235 );
236 let mut left_pos = left_pos.unwrap_or(usize::MAX);
237 let mut right_pos = right_pos.unwrap_or(usize::MAX);
238 let mut left_dot_space = 0;
239 let mut right_dot_space = 0;
240 for i in 0..len {
241 let mut found_dotdot = false;
242 if i == left_pos {
243 left_dot_space += 1;
244 if left_dot_space < len - left_in.len() {
245 left_pos += 1;
246 }
247 found_dotdot = true;
248 }
249 if i == right_pos {
250 right_dot_space += 1;
251 if right_dot_space < len - right_in.len() {
252 right_pos += 1;
253 }
254 found_dotdot = true;
255 }
256 if found_dotdot {
257 continue;
258 }
259 if !contains_only_wilds(&left_in[i - left_dot_space])
260 && !contains_only_wilds(&right_in[i - right_dot_space])
261 {
262 return false;
263 }
264 }
265 true
266 },
267 _ => false,
268 }
269 }