@@ -443,8 +443,8 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
443
443
err. span_note (
444
444
span,
445
445
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",
448
448
) ,
449
449
) ;
450
450
}
@@ -738,6 +738,163 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
738
738
true
739
739
}
740
740
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
+
741
898
fn suggest_cloning (
742
899
& self ,
743
900
err : & mut DiagnosticBuilder < ' _ > ,
@@ -746,6 +903,11 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
746
903
span : Span ,
747
904
) {
748
905
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
+
749
911
// Try to find predicates on *generic params* that would allow copying `ty`
750
912
let suggestion =
751
913
if let Some ( symbol) = tcx. hir ( ) . maybe_get_struct_pattern_shorthand_field ( expr) {
0 commit comments