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