@@ -7,15 +7,18 @@ use clippy_utils::{
7
7
} ;
8
8
use if_chain:: if_chain;
9
9
use rustc_errors:: Applicability ;
10
- use rustc_hir:: LangItem :: OptionSome ;
11
- use rustc_hir:: { def:: Res , BindingAnnotation , Expr , ExprKind , Mutability , PatKind , Path , QPath , UnOp } ;
10
+ use rustc_hir:: LangItem :: { OptionNone , OptionSome } ;
11
+ use rustc_hir:: {
12
+ def:: Res , Arm , BindingAnnotation , Expr , ExprKind , MatchSource , Mutability , Pat , PatKind , Path , QPath , UnOp ,
13
+ } ;
12
14
use rustc_lint:: { LateContext , LateLintPass } ;
13
15
use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
14
16
use rustc_span:: sym;
15
17
16
18
declare_clippy_lint ! {
17
19
/// ### What it does
18
- /// Lints usage of `if let Some(v) = ... { y } else { x }` which is more
20
+ /// Lints usage of `if let Some(v) = ... { y } else { x }` and
21
+ /// `match .. { Some(v) => y, None/_ => x }` which are more
19
22
/// idiomatically done with `Option::map_or` (if the else bit is a pure
20
23
/// expression) or `Option::map_or_else` (if the else bit is an impure
21
24
/// expression).
@@ -39,6 +42,10 @@ declare_clippy_lint! {
39
42
/// } else {
40
43
/// 5
41
44
/// };
45
+ /// let _ = match optional {
46
+ /// Some(val) => val + 1,
47
+ /// None => 5
48
+ /// };
42
49
/// let _ = if let Some(foo) = optional {
43
50
/// foo
44
51
/// } else {
@@ -53,6 +60,7 @@ declare_clippy_lint! {
53
60
/// # let optional: Option<u32> = Some(0);
54
61
/// # fn do_complicated_function() -> u32 { 5 };
55
62
/// let _ = optional.map_or(5, |foo| foo);
63
+ /// let _ = optional.map_or(5, |val| val + 1);
56
64
/// let _ = optional.map_or_else(||{
57
65
/// let y = do_complicated_function();
58
66
/// y*y
@@ -76,9 +84,21 @@ fn is_result_ok(cx: &LateContext<'_>, expr: &'_ Expr<'_>) -> bool {
76
84
}
77
85
}
78
86
79
- /// A struct containing information about occurrences of the
80
- /// `if let Some(..) = .. else` construct that this lint detects.
81
- struct OptionIfLetElseOccurence {
87
+ /// A struct containing information about occurrences of construct that this lint detects
88
+ ///
89
+ /// Such as:
90
+ ///
91
+ /// ```ignore
92
+ /// if let Some(..) = {..} else {..}
93
+ /// ```
94
+ /// or
95
+ /// ```ignore
96
+ /// match x {
97
+ /// Some(..) => {..},
98
+ /// None/_ => {..}
99
+ /// }
100
+ /// ```
101
+ struct OptionOccurence {
82
102
option : String ,
83
103
method_sugg : String ,
84
104
some_expr : String ,
@@ -99,43 +119,39 @@ fn format_option_in_sugg(cx: &LateContext<'_>, cond_expr: &Expr<'_>, as_ref: boo
99
119
)
100
120
}
101
121
102
- /// If this expression is the option if let/else construct we're detecting, then
103
- /// this function returns an `OptionIfLetElseOccurence` struct with details if
104
- /// this construct is found, or None if this construct is not found.
105
- fn detect_option_if_let_else < ' tcx > ( cx : & LateContext < ' tcx > , expr : & Expr < ' tcx > ) -> Option < OptionIfLetElseOccurence > {
122
+ fn try_get_option_occurence < ' tcx > (
123
+ cx : & LateContext < ' tcx > ,
124
+ pat : & Pat < ' tcx > ,
125
+ expr : & Expr < ' _ > ,
126
+ if_then : & ' tcx Expr < ' _ > ,
127
+ if_else : & ' tcx Expr < ' _ > ,
128
+ ) -> Option < OptionOccurence > {
129
+ let cond_expr = match expr. kind {
130
+ ExprKind :: Unary ( UnOp :: Deref , inner_expr) | ExprKind :: AddrOf ( _, _, inner_expr) => inner_expr,
131
+ _ => expr,
132
+ } ;
106
133
if_chain ! {
107
- if !expr. span. from_expansion( ) ; // Don't lint macros, because it behaves weirdly
108
- if !in_constant( cx, expr. hir_id) ;
109
- if let Some ( higher:: IfLet { let_pat, let_expr, if_then, if_else: Some ( if_else) } )
110
- = higher:: IfLet :: hir( cx, expr) ;
111
- if !is_else_clause( cx. tcx, expr) ;
112
- if !is_result_ok( cx, let_expr) ; // Don't lint on Result::ok because a different lint does it already
113
- if let PatKind :: TupleStruct ( struct_qpath, [ inner_pat] , _) = & let_pat. kind;
114
- if is_lang_ctor( cx, struct_qpath, OptionSome ) ;
115
- if let PatKind :: Binding ( bind_annotation, _, id, None ) = & inner_pat. kind;
134
+ if !is_result_ok( cx, cond_expr) ; // Don't lint on Result::ok because a different lint does it already
135
+ let inner_pat = try_get_inner_pat( cx, pat) ?;
136
+ if let PatKind :: Binding ( bind_annotation, _, id, None ) = inner_pat. kind;
116
137
if let Some ( some_captures) = can_move_expr_to_closure( cx, if_then) ;
117
138
if let Some ( none_captures) = can_move_expr_to_closure( cx, if_else) ;
118
139
if some_captures
119
140
. iter( )
120
141
. filter_map( |( id, & c) | none_captures. get( id) . map( |& c2| ( c, c2) ) )
121
142
. all( |( x, y) | x. is_imm_ref( ) && y. is_imm_ref( ) ) ;
122
-
123
143
then {
124
- let capture_mut = if bind_annotation == & BindingAnnotation :: Mutable { "mut " } else { "" } ;
144
+ let capture_mut = if bind_annotation == BindingAnnotation :: Mutable { "mut " } else { "" } ;
125
145
let some_body = peel_blocks( if_then) ;
126
146
let none_body = peel_blocks( if_else) ;
127
147
let method_sugg = if eager_or_lazy:: switch_to_eager_eval( cx, none_body) { "map_or" } else { "map_or_else" } ;
128
148
let capture_name = id. name. to_ident_string( ) ;
129
- let ( as_ref, as_mut) = match & let_expr . kind {
149
+ let ( as_ref, as_mut) = match & expr . kind {
130
150
ExprKind :: AddrOf ( _, Mutability :: Not , _) => ( true , false ) ,
131
151
ExprKind :: AddrOf ( _, Mutability :: Mut , _) => ( false , true ) ,
132
- _ => ( bind_annotation == & BindingAnnotation :: Ref , bind_annotation == & BindingAnnotation :: RefMut ) ,
133
- } ;
134
- let cond_expr = match let_expr. kind {
135
- // Pointer dereferencing happens automatically, so we can omit it in the suggestion
136
- ExprKind :: Unary ( UnOp :: Deref , expr) | ExprKind :: AddrOf ( _, _, expr) => expr,
137
- _ => let_expr,
152
+ _ => ( bind_annotation == BindingAnnotation :: Ref , bind_annotation == BindingAnnotation :: RefMut ) ,
138
153
} ;
154
+
139
155
// Check if captures the closure will need conflict with borrows made in the scrutinee.
140
156
// TODO: check all the references made in the scrutinee expression. This will require interacting
141
157
// with the borrow checker. Currently only `<local>[.<field>]*` is checked for.
@@ -154,33 +170,99 @@ fn detect_option_if_let_else<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) ->
154
170
}
155
171
}
156
172
}
157
- Some ( OptionIfLetElseOccurence {
173
+
174
+ return Some ( OptionOccurence {
158
175
option: format_option_in_sugg( cx, cond_expr, as_ref, as_mut) ,
159
176
method_sugg: method_sugg. to_string( ) ,
160
177
some_expr: format!( "|{}{}| {}" , capture_mut, capture_name, Sugg :: hir_with_macro_callsite( cx, some_body, ".." ) ) ,
161
178
none_expr: format!( "{}{}" , if method_sugg == "map_or" { "" } else { "|| " } , Sugg :: hir_with_macro_callsite( cx, none_body, ".." ) ) ,
162
- } )
179
+ } ) ;
180
+ }
181
+ }
182
+
183
+ None
184
+ }
185
+
186
+ fn try_get_inner_pat < ' tcx > ( cx : & LateContext < ' tcx > , pat : & Pat < ' tcx > ) -> Option < & ' tcx Pat < ' tcx > > {
187
+ if let PatKind :: TupleStruct ( ref qpath, [ inner_pat] , ..) = pat. kind {
188
+ if is_lang_ctor ( cx, qpath, OptionSome ) {
189
+ return Some ( inner_pat) ;
190
+ }
191
+ }
192
+ None
193
+ }
194
+
195
+ /// If this expression is the option if let/else construct we're detecting, then
196
+ /// this function returns an `OptionOccurence` struct with details if
197
+ /// this construct is found, or None if this construct is not found.
198
+ fn detect_option_if_let_else < ' tcx > ( cx : & LateContext < ' tcx > , expr : & Expr < ' tcx > ) -> Option < OptionOccurence > {
199
+ if let Some ( higher:: IfLet {
200
+ let_pat,
201
+ let_expr,
202
+ if_then,
203
+ if_else : Some ( if_else) ,
204
+ } ) = higher:: IfLet :: hir ( cx, expr)
205
+ {
206
+ if !is_else_clause ( cx. tcx , expr) {
207
+ return try_get_option_occurence ( cx, let_pat, let_expr, if_then, if_else) ;
208
+ }
209
+ }
210
+ None
211
+ }
212
+
213
+ fn detect_option_match < ' tcx > ( cx : & LateContext < ' tcx > , expr : & Expr < ' tcx > ) -> Option < OptionOccurence > {
214
+ if let ExprKind :: Match ( ex, arms, MatchSource :: Normal ) = expr. kind {
215
+ if let Some ( ( let_pat, if_then, if_else) ) = try_convert_match ( cx, arms) {
216
+ return try_get_option_occurence ( cx, let_pat, ex, if_then, if_else) ;
217
+ }
218
+ }
219
+ None
220
+ }
221
+
222
+ fn try_convert_match < ' tcx > (
223
+ cx : & LateContext < ' tcx > ,
224
+ arms : & [ Arm < ' tcx > ] ,
225
+ ) -> Option < ( & ' tcx Pat < ' tcx > , & ' tcx Expr < ' tcx > , & ' tcx Expr < ' tcx > ) > {
226
+ if arms. len ( ) == 2 {
227
+ return if is_none_arm ( cx, & arms[ 1 ] ) {
228
+ Some ( ( arms[ 0 ] . pat , arms[ 0 ] . body , arms[ 1 ] . body ) )
229
+ } else if is_none_arm ( cx, & arms[ 0 ] ) {
230
+ Some ( ( arms[ 1 ] . pat , arms[ 1 ] . body , arms[ 0 ] . body ) )
163
231
} else {
164
232
None
165
- }
233
+ } ;
234
+ }
235
+ None
236
+ }
237
+
238
+ fn is_none_arm ( cx : & LateContext < ' _ > , arm : & Arm < ' _ > ) -> bool {
239
+ match arm. pat . kind {
240
+ PatKind :: Path ( ref qpath) => is_lang_ctor ( cx, qpath, OptionNone ) ,
241
+ PatKind :: Wild => true ,
242
+ _ => false ,
166
243
}
167
244
}
168
245
169
246
impl < ' tcx > LateLintPass < ' tcx > for OptionIfLetElse {
170
247
fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & Expr < ' tcx > ) {
171
- if let Some ( detection) = detect_option_if_let_else ( cx, expr) {
172
- span_lint_and_sugg (
173
- cx,
174
- OPTION_IF_LET_ELSE ,
175
- expr. span ,
176
- format ! ( "use Option::{} instead of an if let/else" , detection. method_sugg) . as_str ( ) ,
177
- "try" ,
178
- format ! (
179
- "{}.{}({}, {})" ,
180
- detection. option, detection. method_sugg, detection. none_expr, detection. some_expr,
181
- ) ,
182
- Applicability :: MaybeIncorrect ,
183
- ) ;
248
+ // Don't lint macros, because it behaves weirdly
249
+ // and don't lint constant as well
250
+ if !expr. span . from_expansion ( ) && !in_constant ( cx, expr. hir_id ) {
251
+ let detection = detect_option_if_let_else ( cx, expr) . or_else ( || detect_option_match ( cx, expr) ) ;
252
+ if let Some ( det) = detection {
253
+ span_lint_and_sugg (
254
+ cx,
255
+ OPTION_IF_LET_ELSE ,
256
+ expr. span ,
257
+ format ! ( "use Option::{} instead of an if let/else" , det. method_sugg) . as_str ( ) ,
258
+ "try" ,
259
+ format ! (
260
+ "{}.{}({}, {})" ,
261
+ det. option, det. method_sugg, det. none_expr, det. some_expr
262
+ ) ,
263
+ Applicability :: MaybeIncorrect ,
264
+ ) ;
265
+ }
184
266
}
185
267
}
186
268
}
0 commit comments