]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/matches/redundant_pattern_match.rs
Rollup merge of #96719 - mbartlett21:patch-4, r=Dylan-DPC
[rust.git] / src / tools / clippy / clippy_lints / src / matches / redundant_pattern_match.rs
1 use super::REDUNDANT_PATTERN_MATCHING;
2 use clippy_utils::diagnostics::span_lint_and_then;
3 use clippy_utils::source::snippet;
4 use clippy_utils::sugg::Sugg;
5 use clippy_utils::ty::needs_ordered_drop;
6 use clippy_utils::{higher, match_def_path};
7 use clippy_utils::{is_lang_ctor, is_trait_method, paths};
8 use if_chain::if_chain;
9 use rustc_ast::ast::LitKind;
10 use rustc_errors::Applicability;
11 use rustc_hir::LangItem::{OptionNone, PollPending};
12 use rustc_hir::{
13     intravisit::{walk_expr, Visitor},
14     Arm, Block, Expr, ExprKind, Node, Pat, PatKind, QPath, UnOp,
15 };
16 use rustc_lint::LateContext;
17 use rustc_middle::ty::{self, subst::GenericArgKind, DefIdTree, Ty};
18 use rustc_span::sym;
19
20 pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
21     if let Some(higher::WhileLet { let_pat, let_expr, .. }) = higher::WhileLet::hir(expr) {
22         find_sugg_for_if_let(cx, expr, let_pat, let_expr, "while", false);
23     }
24 }
25
26 pub(super) fn check_if_let<'tcx>(
27     cx: &LateContext<'tcx>,
28     expr: &'tcx Expr<'_>,
29     pat: &'tcx Pat<'_>,
30     scrutinee: &'tcx Expr<'_>,
31     has_else: bool,
32 ) {
33     find_sugg_for_if_let(cx, expr, pat, scrutinee, "if", has_else);
34 }
35
36 // Extract the generic arguments out of a type
37 fn try_get_generic_ty(ty: Ty<'_>, index: usize) -> Option<Ty<'_>> {
38     if_chain! {
39         if let ty::Adt(_, subs) = ty.kind();
40         if let Some(sub) = subs.get(index);
41         if let GenericArgKind::Type(sub_ty) = sub.unpack();
42         then {
43             Some(sub_ty)
44         } else {
45             None
46         }
47     }
48 }
49
50 // Checks if there are any temporaries created in the given expression for which drop order
51 // matters.
52 fn temporaries_need_ordered_drop<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
53     struct V<'a, 'tcx> {
54         cx: &'a LateContext<'tcx>,
55         res: bool,
56     }
57     impl<'a, 'tcx> Visitor<'tcx> for V<'a, 'tcx> {
58         fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
59             match expr.kind {
60                 // Taking the reference of a value leaves a temporary
61                 // e.g. In `&String::new()` the string is a temporary value.
62                 // Remaining fields are temporary values
63                 // e.g. In `(String::new(), 0).1` the string is a temporary value.
64                 ExprKind::AddrOf(_, _, expr) | ExprKind::Field(expr, _) => {
65                     if !matches!(expr.kind, ExprKind::Path(_)) {
66                         if needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(expr)) {
67                             self.res = true;
68                         } else {
69                             self.visit_expr(expr);
70                         }
71                     }
72                 },
73                 // the base type is always taken by reference.
74                 // e.g. In `(vec![0])[0]` the vector is a temporary value.
75                 ExprKind::Index(base, index) => {
76                     if !matches!(base.kind, ExprKind::Path(_)) {
77                         if needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(base)) {
78                             self.res = true;
79                         } else {
80                             self.visit_expr(base);
81                         }
82                     }
83                     self.visit_expr(index);
84                 },
85                 // Method calls can take self by reference.
86                 // e.g. In `String::new().len()` the string is a temporary value.
87                 ExprKind::MethodCall(_, [self_arg, args @ ..], _) => {
88                     if !matches!(self_arg.kind, ExprKind::Path(_)) {
89                         let self_by_ref = self
90                             .cx
91                             .typeck_results()
92                             .type_dependent_def_id(expr.hir_id)
93                             .map_or(false, |id| self.cx.tcx.fn_sig(id).skip_binder().inputs()[0].is_ref());
94                         if self_by_ref && needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(self_arg)) {
95                             self.res = true;
96                         } else {
97                             self.visit_expr(self_arg);
98                         }
99                     }
100                     args.iter().for_each(|arg| self.visit_expr(arg));
101                 },
102                 // Either explicitly drops values, or changes control flow.
103                 ExprKind::DropTemps(_)
104                 | ExprKind::Ret(_)
105                 | ExprKind::Break(..)
106                 | ExprKind::Yield(..)
107                 | ExprKind::Block(Block { expr: None, .. }, _)
108                 | ExprKind::Loop(..) => (),
109
110                 // Only consider the final expression.
111                 ExprKind::Block(Block { expr: Some(expr), .. }, _) => self.visit_expr(expr),
112
113                 _ => walk_expr(self, expr),
114             }
115         }
116     }
117
118     let mut v = V { cx, res: false };
119     v.visit_expr(expr);
120     v.res
121 }
122
123 fn find_sugg_for_if_let<'tcx>(
124     cx: &LateContext<'tcx>,
125     expr: &'tcx Expr<'_>,
126     let_pat: &Pat<'_>,
127     let_expr: &'tcx Expr<'_>,
128     keyword: &'static str,
129     has_else: bool,
130 ) {
131     // also look inside refs
132     // if we have &None for example, peel it so we can detect "if let None = x"
133     let check_pat = match let_pat.kind {
134         PatKind::Ref(inner, _mutability) => inner,
135         _ => let_pat,
136     };
137     let op_ty = cx.typeck_results().expr_ty(let_expr);
138     // Determine which function should be used, and the type contained by the corresponding
139     // variant.
140     let (good_method, inner_ty) = match check_pat.kind {
141         PatKind::TupleStruct(ref qpath, [sub_pat], _) => {
142             if let PatKind::Wild = sub_pat.kind {
143                 let res = cx.typeck_results().qpath_res(qpath, check_pat.hir_id);
144                 let Some(id) = res.opt_def_id().map(|ctor_id| cx.tcx.parent(ctor_id)) else { return };
145                 let lang_items = cx.tcx.lang_items();
146                 if Some(id) == lang_items.result_ok_variant() {
147                     ("is_ok()", try_get_generic_ty(op_ty, 0).unwrap_or(op_ty))
148                 } else if Some(id) == lang_items.result_err_variant() {
149                     ("is_err()", try_get_generic_ty(op_ty, 1).unwrap_or(op_ty))
150                 } else if Some(id) == lang_items.option_some_variant() {
151                     ("is_some()", op_ty)
152                 } else if Some(id) == lang_items.poll_ready_variant() {
153                     ("is_ready()", op_ty)
154                 } else if match_def_path(cx, id, &paths::IPADDR_V4) {
155                     ("is_ipv4()", op_ty)
156                 } else if match_def_path(cx, id, &paths::IPADDR_V6) {
157                     ("is_ipv6()", op_ty)
158                 } else {
159                     return;
160                 }
161             } else {
162                 return;
163             }
164         },
165         PatKind::Path(ref path) => {
166             let method = if is_lang_ctor(cx, path, OptionNone) {
167                 "is_none()"
168             } else if is_lang_ctor(cx, path, PollPending) {
169                 "is_pending()"
170             } else {
171                 return;
172             };
173             // `None` and `Pending` don't have an inner type.
174             (method, cx.tcx.types.unit)
175         },
176         _ => return,
177     };
178
179     // If this is the last expression in a block or there is an else clause then the whole
180     // type needs to be considered, not just the inner type of the branch being matched on.
181     // Note the last expression in a block is dropped after all local bindings.
182     let check_ty = if has_else
183         || (keyword == "if" && matches!(cx.tcx.hir().parent_iter(expr.hir_id).next(), Some((_, Node::Block(..)))))
184     {
185         op_ty
186     } else {
187         inner_ty
188     };
189
190     // All temporaries created in the scrutinee expression are dropped at the same time as the
191     // scrutinee would be, so they have to be considered as well.
192     // e.g. in `if let Some(x) = foo.lock().unwrap().baz.as_ref() { .. }` the lock will be held
193     // for the duration if body.
194     let needs_drop = needs_ordered_drop(cx, check_ty) || temporaries_need_ordered_drop(cx, let_expr);
195
196     // check that `while_let_on_iterator` lint does not trigger
197     if_chain! {
198         if keyword == "while";
199         if let ExprKind::MethodCall(method_path, _, _) = let_expr.kind;
200         if method_path.ident.name == sym::next;
201         if is_trait_method(cx, let_expr, sym::Iterator);
202         then {
203             return;
204         }
205     }
206
207     let result_expr = match &let_expr.kind {
208         ExprKind::AddrOf(_, _, borrowed) => borrowed,
209         ExprKind::Unary(UnOp::Deref, deref) => deref,
210         _ => let_expr,
211     };
212
213     span_lint_and_then(
214         cx,
215         REDUNDANT_PATTERN_MATCHING,
216         let_pat.span,
217         &format!("redundant pattern matching, consider using `{}`", good_method),
218         |diag| {
219             // if/while let ... = ... { ... }
220             // ^^^^^^^^^^^^^^^^^^^^^^^^^^^
221             let expr_span = expr.span;
222
223             // if/while let ... = ... { ... }
224             //                 ^^^
225             let op_span = result_expr.span.source_callsite();
226
227             // if/while let ... = ... { ... }
228             // ^^^^^^^^^^^^^^^^^^^
229             let span = expr_span.until(op_span.shrink_to_hi());
230
231             let app = if needs_drop {
232                 Applicability::MaybeIncorrect
233             } else {
234                 Applicability::MachineApplicable
235             };
236
237             let sugg = Sugg::hir_with_macro_callsite(cx, result_expr, "_")
238                 .maybe_par()
239                 .to_string();
240
241             diag.span_suggestion(span, "try this", format!("{} {}.{}", keyword, sugg, good_method), app);
242
243             if needs_drop {
244                 diag.note("this will change drop order of the result, as well as all temporaries");
245                 diag.note("add `#[allow(clippy::redundant_pattern_matching)]` if this is important");
246             }
247         },
248     );
249 }
250
251 pub(super) fn check_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, op: &Expr<'_>, arms: &[Arm<'_>]) {
252     if arms.len() == 2 {
253         let node_pair = (&arms[0].pat.kind, &arms[1].pat.kind);
254
255         let found_good_method = match node_pair {
256             (
257                 PatKind::TupleStruct(ref path_left, patterns_left, _),
258                 PatKind::TupleStruct(ref path_right, patterns_right, _),
259             ) if patterns_left.len() == 1 && patterns_right.len() == 1 => {
260                 if let (PatKind::Wild, PatKind::Wild) = (&patterns_left[0].kind, &patterns_right[0].kind) {
261                     find_good_method_for_match(
262                         cx,
263                         arms,
264                         path_left,
265                         path_right,
266                         &paths::RESULT_OK,
267                         &paths::RESULT_ERR,
268                         "is_ok()",
269                         "is_err()",
270                     )
271                     .or_else(|| {
272                         find_good_method_for_match(
273                             cx,
274                             arms,
275                             path_left,
276                             path_right,
277                             &paths::IPADDR_V4,
278                             &paths::IPADDR_V6,
279                             "is_ipv4()",
280                             "is_ipv6()",
281                         )
282                     })
283                 } else {
284                     None
285                 }
286             },
287             (PatKind::TupleStruct(ref path_left, patterns, _), PatKind::Path(ref path_right))
288             | (PatKind::Path(ref path_left), PatKind::TupleStruct(ref path_right, patterns, _))
289                 if patterns.len() == 1 =>
290             {
291                 if let PatKind::Wild = patterns[0].kind {
292                     find_good_method_for_match(
293                         cx,
294                         arms,
295                         path_left,
296                         path_right,
297                         &paths::OPTION_SOME,
298                         &paths::OPTION_NONE,
299                         "is_some()",
300                         "is_none()",
301                     )
302                     .or_else(|| {
303                         find_good_method_for_match(
304                             cx,
305                             arms,
306                             path_left,
307                             path_right,
308                             &paths::POLL_READY,
309                             &paths::POLL_PENDING,
310                             "is_ready()",
311                             "is_pending()",
312                         )
313                     })
314                 } else {
315                     None
316                 }
317             },
318             _ => None,
319         };
320
321         if let Some(good_method) = found_good_method {
322             let span = expr.span.to(op.span);
323             let result_expr = match &op.kind {
324                 ExprKind::AddrOf(_, _, borrowed) => borrowed,
325                 _ => op,
326             };
327             span_lint_and_then(
328                 cx,
329                 REDUNDANT_PATTERN_MATCHING,
330                 expr.span,
331                 &format!("redundant pattern matching, consider using `{}`", good_method),
332                 |diag| {
333                     diag.span_suggestion(
334                         span,
335                         "try this",
336                         format!("{}.{}", snippet(cx, result_expr.span, "_"), good_method),
337                         Applicability::MaybeIncorrect, // snippet
338                     );
339                 },
340             );
341         }
342     }
343 }
344
345 #[expect(clippy::too_many_arguments)]
346 fn find_good_method_for_match<'a>(
347     cx: &LateContext<'_>,
348     arms: &[Arm<'_>],
349     path_left: &QPath<'_>,
350     path_right: &QPath<'_>,
351     expected_left: &[&str],
352     expected_right: &[&str],
353     should_be_left: &'a str,
354     should_be_right: &'a str,
355 ) -> Option<&'a str> {
356     let left_id = cx
357         .typeck_results()
358         .qpath_res(path_left, arms[0].pat.hir_id)
359         .opt_def_id()?;
360     let right_id = cx
361         .typeck_results()
362         .qpath_res(path_right, arms[1].pat.hir_id)
363         .opt_def_id()?;
364     let body_node_pair = if match_def_path(cx, left_id, expected_left) && match_def_path(cx, right_id, expected_right) {
365         (&(*arms[0].body).kind, &(*arms[1].body).kind)
366     } else if match_def_path(cx, right_id, expected_left) && match_def_path(cx, right_id, expected_right) {
367         (&(*arms[1].body).kind, &(*arms[0].body).kind)
368     } else {
369         return None;
370     };
371
372     match body_node_pair {
373         (ExprKind::Lit(ref lit_left), ExprKind::Lit(ref lit_right)) => match (&lit_left.node, &lit_right.node) {
374             (LitKind::Bool(true), LitKind::Bool(false)) => Some(should_be_left),
375             (LitKind::Bool(false), LitKind::Bool(true)) => Some(should_be_right),
376             _ => None,
377         },
378         _ => None,
379     }
380 }