@@ -167,10 +167,11 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
167
167
) ;
168
168
}
169
169
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) ;
171
171
172
172
let mut is_loop_move = false ;
173
173
let mut in_pattern = false ;
174
+ let mut seen_spans = FxHashSet :: default ( ) ;
174
175
175
176
for move_site in & move_site_vec {
176
177
let move_out = self . move_data . moves [ ( * move_site) . moi ] ;
@@ -191,37 +192,25 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
191
192
is_loop_move = true ;
192
193
}
193
194
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) ;
223
198
}
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
+ ) ;
224
212
}
213
+ seen_spans. insert ( move_span) ;
225
214
}
226
215
227
216
use_spans. var_path_only_subdiag ( & mut err, desired_action) ;
@@ -317,6 +306,160 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
317
306
}
318
307
}
319
308
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
+
320
463
fn report_use_of_uninitialized (
321
464
& self ,
322
465
mpi : MovePathIndex ,
@@ -590,6 +733,27 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
590
733
true
591
734
}
592
735
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
+
593
757
fn suggest_adding_copy_bounds ( & self , err : & mut Diagnostic , ty : Ty < ' tcx > , span : Span ) {
594
758
let tcx = self . infcx . tcx ;
595
759
let generics = tcx. generics_of ( self . mir_def_id ( ) ) ;
0 commit comments