Skip to content

Commit db391c4

Browse files
committed
Merge branch 'master' of https://github.com/rust-lang-nursery/rust-clippy into range-plus-one
2 parents 2f0a99a + b7587d8 commit db391c4

File tree

7 files changed

+152
-18
lines changed

7 files changed

+152
-18
lines changed

PUBLISH.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ Steps to publish a new Clippy version
22

33
- Bump `package.version` in `./Cargo.toml` (no need to manually bump `dependencies.clippy_lints.version`).
44
- Write a changelog entry.
5-
- If a nightly update is needed, update `min_version.txt` using `rustc -vV > min_version.txt`
65
- Run `./pre_publish.sh`
76
- Review and commit all changed files
87
- `git push`

build.rs

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,3 @@
1-
//! This build script ensures that Clippy is not compiled with an
2-
//! incompatible version of rust. It will panic with a descriptive
3-
//! error message instead.
4-
//!
5-
//! We specifially want to ensure that Clippy is only built with a
6-
//! rustc version that is newer or equal to the one specified in the
7-
//! `min_version.txt` file.
8-
//!
9-
//! `min_version.txt` is in the repo but also in the `.gitignore` to
10-
//! make sure that it is not updated manually by accident. Only CI
11-
//! should update that file.
12-
//!
13-
//! This build script was originally taken from the Rocket web framework:
14-
//! https://github.com/SergioBenitez/Rocket
15-
161
use std::env;
172

