Skip to content

Commit c012693

Browse files
committed
needless_collect: avoid warning if non-iterator methods are used
It can make sense to `collect()` an iterator and then immediately iterate over it if the iterator has special methods that you need. For example, the Map iterator doesn't implement Clone, but the collection iterator might. Or the collection iterator might support slicing.
1 parent 75e3a2e commit c012693

File tree

3 files changed

+93
-6
lines changed

3 files changed

+93
-6
lines changed

clippy_lints/src/methods/needless_collect.rs

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::ops::ControlFlow;
2+
13
use super::NEEDLESS_COLLECT;
24
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
35
use clippy_utils::source::{snippet, snippet_with_applicability};
@@ -9,9 +11,9 @@ use clippy_utils::{
911
};
1012
use rustc_data_structures::fx::FxHashMap;
1113
use rustc_errors::{Applicability, MultiSpan};
12-
use rustc_hir::intravisit::{Visitor, walk_block, walk_expr};
14+
use rustc_hir::intravisit::{Visitor, walk_block, walk_expr, walk_stmt};
1315
use rustc_hir::{
14-
BindingMode, Block, Expr, ExprKind, HirId, HirIdSet, LetStmt, Mutability, Node, PatKind, Stmt, StmtKind,
16+
BindingMode, Block, Expr, ExprKind, HirId, HirIdSet, LetStmt, Mutability, Node, Pat, PatKind, Stmt, StmtKind,
1517
};
1618
use rustc_lint::LateContext;
1719
use rustc_middle::hir::nested_filter;
@@ -103,6 +105,12 @@ pub(super) fn check<'tcx>(
103105
return;
104106
}
105107

