Skip to main content

alef_codegen/
config_gen.rs

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