]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/loops/manual_flatten.rs
Refactors with peel_blocks
[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             span_lint_and_then(
55                 cx,
56                 MANUAL_FLATTEN,
57                 span,
58                 &msg,
59                 |diag| {
60                     let sugg = format!("{}{}.flatten()", arg_snippet, copied);
61                     diag.span_suggestion(
62                         arg.span,
63                         "try",
64                         sugg,
65                         Applicability::MaybeIncorrect,
66                     );
67                     diag.span_help(
68                         inner_expr.span,
69                         "...and remove the `if let` statement in the for loop",
70                     );
71                 }
72             );
73         }
74     }
75 }