diff options
Diffstat (limited to '')
-rw-r--r-- | src/tools/clippy/clippy_lints/src/methods/iter_kv_map.rs | 87 |
1 files changed, 87 insertions, 0 deletions
diff --git a/src/tools/clippy/clippy_lints/src/methods/iter_kv_map.rs b/src/tools/clippy/clippy_lints/src/methods/iter_kv_map.rs new file mode 100644 index 000000000..2244ebfb1 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/iter_kv_map.rs @@ -0,0 +1,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, + } +} |