Skip to content

Commit ca2a25d

Browse files
authored
Rollup merge of rust-lang#5837 - JarredAllen:needless_collect, r=phansch
needless_collect: catch x: Vec<_> = iter.collect(); x.into_iter() ... changelog: Expand the needless_collect lint as suggested in rust-lang#5627 (WIP). This PR is WIP because I can't figure out how to make the multi-part suggestion include its changes in the source code (the fixed is identical to the source, despite the lint making suggestions). Aside from that one issue, I think this should be good.
2 parents 1968aed + 5e10b03 commit ca2a25d

File tree

3 files changed

+235
-6
lines changed

3 files changed

+235
-6
lines changed

clippy_lints/src/loops.rs

+161-6
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
use crate::consts::constant;
22
use crate::reexport::Name;
33
use crate::utils::paths;
4+
use crate::utils::sugg::Sugg;
45
use crate::utils::usage::{is_unused, mutated_variables};
56
use crate::utils::{
67
get_enclosing_block, get_parent_expr, get_trait_def_id, has_iter_method, higher, implements_trait,
7-
is_integer_const, is_no_std_crate, is_refutable, last_path_segment, match_trait_method, match_type, match_var,
8-
multispan_sugg, snippet, snippet_opt, snippet_with_applicability, span_lint, span_lint_and_help,
9-
span_lint_and_sugg, span_lint_and_then, SpanlessEq,
8+
is_integer_const, is_no_std_crate, is_refutable, is_type_diagnostic_item, last_path_segment, match_trait_method,
9+
match_type, match_var, multispan_sugg, qpath_res, snippet, snippet_opt, snippet_with_applicability, span_lint,
10+
span_lint_and_help, span_lint_and_sugg, span_lint_and_then, sugg, SpanlessEq,
1011
};
11-
use crate::utils::{is_type_diagnostic_item, qpath_res, sugg};
1212
use if_chain::if_chain;
1313
use rustc_ast::ast;
1414
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
@@ -17,7 +17,7 @@ use rustc_hir::def::{DefKind, Res};
1717
use rustc_hir::intravisit::{walk_block, walk_expr, walk_pat, walk_stmt, NestedVisitorMap, Visitor};
1818
use rustc_hir::{
1919
def_id, BinOpKind, BindingAnnotation, Block, BorrowKind, Expr, ExprKind, GenericArg, HirId, InlineAsmOperand,
20-
LoopSource, MatchSource, Mutability, Node, Pat, PatKind, QPath, Stmt, StmtKind,
20+
Local, LoopSource, MatchSource, Mutability, Node, Pat, PatKind, QPath, Stmt, StmtKind,
2121
};
2222
use rustc_infer::infer::TyCtxtInferExt;
2323
use rustc_lint::{LateContext, LateLintPass, LintContext};
@@ -27,7 +27,7 @@ use rustc_middle::middle::region;
2727
use rustc_middle::ty::{self, Ty, TyS};
2828
use rustc_session::{declare_lint_pass, declare_tool_lint};
2929
use rustc_span::source_map::Span;
30-
use rustc_span::symbol::Symbol;
30+
use rustc_span::symbol::{sym, Ident, Symbol};
3131
use rustc_typeck::expr_use_visitor::{ConsumeMode, Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
3232
use std::iter::{once, Iterator};
3333
use std::mem;
@@ -2358,6 +2358,10 @@ impl<'a, 'tcx> Visitor<'tcx> for VarCollectorVisitor<'a, 'tcx> {
23582358
const NEEDLESS_COLLECT_MSG: &str = "avoid using `collect()` when not needed";
23592359

23602360
fn check_needless_collect<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) {
2361+
check_needless_collect_direct_usage(expr, cx);
2362+
check_needless_collect_indirect_usage(expr, cx);
2363+
}
2364+
fn check_needless_collect_direct_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) {
23612365
if_chain! {
23622366
if let ExprKind::MethodCall(ref method, _, ref args, _) = expr.kind;
23632367
if let ExprKind::MethodCall(ref chain_method, _, _, _) = args[0].kind;
@@ -2425,6 +2429,157 @@ fn check_needless_collect<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) {
24252429
}
24262430
}
24272431

