11use 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} ;
33use if_chain:: if_chain;
44
55use rustc_errors:: Applicability ;
@@ -10,40 +10,20 @@ use rustc_session::{declare_lint_pass, declare_tool_lint};
1010declare_clippy_lint ! {
1111 /// **What it does:**
1212 /// 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).
1416 ///
1517 /// **Why is this bad?**
1618 /// Using the dedicated functions of the Option type is clearer and
1719 /// more concise than an if let expression.
1820 ///
1921 /// **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`.
4727 ///
4828 /// **Example:**
4929 ///
@@ -54,13 +34,23 @@ declare_clippy_lint! {
5434 /// } else {
5535 /// 5
5636 /// };
37+ /// let _ = if let Some(foo) = optional {
38+ /// foo
39+ /// } else {
40+ /// let y = do_complicated_function();
41+ /// y*y
42+ /// };
5743 /// ```
5844 ///
5945 /// should be
6046 ///
6147 /// ```rust
6248 /// # let optional: Option<u32> = Some(0);
6349 /// 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);
6454 /// ```
6555 pub OPTION_IF_LET_ELSE ,
6656 style,
@@ -83,29 +73,32 @@ fn is_result_ok(cx: &LateContext<'_, '_>, expr: &'_ Expr<'_>) -> bool {
8373 }
8474}
8575
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+
8685/// 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.
9088fn detect_option_if_let_else < ' a > (
9189 cx : & LateContext < ' _ , ' _ > ,
9290 expr : & ' a Expr < ' _ > ,
93- ) -> Option < ( String , String , String , String ) > {
91+ ) -> Option < OptionIfLetElseOccurence > { // (String, String, String, String)> {
9492 if_chain ! {
9593 if let ExprKind :: Match ( let_body, arms, MatchSource :: IfLetDesugar { contains_else_clause: true } ) = & expr. kind;
9694 if arms. len( ) == 2 ;
9795 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;
10098 if let PatKind :: Binding ( _, _, id, _) = & inner_pat. kind;
10199 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- } ;
107100 let some_body = if let ExprKind :: Block ( Block { stmts: statements, expr: Some ( expr) , .. } , _)
108- = & some_body . kind {
101+ = & arms [ 0 ] . body . kind {
109102 if let & [ ] = & statements {
110103 expr
111104 } else {
@@ -120,13 +113,13 @@ fn detect_option_if_let_else<'a>(
120113 } ) {
121114 return None ;
122115 }
123- some_body
116+ & arms [ 0 ] . body
124117 }
125118 } else {
126119 return None ;
127120 } ;
128121 let ( none_body, method_sugg) = if let ExprKind :: Block ( Block { stmts: statements, expr: Some ( expr) , .. } , _)
129- = & none_body . kind {
122+ = & arms [ 1 ] . body . kind {
130123 if let & [ ] = & statements {
131124 ( expr, "map_or" )
132125 } else {
@@ -140,18 +133,18 @@ fn detect_option_if_let_else<'a>(
140133 } ) {
141134 return None ;
142135 }
143- ( & none_body , "map_or_else" )
136+ ( & arms [ 1 ] . body , "map_or_else" )
144137 }
145138 } else {
146139 return None ;
147140 } ;
148141 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+ } )
155148 } else {
156149 None
157150 }
@@ -160,14 +153,14 @@ fn detect_option_if_let_else<'a>(
160153
161154impl < ' a , ' tcx > LateLintPass < ' a , ' tcx > for OptionIfLetElse {
162155 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) {
164157 span_lint_and_sugg (
165158 cx,
166159 OPTION_IF_LET_ELSE ,
167160 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 ( ) ,
169162 "try" ,
170- format ! ( "{}.{}({}, {})" , option, method , else_func , map ) ,
163+ format ! ( "{}.{}({}, {})" , detection . option, detection . method_sugg , detection . none_expr , detection . some_expr ) ,
171164 Applicability :: MachineApplicable ,
172165 ) ;
173166 }
0 commit comments