1 use super::NEEDLESS_COLLECT;
2 use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
3 use clippy_utils::higher;
4 use clippy_utils::source::{snippet, snippet_with_applicability};
5 use clippy_utils::sugg::Sugg;
6 use clippy_utils::ty::{is_type_diagnostic_item, make_normalized_projection, make_projection};
8 can_move_expr_to_closure, get_enclosing_block, get_parent_node, is_trait_method, path_to_local, path_to_local_id,
11 use rustc_data_structures::fx::FxHashMap;
12 use rustc_errors::{Applicability, MultiSpan};
13 use rustc_hir::intravisit::{walk_block, walk_expr, Visitor};
15 BindingAnnotation, Block, Expr, ExprKind, HirId, HirIdSet, Local, Mutability, Node, PatKind, Stmt, StmtKind,
17 use rustc_lint::LateContext;
18 use rustc_middle::hir::nested_filter;
19 use rustc_middle::ty::{self, AssocKind, EarlyBinder, GenericArg, GenericArgKind, Ty};
20 use rustc_span::symbol::Ident;
21 use rustc_span::{sym, Span, Symbol};
23 const NEEDLESS_COLLECT_MSG: &str = "avoid using `collect()` when not needed";
25 pub(super) fn check<'tcx>(
26 cx: &LateContext<'tcx>,
28 collect_expr: &'tcx Expr<'_>,
29 iter_expr: &'tcx Expr<'tcx>,
32 if let Some(parent) = get_parent_node(cx.tcx, collect_expr.hir_id) {
34 Node::Expr(parent) => {
35 if let ExprKind::MethodCall(name, _, args @ ([] | [_]), _) = parent.kind {
36 let mut app = Applicability::MachineApplicable;
37 let name = name.ident.as_str();
38 let collect_ty = cx.typeck_results().expr_ty(collect_expr);
40 let sugg: String = match name {
42 if let Some(adt) = collect_ty.ty_adt_def()
44 cx.tcx.get_diagnostic_name(adt.did()),
45 Some(sym::Vec | sym::VecDeque | sym::LinkedList | sym::BinaryHeap)
54 if is_is_empty_sig(cx, parent.hir_id)
55 && iterates_same_ty(cx, cx.typeck_results().expr_ty(iter_expr), collect_ty) =>
57 "next().is_none()".into()
60 if is_contains_sig(cx, parent.hir_id, iter_expr)
61 && let Some(arg) = args.first()
63 let (span, prefix) = if let ExprKind::AddrOf(_, _, arg) = arg.kind {
68 let snip = snippet_with_applicability(cx, span, "??", &mut app);
69 format!("any(|x| x == {prefix}{snip})")
80 call_span.with_hi(parent.span.hi()),
89 if let PatKind::Binding(BindingAnnotation::NONE | BindingAnnotation::MUT, id, _, None)
91 && let ty = cx.typeck_results().expr_ty(collect_expr)
92 && [sym::Vec, sym::VecDeque, sym::BinaryHeap, sym::LinkedList].into_iter()
93 .any(|item| is_type_diagnostic_item(cx, ty, item))
94 && let iter_ty = cx.typeck_results().expr_ty(iter_expr)
95 && let Some(block) = get_enclosing_block(cx, l.hir_id)
96 && let Some(iter_calls) = detect_iter_and_into_iters(block, id, cx, get_captured_ids(cx, iter_ty))
97 && let [iter_call] = &*iter_calls
99 let mut used_count_visitor = UsedCountVisitor {
104 walk_block(&mut used_count_visitor, block);
105 if used_count_visitor.count > 1 {
109 // Suggest replacing iter_call with iter_replacement, and removing stmt
110 let mut span = MultiSpan::from_span(name_span);
111 span.push_span_label(iter_call.span, "the iterator could be used here instead");
112 span_lint_hir_and_then(
114 super::NEEDLESS_COLLECT,
117 NEEDLESS_COLLECT_MSG,
119 let iter_replacement = format!("{}{}", Sugg::hir(cx, iter_expr, ".."), iter_call.get_iter_method(cx));
120 diag.multipart_suggestion(
121 iter_call.get_suggestion_text(),
123 (l.span, String::new()),
124 (iter_call.span, iter_replacement)
126 Applicability::MaybeIncorrect,
137 /// Checks if the given method call matches the expected signature of `([&[mut]] self) -> bool`
138 fn is_is_empty_sig(cx: &LateContext<'_>, call_id: HirId) -> bool {
139 cx.typeck_results().type_dependent_def_id(call_id).map_or(false, |id| {
140 let sig = cx.tcx.fn_sig(id).skip_binder();
141 sig.inputs().len() == 1 && sig.output().is_bool()
145 /// Checks if `<iter_ty as Iterator>::Item` is the same as `<collect_ty as IntoIter>::Item`
146 fn iterates_same_ty<'tcx>(cx: &LateContext<'tcx>, iter_ty: Ty<'tcx>, collect_ty: Ty<'tcx>) -> bool {
147 let item = Symbol::intern("Item");
148 if let Some(iter_trait) = cx.tcx.get_diagnostic_item(sym::Iterator)
149 && let Some(into_iter_trait) = cx.tcx.get_diagnostic_item(sym::IntoIterator)
150 && let Some(iter_item_ty) = make_normalized_projection(cx.tcx, cx.param_env, iter_trait, item, [iter_ty])
151 && let Some(into_iter_item_proj) = make_projection(cx.tcx, into_iter_trait, item, [collect_ty])
152 && let Ok(into_iter_item_ty) = cx.tcx.try_normalize_erasing_regions(
154 cx.tcx.mk_projection(into_iter_item_proj.item_def_id, into_iter_item_proj.substs)
157 iter_item_ty == into_iter_item_ty
163 /// Checks if the given method call matches the expected signature of
164 /// `([&[mut]] self, &<iter_ty as Iterator>::Item) -> bool`
165 fn is_contains_sig(cx: &LateContext<'_>, call_id: HirId, iter_expr: &Expr<'_>) -> bool {
166 let typeck = cx.typeck_results();
167 if let Some(id) = typeck.type_dependent_def_id(call_id)
168 && let sig = cx.tcx.fn_sig(id)
169 && sig.skip_binder().output().is_bool()
170 && let [_, search_ty] = *sig.skip_binder().inputs()
171 && let ty::Ref(_, search_ty, Mutability::Not) = *cx.tcx.erase_late_bound_regions(sig.rebind(search_ty)).kind()
172 && let Some(iter_trait) = cx.tcx.get_diagnostic_item(sym::Iterator)
173 && let Some(iter_item) = cx.tcx
174 .associated_items(iter_trait)
175 .find_by_name_and_kind(cx.tcx, Ident::with_dummy_span(Symbol::intern("Item")), AssocKind::Type, iter_trait)
176 && let substs = cx.tcx.mk_substs([GenericArg::from(typeck.expr_ty_adjusted(iter_expr))].into_iter())
177 && let proj_ty = cx.tcx.mk_projection(iter_item.def_id, substs)
178 && let Ok(item_ty) = cx.tcx.try_normalize_erasing_regions(cx.param_env, proj_ty)
180 item_ty == EarlyBinder(search_ty).subst(cx.tcx, cx.typeck_results().node_substs(call_id))
186 struct IterFunction {
187 func: IterFunctionKind,
191 fn get_iter_method(&self, cx: &LateContext<'_>) -> String {
193 IterFunctionKind::IntoIter => String::new(),
194 IterFunctionKind::Len => String::from(".count()"),
195 IterFunctionKind::IsEmpty => String::from(".next().is_none()"),
196 IterFunctionKind::Contains(span) => {
197 let s = snippet(cx, *span, "..");
198 if let Some(stripped) = s.strip_prefix('&') {
199 format!(".any(|x| x == {stripped})")
201 format!(".any(|x| x == *{s})")
206 fn get_suggestion_text(&self) -> &'static str {
208 IterFunctionKind::IntoIter => {
209 "use the original Iterator instead of collecting it and then producing a new one"
211 IterFunctionKind::Len => {
212 "take the original Iterator's count instead of collecting it and finding the length"
214 IterFunctionKind::IsEmpty => {
215 "check if the original Iterator has anything instead of collecting it and seeing if it's empty"
217 IterFunctionKind::Contains(_) => {
218 "check if the original Iterator contains an element instead of collecting then checking"
223 enum IterFunctionKind {
230 struct IterFunctionVisitor<'a, 'tcx> {
231 illegal_mutable_capture_ids: HirIdSet,
232 current_mutably_captured_ids: HirIdSet,
233 cx: &'a LateContext<'tcx>,
234 uses: Vec<Option<IterFunction>>,
235 hir_id_uses_map: FxHashMap<HirId, usize>,
236 current_statement_hir_id: Option<HirId>,
240 impl<'tcx> Visitor<'tcx> for IterFunctionVisitor<'_, 'tcx> {
241 fn visit_block(&mut self, block: &'tcx Block<'tcx>) {
242 for (expr, hir_id) in block.stmts.iter().filter_map(get_expr_and_hir_id_from_stmt) {
243 if check_loop_kind(expr).is_some() {
246 self.visit_block_expr(expr, hir_id);
248 if let Some(expr) = block.expr {
249 if let Some(loop_kind) = check_loop_kind(expr) {
250 if let LoopKind::Conditional(block_expr) = loop_kind {
251 self.visit_block_expr(block_expr, None);
254 self.visit_block_expr(expr, None);
259 fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
260 // Check function calls on our collection
261 if let ExprKind::MethodCall(method_name, recv, [args @ ..], _) = &expr.kind {
262 if method_name.ident.name == sym!(collect) && is_trait_method(self.cx, expr, sym::Iterator) {
263 self.current_mutably_captured_ids = get_captured_ids(self.cx, self.cx.typeck_results().expr_ty(recv));
264 self.visit_expr(recv);
268 if path_to_local_id(recv, self.target) {
270 .illegal_mutable_capture_ids
271 .intersection(&self.current_mutably_captured_ids)
275 if let Some(hir_id) = self.current_statement_hir_id {
276 self.hir_id_uses_map.insert(hir_id, self.uses.len());
278 match method_name.ident.name.as_str() {
279 "into_iter" => self.uses.push(Some(IterFunction {
280 func: IterFunctionKind::IntoIter,
283 "len" => self.uses.push(Some(IterFunction {
284 func: IterFunctionKind::Len,
287 "is_empty" => self.uses.push(Some(IterFunction {
288 func: IterFunctionKind::IsEmpty,
291 "contains" => self.uses.push(Some(IterFunction {
292 func: IterFunctionKind::Contains(args[0].span),
296 self.seen_other = true;
297 if let Some(hir_id) = self.current_statement_hir_id {
298 self.hir_id_uses_map.remove(&hir_id);
306 if let Some(hir_id) = path_to_local(recv) {
307 if let Some(index) = self.hir_id_uses_map.remove(&hir_id) {
309 .illegal_mutable_capture_ids
310 .intersection(&self.current_mutably_captured_ids)
314 if let Some(hir_id) = self.current_statement_hir_id {
315 self.hir_id_uses_map.insert(hir_id, index);
318 self.uses[index] = None;
323 // Check if the collection is used for anything else
324 if path_to_local_id(expr, self.target) {
325 self.seen_other = true;
327 walk_expr(self, expr);
332 enum LoopKind<'tcx> {
333 Conditional(&'tcx Expr<'tcx>),
337 fn check_loop_kind<'tcx>(expr: &Expr<'tcx>) -> Option<LoopKind<'tcx>> {
338 if let Some(higher::WhileLet { let_expr, .. }) = higher::WhileLet::hir(expr) {
339 return Some(LoopKind::Conditional(let_expr));
341 if let Some(higher::While { condition, .. }) = higher::While::hir(expr) {
342 return Some(LoopKind::Conditional(condition));
344 if let Some(higher::ForLoop { arg, .. }) = higher::ForLoop::hir(expr) {
345 return Some(LoopKind::Conditional(arg));
347 if let ExprKind::Loop { .. } = expr.kind {
348 return Some(LoopKind::Loop);
354 impl<'tcx> IterFunctionVisitor<'_, 'tcx> {
355 fn visit_block_expr(&mut self, expr: &'tcx Expr<'tcx>, hir_id: Option<HirId>) {
356 self.current_statement_hir_id = hir_id;
357 self.current_mutably_captured_ids = get_captured_ids(self.cx, self.cx.typeck_results().expr_ty(expr));
358 self.visit_expr(expr);
362 fn get_expr_and_hir_id_from_stmt<'v>(stmt: &'v Stmt<'v>) -> Option<(&'v Expr<'v>, Option<HirId>)> {
364 StmtKind::Expr(expr) | StmtKind::Semi(expr) => Some((expr, None)),
365 StmtKind::Item(..) => None,
366 StmtKind::Local(Local { init, pat, .. }) => {
367 if let PatKind::Binding(_, hir_id, ..) = pat.kind {
368 init.map(|init_expr| (init_expr, Some(hir_id)))
370 init.map(|init_expr| (init_expr, None))
376 struct UsedCountVisitor<'a, 'tcx> {
377 cx: &'a LateContext<'tcx>,
382 impl<'a, 'tcx> Visitor<'tcx> for UsedCountVisitor<'a, 'tcx> {
383 type NestedFilter = nested_filter::OnlyBodies;
385 fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
386 if path_to_local_id(expr, self.id) {
389 walk_expr(self, expr);
393 fn nested_visit_map(&mut self) -> Self::Map {
398 /// Detect the occurrences of calls to `iter` or `into_iter` for the
400 fn detect_iter_and_into_iters<'tcx: 'a, 'a>(
401 block: &'tcx Block<'tcx>,
403 cx: &'a LateContext<'tcx>,
404 captured_ids: HirIdSet,
405 ) -> Option<Vec<IterFunction>> {
406 let mut visitor = IterFunctionVisitor {
411 current_mutably_captured_ids: HirIdSet::default(),
412 illegal_mutable_capture_ids: captured_ids,
413 hir_id_uses_map: FxHashMap::default(),
414 current_statement_hir_id: None,
416 visitor.visit_block(block);
417 if visitor.seen_other {
420 Some(visitor.uses.into_iter().flatten().collect())
424 fn get_captured_ids(cx: &LateContext<'_>, ty: Ty<'_>) -> HirIdSet {
425 fn get_captured_ids_recursive(cx: &LateContext<'_>, ty: Ty<'_>, set: &mut HirIdSet) {
427 ty::Adt(_, generics) => {
428 for generic in *generics {
429 if let GenericArgKind::Type(ty) = generic.unpack() {
430 get_captured_ids_recursive(cx, ty, set);
434 ty::Closure(def_id, _) => {
435 let closure_hir_node = cx.tcx.hir().get_if_local(*def_id).unwrap();
436 if let Node::Expr(closure_expr) = closure_hir_node {
437 can_move_expr_to_closure(cx, closure_expr)
440 .for_each(|(hir_id, capture_kind)| {
441 if matches!(capture_kind, CaptureKind::Ref(Mutability::Mut)) {
451 let mut set = HirIdSet::default();
453 get_captured_ids_recursive(cx, ty, &mut set);