Skip to content

Commit 3a264b7

Browse files
committed
new lint: iter_count
1 parent 76a689d commit 3a264b7

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
@@ -2134,6 +2134,7 @@ Released 2018-09-13
21342134
[`invisible_characters`]: https://rust-lang.github.io/rust-clippy/master/index.html#invisible_characters
21352135
[`items_after_statements`]: https://rust-lang.github.io/rust-clippy/master/index.html#items_after_statements
21362136
[`iter_cloned_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_cloned_collect
2137+
[`iter_count`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_count
21372138
[`iter_next_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_next_loop
21382139
[`iter_next_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_next_slice
21392140
[`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
@@ -774,6 +774,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
774774
&methods::INTO_ITER_ON_REF,
775775
&methods::ITERATOR_STEP_BY_ZERO,
776776
&methods::ITER_CLONED_COLLECT,
777+
&methods::ITER_COUNT,
777778
&methods::ITER_NEXT_SLICE,
778779
&methods::ITER_NTH,
779780
&methods::ITER_NTH_ZERO,
@@ -1576,6 +1577,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
15761577
LintId::of(&methods::INTO_ITER_ON_REF),
15771578
LintId::of(&methods::ITERATOR_STEP_BY_ZERO),
15781579
LintId::of(&methods::ITER_CLONED_COLLECT),
1580+
LintId::of(&methods::ITER_COUNT),
15791581
LintId::of(&methods::ITER_NEXT_SLICE),
15801582
LintId::of(&methods::ITER_NTH),
15811583
LintId::of(&methods::ITER_NTH_ZERO),
@@ -1879,6 +1881,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
18791881
LintId::of(&methods::FILTER_NEXT),
18801882
LintId::of(&methods::FLAT_MAP_IDENTITY),
18811883
LintId::of(&methods::INSPECT_FOR_EACH),
1884+
LintId::of(&methods::ITER_COUNT),
18821885
LintId::of(&methods::MANUAL_FILTER_MAP),
18831886
LintId::of(&methods::MANUAL_FIND_MAP),
18841887
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
@@ -1513,6 +1513,32 @@ declare_clippy_lint! {
15131513
"replace `.bytes().nth()` with `.as_bytes().get()`"
15141514
}
15151515

1516+
declare_clippy_lint! {
1517+
/// **What it does:** Checks for the use of `.iter().count()`.
1518+
///
1519+
/// **Why is this bad?** `.len()` is more efficient and more
1520+
/// readable.
1521+
///
1522+
/// **Known problems:** None.
1523+
///
1524+
/// **Example:**
1525+
///
1526+
/// ```rust
1527+
/// // Bad
1528+
/// let some_vec = vec![0, 1, 2, 3];
1529+
/// let _ = some_vec.iter().count();
1530+
/// let _ = &some_vec[..].iter().count();
1531+
///
1532+
/// // Good
1533+
/// let some_vec = vec![0, 1, 2, 3];
1534+
/// let _ = some_vec.len();
1535+
/// let _ = &some_vec[..].len();
1536+
/// ```
1537+
pub ITER_COUNT,
1538+
complexity,
1539+
"replace `.iter().count()` with `.len()`"
1540+
}
1541+
15161542
pub struct Methods {
15171543
msrv: Option<RustcVersion>,
15181544
}
@@ -1558,6 +1584,7 @@ impl_lint_pass!(Methods => [
15581584
MAP_FLATTEN,
15591585
ITERATOR_STEP_BY_ZERO,
15601586
ITER_NEXT_SLICE,
1587+
ITER_COUNT,
15611588
ITER_NTH,
15621589
ITER_NTH_ZERO,
15631590
BYTES_NTH,
@@ -1636,6 +1663,8 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
16361663
lint_search_is_some(cx, expr, "rposition", arg_lists[1], arg_lists[0], method_spans[1])
16371664
},
16381665
["extend", ..] => lint_extend(cx, expr, arg_lists[0]),
1666+
["count", "iter"] => lint_iter_count(cx, expr, &arg_lists[1], false),
1667+
["count", "iter_mut"] => lint_iter_count(cx, expr, &arg_lists[1], true),
16391668
["nth", "iter"] => lint_iter_nth(cx, expr, &arg_lists, false),
16401669
["nth", "iter_mut"] => lint_iter_nth(cx, expr, &arg_lists, true),
16411670
["nth", "bytes"] => bytes_nth::lints(cx, expr, &arg_lists[1]),
@@ -2588,6 +2617,39 @@ fn lint_iter_next<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, iter_
25882617
}
25892618
}
25902619

2620+
fn lint_iter_count<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, iter_args: &'tcx [Expr<'tcx>], is_mut: bool) {
2621+
let mut_str = if is_mut { "_mut" } else { "" };
2622+
if_chain! {
2623+
let caller_type = if derefs_to_slice(cx, &iter_args[0], cx.typeck_results().expr_ty(&iter_args[0])).is_some() {
2624+
Some("slice")
2625+
} else if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&iter_args[0]), sym::vec_type) {
2626+
Some("Vec")
2627+
} else if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&iter_args[0]), sym!(vecdeque_type)) {
2628+
Some("VecDeque")
2629+
} else if match_trait_method(cx, expr, &paths::ITERATOR) {
2630+
Some("std::iter::Iterator")
2631+
} else {
2632+
None
2633+
};
2634+
if let Some(caller_type) = caller_type;
2635+
then {
2636+
let mut applicability = Applicability::MachineApplicable;
2637+
span_lint_and_sugg(
2638+
cx,
2639+
ITER_COUNT,
2640+
expr.span,
2641+
&format!("called `.iter{}().count()` on a `{}`", mut_str, caller_type),
2642+
"try",
2643+
format!(
2644+
"{}.len()",
2645+
snippet_with_applicability(cx, iter_args[0].span, "..", &mut applicability),
2646+
),
2647+
applicability,
2648+
);
2649+
}
2650+
}
2651+
}
2652+
25912653
fn lint_iter_nth<'tcx>(
25922654
cx: &LateContext<'tcx>,
25932655
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)