@@ -9,7 +9,7 @@ use rustc_data_structures::fx::FxIndexSet;
99use rustc_errors:: { codes:: * , struct_span_code_err, Applicability , DiagnosticBuilder , MultiSpan } ;
1010use rustc_hir as hir;
1111use 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 } ;
1313use rustc_hir:: { CoroutineDesugaring , PatField } ;
1414use rustc_hir:: { CoroutineKind , CoroutineSource , LangItem } ;
1515use rustc_infer:: traits:: ObligationCause ;
@@ -794,9 +794,9 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
794794 let e = match node {
795795 hir:: Node :: Expr ( e) => e,
796796 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 ! [ ] } ;
798798 finder. visit_block ( els) ;
799- if finder. found_break {
799+ if ! finder. found_breaks . is_empty ( ) {
800800 // Don't suggest clone as it could be will likely end in an infinite
801801 // loop.
802802 // let Some(_) = foo(non_copy.clone()) else { break; }
@@ -845,51 +845,148 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
845845 _ => { }
846846 }
847847 }
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 ( ) ;
848856
849857 /// Look for `break` expressions within any arbitrary expressions. We'll do this to infer
850858 /// whether this is a case where the moved value would affect the exit of a loop, making it
851859 /// unsuitable for a `.clone()` suggestion.
852860 struct BreakFinder {
853- found_break : bool ,
861+ found_breaks : Vec < ( hir:: Destination , Span ) > ,
862+ found_continues : Vec < ( hir:: Destination , Span ) > ,
854863 }
855864 impl < ' hir > Visitor < ' hir > for BreakFinder {
856865 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+ _ => { }
859874 }
860875 hir:: intravisit:: walk_expr ( self , ex) ;
861876 }
862877 }
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`.
870878
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 ! [ ] } ;
873882 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+ }
884923 } ;
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+ }
893990 }
894991 }
895992 can_suggest_clone
0 commit comments