Skip to main content

alef_codegen/
config_gen.rs

1use alef_core::ir::{DefaultValue, FieldDef, PrimitiveType, TypeDef, TypeRef};
2use heck::{ToPascalCase, ToShoutySnakeCase, ToSnakeCase};
3
4/// Returns true if a field is a tuple struct positional field (e.g., `_0`, `_1`, `0`, `1`).
5/// These fields have no meaningful name and must be skipped in languages requiring named fields.
6fn is_tuple_field(field: &FieldDef) -> bool {
7    (field.name.starts_with('_') && field.name[1..].chars().all(|c| c.is_ascii_digit()))
8        || field.name.chars().next().is_none_or(|c| c.is_ascii_digit())
9}
10
11/// Returns true if the Rust default value for a field is its type's inherent default,
12/// meaning `.unwrap_or_default()` can be used instead of `.unwrap_or(value)`.
13/// This avoids clippy::unwrap_or_default warnings.
14fn use_unwrap_or_default(field: &FieldDef) -> bool {
15    if let Some(typed_default) = &field.typed_default {
16        return matches!(typed_default, DefaultValue::Empty | DefaultValue::None);
17    }
18    // No typed_default — the fallback default_value_for_field generates type-based zero values
19    // which are the same as Default::default() for the type.
20    // Named types may not implement Default in some bindings (e.g. Magnus), so they
21    // fall through to the explicit default path.
22    field.default.is_none() && !matches!(&field.ty, TypeRef::Named(_))
23}
24
25/// Generate a PyO3 `#[new]` constructor with kwargs for a type with `has_default`.
26/// All fields become keyword args with their defaults in `#[pyo3(signature = (...))]`.
27pub fn gen_pyo3_kwargs_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
28    let mut lines = Vec::new();
29    lines.push("#[new]".to_string());
30
31    // Build the signature line with defaults
32    let mut sig_parts = Vec::new();
33    for field in &typ.fields {
34        let default_str = default_value_for_field(field, "python");
35        sig_parts.push(format!("{}={}", field.name, default_str));
36    }
37    let signature = format!("#[pyo3(signature = ({}))]", sig_parts.join(", "));
38    lines.push(signature);
39
40    // Function signature
41    lines.push("fn new(".to_string());
42    for (i, field) in typ.fields.iter().enumerate() {
43        let type_str = type_mapper(&field.ty);
44        let comma = if i < typ.fields.len() - 1 { "," } else { "" };
45        lines.push(format!("    {}: {}{}", field.name, type_str, comma));
46    }
47    lines.push(") -> Self {".to_string());
48
49    // Body
50    lines.push("    Self {".to_string());
51    for field in &typ.fields {
52        lines.push(format!("        {},", field.name));
53    }
54    lines.push("    }".to_string());
55    lines.push("}".to_string());
56
57    lines.join("\n")
58}
59
60/// Generate NAPI constructor that applies defaults for missing optional fields.
61pub fn gen_napi_defaults_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
62    let mut lines = Vec::new();
63    lines.push("pub fn new(mut env: napi::Env, obj: napi::Object) -> napi::Result<Self> {".to_string());
64
65    // Field assignments with defaults
66    for field in &typ.fields {
67        let type_str = type_mapper(&field.ty);
68        let default_str = default_value_for_field(field, "rust");
69        lines.push(format!(
70            "    let {}: {} = obj.get(\"{}\").unwrap_or({})?;",
71            field.name, type_str, field.name, default_str
72        ));
73    }
74
75    lines.push("    Ok(Self {".to_string());
76    for field in &typ.fields {
77        lines.push(format!("        {},", field.name));
78    }
79    lines.push("    })".to_string());
80    lines.push("}".to_string());
81
82    lines.join("\n")
83}
84
85/// Generate Go functional options pattern for a type with `has_default`.
86/// Returns: type definition + Option type + WithField functions + NewConfig constructor
87pub fn gen_go_functional_options(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
88    let mut lines = Vec::new();
89
90    // Type definition
91    lines.push(format!("// {} is a configuration type.", typ.name));
92    lines.push(format!("type {} struct {{", typ.name));
93    for field in &typ.fields {
94        if is_tuple_field(field) {
95            continue;
96        }
97        let go_type = type_mapper(&field.ty);
98        lines.push(format!("    {} {}", field.name.to_pascal_case(), go_type));
99    }
100    lines.push("}".to_string());
101    lines.push("".to_string());
102
103    // Option function type
104    lines.push(format!(
105        "// {}Option is a functional option for {}.",
106        typ.name, typ.name
107    ));
108    lines.push(format!("type {}Option func(*{})", typ.name, typ.name));
109    lines.push("".to_string());
110
111    // WithField functions
112    for field in &typ.fields {
113        if is_tuple_field(field) {
114            continue;
115        }
116        let option_name = format!("With{}{}", typ.name, field.name.to_pascal_case());
117        let go_type = type_mapper(&field.ty);
118        lines.push(format!("// {} sets the {}.", option_name, field.name));
119        lines.push(format!("func {}(val {}) {}Option {{", option_name, go_type, typ.name));
120        lines.push(format!("    return func(c *{}) {{", typ.name));
121        lines.push(format!("        c.{} = val", field.name.to_pascal_case()));
122        lines.push("    }".to_string());
123        lines.push("}".to_string());
124        lines.push("".to_string());
125    }
126
127    // New constructor
128    lines.push(format!(
129        "// New{} creates a new {} with default values and applies options.",
130        typ.name, typ.name
131    ));
132    lines.push(format!(
133        "func New{}(opts ...{}Option) *{} {{",
134        typ.name, typ.name, typ.name
135    ));
136    lines.push(format!("    c := &{} {{", typ.name));
137    for field in &typ.fields {
138        if is_tuple_field(field) {
139            continue;
140        }
141        let default_str = default_value_for_field(field, "go");
142        lines.push(format!("        {}: {},", field.name.to_pascal_case(), default_str));
143    }
144    lines.push("    }".to_string());
145    lines.push("    for _, opt := range opts {".to_string());
146    lines.push("        opt(c)".to_string());
147    lines.push("    }".to_string());
148    lines.push("    return c".to_string());
149    lines.push("}".to_string());
150
151    lines.join("\n")
152}
153
154/// Generate Java builder pattern for a type with `has_default`.
155/// Returns: Builder inner class with withField methods + build() method
156pub fn gen_java_builder(typ: &TypeDef, package: &str, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
157    let mut lines = Vec::new();
158
159    lines.push(format!(
160        "// DO NOT EDIT - auto-generated by alef\npackage {};\n",
161        package
162    ));
163    lines.push("/// Builder for creating instances of {} with sensible defaults".to_string());
164    lines.push(format!("public class {}Builder {{", typ.name));
165
166    // Fields
167    for field in &typ.fields {
168        let java_type = type_mapper(&field.ty);
169        lines.push(format!("    private {} {};", java_type, field.name.to_lowercase()));
170    }
171    lines.push("".to_string());
172
173    // Constructor
174    lines.push(format!("    public {}Builder() {{", typ.name));
175    for field in &typ.fields {
176        let default_str = default_value_for_field(field, "java");
177        lines.push(format!("        this.{} = {};", field.name.to_lowercase(), default_str));
178    }
179    lines.push("    }".to_string());
180    lines.push("".to_string());
181
182    // withField methods
183    for field in &typ.fields {
184        let java_type = type_mapper(&field.ty);
185        let method_name = format!("with{}", field.name.to_pascal_case());
186        lines.push(format!(
187            "    public {}Builder {}({} value) {{",
188            typ.name, method_name, java_type
189        ));
190        lines.push(format!("        this.{} = value;", field.name.to_lowercase()));
191        lines.push("        return this;".to_string());
192        lines.push("    }".to_string());
193        lines.push("".to_string());
194    }
195
196    // build() method
197    lines.push(format!("    public {} build() {{", typ.name));
198    lines.push(format!("        return new {}(", typ.name));
199    for (i, field) in typ.fields.iter().enumerate() {
200        let comma = if i < typ.fields.len() - 1 { "," } else { "" };
201        lines.push(format!("            this.{}{}", field.name.to_lowercase(), comma));
202    }
203    lines.push("        );".to_string());
204    lines.push("    }".to_string());
205    lines.push("}".to_string());
206
207    lines.join("\n")
208}
209
210/// Generate C# record with init properties for a type with `has_default`.
211pub fn gen_csharp_record(typ: &TypeDef, namespace: &str, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
212    let mut lines = Vec::new();
213
214    lines.push("// This file is auto-generated by alef. DO NOT EDIT.".to_string());
215    lines.push("using System;".to_string());
216    lines.push("".to_string());
217    lines.push(format!("namespace {};\n", namespace));
218
219    lines.push(format!("/// Configuration record: {}", typ.name));
220    lines.push(format!("public record {} {{", typ.name));
221
222    for field in &typ.fields {
223        // Skip tuple struct internals (e.g., _0, _1, etc.)
224        if field.name.starts_with('_') && field.name[1..].chars().all(|c| c.is_ascii_digit())
225            || field.name.chars().next().is_none_or(|c| c.is_ascii_digit())
226        {
227            continue;
228        }
229
230        let cs_type = type_mapper(&field.ty);
231        let default_str = default_value_for_field(field, "csharp");
232        lines.push(format!(
233            "    public {} {} {{ get; init; }} = {};",
234            cs_type,
235            field.name.to_pascal_case(),
236            default_str
237        ));
238    }
239
240    lines.push("}".to_string());
241
242    lines.join("\n")
243}
244
245/// Get a language-appropriate default value string for a field.
246/// Uses `typed_default` if available, falls back to `default` string, or type-based zero value.
247pub fn default_value_for_field(field: &FieldDef, language: &str) -> String {
248    // First try typed_default if it exists
249    if let Some(typed_default) = &field.typed_default {
250        return match typed_default {
251            DefaultValue::BoolLiteral(b) => match language {
252                "python" => {
253                    if *b {
254                        "True".to_string()
255                    } else {
256                        "False".to_string()
257                    }
258                }
259                "ruby" => {
260                    if *b {
261                        "true".to_string()
262                    } else {
263                        "false".to_string()
264                    }
265                }
266                "go" => {
267                    if *b {
268                        "true".to_string()
269                    } else {
270                        "false".to_string()
271                    }
272                }
273                "java" => {
274                    if *b {
275                        "true".to_string()
276                    } else {
277                        "false".to_string()
278                    }
279                }
280                "csharp" => {
281                    if *b {
282                        "true".to_string()
283                    } else {
284                        "false".to_string()
285                    }
286                }
287                "php" => {
288                    if *b {
289                        "true".to_string()
290                    } else {
291                        "false".to_string()
292                    }
293                }
294                "r" => {
295                    if *b {
296                        "TRUE".to_string()
297                    } else {
298                        "FALSE".to_string()
299                    }
300                }
301                "rust" => {
302                    if *b {
303                        "true".to_string()
304                    } else {
305                        "false".to_string()
306                    }
307                }
308                _ => {
309                    if *b {
310                        "true".to_string()
311                    } else {
312                        "false".to_string()
313                    }
314                }
315            },
316            DefaultValue::StringLiteral(s) => match language {
317                "rust" => format!("\"{}\".to_string()", s.replace('"', "\\\"")),
318                _ => format!("\"{}\"", s.replace('"', "\\\"")),
319            },
320            DefaultValue::IntLiteral(n) => n.to_string(),
321            DefaultValue::FloatLiteral(f) => {
322                let s = f.to_string();
323                if !s.contains('.') { format!("{}.0", s) } else { s }
324            }
325            DefaultValue::EnumVariant(v) => {
326                // When the field's original enum type was excluded/sanitized and mapped to
327                // String, we must emit a string literal rather than an enum type path.
328                // Example: OutputFormat::Plain → "plain".to_string() (Rust), "plain" (others).
329                if matches!(field.ty, TypeRef::String) {
330                    let snake = v.to_snake_case();
331                    return match language {
332                        "rust" => format!("\"{}\".to_string()", snake),
333                        _ => format!("\"{}\"", snake),
334                    };
335                }
336                match language {
337                    "python" => format!("{}.{}", field.ty.type_name(), v.to_shouty_snake_case()),
338                    "ruby" => format!("{}::{}", field.ty.type_name(), v.to_pascal_case()),
339                    "go" => format!("{}{}", field.ty.type_name(), v.to_pascal_case()),
340                    "java" => format!("{}.{}", field.ty.type_name(), v.to_shouty_snake_case()),
341                    "csharp" => format!("{}.{}", field.ty.type_name(), v.to_pascal_case()),
342                    "php" => format!("{}::{}", field.ty.type_name(), v.to_pascal_case()),
343                    "r" => format!("{}${}", field.ty.type_name(), v.to_pascal_case()),
344                    "rust" => format!("{}::{}", field.ty.type_name(), v.to_pascal_case()),
345                    _ => v.clone(),
346                }
347            }
348            DefaultValue::Empty => {
349                // Empty means "type's default" — check field type to pick the right zero value
350                match &field.ty {
351                    TypeRef::Vec(_) => match language {
352                        "python" | "ruby" | "csharp" => "[]".to_string(),
353                        "go" => "nil".to_string(),
354                        "java" => "List.of()".to_string(),
355                        "php" => "[]".to_string(),
356                        "r" => "c()".to_string(),
357                        "rust" => "vec![]".to_string(),
358                        _ => "null".to_string(),
359                    },
360                    TypeRef::Map(_, _) => match language {
361                        "python" => "{}".to_string(),
362                        "go" => "nil".to_string(),
363                        "java" => "Map.of()".to_string(),
364                        "rust" => "Default::default()".to_string(),
365                        _ => "null".to_string(),
366                    },
367                    TypeRef::Primitive(p) => match p {
368                        PrimitiveType::Bool => match language {
369                            "python" => "False".to_string(),
370                            "ruby" => "false".to_string(),
371                            _ => "false".to_string(),
372                        },
373                        PrimitiveType::F32 | PrimitiveType::F64 => "0.0".to_string(),
374                        _ => "0".to_string(),
375                    },
376                    TypeRef::String | TypeRef::Char | TypeRef::Path => match language {
377                        "rust" => "String::new()".to_string(),
378                        _ => "\"\"".to_string(),
379                    },
380                    TypeRef::Json => match language {
381                        "python" | "ruby" => "{}".to_string(),
382                        "go" => "json.RawMessage(nil)".to_string(),
383                        "java" => "new com.fasterxml.jackson.databind.node.ObjectNode(null)".to_string(),
384                        "csharp" => "JObject.Parse(\"{}\")".to_string(),
385                        "php" => "[]".to_string(),
386                        "r" => "list()".to_string(),
387                        "rust" => "serde_json::json!({})".to_string(),
388                        _ => "{}".to_string(),
389                    },
390                    TypeRef::Duration => "0".to_string(),
391                    TypeRef::Bytes => match language {
392                        "python" => "b\"\"".to_string(),
393                        "go" => "[]byte{}".to_string(),
394                        "rust" => "vec![]".to_string(),
395                        _ => "\"\"".to_string(),
396                    },
397                    _ => match language {
398                        "python" => "None".to_string(),
399                        "ruby" => "nil".to_string(),
400                        "go" => "nil".to_string(),
401                        "rust" => "Default::default()".to_string(),
402                        _ => "null".to_string(),
403                    },
404                }
405            }
406            DefaultValue::None => match language {
407                "python" => "None".to_string(),
408                "ruby" => "nil".to_string(),
409                "go" => "nil".to_string(),
410                "java" => "null".to_string(),
411                "csharp" => "null".to_string(),
412                "php" => "null".to_string(),
413                "r" => "NULL".to_string(),
414                "rust" => "None".to_string(),
415                _ => "null".to_string(),
416            },
417        };
418    }
419
420    // Fall back to string default if it exists
421    if let Some(default_str) = &field.default {
422        return default_str.clone();
423    }
424
425    // Final fallback: type-based zero value
426    match &field.ty {
427        TypeRef::Primitive(p) => match p {
428            alef_core::ir::PrimitiveType::Bool => match language {
429                "python" => "False".to_string(),
430                "ruby" => "false".to_string(),
431                "csharp" => "false".to_string(),
432                "java" => "false".to_string(),
433                "php" => "false".to_string(),
434                "r" => "FALSE".to_string(),
435                _ => "false".to_string(),
436            },
437            alef_core::ir::PrimitiveType::U8
438            | alef_core::ir::PrimitiveType::U16
439            | alef_core::ir::PrimitiveType::U32
440            | alef_core::ir::PrimitiveType::U64
441            | alef_core::ir::PrimitiveType::I8
442            | alef_core::ir::PrimitiveType::I16
443            | alef_core::ir::PrimitiveType::I32
444            | alef_core::ir::PrimitiveType::I64
445            | alef_core::ir::PrimitiveType::Usize
446            | alef_core::ir::PrimitiveType::Isize => "0".to_string(),
447            alef_core::ir::PrimitiveType::F32 | alef_core::ir::PrimitiveType::F64 => "0.0".to_string(),
448        },
449        TypeRef::String | TypeRef::Char => match language {
450            "python" => "\"\"".to_string(),
451            "ruby" => "\"\"".to_string(),
452            "go" => "\"\"".to_string(),
453            "java" => "\"\"".to_string(),
454            "csharp" => "\"\"".to_string(),
455            "php" => "\"\"".to_string(),
456            "r" => "\"\"".to_string(),
457            "rust" => "String::new()".to_string(),
458            _ => "\"\"".to_string(),
459        },
460        TypeRef::Bytes => match language {
461            "python" => "b\"\"".to_string(),
462            "ruby" => "\"\"".to_string(),
463            "go" => "[]byte{}".to_string(),
464            "java" => "new byte[]{}".to_string(),
465            "csharp" => "new byte[]{}".to_string(),
466            "php" => "\"\"".to_string(),
467            "r" => "raw()".to_string(),
468            "rust" => "vec![]".to_string(),
469            _ => "[]".to_string(),
470        },
471        TypeRef::Optional(_) => match language {
472            "python" => "None".to_string(),
473            "ruby" => "nil".to_string(),
474            "go" => "nil".to_string(),
475            "java" => "null".to_string(),
476            "csharp" => "null".to_string(),
477            "php" => "null".to_string(),
478            "r" => "NULL".to_string(),
479            "rust" => "None".to_string(),
480            _ => "null".to_string(),
481        },
482        TypeRef::Vec(_) => match language {
483            "python" => "[]".to_string(),
484            "ruby" => "[]".to_string(),
485            "go" => "[]interface{}{}".to_string(),
486            "java" => "new java.util.ArrayList<>()".to_string(),
487            "csharp" => "[]".to_string(),
488            "php" => "[]".to_string(),
489            "r" => "c()".to_string(),
490            "rust" => "vec![]".to_string(),
491            _ => "[]".to_string(),
492        },
493        TypeRef::Map(_, _) => match language {
494            "python" => "{}".to_string(),
495            "ruby" => "{}".to_string(),
496            "go" => "make(map[string]interface{})".to_string(),
497            "java" => "new java.util.HashMap<>()".to_string(),
498            "csharp" => "new Dictionary<string, object>()".to_string(),
499            "php" => "[]".to_string(),
500            "r" => "list()".to_string(),
501            "rust" => "std::collections::HashMap::new()".to_string(),
502            _ => "{}".to_string(),
503        },
504        TypeRef::Json => match language {
505            "python" => "{}".to_string(),
506            "ruby" => "{}".to_string(),
507            "go" => "json.RawMessage(nil)".to_string(),
508            "java" => "new com.fasterxml.jackson.databind.JsonNode()".to_string(),
509            "csharp" => "JObject.Parse(\"{}\")".to_string(),
510            "php" => "[]".to_string(),
511            "r" => "list()".to_string(),
512            "rust" => "serde_json::json!({})".to_string(),
513            _ => "{}".to_string(),
514        },
515        TypeRef::Named(name) => match language {
516            "rust" => format!("{name}::default()"),
517            "python" => "None".to_string(),
518            "ruby" => "nil".to_string(),
519            "go" => "nil".to_string(),
520            "java" => "null".to_string(),
521            "csharp" => "null".to_string(),
522            "php" => "null".to_string(),
523            "r" => "NULL".to_string(),
524            _ => "null".to_string(),
525        },
526        _ => match language {
527            "python" => "None".to_string(),
528            "ruby" => "nil".to_string(),
529            "go" => "nil".to_string(),
530            "java" => "null".to_string(),
531            "csharp" => "null".to_string(),
532            "php" => "null".to_string(),
533            "r" => "NULL".to_string(),
534            "rust" => "Default::default()".to_string(),
535            _ => "null".to_string(),
536        },
537    }
538}
539
540// Helper trait extension for TypeRef to get type name
541trait TypeRefExt {
542    fn type_name(&self) -> String;
543}
544
545impl TypeRefExt for TypeRef {
546    fn type_name(&self) -> String {
547        match self {
548            TypeRef::Named(n) => n.clone(),
549            TypeRef::Primitive(p) => format!("{:?}", p),
550            TypeRef::String | TypeRef::Char => "String".to_string(),
551            TypeRef::Bytes => "Bytes".to_string(),
552            TypeRef::Optional(inner) => format!("Option<{}>", inner.type_name()),
553            TypeRef::Vec(inner) => format!("Vec<{}>", inner.type_name()),
554            TypeRef::Map(k, v) => format!("Map<{}, {}>", k.type_name(), v.type_name()),
555            TypeRef::Path => "Path".to_string(),
556            TypeRef::Unit => "()".to_string(),
557            TypeRef::Json => "Json".to_string(),
558            TypeRef::Duration => "Duration".to_string(),
559        }
560    }
561}
562
563/// The maximum arity supported by Magnus `function!` macro.
564const MAGNUS_MAX_ARITY: usize = 15;
565
566/// Generate a Magnus (Ruby) kwargs constructor for a type with `has_default`.
567///
568/// For types with <=15 fields, generates a positional `Option<T>` parameter constructor.
569/// For types with >15 fields (exceeding Magnus arity limit), generates a hash-based constructor
570/// using `RHash` that extracts fields by name, applying defaults for missing keys.
571pub fn gen_magnus_kwargs_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
572    if typ.fields.len() > MAGNUS_MAX_ARITY {
573        gen_magnus_hash_constructor(typ, type_mapper)
574    } else {
575        gen_magnus_positional_constructor(typ, type_mapper)
576    }
577}
578
579/// Wrap a type string for use as a type-path prefix in Rust.
580///
581/// Types containing `<` (generics like `Vec<String>`, `Option<T>`) cannot be used as
582/// `Vec<String>::try_convert(v)` — that's a parse error. They must use the UFCS form
583/// `<Vec<String>>::try_convert(v)` instead. Simple names like `String`, `bool` can use
584/// `String::try_convert(v)` directly.
585fn as_type_path_prefix(type_str: &str) -> String {
586    if type_str.contains('<') {
587        format!("<{type_str}>")
588    } else {
589        type_str.to_string()
590    }
591}
592
593/// Generate a hash-based Magnus constructor for types with many fields.
594/// Accepts `(kwargs: RHash)` and extracts each field by symbol name, applying defaults.
595fn gen_magnus_hash_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
596    use std::fmt::Write;
597    let mut out = String::with_capacity(1024);
598
599    writeln!(out, "fn new(kwargs: magnus::RHash) -> Result<Self, magnus::Error> {{").ok();
600    writeln!(out, "    let ruby = unsafe {{ magnus::Ruby::get_unchecked() }};").ok();
601    writeln!(out, "    Ok(Self {{").ok();
602
603    for field in &typ.fields {
604        let is_optional = field_is_optional_in_rust(field);
605        // Use inner type for try_convert, since the hash value is the inner T, not Option<T>.
606        let inner_type = type_mapper(&field.ty);
607        let type_prefix = as_type_path_prefix(&inner_type);
608        if is_optional {
609            // Field is Option<T>: extract from hash, wrap in Some, default to None
610            writeln!(
611                out,
612                "        {name}: kwargs.get(ruby.to_symbol(\"{name}\")).and_then(|v| {type_prefix}::try_convert(v).ok()),",
613                name = field.name,
614                type_prefix = type_prefix,
615            ).ok();
616        } else if use_unwrap_or_default(field) {
617            writeln!(
618                out,
619                "        {name}: kwargs.get(ruby.to_symbol(\"{name}\")).and_then(|v| {type_prefix}::try_convert(v).ok()).unwrap_or_default(),",
620                name = field.name,
621                type_prefix = type_prefix,
622            ).ok();
623        } else {
624            // When the binding maps the field type to String (e.g. an excluded enum), but the
625            // original default is an EnumVariant, `default_value_for_field` would emit
626            // `TypeName::Variant` which is invalid for a `String` field. Fall back to the
627            // string-literal form in that case.
628            let default_str = if inner_type == "String" {
629                if let Some(DefaultValue::EnumVariant(variant)) = &field.typed_default {
630                    use heck::ToSnakeCase;
631                    format!("\"{}\".to_string()", variant.to_snake_case())
632                } else {
633                    default_value_for_field(field, "rust")
634                }
635            } else {
636                default_value_for_field(field, "rust")
637            };
638            writeln!(
639                out,
640                "        {name}: kwargs.get(ruby.to_symbol(\"{name}\")).and_then(|v| {type_prefix}::try_convert(v).ok()).unwrap_or({default}),",
641                name = field.name,
642                type_prefix = type_prefix,
643                default = default_str,
644            ).ok();
645        }
646    }
647
648    writeln!(out, "    }})").ok();
649    writeln!(out, "}}").ok();
650
651    out
652}
653
654/// Returns true if the generated Rust field type is already `Option<T>`.
655/// This covers both:
656/// - Fields with `optional: true` (the Rust field type becomes `Option<inner_type>`)
657/// - Fields whose `TypeRef` is explicitly `Optional(_)` (rare, for nested Option types)
658fn field_is_optional_in_rust(field: &FieldDef) -> bool {
659    field.optional || matches!(&field.ty, TypeRef::Optional(_))
660}
661
662/// Generate a positional Magnus constructor for types with <=15 fields.
663/// Uses `Option<T>` parameters and applies defaults in the body.
664fn gen_magnus_positional_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
665    use std::fmt::Write;
666    let mut out = String::with_capacity(512);
667
668    writeln!(out, "fn new(").ok();
669
670    // All params are Option<T> so Ruby users can pass nil for any field.
671    // If the Rust field type is already Option<T> (via optional:true or TypeRef::Optional),
672    // use that type directly (avoids Option<Option<T>>).
673    for (i, field) in typ.fields.iter().enumerate() {
674        let comma = if i < typ.fields.len() - 1 { "," } else { "" };
675        let is_optional = field_is_optional_in_rust(field);
676        if is_optional {
677            // field.ty is the inner type; the mapper maps inner type, we wrap in Option<>
678            // BUT the type_mapper call site already wraps when field.optional==true.
679            // Here we call type_mapper on the field's inner type directly to get the param type.
680            let inner_type = type_mapper(&field.ty);
681            writeln!(out, "    {}: Option<{}>{}", field.name, inner_type, comma).ok();
682        } else {
683            let field_type = type_mapper(&field.ty);
684            writeln!(out, "    {}: Option<{}>{}", field.name, field_type, comma).ok();
685        }
686    }
687
688    writeln!(out, ") -> Self {{").ok();
689    writeln!(out, "    Self {{").ok();
690
691    for field in &typ.fields {
692        let is_optional = field_is_optional_in_rust(field);
693        if is_optional {
694            // The Rust field is Option<T>; param is Option<T>; assign directly.
695            writeln!(out, "        {},", field.name).ok();
696        } else if use_unwrap_or_default(field) {
697            writeln!(out, "        {}: {}.unwrap_or_default(),", field.name, field.name).ok();
698        } else {
699            let default_str = default_value_for_field(field, "rust");
700            writeln!(
701                out,
702                "        {}: {}.unwrap_or({}),",
703                field.name, field.name, default_str
704            )
705            .ok();
706        }
707    }
708
709    writeln!(out, "    }}").ok();
710    writeln!(out, "}}").ok();
711
712    out
713}
714
715/// Generate a PHP kwargs constructor for a type with `has_default`.
716/// All fields become `Option<T>` parameters so PHP users can omit any field.
717/// Assignments wrap non-Optional fields in `Some()` and apply defaults.
718pub fn gen_php_kwargs_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
719    use std::fmt::Write;
720    let mut out = String::with_capacity(512);
721
722    writeln!(out, "pub fn __construct(").ok();
723
724    // All params are Option<MappedType> — PHP users can omit any field
725    for (i, field) in typ.fields.iter().enumerate() {
726        let mapped = type_mapper(&field.ty);
727        let comma = if i < typ.fields.len() - 1 { "," } else { "" };
728        writeln!(out, "    {}: Option<{}>{}", field.name, mapped, comma).ok();
729    }
730
731    writeln!(out, ") -> Self {{").ok();
732    writeln!(out, "    Self {{").ok();
733
734    for field in &typ.fields {
735        let is_optional_field = field.optional || matches!(&field.ty, TypeRef::Optional(_));
736        if is_optional_field {
737            // Struct field is Option<T>, param is Option<T> — pass through directly
738            writeln!(out, "        {},", field.name).ok();
739        } else if use_unwrap_or_default(field) {
740            // Struct field is T, param is Option<T> — unwrap with type's default
741            writeln!(out, "        {}: {}.unwrap_or_default(),", field.name, field.name).ok();
742        } else {
743            // Struct field is T, param is Option<T> — unwrap with explicit default
744            let default_str = default_value_for_field(field, "rust");
745            writeln!(
746                out,
747                "        {}: {}.unwrap_or({}),",
748                field.name, field.name, default_str
749            )
750            .ok();
751        }
752    }
753
754    writeln!(out, "    }}").ok();
755    writeln!(out, "}}").ok();
756
757    out
758}
759
760/// Generate a Rustler (Elixir) kwargs constructor for a type with `has_default`.
761/// Accepts keyword list or map, applies defaults for missing fields.
762pub fn gen_rustler_kwargs_constructor(typ: &TypeDef, _type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
763    use std::fmt::Write;
764    let mut out = String::with_capacity(512);
765
766    // NifStruct already handles keyword list conversion, but we generate
767    // an explicit constructor wrapper that applies defaults.
768    writeln!(
769        out,
770        "pub fn new(opts: std::collections::HashMap<String, rustler::Term>) -> Self {{"
771    )
772    .ok();
773    writeln!(out, "    Self {{").ok();
774
775    // Field assignments with defaults from opts.
776    // Optional fields (Option<T>) need special handling: decode the inner type
777    // directly so we get Option<T> from and_then, with no unwrap_or needed.
778    for field in &typ.fields {
779        if field.optional {
780            // Field type is Option<T>. Decode inner T from the Term, yielding Option<T>.
781            writeln!(
782                out,
783                "        {}: opts.get(\"{}\").and_then(|t| t.decode().ok()),",
784                field.name, field.name
785            )
786            .ok();
787        } else if use_unwrap_or_default(field) {
788            writeln!(
789                out,
790                "        {}: opts.get(\"{}\").and_then(|t| t.decode().ok()).unwrap_or_default(),",
791                field.name, field.name
792            )
793            .ok();
794        } else {
795            let default_str = default_value_for_field(field, "rust");
796            // Check if the default value looks like an enum variant (e.g., "OutputFormat::Plain")
797            // which wouldn't work for String types. If so, use unwrap_or_default() instead.
798            let is_enum_variant_default = default_str.contains("::") || default_str.starts_with("\"");
799
800            if is_enum_variant_default && matches!(&field.ty, TypeRef::String | TypeRef::Char) {
801                // Use default for String types with enum-like defaults
802                writeln!(
803                    out,
804                    "        {}: opts.get(\"{}\").and_then(|t| t.decode().ok()).unwrap_or_default(),",
805                    field.name, field.name
806                )
807                .ok();
808            } else if matches!(&field.ty, TypeRef::Named(_)) {
809                // For other Named types, use unwrap_or_default() since the binding type may differ
810                // from the core type (e.g., excluded enums become String).
811                writeln!(
812                    out,
813                    "        {}: opts.get(\"{}\").and_then(|t| t.decode().ok()).unwrap_or_default(),",
814                    field.name, field.name
815                )
816                .ok();
817            } else {
818                writeln!(
819                    out,
820                    "        {}: opts.get(\"{}\").and_then(|t| t.decode().ok()).unwrap_or({}),",
821                    field.name, field.name, default_str
822                )
823                .ok();
824            }
825        }
826    }
827
828    writeln!(out, "    }}").ok();
829    writeln!(out, "}}").ok();
830
831    out
832}
833
834/// Generate an extendr (R) kwargs constructor for a type with `has_default`.
835/// Generates an R-callable function accepting named parameters with defaults.
836pub fn gen_extendr_kwargs_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
837    use std::fmt::Write;
838    let mut out = String::with_capacity(512);
839
840    writeln!(out, "#[extendr]").ok();
841    writeln!(out, "pub fn new_{}(", typ.name.to_lowercase()).ok();
842
843    // Add all fields as named parameters with defaults
844    for (i, field) in typ.fields.iter().enumerate() {
845        let field_type = type_mapper(&field.ty);
846        let default_str = default_value_for_field(field, "r");
847        let comma = if i < typ.fields.len() - 1 { "," } else { "" };
848        writeln!(out, "    {}: {} = {}{}", field.name, field_type, default_str, comma).ok();
849    }
850
851    writeln!(out, ") -> {} {{", typ.name).ok();
852    writeln!(out, "    {} {{", typ.name).ok();
853
854    // Field assignments
855    for field in &typ.fields {
856        writeln!(out, "        {},", field.name).ok();
857    }
858
859    writeln!(out, "    }}").ok();
860    writeln!(out, "}}").ok();
861
862    out
863}
864
865#[cfg(test)]
866mod tests {
867    use super::*;
868    use alef_core::ir::{CoreWrapper, FieldDef, PrimitiveType, TypeRef};
869
870    fn make_test_type() -> TypeDef {
871        TypeDef {
872            name: "Config".to_string(),
873            rust_path: "my_crate::Config".to_string(),
874            original_rust_path: String::new(),
875            fields: vec![
876                FieldDef {
877                    name: "timeout".to_string(),
878                    ty: TypeRef::Primitive(PrimitiveType::U64),
879                    optional: false,
880                    default: Some("30".to_string()),
881                    doc: "Timeout in seconds".to_string(),
882                    sanitized: false,
883                    is_boxed: false,
884                    type_rust_path: None,
885                    cfg: None,
886                    typed_default: Some(DefaultValue::IntLiteral(30)),
887                    core_wrapper: CoreWrapper::None,
888                    vec_inner_core_wrapper: CoreWrapper::None,
889                    newtype_wrapper: None,
890                },
891                FieldDef {
892                    name: "enabled".to_string(),
893                    ty: TypeRef::Primitive(PrimitiveType::Bool),
894                    optional: false,
895                    default: None,
896                    doc: "Enable feature".to_string(),
897                    sanitized: false,
898                    is_boxed: false,
899                    type_rust_path: None,
900                    cfg: None,
901                    typed_default: Some(DefaultValue::BoolLiteral(true)),
902                    core_wrapper: CoreWrapper::None,
903                    vec_inner_core_wrapper: CoreWrapper::None,
904                    newtype_wrapper: None,
905                },
906                FieldDef {
907                    name: "name".to_string(),
908                    ty: TypeRef::String,
909                    optional: false,
910                    default: None,
911                    doc: "Config name".to_string(),
912                    sanitized: false,
913                    is_boxed: false,
914                    type_rust_path: None,
915                    cfg: None,
916                    typed_default: Some(DefaultValue::StringLiteral("default".to_string())),
917                    core_wrapper: CoreWrapper::None,
918                    vec_inner_core_wrapper: CoreWrapper::None,
919                    newtype_wrapper: None,
920                },
921            ],
922            methods: vec![],
923            is_opaque: false,
924            is_clone: true,
925            doc: "Configuration type".to_string(),
926            cfg: None,
927            is_trait: false,
928            has_default: true,
929            has_stripped_cfg_fields: false,
930            is_return_type: false,
931            serde_rename_all: None,
932            has_serde: false,
933            super_traits: vec![],
934        }
935    }
936
937    #[test]
938    fn test_default_value_bool_true_python() {
939        let field = FieldDef {
940            name: "enabled".to_string(),
941            ty: TypeRef::Primitive(PrimitiveType::Bool),
942            optional: false,
943            default: None,
944            doc: String::new(),
945            sanitized: false,
946            is_boxed: false,
947            type_rust_path: None,
948            cfg: None,
949            typed_default: Some(DefaultValue::BoolLiteral(true)),
950            core_wrapper: CoreWrapper::None,
951            vec_inner_core_wrapper: CoreWrapper::None,
952            newtype_wrapper: None,
953        };
954        assert_eq!(default_value_for_field(&field, "python"), "True");
955    }
956
957    #[test]
958    fn test_default_value_bool_false_go() {
959        let field = FieldDef {
960            name: "enabled".to_string(),
961            ty: TypeRef::Primitive(PrimitiveType::Bool),
962            optional: false,
963            default: None,
964            doc: String::new(),
965            sanitized: false,
966            is_boxed: false,
967            type_rust_path: None,
968            cfg: None,
969            typed_default: Some(DefaultValue::BoolLiteral(false)),
970            core_wrapper: CoreWrapper::None,
971            vec_inner_core_wrapper: CoreWrapper::None,
972            newtype_wrapper: None,
973        };
974        assert_eq!(default_value_for_field(&field, "go"), "false");
975    }
976
977    #[test]
978    fn test_default_value_string_literal() {
979        let field = FieldDef {
980            name: "name".to_string(),
981            ty: TypeRef::String,
982            optional: false,
983            default: None,
984            doc: String::new(),
985            sanitized: false,
986            is_boxed: false,
987            type_rust_path: None,
988            cfg: None,
989            typed_default: Some(DefaultValue::StringLiteral("hello".to_string())),
990            core_wrapper: CoreWrapper::None,
991            vec_inner_core_wrapper: CoreWrapper::None,
992            newtype_wrapper: None,
993        };
994        assert_eq!(default_value_for_field(&field, "python"), "\"hello\"");
995        assert_eq!(default_value_for_field(&field, "java"), "\"hello\"");
996    }
997
998    #[test]
999    fn test_default_value_int_literal() {
1000        let field = FieldDef {
1001            name: "timeout".to_string(),
1002            ty: TypeRef::Primitive(PrimitiveType::U64),
1003            optional: false,
1004            default: None,
1005            doc: String::new(),
1006            sanitized: false,
1007            is_boxed: false,
1008            type_rust_path: None,
1009            cfg: None,
1010            typed_default: Some(DefaultValue::IntLiteral(42)),
1011            core_wrapper: CoreWrapper::None,
1012            vec_inner_core_wrapper: CoreWrapper::None,
1013            newtype_wrapper: None,
1014        };
1015        let result = default_value_for_field(&field, "python");
1016        assert_eq!(result, "42");
1017    }
1018
1019    #[test]
1020    fn test_default_value_none() {
1021        let field = FieldDef {
1022            name: "maybe".to_string(),
1023            ty: TypeRef::Optional(Box::new(TypeRef::String)),
1024            optional: true,
1025            default: None,
1026            doc: String::new(),
1027            sanitized: false,
1028            is_boxed: false,
1029            type_rust_path: None,
1030            cfg: None,
1031            typed_default: Some(DefaultValue::None),
1032            core_wrapper: CoreWrapper::None,
1033            vec_inner_core_wrapper: CoreWrapper::None,
1034            newtype_wrapper: None,
1035        };
1036        assert_eq!(default_value_for_field(&field, "python"), "None");
1037        assert_eq!(default_value_for_field(&field, "go"), "nil");
1038        assert_eq!(default_value_for_field(&field, "java"), "null");
1039        assert_eq!(default_value_for_field(&field, "csharp"), "null");
1040    }
1041
1042    #[test]
1043    fn test_default_value_fallback_string() {
1044        let field = FieldDef {
1045            name: "name".to_string(),
1046            ty: TypeRef::String,
1047            optional: false,
1048            default: Some("\"custom\"".to_string()),
1049            doc: String::new(),
1050            sanitized: false,
1051            is_boxed: false,
1052            type_rust_path: None,
1053            cfg: None,
1054            typed_default: None,
1055            core_wrapper: CoreWrapper::None,
1056            vec_inner_core_wrapper: CoreWrapper::None,
1057            newtype_wrapper: None,
1058        };
1059        assert_eq!(default_value_for_field(&field, "python"), "\"custom\"");
1060    }
1061
1062    #[test]
1063    fn test_gen_pyo3_kwargs_constructor() {
1064        let typ = make_test_type();
1065        let output = gen_pyo3_kwargs_constructor(&typ, &|tr: &TypeRef| match tr {
1066            TypeRef::Primitive(p) => format!("{:?}", p),
1067            TypeRef::String | TypeRef::Char => "str".to_string(),
1068            _ => "Any".to_string(),
1069        });
1070
1071        assert!(output.contains("#[new]"));
1072        assert!(output.contains("#[pyo3(signature = ("));
1073        assert!(output.contains("timeout=30"));
1074        assert!(output.contains("enabled=True"));
1075        assert!(output.contains("name=\"default\""));
1076        assert!(output.contains("fn new("));
1077    }
1078
1079    #[test]
1080    fn test_gen_napi_defaults_constructor() {
1081        let typ = make_test_type();
1082        let output = gen_napi_defaults_constructor(&typ, &|tr: &TypeRef| match tr {
1083            TypeRef::Primitive(p) => format!("{:?}", p),
1084            TypeRef::String | TypeRef::Char => "String".to_string(),
1085            _ => "Value".to_string(),
1086        });
1087
1088        assert!(output.contains("pub fn new(mut env: napi::Env, obj: napi::Object)"));
1089        assert!(output.contains("timeout"));
1090        assert!(output.contains("enabled"));
1091        assert!(output.contains("name"));
1092    }
1093
1094    #[test]
1095    fn test_gen_go_functional_options() {
1096        let typ = make_test_type();
1097        let output = gen_go_functional_options(&typ, &|tr: &TypeRef| match tr {
1098            TypeRef::Primitive(p) => match p {
1099                PrimitiveType::U64 => "uint64".to_string(),
1100                PrimitiveType::Bool => "bool".to_string(),
1101                _ => "interface{}".to_string(),
1102            },
1103            TypeRef::String | TypeRef::Char => "string".to_string(),
1104            _ => "interface{}".to_string(),
1105        });
1106
1107        assert!(output.contains("type Config struct {"));
1108        assert!(output.contains("type ConfigOption func(*Config)"));
1109        assert!(output.contains("func WithConfigTimeout(val uint64) ConfigOption"));
1110        assert!(output.contains("func WithConfigEnabled(val bool) ConfigOption"));
1111        assert!(output.contains("func WithConfigName(val string) ConfigOption"));
1112        assert!(output.contains("func NewConfig(opts ...ConfigOption) *Config"));
1113    }
1114
1115    #[test]
1116    fn test_gen_java_builder() {
1117        let typ = make_test_type();
1118        let output = gen_java_builder(&typ, "dev.test", &|tr: &TypeRef| match tr {
1119            TypeRef::Primitive(p) => match p {
1120                PrimitiveType::U64 => "long".to_string(),
1121                PrimitiveType::Bool => "boolean".to_string(),
1122                _ => "int".to_string(),
1123            },
1124            TypeRef::String | TypeRef::Char => "String".to_string(),
1125            _ => "Object".to_string(),
1126        });
1127
1128        assert!(output.contains("package dev.test;"));
1129        assert!(output.contains("public class ConfigBuilder"));
1130        assert!(output.contains("withTimeout"));
1131        assert!(output.contains("withEnabled"));
1132        assert!(output.contains("withName"));
1133        assert!(output.contains("public Config build()"));
1134    }
1135
1136    #[test]
1137    fn test_gen_csharp_record() {
1138        let typ = make_test_type();
1139        let output = gen_csharp_record(&typ, "MyNamespace", &|tr: &TypeRef| match tr {
1140            TypeRef::Primitive(p) => match p {
1141                PrimitiveType::U64 => "ulong".to_string(),
1142                PrimitiveType::Bool => "bool".to_string(),
1143                _ => "int".to_string(),
1144            },
1145            TypeRef::String | TypeRef::Char => "string".to_string(),
1146            _ => "object".to_string(),
1147        });
1148
1149        assert!(output.contains("namespace MyNamespace;"));
1150        assert!(output.contains("public record Config"));
1151        assert!(output.contains("public ulong Timeout"));
1152        assert!(output.contains("public bool Enabled"));
1153        assert!(output.contains("public string Name"));
1154        assert!(output.contains("init;"));
1155    }
1156
1157    #[test]
1158    fn test_default_value_float_literal() {
1159        let field = FieldDef {
1160            name: "ratio".to_string(),
1161            ty: TypeRef::Primitive(PrimitiveType::F64),
1162            optional: false,
1163            default: None,
1164            doc: String::new(),
1165            sanitized: false,
1166            is_boxed: false,
1167            type_rust_path: None,
1168            cfg: None,
1169            typed_default: Some(DefaultValue::FloatLiteral(1.5)),
1170            core_wrapper: CoreWrapper::None,
1171            vec_inner_core_wrapper: CoreWrapper::None,
1172            newtype_wrapper: None,
1173        };
1174        let result = default_value_for_field(&field, "python");
1175        assert!(result.contains("1.5"));
1176    }
1177
1178    #[test]
1179    fn test_default_value_no_typed_no_default() {
1180        let field = FieldDef {
1181            name: "count".to_string(),
1182            ty: TypeRef::Primitive(PrimitiveType::U32),
1183            optional: false,
1184            default: None,
1185            doc: String::new(),
1186            sanitized: false,
1187            is_boxed: false,
1188            type_rust_path: None,
1189            cfg: None,
1190            typed_default: None,
1191            core_wrapper: CoreWrapper::None,
1192            vec_inner_core_wrapper: CoreWrapper::None,
1193            newtype_wrapper: None,
1194        };
1195        // Should fall back to type-based zero value
1196        assert_eq!(default_value_for_field(&field, "python"), "0");
1197        assert_eq!(default_value_for_field(&field, "go"), "0");
1198    }
1199
1200    fn make_field(name: &str, ty: TypeRef) -> FieldDef {
1201        FieldDef {
1202            name: name.to_string(),
1203            ty,
1204            optional: false,
1205            default: None,
1206            doc: String::new(),
1207            sanitized: false,
1208            is_boxed: false,
1209            type_rust_path: None,
1210            cfg: None,
1211            typed_default: None,
1212            core_wrapper: CoreWrapper::None,
1213            vec_inner_core_wrapper: CoreWrapper::None,
1214            newtype_wrapper: None,
1215        }
1216    }
1217
1218    fn simple_type_mapper(tr: &TypeRef) -> String {
1219        match tr {
1220            TypeRef::Primitive(p) => match p {
1221                PrimitiveType::U64 => "u64".to_string(),
1222                PrimitiveType::Bool => "bool".to_string(),
1223                PrimitiveType::U32 => "u32".to_string(),
1224                _ => "i64".to_string(),
1225            },
1226            TypeRef::String | TypeRef::Char => "String".to_string(),
1227            TypeRef::Optional(inner) => format!("Option<{}>", simple_type_mapper(inner)),
1228            TypeRef::Vec(inner) => format!("Vec<{}>", simple_type_mapper(inner)),
1229            TypeRef::Named(n) => n.clone(),
1230            _ => "Value".to_string(),
1231        }
1232    }
1233
1234    // -------------------------------------------------------------------------
1235    // default_value_for_field — untested branches
1236    // -------------------------------------------------------------------------
1237
1238    #[test]
1239    fn test_default_value_bool_literal_ruby() {
1240        let field = FieldDef {
1241            name: "flag".to_string(),
1242            ty: TypeRef::Primitive(PrimitiveType::Bool),
1243            optional: false,
1244            default: None,
1245            doc: String::new(),
1246            sanitized: false,
1247            is_boxed: false,
1248            type_rust_path: None,
1249            cfg: None,
1250            typed_default: Some(DefaultValue::BoolLiteral(true)),
1251            core_wrapper: CoreWrapper::None,
1252            vec_inner_core_wrapper: CoreWrapper::None,
1253            newtype_wrapper: None,
1254        };
1255        assert_eq!(default_value_for_field(&field, "ruby"), "true");
1256        assert_eq!(default_value_for_field(&field, "php"), "true");
1257        assert_eq!(default_value_for_field(&field, "csharp"), "true");
1258        assert_eq!(default_value_for_field(&field, "java"), "true");
1259        assert_eq!(default_value_for_field(&field, "rust"), "true");
1260    }
1261
1262    #[test]
1263    fn test_default_value_bool_literal_r() {
1264        let field = FieldDef {
1265            name: "flag".to_string(),
1266            ty: TypeRef::Primitive(PrimitiveType::Bool),
1267            optional: false,
1268            default: None,
1269            doc: String::new(),
1270            sanitized: false,
1271            is_boxed: false,
1272            type_rust_path: None,
1273            cfg: None,
1274            typed_default: Some(DefaultValue::BoolLiteral(false)),
1275            core_wrapper: CoreWrapper::None,
1276            vec_inner_core_wrapper: CoreWrapper::None,
1277            newtype_wrapper: None,
1278        };
1279        assert_eq!(default_value_for_field(&field, "r"), "FALSE");
1280    }
1281
1282    #[test]
1283    fn test_default_value_string_literal_rust() {
1284        let field = FieldDef {
1285            name: "label".to_string(),
1286            ty: TypeRef::String,
1287            optional: false,
1288            default: None,
1289            doc: String::new(),
1290            sanitized: false,
1291            is_boxed: false,
1292            type_rust_path: None,
1293            cfg: None,
1294            typed_default: Some(DefaultValue::StringLiteral("hello".to_string())),
1295            core_wrapper: CoreWrapper::None,
1296            vec_inner_core_wrapper: CoreWrapper::None,
1297            newtype_wrapper: None,
1298        };
1299        assert_eq!(default_value_for_field(&field, "rust"), "\"hello\".to_string()");
1300    }
1301
1302    #[test]
1303    fn test_default_value_string_literal_escapes_quotes() {
1304        let field = FieldDef {
1305            name: "label".to_string(),
1306            ty: TypeRef::String,
1307            optional: false,
1308            default: None,
1309            doc: String::new(),
1310            sanitized: false,
1311            is_boxed: false,
1312            type_rust_path: None,
1313            cfg: None,
1314            typed_default: Some(DefaultValue::StringLiteral("say \"hi\"".to_string())),
1315            core_wrapper: CoreWrapper::None,
1316            vec_inner_core_wrapper: CoreWrapper::None,
1317            newtype_wrapper: None,
1318        };
1319        assert_eq!(default_value_for_field(&field, "python"), "\"say \\\"hi\\\"\"");
1320    }
1321
1322    #[test]
1323    fn test_default_value_float_literal_whole_number() {
1324        // A whole-number float should be rendered with ".0" suffix.
1325        let field = FieldDef {
1326            name: "scale".to_string(),
1327            ty: TypeRef::Primitive(PrimitiveType::F32),
1328            optional: false,
1329            default: None,
1330            doc: String::new(),
1331            sanitized: false,
1332            is_boxed: false,
1333            type_rust_path: None,
1334            cfg: None,
1335            typed_default: Some(DefaultValue::FloatLiteral(2.0)),
1336            core_wrapper: CoreWrapper::None,
1337            vec_inner_core_wrapper: CoreWrapper::None,
1338            newtype_wrapper: None,
1339        };
1340        let result = default_value_for_field(&field, "python");
1341        assert!(result.contains('.'), "whole-number float should contain '.': {result}");
1342    }
1343
1344    #[test]
1345    fn test_default_value_enum_variant_per_language() {
1346        let field = FieldDef {
1347            name: "format".to_string(),
1348            ty: TypeRef::Named("OutputFormat".to_string()),
1349            optional: false,
1350            default: None,
1351            doc: String::new(),
1352            sanitized: false,
1353            is_boxed: false,
1354            type_rust_path: None,
1355            cfg: None,
1356            typed_default: Some(DefaultValue::EnumVariant("JsonOutput".to_string())),
1357            core_wrapper: CoreWrapper::None,
1358            vec_inner_core_wrapper: CoreWrapper::None,
1359            newtype_wrapper: None,
1360        };
1361        assert_eq!(default_value_for_field(&field, "python"), "OutputFormat.JSON_OUTPUT");
1362        assert_eq!(default_value_for_field(&field, "ruby"), "OutputFormat::JsonOutput");
1363        assert_eq!(default_value_for_field(&field, "go"), "OutputFormatJsonOutput");
1364        assert_eq!(default_value_for_field(&field, "java"), "OutputFormat.JSON_OUTPUT");
1365        assert_eq!(default_value_for_field(&field, "csharp"), "OutputFormat.JsonOutput");
1366        assert_eq!(default_value_for_field(&field, "php"), "OutputFormat::JsonOutput");
1367        assert_eq!(default_value_for_field(&field, "r"), "OutputFormat$JsonOutput");
1368        assert_eq!(default_value_for_field(&field, "rust"), "OutputFormat::JsonOutput");
1369    }
1370
1371    #[test]
1372    fn test_default_value_empty_vec_per_language() {
1373        let field = FieldDef {
1374            name: "items".to_string(),
1375            ty: TypeRef::Vec(Box::new(TypeRef::String)),
1376            optional: false,
1377            default: None,
1378            doc: String::new(),
1379            sanitized: false,
1380            is_boxed: false,
1381            type_rust_path: None,
1382            cfg: None,
1383            typed_default: Some(DefaultValue::Empty),
1384            core_wrapper: CoreWrapper::None,
1385            vec_inner_core_wrapper: CoreWrapper::None,
1386            newtype_wrapper: None,
1387        };
1388        assert_eq!(default_value_for_field(&field, "python"), "[]");
1389        assert_eq!(default_value_for_field(&field, "ruby"), "[]");
1390        assert_eq!(default_value_for_field(&field, "csharp"), "[]");
1391        assert_eq!(default_value_for_field(&field, "go"), "nil");
1392        assert_eq!(default_value_for_field(&field, "java"), "List.of()");
1393        assert_eq!(default_value_for_field(&field, "php"), "[]");
1394        assert_eq!(default_value_for_field(&field, "r"), "c()");
1395        assert_eq!(default_value_for_field(&field, "rust"), "vec![]");
1396    }
1397
1398    #[test]
1399    fn test_default_value_empty_map_per_language() {
1400        let field = FieldDef {
1401            name: "meta".to_string(),
1402            ty: TypeRef::Map(Box::new(TypeRef::String), Box::new(TypeRef::String)),
1403            optional: false,
1404            default: None,
1405            doc: String::new(),
1406            sanitized: false,
1407            is_boxed: false,
1408            type_rust_path: None,
1409            cfg: None,
1410            typed_default: Some(DefaultValue::Empty),
1411            core_wrapper: CoreWrapper::None,
1412            vec_inner_core_wrapper: CoreWrapper::None,
1413            newtype_wrapper: None,
1414        };
1415        assert_eq!(default_value_for_field(&field, "python"), "{}");
1416        assert_eq!(default_value_for_field(&field, "go"), "nil");
1417        assert_eq!(default_value_for_field(&field, "java"), "Map.of()");
1418        assert_eq!(default_value_for_field(&field, "rust"), "Default::default()");
1419    }
1420
1421    #[test]
1422    fn test_default_value_empty_bool_primitive() {
1423        let field = FieldDef {
1424            name: "flag".to_string(),
1425            ty: TypeRef::Primitive(PrimitiveType::Bool),
1426            optional: false,
1427            default: None,
1428            doc: String::new(),
1429            sanitized: false,
1430            is_boxed: false,
1431            type_rust_path: None,
1432            cfg: None,
1433            typed_default: Some(DefaultValue::Empty),
1434            core_wrapper: CoreWrapper::None,
1435            vec_inner_core_wrapper: CoreWrapper::None,
1436            newtype_wrapper: None,
1437        };
1438        assert_eq!(default_value_for_field(&field, "python"), "False");
1439        assert_eq!(default_value_for_field(&field, "ruby"), "false");
1440        assert_eq!(default_value_for_field(&field, "go"), "false");
1441    }
1442
1443    #[test]
1444    fn test_default_value_empty_float_primitive() {
1445        let field = FieldDef {
1446            name: "ratio".to_string(),
1447            ty: TypeRef::Primitive(PrimitiveType::F64),
1448            optional: false,
1449            default: None,
1450            doc: String::new(),
1451            sanitized: false,
1452            is_boxed: false,
1453            type_rust_path: None,
1454            cfg: None,
1455            typed_default: Some(DefaultValue::Empty),
1456            core_wrapper: CoreWrapper::None,
1457            vec_inner_core_wrapper: CoreWrapper::None,
1458            newtype_wrapper: None,
1459        };
1460        assert_eq!(default_value_for_field(&field, "python"), "0.0");
1461    }
1462
1463    #[test]
1464    fn test_default_value_empty_string_type() {
1465        let field = FieldDef {
1466            name: "label".to_string(),
1467            ty: TypeRef::String,
1468            optional: false,
1469            default: None,
1470            doc: String::new(),
1471            sanitized: false,
1472            is_boxed: false,
1473            type_rust_path: None,
1474            cfg: None,
1475            typed_default: Some(DefaultValue::Empty),
1476            core_wrapper: CoreWrapper::None,
1477            vec_inner_core_wrapper: CoreWrapper::None,
1478            newtype_wrapper: None,
1479        };
1480        assert_eq!(default_value_for_field(&field, "rust"), "String::new()");
1481        assert_eq!(default_value_for_field(&field, "python"), "\"\"");
1482    }
1483
1484    #[test]
1485    fn test_default_value_empty_bytes_type() {
1486        let field = FieldDef {
1487            name: "data".to_string(),
1488            ty: TypeRef::Bytes,
1489            optional: false,
1490            default: None,
1491            doc: String::new(),
1492            sanitized: false,
1493            is_boxed: false,
1494            type_rust_path: None,
1495            cfg: None,
1496            typed_default: Some(DefaultValue::Empty),
1497            core_wrapper: CoreWrapper::None,
1498            vec_inner_core_wrapper: CoreWrapper::None,
1499            newtype_wrapper: None,
1500        };
1501        assert_eq!(default_value_for_field(&field, "python"), "b\"\"");
1502        assert_eq!(default_value_for_field(&field, "go"), "[]byte{}");
1503        assert_eq!(default_value_for_field(&field, "rust"), "vec![]");
1504    }
1505
1506    #[test]
1507    fn test_default_value_empty_json_type() {
1508        let field = FieldDef {
1509            name: "payload".to_string(),
1510            ty: TypeRef::Json,
1511            optional: false,
1512            default: None,
1513            doc: String::new(),
1514            sanitized: false,
1515            is_boxed: false,
1516            type_rust_path: None,
1517            cfg: None,
1518            typed_default: Some(DefaultValue::Empty),
1519            core_wrapper: CoreWrapper::None,
1520            vec_inner_core_wrapper: CoreWrapper::None,
1521            newtype_wrapper: None,
1522        };
1523        assert_eq!(default_value_for_field(&field, "python"), "{}");
1524        assert_eq!(default_value_for_field(&field, "ruby"), "{}");
1525        assert_eq!(default_value_for_field(&field, "go"), "json.RawMessage(nil)");
1526        assert_eq!(default_value_for_field(&field, "r"), "list()");
1527        assert_eq!(default_value_for_field(&field, "rust"), "serde_json::json!({})");
1528    }
1529
1530    #[test]
1531    fn test_default_value_none_ruby_php_r() {
1532        let field = FieldDef {
1533            name: "maybe".to_string(),
1534            ty: TypeRef::Optional(Box::new(TypeRef::String)),
1535            optional: true,
1536            default: None,
1537            doc: String::new(),
1538            sanitized: false,
1539            is_boxed: false,
1540            type_rust_path: None,
1541            cfg: None,
1542            typed_default: Some(DefaultValue::None),
1543            core_wrapper: CoreWrapper::None,
1544            vec_inner_core_wrapper: CoreWrapper::None,
1545            newtype_wrapper: None,
1546        };
1547        assert_eq!(default_value_for_field(&field, "ruby"), "nil");
1548        assert_eq!(default_value_for_field(&field, "php"), "null");
1549        assert_eq!(default_value_for_field(&field, "r"), "NULL");
1550        assert_eq!(default_value_for_field(&field, "rust"), "None");
1551    }
1552
1553    // -------------------------------------------------------------------------
1554    // Fallback (no typed_default, no default) — type-based zero values
1555    // -------------------------------------------------------------------------
1556
1557    #[test]
1558    fn test_default_value_fallback_bool_all_languages() {
1559        let field = make_field("flag", TypeRef::Primitive(PrimitiveType::Bool));
1560        assert_eq!(default_value_for_field(&field, "python"), "False");
1561        assert_eq!(default_value_for_field(&field, "ruby"), "false");
1562        assert_eq!(default_value_for_field(&field, "csharp"), "false");
1563        assert_eq!(default_value_for_field(&field, "java"), "false");
1564        assert_eq!(default_value_for_field(&field, "php"), "false");
1565        assert_eq!(default_value_for_field(&field, "r"), "FALSE");
1566        assert_eq!(default_value_for_field(&field, "rust"), "false");
1567    }
1568
1569    #[test]
1570    fn test_default_value_fallback_float() {
1571        let field = make_field("ratio", TypeRef::Primitive(PrimitiveType::F64));
1572        assert_eq!(default_value_for_field(&field, "python"), "0.0");
1573        assert_eq!(default_value_for_field(&field, "rust"), "0.0");
1574    }
1575
1576    #[test]
1577    fn test_default_value_fallback_string_all_languages() {
1578        let field = make_field("name", TypeRef::String);
1579        assert_eq!(default_value_for_field(&field, "python"), "\"\"");
1580        assert_eq!(default_value_for_field(&field, "ruby"), "\"\"");
1581        assert_eq!(default_value_for_field(&field, "go"), "\"\"");
1582        assert_eq!(default_value_for_field(&field, "java"), "\"\"");
1583        assert_eq!(default_value_for_field(&field, "csharp"), "\"\"");
1584        assert_eq!(default_value_for_field(&field, "php"), "\"\"");
1585        assert_eq!(default_value_for_field(&field, "r"), "\"\"");
1586        assert_eq!(default_value_for_field(&field, "rust"), "String::new()");
1587    }
1588
1589    #[test]
1590    fn test_default_value_fallback_bytes_all_languages() {
1591        let field = make_field("data", TypeRef::Bytes);
1592        assert_eq!(default_value_for_field(&field, "python"), "b\"\"");
1593        assert_eq!(default_value_for_field(&field, "ruby"), "\"\"");
1594        assert_eq!(default_value_for_field(&field, "go"), "[]byte{}");
1595        assert_eq!(default_value_for_field(&field, "java"), "new byte[]{}");
1596        assert_eq!(default_value_for_field(&field, "csharp"), "new byte[]{}");
1597        assert_eq!(default_value_for_field(&field, "php"), "\"\"");
1598        assert_eq!(default_value_for_field(&field, "r"), "raw()");
1599        assert_eq!(default_value_for_field(&field, "rust"), "vec![]");
1600    }
1601
1602    #[test]
1603    fn test_default_value_fallback_optional() {
1604        let field = make_field("maybe", TypeRef::Optional(Box::new(TypeRef::String)));
1605        assert_eq!(default_value_for_field(&field, "python"), "None");
1606        assert_eq!(default_value_for_field(&field, "ruby"), "nil");
1607        assert_eq!(default_value_for_field(&field, "go"), "nil");
1608        assert_eq!(default_value_for_field(&field, "java"), "null");
1609        assert_eq!(default_value_for_field(&field, "csharp"), "null");
1610        assert_eq!(default_value_for_field(&field, "php"), "null");
1611        assert_eq!(default_value_for_field(&field, "r"), "NULL");
1612        assert_eq!(default_value_for_field(&field, "rust"), "None");
1613    }
1614
1615    #[test]
1616    fn test_default_value_fallback_vec_all_languages() {
1617        let field = make_field("items", TypeRef::Vec(Box::new(TypeRef::String)));
1618        assert_eq!(default_value_for_field(&field, "python"), "[]");
1619        assert_eq!(default_value_for_field(&field, "ruby"), "[]");
1620        assert_eq!(default_value_for_field(&field, "go"), "[]interface{}{}");
1621        assert_eq!(default_value_for_field(&field, "java"), "new java.util.ArrayList<>()");
1622        assert_eq!(default_value_for_field(&field, "csharp"), "[]");
1623        assert_eq!(default_value_for_field(&field, "php"), "[]");
1624        assert_eq!(default_value_for_field(&field, "r"), "c()");
1625        assert_eq!(default_value_for_field(&field, "rust"), "vec![]");
1626    }
1627
1628    #[test]
1629    fn test_default_value_fallback_map_all_languages() {
1630        let field = make_field(
1631            "meta",
1632            TypeRef::Map(Box::new(TypeRef::String), Box::new(TypeRef::String)),
1633        );
1634        assert_eq!(default_value_for_field(&field, "python"), "{}");
1635        assert_eq!(default_value_for_field(&field, "ruby"), "{}");
1636        assert_eq!(default_value_for_field(&field, "go"), "make(map[string]interface{})");
1637        assert_eq!(default_value_for_field(&field, "java"), "new java.util.HashMap<>()");
1638        assert_eq!(
1639            default_value_for_field(&field, "csharp"),
1640            "new Dictionary<string, object>()"
1641        );
1642        assert_eq!(default_value_for_field(&field, "php"), "[]");
1643        assert_eq!(default_value_for_field(&field, "r"), "list()");
1644        assert_eq!(
1645            default_value_for_field(&field, "rust"),
1646            "std::collections::HashMap::new()"
1647        );
1648    }
1649
1650    #[test]
1651    fn test_default_value_fallback_json_all_languages() {
1652        let field = make_field("payload", TypeRef::Json);
1653        assert_eq!(default_value_for_field(&field, "python"), "{}");
1654        assert_eq!(default_value_for_field(&field, "ruby"), "{}");
1655        assert_eq!(default_value_for_field(&field, "go"), "json.RawMessage(nil)");
1656        assert_eq!(default_value_for_field(&field, "r"), "list()");
1657        assert_eq!(default_value_for_field(&field, "rust"), "serde_json::json!({})");
1658    }
1659
1660    #[test]
1661    fn test_default_value_fallback_named_type() {
1662        let field = make_field("config", TypeRef::Named("MyConfig".to_string()));
1663        assert_eq!(default_value_for_field(&field, "rust"), "MyConfig::default()");
1664        assert_eq!(default_value_for_field(&field, "python"), "None");
1665        assert_eq!(default_value_for_field(&field, "ruby"), "nil");
1666        assert_eq!(default_value_for_field(&field, "go"), "nil");
1667        assert_eq!(default_value_for_field(&field, "java"), "null");
1668        assert_eq!(default_value_for_field(&field, "csharp"), "null");
1669        assert_eq!(default_value_for_field(&field, "php"), "null");
1670        assert_eq!(default_value_for_field(&field, "r"), "NULL");
1671    }
1672
1673    #[test]
1674    fn test_default_value_fallback_duration() {
1675        // Duration falls through to the wildcard arm
1676        let field = make_field("timeout", TypeRef::Duration);
1677        assert_eq!(default_value_for_field(&field, "python"), "None");
1678        assert_eq!(default_value_for_field(&field, "rust"), "Default::default()");
1679    }
1680
1681    // -------------------------------------------------------------------------
1682    // gen_magnus_kwargs_constructor — positional (≤15 fields)
1683    // -------------------------------------------------------------------------
1684
1685    #[test]
1686    fn test_gen_magnus_kwargs_constructor_positional_basic() {
1687        let typ = make_test_type();
1688        let output = gen_magnus_kwargs_constructor(&typ, &simple_type_mapper);
1689
1690        assert!(output.contains("fn new("), "should have fn new");
1691        // All params are Option<T>
1692        assert!(output.contains("Option<u64>"), "timeout should be Option<u64>");
1693        assert!(output.contains("Option<bool>"), "enabled should be Option<bool>");
1694        assert!(output.contains("Option<String>"), "name should be Option<String>");
1695        assert!(output.contains("-> Self {"), "should return Self");
1696        // timeout has IntLiteral(30), use_unwrap_or_default is false for Named → uses unwrap_or
1697        assert!(
1698            output.contains("timeout: timeout.unwrap_or(30),"),
1699            "should apply int default"
1700        );
1701        // enabled has BoolLiteral(true), not unwrap_or_default
1702        assert!(
1703            output.contains("enabled: enabled.unwrap_or(true),"),
1704            "should apply bool default"
1705        );
1706        // name has StringLiteral, not unwrap_or_default
1707        assert!(
1708            output.contains("name: name.unwrap_or(\"default\".to_string()),"),
1709            "should apply string default"
1710        );
1711    }
1712
1713    #[test]
1714    fn test_gen_magnus_kwargs_constructor_positional_optional_field() {
1715        // A field with optional=true should be assigned directly (no unwrap)
1716        let mut typ = make_test_type();
1717        typ.fields.push(FieldDef {
1718            name: "extra".to_string(),
1719            ty: TypeRef::String,
1720            optional: true,
1721            default: None,
1722            doc: String::new(),
1723            sanitized: false,
1724            is_boxed: false,
1725            type_rust_path: None,
1726            cfg: None,
1727            typed_default: None,
1728            core_wrapper: CoreWrapper::None,
1729            vec_inner_core_wrapper: CoreWrapper::None,
1730            newtype_wrapper: None,
1731        });
1732        let output = gen_magnus_kwargs_constructor(&typ, &simple_type_mapper);
1733        // Optional field param is Option<String> and assigned directly
1734        assert!(output.contains("extra,"), "optional field should be assigned directly");
1735        assert!(!output.contains("extra.unwrap"), "optional field should not use unwrap");
1736    }
1737
1738    #[test]
1739    fn test_gen_magnus_kwargs_constructor_unwrap_or_default() {
1740        // A primitive field with no typed_default and no default should use unwrap_or_default()
1741        let mut typ = make_test_type();
1742        typ.fields.push(FieldDef {
1743            name: "count".to_string(),
1744            ty: TypeRef::Primitive(PrimitiveType::U32),
1745            optional: false,
1746            default: None,
1747            doc: String::new(),
1748            sanitized: false,
1749            is_boxed: false,
1750            type_rust_path: None,
1751            cfg: None,
1752            typed_default: None,
1753            core_wrapper: CoreWrapper::None,
1754            vec_inner_core_wrapper: CoreWrapper::None,
1755            newtype_wrapper: None,
1756        });
1757        let output = gen_magnus_kwargs_constructor(&typ, &simple_type_mapper);
1758        assert!(
1759            output.contains("count: count.unwrap_or_default(),"),
1760            "plain primitive with no default should use unwrap_or_default"
1761        );
1762    }
1763
1764    #[test]
1765    fn test_gen_magnus_kwargs_constructor_hash_path_for_many_fields() {
1766        // Build a type with 16 fields (> MAGNUS_MAX_ARITY = 15) to force hash path
1767        let mut fields: Vec<FieldDef> = (0..16)
1768            .map(|i| FieldDef {
1769                name: format!("field_{i}"),
1770                ty: TypeRef::Primitive(PrimitiveType::U32),
1771                optional: false,
1772                default: None,
1773                doc: String::new(),
1774                sanitized: false,
1775                is_boxed: false,
1776                type_rust_path: None,
1777                cfg: None,
1778                typed_default: None,
1779                core_wrapper: CoreWrapper::None,
1780                vec_inner_core_wrapper: CoreWrapper::None,
1781                newtype_wrapper: None,
1782            })
1783            .collect();
1784        // Make one field optional to exercise that branch in the hash constructor
1785        fields[0].optional = true;
1786
1787        let typ = TypeDef {
1788            name: "BigConfig".to_string(),
1789            rust_path: "crate::BigConfig".to_string(),
1790            original_rust_path: String::new(),
1791            fields,
1792            methods: vec![],
1793            is_opaque: false,
1794            is_clone: true,
1795            doc: String::new(),
1796            cfg: None,
1797            is_trait: false,
1798            has_default: true,
1799            has_stripped_cfg_fields: false,
1800            is_return_type: false,
1801            serde_rename_all: None,
1802            has_serde: false,
1803            super_traits: vec![],
1804        };
1805        let output = gen_magnus_kwargs_constructor(&typ, &simple_type_mapper);
1806
1807        assert!(output.contains("kwargs: magnus::RHash"), "should accept RHash");
1808        assert!(output.contains("ruby.to_symbol("), "should use symbol lookup");
1809        // Optional field uses and_then without unwrap_or
1810        assert!(
1811            output.contains("field_0: kwargs.get(ruby.to_symbol(\"field_0\")).and_then(|v|"),
1812            "optional field should use and_then"
1813        );
1814        assert!(
1815            output.contains("field_0:").then_some(()).is_some(),
1816            "field_0 should appear in output"
1817        );
1818    }
1819
1820    // -------------------------------------------------------------------------
1821    // gen_php_kwargs_constructor
1822    // -------------------------------------------------------------------------
1823
1824    #[test]
1825    fn test_gen_php_kwargs_constructor_basic() {
1826        let typ = make_test_type();
1827        let output = gen_php_kwargs_constructor(&typ, &simple_type_mapper);
1828
1829        assert!(
1830            output.contains("pub fn __construct("),
1831            "should use PHP constructor name"
1832        );
1833        // All params are Option<T>
1834        assert!(
1835            output.contains("timeout: Option<u64>"),
1836            "timeout param should be Option<u64>"
1837        );
1838        assert!(
1839            output.contains("enabled: Option<bool>"),
1840            "enabled param should be Option<bool>"
1841        );
1842        assert!(
1843            output.contains("name: Option<String>"),
1844            "name param should be Option<String>"
1845        );
1846        assert!(output.contains("-> Self {"), "should return Self");
1847        assert!(
1848            output.contains("timeout: timeout.unwrap_or(30),"),
1849            "should apply int default for timeout"
1850        );
1851        assert!(
1852            output.contains("enabled: enabled.unwrap_or(true),"),
1853            "should apply bool default for enabled"
1854        );
1855        assert!(
1856            output.contains("name: name.unwrap_or(\"default\".to_string()),"),
1857            "should apply string default for name"
1858        );
1859    }
1860
1861    #[test]
1862    fn test_gen_php_kwargs_constructor_optional_field_passthrough() {
1863        let mut typ = make_test_type();
1864        typ.fields.push(FieldDef {
1865            name: "tag".to_string(),
1866            ty: TypeRef::String,
1867            optional: true,
1868            default: None,
1869            doc: String::new(),
1870            sanitized: false,
1871            is_boxed: false,
1872            type_rust_path: None,
1873            cfg: None,
1874            typed_default: None,
1875            core_wrapper: CoreWrapper::None,
1876            vec_inner_core_wrapper: CoreWrapper::None,
1877            newtype_wrapper: None,
1878        });
1879        let output = gen_php_kwargs_constructor(&typ, &simple_type_mapper);
1880        assert!(
1881            output.contains("tag,"),
1882            "optional field should be passed through directly"
1883        );
1884        assert!(!output.contains("tag.unwrap"), "optional field should not call unwrap");
1885    }
1886
1887    #[test]
1888    fn test_gen_php_kwargs_constructor_unwrap_or_default_for_primitive() {
1889        let mut typ = make_test_type();
1890        typ.fields.push(FieldDef {
1891            name: "retries".to_string(),
1892            ty: TypeRef::Primitive(PrimitiveType::U32),
1893            optional: false,
1894            default: None,
1895            doc: String::new(),
1896            sanitized: false,
1897            is_boxed: false,
1898            type_rust_path: None,
1899            cfg: None,
1900            typed_default: None,
1901            core_wrapper: CoreWrapper::None,
1902            vec_inner_core_wrapper: CoreWrapper::None,
1903            newtype_wrapper: None,
1904        });
1905        let output = gen_php_kwargs_constructor(&typ, &simple_type_mapper);
1906        assert!(
1907            output.contains("retries: retries.unwrap_or_default(),"),
1908            "primitive with no default should use unwrap_or_default"
1909        );
1910    }
1911
1912    // -------------------------------------------------------------------------
1913    // gen_rustler_kwargs_constructor
1914    // -------------------------------------------------------------------------
1915
1916    #[test]
1917    fn test_gen_rustler_kwargs_constructor_basic() {
1918        let typ = make_test_type();
1919        let output = gen_rustler_kwargs_constructor(&typ, &simple_type_mapper);
1920
1921        assert!(
1922            output.contains("pub fn new(opts: std::collections::HashMap<String, rustler::Term>)"),
1923            "should accept HashMap of Terms"
1924        );
1925        assert!(output.contains("Self {"), "should construct Self");
1926        // timeout has IntLiteral(30) — explicit unwrap_or
1927        assert!(
1928            output.contains("timeout: opts.get(\"timeout\").and_then(|t| t.decode().ok()).unwrap_or(30),"),
1929            "should apply int default for timeout"
1930        );
1931        // enabled has BoolLiteral(true) — explicit unwrap_or
1932        assert!(
1933            output.contains("enabled: opts.get(\"enabled\").and_then(|t| t.decode().ok()).unwrap_or(true),"),
1934            "should apply bool default for enabled"
1935        );
1936    }
1937
1938    #[test]
1939    fn test_gen_rustler_kwargs_constructor_optional_field() {
1940        let mut typ = make_test_type();
1941        typ.fields.push(FieldDef {
1942            name: "extra".to_string(),
1943            ty: TypeRef::String,
1944            optional: true,
1945            default: None,
1946            doc: String::new(),
1947            sanitized: false,
1948            is_boxed: false,
1949            type_rust_path: None,
1950            cfg: None,
1951            typed_default: None,
1952            core_wrapper: CoreWrapper::None,
1953            vec_inner_core_wrapper: CoreWrapper::None,
1954            newtype_wrapper: None,
1955        });
1956        let output = gen_rustler_kwargs_constructor(&typ, &simple_type_mapper);
1957        assert!(
1958            output.contains("extra: opts.get(\"extra\").and_then(|t| t.decode().ok()),"),
1959            "optional field should decode without unwrap"
1960        );
1961    }
1962
1963    #[test]
1964    fn test_gen_rustler_kwargs_constructor_named_type_uses_unwrap_or_default() {
1965        let mut typ = make_test_type();
1966        typ.fields.push(FieldDef {
1967            name: "inner".to_string(),
1968            ty: TypeRef::Named("InnerConfig".to_string()),
1969            optional: false,
1970            default: None,
1971            doc: String::new(),
1972            sanitized: false,
1973            is_boxed: false,
1974            type_rust_path: None,
1975            cfg: None,
1976            typed_default: None,
1977            core_wrapper: CoreWrapper::None,
1978            vec_inner_core_wrapper: CoreWrapper::None,
1979            newtype_wrapper: None,
1980        });
1981        let output = gen_rustler_kwargs_constructor(&typ, &simple_type_mapper);
1982        assert!(
1983            output.contains("inner: opts.get(\"inner\").and_then(|t| t.decode().ok()).unwrap_or_default(),"),
1984            "Named type with no default should use unwrap_or_default"
1985        );
1986    }
1987
1988    #[test]
1989    fn test_gen_rustler_kwargs_constructor_string_field_uses_unwrap_or_default() {
1990        // A String field with a StringLiteral default contains "::", triggering the
1991        // is_enum_variant_default check — should fall back to unwrap_or_default().
1992        let mut typ = make_test_type();
1993        // 'name' field in make_test_type() has StringLiteral("default") — verify it
1994        let output = gen_rustler_kwargs_constructor(&typ, &simple_type_mapper);
1995        assert!(
1996            output.contains("name: opts.get(\"name\").and_then(|t| t.decode().ok()).unwrap_or_default(),"),
1997            "String field with quoted default should use unwrap_or_default"
1998        );
1999        // Also verify a plain string field (no default) also falls through to unwrap_or_default
2000        typ.fields.push(FieldDef {
2001            name: "label".to_string(),
2002            ty: TypeRef::String,
2003            optional: false,
2004            default: None,
2005            doc: String::new(),
2006            sanitized: false,
2007            is_boxed: false,
2008            type_rust_path: None,
2009            cfg: None,
2010            typed_default: None,
2011            core_wrapper: CoreWrapper::None,
2012            vec_inner_core_wrapper: CoreWrapper::None,
2013            newtype_wrapper: None,
2014        });
2015        let output2 = gen_rustler_kwargs_constructor(&typ, &simple_type_mapper);
2016        assert!(
2017            output2.contains("label: opts.get(\"label\").and_then(|t| t.decode().ok()).unwrap_or_default(),"),
2018            "String field with no default should use unwrap_or_default"
2019        );
2020    }
2021
2022    // -------------------------------------------------------------------------
2023    // gen_extendr_kwargs_constructor
2024    // -------------------------------------------------------------------------
2025
2026    #[test]
2027    fn test_gen_extendr_kwargs_constructor_basic() {
2028        let typ = make_test_type();
2029        let output = gen_extendr_kwargs_constructor(&typ, &simple_type_mapper);
2030
2031        assert!(output.contains("#[extendr]"), "should have extendr attribute");
2032        assert!(
2033            output.contains("pub fn new_config("),
2034            "function name should be lowercase type name"
2035        );
2036        // Fields appear as named parameters with defaults
2037        assert!(
2038            output.contains("timeout: u64 = 30"),
2039            "should include timeout with default"
2040        );
2041        // extendr passes "r" to default_value_for_field; BoolLiteral(true) → "TRUE" in R
2042        assert!(
2043            output.contains("enabled: bool = TRUE"),
2044            "should include enabled with R bool default"
2045        );
2046        assert!(
2047            output.contains("name: String = \"default\""),
2048            "should include name with default"
2049        );
2050        assert!(output.contains("-> Config {"), "should return Config");
2051        assert!(output.contains("Config {"), "should construct Config");
2052        assert!(output.contains("timeout,"), "should include timeout in struct literal");
2053        assert!(output.contains("enabled,"), "should include enabled in struct literal");
2054        assert!(output.contains("name,"), "should include name in struct literal");
2055    }
2056
2057    #[test]
2058    fn test_gen_extendr_kwargs_constructor_r_bool_default() {
2059        // R language uses TRUE/FALSE; extendr calls default_value_for_field with "r"
2060        let mut typ = make_test_type();
2061        // Replace the enabled field so it has no typed_default, forcing fallback
2062        typ.fields.retain(|f| f.name != "enabled");
2063        typ.fields.push(FieldDef {
2064            name: "verbose".to_string(),
2065            ty: TypeRef::Primitive(PrimitiveType::Bool),
2066            optional: false,
2067            default: None,
2068            doc: String::new(),
2069            sanitized: false,
2070            is_boxed: false,
2071            type_rust_path: None,
2072            cfg: None,
2073            typed_default: None,
2074            core_wrapper: CoreWrapper::None,
2075            vec_inner_core_wrapper: CoreWrapper::None,
2076            newtype_wrapper: None,
2077        });
2078        let output = gen_extendr_kwargs_constructor(&typ, &simple_type_mapper);
2079        assert!(
2080            output.contains("verbose: bool = FALSE"),
2081            "R bool default should be FALSE: {output}"
2082        );
2083    }
2084
2085    // -------------------------------------------------------------------------
2086    // gen_go_functional_options — tuple-field filtering
2087    // -------------------------------------------------------------------------
2088
2089    #[test]
2090    fn test_gen_go_functional_options_skips_tuple_fields() {
2091        let mut typ = make_test_type();
2092        typ.fields.push(FieldDef {
2093            name: "_0".to_string(),
2094            ty: TypeRef::Primitive(PrimitiveType::U32),
2095            optional: false,
2096            default: None,
2097            doc: String::new(),
2098            sanitized: false,
2099            is_boxed: false,
2100            type_rust_path: None,
2101            cfg: None,
2102            typed_default: None,
2103            core_wrapper: CoreWrapper::None,
2104            vec_inner_core_wrapper: CoreWrapper::None,
2105            newtype_wrapper: None,
2106        });
2107        let output = gen_go_functional_options(&typ, &simple_type_mapper);
2108        assert!(
2109            !output.contains("_0"),
2110            "tuple field _0 should be filtered out from Go output"
2111        );
2112    }
2113
2114    // -------------------------------------------------------------------------
2115    // as_type_path_prefix — tested indirectly through hash constructor
2116    // -------------------------------------------------------------------------
2117
2118    #[test]
2119    fn test_gen_magnus_hash_constructor_generic_type_prefix() {
2120        // A field with a Vec type should use <Vec<...>>::try_convert UFCS form
2121        let fields: Vec<FieldDef> = (0..16)
2122            .map(|i| FieldDef {
2123                name: format!("field_{i}"),
2124                ty: if i == 0 {
2125                    TypeRef::Vec(Box::new(TypeRef::String))
2126                } else {
2127                    TypeRef::Primitive(PrimitiveType::U32)
2128                },
2129                optional: false,
2130                default: None,
2131                doc: String::new(),
2132                sanitized: false,
2133                is_boxed: false,
2134                type_rust_path: None,
2135                cfg: None,
2136                typed_default: None,
2137                core_wrapper: CoreWrapper::None,
2138                vec_inner_core_wrapper: CoreWrapper::None,
2139                newtype_wrapper: None,
2140            })
2141            .collect();
2142        let typ = TypeDef {
2143            name: "WideConfig".to_string(),
2144            rust_path: "crate::WideConfig".to_string(),
2145            original_rust_path: String::new(),
2146            fields,
2147            methods: vec![],
2148            is_opaque: false,
2149            is_clone: true,
2150            doc: String::new(),
2151            cfg: None,
2152            is_trait: false,
2153            has_default: true,
2154            has_stripped_cfg_fields: false,
2155            is_return_type: false,
2156            serde_rename_all: None,
2157            has_serde: false,
2158            super_traits: vec![],
2159        };
2160        let output = gen_magnus_kwargs_constructor(&typ, &simple_type_mapper);
2161        // Vec<String> is a generic type; must use <Vec<String>>::try_convert
2162        assert!(
2163            output.contains("<Vec<String>>::try_convert"),
2164            "generic types should use UFCS angle-bracket prefix: {output}"
2165        );
2166    }
2167}