@@ -4,31 +4,26 @@ use crate::utils::{match_type, paths, span_lint_and_sugg};
4
4
use if_chain:: if_chain;
5
5
6
6
use rustc_errors:: Applicability ;
7
+ use rustc_hir:: def:: { DefKind , Res } ;
7
8
use rustc_hir:: intravisit:: { NestedVisitorMap , Visitor } ;
8
- use rustc_hir:: { Arm , BindingAnnotation , Block , Expr , ExprKind , MatchSource , Mutability , PatKind , UnOp } ;
9
+ use rustc_hir:: { Arm , BindingAnnotation , Block , Expr , ExprKind , MatchSource , Mutability , PatKind , Path , QPath , UnOp } ;
9
10
use rustc_lint:: { LateContext , LateLintPass } ;
10
11
use rustc_middle:: hir:: map:: Map ;
11
12
use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
12
13
13
14
declare_clippy_lint ! {
14
15
/// **What it does:**
15
16
/// Lints usage of `if let Some(v) = ... { y } else { x }` which is more
16
- /// idiomatically done with `Option::map_or` (if the else bit is a simple
17
- /// expression) or `Option::map_or_else` (if the else bit is a longer
18
- /// block ).
17
+ /// idiomatically done with `Option::map_or` (if the else bit is a pure
18
+ /// expression) or `Option::map_or_else` (if the else bit is an impure
19
+ /// expresion ).
19
20
///
20
21
/// **Why is this bad?**
21
22
/// Using the dedicated functions of the Option type is clearer and
22
23
/// more concise than an if let expression.
23
24
///
24
25
/// **Known problems:**
25
- /// This lint uses whether the block is just an expression or if it has
26
- /// more statements to decide whether to use `Option::map_or` or
27
- /// `Option::map_or_else`. If you have a single expression which calls
28
- /// an expensive function, then it would be more efficient to use
29
- /// `Option::map_or_else`, but this lint would suggest `Option::map_or`.
30
- ///
31
- /// Also, this lint uses a deliberately conservative metric for checking
26
+ /// This lint uses a deliberately conservative metric for checking
32
27
/// if the inside of either body contains breaks or continues which will
33
28
/// cause it to not suggest a fix if either block contains a loop with
34
29
/// continues or breaks contained within the loop.
@@ -92,13 +87,15 @@ struct OptionIfLetElseOccurence {
92
87
struct ReturnBreakContinueMacroVisitor {
93
88
seen_return_break_continue : bool ,
94
89
}
90
+
95
91
impl ReturnBreakContinueMacroVisitor {
96
92
fn new ( ) -> ReturnBreakContinueMacroVisitor {
97
93
ReturnBreakContinueMacroVisitor {
98
94
seen_return_break_continue : false ,
99
95
}
100
96
}
101
97
}
98
+
102
99
impl < ' tcx > Visitor < ' tcx > for ReturnBreakContinueMacroVisitor {
103
100
type Map = Map < ' tcx > ;
104
101
fn nested_visit_map ( & mut self ) -> NestedVisitorMap < Self :: Map > {
@@ -157,7 +154,7 @@ fn extract_body_from_arm<'a>(arm: &'a Arm<'a>) -> Option<&'a Expr<'a>> {
157
154
}
158
155
159
156
/// If this is the else body of an if/else expression, then we need to wrap
160
- /// it in curcly braces. Otherwise, we don't.
157
+ /// it in curly braces. Otherwise, we don't.
161
158
fn should_wrap_in_braces ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > ) -> bool {
162
159
utils:: get_enclosing_block ( cx, expr. hir_id ) . map_or ( false , |parent| {
163
160
if let Some ( Expr {
@@ -196,6 +193,35 @@ fn format_option_in_sugg(cx: &LateContext<'_>, cond_expr: &Expr<'_>, as_ref: boo
196
193
)
197
194
}
198
195
196
+ /// Is the expr pure (is it free from side-effects)?
197
+ /// If yes, we can use `map_or`, else we must use `map_or_else` to preserve semantic equivalence.
198
+ /// This implementation can be improved and identify more pure patterns.
199
+ fn is_pure ( expr : & Expr < ' _ > ) -> bool {
200
+ match expr. kind {
201
+ ExprKind :: Lit ( ..) | ExprKind :: Path ( ..) => true ,
202
+ ExprKind :: AddrOf ( _, _, addr_of_expr) => is_pure ( addr_of_expr) ,
203
+ ExprKind :: Tup ( tup_exprs) => tup_exprs. iter ( ) . all ( |expr| is_pure ( expr) ) ,
204
+ ExprKind :: Struct ( _, fields, expr) => {
205
+ fields. iter ( ) . all ( |f| is_pure ( f. expr ) ) && expr. map_or ( true , |e| is_pure ( e) )
206
+ } ,
207
+ ExprKind :: Call (
208
+ & Expr {
209
+ kind :
210
+ ExprKind :: Path ( QPath :: Resolved (
211
+ _,
212
+ Path {
213
+ res : Res :: Def ( DefKind :: Ctor ( ..) | DefKind :: Variant , ..) ,
214
+ ..
215
+ } ,
216
+ ) ) ,
217
+ ..
218
+ } ,
219
+ args,
220
+ ) => args. iter ( ) . all ( |expr| is_pure ( expr) ) ,
221
+ _ => false ,
222
+ }
223
+ }
224
+
199
225
/// If this expression is the option if let/else construct we're detecting, then
200
226
/// this function returns an `OptionIfLetElseOccurence` struct with details if
201
227
/// this construct is found, or None if this construct is not found.
@@ -214,10 +240,7 @@ fn detect_option_if_let_else(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<Op
214
240
let capture_mut = if bind_annotation == & BindingAnnotation :: Mutable { "mut " } else { "" } ;
215
241
let some_body = extract_body_from_arm( & arms[ 0 ] ) ?;
216
242
let none_body = extract_body_from_arm( & arms[ 1 ] ) ?;
217
- let method_sugg = match & none_body. kind {
218
- ExprKind :: Block ( ..) => "map_or_else" ,
219
- _ => "map_or" ,
220
- } ;
243
+ let method_sugg = if is_pure( none_body) { "map_or" } else { "map_or_else" } ;
221
244
let capture_name = id. name. to_ident_string( ) ;
222
245
let wrap_braces = should_wrap_in_braces( cx, expr) ;
223
246
let ( as_ref, as_mut) = match & cond_expr. kind {
0 commit comments