2432+
fn check_needless_collect_indirect_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) {
2433+
if let ExprKind::Block(ref block, _) = expr.kind {
2434+
for ref stmt in block.stmts {
2435+
if_chain! {
2436+
if let StmtKind::Local(
2437+
Local { pat: Pat { kind: PatKind::Binding(_, _, ident, .. ), .. },
2438+
init: Some(ref init_expr), .. }
2439+
) = stmt.kind;
2440+
if let ExprKind::MethodCall(ref method_name, _, &[ref iter_source], ..) = init_expr.kind;
2441+
if method_name.ident.name == sym!(collect) && match_trait_method(cx, &init_expr, &paths::ITERATOR);
2442+
if let Some(ref generic_args) = method_name.args;
2443+
if let Some(GenericArg::Type(ref ty)) = generic_args.args.get(0);
2444+
if let ty = cx.typeck_results().node_type(ty.hir_id);
2445+
if is_type_diagnostic_item(cx, ty, sym::vec_type) ||
2446+
is_type_diagnostic_item(cx, ty, sym!(vecdeque_type)) ||
2447+
match_type(cx, ty, &paths::LINKED_LIST);
2448+
if let Some(iter_calls) = detect_iter_and_into_iters(block, *ident);
2449+
if iter_calls.len() == 1;
2450+
then {
2451+
// Suggest replacing iter_call with iter_replacement, and removing stmt
2452+
let iter_call = &iter_calls[0];
2453+
span_lint_and_then(
2454+
cx,
2455+
NEEDLESS_COLLECT,
2456+
stmt.span.until(iter_call.span),
2457+
NEEDLESS_COLLECT_MSG,
2458+
|diag| {
2459+
let iter_replacement = format!("{}{}", Sugg::hir(cx, iter_source, ".."), iter_call.get_iter_method(cx));
2460+
diag.multipart_suggestion(
2461+
iter_call.get_suggestion_text(),
2462+
vec![
2463+
(stmt.span, String::new()),
2464+
(iter_call.span, iter_replacement)
2465+
],
2466+
Applicability::MachineApplicable,// MaybeIncorrect,
2467+
).emit();
2468+
},
2469+
);
2470+
}
2471+
}
2472+
}
2473+
}
2474+
}
2475+
2476+
struct IterFunction {
2477+
func: IterFunctionKind,
2478+
span: Span,
2479+
}
2480+
impl IterFunction {
2481+
fn get_iter_method(&self, cx: &LateContext<'_>) -> String {
2482+
match &self.func {
2483+
IterFunctionKind::IntoIter => String::new(),
2484+
IterFunctionKind::Len => String::from(".count()"),
2485+
IterFunctionKind::IsEmpty => String::from(".next().is_none()"),
2486+
IterFunctionKind::Contains(span) => format!(".any(|x| x == {})", snippet(cx, *span, "..")),
2487+
}
2488+
}
2489+
fn get_suggestion_text(&self) -> &'static str {
2490+
match &self.func {
2491+
IterFunctionKind::IntoIter => {
2492+
"Use the original Iterator instead of collecting it and then producing a new one"
2493+
},
2494+
IterFunctionKind::Len => {
2495+
"Take the original Iterator's count instead of collecting it and finding the length"
2496+
},
2497+
IterFunctionKind::IsEmpty => {
2498+
"Check if the original Iterator has anything instead of collecting it and seeing if it's empty"
2499+
},
2500+
IterFunctionKind::Contains(_) => {
2501+
"Check if the original Iterator contains an element instead of collecting then checking"
2502+
},
2503+
}
2504+
}
2505+
}
2506+
enum IterFunctionKind {
2507+
IntoIter,
2508+
Len,
2509+
IsEmpty,
2510+
Contains(Span),
2511+
}
2512+
2513+
struct IterFunctionVisitor {
2514+
uses: Vec<IterFunction>,
2515+
seen_other: bool,
2516+
target: Ident,
2517+
}
2518+
impl<'tcx> Visitor<'tcx> for IterFunctionVisitor {
2519+
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
2520+
// Check function calls on our collection
2521+
if_chain! {
2522+
if let ExprKind::MethodCall(method_name, _, ref args, _) = &expr.kind;
2523+
if let Some(Expr { kind: ExprKind::Path(QPath::Resolved(_, ref path)), .. }) = args.get(0);
2524+
if let &[name] = &path.segments;
2525+
if name.ident == self.target;
2526+
then {
2527+
let len = sym!(len);
2528+
let is_empty = sym!(is_empty);
2529+
let contains = sym!(contains);
2530+
match method_name.ident.name {
2531+
sym::into_iter => self.uses.push(
2532+
IterFunction { func: IterFunctionKind::IntoIter, span: expr.span }
2533+
),
2534+
name if name == len => self.uses.push(
2535+
IterFunction { func: IterFunctionKind::Len, span: expr.span }
2536+
),
2537+
name if name == is_empty => self.uses.push(
2538+
IterFunction { func: IterFunctionKind::IsEmpty, span: expr.span }
2539+
),
2540+
name if name == contains => self.uses.push(
2541+
IterFunction { func: IterFunctionKind::Contains(args[1].span), span: expr.span }
2542+
),
2543+
_ => self.seen_other = true,
2544+
}
2545+
return
2546+
}
2547+
}
2548+
// Check if the collection is used for anything else
2549+
if_chain! {
2550+
if let Expr { kind: ExprKind::Path(QPath::Resolved(_, ref path)), .. } = expr;
2551+
if let &[name] = &path.segments;
2552+
if name.ident == self.target;
2553+
then {
2554+
self.seen_other = true;
2555+
} else {
2556+
walk_expr(self, expr);
2557+
}
2558+
}
2559+
}
2560+
2561+
type Map = Map<'tcx>;
2562+
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
2563+
NestedVisitorMap::None
2564+
}
2565+
}
2566+
2567+
/// Detect the occurences of calls to `iter` or `into_iter` for the
2568+
/// given identifier
2569+
fn detect_iter_and_into_iters<'tcx>(block: &'tcx Block<'tcx>, identifier: Ident) -> Option<Vec<IterFunction>> {
2570+
let mut visitor = IterFunctionVisitor {
2571+
uses: Vec::new(),
2572+
target: identifier,
2573+
seen_other: false,
2574+
};
2575+
visitor.visit_block(block);
2576+
if visitor.seen_other {
2577+
None
2578+
} else {
2579+
Some(visitor.uses)
2580+
}
2581+
}
2582+
24282583
fn shorten_span(expr: &Expr<'_>, target_fn_name: Symbol) -> Span {
24292584
let mut current_expr = expr;
24302585
while let ExprKind::MethodCall(ref path, ref span, ref args, _) = current_expr.kind {

tests/ui/needless_collect_indirect.rs

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
use std::collections::{HashMap, VecDeque};
2+
3+
fn main() {
4+
let sample = [1; 5];
5+
let indirect_iter = sample.iter().collect::<Vec<_>>();
6+
indirect_iter.into_iter().map(|x| (x, x + 1)).collect::<HashMap<_, _>>();
7+
let indirect_len = sample.iter().collect::<VecDeque<_>>();
8+
indirect_len.len();
9+
let indirect_empty = sample.iter().collect::<VecDeque<_>>();
10+
indirect_empty.is_empty();
11+
let indirect_contains = sample.iter().collect::<VecDeque<_>>();
12+
indirect_contains.contains(&&5);
13+
let indirect_negative = sample.iter().collect::<Vec<_>>();
14+
indirect_negative.len();
15+
indirect_negative
16+
.into_iter()
17+
.map(|x| (*x, *x + 1))
18+
.collect::<HashMap<_, _>>();
19+
}
+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
error: avoid using `collect()` when not needed
2+
--> $DIR/needless_collect_indirect.rs:5:5
3+
|
4+
LL | / let indirect_iter = sample.iter().collect::<Vec<_>>();
5+
LL | | indirect_iter.into_iter().map(|x| (x, x + 1)).collect::<HashMap<_, _>>();
6+
| |____^
7+
|
8+
= note: `-D clippy::needless-collect` implied by `-D warnings`
9+
help: Use the original Iterator instead of collecting it and then producing a new one
10+
|
11+
LL |
12+
LL | sample.iter().map(|x| (x, x + 1)).collect::<HashMap<_, _>>();
13+
|
14+
15+
error: avoid using `collect()` when not needed
16+
--> $DIR/needless_collect_indirect.rs:7:5
17+
|
18+
LL | / let indirect_len = sample.iter().collect::<VecDeque<_>>();
19+
LL | | indirect_len.len();
20+
| |____^
21+
|
22+
help: Take the original Iterator's count instead of collecting it and finding the length
23+
|
24+
LL |
25+
LL | sample.iter().count();
26+
|
27+
28+
error: avoid using `collect()` when not needed
29+
--> $DIR/needless_collect_indirect.rs:9:5
30+
|
31+
LL | / let indirect_empty = sample.iter().collect::<VecDeque<_>>();
32+
LL | | indirect_empty.is_empty();
33+
| |____^
34+
|
35+
help: Check if the original Iterator has anything instead of collecting it and seeing if it's empty
36+
|
37+
LL |
38+
LL | sample.iter().next().is_none();
39+
|
40+
41+
error: avoid using `collect()` when not needed
42+
--> $DIR/needless_collect_indirect.rs:11:5
43+
|
44+
LL | / let indirect_contains = sample.iter().collect::<VecDeque<_>>();
45+
LL | | indirect_contains.contains(&&5);
46+
| |____^
47+
|
48+
help: Check if the original Iterator contains an element instead of collecting then checking
49+
|
50+
LL |
51+
LL | sample.iter().any(|x| x == &&5);
52+
|
53+
54+
error: aborting due to 4 previous errors
55+

0 commit comments

Comments
 (0)