-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
392 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
use rustc_errors::Applicability; | ||
use rustc_hir::{ | ||
intravisit::{walk_expr, NestedVisitorMap, Visitor}, | ||
Expr, ExprKind, | ||
}; | ||
use rustc_lint::LateContext; | ||
use rustc_middle::{hir::map::Map, ty, ty::Ty}; | ||
use rustc_span::source_map::Span; | ||
|
||
use crate::utils::{match_trait_method, match_type, paths, snippet, span_lint_and_then}; | ||
|
||
use if_chain::if_chain; | ||
|
||
pub(super) fn lint(cx: &LateContext<'_>, expr: &'tcx Expr<'_>, args: &[&[Expr<'_>]]) { | ||
if args.len() < 2 { | ||
return; | ||
} | ||
|
||
let for_each_args = args[0]; | ||
if for_each_args.len() < 2 { | ||
return; | ||
} | ||
let for_each_receiver = &for_each_args[0]; | ||
let for_each_arg = &for_each_args[1]; | ||
let iter_receiver = &args[1][0]; | ||
|
||
if_chain! { | ||
if match_trait_method(cx, expr, &paths::ITERATOR); | ||
if !match_trait_method(cx, for_each_receiver, &paths::ITERATOR); | ||
if is_target_ty(cx, cx.typeck_results().expr_ty(iter_receiver)); | ||
if let ExprKind::Closure(_, _, body_id, ..) = for_each_arg.kind; | ||
then { | ||
let body = cx.tcx.hir().body(body_id); | ||
let mut ret_span_collector = RetSpanCollector::new(); | ||
ret_span_collector.visit_expr(&body.value); | ||
|
||
let label = "'outer"; | ||
let loop_label = if ret_span_collector.need_label { | ||
format!("{}: ", label) | ||
} else { | ||
"".to_string() | ||
}; | ||
let sugg = | ||
format!("{}for {} in {} {{ .. }}", loop_label, snippet(cx, body.params[0].pat.span, ""), snippet(cx, for_each_receiver.span, "")); | ||
|
||
let mut notes = vec![]; | ||
for (span, need_label) in ret_span_collector.spans { | ||
let cont_label = if need_label { | ||
format!(" {}", label) | ||
} else { | ||
"".to_string() | ||
}; | ||
let note = format!("change `return` to `continue{}` in the loop body", cont_label); | ||
notes.push((span, note)); | ||
} | ||
|
||
span_lint_and_then(cx, | ||
super::EXCESSIVE_FOR_EACH, | ||
expr.span, | ||
"excessive use of `for_each`", | ||
|diag| { | ||
diag.span_suggestion(expr.span, "try", sugg, Applicability::HasPlaceholders); | ||
for note in notes { | ||
diag.span_note(note.0, ¬e.1); | ||
} | ||
} | ||
); | ||
} | ||
} | ||
} | ||
|
||
type PathSegment = &'static [&'static str]; | ||
|
||
const TARGET_ITER_RECEIVER_TY: &[PathSegment] = &[ | ||
&paths::VEC, | ||
&paths::VEC_DEQUE, | ||
&paths::LINKED_LIST, | ||
&paths::HASHMAP, | ||
&paths::BTREEMAP, | ||
&paths::HASHSET, | ||
&paths::BTREESET, | ||
&paths::BINARY_HEAP, | ||
]; | ||
|
||
fn is_target_ty(cx: &LateContext<'_>, expr_ty: Ty<'_>) -> bool { | ||
for target in TARGET_ITER_RECEIVER_TY { | ||
if match_type(cx, expr_ty, target) { | ||
return true; | ||
} | ||
} | ||
|
||
if_chain! { | ||
if let ty::Ref(_, inner_ty, _) = expr_ty.kind(); | ||
if matches!(inner_ty.kind(), ty::Slice(_) | ty::Array(..)); | ||
then { | ||
return true; | ||
} | ||
} | ||
|
||
false | ||
} | ||
|
||
/// Collect spans of `return` in the closure body. | ||
struct RetSpanCollector { | ||
spans: Vec<(Span, bool)>, | ||
loop_depth: u16, | ||
need_label: bool, | ||
} | ||
|
||
impl RetSpanCollector { | ||
fn new() -> Self { | ||
Self { | ||
spans: Vec::new(), | ||
loop_depth: 0, | ||
need_label: false, | ||
} | ||
} | ||
} | ||
|
||
impl<'tcx> Visitor<'tcx> for RetSpanCollector { | ||
type Map = Map<'tcx>; | ||
|
||
fn visit_expr(&mut self, expr: &Expr<'_>) { | ||
match expr.kind { | ||
ExprKind::Ret(..) => { | ||
if self.loop_depth > 0 && !self.need_label { | ||
self.need_label = true | ||
} | ||
|
||
self.spans.push((expr.span, self.loop_depth > 0)) | ||
}, | ||
|
||
ExprKind::Loop(..) => { | ||
self.loop_depth += 1; | ||
walk_expr(self, expr); | ||
self.loop_depth -= 1; | ||
return; | ||
}, | ||
|
||
_ => {}, | ||
} | ||
|
||
walk_expr(self, expr); | ||
} | ||
|
||
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> { | ||
NestedVisitorMap::None | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
#![warn(clippy::excessive_for_each)] | ||
#![allow(clippy::needless_return)] | ||
|
||
use std::collections::*; | ||
|
||
fn main() { | ||
// Should trigger this lint. | ||
let vec: Vec<i32> = Vec::new(); | ||
vec.iter().for_each(|v| println!("{}", v)); | ||
|
||
// Should trigger this lint. | ||
let vec_deq: VecDeque<i32> = VecDeque::new(); | ||
vec_deq.iter().for_each(|v| println!("{}", v)); | ||
|
||
// Should trigger this lint. | ||
let list: LinkedList<i32> = LinkedList::new(); | ||
list.iter().for_each(|v| println!("{}", v)); | ||
|
||
// Should trigger this lint. | ||
let mut hash_map: HashMap<i32, i32> = HashMap::new(); | ||
hash_map.iter().for_each(|(k, v)| println!("{}: {}", k, v)); | ||
hash_map.iter_mut().for_each(|(k, v)| println!("{}: {}", k, v)); | ||
hash_map.keys().for_each(|k| println!("{}", k)); | ||
hash_map.values().for_each(|v| println!("{}", v)); | ||
|
||
// Should trigger this lint. | ||
let hash_set: HashSet<i32> = HashSet::new(); | ||
hash_set.iter().for_each(|v| println!("{}", v)); | ||
|
||
// Should trigger this lint. | ||
let btree_set: BTreeSet<i32> = BTreeSet::new(); | ||
btree_set.iter().for_each(|v| println!("{}", v)); | ||
|
||
// Should trigger this lint. | ||
let binary_heap: BinaryHeap<i32> = BinaryHeap::new(); | ||
binary_heap.iter().for_each(|v| println!("{}", v)); | ||
|
||
// Should trigger this lint. | ||
let s = &[1, 2, 3]; | ||
s.iter().for_each(|v| println!("{}", v)); | ||
|
||
// Should trigger this lint. | ||
vec.as_slice().iter().for_each(|v| println!("{}", v)); | ||
|
||
// Should trigger this lint with notes that say "change `return` to `continue`". | ||
vec.iter().for_each(|v| { | ||
if *v == 10 { | ||
return; | ||
} else { | ||
println!("{}", v); | ||
} | ||
}); | ||
|
||
// Should trigger this lint with notes that say "change `return` to `continue 'outer`". | ||
vec.iter().for_each(|v| { | ||
for i in 0..*v { | ||
if i == 10 { | ||
return; | ||
} else { | ||
println!("{}", v); | ||
} | ||
} | ||
if *v == 20 { | ||
return; | ||
} else { | ||
println!("{}", v); | ||
} | ||
}); | ||
|
||
// Should NOT trigger this lint in case `for_each` follows long iterator chain. | ||
vec.iter().chain(vec.iter()).for_each(|v| println!("{}", v)); | ||
|
||
// Should NOT trigger this lint in case a `for_each` argument is not closure. | ||
fn print(x: &i32) { | ||
println!("{}", x); | ||
} | ||
vec.iter().for_each(print); | ||
|
||
// Should NOT trigger this lint in case the receiver of `iter` is a user defined type. | ||
let my_collection = MyCollection { v: vec![] }; | ||
my_collection.iter().for_each(|v| println!("{}", v)); | ||
} | ||
|
||
struct MyCollection { | ||
v: Vec<i32>, | ||
} | ||
|
||
impl MyCollection { | ||
fn iter(&self) -> impl Iterator<Item = &i32> { | ||
self.v.iter() | ||
} | ||
} |
Oops, something went wrong.