@@ -476,3 +476,144 @@ fn goto_block_nested_loop_break_targets_inner() {
476476 } ) ;
477477 assert_eq ! ( sum, 103 ) ;
478478}
479+
480+ #[ derive( Clone , Copy , Debug , PartialEq ) ]
481+ enum Exit {
482+ None ,
483+ Break ,
484+ Continue ,
485+ LabelledBreak ,
486+ LabelledContinue ,
487+ }
488+
489+ // Build the entire invocation before expanding a procedural macro, so the
490+ // outer macro sees the nested control-flow macros rather than a test helper.
491+ macro_rules! wrap {
492+ ( $trace: ident, $i: ident; [ $( $body: tt) * ] ; ) => { $( $body) * } ;
493+ ( $trace: ident, $i: ident; [ $( $body: tt) * ] ; G $( $rest: tt) * ) => {
494+ wrap!( $trace, $i; [
495+ $trace. push( ( $i, "goto enter" ) ) ;
496+ goto_block!( { ' entry: { $( $body) * } } ) ;
497+ $trace. push( ( $i, "goto tail" ) ) ;
498+ ] ; $( $rest) * ) ;
499+ } ;
500+ ( $trace: ident, $i: ident; [ $( $body: tt) * ] ; S $( $rest: tt) * ) => {
501+ wrap!( $trace, $i; [
502+ $trace. push( ( $i, "switch enter" ) ) ;
503+ switch!( match 0 { _ => { $( $body) * } } ) ;
504+ $trace. push( ( $i, "switch tail" ) ) ;
505+ ] ; $( $rest) * ) ;
506+ } ;
507+ }
508+
509+ fn reference ( words : & [ & str ] , action : Exit , trigger : usize ) -> Vec < ( usize , & ' static str ) > {
510+ fn body ( words : & [ & str ] , exit : Exit , i : usize , trace : & mut Vec < ( usize , & ' static str ) > ) -> Exit {
511+ let Some ( ( word, rest) ) = words. split_last ( ) else {
512+ trace. push ( ( i, "action" ) ) ;
513+ if exit == Exit :: None {
514+ trace. push ( ( i, "after action" ) ) ;
515+ }
516+ return exit;
517+ } ;
518+ let is_switch = * word == "S" ;
519+ trace. push ( (
520+ i,
521+ if is_switch {
522+ "switch enter"
523+ } else {
524+ "goto enter"
525+ } ,
526+ ) ) ;
527+ let mut exit = body ( rest, exit, i, trace) ;
528+ if is_switch && exit == Exit :: Break {
529+ exit = Exit :: None ;
530+ }
531+ if exit == Exit :: None {
532+ trace. push ( (
533+ i,
534+ if is_switch {
535+ "switch tail"
536+ } else {
537+ "goto tail"
538+ } ,
539+ ) ) ;
540+ }
541+ exit
542+ }
543+ let mut trace = Vec :: new ( ) ;
544+ for i in 0 ..3 {
545+ trace. push ( ( i, "enter" ) ) ;
546+ let exit = if i == trigger { action } else { Exit :: None } ;
547+ match body ( words, exit, i, & mut trace) {
548+ Exit :: Break | Exit :: LabelledBreak => break ,
549+ Exit :: Continue | Exit :: LabelledContinue => continue ,
550+ Exit :: None => trace. push ( ( i, "tail" ) ) ,
551+ }
552+ }
553+ trace
554+ }
555+
556+ #[ test]
557+ fn nested_loop_exit_targets ( ) {
558+ let mut mismatches = Vec :: new ( ) ;
559+ macro_rules! check {
560+ ( $( $words: tt) * ) => {
561+ for action in [ Exit :: None , Exit :: Break , Exit :: Continue ,
562+ Exit :: LabelledBreak , Exit :: LabelledContinue ] {
563+ for trigger in 0 ..4 {
564+ let mut trace = Vec :: new( ) ;
565+ let mut visits = 0 ;
566+ ' user: for i in 0 ..3 {
567+ trace. push( ( i, "enter" ) ) ;
568+ wrap!( trace, i; [
569+ visits += 1 ;
570+ if visits > 8 {
571+ trace. push( ( i, "visit limit" ) ) ;
572+ break ' user;
573+ }
574+ trace. push( ( i, "action" ) ) ;
575+ if i == trigger {
576+ match action {
577+ Exit :: None => { } ,
578+ Exit :: Break => break ,
579+ Exit :: Continue => continue ,
580+ Exit :: LabelledBreak => break ' user,
581+ Exit :: LabelledContinue => continue ' user,
582+ }
583+ }
584+ trace. push( ( i, "after action" ) ) ;
585+ ] ; $( $words) * ) ;
586+ trace. push( ( i, "tail" ) ) ;
587+ }
588+ let words: Vec <_> = stringify!( $( $words) * ) . split_whitespace( ) . collect( ) ;
589+ let expected = reference( & words, action, trigger) ;
590+ if trace != expected {
591+ mismatches. push( ( stringify!( $( $words) * ) , action, trigger, trace, expected) ) ;
592+ }
593+ }
594+ }
595+ } ;
596+ }
597+ // Reduced nested-goto replay first, then every other word of depth 0..3.
598+ check ! ( G G ) ;
599+ check ! ( ) ;
600+ check ! ( G ) ;
601+ check ! ( S ) ;
602+ check ! ( G S ) ;
603+ check ! ( S G ) ;
604+ check ! ( S S ) ;
605+ check ! ( G G G ) ;
606+ check ! ( G G S ) ;
607+ check ! ( G S G ) ;
608+ check ! ( G S S ) ;
609+ check ! ( S G G ) ;
610+ check ! ( S G S ) ;
611+ check ! ( S S G ) ;
612+ check ! ( S S S ) ;
613+ assert_eq ! (
614+ mismatches. len( ) ,
615+ 0 ,
616+ "loop-exit target traces differ; first mismatch: {:?}" ,
617+ mismatches. first( )
618+ ) ;
619+ }
0 commit comments