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,122 @@ 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+ 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" ,
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" ,
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+
306413 fn report_cannot_move_from_borrowed_content (
307414 & mut self ,
308415 move_place : Place < ' tcx > ,
309416 deref_target_place : Place < ' tcx > ,
310417 span : Span ,
311418 use_spans : Option < UseSpans < ' tcx > > ,
312419 ) -> Diag < ' tcx > {
420+ let tcx = self . infcx . tcx ;
313421 // Inspect the type of the content behind the
314422 // borrow to provide feedback about why this
315423 // 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 ;
317425 let upvar_field = self
318426 . prefixes ( move_place. as_ref ( ) , PrefixSet :: All )
319427 . find_map ( |p| self . is_upvar_field_projection ( p) ) ;
@@ -363,8 +471,8 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
363471
364472 let upvar = & self . upvars [ upvar_field. unwrap ( ) . index ( ) ] ;
365473 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) ;
368476
369477 let place_name = self . describe_any_place ( move_place. as_ref ( ) ) ;
370478
@@ -380,12 +488,21 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
380488 closure_kind_ty, closure_kind, place_description,
381489 ) ;
382490
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)
384494 . with_span_label ( upvar_span, "captured outer variable" )
385495 . with_span_label (
386- self . infcx . tcx . def_span ( def_id ) ,
496+ closure_span ,
387497 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
389506 }
390507 _ => {
391508 let source = self . borrowed_content_source ( deref_base) ;
@@ -415,7 +532,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
415532 ) ,
416533 ( _, _, _) => self . cannot_move_out_of (
417534 span,
418- & source. describe_for_unnamed_place ( self . infcx . tcx ) ,
535+ & source. describe_for_unnamed_place ( tcx) ,
419536 ) ,
420537 }
421538 }
0 commit comments