Skip to content

Commit 0494071

Browse files
committed
Merge pull request #564 from mcarton/hashmap
Lint looping on maps ignoring the keys or values
2 parents 28814fb + c0063e1 commit 0494071

File tree

5 files changed

+135
-8
lines changed

5 files changed

+135
-8
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ A collection of lints to catch common mistakes and improve your Rust code.
66
[Jump to usage instructions](#usage)
77

88
##Lints
9-
There are 111 lints included in this crate:
9+
There are 112 lints included in this crate:
1010

1111
name | default | meaning
1212
---------------------------------------------------------------------------------------------------------------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -42,6 +42,7 @@ name
4242
[extend_from_slice](https://github.com/Manishearth/rust-clippy/wiki#extend_from_slice) | warn | `.extend_from_slice(_)` is a faster way to extend a Vec by a slice
4343
[filter_next](https://github.com/Manishearth/rust-clippy/wiki#filter_next) | warn | using `filter(p).next()`, which is more succinctly expressed as `.find(p)`
4444
[float_cmp](https://github.com/Manishearth/rust-clippy/wiki#float_cmp) | warn | using `==` or `!=` on float values (as floating-point operations usually involve rounding errors, it is always better to check for approximate equality within small bounds)
45+
[for_kv_map](https://github.com/Manishearth/rust-clippy/wiki#for_kv_map) | warn | looping on a map using `iter` when `keys` or `values` would do
4546
[for_loop_over_option](https://github.com/Manishearth/rust-clippy/wiki#for_loop_over_option) | warn | for-looping over an `Option`, which is more clearly expressed as an `if let`
4647
[for_loop_over_result](https://github.com/Manishearth/rust-clippy/wiki#for_loop_over_result) | warn | for-looping over a `Result`, which is more clearly expressed as an `if let`
4748
[identity_op](https://github.com/Manishearth/rust-clippy/wiki#identity_op) | warn | using identity operations, e.g. `x + 0` or `y / 1`

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ pub fn plugin_registrar(reg: &mut Registry) {
205205
loops::EMPTY_LOOP,
206206
loops::EXPLICIT_COUNTER_LOOP,
207207
loops::EXPLICIT_ITER_LOOP,
208+
loops::FOR_KV_MAP,
208209
loops::FOR_LOOP_OVER_OPTION,
209210
loops::FOR_LOOP_OVER_RESULT,
210211
loops::ITER_NEXT_LOOP,

src/lifetimes.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ fn report_extra_lifetimes(cx: &LateContext, func: &FnDecl, generics: &Generics,
352352
}
353353
}
354354

355-
for (_, v) in checker.0 {
355+
for &v in checker.0.values() {
356356
span_lint(cx, UNUSED_LIFETIMES, v, "this lifetime isn't used in the function definition");
357357
}
358358
}

src/loops.rs

Lines changed: 102 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ use std::borrow::Cow;
1010
use std::collections::{HashSet, HashMap};
1111

1212
use utils::{snippet, span_lint, get_parent_expr, match_trait_method, match_type, in_external_macro, expr_block,
13-
span_help_and_lint, is_integer_literal, get_enclosing_block};
14-
use utils::{HASHMAP_PATH, VEC_PATH, LL_PATH, OPTION_PATH, RESULT_PATH};
13+
span_help_and_lint, is_integer_literal, get_enclosing_block, span_lint_and_then, walk_ptrs_ty};
14+
use utils::{BTREEMAP_PATH, HASHMAP_PATH, LL_PATH, OPTION_PATH, RESULT_PATH, VEC_PATH};
1515

1616
/// **What it does:** This lint checks for looping over the range of `0..len` of some collection just to get the values by index. It is `Warn` by default.
1717
///
@@ -141,6 +141,24 @@ declare_lint!{ pub EMPTY_LOOP, Warn, "empty `loop {}` detected" }
141141
/// **Example:** `while let Some(val) = iter() { .. }`
142142
declare_lint!{ pub WHILE_LET_ON_ITERATOR, Warn, "using a while-let loop instead of a for loop on an iterator" }
143143

144+
/// **What it does:** This warns when you iterate on a map (`HashMap` or `BTreeMap`) and ignore
145+
/// either the keys or values.
146+
///
147+
/// **Why is this bad?** Readability. There are `keys` and `values` methods that can be used to
148+
/// express that don't need the values or keys.
149+
///
150+
/// **Known problems:** None
151+
///
152+
/// **Example:**
153+
/// ```rust
154+
/// for (k, _) in &map { .. }
155+
/// ```
156+
/// could be replaced by
157+
/// ```rust
158+
/// for k in map.keys() { .. }
159+
/// ```
160+
declare_lint!{ pub FOR_KV_MAP, Warn, "looping on a map using `iter` when `keys` or `values` would do" }
161+
144162
#[derive(Copy, Clone)]
145163
pub struct LoopsPass;
146164

@@ -154,7 +172,8 @@ impl LintPass for LoopsPass {
154172
REVERSE_RANGE_LOOP,
155173
EXPLICIT_COUNTER_LOOP,
156174
EMPTY_LOOP,
157-
WHILE_LET_ON_ITERATOR)
175+
WHILE_LET_ON_ITERATOR,
176+
FOR_KV_MAP)
158177
}
159178
}
160179

@@ -270,6 +289,7 @@ fn check_for_loop(cx: &LateContext, pat: &Pat, arg: &Expr, body: &Expr, expr: &E
270289
check_for_loop_reverse_range(cx, arg, expr);
271290
check_for_loop_arg(cx, pat, arg, expr);
272291
check_for_loop_explicit_counter(cx, arg, body, expr);
292+
check_for_loop_over_map_kv(cx, pat, arg, body, expr);
273293
}
274294

275295
/// Check for looping over a range and then indexing a sequence with it.
@@ -499,6 +519,79 @@ fn check_for_loop_explicit_counter(cx: &LateContext, arg: &Expr, body: &Expr, ex
499519
}
500520
}
501521

522+
// Check for the FOR_KV_MAP lint.
523+
fn check_for_loop_over_map_kv(cx: &LateContext, pat: &Pat, arg: &Expr, body: &Expr, expr: &Expr) {
524+
if let PatTup(ref pat) = pat.node {
525+
if pat.len() == 2 {
526+
527+
let (pat_span, kind) = match (&pat[0].node, &pat[1].node) {
528+
(key, _) if pat_is_wild(key, body) => (&pat[1].span, "values"),
529+
(_, value) if pat_is_wild(value, body) => (&pat[0].span, "keys"),
530+
_ => return
531+
};
532+
533+
let ty = walk_ptrs_ty(cx.tcx.expr_ty(arg));
534+
let arg_span = if let ExprAddrOf(_, ref expr) = arg.node {
535+
expr.span
536+
}
537+
else {
538+
arg.span
539+
};
540+
541+
if match_type(cx, ty, &HASHMAP_PATH) ||
542+
match_type(cx, ty, &BTREEMAP_PATH) {
543+
span_lint_and_then(cx,
544+
FOR_KV_MAP,
545+
expr.span,
546+
&format!("you seem to want to iterate on a map's {}", kind),
547+
|db| {
548+
db.span_suggestion(expr.span,
549+
"use the corresponding method",
550+
format!("for {} in {}.{}() {{...}}",
551+
snippet(cx, *pat_span, ".."),
552+
snippet(cx, arg_span, ".."),
553+
kind));
554+
});
555+
}
556+
}
557+
}
558+
559+
}
560+
561+
// Return true if the pattern is a `PatWild` or an ident prefixed with '_'.
562+
fn pat_is_wild(pat: &Pat_, body: &Expr) -> bool {
563+
match *pat {
564+
PatWild => true,
565+
PatIdent(_, ident, None) if ident.node.name.as_str().starts_with('_') => {
566+
let mut visitor = UsedVisitor {
567+
var: ident.node,
568+
used: false,
569+
};
570+
walk_expr(&mut visitor, body);
571+
!visitor.used
572+
},
573+
_ => false,
574+
}
575+
}
576+
577+
struct UsedVisitor {
578+
var: Ident, // var to look for
579+
used: bool, // has the var been used otherwise?
580+
}
581+
582+
impl<'a> Visitor<'a> for UsedVisitor {
583+
fn visit_expr(&mut self, expr: &Expr) {
584+
if let ExprPath(None, ref path) = expr.node {
585+
if path.segments.len() == 1 && path.segments[0].identifier == self.var {
586+
self.used = true;
587+
return
588+
}
589+
}
590+
591+
walk_expr(self, expr);
592+
}
593+
}
594+
502595
/// Recover the essential nodes of a desugared for loop:
503596
/// `for pat in arg { body }` becomes `(pat, arg, body)`.
504597
fn recover_for_loop(expr: &Expr) -> Option<(&Pat, &Expr, &Expr)> {
@@ -601,11 +694,14 @@ fn is_ref_iterable_type(cx: &LateContext, e: &Expr) -> bool {
601694
// no walk_ptrs_ty: calling iter() on a reference can make sense because it
602695
// will allow further borrows afterwards
603696
let ty = cx.tcx.expr_ty(e);
604-
is_iterable_array(ty) || match_type(cx, ty, &VEC_PATH) || match_type(cx, ty, &LL_PATH) ||
605-
match_type(cx, ty, &HASHMAP_PATH) || match_type(cx, ty, &["std", "collections", "hash", "set", "HashSet"]) ||
697+
is_iterable_array(ty) ||
698+
match_type(cx, ty, &VEC_PATH) ||
699+
match_type(cx, ty, &LL_PATH) ||
700+
match_type(cx, ty, &HASHMAP_PATH) ||
701+
match_type(cx, ty, &["std", "collections", "hash", "set", "HashSet"]) ||
606702
match_type(cx, ty, &["collections", "vec_deque", "VecDeque"]) ||
607703
match_type(cx, ty, &["collections", "binary_heap", "BinaryHeap"]) ||
608-
match_type(cx, ty, &["collections", "btree", "map", "BTreeMap"]) ||
704+
match_type(cx, ty, &BTREEMAP_PATH) ||
609705
match_type(cx, ty, &["collections", "btree", "set", "BTreeSet"])
610706
}
611707

tests/compile-fail/for_loop.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,4 +280,33 @@ fn main() {
280280
println!("index: {}", index);
281281

282282
for_loop_over_option_and_result();
283+
284+
let m : HashMap<u64, u64> = HashMap::new();
285+
for (_, v) in &m {
286+
//~^ you seem to want to iterate on a map's values
287+
//~| HELP use the corresponding method
288+
//~| SUGGESTION for v in &m.values()
289+
let _v = v;
290+
}
291+
292+
let rm = &m;
293+
for (k, _value) in rm {
294+
//~^ you seem to want to iterate on a map's keys
295+
//~| HELP use the corresponding method
296+
//~| SUGGESTION for k in rm.keys()
297+
let _k = k;
298+
}
299+
300+
test_for_kv_map();
301+
}
302+
303+
#[allow(used_underscore_binding)]
304+
fn test_for_kv_map() {
305+
let m : HashMap<u64, u64> = HashMap::new();
306+
307+
// No error, _value is actually used
308+
for (k, _value) in &m {
309+
let _ = _value;
310+
let _k = k;
311+
}
283312
}

0 commit comments

Comments
 (0)