108+
if let IterFunctionKind::IntoIter(hir_id) = iter_call.func
109+
&& !check_iter_expr_used_only_as_iterator(cx, hir_id, block)
110+
{
111+
return;
112+
}
113+
106114
// Suggest replacing iter_call with iter_replacement, and removing stmt
107115
let mut span = MultiSpan::from_span(name_span);
108116
span.push_span_label(iter_call.span, "the iterator could be used here instead");
@@ -253,7 +261,7 @@ struct IterFunction {
253261
impl IterFunction {
254262
fn get_iter_method(&self, cx: &LateContext<'_>) -> String {
255263
match &self.func {
256-
IterFunctionKind::IntoIter => String::new(),
264+
IterFunctionKind::IntoIter(_) => String::new(),
257265
IterFunctionKind::Len => String::from(".count()"),
258266
IterFunctionKind::IsEmpty => String::from(".next().is_none()"),
259267
IterFunctionKind::Contains(span) => {
@@ -268,7 +276,7 @@ impl IterFunction {
268276
}
269277
fn get_suggestion_text(&self) -> &'static str {
270278
match &self.func {
271-
IterFunctionKind::IntoIter => {
279+
IterFunctionKind::IntoIter(_) => {
272280
"use the original Iterator instead of collecting it and then producing a new one"
273281
},
274282
IterFunctionKind::Len => {
@@ -284,7 +292,7 @@ impl IterFunction {
284292
}
285293
}
286294
enum IterFunctionKind {
287-
IntoIter,
295+
IntoIter(HirId),
288296
Len,
289297
IsEmpty,
290298
Contains(Span),
@@ -343,7 +351,7 @@ impl<'tcx> Visitor<'tcx> for IterFunctionVisitor<'_, 'tcx> {
343351
}
344352
match method_name.ident.name.as_str() {
345353
"into_iter" => self.uses.push(Some(IterFunction {
346-
func: IterFunctionKind::IntoIter,
354+
func: IterFunctionKind::IntoIter(expr.hir_id),
347355
span: expr.span,
348356
})),
349357
"len" => self.uses.push(Some(IterFunction {
@@ -520,3 +528,54 @@ fn get_captured_ids(cx: &LateContext<'_>, ty: Ty<'_>) -> HirIdSet {
520528

521529
set
522530
}
531+
532+
struct IteratorMethodCheckVisitor<'a, 'tcx> {
533+
cx: &'a LateContext<'tcx>,
534+
hir_id_of_expr: HirId,
535+
hir_id_of_let_binding: Option<HirId>,
536+
}
537+
538+
impl<'tcx> Visitor<'tcx> for IteratorMethodCheckVisitor<'_, 'tcx> {
539+
type Result = ControlFlow<()>;
540+
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) -> ControlFlow<()> {
541+
if let ExprKind::MethodCall(_method_name, recv, _args, _) = &expr.kind
542+
&& (recv.hir_id == self.hir_id_of_expr
543+
|| self
544+
.hir_id_of_let_binding
545+
.is_some_and(|hid| path_to_local_id(recv, hid)))
546+
&& !is_trait_method(self.cx, expr, sym::Iterator)
547+
{
548+
return ControlFlow::Break(());
549+
}
550+
walk_expr(self, expr)
551+
}
552+
fn visit_stmt(&mut self, stmt: &'tcx Stmt<'tcx>) -> ControlFlow<()> {
553+
if let StmtKind::Let(LetStmt {
554+
init: Some(expr),
555+
pat:
556+
Pat {
557+
kind: PatKind::Binding(BindingMode::NONE | BindingMode::MUT, id, _, None),
558+
..
559+
},
560+
..
561+
}) = &stmt.kind
562+
&& expr.hir_id == self.hir_id_of_expr
563+
{
564+
self.hir_id_of_let_binding = Some(*id);
565+
}
566+
walk_stmt(self, stmt)
567+
}
568+
}
569+
570+
fn check_iter_expr_used_only_as_iterator<'tcx>(
571+
cx: &LateContext<'tcx>,
572+
hir_id_of_expr: HirId,
573+
block: &'tcx Block<'tcx>,
574+
) -> bool {
575+
let mut visitor = IteratorMethodCheckVisitor {
576+
cx,
577+
hir_id_of_expr,
578+
hir_id_of_let_binding: None,
579+
};
580+
visitor.visit_block(block).is_continue()
581+
}

tests/ui/needless_collect.fixed

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,20 @@ fn main() {
7373
let mut out = vec![];
7474
values.iter().cloned().map(|x| out.push(x)).collect::<Vec<_>>();
7575
let _y = values.iter().cloned().map(|x| out.push(x)).collect::<Vec<_>>(); // this is fine
76+
77+
// Don't write a warning if we call `clone()` on the iterator
78+
// https://github.com/rust-lang/rust-clippy/issues/13430
79+
let my_collection: Vec<()> = vec![()].into_iter().map(|()| {}).collect();
80+
let _cloned = my_collection.into_iter().clone();
81+
let my_collection: Vec<()> = vec![()].into_iter().map(|()| {}).collect();
82+
let my_iter = my_collection.into_iter();
83+
let _cloned = my_iter.clone();
84+
// Same for `as_slice()`, for same reason.
85+
let my_collection: Vec<()> = vec![()].into_iter().map(|()| {}).collect();
86+
let _sliced = my_collection.into_iter().as_slice();
87+
let my_collection: Vec<()> = vec![()].into_iter().map(|()| {}).collect();
88+
let my_iter = my_collection.into_iter();
89+
let _sliced = my_iter.as_slice();
7690
}
7791

7892
fn foo(_: impl IntoIterator<Item = usize>) {}

tests/ui/needless_collect.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,20 @@ fn main() {
7373
let mut out = vec![];
7474
values.iter().cloned().map(|x| out.push(x)).collect::<Vec<_>>();
7575
let _y = values.iter().cloned().map(|x| out.push(x)).collect::<Vec<_>>(); // this is fine
76+
77+
// Don't write a warning if we call `clone()` on the iterator
78+
// https://github.com/rust-lang/rust-clippy/issues/13430
79+
let my_collection: Vec<()> = vec![()].into_iter().map(|()| {}).collect();
80+
let _cloned = my_collection.into_iter().clone();
81+
let my_collection: Vec<()> = vec![()].into_iter().map(|()| {}).collect();
82+
let my_iter = my_collection.into_iter();
83+
let _cloned = my_iter.clone();
84+
// Same for `as_slice()`, for same reason.
85+
let my_collection: Vec<()> = vec![()].into_iter().map(|()| {}).collect();
86+
let _sliced = my_collection.into_iter().as_slice();
87+
let my_collection: Vec<()> = vec![()].into_iter().map(|()| {}).collect();
88+
let my_iter = my_collection.into_iter();
89+
let _sliced = my_iter.as_slice();
7690
}
7791

7892
fn foo(_: impl IntoIterator<Item = usize>) {}

0 commit comments

Comments
 (0)