@@ -1329,6 +1329,32 @@ declare_clippy_lint! {
1329
1329
"`push_str()` used with a single-character string literal as parameter"
1330
1330
}
1331
1331
1332
+ declare_clippy_lint ! {
1333
+ /// **What it does:** Looks for unnecessary lazily evaluated closures on `Option` and `Result`.
1334
+ ///
1335
+ /// **Why is this bad?** Using eager evaluation is shorter and simpler in some cases.
1336
+ ///
1337
+ /// **Known problems:** None.
1338
+ ///
1339
+ /// **Example:**
1340
+ ///
1341
+ /// ```rust
1342
+ /// // example code where clippy issues a warning
1343
+ /// let opt: Option<u32> = None;
1344
+ ///
1345
+ /// opt.unwrap_or_else(|| 42);
1346
+ /// ```
1347
+ /// Use instead:
1348
+ /// ```rust
1349
+ /// let opt: Option<u32> = None;
1350
+ ///
1351
+ /// opt.unwrap_or(42);
1352
+ /// ```
1353
+ pub UNNECESSARY_LAZY_EVALUATION ,
1354
+ style,
1355
+ "using unnecessary lazy evaluation, which can be replaced with simpler eager evaluation"
1356
+ }
1357
+
1332
1358
declare_lint_pass ! ( Methods => [
1333
1359
UNWRAP_USED ,
1334
1360
EXPECT_USED ,
@@ -1378,6 +1404,7 @@ declare_lint_pass!(Methods => [
1378
1404
ZST_OFFSET ,
1379
1405
FILETYPE_IS_FILE ,
1380
1406
OPTION_AS_REF_DEREF ,
1407
+ UNNECESSARY_LAZY_EVALUATION ,
1381
1408
] ) ;
1382
1409
1383
1410
impl < ' tcx > LateLintPass < ' tcx > for Methods {
@@ -1398,13 +1425,18 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
1398
1425
[ "expect" , "ok" ] => lint_ok_expect ( cx, expr, arg_lists[ 1 ] ) ,
1399
1426
[ "expect" , ..] => lint_expect ( cx, expr, arg_lists[ 0 ] ) ,
1400
1427
[ "unwrap_or" , "map" ] => option_map_unwrap_or:: lint ( cx, expr, arg_lists[ 1 ] , arg_lists[ 0 ] , method_spans[ 1 ] ) ,
1401
- [ "unwrap_or_else" , "map" ] => lint_map_unwrap_or_else ( cx, expr, arg_lists[ 1 ] , arg_lists[ 0 ] ) ,
1428
+ [ "unwrap_or_else" , "map" ] => {
1429
+ lint_lazy_eval ( cx, expr, arg_lists[ 0 ] , true , "unwrap_or" ) ;
1430
+ lint_map_unwrap_or_else ( cx, expr, arg_lists[ 1 ] , arg_lists[ 0 ] ) ;
1431
+ } ,
1402
1432
[ "map_or" , ..] => lint_map_or_none ( cx, expr, arg_lists[ 0 ] ) ,
1403
1433
[ "and_then" , ..] => {
1434
+ lint_lazy_eval ( cx, expr, arg_lists[ 0 ] , false , "and" ) ;
1404
1435
bind_instead_of_map:: OptionAndThenSome :: lint ( cx, expr, arg_lists[ 0 ] ) ;
1405
1436
bind_instead_of_map:: ResultAndThenOk :: lint ( cx, expr, arg_lists[ 0 ] ) ;
1406
1437
} ,
1407
1438
[ "or_else" , ..] => {
1439
+ lint_lazy_eval ( cx, expr, arg_lists[ 0 ] , false , "or" ) ;
1408
1440
bind_instead_of_map:: ResultOrElseErrInfo :: lint ( cx, expr, arg_lists[ 0 ] ) ;
1409
1441
} ,
1410
1442
[ "next" , "filter" ] => lint_filter_next ( cx, expr, arg_lists[ 1 ] ) ,
@@ -1448,6 +1480,9 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
1448
1480
[ "is_file" , ..] => lint_filetype_is_file ( cx, expr, arg_lists[ 0 ] ) ,
1449
1481
[ "map" , "as_ref" ] => lint_option_as_ref_deref ( cx, expr, arg_lists[ 1 ] , arg_lists[ 0 ] , false ) ,
1450
1482
[ "map" , "as_mut" ] => lint_option_as_ref_deref ( cx, expr, arg_lists[ 1 ] , arg_lists[ 0 ] , true ) ,
1483
+ [ "unwrap_or_else" , ..] => lint_lazy_eval ( cx, expr, arg_lists[ 0 ] , true , "unwrap_or" ) ,
1484
+ [ "get_or_insert_with" , ..] => lint_lazy_eval ( cx, expr, arg_lists[ 0 ] , true , "get_or_insert" ) ,
1485
+ [ "ok_or_else" , ..] => lint_lazy_eval ( cx, expr, arg_lists[ 0 ] , true , "ok_or" ) ,
1451
1486
_ => { } ,
1452
1487
}
1453
1488
@@ -2663,6 +2698,99 @@ fn lint_map_flatten<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, map
2663
2698
}
2664
2699
}
2665
2700
2701
+ /// lint use of `<fn>_else(simple closure)` for `Option`s and `Result`s that can be
2702
+ /// replaced with `<fn>(return value of simple closure)`
2703
+ fn lint_lazy_eval < ' a , ' tcx > (
2704
+ cx : & LateContext < ' a , ' tcx > ,
2705
+ expr : & ' tcx hir:: Expr < ' _ > ,
2706
+ args : & ' tcx [ hir:: Expr < ' _ > ] ,
2707
+ allow_variant_calls : bool ,
2708
+ simplify_using : & str ,
2709
+ ) {
2710
+ let is_option = is_type_diagnostic_item ( cx, cx. tables . expr_ty ( & args[ 0 ] ) , sym ! ( option_type) ) ;
2711
+ let is_result = is_type_diagnostic_item ( cx, cx. tables . expr_ty ( & args[ 0 ] ) , sym ! ( result_type) ) ;
2712
+
2713
+ if !is_option && !is_result {
2714
+ return ;
2715
+ }
2716
+
2717
+ // Return true if the expression is an accessor of any of the arguments
2718
+ fn expr_uses_argument ( expr : & hir:: Expr < ' _ > , params : & [ hir:: Param < ' _ > ] ) -> bool {
2719
+ params. iter ( ) . any ( |arg| {
2720
+ if_chain ! {
2721
+ if let hir:: PatKind :: Binding ( _, _, ident, _) = arg. pat. kind;
2722
+ if let hir:: ExprKind :: Path ( hir:: QPath :: Resolved ( _, ref path) ) = expr. kind;
2723
+ if let [ p, ..] = path. segments;
2724
+ then {
2725
+ ident. name == p. ident. name
2726
+ } else {
2727
+ false
2728
+ }
2729
+ }
2730
+ } )
2731
+ }
2732
+
2733
+ fn match_any_qpath ( path : & hir:: QPath < ' _ > , paths : & [ & [ & str ] ] ) -> bool {
2734
+ paths. iter ( ) . any ( |candidate| match_qpath ( path, candidate) )
2735
+ }
2736
+
2737
+ if let hir:: ExprKind :: Closure ( _, _, eid, _, _) = args[ 1 ] . kind {
2738
+ let body = cx. tcx . hir ( ) . body ( eid) ;
2739
+ let ex = & body. value ;
2740
+ let params = & body. params ;
2741
+
2742
+ let simplify = match ex. kind {
2743
+ // Closures returning literals can be unconditionally simplified
2744
+ hir:: ExprKind :: Lit ( _) => true ,
2745
+
2746
+ // Reading fields can be simplified if the object is not an argument of the closure
2747
+ hir:: ExprKind :: Field ( ref object, _) => !expr_uses_argument ( object, params) ,
2748
+
2749
+ // Paths can be simplified if the root is not the argument, this also covers None
2750
+ hir:: ExprKind :: Path ( _) => !expr_uses_argument ( ex, params) ,
2751
+
2752
+ // Calls to Some, Ok, Err can be considered literals if they don't derive an argument
2753
+ hir:: ExprKind :: Call ( ref func, ref args) => if_chain ! {
2754
+ if allow_variant_calls; // Disable lint when rules conflict with bind_instead_of_map
2755
+ if let hir:: ExprKind :: Path ( ref path) = func. kind;
2756
+ if match_any_qpath( path, & [ & [ "Some" ] , & [ "Ok" ] , & [ "Err" ] ] ) ;
2757
+ then {
2758
+ !args. iter( ) . any( |arg| expr_uses_argument( arg, params) )
2759
+ } else {
2760
+ false
2761
+ }
2762
+ } ,
2763
+
2764
+ // For anything more complex than the above, a closure is probably the right solution,
2765
+ // or the case is handled by an other lint
2766
+ _ => false ,
2767
+ } ;
2768
+
2769
+ if simplify {
2770
+ let msg = if is_option {
2771
+ "unnecessary closure used to substitute value for `Option::None`"
2772
+ } else {
2773
+ "unnecessary closure used to substitute value for `Result::Err`"
2774
+ } ;
2775
+
2776
+ span_lint_and_sugg (
2777
+ cx,
2778
+ UNNECESSARY_LAZY_EVALUATION ,
2779
+ expr. span ,
2780
+ msg,
2781
+ & format ! ( "Use `{}` instead" , simplify_using) ,
2782
+ format ! (
2783
+ "{0}.{1}({2})" ,
2784
+ snippet( cx, args[ 0 ] . span, ".." ) ,
2785
+ simplify_using,
2786
+ snippet( cx, ex. span, ".." ) ,
2787
+ ) ,
2788
+ Applicability :: MachineApplicable ,
2789
+ ) ;
2790
+ }
2791
+ }
2792
+ }
2793
+
2666
2794
/// lint use of `map().unwrap_or_else()` for `Option`s and `Result`s
2667
2795
fn lint_map_unwrap_or_else < ' tcx > (
2668
2796
cx : & LateContext < ' tcx > ,
0 commit comments