summaryrefslogtreecommitdiffstats
path: root/src/tools/clippy/clippy_lints/src/methods/iter_kv_map.rs
blob: 2244ebfb129277036c95e95013d0a39d8f213a60 (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
86
87
#![allow(unused_imports)]

use super::ITER_KV_MAP;
use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_sugg, span_lint_and_then};
use clippy_utils::source::{snippet, snippet_with_applicability};
use clippy_utils::sugg;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::visitors::is_local_used;
use rustc_hir::{BindingAnnotation, Body, BorrowKind, Expr, ExprKind, Mutability, Pat, PatKind};
use rustc_lint::{LateContext, LintContext};
use rustc_middle::ty;
use rustc_span::sym;
use rustc_span::Span;

/// lint use of:
/// - `hashmap.iter().map(|(_, v)| v)`
/// - `hashmap.into_iter().map(|(_, v)| v)`
/// on `HashMaps` and `BTreeMaps` in std

pub(super) fn check<'tcx>(
    cx: &LateContext<'tcx>,
    map_type: &'tcx str,     // iter / into_iter
    expr: &'tcx Expr<'tcx>,  // .iter().map(|(_, v_| v))
    recv: &'tcx Expr<'tcx>,  // hashmap
    m_arg: &'tcx Expr<'tcx>, // |(_, v)| v
) {
    if_chain! {
        if !expr.span.from_expansion();
        if let ExprKind::Closure(c) = m_arg.kind;
        if let Body {params: [p], value: body_expr, generator_kind: _ } = cx.tcx.hir().body(c.body);
        if let PatKind::Tuple([key_pat, val_pat], _) = p.pat.kind;

        let (replacement_kind, binded_ident) = match (&key_pat.kind, &val_pat.kind) {
            (key, PatKind::Binding(_, _, value, _)) if pat_is_wild(cx, key, m_arg) => ("value", value),
            (PatKind::Binding(_, _, key, _), value) if pat_is_wild(cx, value, m_arg) => ("key", key),
            _ => return,
        };

        let ty = cx.typeck_results().expr_ty(recv);
        if is_type_diagnostic_item(cx, ty, sym::HashMap) || is_type_diagnostic_item(cx, ty, sym::BTreeMap);

        then {
            let mut applicability = rustc_errors::Applicability::MachineApplicable;
            let recv_snippet = snippet_with_applicability(cx, recv.span, "map", &mut applicability);
            let into_prefix = if map_type == "into_iter" {"into_"} else {""};

            if_chain! {
                if let ExprKind::Path(rustc_hir::QPath::Resolved(_, path)) = body_expr.kind;
                if let [local_ident] = path.segments;
                if local_ident.ident.as_str() == binded_ident.as_str();

                then {
                    span_lint_and_sugg(
                        cx,
                        ITER_KV_MAP,
                        expr.span,
                        &format!("iterating on a map's {replacement_kind}s"),
                        "try",
                        format!("{recv_snippet}.{into_prefix}{replacement_kind}s()"),
                        applicability,
                    );
                } else {
                    span_lint_and_sugg(
                        cx,
                        ITER_KV_MAP,
                        expr.span,
                        &format!("iterating on a map's {replacement_kind}s"),
                        "try",
                        format!("{recv_snippet}.{into_prefix}{replacement_kind}s().map(|{binded_ident}| {})",
                            snippet_with_applicability(cx, body_expr.span, "/* body */", &mut applicability)),
                        applicability,
                    );
                }
            }
        }
    }
}

/// Returns `true` if the pattern is a `PatWild`, or is an ident prefixed with `_`
/// that is not locally used.
fn pat_is_wild<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx PatKind<'_>, body: &'tcx Expr<'_>) -> bool {
    match *pat {
        PatKind::Wild => true,
        PatKind::Binding(_, id, ident, None) if ident.as_str().starts_with('_') => !is_local_used(cx, body, id),
        _ => false,
    }
}