1
1
use crate :: rustc:: hir;
2
2
use crate :: rustc:: hir:: def:: Def ;
3
+ use crate :: rustc:: hir:: intravisit:: { walk_expr, NestedVisitorMap , Visitor } ;
3
4
use crate :: rustc:: lint:: { in_external_macro, LateContext , LateLintPass , Lint , LintArray , LintContext , LintPass } ;
4
5
use crate :: rustc:: ty:: { self , Ty } ;
5
6
use crate :: rustc:: { declare_tool_lint, lint_array} ;
@@ -8,6 +9,7 @@ use crate::syntax::ast;
8
9
use crate :: syntax:: source_map:: { BytePos , Span } ;
9
10
use crate :: utils:: paths;
10
11
use crate :: utils:: sugg;
12
+ use crate :: utils:: usage:: mutated_variables;
11
13
use crate :: utils:: {
12
14
get_arg_name, get_trait_def_id, implements_trait, in_macro, is_copy, is_expn_of, is_self, is_self_ty,
13
15
iter_input_pats, last_path_segment, match_def_path, match_path, match_qpath, match_trait_method, match_type,
@@ -692,6 +694,27 @@ declare_clippy_lint! {
692
694
"using `fold` when a more succinct alternative exists"
693
695
}
694
696
697
+
698
+ /// **What it does:** Checks for `filter_map` calls which could be replaced by `filter` or `map`.
699
+ ///
700
+ /// **Why is this bad?** Complexity
701
+ ///
702
+ /// **Known problems:** None
703
+ ///
704
+ /// **Example:**
705
+ /// ```rust
706
+ /// let _ = (0..3).filter_map(|x| if x > 2 { Some(x) } else { None });
707
+ /// ```
708
+ /// This could be written as:
709
+ /// ```rust
710
+ /// let _ = (0..3).filter(|&x| x > 2);
711
+ /// ```
712
+ declare_clippy_lint ! {
713
+ pub UNNECESSARY_FILTER_MAP ,
714
+ complexity,
715
+ "using `filter_map` when a more succinct alternative exists"
716
+ }
717
+
695
718
impl LintPass for Pass {
696
719
fn get_lints ( & self ) -> LintArray {
697
720
lint_array ! (
@@ -725,7 +748,8 @@ impl LintPass for Pass {
725
748
STRING_EXTEND_CHARS ,
726
749
ITER_CLONED_COLLECT ,
727
750
USELESS_ASREF ,
728
- UNNECESSARY_FOLD
751
+ UNNECESSARY_FOLD ,
752
+ UNNECESSARY_FILTER_MAP
729
753
)
730
754
}
731
755
}
@@ -791,6 +815,8 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Pass {
791
815
lint_asref ( cx, expr, "as_mut" , arglists[ 0 ] ) ;
792
816
} else if let Some ( arglists) = method_chain_args ( expr, & [ "fold" ] ) {
793
817
lint_unnecessary_fold ( cx, expr, arglists[ 0 ] ) ;
818
+ } else if let Some ( arglists) = method_chain_args ( expr, & [ "filter_map" ] ) {
819
+ unnecessary_filter_map:: lint ( cx, expr, arglists[ 0 ] ) ;
794
820
}
795
821
796
822
lint_or_fun_call ( cx, expr, * method_span, & method_call. ident . as_str ( ) , args) ;
@@ -1398,6 +1424,144 @@ fn lint_unnecessary_fold(cx: &LateContext<'_, '_>, expr: &hir::Expr, fold_args:
1398
1424
} ;
1399
1425
}
1400
1426
1427
+ mod unnecessary_filter_map {
1428
+ use super :: * ;
1429
+
1430
+ pub ( super ) fn lint ( cx : & LateContext < ' _ , ' _ > , expr : & hir:: Expr , args : & [ hir:: Expr ] ) {
1431
+
1432
+ if !match_trait_method ( cx, expr, & paths:: ITERATOR ) {
1433
+ return ;
1434
+ }
1435
+
1436
+ if let hir:: ExprKind :: Closure ( _, _, body_id, ..) = args[ 1 ] . node {
1437
+
1438
+ let body = cx. tcx . hir . body ( body_id) ;
1439
+ let arg_id = body. arguments [ 0 ] . pat . id ;
1440
+ let mutates_arg = match mutated_variables ( & body. value , cx) {
1441
+ Some ( used_mutably) => used_mutably. contains ( & arg_id) ,
1442
+ None => true ,
1443
+ } ;
1444
+
1445
+ let ( mut found_mapping, mut found_filtering) = check_expression ( & cx, arg_id, & body. value ) ;
1446
+
1447
+ let mut return_visitor = ReturnVisitor :: new ( & cx, arg_id) ;
1448
+ return_visitor. visit_expr ( & body. value ) ;
1449
+ found_mapping |= return_visitor. found_mapping ;
1450
+ found_filtering |= return_visitor. found_filtering ;
1451
+
1452
+ if !found_filtering {
1453
+ span_lint (
1454
+ cx,
1455
+ UNNECESSARY_FILTER_MAP ,
1456
+ expr. span ,
1457
+ "this `.filter_map` can be written more simply using `.map`" ,
1458
+ ) ;
1459
+ return ;
1460
+ }
1461
+
1462
+ if !found_mapping && !mutates_arg {
1463
+ span_lint (
1464
+ cx,
1465
+ UNNECESSARY_FILTER_MAP ,
1466
+ expr. span ,
1467
+ "this `.filter_map` can be written more simply using `.filter`" ,
1468
+ ) ;
1469
+ return ;
1470
+ }
1471
+ }
1472
+ }
1473
+
1474
+ // returns (found_mapping, found_filtering)
1475
+ fn check_expression < ' a , ' tcx : ' a > ( cx : & ' a LateContext < ' a , ' tcx > , arg_id : ast:: NodeId , expr : & ' tcx hir:: Expr ) -> ( bool , bool ) {
1476
+ match & expr. node {
1477
+ hir:: ExprKind :: Call ( ref func, ref args) => {
1478
+ if_chain ! {
1479
+ if let hir:: ExprKind :: Path ( ref path) = func. node;
1480
+ then {
1481
+ if match_qpath( path, & paths:: OPTION_SOME ) {
1482
+ if_chain! {
1483
+ if let hir:: ExprKind :: Path ( path) = & args[ 0 ] . node;
1484
+ if let Def :: Local ( ref local) = cx. tables. qpath_def( path, args[ 0 ] . hir_id) ;
1485
+ then {
1486
+ if arg_id == * local {
1487
+ return ( false , false )
1488
+ }
1489
+ }
1490
+ }
1491
+ return ( true , false ) ;
1492
+ } else {
1493
+ // We don't know. It might do anything.
1494
+ return ( true , true ) ;
1495
+ }
1496
+ }
1497
+ }
1498
+ ( true , true )
1499
+ } ,
1500
+ hir:: ExprKind :: Block ( ref block, _) => {
1501
+ if let Some ( expr) = & block. expr {
1502
+ check_expression ( cx, arg_id, & expr)
1503
+ } else {
1504
+ ( false , false )
1505
+ }
1506
+ } ,
1507
+ // There must be an else_arm or there will be a type error
1508
+ hir:: ExprKind :: If ( _, ref if_arm, Some ( ref else_arm) ) => {
1509
+ let if_check = check_expression ( cx, arg_id, if_arm) ;
1510
+ let else_check = check_expression ( cx, arg_id, else_arm) ;
1511
+ ( if_check. 0 | else_check. 0 , if_check. 1 | else_check. 1 )
1512
+ } ,
1513
+ hir:: ExprKind :: Match ( _, ref arms, _) => {
1514
+ let mut found_mapping = false ;
1515
+ let mut found_filtering = false ;
1516
+ for arm in arms {
1517
+ let ( m, f) = check_expression ( cx, arg_id, & arm. body ) ;
1518
+ found_mapping |= m;
1519
+ found_filtering |= f;
1520
+ }
1521
+ ( found_mapping, found_filtering)
1522
+ } ,
1523
+ hir:: ExprKind :: Path ( path) if match_qpath ( path, & paths:: OPTION_NONE ) => ( false , true ) ,
1524
+ _ => ( true , true )
1525
+ }
1526
+ }
1527
+
1528
+ struct ReturnVisitor < ' a , ' tcx : ' a > {
1529
+ cx : & ' a LateContext < ' a , ' tcx > ,
1530
+ arg_id : ast:: NodeId ,
1531
+ // Found a non-None return that isn't Some(input)
1532
+ found_mapping : bool ,
1533
+ // Found a return that isn't Some
1534
+ found_filtering : bool ,
1535
+ }
1536
+
1537
+ impl < ' a , ' tcx : ' a > ReturnVisitor < ' a , ' tcx > {
1538
+ fn new ( cx : & ' a LateContext < ' a , ' tcx > , arg_id : ast:: NodeId ) -> ReturnVisitor < ' a , ' tcx > {
1539
+ ReturnVisitor {
1540
+ cx,
1541
+ arg_id,
1542
+ found_mapping : false ,
1543
+ found_filtering : false ,
1544
+ }
1545
+ }
1546
+ }
1547
+
1548
+ impl < ' a , ' tcx > Visitor < ' tcx > for ReturnVisitor < ' a , ' tcx > {
1549
+ fn visit_expr ( & mut self , expr : & ' tcx hir:: Expr ) {
1550
+ if let hir:: ExprKind :: Ret ( Some ( expr) ) = & expr. node {
1551
+ let ( found_mapping, found_filtering) = check_expression ( self . cx , self . arg_id , expr) ;
1552
+ self . found_mapping |= found_mapping;
1553
+ self . found_filtering |= found_filtering;
1554
+ } else {
1555
+ walk_expr ( self , expr) ;
1556
+ }
1557
+ }
1558
+
1559
+ fn nested_visit_map < ' this > ( & ' this mut self ) -> NestedVisitorMap < ' this , ' tcx > {
1560
+ NestedVisitorMap :: None
1561
+ }
1562
+ }
1563
+ }
1564
+
1401
1565
fn lint_iter_nth ( cx : & LateContext < ' _ , ' _ > , expr : & hir:: Expr , iter_args : & [ hir:: Expr ] , is_mut : bool ) {
1402
1566
let mut_str = if is_mut { "_mut" } else { "" } ;
1403
1567
let caller_type = if derefs_to_slice ( cx, & iter_args[ 0 ] , cx. tables . expr_ty ( & iter_args[ 0 ] ) ) . is_some ( ) {
0 commit comments