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() && !matches!(&field.ty, TypeRef::Named(_))
23}
24
25pub fn gen_pyo3_kwargs_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
28 let mut lines = Vec::new();
29 lines.push("#[new]".to_string());
30
31 let mut sig_parts = Vec::new();
33 for field in &typ.fields {
34 let default_str = default_value_for_field(field, "python");
35 sig_parts.push(format!("{}={}", field.name, default_str));
36 }
37 let signature = format!("#[pyo3(signature = ({}))]", sig_parts.join(", "));
38 lines.push(signature);
39
40 lines.push("fn new(".to_string());
42 for (i, field) in typ.fields.iter().enumerate() {
43 let type_str = type_mapper(&field.ty);
44 let comma = if i < typ.fields.len() - 1 { "," } else { "" };
45 lines.push(format!(" {}: {}{}", field.name, type_str, comma));
46 }
47 lines.push(") -> Self {".to_string());
48
49 lines.push(" Self {".to_string());
51 for field in &typ.fields {
52 lines.push(format!(" {},", field.name));
53 }
54 lines.push(" }".to_string());
55 lines.push("}".to_string());
56
57 lines.join("\n")
58}
59
60pub fn gen_napi_defaults_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
62 let mut lines = Vec::new();
63 lines.push("pub fn new(mut env: napi::Env, obj: napi::Object) -> napi::Result<Self> {".to_string());
64
65 for field in &typ.fields {
67 let type_str = type_mapper(&field.ty);
68 let default_str = default_value_for_field(field, "rust");
69 lines.push(format!(
70 " let {}: {} = obj.get(\"{}\").unwrap_or({})?;",
71 field.name, type_str, field.name, default_str
72 ));
73 }
74
75 lines.push(" Ok(Self {".to_string());
76 for field in &typ.fields {
77 lines.push(format!(" {},", field.name));
78 }
79 lines.push(" })".to_string());
80 lines.push("}".to_string());
81
82 lines.join("\n")
83}
84
85pub fn gen_go_functional_options(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
88 let mut lines = Vec::new();
89
90 lines.push(format!("// {} is a configuration type.", typ.name));
92 lines.push(format!("type {} struct {{", typ.name));
93 for field in &typ.fields {
94 if is_tuple_field(field) {
95 continue;
96 }
97 let go_type = type_mapper(&field.ty);
98 lines.push(format!(" {} {}", field.name.to_pascal_case(), go_type));
99 }
100 lines.push("}".to_string());
101 lines.push("".to_string());
102
103 lines.push(format!(
105 "// {}Option is a functional option for {}.",
106 typ.name, typ.name
107 ));
108 lines.push(format!("type {}Option func(*{})", typ.name, typ.name));
109 lines.push("".to_string());
110
111 for field in &typ.fields {
113 if is_tuple_field(field) {
114 continue;
115 }
116 let option_name = format!("With{}{}", typ.name, field.name.to_pascal_case());
117 let go_type = type_mapper(&field.ty);
118 lines.push(format!("// {} sets the {}.", option_name, field.name));
119 lines.push(format!("func {}(val {}) {}Option {{", option_name, go_type, typ.name));
120 lines.push(format!(" return func(c *{}) {{", typ.name));
121 lines.push(format!(" c.{} = val", field.name.to_pascal_case()));
122 lines.push(" }".to_string());
123 lines.push("}".to_string());
124 lines.push("".to_string());
125 }
126
127 lines.push(format!(
129 "// New{} creates a new {} with default values and applies options.",
130 typ.name, typ.name
131 ));
132 lines.push(format!(
133 "func New{}(opts ...{}Option) *{} {{",
134 typ.name, typ.name, typ.name
135 ));
136 lines.push(format!(" c := &{} {{", typ.name));
137 for field in &typ.fields {
138 if is_tuple_field(field) {
139 continue;
140 }
141 let default_str = default_value_for_field(field, "go");
142 lines.push(format!(" {}: {},", field.name.to_pascal_case(), default_str));
143 }
144 lines.push(" }".to_string());
145 lines.push(" for _, opt := range opts {".to_string());
146 lines.push(" opt(c)".to_string());
147 lines.push(" }".to_string());
148 lines.push(" return c".to_string());
149 lines.push("}".to_string());
150
151 lines.join("\n")
152}
153
154pub fn gen_java_builder(typ: &TypeDef, package: &str, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
157 let mut lines = Vec::new();
158
159 lines.push(format!(
160 "// DO NOT EDIT - auto-generated by alef\npackage {};\n",
161 package
162 ));
163 lines.push("/// Builder for creating instances of {} with sensible defaults".to_string());
164 lines.push(format!("public class {}Builder {{", typ.name));
165
166 for field in &typ.fields {
168 let java_type = type_mapper(&field.ty);
169 lines.push(format!(" private {} {};", java_type, field.name.to_lowercase()));
170 }
171 lines.push("".to_string());
172
173 lines.push(format!(" public {}Builder() {{", typ.name));
175 for field in &typ.fields {
176 let default_str = default_value_for_field(field, "java");
177 lines.push(format!(" this.{} = {};", field.name.to_lowercase(), default_str));
178 }
179 lines.push(" }".to_string());
180 lines.push("".to_string());
181
182 for field in &typ.fields {
184 let java_type = type_mapper(&field.ty);
185 let method_name = format!("with{}", field.name.to_pascal_case());
186 lines.push(format!(
187 " public {}Builder {}({} value) {{",
188 typ.name, method_name, java_type
189 ));
190 lines.push(format!(" this.{} = value;", field.name.to_lowercase()));
191 lines.push(" return this;".to_string());
192 lines.push(" }".to_string());
193 lines.push("".to_string());
194 }
195
196 lines.push(format!(" public {} build() {{", typ.name));
198 lines.push(format!(" return new {}(", typ.name));
199 for (i, field) in typ.fields.iter().enumerate() {
200 let comma = if i < typ.fields.len() - 1 { "," } else { "" };
201 lines.push(format!(" this.{}{}", field.name.to_lowercase(), comma));
202 }
203 lines.push(" );".to_string());
204 lines.push(" }".to_string());
205 lines.push("}".to_string());
206
207 lines.join("\n")
208}
209
210pub fn gen_csharp_record(typ: &TypeDef, namespace: &str, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
212 let mut lines = Vec::new();
213
214 lines.push("// This file is auto-generated by alef. DO NOT EDIT.".to_string());
215 lines.push("using System;".to_string());
216 lines.push("".to_string());
217 lines.push(format!("namespace {};\n", namespace));
218
219 lines.push(format!("/// Configuration record: {}", typ.name));
220 lines.push(format!("public record {} {{", typ.name));
221
222 for field in &typ.fields {
223 if field.name.starts_with('_') && field.name[1..].chars().all(|c| c.is_ascii_digit())
225 || field.name.chars().next().is_none_or(|c| c.is_ascii_digit())
226 {
227 continue;
228 }
229
230 let cs_type = type_mapper(&field.ty);
231 let default_str = default_value_for_field(field, "csharp");
232 lines.push(format!(
233 " public {} {} {{ get; init; }} = {};",
234 cs_type,
235 field.name.to_pascal_case(),
236 default_str
237 ));
238 }
239
240 lines.push("}".to_string());
241
242 lines.join("\n")
243}
244
245pub fn default_value_for_field(field: &FieldDef, language: &str) -> String {
248 if let Some(typed_default) = &field.typed_default {
250 return match typed_default {
251 DefaultValue::BoolLiteral(b) => match language {
252 "python" => {
253 if *b {
254 "True".to_string()
255 } else {
256 "False".to_string()
257 }
258 }
259 "ruby" => {
260 if *b {
261 "true".to_string()
262 } else {
263 "false".to_string()
264 }
265 }
266 "go" => {
267 if *b {
268 "true".to_string()
269 } else {
270 "false".to_string()
271 }
272 }
273 "java" => {
274 if *b {
275 "true".to_string()
276 } else {
277 "false".to_string()
278 }
279 }
280 "csharp" => {
281 if *b {
282 "true".to_string()
283 } else {
284 "false".to_string()
285 }
286 }
287 "php" => {
288 if *b {
289 "true".to_string()
290 } else {
291 "false".to_string()
292 }
293 }
294 "r" => {
295 if *b {
296 "TRUE".to_string()
297 } else {
298 "FALSE".to_string()
299 }
300 }
301 "rust" => {
302 if *b {
303 "true".to_string()
304 } else {
305 "false".to_string()
306 }
307 }
308 _ => {
309 if *b {
310 "true".to_string()
311 } else {
312 "false".to_string()
313 }
314 }
315 },
316 DefaultValue::StringLiteral(s) => match language {
317 "rust" => format!("\"{}\".to_string()", s.replace('"', "\\\"")),
318 _ => format!("\"{}\"", s.replace('"', "\\\"")),
319 },
320 DefaultValue::IntLiteral(n) => n.to_string(),
321 DefaultValue::FloatLiteral(f) => {
322 let s = f.to_string();
323 if !s.contains('.') { format!("{}.0", s) } else { s }
324 }
325 DefaultValue::EnumVariant(v) => match language {
326 "python" => format!("{}.{}", field.ty.type_name(), v.to_shouty_snake_case()),
327 "ruby" => format!("{}::{}", field.ty.type_name(), v.to_pascal_case()),
328 "go" => format!("{}{}", field.ty.type_name(), v.to_pascal_case()),
329 "java" => format!("{}.{}", field.ty.type_name(), v.to_shouty_snake_case()),
330 "csharp" => format!("{}.{}", field.ty.type_name(), v.to_pascal_case()),
331 "php" => format!("{}::{}", field.ty.type_name(), v.to_pascal_case()),
332 "r" => format!("{}${}", field.ty.type_name(), v.to_pascal_case()),
333 "rust" => format!("{}::{}", field.ty.type_name(), v.to_pascal_case()),
334 _ => v.clone(),
335 },
336 DefaultValue::Empty => {
337 match &field.ty {
339 TypeRef::Vec(_) => match language {
340 "python" | "ruby" | "csharp" => "[]".to_string(),
341 "go" => "nil".to_string(),
342 "java" => "List.of()".to_string(),
343 "php" => "[]".to_string(),
344 "r" => "c()".to_string(),
345 "rust" => "vec![]".to_string(),
346 _ => "null".to_string(),
347 },
348 TypeRef::Map(_, _) => match language {
349 "python" => "{}".to_string(),
350 "go" => "nil".to_string(),
351 "java" => "Map.of()".to_string(),
352 "rust" => "Default::default()".to_string(),
353 _ => "null".to_string(),
354 },
355 TypeRef::Primitive(p) => match p {
356 PrimitiveType::Bool => match language {
357 "python" => "False".to_string(),
358 "ruby" => "false".to_string(),
359 _ => "false".to_string(),
360 },
361 PrimitiveType::F32 | PrimitiveType::F64 => "0.0".to_string(),
362 _ => "0".to_string(),
363 },
364 TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json => match language {
365 "rust" => "String::new()".to_string(),
366 _ => "\"\"".to_string(),
367 },
368 TypeRef::Duration => "0".to_string(),
369 TypeRef::Bytes => match language {
370 "python" => "b\"\"".to_string(),
371 "go" => "[]byte{}".to_string(),
372 "rust" => "vec![]".to_string(),
373 _ => "\"\"".to_string(),
374 },
375 _ => match language {
376 "python" => "None".to_string(),
377 "ruby" => "nil".to_string(),
378 "go" => "nil".to_string(),
379 "rust" => "Default::default()".to_string(),
380 _ => "null".to_string(),
381 },
382 }
383 }
384 DefaultValue::None => match language {
385 "python" => "None".to_string(),
386 "ruby" => "nil".to_string(),
387 "go" => "nil".to_string(),
388 "java" => "null".to_string(),
389 "csharp" => "null".to_string(),
390 "php" => "null".to_string(),
391 "r" => "NULL".to_string(),
392 "rust" => "None".to_string(),
393 _ => "null".to_string(),
394 },
395 };
396 }
397
398 if let Some(default_str) = &field.default {
400 return default_str.clone();
401 }
402
403 match &field.ty {
405 TypeRef::Primitive(p) => match p {
406 alef_core::ir::PrimitiveType::Bool => match language {
407 "python" => "False".to_string(),
408 "ruby" => "false".to_string(),
409 "csharp" => "false".to_string(),
410 "java" => "false".to_string(),
411 "php" => "false".to_string(),
412 "r" => "FALSE".to_string(),
413 _ => "false".to_string(),
414 },
415 alef_core::ir::PrimitiveType::U8
416 | alef_core::ir::PrimitiveType::U16
417 | alef_core::ir::PrimitiveType::U32
418 | alef_core::ir::PrimitiveType::U64
419 | alef_core::ir::PrimitiveType::I8
420 | alef_core::ir::PrimitiveType::I16
421 | alef_core::ir::PrimitiveType::I32
422 | alef_core::ir::PrimitiveType::I64
423 | alef_core::ir::PrimitiveType::Usize
424 | alef_core::ir::PrimitiveType::Isize => "0".to_string(),
425 alef_core::ir::PrimitiveType::F32 | alef_core::ir::PrimitiveType::F64 => "0.0".to_string(),
426 },
427 TypeRef::String | TypeRef::Char => match language {
428 "python" => "\"\"".to_string(),
429 "ruby" => "\"\"".to_string(),
430 "go" => "\"\"".to_string(),
431 "java" => "\"\"".to_string(),
432 "csharp" => "\"\"".to_string(),
433 "php" => "\"\"".to_string(),
434 "r" => "\"\"".to_string(),
435 "rust" => "String::new()".to_string(),
436 _ => "\"\"".to_string(),
437 },
438 TypeRef::Bytes => match language {
439 "python" => "b\"\"".to_string(),
440 "ruby" => "\"\"".to_string(),
441 "go" => "[]byte{}".to_string(),
442 "java" => "new byte[]{}".to_string(),
443 "csharp" => "new byte[]{}".to_string(),
444 "php" => "\"\"".to_string(),
445 "r" => "raw()".to_string(),
446 "rust" => "vec![]".to_string(),
447 _ => "[]".to_string(),
448 },
449 TypeRef::Optional(_) => match language {
450 "python" => "None".to_string(),
451 "ruby" => "nil".to_string(),
452 "go" => "nil".to_string(),
453 "java" => "null".to_string(),
454 "csharp" => "null".to_string(),
455 "php" => "null".to_string(),
456 "r" => "NULL".to_string(),
457 "rust" => "None".to_string(),
458 _ => "null".to_string(),
459 },
460 TypeRef::Vec(_) => match language {
461 "python" => "[]".to_string(),
462 "ruby" => "[]".to_string(),
463 "go" => "[]interface{}{}".to_string(),
464 "java" => "new java.util.ArrayList<>()".to_string(),
465 "csharp" => "[]".to_string(),
466 "php" => "[]".to_string(),
467 "r" => "c()".to_string(),
468 "rust" => "vec![]".to_string(),
469 _ => "[]".to_string(),
470 },
471 TypeRef::Map(_, _) => match language {
472 "python" => "{}".to_string(),
473 "ruby" => "{}".to_string(),
474 "go" => "make(map[string]interface{})".to_string(),
475 "java" => "new java.util.HashMap<>()".to_string(),
476 "csharp" => "new Dictionary<string, object>()".to_string(),
477 "php" => "[]".to_string(),
478 "r" => "list()".to_string(),
479 "rust" => "std::collections::HashMap::new()".to_string(),
480 _ => "{}".to_string(),
481 },
482 TypeRef::Json => match language {
483 "python" => "{}".to_string(),
484 "ruby" => "{}".to_string(),
485 "go" => "make(map[string]interface{})".to_string(),
486 "java" => "new com.fasterxml.jackson.databind.JsonNode()".to_string(),
487 "csharp" => "JObject.Parse(\"{}\")".to_string(),
488 "php" => "[]".to_string(),
489 "r" => "list()".to_string(),
490 "rust" => "serde_json::json!({})".to_string(),
491 _ => "{}".to_string(),
492 },
493 TypeRef::Named(name) => match language {
494 "rust" => format!("{name}::default()"),
495 "python" => "None".to_string(),
496 "ruby" => "nil".to_string(),
497 "go" => "nil".to_string(),
498 "java" => "null".to_string(),
499 "csharp" => "null".to_string(),
500 "php" => "null".to_string(),
501 "r" => "NULL".to_string(),
502 _ => "null".to_string(),
503 },
504 _ => match language {
505 "python" => "None".to_string(),
506 "ruby" => "nil".to_string(),
507 "go" => "nil".to_string(),
508 "java" => "null".to_string(),
509 "csharp" => "null".to_string(),
510 "php" => "null".to_string(),
511 "r" => "NULL".to_string(),
512 "rust" => "Default::default()".to_string(),
513 _ => "null".to_string(),
514 },
515 }
516}
517
518trait TypeRefExt {
520 fn type_name(&self) -> String;
521}
522
523impl TypeRefExt for TypeRef {
524 fn type_name(&self) -> String {
525 match self {
526 TypeRef::Named(n) => n.clone(),
527 TypeRef::Primitive(p) => format!("{:?}", p),
528 TypeRef::String | TypeRef::Char => "String".to_string(),
529 TypeRef::Bytes => "Bytes".to_string(),
530 TypeRef::Optional(inner) => format!("Option<{}>", inner.type_name()),
531 TypeRef::Vec(inner) => format!("Vec<{}>", inner.type_name()),
532 TypeRef::Map(k, v) => format!("Map<{}, {}>", k.type_name(), v.type_name()),
533 TypeRef::Path => "Path".to_string(),
534 TypeRef::Unit => "()".to_string(),
535 TypeRef::Json => "Json".to_string(),
536 TypeRef::Duration => "Duration".to_string(),
537 }
538 }
539}
540
541const MAGNUS_MAX_ARITY: usize = 15;
543
544pub fn gen_magnus_kwargs_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
550 if typ.fields.len() > MAGNUS_MAX_ARITY {
551 gen_magnus_hash_constructor(typ, type_mapper)
552 } else {
553 gen_magnus_positional_constructor(typ, type_mapper)
554 }
555}
556
557fn as_type_path_prefix(type_str: &str) -> String {
564 if type_str.contains('<') {
565 format!("<{type_str}>")
566 } else {
567 type_str.to_string()
568 }
569}
570
571fn gen_magnus_hash_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
574 use std::fmt::Write;
575 let mut out = String::with_capacity(1024);
576
577 writeln!(out, "fn new(kwargs: magnus::RHash) -> Result<Self, magnus::Error> {{").ok();
578 writeln!(out, " let ruby = unsafe {{ magnus::Ruby::get_unchecked() }};").ok();
579 writeln!(out, " Ok(Self {{").ok();
580
581 for field in &typ.fields {
582 let is_optional = field_is_optional_in_rust(field);
583 let inner_type = type_mapper(&field.ty);
585 let type_prefix = as_type_path_prefix(&inner_type);
586 if is_optional {
587 writeln!(
589 out,
590 " {name}: kwargs.get(ruby.to_symbol(\"{name}\")).and_then(|v| {type_prefix}::try_convert(v).ok()),",
591 name = field.name,
592 type_prefix = type_prefix,
593 ).ok();
594 } else if use_unwrap_or_default(field) {
595 writeln!(
596 out,
597 " {name}: kwargs.get(ruby.to_symbol(\"{name}\")).and_then(|v| {type_prefix}::try_convert(v).ok()).unwrap_or_default(),",
598 name = field.name,
599 type_prefix = type_prefix,
600 ).ok();
601 } else {
602 let default_str = default_value_for_field(field, "rust");
603 writeln!(
604 out,
605 " {name}: kwargs.get(ruby.to_symbol(\"{name}\")).and_then(|v| {type_prefix}::try_convert(v).ok()).unwrap_or({default}),",
606 name = field.name,
607 type_prefix = type_prefix,
608 default = default_str,
609 ).ok();
610 }
611 }
612
613 writeln!(out, " }})").ok();
614 writeln!(out, "}}").ok();
615
616 out
617}
618
619fn field_is_optional_in_rust(field: &FieldDef) -> bool {
624 field.optional || matches!(&field.ty, TypeRef::Optional(_))
625}
626
627fn gen_magnus_positional_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
630 use std::fmt::Write;
631 let mut out = String::with_capacity(512);
632
633 writeln!(out, "fn new(").ok();
634
635 for (i, field) in typ.fields.iter().enumerate() {
639 let comma = if i < typ.fields.len() - 1 { "," } else { "" };
640 let is_optional = field_is_optional_in_rust(field);
641 if is_optional {
642 let inner_type = type_mapper(&field.ty);
646 writeln!(out, " {}: Option<{}>{}", field.name, inner_type, comma).ok();
647 } else {
648 let field_type = type_mapper(&field.ty);
649 writeln!(out, " {}: Option<{}>{}", field.name, field_type, comma).ok();
650 }
651 }
652
653 writeln!(out, ") -> Self {{").ok();
654 writeln!(out, " Self {{").ok();
655
656 for field in &typ.fields {
657 let is_optional = field_is_optional_in_rust(field);
658 if is_optional {
659 writeln!(out, " {},", field.name).ok();
661 } else if use_unwrap_or_default(field) {
662 writeln!(out, " {}: {}.unwrap_or_default(),", field.name, field.name).ok();
663 } else {
664 let default_str = default_value_for_field(field, "rust");
665 writeln!(
666 out,
667 " {}: {}.unwrap_or({}),",
668 field.name, field.name, default_str
669 )
670 .ok();
671 }
672 }
673
674 writeln!(out, " }}").ok();
675 writeln!(out, "}}").ok();
676
677 out
678}
679
680pub fn gen_php_kwargs_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
684 use std::fmt::Write;
685 let mut out = String::with_capacity(512);
686
687 writeln!(out, "pub fn __construct(").ok();
688
689 for (i, field) in typ.fields.iter().enumerate() {
691 let mapped = type_mapper(&field.ty);
692 let comma = if i < typ.fields.len() - 1 { "," } else { "" };
693 writeln!(out, " {}: Option<{}>{}", field.name, mapped, comma).ok();
694 }
695
696 writeln!(out, ") -> Self {{").ok();
697 writeln!(out, " Self {{").ok();
698
699 for field in &typ.fields {
700 let is_optional_field = field.optional || matches!(&field.ty, TypeRef::Optional(_));
701 if is_optional_field {
702 writeln!(out, " {},", field.name).ok();
704 } else if use_unwrap_or_default(field) {
705 writeln!(out, " {}: {}.unwrap_or_default(),", field.name, field.name).ok();
707 } else {
708 let default_str = default_value_for_field(field, "rust");
710 writeln!(
711 out,
712 " {}: {}.unwrap_or({}),",
713 field.name, field.name, default_str
714 )
715 .ok();
716 }
717 }
718
719 writeln!(out, " }}").ok();
720 writeln!(out, "}}").ok();
721
722 out
723}
724
725pub fn gen_rustler_kwargs_constructor(typ: &TypeDef, _type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
728 use std::fmt::Write;
729 let mut out = String::with_capacity(512);
730
731 writeln!(
734 out,
735 "pub fn new(opts: std::collections::HashMap<String, rustler::Term>) -> Self {{"
736 )
737 .ok();
738 writeln!(out, " Self {{").ok();
739
740 for field in &typ.fields {
744 if field.optional {
745 writeln!(
747 out,
748 " {}: opts.get(\"{}\").and_then(|t| t.decode().ok()),",
749 field.name, field.name
750 )
751 .ok();
752 } else if use_unwrap_or_default(field) {
753 writeln!(
754 out,
755 " {}: opts.get(\"{}\").and_then(|t| t.decode().ok()).unwrap_or_default(),",
756 field.name, field.name
757 )
758 .ok();
759 } else {
760 let default_str = default_value_for_field(field, "rust");
761 writeln!(
762 out,
763 " {}: opts.get(\"{}\").and_then(|t| t.decode().ok()).unwrap_or({}),",
764 field.name, field.name, default_str
765 )
766 .ok();
767 }
768 }
769
770 writeln!(out, " }}").ok();
771 writeln!(out, "}}").ok();
772
773 out
774}
775
776pub fn gen_extendr_kwargs_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
779 use std::fmt::Write;
780 let mut out = String::with_capacity(512);
781
782 writeln!(out, "#[extendr]").ok();
783 writeln!(out, "pub fn new_{}(", typ.name.to_lowercase()).ok();
784
785 for (i, field) in typ.fields.iter().enumerate() {
787 let field_type = type_mapper(&field.ty);
788 let default_str = default_value_for_field(field, "r");
789 let comma = if i < typ.fields.len() - 1 { "," } else { "" };
790 writeln!(out, " {}: {} = {}{}", field.name, field_type, default_str, comma).ok();
791 }
792
793 writeln!(out, ") -> {} {{", typ.name).ok();
794 writeln!(out, " {} {{", typ.name).ok();
795
796 for field in &typ.fields {
798 writeln!(out, " {},", field.name).ok();
799 }
800
801 writeln!(out, " }}").ok();
802 writeln!(out, "}}").ok();
803
804 out
805}
806
807#[cfg(test)]
808mod tests {
809 use super::*;
810 use alef_core::ir::{CoreWrapper, FieldDef, PrimitiveType, TypeRef};
811
812 fn make_test_type() -> TypeDef {
813 TypeDef {
814 name: "Config".to_string(),
815 rust_path: "my_crate::Config".to_string(),
816 fields: vec![
817 FieldDef {
818 name: "timeout".to_string(),
819 ty: TypeRef::Primitive(PrimitiveType::U64),
820 optional: false,
821 default: Some("30".to_string()),
822 doc: "Timeout in seconds".to_string(),
823 sanitized: false,
824 is_boxed: false,
825 type_rust_path: None,
826 cfg: None,
827 typed_default: Some(DefaultValue::IntLiteral(30)),
828 core_wrapper: CoreWrapper::None,
829 vec_inner_core_wrapper: CoreWrapper::None,
830 newtype_wrapper: None,
831 },
832 FieldDef {
833 name: "enabled".to_string(),
834 ty: TypeRef::Primitive(PrimitiveType::Bool),
835 optional: false,
836 default: None,
837 doc: "Enable feature".to_string(),
838 sanitized: false,
839 is_boxed: false,
840 type_rust_path: None,
841 cfg: None,
842 typed_default: Some(DefaultValue::BoolLiteral(true)),
843 core_wrapper: CoreWrapper::None,
844 vec_inner_core_wrapper: CoreWrapper::None,
845 newtype_wrapper: None,
846 },
847 FieldDef {
848 name: "name".to_string(),
849 ty: TypeRef::String,
850 optional: false,
851 default: None,
852 doc: "Config name".to_string(),
853 sanitized: false,
854 is_boxed: false,
855 type_rust_path: None,
856 cfg: None,
857 typed_default: Some(DefaultValue::StringLiteral("default".to_string())),
858 core_wrapper: CoreWrapper::None,
859 vec_inner_core_wrapper: CoreWrapper::None,
860 newtype_wrapper: None,
861 },
862 ],
863 methods: vec![],
864 is_opaque: false,
865 is_clone: true,
866 doc: "Configuration type".to_string(),
867 cfg: None,
868 is_trait: false,
869 has_default: true,
870 has_stripped_cfg_fields: false,
871 is_return_type: false,
872 serde_rename_all: None,
873 has_serde: false,
874 }
875 }
876
877 #[test]
878 fn test_default_value_bool_true_python() {
879 let field = FieldDef {
880 name: "enabled".to_string(),
881 ty: TypeRef::Primitive(PrimitiveType::Bool),
882 optional: false,
883 default: None,
884 doc: String::new(),
885 sanitized: false,
886 is_boxed: false,
887 type_rust_path: None,
888 cfg: None,
889 typed_default: Some(DefaultValue::BoolLiteral(true)),
890 core_wrapper: CoreWrapper::None,
891 vec_inner_core_wrapper: CoreWrapper::None,
892 newtype_wrapper: None,
893 };
894 assert_eq!(default_value_for_field(&field, "python"), "True");
895 }
896
897 #[test]
898 fn test_default_value_bool_false_go() {
899 let field = FieldDef {
900 name: "enabled".to_string(),
901 ty: TypeRef::Primitive(PrimitiveType::Bool),
902 optional: false,
903 default: None,
904 doc: String::new(),
905 sanitized: false,
906 is_boxed: false,
907 type_rust_path: None,
908 cfg: None,
909 typed_default: Some(DefaultValue::BoolLiteral(false)),
910 core_wrapper: CoreWrapper::None,
911 vec_inner_core_wrapper: CoreWrapper::None,
912 newtype_wrapper: None,
913 };
914 assert_eq!(default_value_for_field(&field, "go"), "false");
915 }
916
917 #[test]
918 fn test_default_value_string_literal() {
919 let field = FieldDef {
920 name: "name".to_string(),
921 ty: TypeRef::String,
922 optional: false,
923 default: None,
924 doc: String::new(),
925 sanitized: false,
926 is_boxed: false,
927 type_rust_path: None,
928 cfg: None,
929 typed_default: Some(DefaultValue::StringLiteral("hello".to_string())),
930 core_wrapper: CoreWrapper::None,
931 vec_inner_core_wrapper: CoreWrapper::None,
932 newtype_wrapper: None,
933 };
934 assert_eq!(default_value_for_field(&field, "python"), "\"hello\"");
935 assert_eq!(default_value_for_field(&field, "java"), "\"hello\"");
936 }
937
938 #[test]
939 fn test_default_value_int_literal() {
940 let field = FieldDef {
941 name: "timeout".to_string(),
942 ty: TypeRef::Primitive(PrimitiveType::U64),
943 optional: false,
944 default: None,
945 doc: String::new(),
946 sanitized: false,
947 is_boxed: false,
948 type_rust_path: None,
949 cfg: None,
950 typed_default: Some(DefaultValue::IntLiteral(42)),
951 core_wrapper: CoreWrapper::None,
952 vec_inner_core_wrapper: CoreWrapper::None,
953 newtype_wrapper: None,
954 };
955 let result = default_value_for_field(&field, "python");
956 assert_eq!(result, "42");
957 }
958
959 #[test]
960 fn test_default_value_none() {
961 let field = FieldDef {
962 name: "maybe".to_string(),
963 ty: TypeRef::Optional(Box::new(TypeRef::String)),
964 optional: true,
965 default: None,
966 doc: String::new(),
967 sanitized: false,
968 is_boxed: false,
969 type_rust_path: None,
970 cfg: None,
971 typed_default: Some(DefaultValue::None),
972 core_wrapper: CoreWrapper::None,
973 vec_inner_core_wrapper: CoreWrapper::None,
974 newtype_wrapper: None,
975 };
976 assert_eq!(default_value_for_field(&field, "python"), "None");
977 assert_eq!(default_value_for_field(&field, "go"), "nil");
978 assert_eq!(default_value_for_field(&field, "java"), "null");
979 assert_eq!(default_value_for_field(&field, "csharp"), "null");
980 }
981
982 #[test]
983 fn test_default_value_fallback_string() {
984 let field = FieldDef {
985 name: "name".to_string(),
986 ty: TypeRef::String,
987 optional: false,
988 default: Some("\"custom\"".to_string()),
989 doc: String::new(),
990 sanitized: false,
991 is_boxed: false,
992 type_rust_path: None,
993 cfg: None,
994 typed_default: None,
995 core_wrapper: CoreWrapper::None,
996 vec_inner_core_wrapper: CoreWrapper::None,
997 newtype_wrapper: None,
998 };
999 assert_eq!(default_value_for_field(&field, "python"), "\"custom\"");
1000 }
1001
1002 #[test]
1003 fn test_gen_pyo3_kwargs_constructor() {
1004 let typ = make_test_type();
1005 let output = gen_pyo3_kwargs_constructor(&typ, &|tr: &TypeRef| match tr {
1006 TypeRef::Primitive(p) => format!("{:?}", p),
1007 TypeRef::String | TypeRef::Char => "str".to_string(),
1008 _ => "Any".to_string(),
1009 });
1010
1011 assert!(output.contains("#[new]"));
1012 assert!(output.contains("#[pyo3(signature = ("));
1013 assert!(output.contains("timeout=30"));
1014 assert!(output.contains("enabled=True"));
1015 assert!(output.contains("name=\"default\""));
1016 assert!(output.contains("fn new("));
1017 }
1018
1019 #[test]
1020 fn test_gen_napi_defaults_constructor() {
1021 let typ = make_test_type();
1022 let output = gen_napi_defaults_constructor(&typ, &|tr: &TypeRef| match tr {
1023 TypeRef::Primitive(p) => format!("{:?}", p),
1024 TypeRef::String | TypeRef::Char => "String".to_string(),
1025 _ => "Value".to_string(),
1026 });
1027
1028 assert!(output.contains("pub fn new(mut env: napi::Env, obj: napi::Object)"));
1029 assert!(output.contains("timeout"));
1030 assert!(output.contains("enabled"));
1031 assert!(output.contains("name"));
1032 }
1033
1034 #[test]
1035 fn test_gen_go_functional_options() {
1036 let typ = make_test_type();
1037 let output = gen_go_functional_options(&typ, &|tr: &TypeRef| match tr {
1038 TypeRef::Primitive(p) => match p {
1039 PrimitiveType::U64 => "uint64".to_string(),
1040 PrimitiveType::Bool => "bool".to_string(),
1041 _ => "interface{}".to_string(),
1042 },
1043 TypeRef::String | TypeRef::Char => "string".to_string(),
1044 _ => "interface{}".to_string(),
1045 });
1046
1047 assert!(output.contains("type Config struct {"));
1048 assert!(output.contains("type ConfigOption func(*Config)"));
1049 assert!(output.contains("func WithConfigTimeout(val uint64) ConfigOption"));
1050 assert!(output.contains("func WithConfigEnabled(val bool) ConfigOption"));
1051 assert!(output.contains("func WithConfigName(val string) ConfigOption"));
1052 assert!(output.contains("func NewConfig(opts ...ConfigOption) *Config"));
1053 }
1054
1055 #[test]
1056 fn test_gen_java_builder() {
1057 let typ = make_test_type();
1058 let output = gen_java_builder(&typ, "dev.test", &|tr: &TypeRef| match tr {
1059 TypeRef::Primitive(p) => match p {
1060 PrimitiveType::U64 => "long".to_string(),
1061 PrimitiveType::Bool => "boolean".to_string(),
1062 _ => "int".to_string(),
1063 },
1064 TypeRef::String | TypeRef::Char => "String".to_string(),
1065 _ => "Object".to_string(),
1066 });
1067
1068 assert!(output.contains("package dev.test;"));
1069 assert!(output.contains("public class ConfigBuilder"));
1070 assert!(output.contains("withTimeout"));
1071 assert!(output.contains("withEnabled"));
1072 assert!(output.contains("withName"));
1073 assert!(output.contains("public Config build()"));
1074 }
1075
1076 #[test]
1077 fn test_gen_csharp_record() {
1078 let typ = make_test_type();
1079 let output = gen_csharp_record(&typ, "MyNamespace", &|tr: &TypeRef| match tr {
1080 TypeRef::Primitive(p) => match p {
1081 PrimitiveType::U64 => "ulong".to_string(),
1082 PrimitiveType::Bool => "bool".to_string(),
1083 _ => "int".to_string(),
1084 },
1085 TypeRef::String | TypeRef::Char => "string".to_string(),
1086 _ => "object".to_string(),
1087 });
1088
1089 assert!(output.contains("namespace MyNamespace;"));
1090 assert!(output.contains("public record Config"));
1091 assert!(output.contains("public ulong Timeout"));
1092 assert!(output.contains("public bool Enabled"));
1093 assert!(output.contains("public string Name"));
1094 assert!(output.contains("init;"));
1095 }
1096
1097 #[test]
1098 fn test_default_value_float_literal() {
1099 let field = FieldDef {
1100 name: "ratio".to_string(),
1101 ty: TypeRef::Primitive(PrimitiveType::F64),
1102 optional: false,
1103 default: None,
1104 doc: String::new(),
1105 sanitized: false,
1106 is_boxed: false,
1107 type_rust_path: None,
1108 cfg: None,
1109 typed_default: Some(DefaultValue::FloatLiteral(1.5)),
1110 core_wrapper: CoreWrapper::None,
1111 vec_inner_core_wrapper: CoreWrapper::None,
1112 newtype_wrapper: None,
1113 };
1114 let result = default_value_for_field(&field, "python");
1115 assert!(result.contains("1.5"));
1116 }
1117
1118 #[test]
1119 fn test_default_value_no_typed_no_default() {
1120 let field = FieldDef {
1121 name: "count".to_string(),
1122 ty: TypeRef::Primitive(PrimitiveType::U32),
1123 optional: false,
1124 default: None,
1125 doc: String::new(),
1126 sanitized: false,
1127 is_boxed: false,
1128 type_rust_path: None,
1129 cfg: None,
1130 typed_default: None,
1131 core_wrapper: CoreWrapper::None,
1132 vec_inner_core_wrapper: CoreWrapper::None,
1133 newtype_wrapper: None,
1134 };
1135 assert_eq!(default_value_for_field(&field, "python"), "0");
1137 assert_eq!(default_value_for_field(&field, "go"), "0");
1138 }
1139}