]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/loops/manual_flatten.rs
Auto merge of #7539 - Labelray:master, r=camsteffen
[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::visitors::is_local_used;
5 use clippy_utils::{is_lang_ctor, path_to_local_id};
6 use if_chain::if_chain;
7 use rustc_errors::Applicability;
8 use rustc_hir::LangItem::{OptionSome, ResultOk};
9 use rustc_hir::{Expr, ExprKind, MatchSource, Pat, PatKind, StmtKind};
10 use rustc_lint::LateContext;
11 use rustc_middle::ty;
12 use rustc_span::source_map::Span;
13
14 /// Check for unnecessary `if let` usage in a for loop where only the `Some` or `Ok` variant of the
15 /// iterator element is used.
16 pub(super) fn check<'tcx>(
17     cx: &LateContext<'tcx>,
18     pat: &'tcx Pat<'_>,
19     arg: &'tcx Expr<'_>,
20     body: &'tcx Expr<'_>,
21     span: Span,
22 ) {
23     if let ExprKind::Block(block, _) = body.kind {
24         // Ensure the `if let` statement is the only expression or statement in the for-loop
25         let inner_expr = if block.stmts.len() == 1 && block.expr.is_none() {
26             let match_stmt = &block.stmts[0];
27             if let StmtKind::Semi(inner_expr) = match_stmt.kind {
28                 Some(inner_expr)
29             } else {
30                 None
31             }
32         } else if block.stmts.is_empty() {
33             block.expr
34         } else {
35             None
36         };
37
38         if_chain! {
39             if let Some(inner_expr) = inner_expr;
40             if let ExprKind::Match(
41                 match_expr, [true_arm, _else_arm], MatchSource::IfLetDesugar{ contains_else_clause: false }
42             ) = inner_expr.kind;
43             // Ensure match_expr in `if let` statement is the same as the pat from the for-loop
44             if let PatKind::Binding(_, pat_hir_id, _, _) = pat.kind;
45             if path_to_local_id(match_expr, pat_hir_id);
46             // Ensure the `if let` statement is for the `Some` variant of `Option` or the `Ok` variant of `Result`
47             if let PatKind::TupleStruct(ref qpath, _, _) = true_arm.pat.kind;
48             let some_ctor = is_lang_ctor(cx, qpath, OptionSome);
49             let ok_ctor = is_lang_ctor(cx, qpath, ResultOk);
50             if some_ctor || ok_ctor;
51             // Ensure epxr in `if let` is not used afterwards
52             if !is_local_used(cx, true_arm, pat_hir_id);
53             then {
54                 let if_let_type = if some_ctor { "Some" } else { "Ok" };
55                 // Prepare the error message
56                 let msg = format!("unnecessary `if let` since only the `{}` variant of the iterator element is used", if_let_type);
57
58                 // Prepare the help message
59                 let mut applicability = Applicability::MaybeIncorrect;
60                 let arg_snippet = make_iterator_snippet(cx, arg, &mut applicability);
61                 let copied = match cx.typeck_results().expr_ty(match_expr).kind() {
62                     ty::Ref(_, inner, _) => match inner.kind() {
63                         ty::Ref(..) => ".copied()",
64                         _ => ""
65                     }
66                     _ => ""
67                 };
68
69                 span_lint_and_then(
70                     cx,
71                     MANUAL_FLATTEN,
72                     span,
73                     &msg,
74                     |diag| {
75                         let sugg = format!("{}{}.flatten()", arg_snippet, copied);
76                         diag.span_suggestion(
77                             arg.span,
78                             "try",
79                             sugg,
80                             Applicability::MaybeIncorrect,
81                         );
82                         diag.span_help(
83                             inner_expr.span,
84                             "...and remove the `if let` statement in the for loop",
85                         );
86                     }
87                 );
88             }
89         }
90     }
91 }