]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/loops/manual_flatten.rs
clippy: BindingAnnotation change
[rust.git] / clippy_lints / src / loops / manual_flatten.rs
1 use super::utils::make_iterator_snippet;
2 use super::MANUAL_FLATTEN;
3 use clippy_utils::diagnostics::span_lint_and_then;
4 use clippy_utils::higher;
5 use clippy_utils::visitors::is_local_used;
6 use clippy_utils::{is_lang_ctor, path_to_local_id, peel_blocks_with_stmt};
7 use if_chain::if_chain;
8 use rustc_errors::Applicability;
9 use rustc_hir::LangItem::{OptionSome, ResultOk};
10 use rustc_hir::{Expr, Pat, PatKind};
11 use rustc_lint::LateContext;
12 use rustc_middle::ty;
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 ) {
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;
33         let some_ctor = is_lang_ctor(cx, qpath, OptionSome);
34         let ok_ctor = is_lang_ctor(cx, qpath, ResultOk);
35         if some_ctor || ok_ctor;
36         // Ensure expr in `if let` is not used afterwards
37         if !is_local_used(cx, if_then, pat_hir_id);
38         then {
39             let if_let_type = if some_ctor { "Some" } else { "Ok" };
40             // Prepare the error message
41             let msg = format!("unnecessary `if let` since only the `{}` variant of the iterator element is used", if_let_type);
42
43             // Prepare the help message
44             let mut applicability = Applicability::MaybeIncorrect;
45             let arg_snippet = make_iterator_snippet(cx, arg, &mut applicability);
46             let copied = match cx.typeck_results().expr_ty(let_expr).kind() {
47                 ty::Ref(_, inner, _) => match inner.kind() {
48                     ty::Ref(..) => ".copied()",
49                     _ => ""
50                 }
51                 _ => ""
52             };
53
54             let sugg = format!("{arg_snippet}{copied}.flatten()");
55
56             // If suggestion is not a one-liner, it won't be shown inline within the error message. In that case,
57             // it will be shown in the extra `help` message at the end, which is why the first `help_msg` needs
58             // to refer to the correct relative position of the suggestion.
59             let help_msg = if sugg.contains('\n') {
60                 "remove the `if let` statement in the for loop and then..."
61             } else {
62                 "...and remove the `if let` statement in the for loop"
63             };
64
65             span_lint_and_then(
66                 cx,
67                 MANUAL_FLATTEN,
68                 span,
69                 &msg,
70                 |diag| {
71                     diag.span_suggestion(
72                         arg.span,
73                         "try",
74                         sugg,
75                         applicability,
76                     );
77                     diag.span_help(
78                         inner_expr.span,
79                         help_msg,
80                     );
81                 }
82             );
83         }
84     }
85 }