@@ -9,7 +9,7 @@ use rustc_data_structures::fx::FxIndexSet;
99use rustc_errors:: { codes:: * , struct_span_code_err, Applicability , Diag , 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 ;
@@ -795,9 +795,9 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
795795 let e = match node {
796796 hir:: Node :: Expr ( e) => e,
797797 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 ! [ ] } ;
799799 finder. visit_block ( els) ;
800- if finder. found_break {
800+ if ! finder. found_breaks . is_empty ( ) {
801801 // Don't suggest clone as it could be will likely end in an infinite
802802 // loop.
803803 // let Some(_) = foo(non_copy.clone()) else { break; }
@@ -846,51 +846,148 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
846846 _ => { }
847847 }
848848 }
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 ( ) ;
849857
850858 /// Look for `break` expressions within any arbitrary expressions. We'll do this to infer
851859 /// whether this is a case where the moved value would affect the exit of a loop, making it
852860 /// unsuitable for a `.clone()` suggestion.
853861 struct BreakFinder {
854- found_break : bool ,
862+ found_breaks : Vec < ( hir:: Destination , Span ) > ,
863+ found_continues : Vec < ( hir:: Destination , Span ) > ,
855864 }
856865 impl < ' hir > Visitor < ' hir > for BreakFinder {
857866 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+ _ => { }
860875 }
861876 hir:: intravisit:: walk_expr ( self , ex) ;
862877 }
863878 }
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`.
871879
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 ! [ ] } ;
874883 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+ }
885924 } ;
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+ }
894991 }
895992 }
896993 can_suggest_clone
0 commit comments