Skip to main content

alef_codegen/
config_gen.rs

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