22#![ allow( rustc:: untranslatable_diagnostic) ]
33
44use rustc_errors:: { Applicability , Diag } ;
5+ use rustc_hir:: intravisit:: Visitor ;
6+ use rustc_hir:: { CaptureBy , ExprKind , HirId , Node } ;
57use rustc_middle:: mir:: * ;
68use rustc_middle:: ty:: { self , Ty } ;
79use rustc_mir_dataflow:: move_paths:: { LookupResult , MovePathIndex } ;
810use rustc_span:: { BytePos , ExpnKind , MacroKind , Span } ;
11+ use rustc_trait_selection:: traits:: error_reporting:: FindExprBySpan ;
912
1013use crate :: diagnostics:: CapturedMessageOpt ;
1114use crate :: diagnostics:: { DescribePlaceOpt , UseSpans } ;
@@ -303,17 +306,133 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
303306 self . cannot_move_out_of ( span, & description)
304307 }
305308
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+
306424 fn report_cannot_move_from_borrowed_content (
307425 & mut self ,
308426 move_place : Place < ' tcx > ,
309427 deref_target_place : Place < ' tcx > ,
310428 span : Span ,
311429 use_spans : Option < UseSpans < ' tcx > > ,
312430 ) -> Diag < ' tcx > {
431+ let tcx = self . infcx . tcx ;
313432 // Inspect the type of the content behind the
314433 // borrow to provide feedback about why this
315434 // 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 ;
317436 let upvar_field = self
318437 . prefixes ( move_place. as_ref ( ) , PrefixSet :: All )
319438 . find_map ( |p| self . is_upvar_field_projection ( p) ) ;
@@ -363,8 +482,8 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
363482
364483 let upvar = & self . upvars [ upvar_field. unwrap ( ) . index ( ) ] ;
365484 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) ;
368487
369488 let place_name = self . describe_any_place ( move_place. as_ref ( ) ) ;
370489
@@ -380,12 +499,21 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
380499 closure_kind_ty, closure_kind, place_description,
381500 ) ;
382501
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)
384505 . with_span_label ( upvar_span, "captured outer variable" )
385506 . with_span_label (
386- self . infcx . tcx . def_span ( def_id ) ,
507+ closure_span ,
387508 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
389517 }
390518 _ => {
391519 let source = self . borrowed_content_source ( deref_base) ;
@@ -415,7 +543,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
415543 ) ,
416544 ( _, _, _) => self . cannot_move_out_of (
417545 span,
418- & source. describe_for_unnamed_place ( self . infcx . tcx ) ,
546+ & source. describe_for_unnamed_place ( tcx) ,
419547 ) ,
420548 }
421549 }
0 commit comments