Skip to content

Commit 51617b8

Browse files
committed
new lint: iter_count
1 parent 7154b23 commit 51617b8

File tree

7 files changed

+238
-0
lines changed

7 files changed

+238
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2135,6 +2135,7 @@ Released 2018-09-13
21352135
[`invisible_characters`]: https://rust-lang.github.io/rust-clippy/master/index.html#invisible_characters
21362136
[`items_after_statements`]: https://rust-lang.github.io/rust-clippy/master/index.html#items_after_statements
21372137
[`iter_cloned_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_cloned_collect
2138+
[`iter_count`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_count
21382139
[`iter_next_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_next_loop
21392140
[`iter_next_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_next_slice
21402141
[`iter_nth`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_nth

clippy_lints/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -775,6 +775,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
775775
&methods::INTO_ITER_ON_REF,
776776
&methods::ITERATOR_STEP_BY_ZERO,
777777
&methods::ITER_CLONED_COLLECT,
778+
&methods::ITER_COUNT,
778779
&methods::ITER_NEXT_SLICE,
779780
&methods::ITER_NTH,
780781
&methods::ITER_NTH_ZERO,
@@ -1577,6 +1578,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
15771578
LintId::of(&methods::INTO_ITER_ON_REF),
15781579
LintId::of(&methods::ITERATOR_STEP_BY_ZERO),
15791580
LintId::of(&methods::ITER_CLONED_COLLECT),
1581+
LintId::of(&methods::ITER_COUNT),
15801582
LintId::of(&methods::ITER_NEXT_SLICE),
15811583
LintId::of(&methods::ITER_NTH),
15821584
LintId::of(&methods::ITER_NTH_ZERO),
@@ -1881,6 +1883,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
18811883
LintId::of(&methods::FILTER_NEXT),
18821884
LintId::of(&methods::FLAT_MAP_IDENTITY),
18831885
LintId::of(&methods::INSPECT_FOR_EACH),
1886+
LintId::of(&methods::ITER_COUNT),
18841887
LintId::of(&methods::MANUAL_FILTER_MAP),
18851888
LintId::of(&methods::MANUAL_FIND_MAP),
18861889
LintId::of(&methods::OPTION_AS_REF_DEREF),

clippy_lints/src/methods/mod.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1540,6 +1540,32 @@ declare_clippy_lint! {
15401540
"implicitly cloning a value by invoking a function on its dereferenced type"
15411541
}
15421542

1543+
declare_clippy_lint! {
1544+
/// **What it does:** Checks for the use of `.iter().count()`.
1545+
///
1546+
/// **Why is this bad?** `.len()` is more efficient and more
1547+
/// readable.
1548+
///
1549+
/// **Known problems:** None.
1550+
///
1551+
/// **Example:**
1552+
///
1553+
/// ```rust
1554+
/// // Bad
1555+
/// let some_vec = vec![0, 1, 2, 3];
1556+
/// let _ = some_vec.iter().count();
1557+
/// let _ = &some_vec[..].iter().count();
1558+
///
1559+
/// // Good
1560+
/// let some_vec = vec![0, 1, 2, 3];
1561+
/// let _ = some_vec.len();
1562+
/// let _ = &some_vec[..].len();
1563+
/// ```
1564+
pub ITER_COUNT,
1565+
complexity,
1566+
"replace `.iter().count()` with `.len()`"
1567+
}
1568+
15431569
pub struct Methods {
15441570
msrv: Option<RustcVersion>,
15451571
}
@@ -1585,6 +1611,7 @@ impl_lint_pass!(Methods => [
15851611
MAP_FLATTEN,
15861612
ITERATOR_STEP_BY_ZERO,
15871613
ITER_NEXT_SLICE,
1614+
ITER_COUNT,
15881615
ITER_NTH,
15891616
ITER_NTH_ZERO,
15901617
BYTES_NTH,
@@ -1664,6 +1691,8 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
16641691
lint_search_is_some(cx, expr, "rposition", arg_lists[1], arg_lists[0], method_spans[1])
16651692
},
16661693
["extend", ..] => lint_extend(cx, expr, arg_lists[0]),
1694+
["count", "iter"] => lint_iter_count(cx, expr, &arg_lists[1], false),
1695+
["count", "iter_mut"] => lint_iter_count(cx, expr, &arg_lists[1], true),
16671696
["nth", "iter"] => lint_iter_nth(cx, expr, &arg_lists, false),
16681697
["nth", "iter_mut"] => lint_iter_nth(cx, expr, &arg_lists, true),
16691698
["nth", "bytes"] => bytes_nth::lints(cx, expr, &arg_lists[1]),
@@ -2632,6 +2661,39 @@ fn lint_iter_next<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, iter_
26322661
}
26332662
}
26342663

