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