11use clippy_utils:: diagnostics:: span_lint_and_sugg;
22use clippy_utils:: sugg:: Sugg ;
33use clippy_utils:: ty:: is_type_diagnostic_item;
4- use clippy_utils:: usage:: contains_return_break_continue_macro;
5- use clippy_utils:: { eager_or_lazy, in_macro, is_else_clause, is_lang_ctor} ;
4+ use clippy_utils:: {
5+ can_move_expr_to_closure, eager_or_lazy, in_constant, in_macro, is_else_clause, is_lang_ctor, peel_hir_expr_while,
6+ CaptureKind ,
7+ } ;
68use if_chain:: if_chain;
79use rustc_errors:: Applicability ;
810use rustc_hir:: LangItem :: OptionSome ;
9- use rustc_hir:: { Arm , BindingAnnotation , Block , Expr , ExprKind , MatchSource , Mutability , PatKind , UnOp } ;
11+ use rustc_hir:: {
12+ def:: Res , Arm , BindingAnnotation , Block , Expr , ExprKind , MatchSource , Mutability , PatKind , Path , QPath , UnOp ,
13+ } ;
1014use rustc_lint:: { LateContext , LateLintPass } ;
1115use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
1216use rustc_span:: sym;
@@ -127,21 +131,30 @@ fn detect_option_if_let_else<'tcx>(
127131) -> Option < OptionIfLetElseOccurence > {
128132 if_chain ! {
129133 if !in_macro( expr. span) ; // Don't lint macros, because it behaves weirdly
130- if let ExprKind :: Match ( cond_expr, arms, MatchSource :: IfLetDesugar { contains_else_clause: true } ) = & expr. kind;
134+ if !in_constant( cx, expr. hir_id) ;
135+ if let ExprKind :: Match ( cond_expr, [ some_arm, none_arm] , MatchSource :: IfLetDesugar { contains_else_clause: true } )
136+ = & expr. kind;
131137 if !is_else_clause( cx. tcx, expr) ;
132- if arms. len( ) == 2 ;
133138 if !is_result_ok( cx, cond_expr) ; // Don't lint on Result::ok because a different lint does it already
134- if let PatKind :: TupleStruct ( struct_qpath, [ inner_pat] , _) = & arms [ 0 ] . pat. kind;
139+ if let PatKind :: TupleStruct ( struct_qpath, [ inner_pat] , _) = & some_arm . pat. kind;
135140 if is_lang_ctor( cx, struct_qpath, OptionSome ) ;
136141 if let PatKind :: Binding ( bind_annotation, _, id, _) = & inner_pat. kind;
137- if !contains_return_break_continue_macro( arms[ 0 ] . body) ;
138- if !contains_return_break_continue_macro( arms[ 1 ] . body) ;
142+ if let Some ( some_captures) = can_move_expr_to_closure( cx, some_arm. body) ;
143+ if let Some ( none_captures) = can_move_expr_to_closure( cx, none_arm. body) ;
144+ if some_captures
145+ . iter( )
146+ . filter_map( |( id, & c) | none_captures. get( id) . map( |& c2| ( c, c2) ) )
147+ . all( |( x, y) | x. is_imm_ref( ) && y. is_imm_ref( ) ) ;
139148
140149 then {
141150 let capture_mut = if bind_annotation == & BindingAnnotation :: Mutable { "mut " } else { "" } ;
142- let some_body = extract_body_from_arm( & arms[ 0 ] ) ?;
143- let none_body = extract_body_from_arm( & arms[ 1 ] ) ?;
144- let method_sugg = if eager_or_lazy:: is_eagerness_candidate( cx, none_body) { "map_or" } else { "map_or_else" } ;
151+ let some_body = extract_body_from_arm( some_arm) ?;
152+ let none_body = extract_body_from_arm( none_arm) ?;
153+ let method_sugg = if eager_or_lazy:: is_eagerness_candidate( cx, none_body) {
154+ "map_or"
155+ } else {
156+ "map_or_else"
157+ } ;
145158 let capture_name = id. name. to_ident_string( ) ;
146159 let ( as_ref, as_mut) = match & cond_expr. kind {
147160 ExprKind :: AddrOf ( _, Mutability :: Not , _) => ( true , false ) ,
@@ -153,6 +166,24 @@ fn detect_option_if_let_else<'tcx>(
153166 ExprKind :: Unary ( UnOp :: Deref , expr) | ExprKind :: AddrOf ( _, _, expr) => expr,
154167 _ => cond_expr,
155168 } ;
169+ // Check if captures the closure will need conflict with borrows made in the scrutinee.
170+ // TODO: check all the references made in the scrutinee expression. This will require interacting
171+ // with the borrow checker. Currently only `<local>[.<field>]*` is checked for.
172+ if as_ref || as_mut {
173+ let e = peel_hir_expr_while( cond_expr, |e| match e. kind {
174+ ExprKind :: Field ( e, _) | ExprKind :: AddrOf ( _, _, e) => Some ( e) ,
175+ _ => None ,
176+ } ) ;
177+ if let ExprKind :: Path ( QPath :: Resolved ( None , Path { res: Res :: Local ( l) , .. } ) ) = e. kind {
178+ match some_captures. get( l)
179+ . or_else( || ( method_sugg == "map_or_else" ) . then( || ( ) ) . and_then( |_| none_captures. get( l) ) )
180+ {
181+ Some ( CaptureKind :: Value | CaptureKind :: Ref ( Mutability :: Mut ) ) => return None ,
182+ Some ( CaptureKind :: Ref ( Mutability :: Not ) ) if as_mut => return None ,
183+ Some ( CaptureKind :: Ref ( Mutability :: Not ) ) | None => ( ) ,
184+ }
185+ }
186+ }
156187 Some ( OptionIfLetElseOccurence {
157188 option: format_option_in_sugg( cx, cond_expr, as_ref, as_mut) ,
158189 method_sugg: method_sugg. to_string( ) ,
0 commit comments