Skip to content

Commit 02b9692

Browse files
committed
New Lint: excessive_for_each
1 parent d5223be commit 02b9692

File tree

7 files changed

+402
-0
lines changed

7 files changed

+402
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2056,6 +2056,7 @@ Released 2018-09-13
20562056
[`eq_op`]: https://rust-lang.github.io/rust-clippy/master/index.html#eq_op
20572057
[`erasing_op`]: https://rust-lang.github.io/rust-clippy/master/index.html#erasing_op
20582058
[`eval_order_dependence`]: https://rust-lang.github.io/rust-clippy/master/index.html#eval_order_dependence
2059+
[`excessive_for_each`]: https://rust-lang.github.io/rust-clippy/master/index.html#excessive_for_each
20592060
[`excessive_precision`]: https://rust-lang.github.io/rust-clippy/master/index.html#excessive_precision
20602061
[`exhaustive_enums`]: https://rust-lang.github.io/rust-clippy/master/index.html#exhaustive_enums
20612062
[`exhaustive_structs`]: https://rust-lang.github.io/rust-clippy/master/index.html#exhaustive_structs

clippy_lints/src/iter_for_each.rs

Whitespace-only changes.

clippy_lints/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -759,6 +759,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
759759
&methods::CLONE_DOUBLE_REF,
760760
&methods::CLONE_ON_COPY,
761761
&methods::CLONE_ON_REF_PTR,
762+
&methods::EXCESSIVE_FOR_EACH,
762763
&methods::EXPECT_FUN_CALL,
763764
&methods::EXPECT_USED,
764765
&methods::FILETYPE_IS_FILE,
@@ -1567,6 +1568,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
15671568
LintId::of(&methods::CHARS_NEXT_CMP),
15681569
LintId::of(&methods::CLONE_DOUBLE_REF),
15691570
LintId::of(&methods::CLONE_ON_COPY),
1571+
LintId::of(&methods::EXCESSIVE_FOR_EACH),
15701572
LintId::of(&methods::EXPECT_FUN_CALL),
15711573
LintId::of(&methods::FILTER_MAP_IDENTITY),
15721574
LintId::of(&methods::FILTER_NEXT),
@@ -1785,6 +1787,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
17851787
LintId::of(&methods::BYTES_NTH),
17861788
LintId::of(&methods::CHARS_LAST_CMP),
17871789
LintId::of(&methods::CHARS_NEXT_CMP),
1790+
LintId::of(&methods::EXCESSIVE_FOR_EACH),
17881791
LintId::of(&methods::FROM_ITER_INSTEAD_OF_COLLECT),
17891792
LintId::of(&methods::INTO_ITER_ON_REF),
17901793
LintId::of(&methods::ITER_CLONED_COLLECT),
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
use rustc_errors::Applicability;
2+
use rustc_hir::{
3+
intravisit::{walk_expr, NestedVisitorMap, Visitor},
4+
Expr, ExprKind,
5+
};
6+
use rustc_lint::LateContext;
7+
use rustc_middle::{hir::map::Map, ty, ty::Ty};
8+
use rustc_span::source_map::Span;
9+
10+
use crate::utils::{match_trait_method, match_type, paths, snippet, span_lint_and_then};
11+
12+
use if_chain::if_chain;
13+
14+
pub(super) fn lint(cx: &LateContext<'_>, expr: &'tcx Expr<'_>, args: &[&[Expr<'_>]]) {
15+
if args.len() < 2 {
16+
return;
17+
}
18+
19+
let for_each_args = args[0];
20+
if for_each_args.len() < 2 {
21+
return;
22+
}
23+
let for_each_receiver = &for_each_args[0];
24+
let for_each_arg = &for_each_args[1];
25+
let iter_receiver = &args[1][0];
26+
27+
if_chain! {
28+
if match_trait_method(cx, expr, &paths::ITERATOR);
29+
if !match_trait_method(cx, for_each_receiver, &paths::ITERATOR);
30+
if is_target_ty(cx, cx.typeck_results().expr_ty(iter_receiver));
31+
if let ExprKind::Closure(_, _, body_id, ..) = for_each_arg.kind;
32+
then {
33+
let body = cx.tcx.hir().body(body_id);
34+
let mut ret_span_collector = RetSpanCollector::new();
35+
ret_span_collector.visit_expr(&body.value);
36+
37+
let label = "'outer";
38+
let loop_label = if ret_span_collector.need_label {
39+
format!("{}: ", label)
40+
} else {
41+
"".to_string()
42+
};
43+
let sugg =
44+
format!("{}for {} in {} {{ .. }}", loop_label, snippet(cx, body.params[0].pat.span, ""), snippet(cx, for_each_receiver.span, ""));
45+
46+
let mut notes = vec![];
47+
for (span, need_label) in ret_span_collector.spans {
48+
let cont_label = if need_label {
49+
format!(" {}", label)
50+
} else {
51+
"".to_string()
52+
};
53+
let note = format!("change `return` to `continue{}` in the loop body", cont_label);
54+
notes.push((span, note));
55+
}
56+
57+
span_lint_and_then(cx,
58+
super::EXCESSIVE_FOR_EACH,
59+
expr.span,
60+
"excessive use of `for_each`",
61+
|diag| {
62+
diag.span_suggestion(expr.span, "try", sugg, Applicability::HasPlaceholders);
63+
for note in notes {
64+
diag.span_note(note.0, &note.1);
65+
}
66+
}
67+
);
68+
}
69+
}
70+
}
71+
72+
type PathSegment = &'static [&'static str];
73+
74+
const TARGET_ITER_RECEIVER_TY: &[PathSegment] = &[
75+
&paths::VEC,
76+
&paths::VEC_DEQUE,
77+
&paths::LINKED_LIST,
78+
&paths::HASHMAP,
79+
&paths::BTREEMAP,
80+
&paths::HASHSET,
81+
&paths::BTREESET,
82+
&paths::BINARY_HEAP,
83+
];
84+
85+
fn is_target_ty(cx: &LateContext<'_>, expr_ty: Ty<'_>) -> bool {
86+
let expr_ty = expr_ty.peel_refs();
87+
for target in TARGET_ITER_RECEIVER_TY {
88+
if match_type(cx, expr_ty, target) {
89+
return true;
90+
}
91+
}
92+
93+
if_chain! {
94+
if matches!(expr_ty.kind(), ty::Slice(_) | ty::Array(..));
95+
then {
96+
return true;
97+
}
98+
}
99+
100+
false
101+
}
102+
103+
/// Collect spans of `return` in the closure body.
104+
struct RetSpanCollector {
105+
spans: Vec<(Span, bool)>,
106+
loop_depth: u16,
107+
need_label: bool,
108+
}
109+
110+
impl RetSpanCollector {
111+
fn new() -> Self {
112+
Self {
113+
spans: Vec::new(),
114+
loop_depth: 0,
115+
need_label: false,
116+
}
117+
}
118+
}
119+
120+
impl<'tcx> Visitor<'tcx> for RetSpanCollector {
121+
type Map = Map<'tcx>;
122+
123+
fn visit_expr(&mut self, expr: &Expr<'_>) {
124+
match expr.kind {
125+
ExprKind::Ret(..) => {
126+
if self.loop_depth > 0 && !self.need_label {
127+
self.need_label = true
128+
}
129+
130+
self.spans.push((expr.span, self.loop_depth > 0))
131+
},
132+
133+
ExprKind::Loop(..) => {
134+
self.loop_depth += 1;
135+
walk_expr(self, expr);
136+
self.loop_depth -= 1;
137+
return;
138+
},
139+
140+
_ => {},
141+
}
142+
143+
walk_expr(self, expr);
144+
}
145+
146+
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
147+
NestedVisitorMap::None
148+
}
149+
}

