@@ -443,8 +443,8 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
443443 err. span_note (
444444 span,
445445 format ! (
446- "consider changing this parameter type in {descr} `{ident}` to \
447- borrow instead if owning the value isn't necessary",
446+ "consider changing this parameter type in {descr} `{ident}` to borrow \
447+ instead if owning the value isn't necessary",
448448 ) ,
449449 ) ;
450450 }
@@ -738,6 +738,163 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
738738 true
739739 }
740740
741+ /// In a move error that occurs on a call wihtin a loop, we try to identify cases where cloning
742+ /// the value would lead to a logic error. We infer these cases by seeing if the moved value is
743+ /// part of the logic to break the loop, either through an explicit `break` or if the expression
744+ /// is part of a `while let`.
745+ fn suggest_hoisting_call_outside_loop (
746+ & self ,
747+ err : & mut DiagnosticBuilder < ' _ > ,
748+ expr : & hir:: Expr < ' _ > ,
749+ ) -> bool {
750+ let tcx = self . infcx . tcx ;
751+ let mut can_suggest_clone = true ;
752+
753+ // If the moved value is a locally declared binding, we'll look upwards on the expression
754+ // tree until the scope where it is defined, and no further, as suggesting to move the
755+ // expression beyond that point would be illogical.
756+ let local_hir_id = if let hir:: ExprKind :: Path ( hir:: QPath :: Resolved (
757+ _,
758+ hir:: Path { res : hir:: def:: Res :: Local ( local_hir_id) , .. } ,
759+ ) ) = expr. kind
760+ {
761+ Some ( local_hir_id)
762+ } else {
763+ // This case would be if the moved value comes from an argument binding, we'll just
764+ // look within the entire item, that's fine.
765+ None
766+ } ;
767+
768+ /// This will allow us to look for a specific `HirId`, in our case `local_hir_id` where the
769+ /// binding was declared, within any other expression. We'll use it to search for the
770+ /// binding declaration within every scope we inspect.
771+ struct Finder {
772+ hir_id : hir:: HirId ,
773+ found : bool ,
774+ }
775+ impl < ' hir > Visitor < ' hir > for Finder {
776+ fn visit_pat ( & mut self , pat : & ' hir hir:: Pat < ' hir > ) {
777+ if pat. hir_id == self . hir_id {
778+ self . found = true ;
779+ }
780+ hir:: intravisit:: walk_pat ( self , pat) ;
781+ }
782+ fn visit_expr ( & mut self , ex : & ' hir hir:: Expr < ' hir > ) {
783+ if ex. hir_id == self . hir_id {
784+ self . found = true ;
785+ }
786+ hir:: intravisit:: walk_expr ( self , ex) ;
787+ }
788+ }
789+ // The immediate HIR parent of the moved expression. We'll look for it to be a call.
790+ let mut parent = None ;
791+ // The top-most loop where the moved expression could be moved to a new binding.
792+ let mut outer_most_loop: Option < & hir:: Expr < ' _ > > = None ;
793+ for ( _, node) in tcx. hir ( ) . parent_iter ( expr. hir_id ) {
794+ let e = match node {
795+ hir:: Node :: Expr ( e) => e,
796+ hir:: Node :: Local ( hir:: Local { els : Some ( els) , .. } ) => {
797+ let mut finder = BreakFinder { found_break : false } ;
798+ finder. visit_block ( els) ;
799+ if finder. found_break {
800+ // Don't suggest clone as it could be will likely end in an infinite
801+ // loop.
802+ // let Some(_) = foo(non_copy.clone()) else { break; }
803+ // --- ^^^^^^^^ -----
804+ can_suggest_clone = false ;
805+ }
806+ continue ;
807+ }
808+ _ => continue ,
809+ } ;
810+ if let Some ( & hir_id) = local_hir_id {
811+ let mut finder = Finder { hir_id, found : false } ;
812+ finder. visit_expr ( e) ;
813+ if finder. found {
814+ // The current scope includes the declaration of the binding we're accessing, we
815+ // can't look up any further for loops.
816+ break ;
817+ }
818+ }
819+ if parent. is_none ( ) {
820+ parent = Some ( e) ;
821+ }
822+ match e. kind {
823+ hir:: ExprKind :: Let ( _) => {
824+ match tcx. parent_hir_node ( e. hir_id ) {
825+ hir:: Node :: Expr ( hir:: Expr {
826+ kind : hir:: ExprKind :: If ( cond, ..) , ..
827+ } ) => {
828+ let mut finder = Finder { hir_id : expr. hir_id , found : false } ;
829+ finder. visit_expr ( cond) ;
830+ if finder. found {
831+ // The expression where the move error happened is in a `while let`
832+ // condition Don't suggest clone as it will likely end in an
833+ // infinite loop.
834+ // while let Some(_) = foo(non_copy.clone()) { }
835+ // --------- ^^^^^^^^
836+ can_suggest_clone = false ;
837+ }
838+ }
839+ _ => { }
840+ }
841+ }
842+ hir:: ExprKind :: Loop ( ..) => {
843+ outer_most_loop = Some ( e) ;
844+ }
845+ _ => { }
846+ }
847+ }
848+
849+ /// Look for `break` expressions within any arbitrary expressions. We'll do this to infer
850+ /// whether this is a case where the moved value would affect the exit of a loop, making it
851+ /// unsuitable for a `.clone()` suggestion.
852+ struct BreakFinder {
853+ found_break : bool ,
854+ }
855+ impl < ' hir > Visitor < ' hir > for BreakFinder {
856+ fn visit_expr ( & mut self , ex : & ' hir hir:: Expr < ' hir > ) {
857+ if let hir:: ExprKind :: Break ( ..) = ex. kind {
858+ self . found_break = true ;
859+ }
860+ hir:: intravisit:: walk_expr ( self , ex) ;
861+ }
862+ }
863+ if let Some ( in_loop) = outer_most_loop
864+ && let Some ( parent) = parent
865+ && let hir:: ExprKind :: MethodCall ( ..) | hir:: ExprKind :: Call ( ..) = parent. kind
866+ {
867+ // FIXME: We could check that the call's *parent* takes `&mut val` to make the
868+ // suggestion more targeted to the `mk_iter(val).next()` case. Maybe do that only to
869+ // check for wheter to suggest `let value` or `let mut value`.
870+
871+ let span = in_loop. span ;
872+ let mut finder = BreakFinder { found_break : false } ;
873+ finder. visit_expr ( in_loop) ;
874+ let sm = tcx. sess . source_map ( ) ;
875+ if ( finder. found_break || true )
876+ && let Ok ( value) = sm. span_to_snippet ( parent. span )
877+ {
878+ // We know with high certainty that this move would affect the early return of a
879+ // loop, so we suggest moving the expression with the move out of the loop.
880+ let indent = if let Some ( indent) = sm. indentation_before ( span) {
881+ format ! ( "\n {indent}" )
882+ } else {
883+ " " . to_string ( )
884+ } ;
885+ err. multipart_suggestion (
886+ "consider moving the expression out of the loop so it is only moved once" ,
887+ vec ! [
888+ ( parent. span, "value" . to_string( ) ) ,
889+ ( span. shrink_to_lo( ) , format!( "let mut value = {value};{indent}" ) ) ,
890+ ] ,
891+ Applicability :: MaybeIncorrect ,
892+ ) ;
893+ }
894+ }
895+ can_suggest_clone
896+ }
897+
741898 fn suggest_cloning (
742899 & self ,
743900 err : & mut DiagnosticBuilder < ' _ > ,
@@ -746,6 +903,11 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
746903 span : Span ,
747904 ) {
748905 let tcx = self . infcx . tcx ;
906+ if !self . suggest_hoisting_call_outside_loop ( err, expr) {
907+ // The place where the the type moves would be misleading to suggest clone. (#121466)
908+ return ;
909+ }
910+
749911 // Try to find predicates on *generic params* that would allow copying `ty`
750912 let suggestion =
751913 if let Some ( symbol) = tcx. hir ( ) . maybe_get_struct_pattern_shorthand_field ( expr) {
0 commit comments