2664+
fn lint_iter_count<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, iter_args: &'tcx [Expr<'tcx>], is_mut: bool) {
2665+
let mut_str = if is_mut { "_mut" } else { "" };
2666+
if_chain! {
2667+
let caller_type = if derefs_to_slice(cx, &iter_args[0], cx.typeck_results().expr_ty(&iter_args[0])).is_some() {
2668+
Some("slice")
2669+
} else if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&iter_args[0]), sym::vec_type) {
2670+
Some("Vec")
2671+
} else if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&iter_args[0]), sym!(vecdeque_type)) {
2672+
Some("VecDeque")
2673+
} else if match_trait_method(cx, expr, &paths::ITERATOR) {
2674+
Some("std::iter::Iterator")
2675+
} else {
2676+
None
2677+
};
2678+
if let Some(caller_type) = caller_type;
2679+
then {
2680+
let mut applicability = Applicability::MachineApplicable;
2681+
span_lint_and_sugg(
2682+
cx,
2683+
ITER_COUNT,
2684+
expr.span,
2685+
&format!("called `.iter{}().count()` on a `{}`", mut_str, caller_type),
2686+
"try",
2687+
format!(
2688+
"{}.len()",
2689+
snippet_with_applicability(cx, iter_args[0].span, "..", &mut applicability),
2690+
),
2691+
applicability,
2692+
);
2693+
}
2694+
}
2695+
}
2696+
26352697
fn lint_iter_nth<'tcx>(
26362698
cx: &LateContext<'tcx>,
26372699
expr: &hir::Expr<'_>,

tests/ui/auxiliary/option_helpers.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,8 @@ impl IteratorFalsePositives {
4848
pub fn skip_while(self) -> IteratorFalsePositives {
4949
self
5050
}
51+
52+
pub fn count(self) -> usize {
53+
self.foo as usize
54+
}
5155
}

tests/ui/iter_count.fixed

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// run-rustfix
2+
// aux-build:option_helpers.rs
3+
4+
#![warn(clippy::iter_count)]
5+
#![allow(unused_variables)]
6+
#![allow(unused_mut)]
7+
8+
extern crate option_helpers;
9+
10+
use option_helpers::IteratorFalsePositives;
11+
use std::collections::{HashSet, VecDeque};
12+
13+
/// Struct to generate false positives for things with `.iter()`.
14+
#[derive(Copy, Clone)]
15+
struct HasIter;
16+
17+
impl HasIter {
18+
fn iter(self) -> IteratorFalsePositives {
19+
IteratorFalsePositives { foo: 0 }
20+
}
21+
22+
fn iter_mut(self) -> IteratorFalsePositives {
23+
IteratorFalsePositives { foo: 0 }
24+
}
25+
}
26+
27+
fn main() {
28+
let mut some_vec = vec![0, 1, 2, 3];
29+
let mut boxed_slice: Box<[u8]> = Box::new([0, 1, 2, 3]);
30+
let mut some_vec_deque: VecDeque<_> = some_vec.iter().cloned().collect();
31+
let mut some_hash_set = HashSet::new();
32+
some_hash_set.insert(1);
33+
34+
{
35+
// Make sure we lint `.iter()` for relevant types.
36+
let bad_vec = some_vec.len();
37+
let bad_slice = &some_vec[..].len();
38+
let bad_boxed_slice = boxed_slice.len();
39+
let bad_vec_deque = some_vec_deque.len();
40+
let bad_hash_set = some_hash_set.len();
41+
}
42+
43+
{
44+
// Make sure we lint `.iter_mut()` for relevant types.
45+
let bad_vec = some_vec.len();
46+
}
47+
{
48+
let bad_slice = &some_vec[..].len();
49+
}
50+
{
51+
let bad_vec_deque = some_vec_deque.len();
52+
}
53+
54+
// Make sure we don't lint for non-relevant types.
55+
let false_positive = HasIter;
56+
let ok = false_positive.iter().count();
57+
let ok_mut = false_positive.iter_mut().count();
58+
}

