|
| 1 | +#![allow(unused_imports)] |
| 2 | + |
| 3 | +use super::ITER_KV_MAP; |
| 4 | +use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_sugg, span_lint_and_then}; |
| 5 | +use clippy_utils::source::{snippet, snippet_with_applicability}; |
| 6 | +use clippy_utils::sugg; |
| 7 | +use clippy_utils::ty::is_type_diagnostic_item; |
| 8 | +use clippy_utils::visitors::is_local_used; |
| 9 | +use rustc_hir::{BindingAnnotation, Body, BorrowKind, Expr, ExprKind, Mutability, Pat, PatKind}; |
| 10 | +use rustc_lint::{LateContext, LintContext}; |
| 11 | +use rustc_middle::ty; |
| 12 | +use rustc_span::sym; |
| 13 | +use rustc_span::Span; |
| 14 | + |
| 15 | +/// lint use of: |
| 16 | +/// - `hashmap.iter().map(|(_, v)| v)` |
| 17 | +/// - `hashmap.into_iter().map(|(_, v)| v)` |
| 18 | +/// on `HashMaps` and `BTreeMaps` in std |
| 19 | +
|
| 20 | +pub(super) fn check<'tcx>( |
| 21 | + cx: &LateContext<'tcx>, |
| 22 | + map_type: &'tcx str, // iter / into_iter |
| 23 | + expr: &'tcx Expr<'tcx>, // .iter().map(|(_, v_| v)) |
| 24 | + recv: &'tcx Expr<'tcx>, // hashmap |
| 25 | + m_arg: &'tcx Expr<'tcx>, // |(_, v)| v |
| 26 | +) { |
| 27 | + if_chain! { |
| 28 | + if !expr.span.from_expansion(); |
| 29 | + if let ExprKind::Closure(c) = m_arg.kind; |
| 30 | + if let Body {params: [p], value: body_expr, generator_kind: _ } = cx.tcx.hir().body(c.body); |
| 31 | + if let PatKind::Tuple([key_pat, val_pat], _) = p.pat.kind; |
| 32 | + |
| 33 | + let (replacement_kind, binded_ident) = match (&key_pat.kind, &val_pat.kind) { |
| 34 | + (key, PatKind::Binding(_, _, value, _)) if pat_is_wild(cx, key, m_arg) => ("value", value), |
| 35 | + (PatKind::Binding(_, _, key, _), value) if pat_is_wild(cx, value, m_arg) => ("key", key), |
| 36 | + _ => return, |
| 37 | + }; |
| 38 | + |
| 39 | + let ty = cx.typeck_results().expr_ty(recv); |
| 40 | + if is_type_diagnostic_item(cx, ty, sym::HashMap) || is_type_diagnostic_item(cx, ty, sym::BTreeMap); |
| 41 | + |
| 42 | + then { |
| 43 | + let mut applicability = rustc_errors::Applicability::MachineApplicable; |
| 44 | + let recv_snippet = snippet_with_applicability(cx, recv.span, "map", &mut applicability); |
| 45 | + let into_prefix = if map_type == "into_iter" {"into_"} else {""}; |
| 46 | + |
| 47 | + if_chain! { |
| 48 | + if let ExprKind::Path(rustc_hir::QPath::Resolved(_, path)) = body_expr.kind; |
| 49 | + if let [local_ident] = path.segments; |
| 50 | + if local_ident.ident.as_str() == binded_ident.as_str(); |
| 51 | + |
| 52 | + then { |
| 53 | + span_lint_and_sugg( |
| 54 | + cx, |
| 55 | + ITER_KV_MAP, |
| 56 | + expr.span, |
| 57 | + &format!("iterating on a map's {}s", replacement_kind), |
| 58 | + "try", |
| 59 | + format!("{}.{}{}s()", recv_snippet, into_prefix, replacement_kind), |
| 60 | + applicability, |
| 61 | + ); |
| 62 | + } else { |
| 63 | + span_lint_and_sugg( |
| 64 | + cx, |
| 65 | + ITER_KV_MAP, |
| 66 | + expr.span, |
| 67 | + &format!("iterating on a map's {}s", replacement_kind), |
| 68 | + "try", |
| 69 | + format!("{}.{}{}s().map(|{}| {})", recv_snippet, into_prefix, replacement_kind, binded_ident, |
| 70 | + snippet_with_applicability(cx, body_expr.span, "/* body */", &mut applicability)), |
| 71 | + applicability, |
| 72 | + ); |
| 73 | + } |
| 74 | + } |
| 75 | + } |
| 76 | + } |
| 77 | +} |
| 78 | + |
| 79 | +/// Returns `true` if the pattern is a `PatWild`, or is an ident prefixed with `_` |
| 80 | +/// that is not locally used. |
| 81 | +fn pat_is_wild<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx PatKind<'_>, body: &'tcx Expr<'_>) -> bool { |
| 82 | + match *pat { |
| 83 | + PatKind::Wild => true, |
| 84 | + PatKind::Binding(_, id, ident, None) if ident.as_str().starts_with('_') => !is_local_used(cx, body, id), |
| 85 | + _ => false, |
| 86 | + } |
| 87 | +} |
0 commit comments