Skip to content

Commit 34c9a03

Browse files
authored
Merge pull request #3138 from veryl-lang/fix/struct-member-ifdef-semicolon
Fix struct/union member separator emitted outside its ifdef guard
2 parents 09db295 + 93d4383 commit 34c9a03

2 files changed

Lines changed: 82 additions & 33 deletions

File tree

crates/emitter/src/emitter.rs

Lines changed: 46 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1149,6 +1149,40 @@ impl Emitter {
11491149
}
11501150
}
11511151

1152+
/// Pop any trailing `[Hardline, `endif]` pairs (one per group `attribute_end`)
1153+
/// so a separator can be emitted before the endif chain; re-emit with `.rev()`.
1154+
fn pop_trailing_endif_chain(&mut self) -> Vec<Doc> {
1155+
if matches!(self.mode, Mode::Align) {
1156+
return Vec::new();
1157+
}
1158+
let buf = self.doc_buffer.last_mut().unwrap();
1159+
let mut popped: Vec<Doc> = Vec::new();
1160+
loop {
1161+
let len = buf.len();
1162+
if len < 2 {
1163+
break;
1164+
}
1165+
let is_endif = match &buf[len - 1] {
1166+
Doc::Text(s) => s.as_ref() == "`endif",
1167+
_ => false,
1168+
};
1169+
if !is_endif {
1170+
break;
1171+
}
1172+
let is_hard = matches!(&buf[len - 2], Doc::Hardline);
1173+
if !is_hard {
1174+
break;
1175+
}
1176+
let endif = buf.pop().unwrap();
1177+
let hardline = buf.pop().unwrap();
1178+
// Push in reverse pair order so the final iter().rev() yields
1179+
// the original [Hardline, Text] sequence.
1180+
popped.push(endif);
1181+
popped.push(hardline);
1182+
}
1183+
popped
1184+
}
1185+
11521186
fn attribute_end(&mut self) {
11531187
match self.attribute.pop() {
11541188
Some(AttributeType::Ifdef) => {
@@ -2931,39 +2965,7 @@ impl VerylWalker for Emitter {
29312965

29322966
/// Semantic action for non-terminal 'Comma'
29332967
fn comma(&mut self, arg: &Comma) {
2934-
// If the trailing Doc nodes are a sequence of
2935-
// `[Hardline, Doc::Text("`endif")]` pairs (one per `attribute_end`
2936-
// emitted for this group), pop them, emit the comma, then re-emit
2937-
// them so the comma sits on the line before the endif chain.
2938-
if matches!(self.mode, Mode::Align) {
2939-
self.veryl_token(&arg.comma_token);
2940-
return;
2941-
}
2942-
let buf = self.doc_buffer.last_mut().unwrap();
2943-
let mut popped: Vec<Doc> = Vec::new();
2944-
loop {
2945-
let len = buf.len();
2946-
if len < 2 {
2947-
break;
2948-
}
2949-
let is_endif = match &buf[len - 1] {
2950-
Doc::Text(s) => s.as_ref() == "`endif",
2951-
_ => false,
2952-
};
2953-
if !is_endif {
2954-
break;
2955-
}
2956-
let is_hard = matches!(&buf[len - 2], Doc::Hardline);
2957-
if !is_hard {
2958-
break;
2959-
}
2960-
let endif = buf.pop().unwrap();
2961-
let hardline = buf.pop().unwrap();
2962-
// Push in reverse pair order so the final iter().rev() yields
2963-
// the original [Hardline, Text] sequence.
2964-
popped.push(endif);
2965-
popped.push(hardline);
2966-
}
2968+
let popped = self.pop_trailing_endif_chain();
29672969
self.veryl_token(&arg.comma_token);
29682970
for d in popped.into_iter().rev() {
29692971
self.emit_doc(d);
@@ -5314,17 +5316,28 @@ impl VerylWalker for Emitter {
53145316

53155317
/// Semantic action for non-terminal 'StructUnionList'
53165318
fn struct_union_list(&mut self, arg: &StructUnionList) {
5319+
// The ';' separators must land inside a member's `ifdef guard, like
5320+
// comma() does — after `endif the preprocessor would keep a stray
5321+
// ';' when the define is off.
53175322
self.struct_union_group(&arg.struct_union_group);
53185323
for x in &arg.struct_union_list_list {
5324+
let popped = self.pop_trailing_endif_chain();
53195325
self.token(&x.comma.comma_token.replace(";"));
5326+
for d in popped.into_iter().rev() {
5327+
self.emit_doc(d);
5328+
}
53205329
self.newline();
53215330
self.struct_union_group(&x.struct_union_group);
53225331
}
5332+
let popped = self.pop_trailing_endif_chain();
53235333
if let Some(ref x) = arg.struct_union_list_opt {
53245334
self.token(&x.comma.comma_token.replace(";"));
53255335
} else {
53265336
self.str(";");
53275337
}
5338+
for d in popped.into_iter().rev() {
5339+
self.emit_doc(d);
5340+
}
53285341
}
53295342

53305343
/// Semantic action for non-terminal 'StructUnionGroup'

crates/emitter/src/tests.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4697,3 +4697,39 @@ fn hoisted_declaration_keeps_ifdef_guard() {
46974697
"else-arm declaration must sit inside the guard:\n{ret}"
46984698
);
46994699
}
4700+
4701+
#[test]
4702+
fn struct_member_ifdef_keeps_semicolon_inside_guard() {
4703+
// The member-separating ';' used to be emitted after `endif, so with
4704+
// the define off the preprocessor kept a stray ';' inside the struct
4705+
// body — invalid SV.
4706+
let metadata = Metadata::create_default("prj").unwrap();
4707+
4708+
let code = r#"module M {
4709+
struct S {
4710+
a: logic<8>,
4711+
#[ifdef(DEF_A)]
4712+
b: logic<8>,
4713+
c: logic<8>,
4714+
}
4715+
var s: S;
4716+
var o: logic<8>;
4717+
always_comb {
4718+
s.a = 1;
4719+
s.c = 2;
4720+
#[ifdef(DEF_A)]
4721+
s.b = 3;
4722+
}
4723+
assign o = s.a + s.c;
4724+
}
4725+
"#;
4726+
let ret = emit(&metadata, code);
4727+
assert!(
4728+
ret.contains("logic [8-1:0] b;\n `endif"),
4729+
"member ';' must land inside the ifdef guard:\n{ret}"
4730+
);
4731+
assert!(
4732+
!ret.contains("`endif;"),
4733+
"no stray ';' after `endif:\n{ret}"
4734+
);
4735+
}

0 commit comments

Comments
 (0)