@@ -14,10 +14,12 @@ use rustc::middle::mem_categorization::Categorization;
14
14
use rustc:: middle:: mem_categorization:: cmt_;
15
15
use rustc:: ty:: { self , Ty } ;
16
16
use rustc:: ty:: subst:: Subst ;
17
+ use rustc_errors:: Applicability ;
17
18
use std:: collections:: { HashMap , HashSet } ;
18
19
use std:: iter:: { once, Iterator } ;
19
20
use syntax:: ast;
20
21
use syntax:: source_map:: Span ;
22
+ use syntax_pos:: BytePos ;
21
23
use crate :: utils:: { sugg, sext} ;
22
24
use crate :: utils:: usage:: mutated_variables;
23
25
use crate :: consts:: { constant, Constant } ;
@@ -223,6 +225,27 @@ declare_clippy_lint! {
223
225
written as a for loop"
224
226
}
225
227
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
+
226
249
/// **What it does:** Checks for loops over ranges `x..y` where both `x` and `y`
227
250
/// are constant and `x` is greater or equal to `y`, unless the range is
228
251
/// reversed or has a negative `.step_by(_)`.
@@ -400,6 +423,7 @@ impl LintPass for Pass {
400
423
FOR_LOOP_OVER_OPTION ,
401
424
WHILE_LET_LOOP ,
402
425
UNUSED_COLLECT ,
426
+ NEEDLESS_COLLECT ,
403
427
REVERSE_RANGE_LOOP ,
404
428
EXPLICIT_COUNTER_LOOP ,
405
429
EMPTY_LOOP ,
@@ -523,6 +547,8 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Pass {
523
547
if let ExprKind :: While ( ref cond, _, _) = expr. node {
524
548
check_infinite_loop ( cx, cond, expr) ;
525
549
}
550
+
551
+ check_needless_collect ( expr, cx) ;
526
552
}
527
553
528
554
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> {
2241
2267
NestedVisitorMap :: None
2242
2268
}
2243
2269
}
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
+ }
0 commit comments