2
2
#![ allow( rustc:: untranslatable_diagnostic) ]
3
3
4
4
use rustc_errors:: { Applicability , Diag } ;
5
+ use rustc_hir:: intravisit:: Visitor ;
6
+ use rustc_hir:: { CaptureBy , ExprKind , HirId , Node } ;
5
7
use rustc_middle:: mir:: * ;
6
8
use rustc_middle:: ty:: { self , Ty } ;
7
9
use rustc_mir_dataflow:: move_paths:: { LookupResult , MovePathIndex } ;
8
10
use rustc_span:: { BytePos , ExpnKind , MacroKind , Span } ;
11
+ use rustc_trait_selection:: traits:: error_reporting:: FindExprBySpan ;
9
12
10
13
use crate :: diagnostics:: CapturedMessageOpt ;
11
14
use crate :: diagnostics:: { DescribePlaceOpt , UseSpans } ;
@@ -303,17 +306,133 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
303
306
self . cannot_move_out_of ( span, & description)
304
307
}
305
308
309
+ fn suggest_clone_of_captured_var_in_move_closure (
310
+ & self ,
311
+ err : & mut Diag < ' _ > ,
312
+ upvar_hir_id : HirId ,
313
+ upvar_name : & str ,
314
+ use_spans : Option < UseSpans < ' tcx > > ,
315
+ ) {
316
+ let tcx = self . infcx . tcx ;
317
+ let typeck_results = tcx. typeck ( self . mir_def_id ( ) ) ;
318
+ let Some ( use_spans) = use_spans else { return } ;
319
+ // We only care about the case where a closure captured a binding.
320
+ let UseSpans :: ClosureUse { args_span, .. } = use_spans else { return } ;
321
+ let Some ( body_id) = tcx. hir_node ( self . mir_hir_id ( ) ) . body_id ( ) else { return } ;
322
+ // Fetch the type of the expression corresponding to the closure-captured binding.
323
+ let Some ( captured_ty) = typeck_results. node_type_opt ( upvar_hir_id) else { return } ;
324
+ if !self . implements_clone ( captured_ty) {
325
+ // We only suggest cloning the captured binding if the type can actually be cloned.
326
+ return ;
327
+ } ;
328
+ // Find the closure that captured the binding.
329
+ let mut expr_finder = FindExprBySpan :: new ( args_span, tcx) ;
330
+ expr_finder. include_closures = true ;
331
+ expr_finder. visit_expr ( tcx. hir ( ) . body ( body_id) . value ) ;
332
+ let Some ( closure_expr) = expr_finder. result else { return } ;
333
+ let ExprKind :: Closure ( closure) = closure_expr. kind else { return } ;
334
+ // We'll only suggest cloning the binding if it's a `move` closure.
335
+ let CaptureBy :: Value { .. } = closure. capture_clause else { return } ;
336
+ // Find the expression within the closure where the binding is consumed.
337
+ let mut suggested = false ;
338
+ let use_span = use_spans. var_or_use ( ) ;
339
+ let mut expr_finder = FindExprBySpan :: new ( use_span, tcx) ;
340
+ expr_finder. include_closures = true ;
341
+ expr_finder. visit_expr ( tcx. hir ( ) . body ( body_id) . value ) ;
342
+ let Some ( use_expr) = expr_finder. result else { return } ;
343
+ let parent = tcx. parent_hir_node ( use_expr. hir_id ) ;
344
+ if let Node :: Expr ( expr) = parent
345
+ && let ExprKind :: Assign ( lhs, ..) = expr. kind
346
+ && lhs. hir_id == use_expr. hir_id
347
+ {
348
+ // Cloning the value being assigned makes no sense:
349
+ //
350
+ // error[E0507]: cannot move out of `var`, a captured variable in an `FnMut` closure
351
+ // --> $DIR/option-content-move2.rs:11:9
352
+ // |
353
+ // LL | let mut var = None;
354
+ // | ------- captured outer variable
355
+ // LL | func(|| {
356
+ // | -- captured by this `FnMut` closure
357
+ // LL | // Shouldn't suggest `move ||.as_ref()` here
358
+ // LL | move || {
359
+ // | ^^^^^^^ `var` is moved here
360
+ // LL |
361
+ // LL | var = Some(NotCopyable);
362
+ // | ---
363
+ // | |
364
+ // | variable moved due to use in closure
365
+ // | move occurs because `var` has type `Option<NotCopyable>`, which does not implement the `Copy` trait
366
+ // |
367
+ return ;
368
+ }
369
+
370
+ // Search for an appropriate place for the structured `.clone()` suggestion to be applied.
371
+ // If we encounter a statement before the borrow error, we insert a statement there.
372
+ for ( _, node) in tcx. hir ( ) . parent_iter ( closure_expr. hir_id ) {
373
+ if let Node :: Stmt ( stmt) = node {
374
+ let padding = tcx
375
+ . sess
376
+ . source_map ( )
377
+ . indentation_before ( stmt. span )
378
+ . unwrap_or_else ( || " " . to_string ( ) ) ;
379
+ err. multipart_suggestion_verbose (
380
+ "clone the value before moving it into the closure" ,
381
+ vec ! [
382
+ (
383
+ stmt. span. shrink_to_lo( ) ,
384
+ format!( "let value = {upvar_name}.clone();\n {padding}" ) ,
385
+ ) ,
386
+ ( use_span, "value" . to_string( ) ) ,
387
+ ] ,
388
+ Applicability :: MachineApplicable ,
389
+ ) ;
390
+ suggested = true ;
391
+ break ;
392
+ } else if let Node :: Expr ( expr) = node
393
+ && let ExprKind :: Closure ( _) = expr. kind
394
+ {
395
+ // We want to suggest cloning only on the first closure, not
396
+ // subsequent ones (like `ui/suggestions/option-content-move2.rs`).
397
+ break ;
398
+ }
399
+ }
400
+ if !suggested {
401
+ // If we couldn't find a statement for us to insert a new `.clone()` statement before,
402
+ // we have a bare expression, so we suggest the creation of a new block inline to go
403
+ // from `move || val` to `{ let value = val.clone(); move || value }`.
404
+ let padding = tcx
405
+ . sess
406
+ . source_map ( )
407
+ . indentation_before ( closure_expr. span )
408
+ . unwrap_or_else ( || " " . to_string ( ) ) ;
409
+ err. multipart_suggestion_verbose (
410
+ "clone the value before moving it into the closure" ,
411
+ vec ! [
412
+ (
413
+ closure_expr. span. shrink_to_lo( ) ,
414
+ format!( "{{\n {padding}let value = {upvar_name}.clone();\n {padding}" ) ,
415
+ ) ,
416
+ ( use_spans. var_or_use( ) , "value" . to_string( ) ) ,
417
+ ( closure_expr. span. shrink_to_hi( ) , format!( "\n {padding}}}" ) ) ,
418
+ ] ,
419
+ Applicability :: MachineApplicable ,
420
+ ) ;
421
+ }
422
+ }
423
+
306
424
fn report_cannot_move_from_borrowed_content (
307
425
& mut self ,
308
426
move_place : Place < ' tcx > ,
309
427
deref_target_place : Place < ' tcx > ,
310
428
span : Span ,
311
429
use_spans : Option < UseSpans < ' tcx > > ,
312
430
) -> Diag < ' tcx > {
431
+ let tcx = self . infcx . tcx ;
313
432
// Inspect the type of the content behind the
314
433
// borrow to provide feedback about why this
315
434
// was a move rather than a copy.
316
- let ty = deref_target_place. ty ( self . body , self . infcx . tcx ) . ty ;
435
+ let ty = deref_target_place. ty ( self . body , tcx) . ty ;
317
436
let upvar_field = self
318
437
. prefixes ( move_place. as_ref ( ) , PrefixSet :: All )
319
438
. find_map ( |p| self . is_upvar_field_projection ( p) ) ;
@@ -363,8 +482,8 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
363
482
364
483
let upvar = & self . upvars [ upvar_field. unwrap ( ) . index ( ) ] ;
365
484
let upvar_hir_id = upvar. get_root_variable ( ) ;
366
- let upvar_name = upvar. to_string ( self . infcx . tcx ) ;
367
- let upvar_span = self . infcx . tcx . hir ( ) . span ( upvar_hir_id) ;
485
+ let upvar_name = upvar. to_string ( tcx) ;
486
+ let upvar_span = tcx. hir ( ) . span ( upvar_hir_id) ;
368
487
369
488
let place_name = self . describe_any_place ( move_place. as_ref ( ) ) ;
370
489
@@ -380,12 +499,21 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
380
499
closure_kind_ty, closure_kind, place_description,
381
500
) ;
382
501
383
- self . cannot_move_out_of ( span, & place_description)
502
+ let closure_span = tcx. def_span ( def_id) ;
503
+ let mut err = self
504
+ . cannot_move_out_of ( span, & place_description)
384
505
. with_span_label ( upvar_span, "captured outer variable" )
385
506
. with_span_label (
386
- self . infcx . tcx . def_span ( def_id ) ,
507
+ closure_span ,
387
508
format ! ( "captured by this `{closure_kind}` closure" ) ,
388
- )
509
+ ) ;
510
+ self . suggest_clone_of_captured_var_in_move_closure (
511
+ & mut err,
512
+ upvar_hir_id,
513
+ & upvar_name,
514
+ use_spans,
515
+ ) ;
516
+ err
389
517
}
390
518
_ => {
391
519
let source = self . borrowed_content_source ( deref_base) ;
@@ -415,7 +543,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
415
543
) ,
416
544
( _, _, _) => self . cannot_move_out_of (
417
545
span,
418
- & source. describe_for_unnamed_place ( self . infcx . tcx ) ,
546
+ & source. describe_for_unnamed_place ( tcx) ,
419
547
) ,
420
548
}
421
549
}
0 commit comments