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 has_serde: false,
861 }
862 }
863
864 #[test]
865 fn test_default_value_bool_true_python() {
866 let field = FieldDef {
867 name: "enabled".to_string(),
868 ty: TypeRef::Primitive(PrimitiveType::Bool),
869 optional: false,
870 default: None,
871 doc: String::new(),
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 assert_eq!(default_value_for_field(&field, "python"), "True");
882 }
883
884 #[test]
885 fn test_default_value_bool_false_go() {
886 let field = FieldDef {
887 name: "enabled".to_string(),
888 ty: TypeRef::Primitive(PrimitiveType::Bool),
889 optional: false,
890 default: None,
891 doc: String::new(),
892 sanitized: false,
893 is_boxed: false,
894 type_rust_path: None,
895 cfg: None,
896 typed_default: Some(DefaultValue::BoolLiteral(false)),
897 core_wrapper: CoreWrapper::None,
898 vec_inner_core_wrapper: CoreWrapper::None,
899 newtype_wrapper: None,
900 };
901 assert_eq!(default_value_for_field(&field, "go"), "false");
902 }
903
904 #[test]
905 fn test_default_value_string_literal() {
906 let field = FieldDef {
907 name: "name".to_string(),
908 ty: TypeRef::String,
909 optional: false,
910 default: None,
911 doc: String::new(),
912 sanitized: false,
913 is_boxed: false,
914 type_rust_path: None,
915 cfg: None,
916 typed_default: Some(DefaultValue::StringLiteral("hello".to_string())),
917 core_wrapper: CoreWrapper::None,
918 vec_inner_core_wrapper: CoreWrapper::None,
919 newtype_wrapper: None,
920 };
921 assert_eq!(default_value_for_field(&field, "python"), "\"hello\"");
922 assert_eq!(default_value_for_field(&field, "java"), "\"hello\"");
923 }
924
925 #[test]
926 fn test_default_value_int_literal() {
927 let field = FieldDef {
928 name: "timeout".to_string(),
929 ty: TypeRef::Primitive(PrimitiveType::U64),
930 optional: false,
931 default: None,
932 doc: String::new(),
933 sanitized: false,
934 is_boxed: false,
935 type_rust_path: None,
936 cfg: None,
937 typed_default: Some(DefaultValue::IntLiteral(42)),
938 core_wrapper: CoreWrapper::None,
939 vec_inner_core_wrapper: CoreWrapper::None,
940 newtype_wrapper: None,
941 };
942 let result = default_value_for_field(&field, "python");
943 assert_eq!(result, "42");
944 }
945
946 #[test]
947 fn test_default_value_none() {
948 let field = FieldDef {
949 name: "maybe".to_string(),
950 ty: TypeRef::Optional(Box::new(TypeRef::String)),
951 optional: true,
952 default: None,
953 doc: String::new(),
954 sanitized: false,
955 is_boxed: false,
956 type_rust_path: None,
957 cfg: None,
958 typed_default: Some(DefaultValue::None),
959 core_wrapper: CoreWrapper::None,
960 vec_inner_core_wrapper: CoreWrapper::None,
961 newtype_wrapper: None,
962 };
963 assert_eq!(default_value_for_field(&field, "python"), "None");
964 assert_eq!(default_value_for_field(&field, "go"), "nil");
965 assert_eq!(default_value_for_field(&field, "java"), "null");
966 assert_eq!(default_value_for_field(&field, "csharp"), "null");
967 }
968
969 #[test]
970 fn test_default_value_fallback_string() {
971 let field = FieldDef {
972 name: "name".to_string(),
973 ty: TypeRef::String,
974 optional: false,
975 default: Some("\"custom\"".to_string()),
976 doc: String::new(),
977 sanitized: false,
978 is_boxed: false,
979 type_rust_path: None,
980 cfg: None,
981 typed_default: None,
982 core_wrapper: CoreWrapper::None,
983 vec_inner_core_wrapper: CoreWrapper::None,
984 newtype_wrapper: None,
985 };
986 assert_eq!(default_value_for_field(&field, "python"), "\"custom\"");
987 }
988
989 #[test]
990 fn test_gen_pyo3_kwargs_constructor() {
991 let typ = make_test_type();
992 let output = gen_pyo3_kwargs_constructor(&typ, &|tr: &TypeRef| match tr {
993 TypeRef::Primitive(p) => format!("{:?}", p),
994 TypeRef::String | TypeRef::Char => "str".to_string(),
995 _ => "Any".to_string(),
996 });
997
998 assert!(output.contains("#[new]"));
999 assert!(output.contains("#[pyo3(signature = ("));
1000 assert!(output.contains("timeout=30"));
1001 assert!(output.contains("enabled=True"));
1002 assert!(output.contains("name=\"default\""));
1003 assert!(output.contains("fn new("));
1004 }
1005
1006 #[test]
1007 fn test_gen_napi_defaults_constructor() {
1008 let typ = make_test_type();
1009 let output = gen_napi_defaults_constructor(&typ, &|tr: &TypeRef| match tr {
1010 TypeRef::Primitive(p) => format!("{:?}", p),
1011 TypeRef::String | TypeRef::Char => "String".to_string(),
1012 _ => "Value".to_string(),
1013 });
1014
1015 assert!(output.contains("pub fn new(mut env: napi::Env, obj: napi::Object)"));
1016 assert!(output.contains("timeout"));
1017 assert!(output.contains("enabled"));
1018 assert!(output.contains("name"));
1019 }
1020
1021 #[test]
1022 fn test_gen_go_functional_options() {
1023 let typ = make_test_type();
1024 let output = gen_go_functional_options(&typ, &|tr: &TypeRef| match tr {
1025 TypeRef::Primitive(p) => match p {
1026 PrimitiveType::U64 => "uint64".to_string(),
1027 PrimitiveType::Bool => "bool".to_string(),
1028 _ => "interface{}".to_string(),
1029 },
1030 TypeRef::String | TypeRef::Char => "string".to_string(),
1031 _ => "interface{}".to_string(),
1032 });
1033
1034 assert!(output.contains("type Config struct {"));
1035 assert!(output.contains("type ConfigOption func(*Config)"));
1036 assert!(output.contains("func WithConfigTimeout(val uint64) ConfigOption"));
1037 assert!(output.contains("func WithConfigEnabled(val bool) ConfigOption"));
1038 assert!(output.contains("func WithConfigName(val string) ConfigOption"));
1039 assert!(output.contains("func NewConfig(opts ...ConfigOption) *Config"));
1040 }
1041
1042 #[test]
1043 fn test_gen_java_builder() {
1044 let typ = make_test_type();
1045 let output = gen_java_builder(&typ, "dev.test", &|tr: &TypeRef| match tr {
1046 TypeRef::Primitive(p) => match p {
1047 PrimitiveType::U64 => "long".to_string(),
1048 PrimitiveType::Bool => "boolean".to_string(),
1049 _ => "int".to_string(),
1050 },
1051 TypeRef::String | TypeRef::Char => "String".to_string(),
1052 _ => "Object".to_string(),
1053 });
1054
1055 assert!(output.contains("package dev.test;"));
1056 assert!(output.contains("public class ConfigBuilder"));
1057 assert!(output.contains("withTimeout"));
1058 assert!(output.contains("withEnabled"));
1059 assert!(output.contains("withName"));
1060 assert!(output.contains("public Config build()"));
1061 }
1062
1063 #[test]
1064 fn test_gen_csharp_record() {
1065 let typ = make_test_type();
1066 let output = gen_csharp_record(&typ, "MyNamespace", &|tr: &TypeRef| match tr {
1067 TypeRef::Primitive(p) => match p {
1068 PrimitiveType::U64 => "ulong".to_string(),
1069 PrimitiveType::Bool => "bool".to_string(),
1070 _ => "int".to_string(),
1071 },
1072 TypeRef::String | TypeRef::Char => "string".to_string(),
1073 _ => "object".to_string(),
1074 });
1075
1076 assert!(output.contains("namespace MyNamespace;"));
1077 assert!(output.contains("public record Config"));
1078 assert!(output.contains("public ulong Timeout"));
1079 assert!(output.contains("public bool Enabled"));
1080 assert!(output.contains("public string Name"));
1081 assert!(output.contains("init;"));
1082 }
1083
1084 #[test]
1085 fn test_default_value_float_literal() {
1086 let field = FieldDef {
1087 name: "ratio".to_string(),
1088 ty: TypeRef::Primitive(PrimitiveType::F64),
1089 optional: false,
1090 default: None,
1091 doc: String::new(),
1092 sanitized: false,
1093 is_boxed: false,
1094 type_rust_path: None,
1095 cfg: None,
1096 typed_default: Some(DefaultValue::FloatLiteral(1.5)),
1097 core_wrapper: CoreWrapper::None,
1098 vec_inner_core_wrapper: CoreWrapper::None,
1099 newtype_wrapper: None,
1100 };
1101 let result = default_value_for_field(&field, "python");
1102 assert!(result.contains("1.5"));
1103 }
1104
1105 #[test]
1106 fn test_default_value_no_typed_no_default() {
1107 let field = FieldDef {
1108 name: "count".to_string(),
1109 ty: TypeRef::Primitive(PrimitiveType::U32),
1110 optional: false,
1111 default: None,
1112 doc: String::new(),
1113 sanitized: false,
1114 is_boxed: false,
1115 type_rust_path: None,
1116 cfg: None,
1117 typed_default: None,
1118 core_wrapper: CoreWrapper::None,
1119 vec_inner_core_wrapper: CoreWrapper::None,
1120 newtype_wrapper: None,
1121 };
1122 assert_eq!(default_value_for_field(&field, "python"), "0");
1124 assert_eq!(default_value_for_field(&field, "go"), "0");
1125 }
1126}