1+ use rustc_abi:: FieldIdx ;
12use rustc_data_structures:: fx:: FxHashSet ;
23use rustc_errors:: { Applicability , Diag } ;
34use rustc_hir:: intravisit:: Visitor ;
@@ -7,7 +8,7 @@ use rustc_middle::mir::*;
78use rustc_middle:: ty:: { self , Ty , TyCtxt } ;
89use rustc_mir_dataflow:: move_paths:: { LookupResult , MovePathIndex } ;
910use rustc_span:: def_id:: DefId ;
10- use rustc_span:: { BytePos , DUMMY_SP , ExpnKind , MacroKind , Span } ;
11+ use rustc_span:: { BytePos , ExpnKind , MacroKind , Span } ;
1112use rustc_trait_selection:: error_reporting:: traits:: FindExprBySpan ;
1213use rustc_trait_selection:: infer:: InferCtxtExt ;
1314use tracing:: debug;
@@ -472,49 +473,30 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
472473 if def_id. as_local ( ) == Some ( self . mir_def_id ( ) )
473474 && let Some ( upvar_field) = upvar_field =>
474475 {
475- let closure_kind_ty = closure_args. as_closure ( ) . kind_ty ( ) ;
476- let closure_kind = match closure_kind_ty. to_opt_closure_kind ( ) {
477- Some ( kind @ ( ty:: ClosureKind :: Fn | ty:: ClosureKind :: FnMut ) ) => kind,
478- Some ( ty:: ClosureKind :: FnOnce ) => {
479- bug ! ( "closure kind does not match first argument type" )
480- }
481- None => bug ! ( "closure kind not inferred by borrowck" ) ,
482- } ;
483- let capture_description =
484- format ! ( "captured variable in an `{closure_kind}` closure" ) ;
485-
486- let upvar = & self . upvars [ upvar_field. index ( ) ] ;
487- let upvar_hir_id = upvar. get_root_variable ( ) ;
488- let upvar_name = upvar. to_string ( tcx) ;
489- let upvar_span = tcx. hir_span ( upvar_hir_id) ;
490-
491- let place_name = self . describe_any_place ( move_place. as_ref ( ) ) ;
492-
493- let place_description =
494- if self . is_upvar_field_projection ( move_place. as_ref ( ) ) . is_some ( ) {
495- format ! ( "{place_name}, a {capture_description}" )
496- } else {
497- format ! ( "{place_name}, as `{upvar_name}` is a {capture_description}" )
498- } ;
499-
500- debug ! (
501- "report: closure_kind_ty={:?} closure_kind={:?} place_description={:?}" ,
502- closure_kind_ty, closure_kind, place_description,
503- ) ;
504-
505- let closure_span = tcx. def_span ( def_id) ;
506-
507- self . cannot_move_out_of ( span, & place_description)
508- . with_span_label ( upvar_span, "captured outer variable" )
509- . with_span_label (
510- closure_span,
511- format ! ( "captured by this `{closure_kind}` closure" ) ,
512- )
513- . with_span_help (
514- self . get_closure_bound_clause_span ( * def_id) ,
515- "`Fn` and `FnMut` closures require captured values to be able to be \
516- consumed multiple times, but `FnOnce` closures may consume them only once",
517- )
476+ self . report_closure_move_error (
477+ span,
478+ move_place,
479+ * def_id,
480+ closure_args. as_closure ( ) . kind_ty ( ) ,
481+ upvar_field,
482+ ty:: Asyncness :: No ,
483+ )
484+ }
485+ ty:: CoroutineClosure ( def_id, closure_args)
486+ if def_id. as_local ( ) == Some ( self . mir_def_id ( ) )
487+ && let Some ( upvar_field) = upvar_field
488+ && self
489+ . get_closure_bound_clause_span ( * def_id, ty:: Asyncness :: Yes )
490+ . is_some ( ) =>
491+ {
492+ self . report_closure_move_error (
493+ span,
494+ move_place,
495+ * def_id,
496+ closure_args. as_coroutine_closure ( ) . kind_ty ( ) ,
497+ upvar_field,
498+ ty:: Asyncness :: Yes ,
499+ )
518500 }
519501 _ => {
520502 let source = self . borrowed_content_source ( deref_base) ;
@@ -563,45 +545,134 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
563545 err
564546 }
565547
566- fn get_closure_bound_clause_span ( & self , def_id : DefId ) -> Span {
548+ fn report_closure_move_error (
549+ & self ,
550+ span : Span ,
551+ move_place : Place < ' tcx > ,
552+ def_id : DefId ,
553+ closure_kind_ty : Ty < ' tcx > ,
554+ upvar_field : FieldIdx ,
555+ asyncness : ty:: Asyncness ,
556+ ) -> Diag < ' infcx > {
557+ let tcx = self . infcx . tcx ;
558+
559+ let closure_kind = match closure_kind_ty. to_opt_closure_kind ( ) {
560+ Some ( kind @ ( ty:: ClosureKind :: Fn | ty:: ClosureKind :: FnMut ) ) => kind,
561+ Some ( ty:: ClosureKind :: FnOnce ) => {
562+ bug ! ( "closure kind does not match first argument type" )
563+ }
564+ None => bug ! ( "closure kind not inferred by borrowck" ) ,
565+ } ;
566+
567+ let async_prefix = if asyncness. is_async ( ) { "Async" } else { "" } ;
568+ let capture_description =
569+ format ! ( "captured variable in an `{async_prefix}{closure_kind}` closure" ) ;
570+
571+ let upvar = & self . upvars [ upvar_field. index ( ) ] ;
572+ let upvar_hir_id = upvar. get_root_variable ( ) ;
573+ let upvar_name = upvar. to_string ( tcx) ;
574+ let upvar_span = tcx. hir_span ( upvar_hir_id) ;
575+
576+ let place_name = self . describe_any_place ( move_place. as_ref ( ) ) ;
577+
578+ let place_description = if self . is_upvar_field_projection ( move_place. as_ref ( ) ) . is_some ( ) {
579+ format ! ( "{place_name}, a {capture_description}" )
580+ } else {
581+ format ! ( "{place_name}, as `{upvar_name}` is a {capture_description}" )
582+ } ;
583+
584+ debug ! ( ?closure_kind_ty, ?closure_kind, ?place_description) ;
585+
586+ let closure_span = tcx. def_span ( def_id) ;
587+
588+ let help_msg = format ! (
589+ "`{async_prefix}Fn` and `{async_prefix}FnMut` closures require captured values to \
590+ be able to be consumed multiple times, but `{async_prefix}FnOnce` closures may \
591+ consume them only once"
592+ ) ;
593+
594+ let mut err = self
595+ . cannot_move_out_of ( span, & place_description)
596+ . with_span_label ( upvar_span, "captured outer variable" )
597+ . with_span_label (
598+ closure_span,
599+ format ! ( "captured by this `{async_prefix}{closure_kind}` closure" ) ,
600+ ) ;
601+
602+ if let Some ( bound_span) = self . get_closure_bound_clause_span ( def_id, asyncness) {
603+ err. span_help ( bound_span, help_msg) ;
604+ } else if !asyncness. is_async ( ) {
605+ // For sync closures, always emit the help message even without a span.
606+ // For async closures, we only enter this branch if we found a valid span
607+ // (due to the match guard), so no fallback is needed.
608+ err. help ( help_msg) ;
609+ }
610+
611+ err
612+ }
613+
614+ fn get_closure_bound_clause_span (
615+ & self ,
616+ def_id : DefId ,
617+ asyncness : ty:: Asyncness ,
618+ ) -> Option < Span > {
567619 let tcx = self . infcx . tcx ;
568620 let typeck_result = tcx. typeck ( self . mir_def_id ( ) ) ;
569621 // Check whether the closure is an argument to a call, if so,
570622 // get the instantiated where-bounds of that call.
571623 let closure_hir_id = tcx. local_def_id_to_hir_id ( def_id. expect_local ( ) ) ;
572- let hir:: Node :: Expr ( parent) = tcx. parent_hir_node ( closure_hir_id) else { return DUMMY_SP } ;
624+ let hir:: Node :: Expr ( parent) = tcx. parent_hir_node ( closure_hir_id) else { return None } ;
573625
574626 let predicates = match parent. kind {
575627 hir:: ExprKind :: Call ( callee, _) => {
576- let Some ( ty ) = typeck_result. node_type_opt ( callee. hir_id ) else { return DUMMY_SP } ;
577- let ty:: FnDef ( fn_def_id, args) = ty. kind ( ) else { return DUMMY_SP } ;
628+ let ty = typeck_result. node_type_opt ( callee. hir_id ) ? ;
629+ let ty:: FnDef ( fn_def_id, args) = ty. kind ( ) else { return None } ;
578630 tcx. predicates_of ( fn_def_id) . instantiate ( tcx, args)
579631 }
580632 hir:: ExprKind :: MethodCall ( ..) => {
581- let Some ( ( _, method) ) = typeck_result. type_dependent_def ( parent. hir_id ) else {
582- return DUMMY_SP ;
583- } ;
633+ let ( _, method) = typeck_result. type_dependent_def ( parent. hir_id ) ?;
584634 let args = typeck_result. node_args ( parent. hir_id ) ;
585635 tcx. predicates_of ( method) . instantiate ( tcx, args)
586636 }
587- _ => return DUMMY_SP ,
637+ _ => return None ,
588638 } ;
589639
590- // Check whether one of the where-bounds requires the closure to impl `Fn[Mut]`.
640+ // Check whether one of the where-bounds requires the closure to impl `Fn[Mut]`
641+ // or `AsyncFn[Mut]`.
591642 for ( pred, span) in predicates. predicates . iter ( ) . zip ( predicates. spans . iter ( ) ) {
592- if let Some ( clause) = pred. as_trait_clause ( )
593- && let ty:: Closure ( clause_closure_def_id, _) = clause. self_ty ( ) . skip_binder ( ) . kind ( )
594- && * clause_closure_def_id == def_id
595- && ( tcx. lang_items ( ) . fn_mut_trait ( ) == Some ( clause. def_id ( ) )
596- || tcx. lang_items ( ) . fn_trait ( ) == Some ( clause. def_id ( ) ) )
597- {
598- // Found `<TyOfCapturingClosure as FnMut>`
599- // We point at the `Fn()` or `FnMut()` bound that coerced the closure, which
600- // could be changed to `FnOnce()` to avoid the move error.
601- return * span;
643+ let dominated_by_fn_trait = self
644+ . closure_clause_kind ( * pred, def_id, asyncness)
645+ . is_some_and ( |kind| matches ! ( kind, ty:: ClosureKind :: Fn | ty:: ClosureKind :: FnMut ) ) ;
646+ if dominated_by_fn_trait {
647+ // Found `<TyOfCapturingClosure as FnMut>` or
648+ // `<TyOfCapturingClosure as AsyncFnMut>`.
649+ // We point at the bound that coerced the closure, which could be changed
650+ // to `FnOnce()` or `AsyncFnOnce()` to avoid the move error.
651+ return Some ( * span) ;
602652 }
603653 }
604- DUMMY_SP
654+ None
655+ }
656+
657+ /// If `pred` is a trait clause binding the closure `def_id` to `Fn`/`FnMut`/`FnOnce`
658+ /// (or their async equivalents based on `asyncness`), returns the corresponding
659+ /// `ClosureKind`. Otherwise returns `None`.
660+ fn closure_clause_kind (
661+ & self ,
662+ pred : ty:: Clause < ' tcx > ,
663+ def_id : DefId ,
664+ asyncness : ty:: Asyncness ,
665+ ) -> Option < ty:: ClosureKind > {
666+ let tcx = self . infcx . tcx ;
667+ let clause = pred. as_trait_clause ( ) ?;
668+ let kind = match asyncness {
669+ ty:: Asyncness :: Yes => tcx. async_fn_trait_kind_from_def_id ( clause. def_id ( ) ) ,
670+ ty:: Asyncness :: No => tcx. fn_trait_kind_from_def_id ( clause. def_id ( ) ) ,
671+ } ?;
672+ match clause. self_ty ( ) . skip_binder ( ) . kind ( ) {
673+ ty:: Closure ( id, _) | ty:: CoroutineClosure ( id, _) if * id == def_id => Some ( kind) ,
674+ _ => None ,
675+ }
605676 }
606677
607678 fn add_move_hints ( & self , error : GroupedMoveError < ' tcx > , err : & mut Diag < ' _ > , span : Span ) {
0 commit comments