clippy_lints/src/methods/mod.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
mod bind_instead_of_map;
22
mod bytes_nth;
3+
mod excessive_for_each;
34
mod filter_map_identity;
45
mod inefficient_to_string;
56
mod inspect_for_each;
@@ -928,6 +929,33 @@ declare_clippy_lint! {
928929
"using `.skip(x).next()` on an iterator"
929930
}
930931

932+
declare_clippy_lint! {
933+
/// **What it does:** Checks for use of `obj.method().for_each(closure)` if obj doesn't
934+
/// implelement `Iterator` and `method()` returns `Impl Iterator` type.
935+
///
936+
/// **Why is this bad?** Excessive use of `for_each` reduces redability, using `for` loop is
937+
/// clearer and more concise.
938+
///
939+
/// **Known problems:** None.
940+
///
941+
/// **Example:**
942+
///
943+
/// ```rust
944+
/// let v = vec![0, 1, 2];
945+
/// v.iter().for_each(|elem| println!("{}", elem));
946+
/// ```
947+
/// Use instead:
948+
/// ```rust
949+
/// let v = vec![0, 1, 2];
950+
/// for elem in v.iter() {
951+
/// println!("{}", elem);
952+
/// }
953+
/// ```
954+
pub EXCESSIVE_FOR_EACH,
955+
style,
956+
"using `.iter().for_each(|x| {..})` when using `for` loop would work instead"
957+
}
958+
931959
declare_clippy_lint! {
932960
/// **What it does:** Checks for use of `.get().unwrap()` (or
933961
/// `.get_mut().unwrap`) on a standard library type which implements `Index`
@@ -1562,6 +1590,7 @@ impl_lint_pass!(Methods => [
15621590
ITER_NTH_ZERO,
15631591
BYTES_NTH,
15641592
ITER_SKIP_NEXT,
1593+
EXCESSIVE_FOR_EACH,
15651594
GET_UNWRAP,
15661595
STRING_EXTEND_CHARS,
15671596
ITER_CLONED_COLLECT,
@@ -1670,6 +1699,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
16701699
["ok_or_else", ..] => unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], "ok_or"),
16711700
["collect", "map"] => lint_map_collect(cx, expr, arg_lists[1], arg_lists[0]),
16721701
["for_each", "inspect"] => inspect_for_each::lint(cx, expr, method_spans[1]),
1702+
["for_each", ..] => excessive_for_each::lint(cx, expr, &arg_lists),
16731703
_ => {},
16741704
}
16751705

