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 | TypeRef::Json => match language {
365                        "rust" => "String::new()".to_string(),
366                        _ => "\"\"".to_string(),
367                    },
368                    TypeRef::Duration => "0".to_string(),
369                    TypeRef::Bytes => match language {
370                        "python" => "b\"\"".to_string(),
371                        "go" => "[]byte{}".to_string(),
372                        "rust" => "vec![]".to_string(),
373                        _ => "\"\"".to_string(),
374                    },
375                    _ => match language {
376                        "python" => "None".to_string(),
377                        "ruby" => "nil".to_string(),
378                        "go" => "nil".to_string(),
379                        "rust" => "Default::default()".to_string(),
380                        _ => "null".to_string(),
381                    },
382                }
383            }
384            DefaultValue::None => match language {
385                "python" => "None".to_string(),
386                "ruby" => "nil".to_string(),
387                "go" => "nil".to_string(),
388                "java" => "null".to_string(),
389                "csharp" => "null".to_string(),
390                "php" => "null".to_string(),
391                "r" => "NULL".to_string(),
392                "rust" => "None".to_string(),
393                _ => "null".to_string(),
394            },
395        };
396    }
397
398    // Fall back to string default if it exists
399    if let Some(default_str) = &field.default {
400        return default_str.clone();
401    }
402
403    // Final fallback: type-based zero value
404    match &field.ty {
405        TypeRef::Primitive(p) => match p {
406            alef_core::ir::PrimitiveType::Bool => match language {
407                "python" => "False".to_string(),
408                "ruby" => "false".to_string(),
409                "csharp" => "false".to_string(),
410                "java" => "false".to_string(),
411                "php" => "false".to_string(),
412                "r" => "FALSE".to_string(),
413                _ => "false".to_string(),
414            },
415            alef_core::ir::PrimitiveType::U8
416            | alef_core::ir::PrimitiveType::U16
417            | alef_core::ir::PrimitiveType::U32
418            | alef_core::ir::PrimitiveType::U64
419            | alef_core::ir::PrimitiveType::I8
420            | alef_core::ir::PrimitiveType::I16
421            | alef_core::ir::PrimitiveType::I32
422            | alef_core::ir::PrimitiveType::I64
423            | alef_core::ir::PrimitiveType::Usize
424            | alef_core::ir::PrimitiveType::Isize => "0".to_string(),
425            alef_core::ir::PrimitiveType::F32 | alef_core::ir::PrimitiveType::F64 => "0.0".to_string(),
426        },
427        TypeRef::String | TypeRef::Char => match language {
428            "python" => "\"\"".to_string(),
429            "ruby" => "\"\"".to_string(),
430            "go" => "\"\"".to_string(),
431            "java" => "\"\"".to_string(),
432            "csharp" => "\"\"".to_string(),
433            "php" => "\"\"".to_string(),
434            "r" => "\"\"".to_string(),
435            "rust" => "String::new()".to_string(),
436            _ => "\"\"".to_string(),
437        },
438        TypeRef::Bytes => match language {
439            "python" => "b\"\"".to_string(),
440            "ruby" => "\"\"".to_string(),
441            "go" => "[]byte{}".to_string(),
442            "java" => "new byte[]{}".to_string(),
443            "csharp" => "new byte[]{}".to_string(),
444            "php" => "\"\"".to_string(),
445            "r" => "raw()".to_string(),
446            "rust" => "vec![]".to_string(),
447            _ => "[]".to_string(),
448        },
449        TypeRef::Optional(_) => match language {
450            "python" => "None".to_string(),
451            "ruby" => "nil".to_string(),
452            "go" => "nil".to_string(),
453            "java" => "null".to_string(),
454            "csharp" => "null".to_string(),
455            "php" => "null".to_string(),
456            "r" => "NULL".to_string(),
457            "rust" => "None".to_string(),
458            _ => "null".to_string(),
459        },
460        TypeRef::Vec(_) => match language {
461            "python" => "[]".to_string(),
462            "ruby" => "[]".to_string(),
463            "go" => "[]interface{}{}".to_string(),
464            "java" => "new java.util.ArrayList<>()".to_string(),
465            "csharp" => "[]".to_string(),
466            "php" => "[]".to_string(),
467            "r" => "c()".to_string(),
468            "rust" => "vec![]".to_string(),
469            _ => "[]".to_string(),
470        },
471        TypeRef::Map(_, _) => match language {
472            "python" => "{}".to_string(),
473            "ruby" => "{}".to_string(),
474            "go" => "make(map[string]interface{})".to_string(),
475            "java" => "new java.util.HashMap<>()".to_string(),
476            "csharp" => "new Dictionary<string, object>()".to_string(),
477            "php" => "[]".to_string(),
478            "r" => "list()".to_string(),
479            "rust" => "std::collections::HashMap::new()".to_string(),
480            _ => "{}".to_string(),
481        },
482        TypeRef::Json => match language {
483            "python" => "{}".to_string(),
484            "ruby" => "{}".to_string(),
485            "go" => "make(map[string]interface{})".to_string(),
486            "java" => "new com.fasterxml.jackson.databind.JsonNode()".to_string(),
487            "csharp" => "JObject.Parse(\"{}\")".to_string(),
488            "php" => "[]".to_string(),
489            "r" => "list()".to_string(),
490            "rust" => "serde_json::json!({})".to_string(),
491            _ => "{}".to_string(),
492        },
493        TypeRef::Named(name) => match language {
494            "rust" => format!("{name}::default()"),
495            "python" => "None".to_string(),
496            "ruby" => "nil".to_string(),
497            "go" => "nil".to_string(),
498            "java" => "null".to_string(),
499            "csharp" => "null".to_string(),
500            "php" => "null".to_string(),
501            "r" => "NULL".to_string(),
502            _ => "null".to_string(),
503        },
504        _ => match language {
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            "rust" => "Default::default()".to_string(),
513            _ => "null".to_string(),
514        },
515    }
516}
517
518// Helper trait extension for TypeRef to get type name
519trait TypeRefExt {
520    fn type_name(&self) -> String;
521}
522
523impl TypeRefExt for TypeRef {
524    fn type_name(&self) -> String {
525        match self {
526            TypeRef::Named(n) => n.clone(),
527            TypeRef::Primitive(p) => format!("{:?}", p),
528            TypeRef::String | TypeRef::Char => "String".to_string(),
529            TypeRef::Bytes => "Bytes".to_string(),
530            TypeRef::Optional(inner) => format!("Option<{}>", inner.type_name()),
531            TypeRef::Vec(inner) => format!("Vec<{}>", inner.type_name()),
532            TypeRef::Map(k, v) => format!("Map<{}, {}>", k.type_name(), v.type_name()),
533            TypeRef::Path => "Path".to_string(),
534            TypeRef::Unit => "()".to_string(),
535            TypeRef::Json => "Json".to_string(),
536            TypeRef::Duration => "Duration".to_string(),
537        }
538    }
539}
540
541/// The maximum arity supported by Magnus `function!` macro.
542const MAGNUS_MAX_ARITY: usize = 15;
543
544/// Generate a Magnus (Ruby) kwargs constructor for a type with `has_default`.
545///
546/// For types with <=15 fields, generates a positional `Option<T>` parameter constructor.
547/// For types with >15 fields (exceeding Magnus arity limit), generates a hash-based constructor
548/// using `RHash` that extracts fields by name, applying defaults for missing keys.
549pub fn gen_magnus_kwargs_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
550    if typ.fields.len() > MAGNUS_MAX_ARITY {
551        gen_magnus_hash_constructor(typ, type_mapper)
552    } else {
553        gen_magnus_positional_constructor(typ, type_mapper)
554    }
555}
556
557/// Wrap a type string for use as a type-path prefix in Rust.
558///
559/// Types containing `<` (generics like `Vec<String>`, `Option<T>`) cannot be used as
560/// `Vec<String>::try_convert(v)` — that's a parse error. They must use the UFCS form
561/// `<Vec<String>>::try_convert(v)` instead. Simple names like `String`, `bool` can use
562/// `String::try_convert(v)` directly.
563fn as_type_path_prefix(type_str: &str) -> String {
564    if type_str.contains('<') {
565        format!("<{type_str}>")
566    } else {
567        type_str.to_string()
568    }
569}
570
571/// Generate a hash-based Magnus constructor for types with many fields.
572/// Accepts `(kwargs: RHash)` and extracts each field by symbol name, applying defaults.
573fn gen_magnus_hash_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
574    use std::fmt::Write;
575    let mut out = String::with_capacity(1024);
576
577    writeln!(out, "fn new(kwargs: magnus::RHash) -> Result<Self, magnus::Error> {{").ok();
578    writeln!(out, "    let ruby = unsafe {{ magnus::Ruby::get_unchecked() }};").ok();
579    writeln!(out, "    Ok(Self {{").ok();
580
581    for field in &typ.fields {
582        let is_optional = field_is_optional_in_rust(field);
583        // Use inner type for try_convert, since the hash value is the inner T, not Option<T>.
584        let inner_type = type_mapper(&field.ty);
585        let type_prefix = as_type_path_prefix(&inner_type);
586        if is_optional {
587            // Field is Option<T>: extract from hash, wrap in Some, default to None
588            writeln!(
589                out,
590                "        {name}: kwargs.get(ruby.to_symbol(\"{name}\")).and_then(|v| {type_prefix}::try_convert(v).ok()),",
591                name = field.name,
592                type_prefix = type_prefix,
593            ).ok();
594        } else if use_unwrap_or_default(field) {
595            writeln!(
596                out,
597                "        {name}: kwargs.get(ruby.to_symbol(\"{name}\")).and_then(|v| {type_prefix}::try_convert(v).ok()).unwrap_or_default(),",
598                name = field.name,
599                type_prefix = type_prefix,
600            ).ok();
601        } else {
602            let default_str = default_value_for_field(field, "rust");
603            writeln!(
604                out,
605                "        {name}: kwargs.get(ruby.to_symbol(\"{name}\")).and_then(|v| {type_prefix}::try_convert(v).ok()).unwrap_or({default}),",
606                name = field.name,
607                type_prefix = type_prefix,
608                default = default_str,
609            ).ok();
610        }
611    }
612
613    writeln!(out, "    }})").ok();
614    writeln!(out, "}}").ok();
615
616    out
617}
618
619/// Returns true if the generated Rust field type is already `Option<T>`.
620/// This covers both:
621/// - Fields with `optional: true` (the Rust field type becomes `Option<inner_type>`)
622/// - Fields whose `TypeRef` is explicitly `Optional(_)` (rare, for nested Option types)
623fn field_is_optional_in_rust(field: &FieldDef) -> bool {
624    field.optional || matches!(&field.ty, TypeRef::Optional(_))
625}
626
627/// Generate a positional Magnus constructor for types with <=15 fields.
628/// Uses `Option<T>` parameters and applies defaults in the body.
629fn gen_magnus_positional_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
630    use std::fmt::Write;
631    let mut out = String::with_capacity(512);
632
633    writeln!(out, "fn new(").ok();
634
635    // All params are Option<T> so Ruby users can pass nil for any field.
636    // If the Rust field type is already Option<T> (via optional:true or TypeRef::Optional),
637    // use that type directly (avoids Option<Option<T>>).
638    for (i, field) in typ.fields.iter().enumerate() {
639        let comma = if i < typ.fields.len() - 1 { "," } else { "" };
640        let is_optional = field_is_optional_in_rust(field);
641        if is_optional {
642            // field.ty is the inner type; the mapper maps inner type, we wrap in Option<>
643            // BUT the type_mapper call site already wraps when field.optional==true.
644            // Here we call type_mapper on the field's inner type directly to get the param type.
645            let inner_type = type_mapper(&field.ty);
646            writeln!(out, "    {}: Option<{}>{}", field.name, inner_type, comma).ok();
647        } else {
648            let field_type = type_mapper(&field.ty);
649            writeln!(out, "    {}: Option<{}>{}", field.name, field_type, comma).ok();
650        }
651    }
652
653    writeln!(out, ") -> Self {{").ok();
654    writeln!(out, "    Self {{").ok();
655
656    for field in &typ.fields {
657        let is_optional = field_is_optional_in_rust(field);
658        if is_optional {
659            // The Rust field is Option<T>; param is Option<T>; assign directly.
660            writeln!(out, "        {},", field.name).ok();
661        } else if use_unwrap_or_default(field) {
662            writeln!(out, "        {}: {}.unwrap_or_default(),", field.name, field.name).ok();
663        } else {
664            let default_str = default_value_for_field(field, "rust");
665            writeln!(
666                out,
667                "        {}: {}.unwrap_or({}),",
668                field.name, field.name, default_str
669            )
670            .ok();
671        }
672    }
673
674    writeln!(out, "    }}").ok();
675    writeln!(out, "}}").ok();
676
677    out
678}
679
680/// Generate a PHP kwargs constructor for a type with `has_default`.
681/// All fields become `Option<T>` parameters so PHP users can omit any field.
682/// Assignments wrap non-Optional fields in `Some()` and apply defaults.
683pub fn gen_php_kwargs_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
684    use std::fmt::Write;
685    let mut out = String::with_capacity(512);
686
687    writeln!(out, "pub fn __construct(").ok();
688
689    // All params are Option<MappedType> — PHP users can omit any field
690    for (i, field) in typ.fields.iter().enumerate() {
691        let mapped = type_mapper(&field.ty);
692        let comma = if i < typ.fields.len() - 1 { "," } else { "" };
693        writeln!(out, "    {}: Option<{}>{}", field.name, mapped, comma).ok();
694    }
695
696    writeln!(out, ") -> Self {{").ok();
697    writeln!(out, "    Self {{").ok();
698
699    for field in &typ.fields {
700        let is_optional_field = field.optional || matches!(&field.ty, TypeRef::Optional(_));
701        if is_optional_field {
702            // Struct field is Option<T>, param is Option<T> — pass through directly
703            writeln!(out, "        {},", field.name).ok();
704        } else if use_unwrap_or_default(field) {
705            // Struct field is T, param is Option<T> — unwrap with type's default
706            writeln!(out, "        {}: {}.unwrap_or_default(),", field.name, field.name).ok();
707        } else {
708            // Struct field is T, param is Option<T> — unwrap with explicit default
709            let default_str = default_value_for_field(field, "rust");
710            writeln!(
711                out,
712                "        {}: {}.unwrap_or({}),",
713                field.name, field.name, default_str
714            )
715            .ok();
716        }
717    }
718
719    writeln!(out, "    }}").ok();
720    writeln!(out, "}}").ok();
721
722    out
723}
724
725/// Generate a Rustler (Elixir) kwargs constructor for a type with `has_default`.
726/// Accepts keyword list or map, applies defaults for missing fields.
727pub fn gen_rustler_kwargs_constructor(typ: &TypeDef, _type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
728    use std::fmt::Write;
729    let mut out = String::with_capacity(512);
730
731    // NifStruct already handles keyword list conversion, but we generate
732    // an explicit constructor wrapper that applies defaults.
733    writeln!(
734        out,
735        "pub fn new(opts: std::collections::HashMap<String, rustler::Term>) -> Self {{"
736    )
737    .ok();
738    writeln!(out, "    Self {{").ok();
739
740    // Field assignments with defaults from opts.
741    // Optional fields (Option<T>) need special handling: decode the inner type
742    // directly so we get Option<T> from and_then, with no unwrap_or needed.
743    for field in &typ.fields {
744        if field.optional {
745            // Field type is Option<T>. Decode inner T from the Term, yielding Option<T>.
746            writeln!(
747                out,
748                "        {}: opts.get(\"{}\").and_then(|t| t.decode().ok()),",
749                field.name, field.name
750            )
751            .ok();
752        } else if use_unwrap_or_default(field) {
753            writeln!(
754                out,
755                "        {}: opts.get(\"{}\").and_then(|t| t.decode().ok()).unwrap_or_default(),",
756                field.name, field.name
757            )
758            .ok();
759        } else {
760            let default_str = default_value_for_field(field, "rust");
761            writeln!(
762                out,
763                "        {}: opts.get(\"{}\").and_then(|t| t.decode().ok()).unwrap_or({}),",
764                field.name, field.name, default_str
765            )
766            .ok();
767        }
768    }
769
770    writeln!(out, "    }}").ok();
771    writeln!(out, "}}").ok();
772
773    out
774}
775
776/// Generate an extendr (R) kwargs constructor for a type with `has_default`.
777/// Generates an R-callable function accepting named parameters with defaults.
778pub fn gen_extendr_kwargs_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
779    use std::fmt::Write;
780    let mut out = String::with_capacity(512);
781
782    writeln!(out, "#[extendr]").ok();
783    writeln!(out, "pub fn new_{}(", typ.name.to_lowercase()).ok();
784
785    // Add all fields as named parameters with defaults
786    for (i, field) in typ.fields.iter().enumerate() {
787        let field_type = type_mapper(&field.ty);
788        let default_str = default_value_for_field(field, "r");
789        let comma = if i < typ.fields.len() - 1 { "," } else { "" };
790        writeln!(out, "    {}: {} = {}{}", field.name, field_type, default_str, comma).ok();
791    }
792
793    writeln!(out, ") -> {} {{", typ.name).ok();
794    writeln!(out, "    {} {{", typ.name).ok();
795
796    // Field assignments
797    for field in &typ.fields {
798        writeln!(out, "        {},", field.name).ok();
799    }
800
801    writeln!(out, "    }}").ok();
802    writeln!(out, "}}").ok();
803
804    out
805}
806
807#[cfg(test)]
808mod tests {
809    use super::*;
810    use alef_core::ir::{CoreWrapper, FieldDef, PrimitiveType, TypeRef};
811
812    fn make_test_type() -> TypeDef {
813        TypeDef {
814            name: "Config".to_string(),
815            rust_path: "my_crate::Config".to_string(),
816            fields: vec![
817                FieldDef {
818                    name: "timeout".to_string(),
819                    ty: TypeRef::Primitive(PrimitiveType::U64),
820                    optional: false,
821                    default: Some("30".to_string()),
822                    doc: "Timeout in seconds".to_string(),
823                    sanitized: false,
824                    is_boxed: false,
825                    type_rust_path: None,
826                    cfg: None,
827                    typed_default: Some(DefaultValue::IntLiteral(30)),
828                    core_wrapper: CoreWrapper::None,
829                    vec_inner_core_wrapper: CoreWrapper::None,
830                    newtype_wrapper: None,
831                },
832                FieldDef {
833                    name: "enabled".to_string(),
834                    ty: TypeRef::Primitive(PrimitiveType::Bool),
835                    optional: false,
836                    default: None,
837                    doc: "Enable feature".to_string(),
838                    sanitized: false,
839                    is_boxed: false,
840                    type_rust_path: None,
841                    cfg: None,
842                    typed_default: Some(DefaultValue::BoolLiteral(true)),
843                    core_wrapper: CoreWrapper::None,
844                    vec_inner_core_wrapper: CoreWrapper::None,
845                    newtype_wrapper: None,
846                },
847                FieldDef {
848                    name: "name".to_string(),
849                    ty: TypeRef::String,
850                    optional: false,
851                    default: None,
852                    doc: "Config name".to_string(),
853                    sanitized: false,
854                    is_boxed: false,
855                    type_rust_path: None,
856                    cfg: None,
857                    typed_default: Some(DefaultValue::StringLiteral("default".to_string())),
858                    core_wrapper: CoreWrapper::None,
859                    vec_inner_core_wrapper: CoreWrapper::None,
860                    newtype_wrapper: None,
861                },
862            ],
863            methods: vec![],
864            is_opaque: false,
865            is_clone: true,
866            doc: "Configuration type".to_string(),
867            cfg: None,
868            is_trait: false,
869            has_default: true,
870            has_stripped_cfg_fields: false,
871            is_return_type: false,
872            serde_rename_all: None,
873            has_serde: false,
874        }
875    }
876
877    #[test]
878    fn test_default_value_bool_true_python() {
879        let field = FieldDef {
880            name: "enabled".to_string(),
881            ty: TypeRef::Primitive(PrimitiveType::Bool),
882            optional: false,
883            default: None,
884            doc: String::new(),
885            sanitized: false,
886            is_boxed: false,
887            type_rust_path: None,
888            cfg: None,
889            typed_default: Some(DefaultValue::BoolLiteral(true)),
890            core_wrapper: CoreWrapper::None,
891            vec_inner_core_wrapper: CoreWrapper::None,
892            newtype_wrapper: None,
893        };
894        assert_eq!(default_value_for_field(&field, "python"), "True");
895    }
896
897    #[test]
898    fn test_default_value_bool_false_go() {
899        let field = FieldDef {
900            name: "enabled".to_string(),
901            ty: TypeRef::Primitive(PrimitiveType::Bool),
902            optional: false,
903            default: None,
904            doc: String::new(),
905            sanitized: false,
906            is_boxed: false,
907            type_rust_path: None,
908            cfg: None,
909            typed_default: Some(DefaultValue::BoolLiteral(false)),
910            core_wrapper: CoreWrapper::None,
911            vec_inner_core_wrapper: CoreWrapper::None,
912            newtype_wrapper: None,
913        };
914        assert_eq!(default_value_for_field(&field, "go"), "false");
915    }
916
917    #[test]
918    fn test_default_value_string_literal() {
919        let field = FieldDef {
920            name: "name".to_string(),
921            ty: TypeRef::String,
922            optional: false,
923            default: None,
924            doc: String::new(),
925            sanitized: false,
926            is_boxed: false,
927            type_rust_path: None,
928            cfg: None,
929            typed_default: Some(DefaultValue::StringLiteral("hello".to_string())),
930            core_wrapper: CoreWrapper::None,
931            vec_inner_core_wrapper: CoreWrapper::None,
932            newtype_wrapper: None,
933        };
934        assert_eq!(default_value_for_field(&field, "python"), "\"hello\"");
935        assert_eq!(default_value_for_field(&field, "java"), "\"hello\"");
936    }
937
938    #[test]
939    fn test_default_value_int_literal() {
940        let field = FieldDef {
941            name: "timeout".to_string(),
942            ty: TypeRef::Primitive(PrimitiveType::U64),
943            optional: false,
944            default: None,
945            doc: String::new(),
946            sanitized: false,
947            is_boxed: false,
948            type_rust_path: None,
949            cfg: None,
950            typed_default: Some(DefaultValue::IntLiteral(42)),
951            core_wrapper: CoreWrapper::None,
952            vec_inner_core_wrapper: CoreWrapper::None,
953            newtype_wrapper: None,
954        };
955        let result = default_value_for_field(&field, "python");
956        assert_eq!(result, "42");
957    }
958
959    #[test]
960    fn test_default_value_none() {
961        let field = FieldDef {
962            name: "maybe".to_string(),
963            ty: TypeRef::Optional(Box::new(TypeRef::String)),
964            optional: true,
965            default: None,
966            doc: String::new(),
967            sanitized: false,
968            is_boxed: false,
969            type_rust_path: None,
970            cfg: None,
971            typed_default: Some(DefaultValue::None),
972            core_wrapper: CoreWrapper::None,
973            vec_inner_core_wrapper: CoreWrapper::None,
974            newtype_wrapper: None,
975        };
976        assert_eq!(default_value_for_field(&field, "python"), "None");
977        assert_eq!(default_value_for_field(&field, "go"), "nil");
978        assert_eq!(default_value_for_field(&field, "java"), "null");
979        assert_eq!(default_value_for_field(&field, "csharp"), "null");
980    }
981
982    #[test]
983    fn test_default_value_fallback_string() {
984        let field = FieldDef {
985            name: "name".to_string(),
986            ty: TypeRef::String,
987            optional: false,
988            default: Some("\"custom\"".to_string()),
989            doc: String::new(),
990            sanitized: false,
991            is_boxed: false,
992            type_rust_path: None,
993            cfg: None,
994            typed_default: None,
995            core_wrapper: CoreWrapper::None,
996            vec_inner_core_wrapper: CoreWrapper::None,
997            newtype_wrapper: None,
998        };
999        assert_eq!(default_value_for_field(&field, "python"), "\"custom\"");
1000    }
1001
1002    #[test]
1003    fn test_gen_pyo3_kwargs_constructor() {
1004        let typ = make_test_type();
1005        let output = gen_pyo3_kwargs_constructor(&typ, &|tr: &TypeRef| match tr {
1006            TypeRef::Primitive(p) => format!("{:?}", p),
1007            TypeRef::String | TypeRef::Char => "str".to_string(),
1008            _ => "Any".to_string(),
1009        });
1010
1011        assert!(output.contains("#[new]"));
1012        assert!(output.contains("#[pyo3(signature = ("));
1013        assert!(output.contains("timeout=30"));
1014        assert!(output.contains("enabled=True"));
1015        assert!(output.contains("name=\"default\""));
1016        assert!(output.contains("fn new("));
1017    }
1018
1019    #[test]
1020    fn test_gen_napi_defaults_constructor() {
1021        let typ = make_test_type();
1022        let output = gen_napi_defaults_constructor(&typ, &|tr: &TypeRef| match tr {
1023            TypeRef::Primitive(p) => format!("{:?}", p),
1024            TypeRef::String | TypeRef::Char => "String".to_string(),
1025            _ => "Value".to_string(),
1026        });
1027
1028        assert!(output.contains("pub fn new(mut env: napi::Env, obj: napi::Object)"));
1029        assert!(output.contains("timeout"));
1030        assert!(output.contains("enabled"));
1031        assert!(output.contains("name"));
1032    }
1033
1034    #[test]
1035    fn test_gen_go_functional_options() {
1036        let typ = make_test_type();
1037        let output = gen_go_functional_options(&typ, &|tr: &TypeRef| match tr {
1038            TypeRef::Primitive(p) => match p {
1039                PrimitiveType::U64 => "uint64".to_string(),
1040                PrimitiveType::Bool => "bool".to_string(),
1041                _ => "interface{}".to_string(),
1042            },
1043            TypeRef::String | TypeRef::Char => "string".to_string(),
1044            _ => "interface{}".to_string(),
1045        });
1046
1047        assert!(output.contains("type Config struct {"));
1048        assert!(output.contains("type ConfigOption func(*Config)"));
1049        assert!(output.contains("func WithConfigTimeout(val uint64) ConfigOption"));
1050        assert!(output.contains("func WithConfigEnabled(val bool) ConfigOption"));
1051        assert!(output.contains("func WithConfigName(val string) ConfigOption"));
1052        assert!(output.contains("func NewConfig(opts ...ConfigOption) *Config"));
1053    }
1054
1055    #[test]
1056    fn test_gen_java_builder() {
1057        let typ = make_test_type();
1058        let output = gen_java_builder(&typ, "dev.test", &|tr: &TypeRef| match tr {
1059            TypeRef::Primitive(p) => match p {
1060                PrimitiveType::U64 => "long".to_string(),
1061                PrimitiveType::Bool => "boolean".to_string(),
1062                _ => "int".to_string(),
1063            },
1064            TypeRef::String | TypeRef::Char => "String".to_string(),
1065            _ => "Object".to_string(),
1066        });
1067
1068        assert!(output.contains("package dev.test;"));
1069        assert!(output.contains("public class ConfigBuilder"));
1070        assert!(output.contains("withTimeout"));
1071        assert!(output.contains("withEnabled"));
1072        assert!(output.contains("withName"));
1073        assert!(output.contains("public Config build()"));
1074    }
1075
1076    #[test]
1077    fn test_gen_csharp_record() {
1078        let typ = make_test_type();
1079        let output = gen_csharp_record(&typ, "MyNamespace", &|tr: &TypeRef| match tr {
1080            TypeRef::Primitive(p) => match p {
1081                PrimitiveType::U64 => "ulong".to_string(),
1082                PrimitiveType::Bool => "bool".to_string(),
1083                _ => "int".to_string(),
1084            },
1085            TypeRef::String | TypeRef::Char => "string".to_string(),
1086            _ => "object".to_string(),
1087        });
1088
1089        assert!(output.contains("namespace MyNamespace;"));
1090        assert!(output.contains("public record Config"));
1091        assert!(output.contains("public ulong Timeout"));
1092        assert!(output.contains("public bool Enabled"));
1093        assert!(output.contains("public string Name"));
1094        assert!(output.contains("init;"));
1095    }
1096
1097    #[test]
1098    fn test_default_value_float_literal() {
1099        let field = FieldDef {
1100            name: "ratio".to_string(),
1101            ty: TypeRef::Primitive(PrimitiveType::F64),
1102            optional: false,
1103            default: None,
1104            doc: String::new(),
1105            sanitized: false,
1106            is_boxed: false,
1107            type_rust_path: None,
1108            cfg: None,
1109            typed_default: Some(DefaultValue::FloatLiteral(1.5)),
1110            core_wrapper: CoreWrapper::None,
1111            vec_inner_core_wrapper: CoreWrapper::None,
1112            newtype_wrapper: None,
1113        };
1114        let result = default_value_for_field(&field, "python");
1115        assert!(result.contains("1.5"));
1116    }
1117
1118    #[test]
1119    fn test_default_value_no_typed_no_default() {
1120        let field = FieldDef {
1121            name: "count".to_string(),
1122            ty: TypeRef::Primitive(PrimitiveType::U32),
1123            optional: false,
1124            default: None,
1125            doc: String::new(),
1126            sanitized: false,
1127            is_boxed: false,
1128            type_rust_path: None,
1129            cfg: None,
1130            typed_default: None,
1131            core_wrapper: CoreWrapper::None,
1132            vec_inner_core_wrapper: CoreWrapper::None,
1133            newtype_wrapper: None,
1134        };
1135        // Should fall back to type-based zero value
1136        assert_eq!(default_value_for_field(&field, "python"), "0");
1137        assert_eq!(default_value_for_field(&field, "go"), "0");
1138    }
1139}