@@ -167,10 +167,11 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
167167 ) ;
168168 }
169169
170- self . add_moved_or_invoked_closure_note ( location, used_place, & mut err) ;
170+ let closure = self . add_moved_or_invoked_closure_note ( location, used_place, & mut err) ;
171171
172172 let mut is_loop_move = false ;
173173 let mut in_pattern = false ;
174+ let mut seen_spans = FxHashSet :: default ( ) ;
174175
175176 for move_site in & move_site_vec {
176177 let move_out = self . move_data . moves [ ( * move_site) . moi ] ;
@@ -191,37 +192,25 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
191192 is_loop_move = true ;
192193 }
193194
194- self . explain_captures (
195- & mut err,
196- span,
197- move_span,
198- move_spans,
199- * moved_place,
200- partially_str,
201- loop_message,
202- move_msg,
203- is_loop_move,
204- maybe_reinitialized_locations. is_empty ( ) ,
205- ) ;
206-
207- if let ( UseSpans :: PatUse ( span) , [ ] ) =
208- ( move_spans, & maybe_reinitialized_locations[ ..] )
209- {
210- if maybe_reinitialized_locations. is_empty ( ) {
211- err. span_suggestion_verbose (
212- span. shrink_to_lo ( ) ,
213- & format ! (
214- "borrow this field in the pattern to avoid moving {}" ,
215- self . describe_place( moved_place. as_ref( ) )
216- . map( |n| format!( "`{}`" , n) )
217- . unwrap_or_else( || "the value" . to_string( ) )
218- ) ,
219- "ref " ,
220- Applicability :: MachineApplicable ,
221- ) ;
222- in_pattern = true ;
195+ if !seen_spans. contains ( & move_span) {
196+ if !closure {
197+ self . suggest_ref_or_clone ( mpi, move_span, & mut err, & mut in_pattern) ;
223198 }
199+
200+ self . explain_captures (
201+ & mut err,
202+ span,
203+ move_span,
204+ move_spans,
205+ * moved_place,
206+ partially_str,
207+ loop_message,
208+ move_msg,
209+ is_loop_move,
210+ maybe_reinitialized_locations. is_empty ( ) ,
211+ ) ;
224212 }
213+ seen_spans. insert ( move_span) ;
225214 }
226215
227216 use_spans. var_path_only_subdiag ( & mut err, desired_action) ;
@@ -317,6 +306,160 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
317306 }
318307 }
319308
309+ fn suggest_ref_or_clone (
310+ & mut self ,
311+ mpi : MovePathIndex ,
312+ move_span : Span ,
313+ err : & mut DiagnosticBuilder < ' _ , ErrorGuaranteed > ,
314+ in_pattern : & mut bool ,
315+ ) {
316+ struct ExpressionFinder < ' hir > {
317+ expr_span : Span ,
318+ expr : Option < & ' hir hir:: Expr < ' hir > > ,
319+ pat : Option < & ' hir hir:: Pat < ' hir > > ,
320+ parent_pat : Option < & ' hir hir:: Pat < ' hir > > ,
321+ }
322+ impl < ' hir > Visitor < ' hir > for ExpressionFinder < ' hir > {
323+ fn visit_expr ( & mut self , e : & ' hir hir:: Expr < ' hir > ) {
324+ if e. span == self . expr_span {
325+ self . expr = Some ( e) ;
326+ }
327+ hir:: intravisit:: walk_expr ( self , e) ;
328+ }
329+ fn visit_pat ( & mut self , p : & ' hir hir:: Pat < ' hir > ) {
330+ if p. span == self . expr_span {
331+ self . pat = Some ( p) ;
332+ }
333+ if let hir:: PatKind :: Binding ( hir:: BindingAnnotation :: NONE , _, i, sub) = p. kind {
334+ if i. span == self . expr_span || p. span == self . expr_span {
335+ self . pat = Some ( p) ;
336+ }
337+ // Check if we are in a situation of `ident @ ident` where we want to suggest
338+ // `ref ident @ ref ident` or `ref ident @ Struct { ref ident }`.
339+ if let Some ( subpat) = sub && self . pat . is_none ( ) {
340+ self . visit_pat ( subpat) ;
341+ if self . pat . is_some ( ) {
342+ self . parent_pat = Some ( p) ;
343+ }
344+ return ;
345+ }
346+ }
347+ hir:: intravisit:: walk_pat ( self , p) ;
348+ }
349+ }
350+ let hir = self . infcx . tcx . hir ( ) ;
351+ if let Some ( hir:: Node :: Item ( hir:: Item {
352+ kind : hir:: ItemKind :: Fn ( _, _, body_id) ,
353+ ..
354+ } ) ) = hir. find ( hir. local_def_id_to_hir_id ( self . mir_def_id ( ) ) )
355+ && let Some ( hir:: Node :: Expr ( expr) ) = hir. find ( body_id. hir_id )
356+ {
357+ let place = & self . move_data . move_paths [ mpi] . place ;
358+ let span = place. as_local ( )
359+ . map ( |local| self . body . local_decls [ local] . source_info . span ) ;
360+ let mut finder = ExpressionFinder {
361+ expr_span : move_span,
362+ expr : None ,
363+ pat : None ,
364+ parent_pat : None ,
365+ } ;
366+ finder. visit_expr ( expr) ;
367+ if let Some ( span) = span && let Some ( expr) = finder. expr {
368+ for ( _, expr) in hir. parent_iter ( expr. hir_id ) {
369+ if let hir:: Node :: Expr ( expr) = expr {
370+ if expr. span . contains ( span) {
371+ // If the let binding occurs within the same loop, then that
372+ // loop isn't relevant, like in the following, the outermost `loop`
373+ // doesn't play into `x` being moved.
374+ // ```
375+ // loop {
376+ // let x = String::new();
377+ // loop {
378+ // foo(x);
379+ // }
380+ // }
381+ // ```
382+ break ;
383+ }
384+ if let hir:: ExprKind :: Loop ( .., loop_span) = expr. kind {
385+ err. span_label ( loop_span, "inside of this loop" ) ;
386+ }
387+ }
388+ }
389+ let typeck = self . infcx . tcx . typeck ( self . mir_def_id ( ) ) ;
390+ let hir_id = hir. get_parent_node ( expr. hir_id ) ;
391+ if let Some ( parent) = hir. find ( hir_id) {
392+ let ( def_id, args, offset) = if let hir:: Node :: Expr ( parent_expr) = parent
393+ && let hir:: ExprKind :: MethodCall ( _, _, args, _) = parent_expr. kind
394+ && let Some ( def_id) = typeck. type_dependent_def_id ( parent_expr. hir_id )
395+ {
396+ ( def_id. as_local ( ) , args, 1 )
397+ } else if let hir:: Node :: Expr ( parent_expr) = parent
398+ && let hir:: ExprKind :: Call ( call, args) = parent_expr. kind
399+ && let ty:: FnDef ( def_id, _) = typeck. node_type ( call. hir_id ) . kind ( )
400+ {
401+ ( def_id. as_local ( ) , args, 0 )
402+ } else {
403+ ( None , & [ ] [ ..] , 0 )
404+ } ;
405+ if let Some ( def_id) = def_id
406+ && let Some ( node) = hir. find ( hir. local_def_id_to_hir_id ( def_id) )
407+ && let Some ( fn_sig) = node. fn_sig ( )
408+ && let Some ( ident) = node. ident ( )
409+ && let Some ( pos) = args. iter ( ) . position ( |arg| arg. hir_id == expr. hir_id )
410+ && let Some ( arg) = fn_sig. decl . inputs . get ( pos + offset)
411+ {
412+ let mut span: MultiSpan = arg. span . into ( ) ;
413+ span. push_span_label (
414+ arg. span ,
415+ "this parameter takes ownership of the value" . to_string ( ) ,
416+ ) ;
417+ let descr = match node. fn_kind ( ) {
418+ Some ( hir:: intravisit:: FnKind :: ItemFn ( ..) ) | None => "function" ,
419+ Some ( hir:: intravisit:: FnKind :: Method ( ..) ) => "method" ,
420+ Some ( hir:: intravisit:: FnKind :: Closure ) => "closure" ,
421+ } ;
422+ span. push_span_label (
423+ ident. span ,
424+ format ! ( "in this {descr}" ) ,
425+ ) ;
426+ err. span_note (
427+ span,
428+ format ! (
429+ "consider changing this parameter type in {descr} `{ident}` to \
430+ borrow instead if owning the value isn't necessary",
431+ ) ,
432+ ) ;
433+ }
434+ let place = & self . move_data . move_paths [ mpi] . place ;
435+ let ty = place. ty ( self . body , self . infcx . tcx ) . ty ;
436+ if let hir:: Node :: Expr ( parent_expr) = parent
437+ && let hir:: ExprKind :: Call ( call_expr, _) = parent_expr. kind
438+ && let hir:: ExprKind :: Path (
439+ hir:: QPath :: LangItem ( LangItem :: IntoIterIntoIter , _, _)
440+ ) = call_expr. kind
441+ {
442+ // Do not suggest `.clone()` in a `for` loop, we already suggest borrowing.
443+ } else {
444+ self . suggest_cloning ( err, ty, move_span) ;
445+ }
446+ }
447+ }
448+ if let Some ( pat) = finder. pat {
449+ * in_pattern = true ;
450+ let mut sugg = vec ! [ ( pat. span. shrink_to_lo( ) , "ref " . to_string( ) ) ] ;
451+ if let Some ( pat) = finder. parent_pat {
452+ sugg. insert ( 0 , ( pat. span . shrink_to_lo ( ) , "ref " . to_string ( ) ) ) ;
453+ }
454+ err. multipart_suggestion_verbose (
455+ "borrow this binding in the pattern to avoid moving the value" ,
456+ sugg,
457+ Applicability :: MachineApplicable ,
458+ ) ;
459+ }
460+ }
461+ }
462+
320463 fn report_use_of_uninitialized (
321464 & self ,
322465 mpi : MovePathIndex ,
@@ -590,6 +733,27 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
590733 true
591734 }
592735
736+ fn suggest_cloning ( & self , err : & mut Diagnostic , ty : Ty < ' tcx > , span : Span ) {
737+ let tcx = self . infcx . tcx ;
738+ // Try to find predicates on *generic params* that would allow copying `ty`
739+ let infcx = tcx. infer_ctxt ( ) . build ( ) ;
740+ if infcx
741+ . type_implements_trait (
742+ tcx. lang_items ( ) . clone_trait ( ) . unwrap ( ) ,
743+ [ tcx. erase_regions ( ty) ] ,
744+ self . param_env ,
745+ )
746+ . must_apply_modulo_regions ( )
747+ {
748+ err. span_suggestion_verbose (
749+ span. shrink_to_hi ( ) ,
750+ "consider cloning the value if the performance cost is acceptable" ,
751+ ".clone()" . to_string ( ) ,
752+ Applicability :: MachineApplicable ,
753+ ) ;
754+ }
755+ }
756+
593757 fn suggest_adding_copy_bounds ( & self , err : & mut Diagnostic , ty : Ty < ' tcx > , span : Span ) {
594758 let tcx = self . infcx . tcx ;
595759 let generics = tcx. generics_of ( self . mir_def_id ( ) ) ;
0 commit comments