Skip to content

Commit 84f89f3

Browse files
committed
enhance needless_collect
Updates `needless_collect` to lint for collecting into a method or function argument thats taking an `IntoIterator` (for example `extend`). Every `Iterator` trivially implements `IntoIterator` and colleting it only causes an unnecessary allocation.
1 parent a167973 commit 84f89f3

File tree

4 files changed

+115
-3
lines changed

4 files changed

+115
-3
lines changed

clippy_lints/src/methods/needless_collect.rs

+66-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
use super::NEEDLESS_COLLECT;
22
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
3-
use clippy_utils::higher;
43
use clippy_utils::source::{snippet, snippet_with_applicability};
54
use clippy_utils::sugg::Sugg;
65
use clippy_utils::ty::{is_type_diagnostic_item, make_normalized_projection, make_projection};
76
use clippy_utils::{
87
can_move_expr_to_closure, get_enclosing_block, get_parent_node, is_trait_method, path_to_local, path_to_local_id,
98
CaptureKind,
109
};
10+
use clippy_utils::{fn_def_id, higher};
1111
use rustc_data_structures::fx::FxHashMap;
1212
use rustc_errors::{Applicability, MultiSpan};
1313
use rustc_hir::intravisit::{walk_block, walk_expr, Visitor};
@@ -16,7 +16,7 @@ use rustc_hir::{
1616
};
1717
use rustc_lint::LateContext;
1818
use rustc_middle::hir::nested_filter;
19-
use rustc_middle::ty::{self, AssocKind, EarlyBinder, GenericArg, GenericArgKind, Ty};
19+
use rustc_middle::ty::{self, AssocKind, Clause, EarlyBinder, GenericArg, GenericArgKind, PredicateKind, Ty};
2020
use rustc_span::symbol::Ident;
2121
use rustc_span::{sym, Span, Symbol};
2222

