Skip to main content

alef_codegen/
config_gen.rs

1use alef_core::ir::{DefaultValue, FieldDef, PrimitiveType, TypeDef, TypeRef};
2use heck::{ToPascalCase, ToShoutySnakeCase};
3
4/// Generate a PyO3 `#[new]` constructor with kwargs for a type with `has_default`.
5/// All fields become keyword args with their defaults in `#[pyo3(signature = (...))]`.
6pub fn gen_pyo3_kwargs_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
7    let mut lines = Vec::new();
8    lines.push("#[new]".to_string());
9
10    // Build the signature line with defaults
11    let mut sig_parts = Vec::new();
12    for field in &typ.fields {
13        let default_str = default_value_for_field(field, "python");
14        sig_parts.push(format!("{}={}", field.name, default_str));
15    }
16    let signature = format!("#[pyo3(signature = ({}))]", sig_parts.join(", "));
17    lines.push(signature);
18
19    // Function signature
20    lines.push("fn new(".to_string());
21    for (i, field) in typ.fields.iter().enumerate() {
22        let type_str = type_mapper(&field.ty);
23        let comma = if i < typ.fields.len() - 1 { "," } else { "" };
24        lines.push(format!("    {}: {}{}", field.name, type_str, comma));
25    }
26    lines.push(") -> Self {".to_string());
27
28    // Body
29    lines.push("    Self {".to_string());
30    for field in &typ.fields {
31        lines.push(format!("        {},", field.name));
32    }
33    lines.push("    }".to_string());
34    lines.push("}".to_string());
35
36    lines.join("\n")
37}
38
39/// Generate NAPI constructor that applies defaults for missing optional fields.
40pub fn gen_napi_defaults_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
41    let mut lines = Vec::new();
42    lines.push("pub fn new(mut env: napi::Env, obj: napi::Object) -> napi::Result<Self> {".to_string());
43
44    // Field assignments with defaults
45    for field in &typ.fields {
46        let type_str = type_mapper(&field.ty);
47        let default_str = default_value_for_field(field, "rust");
48        lines.push(format!(
49            "    let {}: {} = obj.get(\"{}\").unwrap_or({})?;",
50            field.name, type_str, field.name, default_str
51        ));
52    }
53
54    lines.push("    Ok(Self {".to_string());
55    for field in &typ.fields {
56        lines.push(format!("        {},", field.name));
57    }
58    lines.push("    })".to_string());
59    lines.push("}".to_string());
60
61    lines.join("\n")
62}
63
64/// Generate Go functional options pattern for a type with `has_default`.
65/// Returns: type definition + Option type + WithField functions + NewConfig constructor
66pub fn gen_go_functional_options(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
67    let mut lines = Vec::new();
68
69    // Type definition
70    lines.push(format!("// {} is a configuration type.", typ.name));
71    lines.push(format!("type {} struct {{", typ.name));
72    for field in &typ.fields {
73        let go_type = type_mapper(&field.ty);
74        lines.push(format!("    {} {}", field.name.to_pascal_case(), go_type));
75    }
76    lines.push("}".to_string());
77    lines.push("".to_string());
78
79    // Option function type
80    lines.push(format!(
81        "// {}Option is a functional option for {}.",
82        typ.name, typ.name
83    ));
84    lines.push(format!("type {}Option func(*{})", typ.name, typ.name));
85    lines.push("".to_string());
86
87    // WithField functions
88    for field in &typ.fields {
89        let option_name = format!("With{}", field.name.to_pascal_case());
90        let go_type = type_mapper(&field.ty);
91        lines.push(format!("// {} sets the {}.", option_name, field.name));
92        lines.push(format!("func {}(val {}) {}Option {{", option_name, go_type, typ.name));
93        lines.push(format!("    return func(c *{}) {{", typ.name));
94        lines.push(format!("        c.{} = val", field.name.to_pascal_case()));
95        lines.push("    }".to_string());
96        lines.push("}".to_string());
97        lines.push("".to_string());
98    }
99
100    // New constructor
101    lines.push(format!(
102        "// New{} creates a new {} with default values and applies options.",
103        typ.name, typ.name
104    ));
105    lines.push(format!(
106        "func New{}(opts ...{}Option) *{} {{",
107        typ.name, typ.name, typ.name
108    ));
109    lines.push(format!("    c := &{} {{", typ.name));
110    for field in &typ.fields {
111        let default_str = default_value_for_field(field, "go");
112        lines.push(format!("        {}: {},", field.name.to_pascal_case(), default_str));
113    }
114    lines.push("    }".to_string());
115    lines.push("    for _, opt := range opts {".to_string());
116    lines.push("        opt(c)".to_string());
117    lines.push("    }".to_string());
118    lines.push("    return c".to_string());
119    lines.push("}".to_string());
120
121    lines.join("\n")
122}
123
124/// Generate Java builder pattern for a type with `has_default`.
125/// Returns: Builder inner class with withField methods + build() method
126pub fn gen_java_builder(typ: &TypeDef, package: &str, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
127    let mut lines = Vec::new();
128
129    lines.push(format!(
130        "// DO NOT EDIT - auto-generated by alef\npackage {};\n",
131        package
132    ));
133    lines.push("/// Builder for creating instances of {} with sensible defaults".to_string());
134    lines.push(format!("public class {}Builder {{", typ.name));
135
136    // Fields
137    for field in &typ.fields {
138        let java_type = type_mapper(&field.ty);
139        lines.push(format!("    private {} {};", java_type, field.name.to_lowercase()));
140    }
141    lines.push("".to_string());
142
143    // Constructor
144    lines.push(format!("    public {}Builder() {{", typ.name));
145    for field in &typ.fields {
146        let default_str = default_value_for_field(field, "java");
147        lines.push(format!("        this.{} = {};", field.name.to_lowercase(), default_str));
148    }
149    lines.push("    }".to_string());
150    lines.push("".to_string());
151
152    // withField methods
153    for field in &typ.fields {
154        let java_type = type_mapper(&field.ty);
155        let method_name = format!("with{}", field.name.to_pascal_case());
156        lines.push(format!(
157            "    public {}Builder {}({} value) {{",
158            typ.name, method_name, java_type
159        ));
160        lines.push(format!("        this.{} = value;", field.name.to_lowercase()));
161        lines.push("        return this;".to_string());
162        lines.push("    }".to_string());
163        lines.push("".to_string());
164    }
165
166    // build() method
167    lines.push(format!("    public {} build() {{", typ.name));
168    lines.push(format!("        return new {}(", typ.name));
169    for (i, field) in typ.fields.iter().enumerate() {
170        let comma = if i < typ.fields.len() - 1 { "," } else { "" };
171        lines.push(format!("            this.{}{}", field.name.to_lowercase(), comma));
172    }
173    lines.push("        );".to_string());
174    lines.push("    }".to_string());
175    lines.push("}".to_string());
176
177    lines.join("\n")
178}
179
180/// Generate C# record with init properties for a type with `has_default`.
181pub fn gen_csharp_record(typ: &TypeDef, namespace: &str, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
182    let mut lines = Vec::new();
183
184    lines.push("// This file is auto-generated by alef. DO NOT EDIT.".to_string());
185    lines.push("using System;".to_string());
186    lines.push("".to_string());
187    lines.push(format!("namespace {};\n", namespace));
188
189    lines.push(format!("/// Configuration record: {}", typ.name));
190    lines.push(format!("public record {} {{", typ.name));
191
192    for field in &typ.fields {
193        // Skip tuple struct internals (e.g., _0, _1, etc.)
194        if field.name.starts_with('_') && field.name[1..].chars().all(|c| c.is_ascii_digit())
195            || field.name.chars().next().is_none_or(|c| c.is_ascii_digit())
196        {
197            continue;
198        }
199
200        let cs_type = type_mapper(&field.ty);
201        let default_str = default_value_for_field(field, "csharp");
202        lines.push(format!(
203            "    public {} {} {{ get; init; }} = {};",
204            cs_type,
205            field.name.to_pascal_case(),
206            default_str
207        ));
208    }
209
210    lines.push("}".to_string());
211
212    lines.join("\n")
213}
214
215/// Get a language-appropriate default value string for a field.
216/// Uses `typed_default` if available, falls back to `default` string, or type-based zero value.
217pub fn default_value_for_field(field: &FieldDef, language: &str) -> String {
218    // First try typed_default if it exists
219    if let Some(typed_default) = &field.typed_default {
220        return match typed_default {
221            DefaultValue::BoolLiteral(b) => match language {
222                "python" => {
223                    if *b {
224                        "True".to_string()
225                    } else {
226                        "False".to_string()
227                    }
228                }
229                "ruby" => {
230                    if *b {
231                        "true".to_string()
232                    } else {
233                        "false".to_string()
234                    }
235                }
236                "go" => {
237                    if *b {
238                        "true".to_string()
239                    } else {
240                        "false".to_string()
241                    }
242                }
243                "java" => {
244                    if *b {
245                        "true".to_string()
246                    } else {
247                        "false".to_string()
248                    }
249                }
250                "csharp" => {
251                    if *b {
252                        "true".to_string()
253                    } else {
254                        "false".to_string()
255                    }
256                }
257                "php" => {
258                    if *b {
259                        "true".to_string()
260                    } else {
261                        "false".to_string()
262                    }
263                }
264                "r" => {
265                    if *b {
266                        "TRUE".to_string()
267                    } else {
268                        "FALSE".to_string()
269                    }
270                }
271                "rust" => {
272                    if *b {
273                        "true".to_string()
274                    } else {
275                        "false".to_string()
276                    }
277                }
278                _ => {
279                    if *b {
280                        "true".to_string()
281                    } else {
282                        "false".to_string()
283                    }
284                }
285            },
286            DefaultValue::StringLiteral(s) => format!("\"{}\"", s.replace('"', "\\\"")),
287            DefaultValue::IntLiteral(n) => n.to_string(),
288            DefaultValue::FloatLiteral(f) => {
289                let s = f.to_string();
290                if !s.contains('.') { format!("{}.0", s) } else { s }
291            }
292            DefaultValue::EnumVariant(v) => match language {
293                "python" => format!("{}.{}", field.ty.type_name(), v.to_shouty_snake_case()),
294                "ruby" => format!("{}::{}", field.ty.type_name(), v.to_pascal_case()),
295                "go" => format!("{}{}()", field.ty.type_name(), v.to_pascal_case()),
296                "java" => format!("{}.{}", field.ty.type_name(), v.to_shouty_snake_case()),
297                "csharp" => format!("{}.{}", field.ty.type_name(), v.to_pascal_case()),
298                "php" => format!("{}::{}", field.ty.type_name(), v.to_pascal_case()),
299                "r" => format!("{}${}", field.ty.type_name(), v.to_pascal_case()),
300                "rust" => format!("{}::{}", field.ty.type_name(), v.to_pascal_case()),
301                _ => v.clone(),
302            },
303            DefaultValue::Empty => {
304                // Empty means "type's default" — check field type to pick the right zero value
305                match &field.ty {
306                    TypeRef::Vec(_) => match language {
307                        "python" | "ruby" | "csharp" => "[]".to_string(),
308                        "go" => "nil".to_string(),
309                        "java" => "List.of()".to_string(),
310                        "php" => "[]".to_string(),
311                        "r" => "c()".to_string(),
312                        "rust" => "vec![]".to_string(),
313                        _ => "null".to_string(),
314                    },
315                    TypeRef::Map(_, _) => match language {
316                        "python" => "{}".to_string(),
317                        "go" => "nil".to_string(),
318                        "java" => "Map.of()".to_string(),
319                        "rust" => "Default::default()".to_string(),
320                        _ => "null".to_string(),
321                    },
322                    TypeRef::Primitive(p) => match p {
323                        PrimitiveType::Bool => match language {
324                            "python" => "False".to_string(),
325                            "ruby" => "false".to_string(),
326                            _ => "false".to_string(),
327                        },
328                        PrimitiveType::F32 | PrimitiveType::F64 => "0.0".to_string(),
329                        _ => "0".to_string(),
330                    },
331                    TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json => match language {
332                        "rust" => "String::new()".to_string(),
333                        _ => "\"\"".to_string(),
334                    },
335                    _ => match language {
336                        "python" => "None".to_string(),
337                        "ruby" => "nil".to_string(),
338                        "go" => "nil".to_string(),
339                        "rust" => "Default::default()".to_string(),
340                        _ => "null".to_string(),
341                    },
342                }
343            }
344            DefaultValue::None => match language {
345                "python" => "None".to_string(),
346                "ruby" => "nil".to_string(),
347                "go" => "nil".to_string(),
348                "java" => "null".to_string(),
349                "csharp" => "null".to_string(),
350                "php" => "null".to_string(),
351                "r" => "NULL".to_string(),
352                "rust" => "None".to_string(),
353                _ => "null".to_string(),
354            },
355        };
356    }
357
358    // Fall back to string default if it exists
359    if let Some(default_str) = &field.default {
360        return default_str.clone();
361    }
362
363    // Final fallback: type-based zero value
364    match &field.ty {
365        TypeRef::Primitive(p) => match p {
366            alef_core::ir::PrimitiveType::Bool => match language {
367                "python" => "False".to_string(),
368                "ruby" => "false".to_string(),
369                "csharp" => "false".to_string(),
370                "java" => "false".to_string(),
371                "php" => "false".to_string(),
372                "r" => "FALSE".to_string(),
373                _ => "false".to_string(),
374            },
375            alef_core::ir::PrimitiveType::U8
376            | alef_core::ir::PrimitiveType::U16
377            | alef_core::ir::PrimitiveType::U32
378            | alef_core::ir::PrimitiveType::U64
379            | alef_core::ir::PrimitiveType::I8
380            | alef_core::ir::PrimitiveType::I16
381            | alef_core::ir::PrimitiveType::I32
382            | alef_core::ir::PrimitiveType::I64
383            | alef_core::ir::PrimitiveType::Usize
384            | alef_core::ir::PrimitiveType::Isize => "0".to_string(),
385            alef_core::ir::PrimitiveType::F32 | alef_core::ir::PrimitiveType::F64 => "0.0".to_string(),
386        },
387        TypeRef::String | TypeRef::Char => match language {
388            "python" => "\"\"".to_string(),
389            "ruby" => "\"\"".to_string(),
390            "go" => "\"\"".to_string(),
391            "java" => "\"\"".to_string(),
392            "csharp" => "\"\"".to_string(),
393            "php" => "\"\"".to_string(),
394            "r" => "\"\"".to_string(),
395            "rust" => "String::new()".to_string(),
396            _ => "\"\"".to_string(),
397        },
398        TypeRef::Bytes => match language {
399            "python" => "b\"\"".to_string(),
400            "ruby" => "\"\"".to_string(),
401            "go" => "[]byte{}".to_string(),
402            "java" => "new byte[]{}".to_string(),
403            "csharp" => "new byte[]{}".to_string(),
404            "php" => "\"\"".to_string(),
405            "r" => "raw()".to_string(),
406            "rust" => "vec![]".to_string(),
407            _ => "[]".to_string(),
408        },
409        TypeRef::Optional(_) => match language {
410            "python" => "None".to_string(),
411            "ruby" => "nil".to_string(),
412            "go" => "nil".to_string(),
413            "java" => "null".to_string(),
414            "csharp" => "null".to_string(),
415            "php" => "null".to_string(),
416            "r" => "NULL".to_string(),
417            "rust" => "None".to_string(),
418            _ => "null".to_string(),
419        },
420        TypeRef::Vec(_) => match language {
421            "python" => "[]".to_string(),
422            "ruby" => "[]".to_string(),
423            "go" => "[]interface{}{}".to_string(),
424            "java" => "new java.util.ArrayList<>()".to_string(),
425            "csharp" => "[]".to_string(),
426            "php" => "[]".to_string(),
427            "r" => "c()".to_string(),
428            "rust" => "vec![]".to_string(),
429            _ => "[]".to_string(),
430        },
431        TypeRef::Map(_, _) => match language {
432            "python" => "{}".to_string(),
433            "ruby" => "{}".to_string(),
434            "go" => "make(map[string]interface{})".to_string(),
435            "java" => "new java.util.HashMap<>()".to_string(),
436            "csharp" => "new Dictionary<string, object>()".to_string(),
437            "php" => "[]".to_string(),
438            "r" => "list()".to_string(),
439            "rust" => "std::collections::HashMap::new()".to_string(),
440            _ => "{}".to_string(),
441        },
442        TypeRef::Json => match language {
443            "python" => "{}".to_string(),
444            "ruby" => "{}".to_string(),
445            "go" => "make(map[string]interface{})".to_string(),
446            "java" => "new com.fasterxml.jackson.databind.JsonNode()".to_string(),
447            "csharp" => "JObject.Parse(\"{}\")".to_string(),
448            "php" => "[]".to_string(),
449            "r" => "list()".to_string(),
450            "rust" => "serde_json::json!({})".to_string(),
451            _ => "{}".to_string(),
452        },
453        _ => match language {
454            "python" => "None".to_string(),
455            "ruby" => "nil".to_string(),
456            "go" => "nil".to_string(),
457            "java" => "null".to_string(),
458            "csharp" => "null".to_string(),
459            "php" => "null".to_string(),
460            "r" => "NULL".to_string(),
461            "rust" => "Default::default()".to_string(),
462            _ => "null".to_string(),
463        },
464    }
465}
466
467// Helper trait extension for TypeRef to get type name
468trait TypeRefExt {
469    fn type_name(&self) -> String;
470}
471
472impl TypeRefExt for TypeRef {
473    fn type_name(&self) -> String {
474        match self {
475            TypeRef::Named(n) => n.clone(),
476            TypeRef::Primitive(p) => format!("{:?}", p),
477            TypeRef::String | TypeRef::Char => "String".to_string(),
478            TypeRef::Bytes => "Bytes".to_string(),
479            TypeRef::Optional(inner) => format!("Option<{}>", inner.type_name()),
480            TypeRef::Vec(inner) => format!("Vec<{}>", inner.type_name()),
481            TypeRef::Map(k, v) => format!("Map<{}, {}>", k.type_name(), v.type_name()),
482            TypeRef::Path => "Path".to_string(),
483            TypeRef::Unit => "()".to_string(),
484            TypeRef::Json => "Json".to_string(),
485            TypeRef::Duration => "Duration".to_string(),
486        }
487    }
488}
489
490/// Generate a Magnus (Ruby) kwargs constructor for a type with `has_default`.
491/// Generates a `new` method that accepts keyword arguments with defaults.
492pub fn gen_magnus_kwargs_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
493    use std::fmt::Write;
494    let mut out = String::with_capacity(512);
495
496    // Start with fn new(
497    writeln!(out, "fn new(").ok();
498
499    // Add all fields as keyword parameters with defaults
500    for (i, field) in typ.fields.iter().enumerate() {
501        let field_type = type_mapper(&field.ty);
502        let default_str = default_value_for_field(field, "ruby");
503        let comma = if i < typ.fields.len() - 1 { "," } else { "" };
504        writeln!(out, "    {}: {} = {}{}", field.name, field_type, default_str, comma).ok();
505    }
506
507    writeln!(out, ") -> Self {{").ok();
508    writeln!(out, "    Self {{").ok();
509
510    // Field assignments
511    for field in &typ.fields {
512        writeln!(out, "        {},", field.name).ok();
513    }
514
515    writeln!(out, "    }}").ok();
516    writeln!(out, "}}").ok();
517
518    out
519}
520
521/// Generate a PHP kwargs constructor for a type with `has_default`.
522/// All fields become `Option<T>` parameters so PHP users can omit any field.
523/// Assignments wrap non-Optional fields in `Some()` and apply defaults.
524pub fn gen_php_kwargs_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
525    use std::fmt::Write;
526    let mut out = String::with_capacity(512);
527
528    writeln!(out, "pub fn __construct(").ok();
529
530    // All params are Option<MappedType> — PHP users can omit any field
531    for (i, field) in typ.fields.iter().enumerate() {
532        let mapped = type_mapper(&field.ty);
533        let comma = if i < typ.fields.len() - 1 { "," } else { "" };
534        writeln!(out, "    {}: Option<{}>{}", field.name, mapped, comma).ok();
535    }
536
537    writeln!(out, ") -> Self {{").ok();
538    writeln!(out, "    Self {{").ok();
539
540    for field in &typ.fields {
541        let is_optional_field = field.optional || matches!(&field.ty, TypeRef::Optional(_));
542        if is_optional_field {
543            // Struct field is Option<T>, param is Option<T> — pass through directly
544            writeln!(out, "        {},", field.name).ok();
545        } else {
546            // Struct field is T, param is Option<T> — unwrap with default
547            let default_str = default_value_for_field(field, "php");
548            writeln!(
549                out,
550                "        {}: {}.unwrap_or({}),",
551                field.name, field.name, default_str
552            )
553            .ok();
554        }
555    }
556
557    writeln!(out, "    }}").ok();
558    writeln!(out, "}}").ok();
559
560    out
561}
562
563/// Generate a Rustler (Elixir) kwargs constructor for a type with `has_default`.
564/// Accepts keyword list or map, applies defaults for missing fields.
565pub fn gen_rustler_kwargs_constructor(typ: &TypeDef, _type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
566    use std::fmt::Write;
567    let mut out = String::with_capacity(512);
568
569    // NifStruct already handles keyword list conversion, but we generate
570    // an explicit constructor wrapper that applies defaults.
571    writeln!(
572        out,
573        "pub fn new(opts: std::collections::HashMap<String, rustler::Term>) -> Self {{"
574    )
575    .ok();
576    writeln!(out, "    Self {{").ok();
577
578    // Field assignments with defaults from opts
579    for field in &typ.fields {
580        let default_str = default_value_for_field(field, "rust");
581        writeln!(
582            out,
583            "        {}: opts.get(\"{}\").and_then(|t| t.decode()).unwrap_or({}),",
584            field.name, field.name, default_str
585        )
586        .ok();
587    }
588
589    writeln!(out, "    }}").ok();
590    writeln!(out, "}}").ok();
591
592    out
593}
594
595/// Generate an extendr (R) kwargs constructor for a type with `has_default`.
596/// Generates an R-callable function accepting named parameters with defaults.
597pub fn gen_extendr_kwargs_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
598    use std::fmt::Write;
599    let mut out = String::with_capacity(512);
600
601    writeln!(out, "#[extendr]").ok();
602    writeln!(out, "pub fn new_{}(", typ.name.to_lowercase()).ok();
603
604    // Add all fields as named parameters with defaults
605    for (i, field) in typ.fields.iter().enumerate() {
606        let field_type = type_mapper(&field.ty);
607        let default_str = default_value_for_field(field, "r");
608        let comma = if i < typ.fields.len() - 1 { "," } else { "" };
609        writeln!(out, "    {}: {} = {}{}", field.name, field_type, default_str, comma).ok();
610    }
611
612    writeln!(out, ") -> {} {{", typ.name).ok();
613    writeln!(out, "    {} {{", typ.name).ok();
614
615    // Field assignments
616    for field in &typ.fields {
617        writeln!(out, "        {},", field.name).ok();
618    }
619
620    writeln!(out, "    }}").ok();
621    writeln!(out, "}}").ok();
622
623    out
624}
625
626#[cfg(test)]
627mod tests {
628    use super::*;
629    use alef_core::ir::{FieldDef, PrimitiveType, TypeRef};
630
631    fn make_test_type() -> TypeDef {
632        TypeDef {
633            name: "Config".to_string(),
634            rust_path: "my_crate::Config".to_string(),
635            fields: vec![
636                FieldDef {
637                    name: "timeout".to_string(),
638                    ty: TypeRef::Primitive(PrimitiveType::U64),
639                    optional: false,
640                    default: Some("30".to_string()),
641                    doc: "Timeout in seconds".to_string(),
642                    sanitized: false,
643                    is_boxed: false,
644                    type_rust_path: None,
645                    cfg: None,
646                    typed_default: Some(DefaultValue::IntLiteral(30)),
647                },
648                FieldDef {
649                    name: "enabled".to_string(),
650                    ty: TypeRef::Primitive(PrimitiveType::Bool),
651                    optional: false,
652                    default: None,
653                    doc: "Enable feature".to_string(),
654                    sanitized: false,
655                    is_boxed: false,
656                    type_rust_path: None,
657                    cfg: None,
658                    typed_default: Some(DefaultValue::BoolLiteral(true)),
659                },
660                FieldDef {
661                    name: "name".to_string(),
662                    ty: TypeRef::String,
663                    optional: false,
664                    default: None,
665                    doc: "Config name".to_string(),
666                    sanitized: false,
667                    is_boxed: false,
668                    type_rust_path: None,
669                    cfg: None,
670                    typed_default: Some(DefaultValue::StringLiteral("default".to_string())),
671                },
672            ],
673            methods: vec![],
674            is_opaque: false,
675            is_clone: true,
676            doc: "Configuration type".to_string(),
677            cfg: None,
678            is_trait: false,
679            has_default: true,
680            has_stripped_cfg_fields: false,
681            is_return_type: false,
682        }
683    }
684
685    #[test]
686    fn test_default_value_bool_true_python() {
687        let field = FieldDef {
688            name: "enabled".to_string(),
689            ty: TypeRef::Primitive(PrimitiveType::Bool),
690            optional: false,
691            default: None,
692            doc: String::new(),
693            sanitized: false,
694            is_boxed: false,
695            type_rust_path: None,
696            cfg: None,
697            typed_default: Some(DefaultValue::BoolLiteral(true)),
698        };
699        assert_eq!(default_value_for_field(&field, "python"), "True");
700    }
701
702    #[test]
703    fn test_default_value_bool_false_go() {
704        let field = FieldDef {
705            name: "enabled".to_string(),
706            ty: TypeRef::Primitive(PrimitiveType::Bool),
707            optional: false,
708            default: None,
709            doc: String::new(),
710            sanitized: false,
711            is_boxed: false,
712            type_rust_path: None,
713            cfg: None,
714            typed_default: Some(DefaultValue::BoolLiteral(false)),
715        };
716        assert_eq!(default_value_for_field(&field, "go"), "false");
717    }
718
719    #[test]
720    fn test_default_value_string_literal() {
721        let field = FieldDef {
722            name: "name".to_string(),
723            ty: TypeRef::String,
724            optional: false,
725            default: None,
726            doc: String::new(),
727            sanitized: false,
728            is_boxed: false,
729            type_rust_path: None,
730            cfg: None,
731            typed_default: Some(DefaultValue::StringLiteral("hello".to_string())),
732        };
733        assert_eq!(default_value_for_field(&field, "python"), "\"hello\"");
734        assert_eq!(default_value_for_field(&field, "java"), "\"hello\"");
735    }
736
737    #[test]
738    fn test_default_value_int_literal() {
739        let field = FieldDef {
740            name: "timeout".to_string(),
741            ty: TypeRef::Primitive(PrimitiveType::U64),
742            optional: false,
743            default: None,
744            doc: String::new(),
745            sanitized: false,
746            is_boxed: false,
747            type_rust_path: None,
748            cfg: None,
749            typed_default: Some(DefaultValue::IntLiteral(42)),
750        };
751        let result = default_value_for_field(&field, "python");
752        assert_eq!(result, "42");
753    }
754
755    #[test]
756    fn test_default_value_none() {
757        let field = FieldDef {
758            name: "maybe".to_string(),
759            ty: TypeRef::Optional(Box::new(TypeRef::String)),
760            optional: true,
761            default: None,
762            doc: String::new(),
763            sanitized: false,
764            is_boxed: false,
765            type_rust_path: None,
766            cfg: None,
767            typed_default: Some(DefaultValue::None),
768        };
769        assert_eq!(default_value_for_field(&field, "python"), "None");
770        assert_eq!(default_value_for_field(&field, "go"), "nil");
771        assert_eq!(default_value_for_field(&field, "java"), "null");
772        assert_eq!(default_value_for_field(&field, "csharp"), "null");
773    }
774
775    #[test]
776    fn test_default_value_fallback_string() {
777        let field = FieldDef {
778            name: "name".to_string(),
779            ty: TypeRef::String,
780            optional: false,
781            default: Some("\"custom\"".to_string()),
782            doc: String::new(),
783            sanitized: false,
784            is_boxed: false,
785            type_rust_path: None,
786            cfg: None,
787            typed_default: None,
788        };
789        assert_eq!(default_value_for_field(&field, "python"), "\"custom\"");
790    }
791
792    #[test]
793    fn test_gen_pyo3_kwargs_constructor() {
794        let typ = make_test_type();
795        let output = gen_pyo3_kwargs_constructor(&typ, &|tr: &TypeRef| match tr {
796            TypeRef::Primitive(p) => format!("{:?}", p),
797            TypeRef::String | TypeRef::Char => "str".to_string(),
798            _ => "Any".to_string(),
799        });
800
801        assert!(output.contains("#[new]"));
802        assert!(output.contains("#[pyo3(signature = ("));
803        assert!(output.contains("timeout=30"));
804        assert!(output.contains("enabled=True"));
805        assert!(output.contains("name=\"default\""));
806        assert!(output.contains("fn new("));
807    }
808
809    #[test]
810    fn test_gen_napi_defaults_constructor() {
811        let typ = make_test_type();
812        let output = gen_napi_defaults_constructor(&typ, &|tr: &TypeRef| match tr {
813            TypeRef::Primitive(p) => format!("{:?}", p),
814            TypeRef::String | TypeRef::Char => "String".to_string(),
815            _ => "Value".to_string(),
816        });
817
818        assert!(output.contains("pub fn new(mut env: napi::Env, obj: napi::Object)"));
819        assert!(output.contains("timeout"));
820        assert!(output.contains("enabled"));
821        assert!(output.contains("name"));
822    }
823
824    #[test]
825    fn test_gen_go_functional_options() {
826        let typ = make_test_type();
827        let output = gen_go_functional_options(&typ, &|tr: &TypeRef| match tr {
828            TypeRef::Primitive(p) => match p {
829                PrimitiveType::U64 => "uint64".to_string(),
830                PrimitiveType::Bool => "bool".to_string(),
831                _ => "interface{}".to_string(),
832            },
833            TypeRef::String | TypeRef::Char => "string".to_string(),
834            _ => "interface{}".to_string(),
835        });
836
837        assert!(output.contains("type Config struct {"));
838        assert!(output.contains("type ConfigOption func(*Config)"));
839        assert!(output.contains("func WithTimeout(val uint64) ConfigOption"));
840        assert!(output.contains("func WithEnabled(val bool) ConfigOption"));
841        assert!(output.contains("func WithName(val string) ConfigOption"));
842        assert!(output.contains("func NewConfig(opts ...ConfigOption) *Config"));
843    }
844
845    #[test]
846    fn test_gen_java_builder() {
847        let typ = make_test_type();
848        let output = gen_java_builder(&typ, "dev.test", &|tr: &TypeRef| match tr {
849            TypeRef::Primitive(p) => match p {
850                PrimitiveType::U64 => "long".to_string(),
851                PrimitiveType::Bool => "boolean".to_string(),
852                _ => "int".to_string(),
853            },
854            TypeRef::String | TypeRef::Char => "String".to_string(),
855            _ => "Object".to_string(),
856        });
857
858        assert!(output.contains("package dev.test;"));
859        assert!(output.contains("public class ConfigBuilder"));
860        assert!(output.contains("withTimeout"));
861        assert!(output.contains("withEnabled"));
862        assert!(output.contains("withName"));
863        assert!(output.contains("public Config build()"));
864    }
865
866    #[test]
867    fn test_gen_csharp_record() {
868        let typ = make_test_type();
869        let output = gen_csharp_record(&typ, "MyNamespace", &|tr: &TypeRef| match tr {
870            TypeRef::Primitive(p) => match p {
871                PrimitiveType::U64 => "ulong".to_string(),
872                PrimitiveType::Bool => "bool".to_string(),
873                _ => "int".to_string(),
874            },
875            TypeRef::String | TypeRef::Char => "string".to_string(),
876            _ => "object".to_string(),
877        });
878
879        assert!(output.contains("namespace MyNamespace;"));
880        assert!(output.contains("public record Config"));
881        assert!(output.contains("public ulong Timeout"));
882        assert!(output.contains("public bool Enabled"));
883        assert!(output.contains("public string Name"));
884        assert!(output.contains("init;"));
885    }
886
887    #[test]
888    fn test_default_value_float_literal() {
889        let field = FieldDef {
890            name: "ratio".to_string(),
891            ty: TypeRef::Primitive(PrimitiveType::F64),
892            optional: false,
893            default: None,
894            doc: String::new(),
895            sanitized: false,
896            is_boxed: false,
897            type_rust_path: None,
898            cfg: None,
899            typed_default: Some(DefaultValue::FloatLiteral(1.5)),
900        };
901        let result = default_value_for_field(&field, "python");
902        assert!(result.contains("1.5"));
903    }
904
905    #[test]
906    fn test_default_value_no_typed_no_default() {
907        let field = FieldDef {
908            name: "count".to_string(),
909            ty: TypeRef::Primitive(PrimitiveType::U32),
910            optional: false,
911            default: None,
912            doc: String::new(),
913            sanitized: false,
914            is_boxed: false,
915            type_rust_path: None,
916            cfg: None,
917            typed_default: None,
918        };
919        // Should fall back to type-based zero value
920        assert_eq!(default_value_for_field(&field, "python"), "0");
921        assert_eq!(default_value_for_field(&field, "go"), "0");
922    }
923}