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