183
fn main() {

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry<'_>, conf: &Conf) {
554554
loops::ITER_NEXT_LOOP,
555555
loops::MANUAL_MEMCPY,
556556
loops::MUT_RANGE_BOUND,
557+
loops::NEEDLESS_COLLECT,
557558
loops::NEEDLESS_RANGE_LOOP,
558559
loops::NEVER_LOOP,
559560
loops::REVERSE_RANGE_LOOP,
@@ -904,6 +905,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry<'_>, conf: &Conf) {
904905
escape::BOXED_LOCAL,
905906
large_enum_variant::LARGE_ENUM_VARIANT,
906907
loops::MANUAL_MEMCPY,
908+
loops::NEEDLESS_COLLECT,
907909
loops::UNUSED_COLLECT,
908910
methods::EXPECT_FUN_CALL,
909911
methods::ITER_NTH,

clippy_lints/src/loops.rs

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@ use rustc::middle::mem_categorization::Categorization;
1414
use rustc::middle::mem_categorization::cmt_;
1515
use rustc::ty::{self, Ty};
1616
use rustc::ty::subst::Subst;
17+
use rustc_errors::Applicability;
1718
use std::collections::{HashMap, HashSet};
1819
use std::iter::{once, Iterator};
1920
use syntax::ast;
2021
use syntax::source_map::Span;
22+
use syntax_pos::BytePos;
2123
use crate::utils::{sugg, sext};
2224
use crate::utils::usage::mutated_variables;
2325
use crate::consts::{constant, Constant};
@@ -223,6 +225,27 @@ declare_clippy_lint! {
223225
written as a for loop"
224226
}
225227

228+
/// **What it does:** Checks for functions collecting an iterator when collect
229+
/// is not needed.
230+
///
231+
/// **Why is this bad?** `collect` causes the allocation of a new data structure,
232+
/// when this allocation may not be needed.
233+
///
234+
/// **Known problems:**
235+
/// None
236+
///
237+
/// **Example:**
238+
/// ```rust
239+
/// let len = iterator.collect::<Vec<_>>().len();
240+
/// // should be
241+
/// let len = iterator.count();
242+
/// ```
243+
declare_clippy_lint! {
244+
pub NEEDLESS_COLLECT,
245+
perf,
246+
"collecting an iterator when collect is not needed"
247+
}
248+
226249
/// **What it does:** Checks for loops over ranges `x..y` where both `x` and `y`
227250
/// are constant and `x` is greater or equal to `y`, unless the range is
228251
/// reversed or has a negative `.step_by(_)`.
@@ -400,6 +423,7 @@ impl LintPass for Pass {
400423
FOR_LOOP_OVER_OPTION,
401424
WHILE_LET_LOOP,
402425
UNUSED_COLLECT,
426+
NEEDLESS_COLLECT,
403427
REVERSE_RANGE_LOOP,
404428
EXPLICIT_COUNTER_LOOP,
405429
EMPTY_LOOP,
@@ -523,6 +547,8 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Pass {
523547
if let ExprKind::While(ref cond, _, _) = expr.node {
524548
check_infinite_loop(cx, cond, expr);
525549
}
550+
551+
check_needless_collect(expr, cx);
526552
}
527553

528554
fn check_stmt(&mut self, cx: &LateContext<'a, 'tcx>, stmt: &'tcx Stmt) {
@@ -2241,3 +2267,71 @@ impl<'a, 'tcx> Visitor<'tcx> for VarCollectorVisitor<'a, 'tcx> {
22412267
NestedVisitorMap::None
22422268
}
22432269
}
2270+
2271+
const NEEDLESS_COLLECT_MSG: &str = "avoid using `collect()` when not needed";
2272+
2273+
fn check_needless_collect<'a, 'tcx>(expr: &'tcx Expr, cx: &LateContext<'a, 'tcx>) {
2274+
if_chain! {
2275+
if let ExprKind::MethodCall(ref method, _, ref args) = expr.node;
2276+
if let ExprKind::MethodCall(ref chain_method, _, _) = args[0].node;
2277+
if chain_method.ident.name == "collect" && match_trait_method(cx, &args[0], &paths::ITERATOR);
2278+
if let Some(ref generic_args) = chain_method.args;
2279+
if let Some(GenericArg::Type(ref ty)) = generic_args.args.get(0);
2280+
then {
2281+
let ty = cx.tables.node_id_to_type(ty.hir_id);
2282+
if match_type(cx, ty, &paths::VEC) ||
2283+
match_type(cx, ty, &paths::VEC_DEQUE) ||
2284+
match_type(cx, ty, &paths::BTREEMAP) ||
2285+
match_type(cx, ty, &paths::HASHMAP) {
2286+
if method.ident.name == "len" {
2287+
let span = shorten_needless_collect_span(expr);
2288+
span_lint_and_then(cx, NEEDLESS_COLLECT, span, NEEDLESS_COLLECT_MSG, |db| {
2289+
db.span_suggestion_with_applicability(
2290+
span,
2291+
"replace with",
2292+
".count()".to_string(),
2293+
Applicability::MachineApplicable,
2294+
);
2295+
});
2296+
}
2297+
if method.ident.name == "is_empty" {
2298+
let span = shorten_needless_collect_span(expr);
2299+
span_lint_and_then(cx, NEEDLESS_COLLECT, span, NEEDLESS_COLLECT_MSG, |db| {
2300+
db.span_suggestion_with_applicability(
2301+
span,
2302+
"replace with",
2303+
".next().is_none()".to_string(),
2304+
Applicability::MachineApplicable,
2305+
);
2306+
});
2307+
}
2308+
if method.ident.name == "contains" {
2309+
let contains_arg = snippet(cx, args[1].span, "??");
2310+
let span = shorten_needless_collect_span(expr);
2311+
span_lint_and_then(cx, NEEDLESS_COLLECT, span, NEEDLESS_COLLECT_MSG, |db| {
2312+
db.span_suggestion_with_applicability(
2313+
span,
2314+
"replace with",
2315+
format!(
2316+
".any(|&x| x == {})",
2317+
if contains_arg.starts_with('&') { &contains_arg[1..] } else { &contains_arg }
2318+
),
2319+
Applicability::MachineApplicable,
2320+
);
2321+
});
2322+
}
2323+
}
2324+
}
2325+
}
2326+
}
2327+
2328+
fn shorten_needless_collect_span(expr: &Expr) -> Span {
2329+
if_chain! {
2330+
if let ExprKind::MethodCall(_, _, ref args) = expr.node;
2331+
if let ExprKind::MethodCall(_, ref span, _) = args[0].node;
2332+
then {
2333+
return expr.span.with_lo(span.lo() - BytePos(1));
2334+
}
2335+
}
2336+
unreachable!()
2337+
}

clippy_lints/src/utils/conf.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#![deny(clippy::missing_docs_in_private_items)]
44

55
use lazy_static::lazy_static;
6+
use std::default::Default;
67
use std::{env, fmt, fs, io, path};
78
use std::io::Read;
89
use syntax::{ast, source_map};
@@ -65,7 +66,7 @@ macro_rules! define_Conf {
6566
mod helpers {
6667
use serde_derive::Deserialize;
6768
/// Type used to store lint configuration.
68-
#[derive(Default, Deserialize)]
69+
#[derive(Deserialize)]
6970
#[serde(rename_all="kebab-case", deny_unknown_fields)]
7071
pub struct Conf {
7172
$(#[$doc] #[serde(default=$rust_name_str)] #[serde(with=$rust_name_str)]
@@ -146,6 +147,12 @@ define_Conf! {
146147
(trivial_copy_size_limit, "trivial_copy_size_limit", None => Option<u64>),
147148
}
148149

150+
impl Default for Conf {
151+
fn default() -> Conf {
152+
toml::from_str("").expect("we never error on empty config files")
153+
}
154+
}
155+
149156
/// Search for the configuration file.
150157
pub fn lookup_conf_file() -> io::Result<Option<path::PathBuf>> {
151158
/// Possible filename to search for.
@@ -180,7 +187,7 @@ pub fn lookup_conf_file() -> io::Result<Option<path::PathBuf>> {
180187
///
181188
/// Used internally for convenience
182189
fn default(errors: Vec<Error>) -> (Conf, Vec<Error>) {
183-
(toml::from_str("").expect("we never error on empty config files"), errors)
190+
(Conf::default(), errors)
184191
}
185192

186193
/// Read the `toml` configuration file.

tests/ui/needless_collect.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#![feature(tool_lints)]
2+
3+
use std::collections::{HashMap, HashSet, BTreeSet};
4+
5+
#[warn(clippy::needless_collect)]
6+
#[allow(unused_variables, clippy::iter_cloned_collect)]
7+
fn main() {
8+
let sample = [1; 5];
9+
let len = sample.iter().collect::<Vec<_>>().len();
10+
if sample.iter().collect::<Vec<_>>().is_empty() {
11+
// Empty
12+
}
13+
sample.iter().cloned().collect::<Vec<_>>().contains(&1);
14+
sample.iter().map(|x| (x, x)).collect::<HashMap<_, _>>().len();
15+
// Notice the `HashSet`--this should not be linted
16+
sample.iter().collect::<HashSet<_>>().len();
17+
// Neither should this
18+
sample.iter().collect::<BTreeSet<_>>().len();
19+
}

tests/ui/needless_collect.stderr

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
error: avoid using `collect()` when not needed
2+
--> $DIR/needless_collect.rs:9:28
3+
|
4+
9 | let len = sample.iter().collect::<Vec<_>>().len();
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `.count()`
6+
|
7+
= note: `-D clippy::needless-collect` implied by `-D warnings`
8+
9+
error: avoid using `collect()` when not needed
10+
--> $DIR/needless_collect.rs:10:21
11+
|
12+
10 | if sample.iter().collect::<Vec<_>>().is_empty() {
13+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `.next().is_none()`
14+
15+
error: avoid using `collect()` when not needed
16+
--> $DIR/needless_collect.rs:13:27
17+
|
18+
13 | sample.iter().cloned().collect::<Vec<_>>().contains(&1);
19+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `.any(|&x| x == 1)`
20+
21+
error: avoid using `collect()` when not needed
22+
--> $DIR/needless_collect.rs:14:34
23+
|
24+
14 | sample.iter().map(|x| (x, x)).collect::<HashMap<_, _>>().len();
25+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `.count()`
26+
27+
error: aborting due to 4 previous errors
28+

0 commit comments

Comments
 (0)