Skip to main content

alef_codegen/
config_gen.rs

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