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