Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 46 additions & 33 deletions crates/emitter/src/emitter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1149,6 +1149,40 @@ impl Emitter {
}
}

/// Pop any trailing `[Hardline, `endif]` pairs (one per group `attribute_end`)
/// so a separator can be emitted before the endif chain; re-emit with `.rev()`.
fn pop_trailing_endif_chain(&mut self) -> Vec<Doc> {
if matches!(self.mode, Mode::Align) {
return Vec::new();
}
let buf = self.doc_buffer.last_mut().unwrap();
let mut popped: Vec<Doc> = Vec::new();
loop {
let len = buf.len();
if len < 2 {
break;
}
let is_endif = match &buf[len - 1] {
Doc::Text(s) => s.as_ref() == "`endif",
_ => false,
};
if !is_endif {
break;
}
let is_hard = matches!(&buf[len - 2], Doc::Hardline);
if !is_hard {
break;
}
let endif = buf.pop().unwrap();
let hardline = buf.pop().unwrap();
// Push in reverse pair order so the final iter().rev() yields
// the original [Hardline, Text] sequence.
popped.push(endif);
popped.push(hardline);
}
popped
}

fn attribute_end(&mut self) {
match self.attribute.pop() {
Some(AttributeType::Ifdef) => {
Expand Down Expand Up @@ -2931,39 +2965,7 @@ impl VerylWalker for Emitter {

/// Semantic action for non-terminal 'Comma'
fn comma(&mut self, arg: &Comma) {
// If the trailing Doc nodes are a sequence of
// `[Hardline, Doc::Text("`endif")]` pairs (one per `attribute_end`
// emitted for this group), pop them, emit the comma, then re-emit
// them so the comma sits on the line before the endif chain.
if matches!(self.mode, Mode::Align) {
self.veryl_token(&arg.comma_token);
return;
}
let buf = self.doc_buffer.last_mut().unwrap();
let mut popped: Vec<Doc> = Vec::new();
loop {
let len = buf.len();
if len < 2 {
break;
}
let is_endif = match &buf[len - 1] {
Doc::Text(s) => s.as_ref() == "`endif",
_ => false,
};
if !is_endif {
break;
}
let is_hard = matches!(&buf[len - 2], Doc::Hardline);
if !is_hard {
break;
}
let endif = buf.pop().unwrap();
let hardline = buf.pop().unwrap();
// Push in reverse pair order so the final iter().rev() yields
// the original [Hardline, Text] sequence.
popped.push(endif);
popped.push(hardline);
}
let popped = self.pop_trailing_endif_chain();
self.veryl_token(&arg.comma_token);
for d in popped.into_iter().rev() {
self.emit_doc(d);
Expand Down Expand Up @@ -5314,17 +5316,28 @@ impl VerylWalker for Emitter {

/// Semantic action for non-terminal 'StructUnionList'
fn struct_union_list(&mut self, arg: &StructUnionList) {
// The ';' separators must land inside a member's `ifdef guard, like
// comma() does — after `endif the preprocessor would keep a stray
// ';' when the define is off.
self.struct_union_group(&arg.struct_union_group);
for x in &arg.struct_union_list_list {
let popped = self.pop_trailing_endif_chain();
self.token(&x.comma.comma_token.replace(";"));
for d in popped.into_iter().rev() {
self.emit_doc(d);
}
self.newline();
self.struct_union_group(&x.struct_union_group);
}
let popped = self.pop_trailing_endif_chain();
if let Some(ref x) = arg.struct_union_list_opt {
self.token(&x.comma.comma_token.replace(";"));
} else {
self.str(";");
}
for d in popped.into_iter().rev() {
self.emit_doc(d);
}
}

/// Semantic action for non-terminal 'StructUnionGroup'
Expand Down
36 changes: 36 additions & 0 deletions crates/emitter/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4697,3 +4697,39 @@ fn hoisted_declaration_keeps_ifdef_guard() {
"else-arm declaration must sit inside the guard:\n{ret}"
);
}

#[test]
fn struct_member_ifdef_keeps_semicolon_inside_guard() {
// The member-separating ';' used to be emitted after `endif, so with
// the define off the preprocessor kept a stray ';' inside the struct
// body — invalid SV.
let metadata = Metadata::create_default("prj").unwrap();

let code = r#"module M {
struct S {
a: logic<8>,
#[ifdef(DEF_A)]
b: logic<8>,
c: logic<8>,
}
var s: S;
var o: logic<8>;
always_comb {
s.a = 1;
s.c = 2;
#[ifdef(DEF_A)]
s.b = 3;
}
assign o = s.a + s.c;
}
"#;
let ret = emit(&metadata, code);
assert!(
ret.contains("logic [8-1:0] b;\n `endif"),
"member ';' must land inside the ifdef guard:\n{ret}"
);
assert!(
!ret.contains("`endif;"),
"no stray ';' after `endif:\n{ret}"
);
}
Loading