@@ -5,7 +5,7 @@ use if_chain::if_chain;
55
66use rustc_errors:: Applicability ;
77use rustc_hir:: intravisit:: { NestedVisitorMap , Visitor } ;
8- use rustc_hir:: * ;
8+ use rustc_hir:: { Arm , BindingAnnotation , Block , Expr , ExprKind , MatchSource , Mutability , PatKind , UnOp } ;
99use rustc_lint:: { LateContext , LateLintPass } ;
1010use rustc_middle:: hir:: map:: Map ;
1111use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
@@ -69,17 +69,12 @@ declare_clippy_lint! {
6969
7070declare_lint_pass ! ( OptionIfLetElse => [ OPTION_IF_LET_ELSE ] ) ;
7171
72- /// Returns true iff the given expression is the result of calling Result::ok
72+ /// Returns true iff the given expression is the result of calling ` Result::ok`
7373fn is_result_ok ( cx : & LateContext < ' _ , ' _ > , expr : & ' _ Expr < ' _ > ) -> bool {
74- if_chain ! {
75- if let ExprKind :: MethodCall ( ref path, _, & [ ref receiver] ) = & expr. kind;
76- if path. ident. name. to_ident_string( ) == "ok" ;
77- if match_type( cx, & cx. tables. expr_ty( & receiver) , & paths:: RESULT ) ;
78- then {
79- true
80- } else {
81- false
82- }
74+ if let ExprKind :: MethodCall ( ref path, _, & [ ref receiver] ) = & expr. kind {
75+ path. ident . name . to_ident_string ( ) == "ok" && match_type ( cx, & cx. tables . expr_ty ( & receiver) , & paths:: RESULT )
76+ } else {
77+ false
8378 }
8479}
8580
@@ -136,82 +131,129 @@ fn contains_return_break_continue<'tcx>(expression: &'tcx Expr<'tcx>) -> bool {
136131 recursive_visitor. seen_return_break_continue
137132}
138133
134+ /// Extracts the body of a given arm. If the arm contains only an expression,
135+ /// then it returns the expression. Otherwise, it returns the entire block
136+ fn extract_body_from_arm < ' a > ( arm : & ' a Arm < ' a > ) -> Option < & ' a Expr < ' a > > {
137+ if let ExprKind :: Block (
138+ Block {
139+ stmts : statements,
140+ expr : Some ( expr) ,
141+ ..
142+ } ,
143+ _,
144+ ) = & arm. body . kind
145+ {
146+ if let [ ] = statements {
147+ Some ( & expr)
148+ } else {
149+ Some ( & arm. body )
150+ }
151+ } else {
152+ None
153+ }
154+ }
155+
156+ /// If this is the else body of an if/else expression, then we need to wrap
157+ /// it in curcly braces. Otherwise, we don't.
158+ fn should_wrap_in_braces ( cx : & LateContext < ' _ , ' _ > , expr : & Expr < ' _ > ) -> bool {
159+ utils:: get_enclosing_block ( cx, expr. hir_id ) . map_or ( false , |parent| {
160+ if let Some ( Expr {
161+ kind :
162+ ExprKind :: Match (
163+ _,
164+ arms,
165+ MatchSource :: IfDesugar {
166+ contains_else_clause : true ,
167+ }
168+ | MatchSource :: IfLetDesugar {
169+ contains_else_clause : true ,
170+ } ,
171+ ) ,
172+ ..
173+ } ) = parent. expr
174+ {
175+ expr. hir_id == arms[ 1 ] . body . hir_id
176+ } else {
177+ false
178+ }
179+ } )
180+ }
181+
182+ fn format_option_in_sugg (
183+ cx : & LateContext < ' _ , ' _ > ,
184+ cond_expr : & Expr < ' _ > ,
185+ parens_around_option : bool ,
186+ as_ref : bool ,
187+ as_mut : bool ,
188+ ) -> String {
189+ format ! (
190+ "{}{}{}{}" ,
191+ if parens_around_option { "(" } else { "" } ,
192+ Sugg :: hir( cx, cond_expr, ".." ) ,
193+ if parens_around_option { ")" } else { "" } ,
194+ if as_mut {
195+ ".as_mut()"
196+ } else if as_ref {
197+ ".as_ref()"
198+ } else {
199+ ""
200+ }
201+ )
202+ }
203+
139204/// If this expression is the option if let/else construct we're detecting, then
140- /// this function returns an OptionIfLetElseOccurence struct with details if
205+ /// this function returns an ` OptionIfLetElseOccurence` struct with details if
141206/// this construct is found, or None if this construct is not found.
142207fn detect_option_if_let_else < ' a > ( cx : & LateContext < ' _ , ' a > , expr : & ' a Expr < ' a > ) -> Option < OptionIfLetElseOccurence > {
143208 if_chain ! {
144209 if !utils:: in_macro( expr. span) ; // Don't lint macros, because it behaves weirdly
145- if let ExprKind :: Match ( let_body , arms, MatchSource :: IfLetDesugar { contains_else_clause: true } ) = & expr. kind;
210+ if let ExprKind :: Match ( cond_expr , arms, MatchSource :: IfLetDesugar { contains_else_clause: true } ) = & expr. kind;
146211 if arms. len( ) == 2 ;
147- // if type_is_option(cx, &cx.tables.expr_ty(let_body).kind);
148- if !is_result_ok( cx, let_body) ; // Don't lint on Result::ok because a different lint does it already
212+ if !is_result_ok( cx, cond_expr) ; // Don't lint on Result::ok because a different lint does it already
149213 if let PatKind :: TupleStruct ( struct_qpath, & [ inner_pat] , _) = & arms[ 0 ] . pat. kind;
150214 if utils:: match_qpath( struct_qpath, & paths:: OPTION_SOME ) ;
151215 if let PatKind :: Binding ( bind_annotation, _, id, _) = & inner_pat. kind;
152216 if !contains_return_break_continue( arms[ 0 ] . body) ;
153217 if !contains_return_break_continue( arms[ 1 ] . body) ;
154218 then {
155- let ( capture_mut, capture_ref, capture_ref_mut) = match bind_annotation {
156- BindingAnnotation :: Unannotated => ( false , false , false ) ,
157- BindingAnnotation :: Mutable => ( true , false , false ) ,
158- BindingAnnotation :: Ref => ( false , true , false ) ,
159- BindingAnnotation :: RefMut => ( false , false , true ) ,
160- } ;
161- let some_body = if let ExprKind :: Block ( Block { stmts: statements, expr: Some ( expr) , .. } , _)
162- = & arms[ 0 ] . body. kind {
163- if let & [ ] = & statements {
164- expr
165- } else {
166- & arms[ 0 ] . body
167- }
168- } else {
169- return None ;
170- } ;
171- let ( none_body, method_sugg) = if let ExprKind :: Block ( Block { stmts: statements, expr: Some ( expr) , .. } , _)
172- = & arms[ 1 ] . body. kind {
173- if let & [ ] = & statements {
174- ( expr, "map_or" )
175- } else {
176- ( & arms[ 1 ] . body, "map_or_else" )
177- }
178- } else {
179- return None ;
219+ let capture_mut = if bind_annotation == & BindingAnnotation :: Mutable { "mut " } else { "" } ;
220+ let some_body = extract_body_from_arm( & arms[ 0 ] ) ?;
221+ let none_body = extract_body_from_arm( & arms[ 1 ] ) ?;
222+ let method_sugg = match & none_body. kind {
223+ ExprKind :: Block ( ..) => "map_or_else" ,
224+ _ => "map_or" ,
180225 } ;
181226 let capture_name = id. name. to_ident_string( ) ;
182- let wrap_braces = utils:: get_enclosing_block( cx, expr. hir_id) . map_or( false , |parent| {
183- if_chain! {
184- if let Some ( Expr { kind: ExprKind :: Match (
185- _,
186- arms,
187- MatchSource :: IfDesugar { contains_else_clause: true }
188- | MatchSource :: IfLetDesugar { contains_else_clause: true } ) ,
189- .. } ) = parent. expr;
190- if expr. hir_id == arms[ 1 ] . body. hir_id;
191- then {
192- true
193- } else {
194- false
195- }
196- }
197- } ) ;
198- let ( parens_around_option, as_ref, as_mut, let_body) = match & let_body. kind {
227+ let wrap_braces = should_wrap_in_braces( cx, expr) ;
228+ let ( as_ref, as_mut) = match & cond_expr. kind {
229+ ExprKind :: AddrOf ( _, Mutability :: Not , _) => ( true , false ) ,
230+ ExprKind :: AddrOf ( _, Mutability :: Mut , _) => ( false , true ) ,
231+ _ => ( bind_annotation == & BindingAnnotation :: Ref , bind_annotation == & BindingAnnotation :: RefMut ) ,
232+ } ;
233+ let parens_around_option = match & cond_expr. kind {
234+ // Put parens around the option expression if not doing so might
235+ // mess up the order of operations.
199236 ExprKind :: Call ( ..)
200237 | ExprKind :: MethodCall ( ..)
201238 | ExprKind :: Loop ( ..)
202239 | ExprKind :: Match ( ..)
203240 | ExprKind :: Block ( ..)
204241 | ExprKind :: Field ( ..)
205242 | ExprKind :: Path ( _)
206- => ( false , capture_ref, capture_ref_mut, let_body) ,
207- ExprKind :: Unary ( UnOp :: UnDeref , expr) => ( false , capture_ref, capture_ref_mut, expr) ,
208- ExprKind :: AddrOf ( _, mutability, expr) => ( false , mutability == & Mutability :: Not , mutability == & Mutability :: Mut , expr) ,
209- _ => ( true , capture_ref, capture_ref_mut, let_body) ,
243+ | ExprKind :: Unary ( UnOp :: UnDeref , _)
244+ | ExprKind :: AddrOf ( ..)
245+ => false ,
246+ _ => true ,
247+ } ;
248+ let cond_expr = match & cond_expr. kind {
249+ // Pointer dereferencing happens automatically, so we can omit it in the suggestion
250+ ExprKind :: Unary ( UnOp :: UnDeref , expr) |ExprKind :: AddrOf ( _, _, expr) => expr,
251+ _ => cond_expr,
210252 } ;
211253 Some ( OptionIfLetElseOccurence {
212- option: format! ( "{}{}{}{}" , if parens_around_option { "(" } else { "" } , Sugg :: hir ( cx, let_body , ".." ) , if parens_around_option { ")" } else { "" } , if as_mut { ".as_mut()" } else if as_ref { ".as_ref()" } else { "" } ) ,
213- method_sugg: format! ( "{}" , method_sugg) ,
214- some_expr: format!( "|{}{}{} | {}" , if false { "ref " } else { "" } , if capture_mut { "mut " } else { "" } , capture_name, Sugg :: hir( cx, some_body, ".." ) ) ,
254+ option: format_option_in_sugg ( cx, cond_expr , parens_around_option, as_ref, as_mut ) ,
255+ method_sugg: method_sugg. to_string ( ) ,
256+ some_expr: format!( "|{}{}| {}" , capture_mut, capture_name, Sugg :: hir( cx, some_body, ".." ) ) ,
215257 none_expr: format!( "{}{}" , if method_sugg == "map_or" { "" } else { "|| " } , Sugg :: hir( cx, none_body, ".." ) ) ,
216258 wrap_braces,
217259 } )
0 commit comments