1
1
use crate :: consts:: constant;
2
2
use crate :: reexport:: Name ;
3
3
use crate :: utils:: paths;
4
+ use crate :: utils:: sugg:: Sugg ;
4
5
use crate :: utils:: usage:: { is_unused, mutated_variables} ;
5
6
use crate :: utils:: {
6
7
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 ,
10
11
} ;
11
- use crate :: utils:: { is_type_diagnostic_item, qpath_res, sugg} ;
12
12
use if_chain:: if_chain;
13
13
use rustc_ast:: ast;
14
14
use rustc_data_structures:: fx:: { FxHashMap , FxHashSet } ;
@@ -17,7 +17,7 @@ use rustc_hir::def::{DefKind, Res};
17
17
use rustc_hir:: intravisit:: { walk_block, walk_expr, walk_pat, walk_stmt, NestedVisitorMap , Visitor } ;
18
18
use rustc_hir:: {
19
19
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 ,
21
21
} ;
22
22
use rustc_infer:: infer:: TyCtxtInferExt ;
23
23
use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
@@ -27,7 +27,7 @@ use rustc_middle::middle::region;
27
27
use rustc_middle:: ty:: { self , Ty , TyS } ;
28
28
use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
29
29
use rustc_span:: source_map:: Span ;
30
- use rustc_span:: symbol:: Symbol ;
30
+ use rustc_span:: symbol:: { sym , Ident , Symbol } ;
31
31
use rustc_typeck:: expr_use_visitor:: { ConsumeMode , Delegate , ExprUseVisitor , PlaceBase , PlaceWithHirId } ;
32
32
use std:: iter:: { once, Iterator } ;
33
33
use std:: mem;
@@ -2358,6 +2358,10 @@ impl<'a, 'tcx> Visitor<'tcx> for VarCollectorVisitor<'a, 'tcx> {
2358
2358
const NEEDLESS_COLLECT_MSG : & str = "avoid using `collect()` when not needed" ;
2359
2359
2360
2360
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 > ) {
2361
2365
if_chain ! {
2362
2366
if let ExprKind :: MethodCall ( ref method, _, ref args, _) = expr. kind;
2363
2367
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>) {
2425
2429
}
2426
2430
}
2427
2431
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
+
2428
2583
fn shorten_span ( expr : & Expr < ' _ > , target_fn_name : Symbol ) -> Span {
2429
2584
let mut current_expr = expr;
2430
2585
while let ExprKind :: MethodCall ( ref path, ref span, ref args, _) = current_expr. kind {
0 commit comments