Skip to content

Commit 0cbec62

Browse files
committed
New Lint: excessive_for_each
1 parent de35c29 commit 0cbec62

File tree

7 files changed

+392
-0
lines changed

7 files changed

+392
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1937,6 +1937,7 @@ Released 2018-09-13
19371937
[`eq_op`]: https://rust-lang.github.io/rust-clippy/master/index.html#eq_op
19381938
[`erasing_op`]: https://rust-lang.github.io/rust-clippy/master/index.html#erasing_op
19391939
[`eval_order_dependence`]: https://rust-lang.github.io/rust-clippy/master/index.html#eval_order_dependence
1940+
[`excessive_for_each`]: https://rust-lang.github.io/rust-clippy/master/index.html#excessive_for_each
19401941
[`excessive_precision`]: https://rust-lang.github.io/rust-clippy/master/index.html#excessive_precision
19411942
[`exhaustive_enums`]: https://rust-lang.github.io/rust-clippy/master/index.html#exhaustive_enums
19421943
[`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
@@ -739,6 +739,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
739739
&methods::CLONE_DOUBLE_REF,
740740
&methods::CLONE_ON_COPY,
741741
&methods::CLONE_ON_REF_PTR,
742+
&methods::EXCESSIVE_FOR_EACH,
742743
&methods::EXPECT_FUN_CALL,
743744
&methods::EXPECT_USED,
744745
&methods::FILETYPE_IS_FILE,
@@ -1535,6 +1536,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
15351536
LintId::of(&methods::CHARS_NEXT_CMP),
15361537
LintId::of(&methods::CLONE_DOUBLE_REF),
15371538
LintId::of(&methods::CLONE_ON_COPY),
1539+
LintId::of(&methods::EXCESSIVE_FOR_EACH),
15381540
LintId::of(&methods::EXPECT_FUN_CALL),
15391541
LintId::of(&methods::FILTER_MAP_IDENTITY),
15401542
LintId::of(&methods::FILTER_NEXT),
@@ -1751,6 +1753,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
17511753
LintId::of(&mem_replace::MEM_REPLACE_WITH_DEFAULT),
17521754
LintId::of(&methods::CHARS_LAST_CMP),
17531755
LintId::of(&methods::CHARS_NEXT_CMP),
1756+
LintId::of(&methods::EXCESSIVE_FOR_EACH),
17541757
LintId::of(&methods::FROM_ITER_INSTEAD_OF_COLLECT),
17551758
LintId::of(&methods::INTO_ITER_ON_REF),
17561759
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+
for target in TARGET_ITER_RECEIVER_TY {
87+
if match_type(cx, expr_ty, target) {
88+
return true;
89+
}
90+
}
91+
92+
if_chain! {
93+
if let ty::Ref(_, inner_ty, _) = expr_ty.kind();
94+
if matches!(inner_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,4 +1,5 @@
11
mod bind_instead_of_map;
2+
mod excessive_for_each;
23
mod filter_map_identity;
34
mod inefficient_to_string;
45
mod inspect_for_each;
@@ -927,6 +928,33 @@ declare_clippy_lint! {
927928
"using `.skip(x).next()` on an iterator"
928929
}
929930

931+
declare_clippy_lint! {
932+
/// **What it does:** Checks for use of `obj.method().for_each(closure)` if obj doesn't
933+
/// implelement `Iterator` and `method()` returns `Impl Iterator` type.
934+
///
935+
/// **Why is this bad?** Excessive use of `for_each` reduces redability, using `for` loop is
936+
/// clearer and more concise.
937+
///
938+
/// **Known problems:** None.
939+
///
940+
/// **Example:**
941+
///
942+
/// ```rust
943+
/// let v = vec![0, 1, 2];
944+
/// v.iter().for_each(|elem| println!("{}", elem));
945+
/// ```
946+
/// Use instead:
947+
/// ```rust
948+
/// let v = vec![0, 1, 2];
949+
/// for elem in v.iter() {
950+
/// println!("{}", elem);
951+
/// }
952+
/// ```
953+
pub EXCESSIVE_FOR_EACH,
954+
style,
955+
"using `.iter().for_each(|x| {..})` when using `for` loop would work instead"
956+
}
957+
930958
declare_clippy_lint! {
931959
/// **What it does:** Checks for use of `.get().unwrap()` (or
932960
/// `.get_mut().unwrap`) on a standard library type which implements `Index`
@@ -1538,6 +1566,7 @@ impl_lint_pass!(Methods => [
15381566
ITER_NTH,
15391567
ITER_NTH_ZERO,
15401568
ITER_SKIP_NEXT,
1569+
EXCESSIVE_FOR_EACH,
15411570
GET_UNWRAP,
15421571
STRING_EXTEND_CHARS,
15431572
ITER_CLONED_COLLECT,
@@ -1645,6 +1674,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
16451674
["ok_or_else", ..] => unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], "ok_or"),
16461675
["collect", "map"] => lint_map_collect(cx, expr, arg_lists[1], arg_lists[0]),
16471676
["for_each", "inspect"] => inspect_for_each::lint(cx, expr, method_spans[1]),
1677+
["for_each", ..] => excessive_for_each::lint(cx, expr, &arg_lists),
16481678
_ => {},
16491679
}
16501680

tests/ui/excessive_for_each.rs

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

0 commit comments

Comments
 (0)