]>
Commit | Line | Data |
---|---|---|
f20569fa XL |
1 | use super::utils::make_iterator_snippet; |
2 | use super::MANUAL_FLATTEN; | |
cdc7bbd5 | 3 | use clippy_utils::diagnostics::span_lint_and_then; |
94222f64 | 4 | use clippy_utils::higher; |
c295e0f8 | 5 | use clippy_utils::visitors::is_local_used; |
2b03887a | 6 | use clippy_utils::{path_to_local_id, peel_blocks_with_stmt}; |
f20569fa XL |
7 | use if_chain::if_chain; |
8 | use rustc_errors::Applicability; | |
2b03887a | 9 | use rustc_hir::def::{DefKind, Res}; |
a2a8927a | 10 | use rustc_hir::{Expr, Pat, PatKind}; |
f20569fa | 11 | use rustc_lint::LateContext; |
353b0b11 | 12 | use rustc_middle::ty; |
f20569fa XL |
13 | use rustc_span::source_map::Span; |
14 | ||
15 | /// Check for unnecessary `if let` usage in a for loop where only the `Some` or `Ok` variant of the | |
16 | /// iterator element is used. | |
17 | pub(super) fn check<'tcx>( | |
18 | cx: &LateContext<'tcx>, | |
19 | pat: &'tcx Pat<'_>, | |
20 | arg: &'tcx Expr<'_>, | |
21 | body: &'tcx Expr<'_>, | |
22 | span: Span, | |
23 | ) { | |
a2a8927a XL |
24 | let inner_expr = peel_blocks_with_stmt(body); |
25 | if_chain! { | |
26 | if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else: None }) | |
27 | = higher::IfLet::hir(cx, inner_expr); | |
28 | // Ensure match_expr in `if let` statement is the same as the pat from the for-loop | |
29 | if let PatKind::Binding(_, pat_hir_id, _, _) = pat.kind; | |
30 | if path_to_local_id(let_expr, pat_hir_id); | |
31 | // Ensure the `if let` statement is for the `Some` variant of `Option` or the `Ok` variant of `Result` | |
32 | if let PatKind::TupleStruct(ref qpath, _, _) = let_pat.kind; | |
2b03887a FG |
33 | if let Res::Def(DefKind::Ctor(..), ctor_id) = cx.qpath_res(qpath, let_pat.hir_id); |
34 | if let Some(variant_id) = cx.tcx.opt_parent(ctor_id); | |
35 | let some_ctor = cx.tcx.lang_items().option_some_variant() == Some(variant_id); | |
36 | let ok_ctor = cx.tcx.lang_items().result_ok_variant() == Some(variant_id); | |
a2a8927a XL |
37 | if some_ctor || ok_ctor; |
38 | // Ensure expr in `if let` is not used afterwards | |
39 | if !is_local_used(cx, if_then, pat_hir_id); | |
40 | then { | |
41 | let if_let_type = if some_ctor { "Some" } else { "Ok" }; | |
42 | // Prepare the error message | |
2b03887a | 43 | let msg = format!("unnecessary `if let` since only the `{if_let_type}` variant of the iterator element is used"); |
f20569fa | 44 | |
a2a8927a XL |
45 | // Prepare the help message |
46 | let mut applicability = Applicability::MaybeIncorrect; | |
47 | let arg_snippet = make_iterator_snippet(cx, arg, &mut applicability); | |
48 | let copied = match cx.typeck_results().expr_ty(let_expr).kind() { | |
49 | ty::Ref(_, inner, _) => match inner.kind() { | |
50 | ty::Ref(..) => ".copied()", | |
cdc7bbd5 | 51 | _ => "" |
a2a8927a XL |
52 | } |
53 | _ => "" | |
54 | }; | |
f20569fa | 55 | |
064997fb FG |
56 | let sugg = format!("{arg_snippet}{copied}.flatten()"); |
57 | ||
58 | // If suggestion is not a one-liner, it won't be shown inline within the error message. In that case, | |
59 | // it will be shown in the extra `help` message at the end, which is why the first `help_msg` needs | |
60 | // to refer to the correct relative position of the suggestion. | |
61 | let help_msg = if sugg.contains('\n') { | |
62 | "remove the `if let` statement in the for loop and then..." | |
63 | } else { | |
64 | "...and remove the `if let` statement in the for loop" | |
65 | }; | |
66 | ||
a2a8927a XL |
67 | span_lint_and_then( |
68 | cx, | |
69 | MANUAL_FLATTEN, | |
70 | span, | |
71 | &msg, | |
72 | |diag| { | |
a2a8927a XL |
73 | diag.span_suggestion( |
74 | arg.span, | |
75 | "try", | |
76 | sugg, | |
064997fb | 77 | applicability, |
a2a8927a XL |
78 | ); |
79 | diag.span_help( | |
80 | inner_expr.span, | |
064997fb | 81 | help_msg, |
a2a8927a XL |
82 | ); |
83 | } | |
84 | ); | |
f20569fa XL |
85 | } |
86 | } | |
87 | } |