1
1
use crate :: utils:: sugg:: Sugg ;
2
- use crate :: utils:: { match_qpath , match_type, paths, span_lint_and_sugg} ;
2
+ use crate :: utils:: { match_type, paths, span_lint_and_sugg} ;
3
3
use if_chain:: if_chain;
4
4
5
5
use rustc_errors:: Applicability ;
@@ -10,40 +10,20 @@ use rustc_session::{declare_lint_pass, declare_tool_lint};
10
10
declare_clippy_lint ! {
11
11
/// **What it does:**
12
12
/// Lints usage of `if let Some(v) = ... { y } else { x }` which is more
13
- /// idiomatically done with `Option::map_or` or `Option::map_or_else`.
13
+ /// idiomatically done with `Option::map_or` (if the else bit is a simple
14
+ /// expression) or `Option::map_or_else` (if the else bit is a longer
15
+ /// block).
14
16
///
15
17
/// **Why is this bad?**
16
18
/// Using the dedicated functions of the Option type is clearer and
17
19
/// more concise than an if let expression.
18
20
///
19
21
/// **Known problems:**
20
- /// Using `Option::map_or` (as the lint currently suggests) requires
21
- /// it to compute the else value every function call, even if the
22
- /// value is unneeded. It might be better to use Option::map_or_else
23
- /// if the code is either very expensive or if it changes some state
24
- /// (i.e. mutates a value). For example,
25
- /// ```rust
26
- /// # let optional = Some(0);
27
- /// let _ = if let Some(x) = optional {
28
- /// x
29
- /// } else {
30
- /// let y = 0;
31
- /// /* Some expensive computation or state-changing operation */
32
- /// y
33
- /// };
34
- /// ```
35
- /// would be better as
36
- /// ```rust
37
- /// # let optional = Some(0);
38
- /// let _ = optional.map_or_else(|| { let y = 0; /* some expensive computation or
39
- /// state-changing operation */ y }, |x| x);
40
- /// ```
41
- /// instead of the suggested
42
- /// ```rust
43
- /// # let optional = Some(0);
44
- /// let _ = optional.map_or({ let y = 0; /* some expensive computation or state-changing
45
- /// operation */ y }, |x| x);
46
- /// ```
22
+ /// This lint uses whether the block is just an expression or if it has
23
+ /// more statements to decide whether to use `Option::map_or` or
24
+ /// `Option::map_or_else`. If you have a single expression which calls
25
+ /// an expensive function, then it would be more efficient to use
26
+ /// `Option::map_or_else`, but this lint would suggest `Option::map_or`.
47
27
///
48
28
/// **Example:**
49
29
///
@@ -54,13 +34,23 @@ declare_clippy_lint! {
54
34
/// } else {
55
35
/// 5
56
36
/// };
37
+ /// let _ = if let Some(foo) = optional {
38
+ /// foo
39
+ /// } else {
40
+ /// let y = do_complicated_function();
41
+ /// y*y
42
+ /// };
57
43
/// ```
58
44
///
59
45
/// should be
60
46
///
61
47
/// ```rust
62
48
/// # let optional: Option<u32> = Some(0);
63
49
/// let _ = optional.map_or(5, |foo| foo);
50
+ /// let _ = optional.map_or_else(||{
51
+ /// let y = do_complicated_function;
52
+ /// y*y
53
+ /// }, |foo| foo);
64
54
/// ```
65
55
pub OPTION_IF_LET_ELSE ,
66
56
style,
@@ -83,29 +73,32 @@ fn is_result_ok(cx: &LateContext<'_, '_>, expr: &'_ Expr<'_>) -> bool {
83
73
}
84
74
}
85
75
76
+ /// A struct containing information about occurences of the
77
+ /// `if let Some(..) = .. else` construct that this lint detects.
78
+ struct OptionIfLetElseOccurence {
79
+ option : String ,
80
+ method_sugg : String ,
81
+ some_expr : String ,
82
+ none_expr : String ,
83
+ }
84
+
86
85
/// If this expression is the option if let/else construct we're detecting, then
87
- /// this function returns Some(Option<_> tested against, the method to call on
88
- /// the object (map_or or map_or_else), expression if Option is Some,
89
- /// expression if Option is None). Otherwise, it returns None.
86
+ /// this function returns an OptionIfLetElseOccurence struct with details if
87
+ /// this construct is found, or None if this construct is not found.
90
88
fn detect_option_if_let_else < ' a > (
91
89
cx : & LateContext < ' _ , ' _ > ,
92
90
expr : & ' a Expr < ' _ > ,
93
- ) -> Option < ( String , String , String , String ) > {
91
+ ) -> Option < OptionIfLetElseOccurence > { // (String, String, String, String)> {
94
92
if_chain ! {
95
93
if let ExprKind :: Match ( let_body, arms, MatchSource :: IfLetDesugar { contains_else_clause: true } ) = & expr. kind;
96
94
if arms. len( ) == 2 ;
97
95
if match_type( cx, & cx. tables. expr_ty( let_body) , & paths:: OPTION ) ;
98
- if !is_result_ok( cx, let_body) ;
99
- if let PatKind :: TupleStruct ( path , & [ inner_pat] , _) = & arms[ 0 ] . pat. kind;
96
+ if !is_result_ok( cx, let_body) ; // Don't lint on Result::ok because a different lint does it already
97
+ if let PatKind :: TupleStruct ( _ , & [ inner_pat] , _) = & arms[ 0 ] . pat. kind;
100
98
if let PatKind :: Binding ( _, _, id, _) = & inner_pat. kind;
101
99
then {
102
- let ( some_body, none_body) = if match_qpath( path, & paths:: OPTION_SOME ) {
103
- ( arms[ 0 ] . body, arms[ 1 ] . body)
104
- } else {
105
- ( arms[ 1 ] . body, arms[ 0 ] . body)
106
- } ;
107
100
let some_body = if let ExprKind :: Block ( Block { stmts: statements, expr: Some ( expr) , .. } , _)
108
- = & some_body . kind {
101
+ = & arms [ 0 ] . body . kind {
109
102
if let & [ ] = & statements {
110
103
expr
111
104
} else {
@@ -120,13 +113,13 @@ fn detect_option_if_let_else<'a>(
120
113
} ) {
121
114
return None ;
122
115
}
123
- some_body
116
+ & arms [ 0 ] . body
124
117
}
125
118
} else {
126
119
return None ;
127
120
} ;
128
121
let ( none_body, method_sugg) = if let ExprKind :: Block ( Block { stmts: statements, expr: Some ( expr) , .. } , _)
129
- = & none_body . kind {
122
+ = & arms [ 1 ] . body . kind {
130
123
if let & [ ] = & statements {
131
124
( expr, "map_or" )
132
125
} else {
@@ -140,18 +133,18 @@ fn detect_option_if_let_else<'a>(
140
133
} ) {
141
134
return None ;
142
135
}
143
- ( & none_body , "map_or_else" )
136
+ ( & arms [ 1 ] . body , "map_or_else" )
144
137
}
145
138
} else {
146
139
return None ;
147
140
} ;
148
141
let capture_name = id. name. to_ident_string( ) ;
149
- Some ( (
150
- format!( "{}" , Sugg :: hir( cx, let_body, ".." ) ) ,
151
- format!( "{}" , method_sugg) ,
152
- format!( "|{}| {}" , capture_name, Sugg :: hir( cx, some_body, ".." ) ) ,
153
- format!( "{}{}" , if method_sugg == "map_or" { "" } else { "||" } , Sugg :: hir( cx, none_body, ".." ) )
154
- ) )
142
+ Some ( OptionIfLetElseOccurence {
143
+ option : format!( "{}" , Sugg :: hir( cx, let_body, ".." ) ) ,
144
+ method_sugg : format!( "{}" , method_sugg) ,
145
+ some_expr : format!( "|{}| {}" , capture_name, Sugg :: hir( cx, some_body, ".." ) ) ,
146
+ none_expr : format!( "{}{}" , if method_sugg == "map_or" { "" } else { "||" } , Sugg :: hir( cx, none_body, ".." ) )
147
+ } )
155
148
} else {
156
149
None
157
150
}
@@ -160,14 +153,14 @@ fn detect_option_if_let_else<'a>(
160
153
161
154
impl < ' a , ' tcx > LateLintPass < ' a , ' tcx > for OptionIfLetElse {
162
155
fn check_expr ( & mut self , cx : & LateContext < ' a , ' tcx > , expr : & ' tcx Expr < ' _ > ) {
163
- if let Some ( ( option , method , map , else_func ) ) = detect_option_if_let_else ( cx, expr) {
156
+ if let Some ( detection ) = detect_option_if_let_else ( cx, expr) {
164
157
span_lint_and_sugg (
165
158
cx,
166
159
OPTION_IF_LET_ELSE ,
167
160
expr. span ,
168
- format ! ( "use Option::{} instead of an if let/else" , method ) . as_str ( ) ,
161
+ format ! ( "use Option::{} instead of an if let/else" , detection . method_sugg ) . as_str ( ) ,
169
162
"try" ,
170
- format ! ( "{}.{}({}, {})" , option, method , else_func , map ) ,
163
+ format ! ( "{}.{}({}, {})" , detection . option, detection . method_sugg , detection . none_expr , detection . some_expr ) ,
171
164
Applicability :: MachineApplicable ,
172
165
) ;
173
166
}
0 commit comments