Skip to content

Commit 3ee6137

Browse files
committed
Write the lint and write tests
1 parent 05bb6e6 commit 3ee6137

File tree

4 files changed

+137
-16
lines changed

4 files changed

+137
-16
lines changed

clippy_lints/src/loops.rs

Lines changed: 101 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
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_path,
9+
match_trait_method, match_type, match_var, multispan_sugg, qpath_res, snippet, snippet_opt,
10+
snippet_with_applicability, span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then, sugg,
11+
SpanlessEq,
1012
};
11-
use crate::utils::{is_type_diagnostic_item, qpath_res, sugg};
1213
use if_chain::if_chain;
1314
use rustc_ast::ast;
1415
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
@@ -17,7 +18,7 @@ use rustc_hir::def::{DefKind, Res};
1718
use rustc_hir::intravisit::{walk_block, walk_expr, walk_pat, walk_stmt, NestedVisitorMap, Visitor};
1819
use rustc_hir::{
1920
def_id, BinOpKind, BindingAnnotation, Block, BorrowKind, Expr, ExprKind, GenericArg, HirId, InlineAsmOperand,
20-
LoopSource, MatchSource, Mutability, Node, Pat, PatKind, QPath, Stmt, StmtKind,
21+
Local, LoopSource, MatchSource, Mutability, Node, Pat, PatKind, QPath, Stmt, StmtKind,
2122
};
2223
use rustc_infer::infer::TyCtxtInferExt;
2324
use rustc_lint::{LateContext, LateLintPass, LintContext};
@@ -27,7 +28,7 @@ use rustc_middle::middle::region;
2728
use rustc_middle::ty::{self, Ty, TyS};
2829
use rustc_session::{declare_lint_pass, declare_tool_lint};
2930
use rustc_span::source_map::Span;
30-
use rustc_span::symbol::Symbol;
31+
use rustc_span::symbol::{Ident, Symbol};
3132
use rustc_typeck::expr_use_visitor::{ConsumeMode, Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
3233
use std::iter::{once, Iterator};
3334
use std::mem;
@@ -2358,6 +2359,7 @@ impl<'a, 'tcx> Visitor<'tcx> for VarCollectorVisitor<'a, 'tcx> {
23582359
const NEEDLESS_COLLECT_MSG: &str = "avoid using `collect()` when not needed";
23592360

23602361
fn check_needless_collect<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) {
2362+
// Check for direct, immediate usage
23612363
if_chain! {
23622364
if let ExprKind::MethodCall(ref method, _, ref args, _) = expr.kind;
23632365
if let ExprKind::MethodCall(ref chain_method, _, _, _) = args[0].kind;
@@ -2423,6 +2425,99 @@ fn check_needless_collect<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) {
24232425
}
24242426
}
24252427
}
2428+
// Check for collecting it and then turning it back into an iterator later
2429+
if let ExprKind::Block(ref block, _) = expr.kind {
2430+
for ref stmt in block.stmts {
2431+
if_chain! {
2432+
// TODO also work for assignments to an existing variable
2433+
if let StmtKind::Local(
2434+
Local { pat: Pat { kind: PatKind::Binding(_, _, ident, .. ), .. },
2435+
init: Some(ref init_expr), .. }
2436+
) = stmt.kind;
2437+
if let ExprKind::MethodCall(ref method_name, _, &[ref iter_source], ..) = init_expr.kind;
2438+
if method_name.ident.name == sym!(collect) && match_trait_method(cx, &init_expr, &paths::ITERATOR);
2439+
if let Some(ref generic_args) = method_name.args;
2440+
if let Some(GenericArg::Type(ref ty)) = generic_args.args.get(0);
2441+
if let ty = cx.typeck_results().node_type(ty.hir_id);
2442+
if is_type_diagnostic_item(cx, ty, sym!(vec_type)) ||
2443+
is_type_diagnostic_item(cx, ty, sym!(vecdeque_type)) ||
2444+
match_type(cx, ty, &paths::LINKED_LIST);
2445+
if let Some(iter_calls) = detect_iter_and_into_iters(block, *ident);
2446+
if iter_calls.len() == 1;
2447+
then {
2448+
// Suggest replacing iter_call with iter_replacement, and removing stmt
2449+
span_lint_and_then(
2450+
cx,
2451+
NEEDLESS_COLLECT,
2452+
stmt.span,
2453+
NEEDLESS_COLLECT_MSG,
2454+
|diag| {
2455+
let iter_replacement = Sugg::hir(cx, iter_source, "..").to_string();
2456+
diag.multipart_suggestion(
2457+
"Use the original Iterator instead of collecting it and then producing a new one",
2458+
vec![
2459+
(stmt.span, String::new()),
2460+
(iter_calls[0].span, iter_replacement)
2461+
],
2462+
Applicability::MaybeIncorrect,
2463+
);
2464+
},
2465+
);
2466+
}
2467+
}
2468+
}
2469+
}
2470+
}
2471+
2472+
struct IntoIterVisitor<'tcx> {
2473+
iters: Vec<&'tcx Expr<'tcx>>,
2474+
seen_other: bool,
2475+
target: String,
2476+
}
2477+
impl<'tcx> Visitor<'tcx> for IntoIterVisitor<'tcx> {
2478+
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
2479+
match &expr.kind {
2480+
ExprKind::MethodCall(
2481+
method_name,
2482+
_,
2483+
&[Expr {
2484+
kind: ExprKind::Path(QPath::Resolved(_, ref path)),
2485+
..
2486+
}],
2487+
_,
2488+
) if match_path(path, &[&self.target]) => {
2489+
// TODO Check what method is being called, if it's called on target, and act
2490+
// accordingly
2491+
if method_name.ident.name == sym!(into_iter) {
2492+
self.iters.push(expr);
2493+
} else {
2494+
self.seen_other = true;
2495+
}
2496+
},
2497+
_ => walk_expr(self, expr),
2498+
}
2499+
}
2500+
2501+
type Map = Map<'tcx>;
2502+
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
2503+
NestedVisitorMap::None
2504+
}
2505+
}
2506+
2507+
/// Detect the occurences of calls to `iter` or `into_iter` for the
2508+
/// given identifier
2509+
fn detect_iter_and_into_iters<'tcx>(block: &'tcx Block<'tcx>, identifier: Ident) -> Option<Vec<&'tcx Expr<'tcx>>> {
2510+
let mut visitor = IntoIterVisitor {
2511+
iters: Vec::new(),
2512+
target: identifier.name.to_ident_string(),
2513+
seen_other: false,
2514+
};
2515+
visitor.visit_block(block);
2516+
if visitor.seen_other {
2517+
None
2518+
} else {
2519+
Some(visitor.iters)
2520+
}
24262521
}
24272522