tests/ui/excessive_for_each.rs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
#![warn(clippy::excessive_for_each)]
2+
#![allow(clippy::needless_return)]
3+
4+
use std::collections::*;
5+
6+
fn main() {
7+
// Should trigger this lint: Vec.
8+
let vec: Vec<i32> = Vec::new();
9+
vec.iter().for_each(|v| println!("{}", v));
10+
11+
// Should trigger this lint: &Vec.
12+
let vec_ref = &vec;
13+
vec_ref.iter().for_each(|v| println!("{}", v));
14+
15+
// Should trigger this lint: VecDeque.
16+
let vec_deq: VecDeque<i32> = VecDeque::new();
17+
vec_deq.iter().for_each(|v| println!("{}", v));
18+
19+
// Should trigger this lint: LinkedList.
20+
let list: LinkedList<i32> = LinkedList::new();
21+
list.iter().for_each(|v| println!("{}", v));
22+
23+
// Should trigger this lint: HashMap.
24+
let mut hash_map: HashMap<i32, i32> = HashMap::new();
25+
hash_map.iter().for_each(|(k, v)| println!("{}: {}", k, v));
26+
hash_map.iter_mut().for_each(|(k, v)| println!("{}: {}", k, v));
27+
hash_map.keys().for_each(|k| println!("{}", k));
28+
hash_map.values().for_each(|v| println!("{}", v));
29+
30+
// Should trigger this lint: HashSet.
31+
let hash_set: HashSet<i32> = HashSet::new();
32+
hash_set.iter().for_each(|v| println!("{}", v));
33+
34+
// Should trigger this lint: BTreeSet.
35+
let btree_set: BTreeSet<i32> = BTreeSet::new();
36+
btree_set.iter().for_each(|v| println!("{}", v));
37+
38+
// Should trigger this lint: BinaryHeap.
39+
let binary_heap: BinaryHeap<i32> = BinaryHeap::new();
40+
binary_heap.iter().for_each(|v| println!("{}", v));
41+
42+
// Should trigger this lint: Array.
43+
let s = [1, 2, 3];
44+
s.iter().for_each(|v| println!("{}", v));
45+
46+
// Should trigger this lint. Slice.
47+
vec.as_slice().iter().for_each(|v| println!("{}", v));
48+
49+
// Should trigger this lint with notes that say "change `return` to `continue`".
50+
vec.iter().for_each(|v| {
51+
if *v == 10 {
52+
return;
53+
} else {
54+
println!("{}", v);
55+
}
56+
});
57+
58+
// Should trigger this lint with notes that say "change `return` to `continue 'outer`".
59+
vec.iter().for_each(|v| {
60+
for i in 0..*v {
61+
if i == 10 {
62+
return;
63+
} else {
64+
println!("{}", v);
65+
}
66+
}
67+
if *v == 20 {
68+
return;
69+
} else {
70+
println!("{}", v);
71+
}
72+
});
73+
74+
// Should NOT trigger this lint in case `for_each` follows long iterator chain.
75+
vec.iter().chain(vec.iter()).for_each(|v| println!("{}", v));
76+
77+
// Should NOT trigger this lint in case a `for_each` argument is not closure.
78+
fn print(x: &i32) {
79+
println!("{}", x);
80+
}
81+
vec.iter().for_each(print);
82+
83+
// Should NOT trigger this lint in case the receiver of `iter` is a user defined type.
84+
let my_collection = MyCollection { v: vec![] };
85+
my_collection.iter().for_each(|v| println!("{}", v));
86+
}
87+
88+
struct MyCollection {
89+
v: Vec<i32>,
90+
}
91+
92+
impl MyCollection {
93+
fn iter(&self) -> impl Iterator<Item = &i32> {
94+
self.v.iter()
95+
}
96+
}

0 commit comments

Comments
 (0)