@@ -32,6 +32,8 @@ pub(super) fn check<'tcx>(
3232
if let Some(parent) = get_parent_node(cx.tcx, collect_expr.hir_id) {
3333
match parent {
3434
Node::Expr(parent) => {
35+
check_collect_into_intoiterator(cx, parent, collect_expr, call_span, iter_expr);
36+
3537
if let ExprKind::MethodCall(name, _, args @ ([] | [_]), _) = parent.kind {
3638
let mut app = Applicability::MachineApplicable;
3739
let name = name.ident.as_str();
@@ -134,6 +136,68 @@ pub(super) fn check<'tcx>(
134136
}
135137
}
136138

139+
/// checks for for collecting into a (generic) method or function argument
140+
/// taking an `IntoIterator`
141+
fn check_collect_into_intoiterator<'tcx>(
142+
cx: &LateContext<'tcx>,
143+
parent: &'tcx Expr<'tcx>,
144+
collect_expr: &'tcx Expr<'tcx>,
145+
call_span: Span,
146+
iter_expr: &'tcx Expr<'tcx>,
147+
) {
148+
if let Some(id) = fn_def_id(cx, parent) {
149+
let args = match parent.kind {
150+
ExprKind::Call(_, args) | ExprKind::MethodCall(_, _, args, _) => args,
151+
_ => &[],
152+
};
153+
// find the argument index of the `collect_expr` in the
154+
// function / method call
155+
if let Some(arg_idx) = args.iter().position(|e| e.hir_id == collect_expr.hir_id).map(|i| {
156+
if matches!(parent.kind, ExprKind::MethodCall(_, _, _, _)) {
157+
i + 1
158+
} else {
159+
i
160+
}
161+
}) {
162+
// extract the input types of the function/method call
163+
// that contains `collect_expr`
164+
let inputs = cx
165+
.tcx
166+
.liberate_late_bound_regions(id, cx.tcx.fn_sig(id).subst_identity())
167+
.inputs();
168+
169+
// map IntoIterator generic bounds to their signature
170+
// types and check whether the argument type is an
171+
// `IntoIterator`
172+
if cx
173+
.tcx
174+
.param_env(id)
175+
.caller_bounds()
176+
.into_iter()
177+
.filter_map(|p| {
178+
if let PredicateKind::Clause(Clause::Trait(t)) = p.kind().skip_binder()
179+
&& cx.tcx.is_diagnostic_item(sym::IntoIterator,t.trait_ref.def_id) {
180+
Some(t.self_ty())
181+
} else {
182+
None
183+
}
184+
})
185+
.any(|ty| ty == inputs[arg_idx])
186+
{
187+
span_lint_and_sugg(
188+
cx,
189+
NEEDLESS_COLLECT,
190+
call_span.with_lo(iter_expr.span.hi()),
191+
NEEDLESS_COLLECT_MSG,
192+
"remove this call",
193+
String::new(),
194+
Applicability::MachineApplicable,
195+
);
196+
}
197+
}
198+
}
199+
}
200+
137201
/// Checks if the given method call matches the expected signature of `([&[mut]] self) -> bool`
138202
fn is_is_empty_sig(cx: &LateContext<'_>, call_id: HirId) -> bool {
139203
cx.typeck_results().type_dependent_def_id(call_id).map_or(false, |id| {

tests/ui/needless_collect.fixed

+12
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,16 @@ fn main() {
6262

6363
let _ = sample.iter().next().is_none();
6464
let _ = sample.iter().any(|x| x == &0);
65+
66+
#[allow(clippy::double_parens)]
67+
{
68+
Vec::<u8>::new().extend((0..10));
69+
foo((0..10));
70+
bar((0..10).collect::<Vec<_>>(), (0..10));
71+
baz((0..10), (), ('a'..='z'))
72+
}
6573
}
74+
75+
fn foo(_: impl IntoIterator<Item = usize>) {}
76+
fn bar<I: IntoIterator<Item = usize>>(_: Vec<usize>, _: I) {}
77+
fn baz<I: IntoIterator<Item = usize>>(_: I, _: (), _: impl IntoIterator<Item = char>) {}

tests/ui/needless_collect.rs

+12
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,16 @@ fn main() {
6262

6363
let _ = sample.iter().collect::<VecWrapper<_>>().is_empty();
6464
let _ = sample.iter().collect::<VecWrapper<_>>().contains(&&0);
65+
66+
#[allow(clippy::double_parens)]
67+
{
68+
Vec::<u8>::new().extend((0..10).collect::<Vec<_>>());
69+
foo((0..10).collect::<Vec<_>>());
70+
bar((0..10).collect::<Vec<_>>(), (0..10).collect::<Vec<_>>());
71+
baz((0..10), (), ('a'..='z').collect::<Vec<_>>())
72+
}
6573
}
74+
75+
fn foo(_: impl IntoIterator<Item = usize>) {}
76+
fn bar<I: IntoIterator<Item = usize>>(_: Vec<usize>, _: I) {}
77+
fn baz<I: IntoIterator<Item = usize>>(_: I, _: (), _: impl IntoIterator<Item = char>) {}

tests/ui/needless_collect.stderr

+25-1
Original file line numberDiff line numberDiff line change
@@ -90,5 +90,29 @@ error: avoid using `collect()` when not needed
9090
LL | let _ = sample.iter().collect::<VecWrapper<_>>().contains(&&0);
9191
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `any(|x| x == &0)`
9292

93-
error: aborting due to 15 previous errors
93+
error: avoid using `collect()` when not needed
94+
--> $DIR/needless_collect.rs:68:40
95+
|
96+
LL | Vec::<u8>::new().extend((0..10).collect::<Vec<_>>());
97+
| ^^^^^^^^^^^^^^^^^^^^ help: remove this call
98+
99+
error: avoid using `collect()` when not needed
100+
--> $DIR/needless_collect.rs:69:20
101+
|
102+
LL | foo((0..10).collect::<Vec<_>>());
103+
| ^^^^^^^^^^^^^^^^^^^^ help: remove this call
104+
105+
error: avoid using `collect()` when not needed
106+
--> $DIR/needless_collect.rs:70:49
107+
|
108+
LL | bar((0..10).collect::<Vec<_>>(), (0..10).collect::<Vec<_>>());
109+
| ^^^^^^^^^^^^^^^^^^^^ help: remove this call
110+
111+
error: avoid using `collect()` when not needed
112+
--> $DIR/needless_collect.rs:71:37
113+
|
114+
LL | baz((0..10), (), ('a'..='z').collect::<Vec<_>>())
115+
| ^^^^^^^^^^^^^^^^^^^^ help: remove this call
116+
117+
error: aborting due to 19 previous errors
94118

0 commit comments

Comments
 (0)