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,122 @@ 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
+ let UseSpans :: ClosureUse { args_span, .. } = use_spans else { return } ;
320
+ let Some ( body_id) = tcx. hir_node ( self . mir_hir_id ( ) ) . body_id ( ) else { return } ;
321
+ let Some ( captured_ty) = typeck_results. node_type_opt ( upvar_hir_id) else { return } ;
322
+ if !self . implements_clone ( captured_ty) {
323
+ return ;
324
+ } ;
325
+ let mut expr_finder = FindExprBySpan :: new ( args_span, tcx) ;
326
+ expr_finder. include_closures = true ;
327
+ expr_finder. visit_expr ( tcx. hir ( ) . body ( body_id) . value ) ;
328
+ let Some ( closure_expr) = expr_finder. result else { return } ;
329
+ let ExprKind :: Closure ( closure) = closure_expr. kind else { return } ;
330
+ let CaptureBy :: Value { .. } = closure. capture_clause else { return } ;
331
+ let mut suggested = false ;
332
+ let use_span = use_spans. var_or_use ( ) ;
333
+ let mut expr_finder = FindExprBySpan :: new ( use_span, tcx) ;
334
+ expr_finder. include_closures = true ;
335
+ expr_finder. visit_expr ( tcx. hir ( ) . body ( body_id) . value ) ;
336
+ let Some ( use_expr) = expr_finder. result else { return } ;
337
+ let parent = tcx. parent_hir_node ( use_expr. hir_id ) ;
338
+ if let Node :: Expr ( expr) = parent
339
+ && let ExprKind :: Assign ( lhs, ..) = expr. kind
340
+ && lhs. hir_id == use_expr. hir_id
341
+ {
342
+ // Cloning the value being assigned makes no sense:
343
+ //
344
+ // error[E0507]: cannot move out of `var`, a captured variable in an `FnMut` closure
345
+ // --> $DIR/option-content-move2.rs:11:9
346
+ // |
347
+ // LL | let mut var = None;
348
+ // | ------- captured outer variable
349
+ // LL | func(|| {
350
+ // | -- captured by this `FnMut` closure
351
+ // LL | // Shouldn't suggest `move ||.as_ref()` here
352
+ // LL | move || {
353
+ // | ^^^^^^^ `var` is moved here
354
+ // LL |
355
+ // LL | var = Some(NotCopyable);
356
+ // | ---
357
+ // | |
358
+ // | variable moved due to use in closure
359
+ // | move occurs because `var` has type `Option<NotCopyable>`, which does not implement the `Copy` trait
360
+ // |
361
+ return ;
362
+ }
363
+
364
+ for ( _, node) in tcx. hir ( ) . parent_iter ( closure_expr. hir_id ) {
365
+ if let Node :: Stmt ( stmt) = node {
366
+ let padding = tcx
367
+ . sess
368
+ . source_map ( )
369
+ . indentation_before ( stmt. span )
370
+ . unwrap_or_else ( || " " . to_string ( ) ) ;
371
+ err. multipart_suggestion_verbose (
372
+ "clone the value before moving it into the closure 1" ,
373
+ vec ! [
374
+ (
375
+ stmt. span. shrink_to_lo( ) ,
376
+ format!( "let value = {upvar_name}.clone();\n {padding}" ) ,
377
+ ) ,
378
+ ( use_span, "value" . to_string( ) ) ,
379
+ ] ,
380
+ Applicability :: MachineApplicable ,
381
+ ) ;
382
+ suggested = true ;
383
+ break ;
384
+ } else if let Node :: Expr ( expr) = node
385
+ && let ExprKind :: Closure ( _) = expr. kind
386
+ {
387
+ // We want to suggest cloning only on the first closure, not
388
+ // subsequent ones (like `ui/suggestions/option-content-move2.rs`).
389
+ break ;
390
+ }
391
+ }
392
+ if !suggested {
393
+ let padding = tcx
394
+ . sess
395
+ . source_map ( )
396
+ . indentation_before ( closure_expr. span )
397
+ . unwrap_or_else ( || " " . to_string ( ) ) ;
398
+ err. multipart_suggestion_verbose (
399
+ "clone the value before moving it into the closure 2" ,
400
+ vec ! [
401
+ (
402
+ closure_expr. span. shrink_to_lo( ) ,
403
+ format!( "{{\n {padding}let value = {upvar_name}.clone();\n {padding}" ) ,
404
+ ) ,
405
+ ( use_spans. var_or_use( ) , "value" . to_string( ) ) ,
406
+ ( closure_expr. span. shrink_to_hi( ) , format!( "\n {padding}}}" ) ) ,
407
+ ] ,
408
+ Applicability :: MachineApplicable ,
409
+ ) ;
410
+ }
411
+ }
412
+
306
413
fn report_cannot_move_from_borrowed_content (
307
414
& mut self ,
308
415
move_place : Place < ' tcx > ,
309
416
deref_target_place : Place < ' tcx > ,
310
417
span : Span ,
311
418
use_spans : Option < UseSpans < ' tcx > > ,
312
419
) -> Diag < ' tcx > {
420
+ let tcx = self . infcx . tcx ;
313
421
// Inspect the type of the content behind the
314
422
// borrow to provide feedback about why this
315
423
// was a move rather than a copy.
316
- let ty = deref_target_place. ty ( self . body , self . infcx . tcx ) . ty ;
424
+ let ty = deref_target_place. ty ( self . body , tcx) . ty ;
317
425
let upvar_field = self
318
426
. prefixes ( move_place. as_ref ( ) , PrefixSet :: All )
319
427
. find_map ( |p| self . is_upvar_field_projection ( p) ) ;
@@ -363,8 +471,8 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
363
471
364
472
let upvar = & self . upvars [ upvar_field. unwrap ( ) . index ( ) ] ;
365
473
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) ;
474
+ let upvar_name = upvar. to_string ( tcx) ;
475
+ let upvar_span = tcx. hir ( ) . span ( upvar_hir_id) ;
368
476
369
477
let place_name = self . describe_any_place ( move_place. as_ref ( ) ) ;
370
478
@@ -380,12 +488,21 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
380
488
closure_kind_ty, closure_kind, place_description,
381
489
) ;
382
490
383
- self . cannot_move_out_of ( span, & place_description)
491
+ let closure_span = tcx. def_span ( def_id) ;
492
+ let mut err = self
493
+ . cannot_move_out_of ( span, & place_description)
384
494
. with_span_label ( upvar_span, "captured outer variable" )
385
495
. with_span_label (
386
- self . infcx . tcx . def_span ( def_id ) ,
496
+ closure_span ,
387
497
format ! ( "captured by this `{closure_kind}` closure" ) ,
388
- )
498
+ ) ;
499
+ self . suggest_clone_of_captured_var_in_move_closure (
500
+ & mut err,
501
+ upvar_hir_id,
502
+ & upvar_name,
503
+ use_spans,
504
+ ) ;
505
+ err
389
506
}
390
507
_ => {
391
508
let source = self . borrowed_content_source ( deref_base) ;
@@ -415,7 +532,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
415
532
) ,
416
533
( _, _, _) => self . cannot_move_out_of (
417
534
span,
418
- & source. describe_for_unnamed_place ( self . infcx . tcx ) ,
535
+ & source. describe_for_unnamed_place ( tcx) ,
419
536
) ,
420
537
}
421
538
}
0 commit comments