@@ -12,7 +12,7 @@ use rustc_middle::mir::{
1212 FakeReadCause , LocalDecl , LocalInfo , LocalKind , Location , Operand , Place , PlaceRef ,
1313 ProjectionElem , Rvalue , Statement , StatementKind , Terminator , TerminatorKind , VarBindingForm ,
1414} ;
15- use rustc_middle:: ty:: { self , suggest_constraining_type_params, PredicateKind , Ty } ;
15+ use rustc_middle:: ty:: { self , subst :: Subst , suggest_constraining_type_params, PredicateKind , Ty } ;
1616use rustc_mir_dataflow:: move_paths:: { InitKind , MoveOutIndex , MovePathIndex } ;
1717use rustc_span:: symbol:: sym;
1818use rustc_span:: { BytePos , MultiSpan , Span } ;
@@ -276,22 +276,25 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
276276 }
277277 }
278278
279- if needs_note {
280- let opt_name =
281- self . describe_place_with_options ( place. as_ref ( ) , IncludingDowncast ( true ) ) ;
282- let note_msg = match opt_name {
283- Some ( ref name) => format ! ( "`{}`" , name) ,
284- None => "value" . to_owned ( ) ,
285- } ;
279+ let opt_name =
280+ self . describe_place_with_options ( place. as_ref ( ) , IncludingDowncast ( true ) ) ;
281+ let note_msg = match opt_name {
282+ Some ( ref name) => format ! ( "`{}`" , name) ,
283+ None => "value" . to_owned ( ) ,
284+ } ;
285+
286+ let tcx = self . infcx . tcx ;
287+ let generics = tcx. generics_of ( self . mir_def_id ( ) ) ;
286288
287- // Try to find predicates on *generic params* that would allow copying `ty`
288- let tcx = self . infcx . tcx ;
289- let generics = tcx . generics_of ( self . mir_def_id ( ) ) ;
289+ if self . suggest_borrow_fn_like ( & mut err , ty , & move_site_vec , & note_msg ) {
290+ // Suppress the next note, since we don't want to put more `Fn`-like bounds onto something that already has them
291+ } else if needs_note {
290292 if let Some ( hir_generics) = tcx
291293 . typeck_root_def_id ( self . mir_def_id ( ) . to_def_id ( ) )
292294 . as_local ( )
293295 . and_then ( |def_id| tcx. hir ( ) . get_generics ( def_id) )
294296 {
297+ // Try to find predicates on *generic params* that would allow copying `ty`
295298 let predicates: Result < Vec < _ > , _ > = tcx. infer_ctxt ( ) . enter ( |infcx| {
296299 let mut fulfill_cx =
297300 <dyn rustc_infer:: traits:: TraitEngine < ' _ > >:: new ( infcx. tcx ) ;
@@ -344,8 +347,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
344347 }
345348
346349 let span = if let Some ( local) = place. as_local ( ) {
347- let decl = & self . body . local_decls [ local] ;
348- Some ( decl. source_info . span )
350+ Some ( self . body . local_decls [ local] . source_info . span )
349351 } else {
350352 None
351353 } ;
@@ -373,6 +375,81 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
373375 }
374376 }
375377
378+ fn suggest_borrow_fn_like (
379+ & self ,
380+ err : & mut DiagnosticBuilder < ' tcx , ErrorGuaranteed > ,
381+ ty : Ty < ' tcx > ,
382+ move_sites : & [ MoveSite ] ,
383+ value_name : & str ,
384+ ) -> bool {
385+ let tcx = self . infcx . tcx ;
386+
387+ // Find out if the predicates show that the type is a Fn or FnMut
388+ let find_fn_kind_from_did = |predicates : & [ ( ty:: Predicate < ' tcx > , Span ) ] , substs| {
389+ predicates. iter ( ) . find_map ( |( pred, _) | {
390+ let pred = if let Some ( substs) = substs {
391+ pred. subst ( tcx, substs) . kind ( ) . skip_binder ( )
392+ } else {
393+ pred. kind ( ) . skip_binder ( )
394+ } ;
395+ if let ty:: PredicateKind :: Trait ( pred) = pred && pred. self_ty ( ) == ty {
396+ if Some ( pred. def_id ( ) ) == tcx. lang_items ( ) . fn_trait ( ) {
397+ return Some ( hir:: Mutability :: Not ) ;
398+ } else if Some ( pred. def_id ( ) ) == tcx. lang_items ( ) . fn_mut_trait ( ) {
399+ return Some ( hir:: Mutability :: Mut ) ;
400+ }
401+ }
402+ None
403+ } )
404+ } ;
405+
406+ // If the type is opaque/param/closure, and it is Fn or FnMut, let's suggest (mutably)
407+ // borrowing the type, since `&mut F: FnMut` iff `F: FnMut` and similarly for `Fn`.
408+ // These types seem reasonably opaque enough that they could be substituted with their
409+ // borrowed variants in a function body when we see a move error.
410+ let borrow_level = match ty. kind ( ) {
411+ ty:: Param ( _) => find_fn_kind_from_did (
412+ tcx. explicit_predicates_of ( self . mir_def_id ( ) . to_def_id ( ) ) . predicates ,
413+ None ,
414+ ) ,
415+ ty:: Opaque ( did, substs) => {
416+ find_fn_kind_from_did ( tcx. explicit_item_bounds ( * did) , Some ( * substs) )
417+ }
418+ ty:: Closure ( _, substs) => match substs. as_closure ( ) . kind ( ) {
419+ ty:: ClosureKind :: Fn => Some ( hir:: Mutability :: Not ) ,
420+ ty:: ClosureKind :: FnMut => Some ( hir:: Mutability :: Mut ) ,
421+ _ => None ,
422+ } ,
423+ _ => None ,
424+ } ;
425+
426+ let Some ( borrow_level) = borrow_level else { return false ; } ;
427+ let sugg = move_sites
428+ . iter ( )
429+ . map ( |move_site| {
430+ let move_out = self . move_data . moves [ ( * move_site) . moi ] ;
431+ let moved_place = & self . move_data . move_paths [ move_out. path ] . place ;
432+ let move_spans = self . move_spans ( moved_place. as_ref ( ) , move_out. source ) ;
433+ let move_span = move_spans. args_or_use ( ) ;
434+ let suggestion = if borrow_level == hir:: Mutability :: Mut {
435+ "&mut " . to_string ( )
436+ } else {
437+ "&" . to_string ( )
438+ } ;
439+ ( move_span. shrink_to_lo ( ) , suggestion)
440+ } )
441+ . collect ( ) ;
442+ err. multipart_suggestion_verbose (
443+ & format ! (
444+ "consider {}borrowing {value_name}" ,
445+ if borrow_level == hir:: Mutability :: Mut { "mutably " } else { "" }
446+ ) ,
447+ sugg,
448+ Applicability :: MaybeIncorrect ,
449+ ) ;
450+ true
451+ }
452+
376453 pub ( crate ) fn report_move_out_while_borrowed (
377454 & mut self ,
378455 location : Location ,
0 commit comments