@@ -9,7 +9,7 @@ use rustc_data_structures::fx::FxIndexSet;
9
9
use rustc_errors:: { codes:: * , struct_span_code_err, Applicability , Diag , MultiSpan } ;
10
10
use rustc_hir as hir;
11
11
use rustc_hir:: def:: { DefKind , Res } ;
12
- use rustc_hir:: intravisit:: { walk_block, walk_expr, Visitor } ;
12
+ use rustc_hir:: intravisit:: { walk_block, walk_expr, Map , Visitor } ;
13
13
use rustc_hir:: { CoroutineDesugaring , PatField } ;
14
14
use rustc_hir:: { CoroutineKind , CoroutineSource , LangItem } ;
15
15
use rustc_infer:: traits:: ObligationCause ;
@@ -795,9 +795,9 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
795
795
let e = match node {
796
796
hir:: Node :: Expr ( e) => e,
797
797
hir:: Node :: Local ( hir:: Local { els : Some ( els) , .. } ) => {
798
- let mut finder = BreakFinder { found_break : false } ;
798
+ let mut finder = BreakFinder { found_breaks : vec ! [ ] , found_continues : vec ! [ ] } ;
799
799
finder. visit_block ( els) ;
800
- if finder. found_break {
800
+ if ! finder. found_breaks . is_empty ( ) {
801
801
// Don't suggest clone as it could be will likely end in an infinite
802
802
// loop.
803
803
// let Some(_) = foo(non_copy.clone()) else { break; }
@@ -846,51 +846,148 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
846
846
_ => { }
847
847
}
848
848
}
849
+ let loop_count: usize = tcx
850
+ . hir ( )
851
+ . parent_iter ( expr. hir_id )
852
+ . map ( |( _, node) | match node {
853
+ hir:: Node :: Expr ( hir:: Expr { kind : hir:: ExprKind :: Loop ( ..) , .. } ) => 1 ,
854
+ _ => 0 ,
855
+ } )
856
+ . sum ( ) ;
849
857
850
858
/// Look for `break` expressions within any arbitrary expressions. We'll do this to infer
851
859
/// whether this is a case where the moved value would affect the exit of a loop, making it
852
860
/// unsuitable for a `.clone()` suggestion.
853
861
struct BreakFinder {
854
- found_break : bool ,
862
+ found_breaks : Vec < ( hir:: Destination , Span ) > ,
863
+ found_continues : Vec < ( hir:: Destination , Span ) > ,
855
864
}
856
865
impl < ' hir > Visitor < ' hir > for BreakFinder {
857
866
fn visit_expr ( & mut self , ex : & ' hir hir:: Expr < ' hir > ) {
858
- if let hir:: ExprKind :: Break ( ..) = ex. kind {
859
- self . found_break = true ;
867
+ match ex. kind {
868
+ hir:: ExprKind :: Break ( destination, _) => {
869
+ self . found_breaks . push ( ( destination, ex. span ) ) ;
870
+ }
871
+ hir:: ExprKind :: Continue ( destination) => {
872
+ self . found_continues . push ( ( destination, ex. span ) ) ;
873
+ }
874
+ _ => { }
860
875
}
861
876
hir:: intravisit:: walk_expr ( self , ex) ;
862
877
}
863
878
}
864
- if let Some ( in_loop) = outer_most_loop
865
- && let Some ( parent) = parent
866
- && let hir:: ExprKind :: MethodCall ( ..) | hir:: ExprKind :: Call ( ..) = parent. kind
867
- {
868
- // FIXME: We could check that the call's *parent* takes `&mut val` to make the
869
- // suggestion more targeted to the `mk_iter(val).next()` case. Maybe do that only to
870
- // check for wheter to suggest `let value` or `let mut value`.
871
879
872
- let span = in_loop. span ;
873
- let mut finder = BreakFinder { found_break : false } ;
880
+ let sm = tcx. sess . source_map ( ) ;
881
+ if let Some ( in_loop) = outer_most_loop {
882
+ let mut finder = BreakFinder { found_breaks : vec ! [ ] , found_continues : vec ! [ ] } ;
874
883
finder. visit_expr ( in_loop) ;
875
- let sm = tcx. sess . source_map ( ) ;
876
- if ( finder. found_break || true )
877
- && let Ok ( value) = sm. span_to_snippet ( parent. span )
878
- {
879
- // We know with high certainty that this move would affect the early return of a
880
- // loop, so we suggest moving the expression with the move out of the loop.
881
- let indent = if let Some ( indent) = sm. indentation_before ( span) {
882
- format ! ( "\n {indent}" )
883
- } else {
884
- " " . to_string ( )
884
+ // All of the spans for `break` and `continue` expressions.
885
+ let spans = finder
886
+ . found_breaks
887
+ . iter ( )
888
+ . chain ( finder. found_continues . iter ( ) )
889
+ . map ( |( _, span) | * span)
890
+ . filter ( |span| {
891
+ !matches ! (
892
+ span. desugaring_kind( ) ,
893
+ Some ( DesugaringKind :: ForLoop | DesugaringKind :: WhileLoop )
894
+ )
895
+ } )
896
+ . collect :: < Vec < Span > > ( ) ;
897
+ // All of the spans for the loops above the expression with the move error.
898
+ let loop_spans: Vec < _ > = tcx
899
+ . hir ( )
900
+ . parent_iter ( expr. hir_id )
901
+ . filter_map ( |( _, node) | match node {
902
+ hir:: Node :: Expr ( hir:: Expr { span, kind : hir:: ExprKind :: Loop ( ..) , .. } ) => {
903
+ Some ( * span)
904
+ }
905
+ _ => None ,
906
+ } )
907
+ . collect ( ) ;
908
+ // It is possible that a user written `break` or `continue` is in the wrong place. We
909
+ // point them out at the user for them to make a determination. (#92531)
910
+ if !spans. is_empty ( ) && loop_count > 1 {
911
+ // Getting fancy: if the spans of the loops *do not* overlap, we only use the line
912
+ // number when referring to them. If there *are* overlaps (multiple loops on the
913
+ // same line) then we use the more verbose span output (`file.rs:col:ll`).
914
+ let mut lines: Vec < _ > =
915
+ loop_spans. iter ( ) . map ( |sp| sm. lookup_char_pos ( sp. lo ( ) ) . line ) . collect ( ) ;
916
+ lines. sort ( ) ;
917
+ lines. dedup ( ) ;
918
+ let fmt_span = |span : Span | {
919
+ if lines. len ( ) == loop_spans. len ( ) {
920
+ format ! ( "line {}" , sm. lookup_char_pos( span. lo( ) ) . line)
921
+ } else {
922
+ sm. span_to_diagnostic_string ( span)
923
+ }
885
924
} ;
886
- err. multipart_suggestion (
887
- "consider moving the expression out of the loop so it is only moved once" ,
888
- vec ! [
889
- ( parent. span, "value" . to_string( ) ) ,
890
- ( span. shrink_to_lo( ) , format!( "let mut value = {value};{indent}" ) ) ,
891
- ] ,
892
- Applicability :: MaybeIncorrect ,
893
- ) ;
925
+ let mut spans: MultiSpan = spans. clone ( ) . into ( ) ;
926
+ // Point at all the `continue`s and explicit `break`s in the relevant loops.
927
+ for ( desc, elements) in [
928
+ ( "`break` exits" , & finder. found_breaks ) ,
929
+ ( "`continue` advances" , & finder. found_continues ) ,
930
+ ] {
931
+ for ( destination, sp) in elements {
932
+ if let Ok ( hir_id) = destination. target_id
933
+ && let hir:: Node :: Expr ( expr) = tcx. hir ( ) . hir_node ( hir_id)
934
+ && !matches ! (
935
+ sp. desugaring_kind( ) ,
936
+ Some ( DesugaringKind :: ForLoop | DesugaringKind :: WhileLoop )
937
+ )
938
+ {
939
+ spans. push_span_label (
940
+ * sp,
941
+ format ! ( "this {desc} the loop at {}" , fmt_span( expr. span) ) ,
942
+ ) ;
943
+ }
944
+ }
945
+ }
946
+ // Point at all the loops that are between this move and the parent item.
947
+ for span in loop_spans {
948
+ spans. push_span_label ( sm. guess_head_span ( span) , "" ) ;
949
+ }
950
+
951
+ // note: verify that your loop breaking logic is correct
952
+ // --> $DIR/nested-loop-moved-value-wrong-continue.rs:41:17
953
+ // |
954
+ // 28 | for foo in foos {
955
+ // | ---------------
956
+ // ...
957
+ // 33 | for bar in &bars {
958
+ // | ----------------
959
+ // ...
960
+ // 41 | continue;
961
+ // | ^^^^^^^^ this `continue` advances the loop at line 33
962
+ err. span_note ( spans, "verify that your loop breaking logic is correct" ) ;
963
+ }
964
+ if let Some ( parent) = parent
965
+ && let hir:: ExprKind :: MethodCall ( ..) | hir:: ExprKind :: Call ( ..) = parent. kind
966
+ {
967
+ // FIXME: We could check that the call's *parent* takes `&mut val` to make the
968
+ // suggestion more targeted to the `mk_iter(val).next()` case. Maybe do that only to
969
+ // check for wheter to suggest `let value` or `let mut value`.
970
+
971
+ let span = in_loop. span ;
972
+ if !finder. found_breaks . is_empty ( )
973
+ && let Ok ( value) = sm. span_to_snippet ( parent. span )
974
+ {
975
+ // We know with high certainty that this move would affect the early return of a
976
+ // loop, so we suggest moving the expression with the move out of the loop.
977
+ let indent = if let Some ( indent) = sm. indentation_before ( span) {
978
+ format ! ( "\n {indent}" )
979
+ } else {
980
+ " " . to_string ( )
981
+ } ;
982
+ err. multipart_suggestion (
983
+ "consider moving the expression out of the loop so it is only moved once" ,
984
+ vec ! [
985
+ ( parent. span, "value" . to_string( ) ) ,
986
+ ( span. shrink_to_lo( ) , format!( "let mut value = {value};{indent}" ) ) ,
987
+ ] ,
988
+ Applicability :: MaybeIncorrect ,
989
+ ) ;
990
+ }
894
991
}
895
992
}
896
993
can_suggest_clone
0 commit comments