1
1
use crate :: utils:: sugg:: Sugg ;
2
- use crate :: utils:: { match_qpath, match_type, paths, span_lint , span_lint_and_sugg} ;
2
+ use crate :: utils:: { match_qpath, match_type, paths, span_lint_and_sugg} ;
3
3
use if_chain:: if_chain;
4
4
5
5
use rustc_errors:: Applicability ;
@@ -16,13 +16,38 @@ declare_clippy_lint! {
16
16
/// Using the dedicated function in the Option class is clearer and
17
17
/// more concise than an if let
18
18
///
19
- /// **Known problems:** None.
19
+ /// **Known problems:**
20
+ /// Using `Option::map_or` requires it to compute the or value every
21
+ /// function call, even if the value is unneeded. In some circumstances,
22
+ /// it might be better to use Option::map_or_else. For example,
23
+ /// ```rust
24
+ /// # let optional = Some(0);
25
+ /// let _ = if let Some(x) = optional {
26
+ /// x
27
+ /// } else {
28
+ /// let y = 0;
29
+ /// /* Some expensive computation or state-changing operation */
30
+ /// y
31
+ /// };
32
+ /// ```
33
+ /// would be better as
34
+ /// ```rust
35
+ /// # let optional = Some(0);
36
+ /// let _ = optional.map_or_else(|| { let y = 0; /* some expensive computation or
37
+ /// state-changing operation */ y }, |x| x);
38
+ /// ```
39
+ /// instead of the suggested
40
+ /// ```rust
41
+ /// # let optional = Some(0);
42
+ /// let _ = optional.map_or({ let y = 0; /* some expensive computation or state-changing
43
+ /// operation */ y }, |x| x);
44
+ /// ```
20
45
///
21
46
/// **Example:**
22
47
///
23
48
/// ```rust
24
- /// let var : Option<u32> = Some(5u32 );
25
- /// let _ = if let Some(foo) = var {
49
+ /// # let optional : Option<u32> = Some(0 );
50
+ /// let _ = if let Some(foo) = optional {
26
51
/// foo
27
52
/// } else {
28
53
/// 5
@@ -32,8 +57,8 @@ declare_clippy_lint! {
32
57
/// should be
33
58
///
34
59
/// ```rust
35
- /// let var : Option<u32> = Some(5u32 );
36
- /// let _ = var .map_or(5, |foo| foo);
60
+ /// # let optional : Option<u32> = Some(0 );
61
+ /// let _ = optional .map_or(5, |foo| foo);
37
62
/// ```
38
63
pub OPTION_IF_LET_ELSE ,
39
64
style,
@@ -45,57 +70,52 @@ declare_lint_pass!(OptionIfLetElse => [OPTION_IF_LET_ELSE]);
45
70
/// If this is the option if let/else thing we're detecting, then this
46
71
/// function returns Some(Option<_> compared, expression if Option is
47
72
/// Some, expression if Option is None). Otherwise, it returns None
48
- fn detect_option_if_let_else < ' a > (
49
- cx : & LateContext < ' _ , ' _ > ,
50
- expr : & ' a Expr < ' _ > ,
51
- ) -> Option < ( & ' a Expr < ' a > , & ' a Expr < ' a > , & ' a Expr < ' a > ) > {
52
- if let ExprKind :: Match ( let_body, arms, MatchSource :: IfLetDesugar { contains_else_clause : true } ) = & expr. kind {
53
- dbg ! ( "Found if let/else stmt" ) ;
54
- if arms. len ( ) == 2 {
55
- dbg ! ( "If let/else statement has two arms" ) ;
56
- if match_type ( cx, & cx. tables . expr_ty ( let_body) , & paths:: OPTION ) {
57
- dbg ! ( "rhs of if let is Option<T>" ) ;
58
- if let PatKind :: TupleStruct ( path, & [ inner_pat] , _) = & arms[ 0 ] . pat . kind {
59
- dbg ! ( "arm0 is TupleStruct" ) ;
60
- if let PatKind :: Wild | PatKind :: Binding ( ..) = & inner_pat. kind {
61
- dbg ! ( "inside of Some(x) matches anything" ) ;
62
- let ( some_body, none_body) = if match_qpath ( path, & paths:: OPTION_SOME ) {
63
- ( arms[ 0 ] . body , arms[ 1 ] . body )
64
- } else {
65
- ( arms[ 1 ] . body , arms[ 0 ] . body )
66
- } ;
67
- return Some ( ( let_body, some_body, none_body) ) ;
68
- }
69
- }
70
- }
73
+ fn detect_option_if_let_else < ' a > ( cx : & LateContext < ' _ , ' _ > , expr : & ' a Expr < ' _ > ) -> Option < ( String , String , String ) > {
74
+ if_chain ! {
75
+ if let ExprKind :: Match ( let_body, arms, MatchSource :: IfLetDesugar { contains_else_clause: true } ) = & expr. kind;
76
+ if arms. len( ) == 2 ;
77
+ if match_type( cx, & cx. tables. expr_ty( let_body) , & paths:: OPTION ) ;
78
+ if let PatKind :: TupleStruct ( path, & [ inner_pat] , _) = & arms[ 0 ] . pat. kind;
79
+ if let PatKind :: Binding ( _, _, id, _) = & inner_pat. kind;
80
+ then {
81
+ let ( mut some_body, mut none_body) = if match_qpath( path, & paths:: OPTION_SOME ) {
82
+ ( arms[ 0 ] . body, arms[ 1 ] . body)
83
+ } else {
84
+ ( arms[ 1 ] . body, arms[ 0 ] . body)
85
+ } ;
86
+ some_body = if let ExprKind :: Block ( Block { stmts: & [ ] , expr: Some ( expr) , .. } , _) = & some_body. kind {
87
+ expr
88
+ } else {
89
+ some_body
90
+ } ;
91
+ none_body = if let ExprKind :: Block ( Block { stmts: & [ ] , expr: Some ( expr) , .. } , _) = & none_body. kind {
92
+ expr
93
+ } else {
94
+ none_body
95
+ } ;
96
+ let capture_name = id. name. to_ident_string( ) ;
97
+ Some ( (
98
+ format!( "{}" , Sugg :: hir( cx, let_body, ".." ) ) ,
99
+ format!( "|{}| {}" , capture_name, Sugg :: hir( cx, some_body, ".." ) ) ,
100
+ format!( "{}" , Sugg :: hir( cx, none_body, ".." ) )
101
+ ) )
102
+ } else {
103
+ None
71
104
}
72
105
}
73
- None
74
106
}
75
107
76
108
impl < ' a , ' tcx > LateLintPass < ' a , ' tcx > for OptionIfLetElse {
77
109
fn check_expr ( & mut self , cx : & LateContext < ' a , ' tcx > , expr : & ' tcx Expr < ' _ > ) {
78
- span_lint (
79
- cx,
80
- OPTION_IF_LET_ELSE ,
81
- expr. span ,
82
- "OptionIfLetElse::check_expr was run on this span" ,
83
- ) ;
84
- return ;
85
110
if let Some ( ( option, map, else_func) ) = detect_option_if_let_else ( cx, expr) {
86
111
span_lint_and_sugg (
87
112
cx,
88
113
OPTION_IF_LET_ELSE ,
89
114
expr. span ,
90
115
"use Option::map_or_else instead of an if let/else" ,
91
116
"try" ,
92
- format ! (
93
- "{}.map_or_else({}, {})" ,
94
- Sugg :: hir( cx, option, ".." ) ,
95
- Sugg :: hir( cx, else_func, ".." ) ,
96
- Sugg :: hir( cx, map, ".." )
97
- ) ,
98
- Applicability :: MachineApplicable ,
117
+ format ! ( "{}.map_or_else({}, {})" , option, else_func, map) ,
118
+ Applicability :: MaybeIncorrect ,
99
119
) ;
100
120
}
101
121
}
0 commit comments