@@ -7,7 +7,7 @@ use clippy_utils::{get_parent_expr_for_hir, is_trait_method, strip_pat_refs};
77use if_chain:: if_chain;
88use rustc_errors:: Applicability ;
99use rustc_hir as hir;
10- use rustc_hir:: { self , ExprKind , HirId , PatKind } ;
10+ use rustc_hir:: { self , ExprKind , HirId , MutTy , PatKind , TyKind } ;
1111use rustc_infer:: infer:: TyCtxtInferExt ;
1212use rustc_lint:: LateContext ;
1313use rustc_middle:: hir:: place:: ProjectionKind ;
@@ -52,26 +52,12 @@ pub(super) fn check<'tcx>(
5252 then {
5353 if let hir:: PatKind :: Ref ( ..) = closure_arg. pat. kind {
5454 Some ( search_snippet. replacen( '&' , "" , 1 ) )
55- } else if let PatKind :: Binding ( _, binding_id, _, _) = strip_pat_refs( closure_arg. pat) . kind {
56- // this binding is composed of at least two levels of references, so we need to remove one
57- let binding_type = cx. typeck_results( ) . node_type( binding_id) ;
58- let innermost_is_ref = if let ty:: Ref ( _, inner, _) = binding_type. kind( ) {
59- matches!( inner. kind( ) , ty:: Ref ( _, innermost, _) if innermost. is_ref( ) )
60- } else {
61- false
62- } ;
63-
55+ } else if let PatKind :: Binding ( ..) = strip_pat_refs( closure_arg. pat) . kind {
6456 // `find()` provides a reference to the item, but `any` does not,
6557 // so we should fix item usages for suggestion
66- if let Some ( closure_sugg) = get_closure_suggestion( cx, search_arg, closure_body ) {
58+ if let Some ( closure_sugg) = get_closure_suggestion( cx, search_arg) {
6759 applicability = closure_sugg. applicability;
68- if innermost_is_ref {
69- Some ( closure_sugg. suggestion. replacen( '&' , "" , 1 ) )
70- } else {
71- Some ( closure_sugg. suggestion)
72- }
73- } else if innermost_is_ref {
74- Some ( search_snippet. replacen( '&' , "" , 1 ) )
60+ Some ( closure_sugg. suggestion)
7561 } else {
7662 Some ( search_snippet. to_string( ) )
7763 }
@@ -174,6 +160,7 @@ pub(super) fn check<'tcx>(
174160 }
175161}
176162
163+ #[ derive( Debug ) ]
177164struct ClosureSugg {
178165 applicability : Applicability ,
179166 suggestion : String ,
@@ -182,38 +169,45 @@ struct ClosureSugg {
182169// Build suggestion gradually by handling closure arg specific usages,
183170// such as explicit deref and borrowing cases.
184171// Returns `None` if no such use cases have been triggered in closure body
185- fn get_closure_suggestion < ' tcx > (
186- cx : & LateContext < ' _ > ,
187- search_arg : & ' tcx hir:: Expr < ' _ > ,
188- closure_body : & hir:: Body < ' _ > ,
189- ) -> Option < ClosureSugg > {
190- let mut visitor = DerefDelegate {
191- cx,
192- closure_span : search_arg. span ,
193- next_pos : search_arg. span . lo ( ) ,
194- suggestion_start : String :: new ( ) ,
195- applicability : Applicability :: MachineApplicable ,
196- } ;
172+ fn get_closure_suggestion < ' tcx > ( cx : & LateContext < ' _ > , search_expr : & ' tcx hir:: Expr < ' _ > ) -> Option < ClosureSugg > {
173+ if let hir:: ExprKind :: Closure ( _, fn_decl, body_id, ..) = search_expr. kind {
174+ let closure_body = cx. tcx . hir ( ) . body ( body_id) ;
175+ // is closure arg a double reference (i.e.: `|x: &&i32| ...`)
176+ let closure_arg_is_double_ref = if let TyKind :: Rptr ( _, MutTy { ty, .. } ) = fn_decl. inputs [ 0 ] . kind {
177+ matches ! ( ty. kind, TyKind :: Rptr ( _, MutTy { .. } ) )
178+ } else {
179+ false
180+ } ;
181+
182+ let mut visitor = DerefDelegate {
183+ cx,
184+ closure_span : search_expr. span ,
185+ closure_arg_is_double_ref,
186+ next_pos : search_expr. span . lo ( ) ,
187+ suggestion_start : String :: new ( ) ,
188+ applicability : Applicability :: MachineApplicable ,
189+ } ;
197190
198- let fn_def_id = cx. tcx . hir ( ) . local_def_id ( search_arg . hir_id ) ;
199- cx. tcx . infer_ctxt ( ) . enter ( |infcx| {
200- ExprUseVisitor :: new ( & mut visitor, & infcx, fn_def_id, cx. param_env , cx. typeck_results ( ) )
201- . consume_body ( closure_body) ;
202- } ) ;
191+ let fn_def_id = cx. tcx . hir ( ) . local_def_id ( search_expr . hir_id ) ;
192+ cx. tcx . infer_ctxt ( ) . enter ( |infcx| {
193+ ExprUseVisitor :: new ( & mut visitor, & infcx, fn_def_id, cx. param_env , cx. typeck_results ( ) )
194+ . consume_body ( closure_body) ;
195+ } ) ;
203196
204- if visitor. suggestion_start . is_empty ( ) {
205- None
206- } else {
207- Some ( ClosureSugg {
208- applicability : visitor. applicability ,
209- suggestion : visitor. finish ( ) ,
210- } )
197+ if !visitor. suggestion_start . is_empty ( ) {
198+ return Some ( ClosureSugg {
199+ applicability : visitor. applicability ,
200+ suggestion : visitor. finish ( ) ,
201+ } ) ;
202+ }
211203 }
204+ None
212205}
213206
214207struct DerefDelegate < ' a , ' tcx > {
215208 cx : & ' a LateContext < ' tcx > ,
216209 closure_span : Span ,
210+ closure_arg_is_double_ref : bool ,
217211 next_pos : BytePos ,
218212 suggestion_start : String ,
219213 applicability : Applicability ,
@@ -223,7 +217,12 @@ impl DerefDelegate<'_, 'tcx> {
223217 pub fn finish ( & mut self ) -> String {
224218 let end_span = Span :: new ( self . next_pos , self . closure_span . hi ( ) , self . closure_span . ctxt ( ) , None ) ;
225219 let end_snip = snippet_with_applicability ( self . cx , end_span, ".." , & mut self . applicability ) ;
226- format ! ( "{}{}" , self . suggestion_start, end_snip)
220+ let sugg = format ! ( "{}{}" , self . suggestion_start, end_snip) ;
221+ if self . closure_arg_is_double_ref {
222+ sugg. replacen ( '&' , "" , 1 )
223+ } else {
224+ sugg
225+ }
227226 }
228227
229228 fn func_takes_arg_by_double_ref ( & self , parent_expr : & ' tcx hir:: Expr < ' _ > , cmt_hir_id : HirId ) -> bool {
@@ -261,6 +260,7 @@ impl<'tcx> Delegate<'tcx> for DerefDelegate<'_, 'tcx> {
261260 if cmt. place . projections . is_empty ( ) {
262261 // handle item without any projection, that needs an explicit borrowing
263262 // i.e.: suggest `&x` instead of `x`
263+ self . closure_arg_is_double_ref = false ;
264264 self . suggestion_start . push_str ( & format ! ( "{}&{}" , start_snip, ident_str) ) ;
265265 } else {
266266 // cases where a parent `Call` or `MethodCall` is using the item
@@ -272,29 +272,43 @@ impl<'tcx> Delegate<'tcx> for DerefDelegate<'_, 'tcx> {
272272 // - `self` arguments in the case of `x.is_something()` are also automatically (de)referenced, and
273273 // no projection should be suggested
274274 if let Some ( parent_expr) = get_parent_expr_for_hir ( self . cx , cmt. hir_id ) {
275- if let ExprKind :: Call ( _, call_args) | ExprKind :: MethodCall ( _, _, call_args, _) = parent_expr. kind {
276- let expr = self . cx . tcx . hir ( ) . expect_expr ( cmt. hir_id ) ;
277- let arg_ty_kind = self . cx . typeck_results ( ) . expr_ty ( expr) . kind ( ) ;
278-
279- if matches ! ( arg_ty_kind, ty:: Ref ( _, _, Mutability :: Not ) ) {
280- // suggest ampersand if call function is taking args by double reference
281- let takes_arg_by_double_ref = self . func_takes_arg_by_double_ref ( parent_expr, cmt. hir_id ) ;
282-
283- // do not suggest ampersand if the ident is the method caller
284- let ident_sugg = if !call_args. is_empty ( )
285- && call_args[ 0 ] . hir_id == cmt. hir_id
286- && !takes_arg_by_double_ref
287- {
288- format ! ( "{}{}" , start_snip, ident_str)
289- } else {
290- format ! ( "{}&{}" , start_snip, ident_str)
291- } ;
292- self . suggestion_start . push_str ( & ident_sugg) ;
275+ match & parent_expr. kind {
276+ // given expression is the self argument and will be handled completely by the compiler
277+ // i.e.: `|x| x.is_something()`
278+ ExprKind :: MethodCall ( _, _, [ self_expr, ..] , _) if self_expr. hir_id == cmt. hir_id => {
279+ self . suggestion_start . push_str ( & format ! ( "{}{}" , start_snip, ident_str) ) ;
293280 self . next_pos = span. hi ( ) ;
294281 return ;
295- }
282+ } ,
283+ // item is used in a call
284+ // i.e.: `Call`: `|x| please(x)` or `MethodCall`: `|x| [1, 2, 3].contains(x)`
285+ ExprKind :: Call ( _, [ call_args @ ..] ) | ExprKind :: MethodCall ( _, _, [ _, call_args @ ..] , _) => {
286+ let expr = self . cx . tcx . hir ( ) . expect_expr ( cmt. hir_id ) ;
287+ let arg_ty_kind = self . cx . typeck_results ( ) . expr_ty ( expr) . kind ( ) ;
296288
297- self . applicability = Applicability :: Unspecified ;
289+ if matches ! ( arg_ty_kind, ty:: Ref ( _, _, Mutability :: Not ) ) {
290+ // suggest ampersand if call function is taking args by double reference
291+ let takes_arg_by_double_ref =
292+ self . func_takes_arg_by_double_ref ( parent_expr, cmt. hir_id ) ;
293+
294+ // no need to bind again if the function doesn't take arg by double ref
295+ // and if the item is already a double ref
296+ let ident_sugg = if !call_args. is_empty ( )
297+ && !takes_arg_by_double_ref
298+ && self . closure_arg_is_double_ref
299+ {
300+ format ! ( "{}{}" , start_snip, ident_str)
301+ } else {
302+ format ! ( "{}&{}" , start_snip, ident_str)
303+ } ;
304+ self . suggestion_start . push_str ( & ident_sugg) ;
305+ self . next_pos = span. hi ( ) ;
306+ return ;
307+ }
308+
309+ self . applicability = Applicability :: Unspecified ;
310+ } ,
311+ _ => ( ) ,
298312 }
299313 }
300314
@@ -346,7 +360,9 @@ impl<'tcx> Delegate<'tcx> for DerefDelegate<'_, 'tcx> {
346360
347361 // handle `ProjectionKind::Deref` by removing one explicit deref
348362 // if no special case was detected (i.e.: suggest `*x` instead of `**x`)
349- if !projections_handled {
363+ if projections_handled {
364+ self . closure_arg_is_double_ref = false ;
365+ } else {
350366 let last_deref = cmt
351367 . place
352368 . projections
0 commit comments