@@ -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_middle:: hir:: nested_filter:: OnlyBodies ;
@@ -799,9 +799,9 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
799799 let e = match node {
800800 hir:: Node :: Expr ( e) => e,
801801 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 ! [ ] } ;
803803 finder. visit_block ( els) ;
804- if finder. found_break {
804+ if ! finder. found_breaks . is_empty ( ) {
805805 // Don't suggest clone as it could be will likely end in an infinite
806806 // loop.
807807 // let Some(_) = foo(non_copy.clone()) else { break; }
@@ -850,51 +850,148 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
850850 _ => { }
851851 }
852852 }
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 ( ) ;
853861
854862 /// Look for `break` expressions within any arbitrary expressions. We'll do this to infer
855863 /// whether this is a case where the moved value would affect the exit of a loop, making it
856864 /// unsuitable for a `.clone()` suggestion.
857865 struct BreakFinder {
858- found_break : bool ,
866+ found_breaks : Vec < ( hir:: Destination , Span ) > ,
867+ found_continues : Vec < ( hir:: Destination , Span ) > ,
859868 }
860869 impl < ' hir > Visitor < ' hir > for BreakFinder {
861870 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+ _ => { }
864879 }
865880 hir:: intravisit:: walk_expr ( self , ex) ;
866881 }
867882 }
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`.
875883
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 ! [ ] } ;
878887 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+ }
889928 } ;
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+ }
898995 }
899996 }
900997 can_suggest_clone
0 commit comments