Skip to main content

vexil_codegen_rust/
enum_gen.rs

1use vexil_lang::ir::{EnumDef, TypeRegistry};
2
3use crate::annotations::{emit_tombstones, emit_type_annotations};
4use crate::emit::CodeWriter;
5
6/// Emit a complete enum type with `Pack` and `Unpack` implementations.
7///
8/// # Non-exhaustive enums
9/// When `annotations.non_exhaustive` is true, the enum gains an `Unknown(u64)` catch-all
10/// variant.  Because `Unknown` carries data, `#[repr(u64)]` cannot be used (Rust only
11/// allows repr discriminants on fieldless enums).  In that case we emit a plain enum
12/// and implement the Pack/Unpack trait by hand.
13///
14/// # Exhaustive enums
15/// When the enum is exhaustive every variant is fieldless, so we use `#[repr(u64)]` to
16/// let the compiler verify the discriminant assignments.
17pub fn emit_enum(w: &mut CodeWriter, en: &EnumDef, _registry: &TypeRegistry) {
18    let name = en.name.as_str();
19    let non_exhaustive = en.annotations.non_exhaustive;
20    let wire_bits = en.wire_bits;
21
22    // ── Tombstone block ─────────────────────────────────────────────────────
23    emit_tombstones(w, name, &en.tombstones);
24
25    // ── Type-level annotations (doc, since, deprecated, non_exhaustive) ─────
26    emit_type_annotations(w, &en.annotations);
27
28    // ── Derive + repr ────────────────────────────────────────────────────────
29    w.line("#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]");
30    if !non_exhaustive {
31        // repr(u64) is only valid for fieldless enums
32        w.line("#[repr(u64)]");
33    }
34
35    // ── Enum body ────────────────────────────────────────────────────────────
36    w.open_block(&format!("pub enum {name}"));
37    for variant in &en.variants {
38        let ordinal = variant.ordinal;
39        // Field-level annotations for the variant
40        for doc in &variant.annotations.doc {
41            w.line(&format!("/// {doc}"));
42        }
43        if let Some(ref dep) = variant.annotations.deprecated {
44            match &dep.since {
45                Some(since) => w.line(&format!(
46                    "#[deprecated(since = \"{since}\", note = \"{}\")]",
47                    dep.reason
48                )),
49                None => w.line(&format!("#[deprecated(note = \"{}\")]", dep.reason)),
50            }
51        }
52        if non_exhaustive {
53            // No repr discriminant allowed when there are tuple variants
54            w.line(&format!("{},", variant.name));
55        } else {
56            w.line(&format!("{} = {ordinal}_u64,", variant.name));
57        }
58    }
59    if non_exhaustive {
60        // Catch-all for unknown ordinals received from the wire
61        w.line("Unknown(u64),");
62    }
63    w.close_block();
64    w.blank();
65
66    // ── Pack impl ────────────────────────────────────────────────────────────
67    w.open_block(&format!("impl vexil_runtime::Pack for {name}"));
68    w.open_block(
69        "fn pack(&self, w: &mut vexil_runtime::BitWriter) -> Result<(), vexil_runtime::EncodeError>",
70    );
71    // Build the match arms inline then emit `let disc: u64 = match self { ... };`
72    // We emit it as a block statement with a trailing semicolon on the closing brace.
73    w.line("let disc: u64 = match self {");
74    w.indent();
75    for variant in &en.variants {
76        let ordinal = variant.ordinal;
77        w.line(&format!("Self::{} => {ordinal}_u64,", variant.name));
78    }
79    if non_exhaustive {
80        w.line("Self::Unknown(v) => *v,");
81    }
82    w.dedent();
83    w.line("};");
84    w.line(&format!("w.write_bits(disc, {wire_bits}_u8);"));
85    w.line("Ok(())");
86    w.close_block();
87    w.close_block();
88    w.blank();
89
90    // ── Unpack impl ──────────────────────────────────────────────────────────
91    w.open_block(&format!("impl vexil_runtime::Unpack for {name}"));
92    w.open_block(
93        "fn unpack(r: &mut vexil_runtime::BitReader<'_>) -> Result<Self, vexil_runtime::DecodeError>",
94    );
95    w.line(&format!("let disc = r.read_bits({wire_bits}_u8)?;"));
96    w.open_block("match disc");
97    for variant in &en.variants {
98        let ordinal = variant.ordinal;
99        w.line(&format!("{ordinal}_u64 => Ok(Self::{}),", variant.name));
100    }
101    if non_exhaustive {
102        w.line("other => Ok(Self::Unknown(other)),");
103    } else {
104        w.line(&format!(
105            "_ => Err(vexil_runtime::DecodeError::UnknownEnumVariant {{ type_name: \"{name}\", value: disc }}),"
106        ));
107    }
108    w.close_block();
109    w.close_block();
110    w.close_block();
111    w.blank();
112}