summaryrefslogtreecommitdiffstats
path: root/src/tools/clippy/clippy_lints/src/loops/manual_flatten.rs
blob: 1d6ddf4b99f7b6835717672b238eb660547a4e45 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
use super::utils::make_iterator_snippet;
use super::MANUAL_FLATTEN;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::higher;
use clippy_utils::visitors::is_local_used;
use clippy_utils::{is_lang_ctor, path_to_local_id, peel_blocks_with_stmt};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::LangItem::{OptionSome, ResultOk};
use rustc_hir::{Expr, Pat, PatKind};
use rustc_lint::LateContext;
use rustc_middle::ty;
use rustc_span::source_map::Span;

/// Check for unnecessary `if let` usage in a for loop where only the `Some` or `Ok` variant of the
/// iterator element is used.
pub(super) fn check<'tcx>(
    cx: &LateContext<'tcx>,
    pat: &'tcx Pat<'_>,
    arg: &'tcx Expr<'_>,
    body: &'tcx Expr<'_>,
    span: Span,
) {
    let inner_expr = peel_blocks_with_stmt(body);
    if_chain! {
        if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else: None })
            = higher::IfLet::hir(cx, inner_expr);
        // Ensure match_expr in `if let` statement is the same as the pat from the for-loop
        if let PatKind::Binding(_, pat_hir_id, _, _) = pat.kind;
        if path_to_local_id(let_expr, pat_hir_id);
        // Ensure the `if let` statement is for the `Some` variant of `Option` or the `Ok` variant of `Result`
        if let PatKind::TupleStruct(ref qpath, _, _) = let_pat.kind;
        let some_ctor = is_lang_ctor(cx, qpath, OptionSome);
        let ok_ctor = is_lang_ctor(cx, qpath, ResultOk);
        if some_ctor || ok_ctor;
        // Ensure expr in `if let` is not used afterwards
        if !is_local_used(cx, if_then, pat_hir_id);
        then {
            let if_let_type = if some_ctor { "Some" } else { "Ok" };
            // Prepare the error message
            let msg = format!("unnecessary `if let` since only the `{}` variant of the iterator element is used", if_let_type);

            // Prepare the help message
            let mut applicability = Applicability::MaybeIncorrect;
            let arg_snippet = make_iterator_snippet(cx, arg, &mut applicability);
            let copied = match cx.typeck_results().expr_ty(let_expr).kind() {
                ty::Ref(_, inner, _) => match inner.kind() {
                    ty::Ref(..) => ".copied()",
                    _ => ""
                }
                _ => ""
            };

            let sugg = format!("{arg_snippet}{copied}.flatten()");

            // If suggestion is not a one-liner, it won't be shown inline within the error message. In that case,
            // it will be shown in the extra `help` message at the end, which is why the first `help_msg` needs
            // to refer to the correct relative position of the suggestion.
            let help_msg = if sugg.contains('\n') {
                "remove the `if let` statement in the for loop and then..."
            } else {
                "...and remove the `if let` statement in the for loop"
            };

            span_lint_and_then(
                cx,
                MANUAL_FLATTEN,
                span,
                &msg,
                |diag| {
                    diag.span_suggestion(
                        arg.span,
                        "try",
                        sugg,
                        applicability,
                    );
                    diag.span_help(
                        inner_expr.span,
                        help_msg,
                    );
                }
            );
        }
    }
}