@@ -14,7 +14,8 @@ use if_chain::if_chain;
14
14
use rustc_ast:: ast;
15
15
use rustc_errors:: Applicability ;
16
16
use rustc_hir as hir;
17
- use rustc_hir:: { TraitItem , TraitItemKind } ;
17
+ use rustc_hir:: def:: Res ;
18
+ use rustc_hir:: { Expr , ExprKind , PatKind , QPath , TraitItem , TraitItemKind , UnOp } ;
18
19
use rustc_lint:: { LateContext , LateLintPass , Lint , LintContext } ;
19
20
use rustc_middle:: lint:: in_external_macro;
20
21
use rustc_middle:: ty:: { self , TraitRef , Ty , TyS } ;
@@ -449,6 +450,32 @@ declare_clippy_lint! {
449
450
"using combinations of `filter`, `map`, `filter_map` and `flat_map` which can usually be written as a single method call"
450
451
}
451
452
453
+ declare_clippy_lint ! {
454
+ /// **What it does:** Checks for usage of `_.filter(_).map(_)` that can be written more simply
455
+ /// as `filter_map(_)`.
456
+ ///
457
+ /// **Why is this bad?** Redundant code in the `filter` and `map` operations is poor style and
458
+ /// less performant.
459
+ ///
460
+ /// **Known problems:** None.
461
+ ///
462
+ /// **Example:**
463
+ /// Bad:
464
+ /// ```rust
465
+ /// (0_i32..10)
466
+ /// .filter(|n| n.checked_add(1).is_some())
467
+ /// .map(|n| n.checked_add(1).unwrap());
468
+ /// ```
469
+ ///
470
+ /// Good:
471
+ /// ```rust
472
+ /// (0_i32..10).filter_map(|n| n.checked_add(1));
473
+ /// ```
474
+ pub MANUAL_FILTER_MAP ,
475
+ complexity,
476
+ "using `_.filter(_).map(_)` in a way that can be written more simply as `filter_map(_)`"
477
+ }
478
+
452
479
declare_clippy_lint ! {
453
480
/// **What it does:** Checks for usage of `_.filter_map(_).next()`.
454
481
///
@@ -1442,6 +1469,7 @@ impl_lint_pass!(Methods => [
1442
1469
FILTER_NEXT ,
1443
1470
SKIP_WHILE_NEXT ,
1444
1471
FILTER_MAP ,
1472
+ MANUAL_FILTER_MAP ,
1445
1473
FILTER_MAP_NEXT ,
1446
1474
FLAT_MAP_IDENTITY ,
1447
1475
FIND_MAP ,
@@ -1508,7 +1536,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
1508
1536
[ "next" , "filter" ] => lint_filter_next ( cx, expr, arg_lists[ 1 ] ) ,
1509
1537
[ "next" , "skip_while" ] => lint_skip_while_next ( cx, expr, arg_lists[ 1 ] ) ,
1510
1538
[ "next" , "iter" ] => lint_iter_next ( cx, expr, arg_lists[ 1 ] ) ,
1511
- [ "map" , "filter" ] => lint_filter_map ( cx, expr, arg_lists [ 1 ] , arg_lists [ 0 ] ) ,
1539
+ [ "map" , "filter" ] => lint_filter_map ( cx, expr) ,
1512
1540
[ "map" , "filter_map" ] => lint_filter_map_map ( cx, expr, arg_lists[ 1 ] , arg_lists[ 0 ] ) ,
1513
1541
[ "next" , "filter_map" ] => lint_filter_map_next ( cx, expr, arg_lists[ 1 ] , self . msrv . as_ref ( ) ) ,
1514
1542
[ "map" , "find" ] => lint_find_map ( cx, expr, arg_lists[ 1 ] , arg_lists[ 0 ] ) ,
@@ -2956,17 +2984,72 @@ fn lint_skip_while_next<'tcx>(
2956
2984
}
2957
2985
2958
2986
/// lint use of `filter().map()` for `Iterators`
2959
- fn lint_filter_map < ' tcx > (
2960
- cx : & LateContext < ' tcx > ,
2961
- expr : & ' tcx hir:: Expr < ' _ > ,
2962
- _filter_args : & ' tcx [ hir:: Expr < ' _ > ] ,
2963
- _map_args : & ' tcx [ hir:: Expr < ' _ > ] ,
2964
- ) {
2965
- // lint if caller of `.filter().map()` is an Iterator
2966
- if match_trait_method ( cx, expr, & paths:: ITERATOR ) {
2967
- let msg = "called `filter(..).map(..)` on an `Iterator`" ;
2968
- let hint = "this is more succinctly expressed by calling `.filter_map(..)` instead" ;
2969
- span_lint_and_help ( cx, FILTER_MAP , expr. span , msg, None , hint) ;
2987
+ fn lint_filter_map < ' tcx > ( cx : & LateContext < ' tcx > , expr : & ' tcx hir:: Expr < ' _ > ) {
2988
+ if_chain ! {
2989
+ if let ExprKind :: MethodCall ( _, _, [ map_recv, map_arg] , map_span) = expr. kind;
2990
+ if let ExprKind :: MethodCall ( _, _, [ _, filter_arg] , filter_span) = map_recv. kind;
2991
+ if match_trait_method( cx, expr, & paths:: ITERATOR ) ;
2992
+
2993
+ // filter(|x| ...is_some())...
2994
+ if let ExprKind :: Closure ( _, _, filter_body_id, ..) = filter_arg. kind;
2995
+ let filter_body = cx. tcx. hir( ) . body( filter_body_id) ;
2996
+ if let [ filter_param] = filter_body. params;
2997
+ // optional ref pattern: `filter(|&x| ..)`
2998
+ let ( filter_pat, is_filter_param_ref) = if let PatKind :: Ref ( ref_pat, _) = filter_param. pat. kind {
2999
+ ( ref_pat, true )
3000
+ } else {
3001
+ ( filter_param. pat, false )
3002
+ } ;
3003
+ // closure ends with is_some() or is_ok()
3004
+ if let PatKind :: Binding ( _, filter_param_id, _, None ) = filter_pat. kind;
3005
+ if let ExprKind :: MethodCall ( path, _, [ filter_arg] , _) = filter_body. value. kind;
3006
+ if let Some ( opt_ty) = cx. typeck_results( ) . expr_ty( filter_arg) . ty_adt_def( ) ;
3007
+ if let Some ( is_result) = if cx. tcx. is_diagnostic_item( sym:: option_type, opt_ty. did) {
3008
+ Some ( false )
3009
+ } else if cx. tcx. is_diagnostic_item( sym:: result_type, opt_ty. did) {
3010
+ Some ( true )
3011
+ } else {
3012
+ None
3013
+ } ;
3014
+ if path. ident. name. as_str( ) == if is_result { "is_ok" } else { "is_some" } ;
3015
+
3016
+ // ...map(|x| ...unwrap())
3017
+ if let ExprKind :: Closure ( _, _, map_body_id, ..) = map_arg. kind;
3018
+ let map_body = cx. tcx. hir( ) . body( map_body_id) ;
3019
+ if let [ map_param] = map_body. params;
3020
+ if let PatKind :: Binding ( _, map_param_id, map_param_ident, None ) = map_param. pat. kind;
3021
+ // closure ends with expect() or unwrap()
3022
+ if let ExprKind :: MethodCall ( seg, _, [ map_arg, ..] , _) = map_body. value. kind;
3023
+ if matches!( seg. ident. name, sym:: expect | sym:: unwrap | sym:: unwrap_or) ;
3024
+
3025
+ let eq_fallback = |a: & Expr <' _>, b: & Expr <' _>| {
3026
+ // in `filter(|x| ..)`, replace `*x` with `x`
3027
+ let a_path = if_chain! {
3028
+ if !is_filter_param_ref;
3029
+ if let ExprKind :: Unary ( UnOp :: UnDeref , expr_path) = a. kind;
3030
+ then { expr_path } else { a }
3031
+ } ;
3032
+ // let the filter closure arg and the map closure arg be equal
3033
+ if_chain! {
3034
+ if let ExprKind :: Path ( QPath :: Resolved ( None , a_path) ) = a_path. kind;
3035
+ if let ExprKind :: Path ( QPath :: Resolved ( None , b_path) ) = b. kind;
3036
+ if a_path. res == Res :: Local ( filter_param_id) ;
3037
+ if b_path. res == Res :: Local ( map_param_id) ;
3038
+ if TyS :: same_type( cx. typeck_results( ) . expr_ty_adjusted( a) , cx. typeck_results( ) . expr_ty_adjusted( b) ) ;
3039
+ then {
3040
+ return true ;
3041
+ }
3042
+ }
3043
+ false
3044
+ } ;
3045
+ if SpanlessEq :: new( cx) . expr_fallback( eq_fallback) . eq_expr( filter_arg, map_arg) ;
3046
+ then {
3047
+ let span = filter_span. to( map_span) ;
3048
+ let msg = "`filter(..).map(..)` can be simplified as `filter_map(..)`" ;
3049
+ let to_opt = if is_result { ".ok()" } else { "" } ;
3050
+ let sugg = format!( "filter_map(|{}| {}{})" , map_param_ident, snippet( cx, map_arg. span, ".." ) , to_opt) ;
3051
+ span_lint_and_sugg( cx, MANUAL_FILTER_MAP , span, msg, "try" , sugg, Applicability :: MachineApplicable ) ;
3052
+ }
2970
3053
}
2971
3054
}
2972
3055
0 commit comments