1use alef_core::ir::{DefaultValue, FieldDef, PrimitiveType, TypeDef, TypeRef};
2use heck::{ToPascalCase, ToShoutySnakeCase};
3
4fn 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
11fn 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 field.default.is_none()
21}
22
23pub 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 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 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 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
58pub 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 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
83pub fn gen_go_functional_options(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
86 let mut lines = Vec::new();
87
88 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 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 for field in &typ.fields {
111 if is_tuple_field(field) {
112 continue;
113 }
114 let option_name = format!("With{}{}", typ.name, field.name.to_pascal_case());
115 let go_type = type_mapper(&field.ty);
116 lines.push(format!("// {} sets the {}.", option_name, field.name));
117 lines.push(format!("func {}(val {}) {}Option {{", option_name, go_type, typ.name));
118 lines.push(format!(" return func(c *{}) {{", typ.name));
119 lines.push(format!(" c.{} = val", field.name.to_pascal_case()));
120 lines.push(" }".to_string());
121 lines.push("}".to_string());
122 lines.push("".to_string());
123 }
124
125 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
152pub 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 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 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 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 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
208pub 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 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
243pub fn default_value_for_field(field: &FieldDef, language: &str) -> String {
246 if let Some(typed_default) = &field.typed_default {
248 return match typed_default {
249 DefaultValue::BoolLiteral(b) => match language {
250 "python" => {
251 if *b {
252 "True".to_string()
253 } else {
254 "False".to_string()
255 }
256 }
257 "ruby" => {
258 if *b {
259 "true".to_string()
260 } else {
261 "false".to_string()
262 }
263 }
264 "go" => {
265 if *b {
266 "true".to_string()
267 } else {
268 "false".to_string()
269 }
270 }
271 "java" => {
272 if *b {
273 "true".to_string()
274 } else {
275 "false".to_string()
276 }
277 }
278 "csharp" => {
279 if *b {
280 "true".to_string()
281 } else {
282 "false".to_string()
283 }
284 }
285 "php" => {
286 if *b {
287 "true".to_string()
288 } else {
289 "false".to_string()
290 }
291 }
292 "r" => {
293 if *b {
294 "TRUE".to_string()
295 } else {
296 "FALSE".to_string()
297 }
298 }
299 "rust" => {
300 if *b {
301 "true".to_string()
302 } else {
303 "false".to_string()
304 }
305 }
306 _ => {
307 if *b {
308 "true".to_string()
309 } else {
310 "false".to_string()
311 }
312 }
313 },
314 DefaultValue::StringLiteral(s) => match language {
315 "rust" => format!("\"{}\".to_string()", s.replace('"', "\\\"")),
316 _ => format!("\"{}\"", s.replace('"', "\\\"")),
317 },
318 DefaultValue::IntLiteral(n) => n.to_string(),
319 DefaultValue::FloatLiteral(f) => {
320 let s = f.to_string();
321 if !s.contains('.') { format!("{}.0", s) } else { s }
322 }
323 DefaultValue::EnumVariant(v) => match language {
324 "python" => format!("{}.{}", field.ty.type_name(), v.to_shouty_snake_case()),
325 "ruby" => format!("{}::{}", field.ty.type_name(), v.to_pascal_case()),
326 "go" => format!("{}{}", field.ty.type_name(), v.to_pascal_case()),
327 "java" => format!("{}.{}", field.ty.type_name(), v.to_shouty_snake_case()),
328 "csharp" => format!("{}.{}", field.ty.type_name(), v.to_pascal_case()),
329 "php" => format!("{}::{}", field.ty.type_name(), v.to_pascal_case()),
330 "r" => format!("{}${}", field.ty.type_name(), v.to_pascal_case()),
331 "rust" => format!("{}::{}", field.ty.type_name(), v.to_pascal_case()),
332 _ => v.clone(),
333 },
334 DefaultValue::Empty => {
335 match &field.ty {
337 TypeRef::Vec(_) => match language {
338 "python" | "ruby" | "csharp" => "[]".to_string(),
339 "go" => "nil".to_string(),
340 "java" => "List.of()".to_string(),
341 "php" => "[]".to_string(),
342 "r" => "c()".to_string(),
343 "rust" => "vec![]".to_string(),
344 _ => "null".to_string(),
345 },
346 TypeRef::Map(_, _) => match language {
347 "python" => "{}".to_string(),
348 "go" => "nil".to_string(),
349 "java" => "Map.of()".to_string(),
350 "rust" => "Default::default()".to_string(),
351 _ => "null".to_string(),
352 },
353 TypeRef::Primitive(p) => match p {
354 PrimitiveType::Bool => match language {
355 "python" => "False".to_string(),
356 "ruby" => "false".to_string(),
357 _ => "false".to_string(),
358 },
359 PrimitiveType::F32 | PrimitiveType::F64 => "0.0".to_string(),
360 _ => "0".to_string(),
361 },
362 TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json => match language {
363 "rust" => "String::new()".to_string(),
364 _ => "\"\"".to_string(),
365 },
366 TypeRef::Duration => "0".to_string(),
367 TypeRef::Bytes => match language {
368 "python" => "b\"\"".to_string(),
369 "go" => "[]byte{}".to_string(),
370 "rust" => "vec![]".to_string(),
371 _ => "\"\"".to_string(),
372 },
373 _ => match language {
374 "python" => "None".to_string(),
375 "ruby" => "nil".to_string(),
376 "go" => "nil".to_string(),
377 "rust" => "Default::default()".to_string(),
378 _ => "null".to_string(),
379 },
380 }
381 }
382 DefaultValue::None => match language {
383 "python" => "None".to_string(),
384 "ruby" => "nil".to_string(),
385 "go" => "nil".to_string(),
386 "java" => "null".to_string(),
387 "csharp" => "null".to_string(),
388 "php" => "null".to_string(),
389 "r" => "NULL".to_string(),
390 "rust" => "None".to_string(),
391 _ => "null".to_string(),
392 },
393 };
394 }
395
396 if let Some(default_str) = &field.default {
398 return default_str.clone();
399 }
400
401 match &field.ty {
403 TypeRef::Primitive(p) => match p {
404 alef_core::ir::PrimitiveType::Bool => match language {
405 "python" => "False".to_string(),
406 "ruby" => "false".to_string(),
407 "csharp" => "false".to_string(),
408 "java" => "false".to_string(),
409 "php" => "false".to_string(),
410 "r" => "FALSE".to_string(),
411 _ => "false".to_string(),
412 },
413 alef_core::ir::PrimitiveType::U8
414 | alef_core::ir::PrimitiveType::U16
415 | alef_core::ir::PrimitiveType::U32
416 | alef_core::ir::PrimitiveType::U64
417 | alef_core::ir::PrimitiveType::I8
418 | alef_core::ir::PrimitiveType::I16
419 | alef_core::ir::PrimitiveType::I32
420 | alef_core::ir::PrimitiveType::I64
421 | alef_core::ir::PrimitiveType::Usize
422 | alef_core::ir::PrimitiveType::Isize => "0".to_string(),
423 alef_core::ir::PrimitiveType::F32 | alef_core::ir::PrimitiveType::F64 => "0.0".to_string(),
424 },
425 TypeRef::String | TypeRef::Char => match language {
426 "python" => "\"\"".to_string(),
427 "ruby" => "\"\"".to_string(),
428 "go" => "\"\"".to_string(),
429 "java" => "\"\"".to_string(),
430 "csharp" => "\"\"".to_string(),
431 "php" => "\"\"".to_string(),
432 "r" => "\"\"".to_string(),
433 "rust" => "String::new()".to_string(),
434 _ => "\"\"".to_string(),
435 },
436 TypeRef::Bytes => match language {
437 "python" => "b\"\"".to_string(),
438 "ruby" => "\"\"".to_string(),
439 "go" => "[]byte{}".to_string(),
440 "java" => "new byte[]{}".to_string(),
441 "csharp" => "new byte[]{}".to_string(),
442 "php" => "\"\"".to_string(),
443 "r" => "raw()".to_string(),
444 "rust" => "vec![]".to_string(),
445 _ => "[]".to_string(),
446 },
447 TypeRef::Optional(_) => match language {
448 "python" => "None".to_string(),
449 "ruby" => "nil".to_string(),
450 "go" => "nil".to_string(),
451 "java" => "null".to_string(),
452 "csharp" => "null".to_string(),
453 "php" => "null".to_string(),
454 "r" => "NULL".to_string(),
455 "rust" => "None".to_string(),
456 _ => "null".to_string(),
457 },
458 TypeRef::Vec(_) => match language {
459 "python" => "[]".to_string(),
460 "ruby" => "[]".to_string(),
461 "go" => "[]interface{}{}".to_string(),
462 "java" => "new java.util.ArrayList<>()".to_string(),
463 "csharp" => "[]".to_string(),
464 "php" => "[]".to_string(),
465 "r" => "c()".to_string(),
466 "rust" => "vec![]".to_string(),
467 _ => "[]".to_string(),
468 },
469 TypeRef::Map(_, _) => match language {
470 "python" => "{}".to_string(),
471 "ruby" => "{}".to_string(),
472 "go" => "make(map[string]interface{})".to_string(),
473 "java" => "new java.util.HashMap<>()".to_string(),
474 "csharp" => "new Dictionary<string, object>()".to_string(),
475 "php" => "[]".to_string(),
476 "r" => "list()".to_string(),
477 "rust" => "std::collections::HashMap::new()".to_string(),
478 _ => "{}".to_string(),
479 },
480 TypeRef::Json => match language {
481 "python" => "{}".to_string(),
482 "ruby" => "{}".to_string(),
483 "go" => "make(map[string]interface{})".to_string(),
484 "java" => "new com.fasterxml.jackson.databind.JsonNode()".to_string(),
485 "csharp" => "JObject.Parse(\"{}\")".to_string(),
486 "php" => "[]".to_string(),
487 "r" => "list()".to_string(),
488 "rust" => "serde_json::json!({})".to_string(),
489 _ => "{}".to_string(),
490 },
491 _ => match language {
492 "python" => "None".to_string(),
493 "ruby" => "nil".to_string(),
494 "go" => "nil".to_string(),
495 "java" => "null".to_string(),
496 "csharp" => "null".to_string(),
497 "php" => "null".to_string(),
498 "r" => "NULL".to_string(),
499 "rust" => "Default::default()".to_string(),
500 _ => "null".to_string(),
501 },
502 }
503}
504
505trait TypeRefExt {
507 fn type_name(&self) -> String;
508}
509
510impl TypeRefExt for TypeRef {
511 fn type_name(&self) -> String {
512 match self {
513 TypeRef::Named(n) => n.clone(),
514 TypeRef::Primitive(p) => format!("{:?}", p),
515 TypeRef::String | TypeRef::Char => "String".to_string(),
516 TypeRef::Bytes => "Bytes".to_string(),
517 TypeRef::Optional(inner) => format!("Option<{}>", inner.type_name()),
518 TypeRef::Vec(inner) => format!("Vec<{}>", inner.type_name()),
519 TypeRef::Map(k, v) => format!("Map<{}, {}>", k.type_name(), v.type_name()),
520 TypeRef::Path => "Path".to_string(),
521 TypeRef::Unit => "()".to_string(),
522 TypeRef::Json => "Json".to_string(),
523 TypeRef::Duration => "Duration".to_string(),
524 }
525 }
526}
527
528const MAGNUS_MAX_ARITY: usize = 15;
530
531pub fn gen_magnus_kwargs_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
537 if typ.fields.len() > MAGNUS_MAX_ARITY {
538 gen_magnus_hash_constructor(typ, type_mapper)
539 } else {
540 gen_magnus_positional_constructor(typ, type_mapper)
541 }
542}
543
544fn as_type_path_prefix(type_str: &str) -> String {
551 if type_str.contains('<') {
552 format!("<{type_str}>")
553 } else {
554 type_str.to_string()
555 }
556}
557
558fn gen_magnus_hash_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
561 use std::fmt::Write;
562 let mut out = String::with_capacity(1024);
563
564 writeln!(out, "fn new(kwargs: magnus::RHash) -> Result<Self, magnus::Error> {{").ok();
565 writeln!(out, " let ruby = unsafe {{ magnus::Ruby::get_unchecked() }};").ok();
566 writeln!(out, " Ok(Self {{").ok();
567
568 for field in &typ.fields {
569 let is_optional = field_is_optional_in_rust(field);
570 let inner_type = type_mapper(&field.ty);
572 let type_prefix = as_type_path_prefix(&inner_type);
573 if is_optional {
574 writeln!(
576 out,
577 " {name}: kwargs.get(ruby.to_symbol(\"{name}\")).and_then(|v| {type_prefix}::try_convert(v).ok()),",
578 name = field.name,
579 type_prefix = type_prefix,
580 ).ok();
581 } else if use_unwrap_or_default(field) {
582 writeln!(
583 out,
584 " {name}: kwargs.get(ruby.to_symbol(\"{name}\")).and_then(|v| {type_prefix}::try_convert(v).ok()).unwrap_or_default(),",
585 name = field.name,
586 type_prefix = type_prefix,
587 ).ok();
588 } else {
589 let default_str = default_value_for_field(field, "rust");
590 writeln!(
591 out,
592 " {name}: kwargs.get(ruby.to_symbol(\"{name}\")).and_then(|v| {type_prefix}::try_convert(v).ok()).unwrap_or({default}),",
593 name = field.name,
594 type_prefix = type_prefix,
595 default = default_str,
596 ).ok();
597 }
598 }
599
600 writeln!(out, " }})").ok();
601 writeln!(out, "}}").ok();
602
603 out
604}
605
606fn field_is_optional_in_rust(field: &FieldDef) -> bool {
611 field.optional || matches!(&field.ty, TypeRef::Optional(_))
612}
613
614fn gen_magnus_positional_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
617 use std::fmt::Write;
618 let mut out = String::with_capacity(512);
619
620 writeln!(out, "fn new(").ok();
621
622 for (i, field) in typ.fields.iter().enumerate() {
626 let comma = if i < typ.fields.len() - 1 { "," } else { "" };
627 let is_optional = field_is_optional_in_rust(field);
628 if is_optional {
629 let inner_type = type_mapper(&field.ty);
633 writeln!(out, " {}: Option<{}>{}", field.name, inner_type, comma).ok();
634 } else {
635 let field_type = type_mapper(&field.ty);
636 writeln!(out, " {}: Option<{}>{}", field.name, field_type, comma).ok();
637 }
638 }
639
640 writeln!(out, ") -> Self {{").ok();
641 writeln!(out, " Self {{").ok();
642
643 for field in &typ.fields {
644 let is_optional = field_is_optional_in_rust(field);
645 if is_optional {
646 writeln!(out, " {},", field.name).ok();
648 } else if use_unwrap_or_default(field) {
649 writeln!(out, " {}: {}.unwrap_or_default(),", field.name, field.name).ok();
650 } else {
651 let default_str = default_value_for_field(field, "rust");
652 writeln!(
653 out,
654 " {}: {}.unwrap_or({}),",
655 field.name, field.name, default_str
656 )
657 .ok();
658 }
659 }
660
661 writeln!(out, " }}").ok();
662 writeln!(out, "}}").ok();
663
664 out
665}
666
667pub fn gen_php_kwargs_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
671 use std::fmt::Write;
672 let mut out = String::with_capacity(512);
673
674 writeln!(out, "pub fn __construct(").ok();
675
676 for (i, field) in typ.fields.iter().enumerate() {
678 let mapped = type_mapper(&field.ty);
679 let comma = if i < typ.fields.len() - 1 { "," } else { "" };
680 writeln!(out, " {}: Option<{}>{}", field.name, mapped, comma).ok();
681 }
682
683 writeln!(out, ") -> Self {{").ok();
684 writeln!(out, " Self {{").ok();
685
686 for field in &typ.fields {
687 let is_optional_field = field.optional || matches!(&field.ty, TypeRef::Optional(_));
688 if is_optional_field {
689 writeln!(out, " {},", field.name).ok();
691 } else if use_unwrap_or_default(field) {
692 writeln!(out, " {}: {}.unwrap_or_default(),", field.name, field.name).ok();
694 } else {
695 let default_str = default_value_for_field(field, "rust");
697 writeln!(
698 out,
699 " {}: {}.unwrap_or({}),",
700 field.name, field.name, default_str
701 )
702 .ok();
703 }
704 }
705
706 writeln!(out, " }}").ok();
707 writeln!(out, "}}").ok();
708
709 out
710}
711
712pub fn gen_rustler_kwargs_constructor(typ: &TypeDef, _type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
715 use std::fmt::Write;
716 let mut out = String::with_capacity(512);
717
718 writeln!(
721 out,
722 "pub fn new(opts: std::collections::HashMap<String, rustler::Term>) -> Self {{"
723 )
724 .ok();
725 writeln!(out, " Self {{").ok();
726
727 for field in &typ.fields {
731 if field.optional {
732 writeln!(
734 out,
735 " {}: opts.get(\"{}\").and_then(|t| t.decode().ok()),",
736 field.name, field.name
737 )
738 .ok();
739 } else if use_unwrap_or_default(field) {
740 writeln!(
741 out,
742 " {}: opts.get(\"{}\").and_then(|t| t.decode().ok()).unwrap_or_default(),",
743 field.name, field.name
744 )
745 .ok();
746 } else {
747 let default_str = default_value_for_field(field, "rust");
748 writeln!(
749 out,
750 " {}: opts.get(\"{}\").and_then(|t| t.decode().ok()).unwrap_or({}),",
751 field.name, field.name, default_str
752 )
753 .ok();
754 }
755 }
756
757 writeln!(out, " }}").ok();
758 writeln!(out, "}}").ok();
759
760 out
761}
762
763pub fn gen_extendr_kwargs_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
766 use std::fmt::Write;
767 let mut out = String::with_capacity(512);
768
769 writeln!(out, "#[extendr]").ok();
770 writeln!(out, "pub fn new_{}(", typ.name.to_lowercase()).ok();
771
772 for (i, field) in typ.fields.iter().enumerate() {
774 let field_type = type_mapper(&field.ty);
775 let default_str = default_value_for_field(field, "r");
776 let comma = if i < typ.fields.len() - 1 { "," } else { "" };
777 writeln!(out, " {}: {} = {}{}", field.name, field_type, default_str, comma).ok();
778 }
779
780 writeln!(out, ") -> {} {{", typ.name).ok();
781 writeln!(out, " {} {{", typ.name).ok();
782
783 for field in &typ.fields {
785 writeln!(out, " {},", field.name).ok();
786 }
787
788 writeln!(out, " }}").ok();
789 writeln!(out, "}}").ok();
790
791 out
792}
793
794#[cfg(test)]
795mod tests {
796 use super::*;
797 use alef_core::ir::{CoreWrapper, FieldDef, PrimitiveType, TypeRef};
798
799 fn make_test_type() -> TypeDef {
800 TypeDef {
801 name: "Config".to_string(),
802 rust_path: "my_crate::Config".to_string(),
803 fields: vec![
804 FieldDef {
805 name: "timeout".to_string(),
806 ty: TypeRef::Primitive(PrimitiveType::U64),
807 optional: false,
808 default: Some("30".to_string()),
809 doc: "Timeout in seconds".to_string(),
810 sanitized: false,
811 is_boxed: false,
812 type_rust_path: None,
813 cfg: None,
814 typed_default: Some(DefaultValue::IntLiteral(30)),
815 core_wrapper: CoreWrapper::None,
816 vec_inner_core_wrapper: CoreWrapper::None,
817 newtype_wrapper: None,
818 },
819 FieldDef {
820 name: "enabled".to_string(),
821 ty: TypeRef::Primitive(PrimitiveType::Bool),
822 optional: false,
823 default: None,
824 doc: "Enable feature".to_string(),
825 sanitized: false,
826 is_boxed: false,
827 type_rust_path: None,
828 cfg: None,
829 typed_default: Some(DefaultValue::BoolLiteral(true)),
830 core_wrapper: CoreWrapper::None,
831 vec_inner_core_wrapper: CoreWrapper::None,
832 newtype_wrapper: None,
833 },
834 FieldDef {
835 name: "name".to_string(),
836 ty: TypeRef::String,
837 optional: false,
838 default: None,
839 doc: "Config name".to_string(),
840 sanitized: false,
841 is_boxed: false,
842 type_rust_path: None,
843 cfg: None,
844 typed_default: Some(DefaultValue::StringLiteral("default".to_string())),
845 core_wrapper: CoreWrapper::None,
846 vec_inner_core_wrapper: CoreWrapper::None,
847 newtype_wrapper: None,
848 },
849 ],
850 methods: vec![],
851 is_opaque: false,
852 is_clone: true,
853 doc: "Configuration type".to_string(),
854 cfg: None,
855 is_trait: false,
856 has_default: true,
857 has_stripped_cfg_fields: false,
858 is_return_type: false,
859 serde_rename_all: None,
860 }
861 }
862
863 #[test]
864 fn test_default_value_bool_true_python() {
865 let field = FieldDef {
866 name: "enabled".to_string(),
867 ty: TypeRef::Primitive(PrimitiveType::Bool),
868 optional: false,
869 default: None,
870 doc: String::new(),
871 sanitized: false,
872 is_boxed: false,
873 type_rust_path: None,
874 cfg: None,
875 typed_default: Some(DefaultValue::BoolLiteral(true)),
876 core_wrapper: CoreWrapper::None,
877 vec_inner_core_wrapper: CoreWrapper::None,
878 newtype_wrapper: None,
879 };
880 assert_eq!(default_value_for_field(&field, "python"), "True");
881 }
882
883 #[test]
884 fn test_default_value_bool_false_go() {
885 let field = FieldDef {
886 name: "enabled".to_string(),
887 ty: TypeRef::Primitive(PrimitiveType::Bool),
888 optional: false,
889 default: None,
890 doc: String::new(),
891 sanitized: false,
892 is_boxed: false,
893 type_rust_path: None,
894 cfg: None,
895 typed_default: Some(DefaultValue::BoolLiteral(false)),
896 core_wrapper: CoreWrapper::None,
897 vec_inner_core_wrapper: CoreWrapper::None,
898 newtype_wrapper: None,
899 };
900 assert_eq!(default_value_for_field(&field, "go"), "false");
901 }
902
903 #[test]
904 fn test_default_value_string_literal() {
905 let field = FieldDef {
906 name: "name".to_string(),
907 ty: TypeRef::String,
908 optional: false,
909 default: None,
910 doc: String::new(),
911 sanitized: false,
912 is_boxed: false,
913 type_rust_path: None,
914 cfg: None,
915 typed_default: Some(DefaultValue::StringLiteral("hello".to_string())),
916 core_wrapper: CoreWrapper::None,
917 vec_inner_core_wrapper: CoreWrapper::None,
918 newtype_wrapper: None,
919 };
920 assert_eq!(default_value_for_field(&field, "python"), "\"hello\"");
921 assert_eq!(default_value_for_field(&field, "java"), "\"hello\"");
922 }
923
924 #[test]
925 fn test_default_value_int_literal() {
926 let field = FieldDef {
927 name: "timeout".to_string(),
928 ty: TypeRef::Primitive(PrimitiveType::U64),
929 optional: false,
930 default: None,
931 doc: String::new(),
932 sanitized: false,
933 is_boxed: false,
934 type_rust_path: None,
935 cfg: None,
936 typed_default: Some(DefaultValue::IntLiteral(42)),
937 core_wrapper: CoreWrapper::None,
938 vec_inner_core_wrapper: CoreWrapper::None,
939 newtype_wrapper: None,
940 };
941 let result = default_value_for_field(&field, "python");
942 assert_eq!(result, "42");
943 }
944
945 #[test]
946 fn test_default_value_none() {
947 let field = FieldDef {
948 name: "maybe".to_string(),
949 ty: TypeRef::Optional(Box::new(TypeRef::String)),
950 optional: true,
951 default: None,
952 doc: String::new(),
953 sanitized: false,
954 is_boxed: false,
955 type_rust_path: None,
956 cfg: None,
957 typed_default: Some(DefaultValue::None),
958 core_wrapper: CoreWrapper::None,
959 vec_inner_core_wrapper: CoreWrapper::None,
960 newtype_wrapper: None,
961 };
962 assert_eq!(default_value_for_field(&field, "python"), "None");
963 assert_eq!(default_value_for_field(&field, "go"), "nil");
964 assert_eq!(default_value_for_field(&field, "java"), "null");
965 assert_eq!(default_value_for_field(&field, "csharp"), "null");
966 }
967
968 #[test]
969 fn test_default_value_fallback_string() {
970 let field = FieldDef {
971 name: "name".to_string(),
972 ty: TypeRef::String,
973 optional: false,
974 default: Some("\"custom\"".to_string()),
975 doc: String::new(),
976 sanitized: false,
977 is_boxed: false,
978 type_rust_path: None,
979 cfg: None,
980 typed_default: None,
981 core_wrapper: CoreWrapper::None,
982 vec_inner_core_wrapper: CoreWrapper::None,
983 newtype_wrapper: None,
984 };
985 assert_eq!(default_value_for_field(&field, "python"), "\"custom\"");
986 }
987
988 #[test]
989 fn test_gen_pyo3_kwargs_constructor() {
990 let typ = make_test_type();
991 let output = gen_pyo3_kwargs_constructor(&typ, &|tr: &TypeRef| match tr {
992 TypeRef::Primitive(p) => format!("{:?}", p),
993 TypeRef::String | TypeRef::Char => "str".to_string(),
994 _ => "Any".to_string(),
995 });
996
997 assert!(output.contains("#[new]"));
998 assert!(output.contains("#[pyo3(signature = ("));
999 assert!(output.contains("timeout=30"));
1000 assert!(output.contains("enabled=True"));
1001 assert!(output.contains("name=\"default\""));
1002 assert!(output.contains("fn new("));
1003 }
1004
1005 #[test]
1006 fn test_gen_napi_defaults_constructor() {
1007 let typ = make_test_type();
1008 let output = gen_napi_defaults_constructor(&typ, &|tr: &TypeRef| match tr {
1009 TypeRef::Primitive(p) => format!("{:?}", p),
1010 TypeRef::String | TypeRef::Char => "String".to_string(),
1011 _ => "Value".to_string(),
1012 });
1013
1014 assert!(output.contains("pub fn new(mut env: napi::Env, obj: napi::Object)"));
1015 assert!(output.contains("timeout"));
1016 assert!(output.contains("enabled"));
1017 assert!(output.contains("name"));
1018 }
1019
1020 #[test]
1021 fn test_gen_go_functional_options() {
1022 let typ = make_test_type();
1023 let output = gen_go_functional_options(&typ, &|tr: &TypeRef| match tr {
1024 TypeRef::Primitive(p) => match p {
1025 PrimitiveType::U64 => "uint64".to_string(),
1026 PrimitiveType::Bool => "bool".to_string(),
1027 _ => "interface{}".to_string(),
1028 },
1029 TypeRef::String | TypeRef::Char => "string".to_string(),
1030 _ => "interface{}".to_string(),
1031 });
1032
1033 assert!(output.contains("type Config struct {"));
1034 assert!(output.contains("type ConfigOption func(*Config)"));
1035 assert!(output.contains("func WithConfigTimeout(val uint64) ConfigOption"));
1036 assert!(output.contains("func WithConfigEnabled(val bool) ConfigOption"));
1037 assert!(output.contains("func WithConfigName(val string) ConfigOption"));
1038 assert!(output.contains("func NewConfig(opts ...ConfigOption) *Config"));
1039 }
1040
1041 #[test]
1042 fn test_gen_java_builder() {
1043 let typ = make_test_type();
1044 let output = gen_java_builder(&typ, "dev.test", &|tr: &TypeRef| match tr {
1045 TypeRef::Primitive(p) => match p {
1046 PrimitiveType::U64 => "long".to_string(),
1047 PrimitiveType::Bool => "boolean".to_string(),
1048 _ => "int".to_string(),
1049 },
1050 TypeRef::String | TypeRef::Char => "String".to_string(),
1051 _ => "Object".to_string(),
1052 });
1053
1054 assert!(output.contains("package dev.test;"));
1055 assert!(output.contains("public class ConfigBuilder"));
1056 assert!(output.contains("withTimeout"));
1057 assert!(output.contains("withEnabled"));
1058 assert!(output.contains("withName"));
1059 assert!(output.contains("public Config build()"));
1060 }
1061
1062 #[test]
1063 fn test_gen_csharp_record() {
1064 let typ = make_test_type();
1065 let output = gen_csharp_record(&typ, "MyNamespace", &|tr: &TypeRef| match tr {
1066 TypeRef::Primitive(p) => match p {
1067 PrimitiveType::U64 => "ulong".to_string(),
1068 PrimitiveType::Bool => "bool".to_string(),
1069 _ => "int".to_string(),
1070 },
1071 TypeRef::String | TypeRef::Char => "string".to_string(),
1072 _ => "object".to_string(),
1073 });
1074
1075 assert!(output.contains("namespace MyNamespace;"));
1076 assert!(output.contains("public record Config"));
1077 assert!(output.contains("public ulong Timeout"));
1078 assert!(output.contains("public bool Enabled"));
1079 assert!(output.contains("public string Name"));
1080 assert!(output.contains("init;"));
1081 }
1082
1083 #[test]
1084 fn test_default_value_float_literal() {
1085 let field = FieldDef {
1086 name: "ratio".to_string(),
1087 ty: TypeRef::Primitive(PrimitiveType::F64),
1088 optional: false,
1089 default: None,
1090 doc: String::new(),
1091 sanitized: false,
1092 is_boxed: false,
1093 type_rust_path: None,
1094 cfg: None,
1095 typed_default: Some(DefaultValue::FloatLiteral(1.5)),
1096 core_wrapper: CoreWrapper::None,
1097 vec_inner_core_wrapper: CoreWrapper::None,
1098 newtype_wrapper: None,
1099 };
1100 let result = default_value_for_field(&field, "python");
1101 assert!(result.contains("1.5"));
1102 }
1103
1104 #[test]
1105 fn test_default_value_no_typed_no_default() {
1106 let field = FieldDef {
1107 name: "count".to_string(),
1108 ty: TypeRef::Primitive(PrimitiveType::U32),
1109 optional: false,
1110 default: None,
1111 doc: String::new(),
1112 sanitized: false,
1113 is_boxed: false,
1114 type_rust_path: None,
1115 cfg: None,
1116 typed_default: None,
1117 core_wrapper: CoreWrapper::None,
1118 vec_inner_core_wrapper: CoreWrapper::None,
1119 newtype_wrapper: None,
1120 };
1121 assert_eq!(default_value_for_field(&field, "python"), "0");
1123 assert_eq!(default_value_for_field(&field, "go"), "0");
1124 }
1125}