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