24282523
fn shorten_span(expr: &Expr<'_>, target_fn_name: Symbol) -> Span {

tests/ui/needless_collect.fixed

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,15 @@ fn main() {
1818
sample.iter().collect::<HashSet<_>>().len();
1919
// Neither should this
2020
sample.iter().collect::<BTreeSet<_>>().len();
21+
let indirect_positive = sample.iter().collect::<Vec<_>>();
22+
indirect_positive
23+
.into_iter()
24+
.map(|x| (x, x + 1))
25+
.collect::<HashMap<_, _>>();
26+
let indirect_negative = sample.iter().collect::<Vec<_>>();
27+
indirect_negative.len();
28+
indirect_negative
29+
.iter()
30+
.map(|x| (*x, *x + 1))
31+
.collect::<HashMap<_, _>>();
2132
}

tests/ui/needless_collect.rs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,6 @@ use std::collections::{BTreeSet, HashMap, HashSet};
88
#[allow(unused_variables, clippy::iter_cloned_collect)]
99
fn main() {
1010
let sample = [1; 5];
11-
let indirect_with_into_iter = sample.iter().collect::<Vec<_>>();
12-
let indirect_with_iter = sample.iter().collect::<Vec<_>>();;
13-
let indirect_negative = sample.iter().collect::<Vec<_>>();;
1411
let len = sample.iter().collect::<Vec<_>>().len();
1512
if sample.iter().collect::<Vec<_>>().is_empty() {
1613
// Empty
@@ -21,8 +18,15 @@ fn main() {
2118
sample.iter().collect::<HashSet<_>>().len();
2219
// Neither should this
2320
sample.iter().collect::<BTreeSet<_>>().len();
24-
indirect_with_into_iter.into_iter().map(|x| (x, x+1)).collect::<HashMap<_, _>>();
25-
indirect_with_iter.iter().map(|x| (x, x+1)).collect::<HashMap<_, _>>();
26-
indirect_negative.iter().map(|x| (x, x+1)).collect::<HashMap<_, _>>();
27-
indirect_negative.iter().map(|x| (x, x+1)).collect::<HashMap<_, _>>();
21+
let indirect_positive = sample.iter().collect::<Vec<_>>();
22+
indirect_positive
23+
.into_iter()
24+
.map(|x| (x, x + 1))
25+
.collect::<HashMap<_, _>>();
26+
let indirect_negative = sample.iter().collect::<Vec<_>>();
27+
indirect_negative.len();
28+
indirect_negative
29+
.iter()
30+
.map(|x| (*x, *x + 1))
31+
.collect::<HashMap<_, _>>();
2832
}

tests/ui/needless_collect.stderr

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,21 @@
1+
error: avoid using `collect()` when not needed
2+
--> $DIR/needless_collect.rs:21:5
3+
|
4+
LL | let indirect_positive = sample.iter().collect::<Vec<_>>();
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: `-D clippy::needless-collect` implied by `-D warnings`
8+
help: Use the original Iterator instead of collecting it and then producing a new one
9+
|
10+
LL |
11+
LL | sample.iter()
12+
|
13+
114
error: avoid using `collect()` when not needed
215
--> $DIR/needless_collect.rs:11:29
316
|
417
LL | let len = sample.iter().collect::<Vec<_>>().len();
518
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `count()`
6-
|
7-
= note: `-D clippy::needless-collect` implied by `-D warnings`
819

920
error: avoid using `collect()` when not needed
1021
--> $DIR/needless_collect.rs:12:15
@@ -24,5 +35,5 @@ error: avoid using `collect()` when not needed
2435
LL | sample.iter().map(|x| (x, x)).collect::<HashMap<_, _>>().len();
2536
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `count()`
2637

27-
error: aborting due to 4 previous errors
38+
error: aborting due to 5 previous errors
2839

0 commit comments

Comments
 (0)