tests/ui/iter_count.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// run-rustfix
2+
// aux-build:option_helpers.rs
3+
4+
#![warn(clippy::iter_count)]
5+
#![allow(unused_variables)]
6+
#![allow(unused_mut)]
7+
8+
extern crate option_helpers;
9+
10+
use option_helpers::IteratorFalsePositives;
11+
use std::collections::{HashSet, VecDeque};
12+
13+
/// Struct to generate false positives for things with `.iter()`.
14+
#[derive(Copy, Clone)]
15+
struct HasIter;
16+
17+
impl HasIter {
18+
fn iter(self) -> IteratorFalsePositives {
19+
IteratorFalsePositives { foo: 0 }
20+
}
21+
22+
fn iter_mut(self) -> IteratorFalsePositives {
23+
IteratorFalsePositives { foo: 0 }
24+
}
25+
}
26+
27+
fn main() {
28+
let mut some_vec = vec![0, 1, 2, 3];
29+
let mut boxed_slice: Box<[u8]> = Box::new([0, 1, 2, 3]);
30+
let mut some_vec_deque: VecDeque<_> = some_vec.iter().cloned().collect();
31+
let mut some_hash_set = HashSet::new();
32+
some_hash_set.insert(1);
33+
34+
{
35+
// Make sure we lint `.iter()` for relevant types.
36+
let bad_vec = some_vec.iter().count();
37+
let bad_slice = &some_vec[..].iter().count();
38+
let bad_boxed_slice = boxed_slice.iter().count();
39+
let bad_vec_deque = some_vec_deque.iter().count();
40+
let bad_hash_set = some_hash_set.iter().count();
41+
}
42+
43+
{
44+
// Make sure we lint `.iter_mut()` for relevant types.
45+
let bad_vec = some_vec.iter_mut().count();
46+
}
47+
{
48+
let bad_slice = &some_vec[..].iter_mut().count();
49+
}
50+
{
51+
let bad_vec_deque = some_vec_deque.iter_mut().count();
52+
}
53+
54+
// Make sure we don't lint for non-relevant types.
55+
let false_positive = HasIter;
56+
let ok = false_positive.iter().count();
57+
let ok_mut = false_positive.iter_mut().count();
58+
}

tests/ui/iter_count.stderr

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
error: called `.iter().count()` on a `Vec`
2+
--> $DIR/iter_count.rs:36:23
3+
|
4+
LL | let bad_vec = some_vec.iter().count();
5+
| ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `some_vec.len()`
6+
|
7+
= note: `-D clippy::iter-count` implied by `-D warnings`
8+
9+
error: called `.iter().count()` on a `slice`
10+
--> $DIR/iter_count.rs:37:26
11+
|
12+
LL | let bad_slice = &some_vec[..].iter().count();
13+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `some_vec[..].len()`
14+
15+
error: called `.iter().count()` on a `slice`
16+
--> $DIR/iter_count.rs:38:31
17+
|
18+
LL | let bad_boxed_slice = boxed_slice.iter().count();
19+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `boxed_slice.len()`
20+
21+
error: called `.iter().count()` on a `VecDeque`
22+
--> $DIR/iter_count.rs:39:29
23+
|
24+
LL | let bad_vec_deque = some_vec_deque.iter().count();
25+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `some_vec_deque.len()`
26+
27+
error: called `.iter().count()` on a `std::iter::Iterator`
28+
--> $DIR/iter_count.rs:40:28
29+
|
30+
LL | let bad_hash_set = some_hash_set.iter().count();
31+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `some_hash_set.len()`
32+
33+
error: called `.iter_mut().count()` on a `Vec`
34+
--> $DIR/iter_count.rs:45:23
35+
|
36+
LL | let bad_vec = some_vec.iter_mut().count();
37+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `some_vec.len()`
38+
39+
error: called `.iter_mut().count()` on a `slice`
40+
--> $DIR/iter_count.rs:48:26
41+
|
42+
LL | let bad_slice = &some_vec[..].iter_mut().count();
43+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `some_vec[..].len()`
44+
45+
error: called `.iter_mut().count()` on a `VecDeque`
46+
--> $DIR/iter_count.rs:51:29
47+
|
48+
LL | let bad_vec_deque = some_vec_deque.iter_mut().count();
49+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `some_vec_deque.len()`
50+
51+
error: aborting due to 8 previous errors
52+

0 commit comments

Comments
 (0)