Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions clippy_lints/src/methods/needless_collect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ pub(super) fn check<'tcx>(
return; // don't lint if the iterator has side effects
}

if collect_turbofish_is_fully_concrete(collect_expr) {
return; // don't lint if turbofish on collect maybe the only thing anchoring the type
}

match cx.tcx.parent_hir_node(collect_expr.hir_id) {
Node::Expr(parent) => {
check_collect_into_intoiterator(cx, parent, collect_expr, call_span, iter_expr);
Expand Down Expand Up @@ -230,6 +234,41 @@ fn check_collect_into_intoiterator<'tcx>(
}
}

/// Returns `true` if `collect_expr`'s turbofish is fully concrete (has
/// generic arguments and none of them are inference placeholders)
fn collect_turbofish_is_fully_concrete(collect_expr: &Expr<'_>) -> bool {
if let ExprKind::MethodCall(segment, ..) = collect_expr.kind
&& let Some(args) = segment.args
&& let [a] = args.args
{
generic_arg_is_fully_concrete(a)
} else {
false
}
}
Comment on lines +237 to +248

@hkBst hkBst Jul 11, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

collect has only a single generic parameter, so either it is given and concrete, or it isn't. The code makes it seem like the number of generic parameters is unknown, and thus has to manually fix the empty case, because all would give true.

View changes since the review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess matching args.args with [a] if generic_arg_is_fully_concrete(a) should do the trick?


fn generic_arg_is_fully_concrete(arg: &rustc_hir::GenericArg<'_>) -> bool {
match arg {
rustc_hir::GenericArg::Infer(_) => false,
rustc_hir::GenericArg::Type(ty) => ty_is_fully_concrete(ty.as_unambig_ty()),
rustc_hir::GenericArg::Const(ct) => !matches!(ct.as_unambig_ct().kind, rustc_hir::ConstArgKind::Infer(..)),
rustc_hir::GenericArg::Lifetime(_) => true,
}
}

fn ty_is_fully_concrete(ty: &rustc_hir::Ty<'_>) -> bool {
match &ty.kind {
rustc_hir::TyKind::Infer(..) => false,
rustc_hir::TyKind::Path(rustc_hir::QPath::Resolved(_, path)) => path.segments.iter().all(|seg| {
seg.args
.is_none_or(|a| a.args.iter().all(generic_arg_is_fully_concrete))
}),
rustc_hir::TyKind::Ref(_, mut_ty) => ty_is_fully_concrete(mut_ty.ty),
rustc_hir::TyKind::Slice(ty) | rustc_hir::TyKind::Array(ty, _) => ty_is_fully_concrete(ty),
rustc_hir::TyKind::Tup(tys) => tys.iter().all(ty_is_fully_concrete),
_ => true,
}
}
/// Checks if the given method call matches the expected signature of `([&[mut]] self) -> bool`
fn is_is_empty_sig(cx: &LateContext<'_>, call_id: HirId) -> bool {
cx.typeck_results().type_dependent_def_id(call_id).is_some_and(|id| {
Expand Down
4 changes: 0 additions & 4 deletions tests/ui/needless_collect.fixed
Original file line number Diff line number Diff line change
Expand Up @@ -198,10 +198,6 @@ mod issue8055_regression {
}
}

#[expect(
clippy::needless_collect,
reason = "FIXME: proposed fix cannot determine type, see issue #17315"
)]
fn foo() {
Foo {
inner: [].iter(),
Expand Down
4 changes: 0 additions & 4 deletions tests/ui/needless_collect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,10 +198,6 @@ mod issue8055_regression {
}
}

#[expect(
clippy::needless_collect,
reason = "FIXME: proposed fix cannot determine type, see issue #17315"
)]
fn foo() {
Foo {
inner: [].iter(),
Expand Down
12 changes: 6 additions & 6 deletions tests/ui/needless_collect.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ LL | baz((0..10), (), ('a'..='z').collect::<Vec<_>>())
| ^^^^^^^^^^^^^^^^^^^^ help: remove this call

error: avoid using `collect()` when not needed
--> tests/ui/needless_collect.rs:224:26
--> tests/ui/needless_collect.rs:220:26
|
LL | let mut v = iter.collect::<Vec<_>>();
| ^^^^^^^
Expand All @@ -139,7 +139,7 @@ LL ~ iter.chain([1]).map(|x| x + 1).collect()
|

error: avoid using `collect()` when not needed
--> tests/ui/needless_collect.rs:236:26
--> tests/ui/needless_collect.rs:232:26
|
LL | let mut v = iter.collect::<Vec<_>>();
| ^^^^^^^
Expand All @@ -157,7 +157,7 @@ LL ~ iter.chain([1, 2]).map(|x| x + 1).collect()
|

error: avoid using `collect()` when not needed
--> tests/ui/needless_collect.rs:244:26
--> tests/ui/needless_collect.rs:240:26
|
LL | let mut v = iter.collect::<LinkedList<_>>();
| ^^^^^^^
Expand All @@ -174,7 +174,7 @@ LL ~ iter.chain([1]).map(|x| x + 1).collect()
|

error: avoid using `collect()` when not needed
--> tests/ui/needless_collect.rs:264:27
--> tests/ui/needless_collect.rs:260:27
|
LL | let mut ll = iter.collect::<LinkedList<_>>();
| ^^^^^^^
Expand All @@ -191,7 +191,7 @@ LL ~ iter.chain(s).map(|x| x + 1).collect()
|

error: avoid using `collect()` when not needed
--> tests/ui/needless_collect.rs:271:26
--> tests/ui/needless_collect.rs:267:26
|
LL | let mut v = iter.collect::<VecDeque<_>>();
| ^^^^^^^
Expand All @@ -209,7 +209,7 @@ LL ~ [1, 2].into_iter().chain(iter).map(|x| x + 1).collect()
|

error: avoid using `collect()` when not needed
--> tests/ui/needless_collect.rs:282:26
--> tests/ui/needless_collect.rs:278:26
|
LL | let mut v = iter.collect::<LinkedList<_>>();
| ^^^^^^^
Expand Down