Skip to content

Commit a9c61ec

Browse files
authored
needless_collect: avoid warning if non-iterator methods are used (#14147)
changelog: [`needless_collect`]: avoid warning if non-`Iterator` methods are called on the result of `into_iter` Fixes #13430
2 parents aa2180f + 6321ac7 commit a9c61ec

File tree

3 files changed

+118
-6
lines changed

3 files changed

+118
-6
lines changed

clippy_lints/src/methods/needless_collect.rs

+72-6
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,61 @@ 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+
} else if let ExprKind::Assign(place, value, _span) = &expr.kind
550+
&& value.hir_id == self.hir_id_of_expr
551+
&& let Some(id) = path_to_local(place)
552+
{
553+
// our iterator was directly assigned to a variable
554+
self.hir_id_of_let_binding = Some(id);
555+
}
556+
walk_expr(self, expr)
557+
}
558+
fn visit_stmt(&mut self, stmt: &'tcx Stmt<'tcx>) -> ControlFlow<()> {
559+
if let StmtKind::Let(LetStmt {
560+
init: Some(expr),
561+
pat:
562+
Pat {
563+
kind: PatKind::Binding(BindingMode::NONE | BindingMode::MUT, id, _, None),
564+
..
565+
},
566+
..
567+
}) = &stmt.kind
568+
&& expr.hir_id == self.hir_id_of_expr
569+
{
570+
// our iterator was directly assigned to a variable
571+
self.hir_id_of_let_binding = Some(*id);
572+
}
573+
walk_stmt(self, stmt)
574+
}
575+
}
576+
577+
fn check_iter_expr_used_only_as_iterator<'tcx>(
578+
cx: &LateContext<'tcx>,
579+
hir_id_of_expr: HirId,
580+
block: &'tcx Block<'tcx>,
581+
) -> bool {
582+
let mut visitor = IteratorMethodCheckVisitor {
583+
cx,
584+
hir_id_of_expr,
585+
hir_id_of_let_binding: None,
586+
};
587+
visitor.visit_block(block).is_continue()
588+
}

tests/ui/needless_collect.fixed

+23
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,29 @@ fn main() {
9898
let mut out = vec![];
9999
values.iter().cloned().map(|x| out.push(x)).collect::<Vec<_>>();
100100
let _y = values.iter().cloned().map(|x| out.push(x)).collect::<Vec<_>>(); // this is fine
101+
102+
// Don't write a warning if we call `clone()` on the iterator
103+
// https://github.com/rust-lang/rust-clippy/issues/13430
104+
let my_collection: Vec<()> = vec![()].into_iter().map(|()| {}).collect();
105+
let _cloned = my_collection.into_iter().clone();
106+
let my_collection: Vec<()> = vec![()].into_iter().map(|()| {}).collect();
107+
let my_iter = my_collection.into_iter();
108+
let _cloned = my_iter.clone();
109+
// Same for `as_slice()`, for same reason.
110+
let my_collection: Vec<()> = vec![()].into_iter().map(|()| {}).collect();
111+
let _sliced = my_collection.into_iter().as_slice();
112+
let my_collection: Vec<()> = vec![()].into_iter().map(|()| {}).collect();
113+
let my_iter = my_collection.into_iter();
114+
let _sliced = my_iter.as_slice();
115+
// Assignment outside of main scope
116+
{
117+
let x;
118+
{
119+
let xxx: Vec<()> = vec![()].into_iter().map(|()| {}).collect();
120+
x = xxx.into_iter();
121+
for i in x.as_slice() {}
122+
}
123+
}
101124
}
102125

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

tests/ui/needless_collect.rs

+23
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,29 @@ fn main() {
9898
let mut out = vec![];
9999
values.iter().cloned().map(|x| out.push(x)).collect::<Vec<_>>();
100100
let _y = values.iter().cloned().map(|x| out.push(x)).collect::<Vec<_>>(); // this is fine
101+
102+
// Don't write a warning if we call `clone()` on the iterator
103+
// https://github.com/rust-lang/rust-clippy/issues/13430
104+
let my_collection: Vec<()> = vec![()].into_iter().map(|()| {}).collect();
105+
let _cloned = my_collection.into_iter().clone();
106+
let my_collection: Vec<()> = vec![()].into_iter().map(|()| {}).collect();
107+
let my_iter = my_collection.into_iter();
108+
let _cloned = my_iter.clone();
109+
// Same for `as_slice()`, for same reason.
110+
let my_collection: Vec<()> = vec![()].into_iter().map(|()| {}).collect();
111+
let _sliced = my_collection.into_iter().as_slice();
112+
let my_collection: Vec<()> = vec![()].into_iter().map(|()| {}).collect();
113+
let my_iter = my_collection.into_iter();
114+
let _sliced = my_iter.as_slice();
115+
// Assignment outside of main scope
116+
{
117+
let x;
118+
{
119+
let xxx: Vec<()> = vec![()].into_iter().map(|()| {}).collect();
120+
x = xxx.into_iter();
121+
for i in x.as_slice() {}
122+
}
123+
}
101124
}
102125

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

0 commit comments

Comments
 (0)