Skip to content

Commit d33cf56

Browse files
Test nested control-flow macro exit targets
Signed-off-by: Perfloop Agent <agent@perfloop.ai> Signed-off-by: Tomás Senart <ts@perfloop.ai>
1 parent e2b679d commit d33cf56

1 file changed

Lines changed: 141 additions & 0 deletions

File tree

‎libcc2rs-macros/tests/control_flow.rs‎

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)