about summary refs log tree commit diff
path: root/clippy_lints/src
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2022-09-16 08:44:58 +0000
committerbors <bors@rust-lang.org>2022-09-16 08:44:58 +0000
commit481dc2e81cf6e5b68095a6baa5d005a2edf59e01 (patch)
tree03a5c5e82c663d9998492a5d4dd5103ca6ecd0f7 /clippy_lints/src
parent56a8ef4dbe13a537e18ad6cdb0e1dabd25a1250a (diff)
parentc6219b2ece469a17e3c6d7e28e0acbd6ce5c9d44 (diff)
downloadrust-481dc2e81cf6e5b68095a6baa5d005a2edf59e01.tar.gz
rust-481dc2e81cf6e5b68095a6baa5d005a2edf59e01.zip
Auto merge of #9409 - DesmondWillowbrook:iter_kv_map, r=xFrednet
Add `iter_kv_map` lint

fixes #9376

| before | after |
| -------------- | ------------------------- |
| `hmap.iter().map(\|(key, _)\| key)` | `hmap.keys()` |
| `hmap.iter().map(\|(_, v)\| v + 2)` | `hmap.values().map(\|v\| v + 2)` |
| `hmap.into_iter().map(\|(key, _)\| key)` | `hmap.into_keys()` |

Is `MachineApplicable`

changelog: [`iter_kv_map`]: added lint
Diffstat (limited to 'clippy_lints/src')
-rw-r--r--clippy_lints/src/lib.register_all.rs1
-rw-r--r--clippy_lints/src/lib.register_complexity.rs1
-rw-r--r--clippy_lints/src/lib.register_lints.rs1
-rw-r--r--clippy_lints/src/methods/iter_kv_map.rs87
-rw-r--r--clippy_lints/src/methods/mod.rs36
5 files changed, 126 insertions, 0 deletions
diff --git a/clippy_lints/src/lib.register_all.rs b/clippy_lints/src/lib.register_all.rs
index 39ab175341f..ae57a9a24a7 100644
--- a/clippy_lints/src/lib.register_all.rs
+++ b/clippy_lints/src/lib.register_all.rs
@@ -171,6 +171,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
     LintId::of(methods::ITERATOR_STEP_BY_ZERO),
     LintId::of(methods::ITER_CLONED_COLLECT),
     LintId::of(methods::ITER_COUNT),
+    LintId::of(methods::ITER_KV_MAP),
     LintId::of(methods::ITER_NEXT_SLICE),
     LintId::of(methods::ITER_NTH),
     LintId::of(methods::ITER_NTH_ZERO),
diff --git a/clippy_lints/src/lib.register_complexity.rs b/clippy_lints/src/lib.register_complexity.rs
index aa247352f88..185189a6af5 100644
--- a/clippy_lints/src/lib.register_complexity.rs
+++ b/clippy_lints/src/lib.register_complexity.rs
@@ -40,6 +40,7 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec!
     LintId::of(methods::GET_LAST_WITH_LEN),
     LintId::of(methods::INSPECT_FOR_EACH),
     LintId::of(methods::ITER_COUNT),
+    LintId::of(methods::ITER_KV_MAP),
     LintId::of(methods::MANUAL_FILTER_MAP),
     LintId::of(methods::MANUAL_FIND_MAP),
     LintId::of(methods::MANUAL_SPLIT_ONCE),
diff --git a/clippy_lints/src/lib.register_lints.rs b/clippy_lints/src/lib.register_lints.rs
index c16f2d86ae0..02fcc8de507 100644
--- a/clippy_lints/src/lib.register_lints.rs
+++ b/clippy_lints/src/lib.register_lints.rs
@@ -313,6 +313,7 @@ store.register_lints(&[
     methods::ITERATOR_STEP_BY_ZERO,
     methods::ITER_CLONED_COLLECT,
     methods::ITER_COUNT,
+    methods::ITER_KV_MAP,
     methods::ITER_NEXT_SLICE,
     methods::ITER_NTH,
     methods::ITER_NTH_ZERO,
diff --git a/clippy_lints/src/methods/iter_kv_map.rs b/clippy_lints/src/methods/iter_kv_map.rs
new file mode 100644
index 00000000000..a7eecabd684
--- /dev/null
+++ b/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 {}s", replacement_kind),
+                        "try",
+                        format!("{}.{}{}s()", recv_snippet, into_prefix, replacement_kind),
+                        applicability,
+                    );
+                } else {
+                    span_lint_and_sugg(
+                        cx,
+                        ITER_KV_MAP,
+                        expr.span,
+                        &format!("iterating on a map's {}s", replacement_kind),
+                        "try",
+                        format!("{}.{}{}s().map(|{}| {})", recv_snippet, into_prefix, replacement_kind, 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,
+    }
+}
diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs
index 41942b20ea1..cdde4c54d63 100644
--- a/clippy_lints/src/methods/mod.rs
+++ b/clippy_lints/src/methods/mod.rs
@@ -35,6 +35,7 @@ mod into_iter_on_ref;
 mod is_digit_ascii_radix;
 mod iter_cloned_collect;
 mod iter_count;
+mod iter_kv_map;
 mod iter_next_slice;
 mod iter_nth;
 mod iter_nth_zero;
@@ -3036,6 +3037,37 @@ declare_clippy_lint! {
     "use of `File::read_to_end` or `File::read_to_string`"
 }
 
+declare_clippy_lint! {
+    /// ### What it does
+    ///
+    /// Checks for iterating a map (`HashMap` or `BTreeMap`) and
+    /// ignoring either the keys or values.
+    ///
+    /// ### Why is this bad?
+    ///
+    /// Readability. There are `keys` and `values` methods that
+    /// can be used to express that we only need the keys or the values.
+    ///
+    /// ### Example
+    ///
+    /// ```
+    /// # use std::collections::HashMap;
+    /// let map: HashMap<u32, u32> = HashMap::new();
+    /// let values = map.iter().map(|(_, value)| value).collect::<Vec<_>>();
+    /// ```
+    ///
+    /// Use instead:
+    /// ```
+    /// # use std::collections::HashMap;
+    /// let map: HashMap<u32, u32> = HashMap::new();
+    /// let values = map.values().collect::<Vec<_>>();
+    /// ```
+    #[clippy::version = "1.65.0"]
+    pub ITER_KV_MAP,
+    complexity,
+    "iterating on map using `iter` when `keys` or `values` would do"
+}
+
 pub struct Methods {
     avoid_breaking_exported_api: bool,
     msrv: Option<RustcVersion>,
@@ -3159,6 +3191,7 @@ impl_lint_pass!(Methods => [
     UNNECESSARY_SORT_BY,
     VEC_RESIZE_TO_ZERO,
     VERBOSE_FILE_READS,
+    ITER_KV_MAP,
 ]);
 
 /// Extracts a method call name, args, and `Span` of the method name.
@@ -3498,6 +3531,9 @@ impl Methods {
                 (name @ ("map" | "map_err"), [m_arg]) => {
                     if name == "map" {
                         map_clone::check(cx, expr, recv, m_arg, self.msrv);
+                        if let Some((map_name @ ("iter" | "into_iter"), recv2, _, _)) = method_call(recv) {
+                            iter_kv_map::check(cx, map_name, expr, recv2, m_arg);
+                        }
                     } else {
                         map_err_ignore::check(cx, expr, m_arg);
                     }