Skip to main content

vexil_codegen_rust/
config.rs

1use std::collections::HashSet;
2
3use vexil_lang::ast::DefaultValue;
4use vexil_lang::ir::{ConfigDef, ConfigFieldDef, ResolvedType, TypeDef, TypeId, TypeRegistry};
5
6use crate::annotations::{emit_field_annotations, emit_type_annotations};
7use crate::emit::CodeWriter;
8use crate::types::rust_type;
9
10/// Emit a config struct with a `Default` implementation.
11///
12/// Config types are compile-time only — no `Pack`/`Unpack` is generated.
13pub fn emit_config(
14    w: &mut CodeWriter,
15    cfg: &ConfigDef,
16    registry: &TypeRegistry,
17    needs_box: &HashSet<(TypeId, usize)>,
18) {
19    let name = cfg.name.as_str();
20
21    // ── Type-level annotations ───────────────────────────────────────────────
22    emit_type_annotations(w, &cfg.annotations);
23
24    // ── Struct definition ────────────────────────────────────────────────────
25    w.line("#[derive(Debug, Clone, PartialEq)]");
26    w.open_block(&format!("pub struct {name}"));
27    for field in &cfg.fields {
28        emit_field_annotations(w, &field.annotations);
29        let field_rust_type = rust_type(&field.resolved_type, registry, needs_box, None);
30        w.line(&format!("pub {}: {},", field.name, field_rust_type));
31    }
32    w.close_block();
33    w.blank();
34
35    // ── Default impl ─────────────────────────────────────────────────────────
36    w.open_block(&format!("impl Default for {name}"));
37    w.open_block("fn default() -> Self");
38    w.open_block("Self");
39    for field in &cfg.fields {
40        let mut expr = default_value_expr(&field.default_value, field, registry);
41        // Wrap in Some(...) if the field type is Optional and the default is not None
42        if matches!(&field.resolved_type, ResolvedType::Optional(_))
43            && !matches!(&field.default_value, DefaultValue::None)
44        {
45            expr = format!("Some({expr})");
46        }
47        w.line(&format!("{}: {},", field.name, expr));
48    }
49    w.close_block();
50    w.close_block();
51    w.close_block();
52    w.blank();
53}
54
55/// Convert a `DefaultValue` to its Rust literal / expression.
56fn default_value_expr(
57    value: &DefaultValue,
58    field: &ConfigFieldDef,
59    registry: &TypeRegistry,
60) -> String {
61    match value {
62        DefaultValue::None => "None".to_string(),
63        DefaultValue::Bool(b) => {
64            if *b {
65                "true".to_string()
66            } else {
67                "false".to_string()
68            }
69        }
70        DefaultValue::Int(n) => format!("{n}"),
71        DefaultValue::UInt(n) => format!("{n}"),
72        DefaultValue::Float(f) => format!("{f}"),
73        DefaultValue::Str(s) => format!("String::from(\"{s}\")"),
74        // An identifier that matches a named type's variant — treated as an
75        // enum variant reference using the field's resolved type name.
76        DefaultValue::Ident(name) => {
77            let type_name = resolve_type_name(&field.resolved_type, registry);
78            format!("{type_name}::{name}")
79        }
80        // Upper-case identifiers are always enum variant references.
81        DefaultValue::UpperIdent(name) => {
82            let type_name = resolve_type_name(&field.resolved_type, registry);
83            format!("{type_name}::{name}")
84        }
85        DefaultValue::Array(items) => {
86            let exprs: Vec<String> = items
87                .iter()
88                .map(|spanned| default_value_expr(&spanned.node, field, registry))
89                .collect();
90            format!("vec![{}]", exprs.join(", "))
91        }
92    }
93}
94
95/// Given a resolved type, return the Rust type name string for use in enum
96/// variant expressions like `TypeName::Variant`.
97fn resolve_type_name(ty: &ResolvedType, registry: &TypeRegistry) -> String {
98    match ty {
99        ResolvedType::Named(id) => match registry.get(*id) {
100            Some(TypeDef::Enum(e)) => e.name.to_string(),
101            Some(TypeDef::Flags(f)) => f.name.to_string(),
102            Some(TypeDef::Newtype(n)) => n.name.to_string(),
103            Some(TypeDef::Message(m)) => m.name.to_string(),
104            Some(TypeDef::Union(u)) => u.name.to_string(),
105            Some(TypeDef::Config(c)) => c.name.to_string(),
106            _ => "UnresolvedType".to_string(),
107        },
108        // For optional/array/etc., unwrap one layer
109        ResolvedType::Optional(inner) => resolve_type_name(inner, registry),
110        _ => "UnresolvedType".to_string(),
111    }
112}