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