]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/matches/redundant_pattern_match.rs
Auto merge of #98457 - japaric:gh98378, r=m-ou-se
[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::{is_type_diagnostic_item, needs_ordered_drop};
6 use clippy_utils::visitors::any_temporaries_need_ordered_drop;
7 use clippy_utils::{higher, is_lang_ctor, is_trait_method};
8 use if_chain::if_chain;
9 use rustc_ast::ast::LitKind;
10 use rustc_errors::Applicability;
11 use rustc_hir::LangItem::{self, OptionSome, OptionNone, PollPending, PollReady, ResultOk, ResultErr};
12 use rustc_hir::{Arm, Expr, ExprKind, Node, Pat, PatKind, QPath, UnOp};
13 use rustc_lint::LateContext;
14 use rustc_middle::ty::{self, subst::GenericArgKind, DefIdTree, Ty};
15 use rustc_span::{sym, Symbol};
16
17 pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
18     if let Some(higher::WhileLet { let_pat, let_expr, .. }) = higher::WhileLet::hir(expr) {
19         find_sugg_for_if_let(cx, expr, let_pat, let_expr, "while", false);
20     }
21 }
22
23 pub(super) fn check_if_let<'tcx>(
24     cx: &LateContext<'tcx>,
25     expr: &'tcx Expr<'_>,
26     pat: &'tcx Pat<'_>,
27     scrutinee: &'tcx Expr<'_>,
28     has_else: bool,
29 ) {
30     find_sugg_for_if_let(cx, expr, pat, scrutinee, "if", has_else);
31 }
32
33 // Extract the generic arguments out of a type
34 fn try_get_generic_ty(ty: Ty<'_>, index: usize) -> Option<Ty<'_>> {
35     if_chain! {
36         if let ty::Adt(_, subs) = ty.kind();
37         if let Some(sub) = subs.get(index);
38         if let GenericArgKind::Type(sub_ty) = sub.unpack();
39         then {
40             Some(sub_ty)
41         } else {
42             None
43         }
44     }
45 }
46
47 fn find_sugg_for_if_let<'tcx>(
48     cx: &LateContext<'tcx>,
49     expr: &'tcx Expr<'_>,
50     let_pat: &Pat<'_>,
51     let_expr: &'tcx Expr<'_>,
52     keyword: &'static str,
53     has_else: bool,
54 ) {
55     // also look inside refs
56     // if we have &None for example, peel it so we can detect "if let None = x"
57     let check_pat = match let_pat.kind {
58         PatKind::Ref(inner, _mutability) => inner,
59         _ => let_pat,
60     };
61     let op_ty = cx.typeck_results().expr_ty(let_expr);
62     // Determine which function should be used, and the type contained by the corresponding
63     // variant.
64     let (good_method, inner_ty) = match check_pat.kind {
65         PatKind::TupleStruct(ref qpath, [sub_pat], _) => {
66             if let PatKind::Wild = sub_pat.kind {
67                 let res = cx.typeck_results().qpath_res(qpath, check_pat.hir_id);
68                 let Some(id) = res.opt_def_id().map(|ctor_id| cx.tcx.parent(ctor_id)) else { return };
69                 let lang_items = cx.tcx.lang_items();
70                 if Some(id) == lang_items.result_ok_variant() {
71                     ("is_ok()", try_get_generic_ty(op_ty, 0).unwrap_or(op_ty))
72                 } else if Some(id) == lang_items.result_err_variant() {
73                     ("is_err()", try_get_generic_ty(op_ty, 1).unwrap_or(op_ty))
74                 } else if Some(id) == lang_items.option_some_variant() {
75                     ("is_some()", op_ty)
76                 } else if Some(id) == lang_items.poll_ready_variant() {
77                     ("is_ready()", op_ty)
78                 } else if is_pat_variant(cx, check_pat, qpath, Item::Diag(sym::IpAddr, sym!(V4))) {
79                     ("is_ipv4()", op_ty)
80                 } else if is_pat_variant(cx, check_pat, qpath, Item::Diag(sym::IpAddr, sym!(V6))) {
81                     ("is_ipv6()", op_ty)
82                 } else {
83                     return;
84                 }
85             } else {
86                 return;
87             }
88         },
89         PatKind::Path(ref path) => {
90             let method = if is_lang_ctor(cx, path, OptionNone) {
91                 "is_none()"
92             } else if is_lang_ctor(cx, path, PollPending) {
93                 "is_pending()"
94             } else {
95                 return;
96             };
97             // `None` and `Pending` don't have an inner type.
98             (method, cx.tcx.types.unit)
99         },
100         _ => return,
101     };
102
103     // If this is the last expression in a block or there is an else clause then the whole
104     // type needs to be considered, not just the inner type of the branch being matched on.
105     // Note the last expression in a block is dropped after all local bindings.
106     let check_ty = if has_else
107         || (keyword == "if" && matches!(cx.tcx.hir().parent_iter(expr.hir_id).next(), Some((_, Node::Block(..)))))
108     {
109         op_ty
110     } else {
111         inner_ty
112     };
113
114     // All temporaries created in the scrutinee expression are dropped at the same time as the
115     // scrutinee would be, so they have to be considered as well.
116     // e.g. in `if let Some(x) = foo.lock().unwrap().baz.as_ref() { .. }` the lock will be held
117     // for the duration if body.
118     let needs_drop = needs_ordered_drop(cx, check_ty) || any_temporaries_need_ordered_drop(cx, let_expr);
119
120     // check that `while_let_on_iterator` lint does not trigger
121     if_chain! {
122         if keyword == "while";
123         if let ExprKind::MethodCall(method_path, ..) = let_expr.kind;
124         if method_path.ident.name == sym::next;
125         if is_trait_method(cx, let_expr, sym::Iterator);
126         then {
127             return;
128         }
129     }
130
131     let result_expr = match &let_expr.kind {
132         ExprKind::AddrOf(_, _, borrowed) => borrowed,
133         ExprKind::Unary(UnOp::Deref, deref) => deref,
134         _ => let_expr,
135     };
136
137     span_lint_and_then(
138         cx,
139         REDUNDANT_PATTERN_MATCHING,
140         let_pat.span,
141         &format!("redundant pattern matching, consider using `{}`", good_method),
142         |diag| {
143             // if/while let ... = ... { ... }
144             // ^^^^^^^^^^^^^^^^^^^^^^^^^^^
145             let expr_span = expr.span;
146
147             // if/while let ... = ... { ... }
148             //                 ^^^
149             let op_span = result_expr.span.source_callsite();
150
151             // if/while let ... = ... { ... }
152             // ^^^^^^^^^^^^^^^^^^^
153             let span = expr_span.until(op_span.shrink_to_hi());
154
155             let app = if needs_drop {
156                 Applicability::MaybeIncorrect
157             } else {
158                 Applicability::MachineApplicable
159             };
160
161             let sugg = Sugg::hir_with_macro_callsite(cx, result_expr, "_")
162                 .maybe_par()
163                 .to_string();
164
165             diag.span_suggestion(span, "try this", format!("{} {}.{}", keyword, sugg, good_method), app);
166
167             if needs_drop {
168                 diag.note("this will change drop order of the result, as well as all temporaries");
169                 diag.note("add `#[allow(clippy::redundant_pattern_matching)]` if this is important");
170             }
171         },
172     );
173 }
174
175 pub(super) fn check_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, op: &Expr<'_>, arms: &[Arm<'_>]) {
176     if arms.len() == 2 {
177         let node_pair = (&arms[0].pat.kind, &arms[1].pat.kind);
178
179         let found_good_method = match node_pair {
180             (
181                 PatKind::TupleStruct(ref path_left, patterns_left, _),
182                 PatKind::TupleStruct(ref path_right, patterns_right, _),
183             ) if patterns_left.len() == 1 && patterns_right.len() == 1 => {
184                 if let (PatKind::Wild, PatKind::Wild) = (&patterns_left[0].kind, &patterns_right[0].kind) {
185                     find_good_method_for_match(
186                         cx,
187                         arms,
188                         path_left,
189                         path_right,
190                         Item::Lang(ResultOk),
191                         Item::Lang(ResultErr),
192                         "is_ok()",
193                         "is_err()",
194                     )
195                     .or_else(|| {
196                         find_good_method_for_match(
197                             cx,
198                             arms,
199                             path_left,
200                             path_right,
201                             Item::Diag(sym::IpAddr, sym!(V4)),
202                             Item::Diag(sym::IpAddr, sym!(V6)),
203                             "is_ipv4()",
204                             "is_ipv6()",
205                         )
206                     })
207                 } else {
208                     None
209                 }
210             },
211             (PatKind::TupleStruct(ref path_left, patterns, _), PatKind::Path(ref path_right))
212             | (PatKind::Path(ref path_left), PatKind::TupleStruct(ref path_right, patterns, _))
213                 if patterns.len() == 1 =>
214             {
215                 if let PatKind::Wild = patterns[0].kind {
216
217                     find_good_method_for_match(
218                         cx,
219                         arms,
220                         path_left,
221                         path_right,
222                         Item::Lang(OptionSome),
223                         Item::Lang(OptionNone),
224                         "is_some()",
225                         "is_none()",
226                     )
227                     .or_else(|| {
228                         find_good_method_for_match(
229                             cx,
230                             arms,
231                             path_left,
232                             path_right,
233                             Item::Lang(PollReady),
234                             Item::Lang(PollPending),
235                             "is_ready()",
236                             "is_pending()",
237                         )
238                     })
239                 } else {
240                     None
241                 }
242             },
243             _ => None,
244         };
245
246         if let Some(good_method) = found_good_method {
247             let span = expr.span.to(op.span);
248             let result_expr = match &op.kind {
249                 ExprKind::AddrOf(_, _, borrowed) => borrowed,
250                 _ => op,
251             };
252             span_lint_and_then(
253                 cx,
254                 REDUNDANT_PATTERN_MATCHING,
255                 expr.span,
256                 &format!("redundant pattern matching, consider using `{}`", good_method),
257                 |diag| {
258                     diag.span_suggestion(
259                         span,
260                         "try this",
261                         format!("{}.{}", snippet(cx, result_expr.span, "_"), good_method),
262                         Applicability::MaybeIncorrect, // snippet
263                     );
264                 },
265             );
266         }
267     }
268 }
269
270 #[derive(Clone, Copy)]
271 enum Item {
272   Lang(LangItem),
273   Diag(Symbol, Symbol),
274 }
275
276 fn is_pat_variant(cx: &LateContext<'_>, pat: &Pat<'_>, path: &QPath<'_>, expected_item: Item) -> bool {
277     let Some(id) = cx.typeck_results().qpath_res(path, pat.hir_id).opt_def_id() else { return false };
278
279     match expected_item {
280         Item::Lang(expected_lang_item) => {
281             let expected_id = cx.tcx.lang_items().require(expected_lang_item).unwrap();
282             cx.tcx.parent(id) == expected_id
283         },
284         Item::Diag(expected_ty, expected_variant) => {
285             let ty = cx.typeck_results().pat_ty(pat);
286
287             if is_type_diagnostic_item(cx, ty, expected_ty) {
288                 let variant = ty.ty_adt_def()
289                     .expect("struct pattern type is not an ADT")
290                     .variant_of_res(cx.qpath_res(path, pat.hir_id));
291
292                 return variant.name == expected_variant
293             }
294
295             false
296         }
297     }
298 }
299
300 #[expect(clippy::too_many_arguments)]
301 fn find_good_method_for_match<'a>(
302     cx: &LateContext<'_>,
303     arms: &[Arm<'_>],
304     path_left: &QPath<'_>,
305     path_right: &QPath<'_>,
306     expected_item_left: Item,
307     expected_item_right: Item,
308     should_be_left: &'a str,
309     should_be_right: &'a str,
310 ) -> Option<&'a str> {
311     let pat_left = arms[0].pat;
312     let pat_right = arms[1].pat;
313
314     let body_node_pair = if (
315         is_pat_variant(cx, pat_left, path_left, expected_item_left)
316     ) && (
317         is_pat_variant(cx, pat_right, path_right, expected_item_right)
318     ) {
319         (&arms[0].body.kind, &arms[1].body.kind)
320     } else if (
321         is_pat_variant(cx, pat_left, path_left, expected_item_right)
322     ) && (
323         is_pat_variant(cx, pat_right, path_right, expected_item_left)
324     ) {
325         (&arms[1].body.kind, &arms[0].body.kind)
326     } else {
327         return None;
328     };
329
330     match body_node_pair {
331         (ExprKind::Lit(ref lit_left), ExprKind::Lit(ref lit_right)) => match (&lit_left.node, &lit_right.node) {
332             (LitKind::Bool(true), LitKind::Bool(false)) => Some(should_be_left),
333             (LitKind::Bool(false), LitKind::Bool(true)) => Some(should_be_right),
334             _ => None,
335         },
336         _ => None,
337     }
338 }