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 => match language {
365 "rust" => "String::new()".to_string(),
366 _ => "\"\"".to_string(),
367 },
368 TypeRef::Json => match language {
369 "python" | "ruby" => "{}".to_string(),
370 "go" => "json.RawMessage(nil)".to_string(),
371 "java" => "new com.fasterxml.jackson.databind.node.ObjectNode(null)".to_string(),
372 "csharp" => "JObject.Parse(\"{}\")".to_string(),
373 "php" => "[]".to_string(),
374 "r" => "list()".to_string(),
375 "rust" => "serde_json::json!({})".to_string(),
376 _ => "{}".to_string(),
377 },
378 TypeRef::Duration => "0".to_string(),
379 TypeRef::Bytes => match language {
380 "python" => "b\"\"".to_string(),
381 "go" => "[]byte{}".to_string(),
382 "rust" => "vec![]".to_string(),
383 _ => "\"\"".to_string(),
384 },
385 _ => match language {
386 "python" => "None".to_string(),
387 "ruby" => "nil".to_string(),
388 "go" => "nil".to_string(),
389 "rust" => "Default::default()".to_string(),
390 _ => "null".to_string(),
391 },
392 }
393 }
394 DefaultValue::None => match language {
395 "python" => "None".to_string(),
396 "ruby" => "nil".to_string(),
397 "go" => "nil".to_string(),
398 "java" => "null".to_string(),
399 "csharp" => "null".to_string(),
400 "php" => "null".to_string(),
401 "r" => "NULL".to_string(),
402 "rust" => "None".to_string(),
403 _ => "null".to_string(),
404 },
405 };
406 }
407
408 if let Some(default_str) = &field.default {
410 return default_str.clone();
411 }
412
413 match &field.ty {
415 TypeRef::Primitive(p) => match p {
416 alef_core::ir::PrimitiveType::Bool => match language {
417 "python" => "False".to_string(),
418 "ruby" => "false".to_string(),
419 "csharp" => "false".to_string(),
420 "java" => "false".to_string(),
421 "php" => "false".to_string(),
422 "r" => "FALSE".to_string(),
423 _ => "false".to_string(),
424 },
425 alef_core::ir::PrimitiveType::U8
426 | alef_core::ir::PrimitiveType::U16
427 | alef_core::ir::PrimitiveType::U32
428 | alef_core::ir::PrimitiveType::U64
429 | alef_core::ir::PrimitiveType::I8
430 | alef_core::ir::PrimitiveType::I16
431 | alef_core::ir::PrimitiveType::I32
432 | alef_core::ir::PrimitiveType::I64
433 | alef_core::ir::PrimitiveType::Usize
434 | alef_core::ir::PrimitiveType::Isize => "0".to_string(),
435 alef_core::ir::PrimitiveType::F32 | alef_core::ir::PrimitiveType::F64 => "0.0".to_string(),
436 },
437 TypeRef::String | TypeRef::Char => match language {
438 "python" => "\"\"".to_string(),
439 "ruby" => "\"\"".to_string(),
440 "go" => "\"\"".to_string(),
441 "java" => "\"\"".to_string(),
442 "csharp" => "\"\"".to_string(),
443 "php" => "\"\"".to_string(),
444 "r" => "\"\"".to_string(),
445 "rust" => "String::new()".to_string(),
446 _ => "\"\"".to_string(),
447 },
448 TypeRef::Bytes => match language {
449 "python" => "b\"\"".to_string(),
450 "ruby" => "\"\"".to_string(),
451 "go" => "[]byte{}".to_string(),
452 "java" => "new byte[]{}".to_string(),
453 "csharp" => "new byte[]{}".to_string(),
454 "php" => "\"\"".to_string(),
455 "r" => "raw()".to_string(),
456 "rust" => "vec![]".to_string(),
457 _ => "[]".to_string(),
458 },
459 TypeRef::Optional(_) => match language {
460 "python" => "None".to_string(),
461 "ruby" => "nil".to_string(),
462 "go" => "nil".to_string(),
463 "java" => "null".to_string(),
464 "csharp" => "null".to_string(),
465 "php" => "null".to_string(),
466 "r" => "NULL".to_string(),
467 "rust" => "None".to_string(),
468 _ => "null".to_string(),
469 },
470 TypeRef::Vec(_) => match language {
471 "python" => "[]".to_string(),
472 "ruby" => "[]".to_string(),
473 "go" => "[]interface{}{}".to_string(),
474 "java" => "new java.util.ArrayList<>()".to_string(),
475 "csharp" => "[]".to_string(),
476 "php" => "[]".to_string(),
477 "r" => "c()".to_string(),
478 "rust" => "vec![]".to_string(),
479 _ => "[]".to_string(),
480 },
481 TypeRef::Map(_, _) => match language {
482 "python" => "{}".to_string(),
483 "ruby" => "{}".to_string(),
484 "go" => "make(map[string]interface{})".to_string(),
485 "java" => "new java.util.HashMap<>()".to_string(),
486 "csharp" => "new Dictionary<string, object>()".to_string(),
487 "php" => "[]".to_string(),
488 "r" => "list()".to_string(),
489 "rust" => "std::collections::HashMap::new()".to_string(),
490 _ => "{}".to_string(),
491 },
492 TypeRef::Json => match language {
493 "python" => "{}".to_string(),
494 "ruby" => "{}".to_string(),
495 "go" => "json.RawMessage(nil)".to_string(),
496 "java" => "new com.fasterxml.jackson.databind.JsonNode()".to_string(),
497 "csharp" => "JObject.Parse(\"{}\")".to_string(),
498 "php" => "[]".to_string(),
499 "r" => "list()".to_string(),
500 "rust" => "serde_json::json!({})".to_string(),
501 _ => "{}".to_string(),
502 },
503 TypeRef::Named(name) => match language {
504 "rust" => format!("{name}::default()"),
505 "python" => "None".to_string(),
506 "ruby" => "nil".to_string(),
507 "go" => "nil".to_string(),
508 "java" => "null".to_string(),
509 "csharp" => "null".to_string(),
510 "php" => "null".to_string(),
511 "r" => "NULL".to_string(),
512 _ => "null".to_string(),
513 },
514 _ => match language {
515 "python" => "None".to_string(),
516 "ruby" => "nil".to_string(),
517 "go" => "nil".to_string(),
518 "java" => "null".to_string(),
519 "csharp" => "null".to_string(),
520 "php" => "null".to_string(),
521 "r" => "NULL".to_string(),
522 "rust" => "Default::default()".to_string(),
523 _ => "null".to_string(),
524 },
525 }
526}
527
528trait TypeRefExt {
530 fn type_name(&self) -> String;
531}
532
533impl TypeRefExt for TypeRef {
534 fn type_name(&self) -> String {
535 match self {
536 TypeRef::Named(n) => n.clone(),
537 TypeRef::Primitive(p) => format!("{:?}", p),
538 TypeRef::String | TypeRef::Char => "String".to_string(),
539 TypeRef::Bytes => "Bytes".to_string(),
540 TypeRef::Optional(inner) => format!("Option<{}>", inner.type_name()),
541 TypeRef::Vec(inner) => format!("Vec<{}>", inner.type_name()),
542 TypeRef::Map(k, v) => format!("Map<{}, {}>", k.type_name(), v.type_name()),
543 TypeRef::Path => "Path".to_string(),
544 TypeRef::Unit => "()".to_string(),
545 TypeRef::Json => "Json".to_string(),
546 TypeRef::Duration => "Duration".to_string(),
547 }
548 }
549}
550
551const MAGNUS_MAX_ARITY: usize = 15;
553
554pub fn gen_magnus_kwargs_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
560 if typ.fields.len() > MAGNUS_MAX_ARITY {
561 gen_magnus_hash_constructor(typ, type_mapper)
562 } else {
563 gen_magnus_positional_constructor(typ, type_mapper)
564 }
565}
566
567fn as_type_path_prefix(type_str: &str) -> String {
574 if type_str.contains('<') {
575 format!("<{type_str}>")
576 } else {
577 type_str.to_string()
578 }
579}
580
581fn gen_magnus_hash_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
584 use std::fmt::Write;
585 let mut out = String::with_capacity(1024);
586
587 writeln!(out, "fn new(kwargs: magnus::RHash) -> Result<Self, magnus::Error> {{").ok();
588 writeln!(out, " let ruby = unsafe {{ magnus::Ruby::get_unchecked() }};").ok();
589 writeln!(out, " Ok(Self {{").ok();
590
591 for field in &typ.fields {
592 let is_optional = field_is_optional_in_rust(field);
593 let inner_type = type_mapper(&field.ty);
595 let type_prefix = as_type_path_prefix(&inner_type);
596 if is_optional {
597 writeln!(
599 out,
600 " {name}: kwargs.get(ruby.to_symbol(\"{name}\")).and_then(|v| {type_prefix}::try_convert(v).ok()),",
601 name = field.name,
602 type_prefix = type_prefix,
603 ).ok();
604 } else if use_unwrap_or_default(field) {
605 writeln!(
606 out,
607 " {name}: kwargs.get(ruby.to_symbol(\"{name}\")).and_then(|v| {type_prefix}::try_convert(v).ok()).unwrap_or_default(),",
608 name = field.name,
609 type_prefix = type_prefix,
610 ).ok();
611 } else {
612 let default_str = default_value_for_field(field, "rust");
613 writeln!(
614 out,
615 " {name}: kwargs.get(ruby.to_symbol(\"{name}\")).and_then(|v| {type_prefix}::try_convert(v).ok()).unwrap_or({default}),",
616 name = field.name,
617 type_prefix = type_prefix,
618 default = default_str,
619 ).ok();
620 }
621 }
622
623 writeln!(out, " }})").ok();
624 writeln!(out, "}}").ok();
625
626 out
627}
628
629fn field_is_optional_in_rust(field: &FieldDef) -> bool {
634 field.optional || matches!(&field.ty, TypeRef::Optional(_))
635}
636
637fn gen_magnus_positional_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
640 use std::fmt::Write;
641 let mut out = String::with_capacity(512);
642
643 writeln!(out, "fn new(").ok();
644
645 for (i, field) in typ.fields.iter().enumerate() {
649 let comma = if i < typ.fields.len() - 1 { "," } else { "" };
650 let is_optional = field_is_optional_in_rust(field);
651 if is_optional {
652 let inner_type = type_mapper(&field.ty);
656 writeln!(out, " {}: Option<{}>{}", field.name, inner_type, comma).ok();
657 } else {
658 let field_type = type_mapper(&field.ty);
659 writeln!(out, " {}: Option<{}>{}", field.name, field_type, comma).ok();
660 }
661 }
662
663 writeln!(out, ") -> Self {{").ok();
664 writeln!(out, " Self {{").ok();
665
666 for field in &typ.fields {
667 let is_optional = field_is_optional_in_rust(field);
668 if is_optional {
669 writeln!(out, " {},", field.name).ok();
671 } else if use_unwrap_or_default(field) {
672 writeln!(out, " {}: {}.unwrap_or_default(),", field.name, field.name).ok();
673 } else {
674 let default_str = default_value_for_field(field, "rust");
675 writeln!(
676 out,
677 " {}: {}.unwrap_or({}),",
678 field.name, field.name, default_str
679 )
680 .ok();
681 }
682 }
683
684 writeln!(out, " }}").ok();
685 writeln!(out, "}}").ok();
686
687 out
688}
689
690pub fn gen_php_kwargs_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
694 use std::fmt::Write;
695 let mut out = String::with_capacity(512);
696
697 writeln!(out, "pub fn __construct(").ok();
698
699 for (i, field) in typ.fields.iter().enumerate() {
701 let mapped = type_mapper(&field.ty);
702 let comma = if i < typ.fields.len() - 1 { "," } else { "" };
703 writeln!(out, " {}: Option<{}>{}", field.name, mapped, comma).ok();
704 }
705
706 writeln!(out, ") -> Self {{").ok();
707 writeln!(out, " Self {{").ok();
708
709 for field in &typ.fields {
710 let is_optional_field = field.optional || matches!(&field.ty, TypeRef::Optional(_));
711 if is_optional_field {
712 writeln!(out, " {},", field.name).ok();
714 } else if use_unwrap_or_default(field) {
715 writeln!(out, " {}: {}.unwrap_or_default(),", field.name, field.name).ok();
717 } else {
718 let default_str = default_value_for_field(field, "rust");
720 writeln!(
721 out,
722 " {}: {}.unwrap_or({}),",
723 field.name, field.name, default_str
724 )
725 .ok();
726 }
727 }
728
729 writeln!(out, " }}").ok();
730 writeln!(out, "}}").ok();
731
732 out
733}
734
735pub fn gen_rustler_kwargs_constructor(typ: &TypeDef, _type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
738 use std::fmt::Write;
739 let mut out = String::with_capacity(512);
740
741 writeln!(
744 out,
745 "pub fn new(opts: std::collections::HashMap<String, rustler::Term>) -> Self {{"
746 )
747 .ok();
748 writeln!(out, " Self {{").ok();
749
750 for field in &typ.fields {
754 if field.optional {
755 writeln!(
757 out,
758 " {}: opts.get(\"{}\").and_then(|t| t.decode().ok()),",
759 field.name, field.name
760 )
761 .ok();
762 } else if use_unwrap_or_default(field) {
763 writeln!(
764 out,
765 " {}: opts.get(\"{}\").and_then(|t| t.decode().ok()).unwrap_or_default(),",
766 field.name, field.name
767 )
768 .ok();
769 } else {
770 let default_str = default_value_for_field(field, "rust");
771 let is_enum_variant_default = default_str.contains("::") || default_str.starts_with("\"");
774
775 if is_enum_variant_default && matches!(&field.ty, TypeRef::String | TypeRef::Char) {
776 writeln!(
778 out,
779 " {}: opts.get(\"{}\").and_then(|t| t.decode().ok()).unwrap_or_default(),",
780 field.name, field.name
781 )
782 .ok();
783 } else if matches!(&field.ty, TypeRef::Named(_)) {
784 writeln!(
787 out,
788 " {}: opts.get(\"{}\").and_then(|t| t.decode().ok()).unwrap_or_default(),",
789 field.name, field.name
790 )
791 .ok();
792 } else {
793 writeln!(
794 out,
795 " {}: opts.get(\"{}\").and_then(|t| t.decode().ok()).unwrap_or({}),",
796 field.name, field.name, default_str
797 )
798 .ok();
799 }
800 }
801 }
802
803 writeln!(out, " }}").ok();
804 writeln!(out, "}}").ok();
805
806 out
807}
808
809pub fn gen_extendr_kwargs_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
812 use std::fmt::Write;
813 let mut out = String::with_capacity(512);
814
815 writeln!(out, "#[extendr]").ok();
816 writeln!(out, "pub fn new_{}(", typ.name.to_lowercase()).ok();
817
818 for (i, field) in typ.fields.iter().enumerate() {
820 let field_type = type_mapper(&field.ty);
821 let default_str = default_value_for_field(field, "r");
822 let comma = if i < typ.fields.len() - 1 { "," } else { "" };
823 writeln!(out, " {}: {} = {}{}", field.name, field_type, default_str, comma).ok();
824 }
825
826 writeln!(out, ") -> {} {{", typ.name).ok();
827 writeln!(out, " {} {{", typ.name).ok();
828
829 for field in &typ.fields {
831 writeln!(out, " {},", field.name).ok();
832 }
833
834 writeln!(out, " }}").ok();
835 writeln!(out, "}}").ok();
836
837 out
838}
839
840#[cfg(test)]
841mod tests {
842 use super::*;
843 use alef_core::ir::{CoreWrapper, FieldDef, PrimitiveType, TypeRef};
844
845 fn make_test_type() -> TypeDef {
846 TypeDef {
847 name: "Config".to_string(),
848 rust_path: "my_crate::Config".to_string(),
849 original_rust_path: String::new(),
850 fields: vec![
851 FieldDef {
852 name: "timeout".to_string(),
853 ty: TypeRef::Primitive(PrimitiveType::U64),
854 optional: false,
855 default: Some("30".to_string()),
856 doc: "Timeout in seconds".to_string(),
857 sanitized: false,
858 is_boxed: false,
859 type_rust_path: None,
860 cfg: None,
861 typed_default: Some(DefaultValue::IntLiteral(30)),
862 core_wrapper: CoreWrapper::None,
863 vec_inner_core_wrapper: CoreWrapper::None,
864 newtype_wrapper: None,
865 },
866 FieldDef {
867 name: "enabled".to_string(),
868 ty: TypeRef::Primitive(PrimitiveType::Bool),
869 optional: false,
870 default: None,
871 doc: "Enable feature".to_string(),
872 sanitized: false,
873 is_boxed: false,
874 type_rust_path: None,
875 cfg: None,
876 typed_default: Some(DefaultValue::BoolLiteral(true)),
877 core_wrapper: CoreWrapper::None,
878 vec_inner_core_wrapper: CoreWrapper::None,
879 newtype_wrapper: None,
880 },
881 FieldDef {
882 name: "name".to_string(),
883 ty: TypeRef::String,
884 optional: false,
885 default: None,
886 doc: "Config name".to_string(),
887 sanitized: false,
888 is_boxed: false,
889 type_rust_path: None,
890 cfg: None,
891 typed_default: Some(DefaultValue::StringLiteral("default".to_string())),
892 core_wrapper: CoreWrapper::None,
893 vec_inner_core_wrapper: CoreWrapper::None,
894 newtype_wrapper: None,
895 },
896 ],
897 methods: vec![],
898 is_opaque: false,
899 is_clone: true,
900 doc: "Configuration type".to_string(),
901 cfg: None,
902 is_trait: false,
903 has_default: true,
904 has_stripped_cfg_fields: false,
905 is_return_type: false,
906 serde_rename_all: None,
907 has_serde: false,
908 super_traits: vec![],
909 }
910 }
911
912 #[test]
913 fn test_default_value_bool_true_python() {
914 let field = FieldDef {
915 name: "enabled".to_string(),
916 ty: TypeRef::Primitive(PrimitiveType::Bool),
917 optional: false,
918 default: None,
919 doc: String::new(),
920 sanitized: false,
921 is_boxed: false,
922 type_rust_path: None,
923 cfg: None,
924 typed_default: Some(DefaultValue::BoolLiteral(true)),
925 core_wrapper: CoreWrapper::None,
926 vec_inner_core_wrapper: CoreWrapper::None,
927 newtype_wrapper: None,
928 };
929 assert_eq!(default_value_for_field(&field, "python"), "True");
930 }
931
932 #[test]
933 fn test_default_value_bool_false_go() {
934 let field = FieldDef {
935 name: "enabled".to_string(),
936 ty: TypeRef::Primitive(PrimitiveType::Bool),
937 optional: false,
938 default: None,
939 doc: String::new(),
940 sanitized: false,
941 is_boxed: false,
942 type_rust_path: None,
943 cfg: None,
944 typed_default: Some(DefaultValue::BoolLiteral(false)),
945 core_wrapper: CoreWrapper::None,
946 vec_inner_core_wrapper: CoreWrapper::None,
947 newtype_wrapper: None,
948 };
949 assert_eq!(default_value_for_field(&field, "go"), "false");
950 }
951
952 #[test]
953 fn test_default_value_string_literal() {
954 let field = FieldDef {
955 name: "name".to_string(),
956 ty: TypeRef::String,
957 optional: false,
958 default: None,
959 doc: String::new(),
960 sanitized: false,
961 is_boxed: false,
962 type_rust_path: None,
963 cfg: None,
964 typed_default: Some(DefaultValue::StringLiteral("hello".to_string())),
965 core_wrapper: CoreWrapper::None,
966 vec_inner_core_wrapper: CoreWrapper::None,
967 newtype_wrapper: None,
968 };
969 assert_eq!(default_value_for_field(&field, "python"), "\"hello\"");
970 assert_eq!(default_value_for_field(&field, "java"), "\"hello\"");
971 }
972
973 #[test]
974 fn test_default_value_int_literal() {
975 let field = FieldDef {
976 name: "timeout".to_string(),
977 ty: TypeRef::Primitive(PrimitiveType::U64),
978 optional: false,
979 default: None,
980 doc: String::new(),
981 sanitized: false,
982 is_boxed: false,
983 type_rust_path: None,
984 cfg: None,
985 typed_default: Some(DefaultValue::IntLiteral(42)),
986 core_wrapper: CoreWrapper::None,
987 vec_inner_core_wrapper: CoreWrapper::None,
988 newtype_wrapper: None,
989 };
990 let result = default_value_for_field(&field, "python");
991 assert_eq!(result, "42");
992 }
993
994 #[test]
995 fn test_default_value_none() {
996 let field = FieldDef {
997 name: "maybe".to_string(),
998 ty: TypeRef::Optional(Box::new(TypeRef::String)),
999 optional: true,
1000 default: None,
1001 doc: String::new(),
1002 sanitized: false,
1003 is_boxed: false,
1004 type_rust_path: None,
1005 cfg: None,
1006 typed_default: Some(DefaultValue::None),
1007 core_wrapper: CoreWrapper::None,
1008 vec_inner_core_wrapper: CoreWrapper::None,
1009 newtype_wrapper: None,
1010 };
1011 assert_eq!(default_value_for_field(&field, "python"), "None");
1012 assert_eq!(default_value_for_field(&field, "go"), "nil");
1013 assert_eq!(default_value_for_field(&field, "java"), "null");
1014 assert_eq!(default_value_for_field(&field, "csharp"), "null");
1015 }
1016
1017 #[test]
1018 fn test_default_value_fallback_string() {
1019 let field = FieldDef {
1020 name: "name".to_string(),
1021 ty: TypeRef::String,
1022 optional: false,
1023 default: Some("\"custom\"".to_string()),
1024 doc: String::new(),
1025 sanitized: false,
1026 is_boxed: false,
1027 type_rust_path: None,
1028 cfg: None,
1029 typed_default: None,
1030 core_wrapper: CoreWrapper::None,
1031 vec_inner_core_wrapper: CoreWrapper::None,
1032 newtype_wrapper: None,
1033 };
1034 assert_eq!(default_value_for_field(&field, "python"), "\"custom\"");
1035 }
1036
1037 #[test]
1038 fn test_gen_pyo3_kwargs_constructor() {
1039 let typ = make_test_type();
1040 let output = gen_pyo3_kwargs_constructor(&typ, &|tr: &TypeRef| match tr {
1041 TypeRef::Primitive(p) => format!("{:?}", p),
1042 TypeRef::String | TypeRef::Char => "str".to_string(),
1043 _ => "Any".to_string(),
1044 });
1045
1046 assert!(output.contains("#[new]"));
1047 assert!(output.contains("#[pyo3(signature = ("));
1048 assert!(output.contains("timeout=30"));
1049 assert!(output.contains("enabled=True"));
1050 assert!(output.contains("name=\"default\""));
1051 assert!(output.contains("fn new("));
1052 }
1053
1054 #[test]
1055 fn test_gen_napi_defaults_constructor() {
1056 let typ = make_test_type();
1057 let output = gen_napi_defaults_constructor(&typ, &|tr: &TypeRef| match tr {
1058 TypeRef::Primitive(p) => format!("{:?}", p),
1059 TypeRef::String | TypeRef::Char => "String".to_string(),
1060 _ => "Value".to_string(),
1061 });
1062
1063 assert!(output.contains("pub fn new(mut env: napi::Env, obj: napi::Object)"));
1064 assert!(output.contains("timeout"));
1065 assert!(output.contains("enabled"));
1066 assert!(output.contains("name"));
1067 }
1068
1069 #[test]
1070 fn test_gen_go_functional_options() {
1071 let typ = make_test_type();
1072 let output = gen_go_functional_options(&typ, &|tr: &TypeRef| match tr {
1073 TypeRef::Primitive(p) => match p {
1074 PrimitiveType::U64 => "uint64".to_string(),
1075 PrimitiveType::Bool => "bool".to_string(),
1076 _ => "interface{}".to_string(),
1077 },
1078 TypeRef::String | TypeRef::Char => "string".to_string(),
1079 _ => "interface{}".to_string(),
1080 });
1081
1082 assert!(output.contains("type Config struct {"));
1083 assert!(output.contains("type ConfigOption func(*Config)"));
1084 assert!(output.contains("func WithConfigTimeout(val uint64) ConfigOption"));
1085 assert!(output.contains("func WithConfigEnabled(val bool) ConfigOption"));
1086 assert!(output.contains("func WithConfigName(val string) ConfigOption"));
1087 assert!(output.contains("func NewConfig(opts ...ConfigOption) *Config"));
1088 }
1089
1090 #[test]
1091 fn test_gen_java_builder() {
1092 let typ = make_test_type();
1093 let output = gen_java_builder(&typ, "dev.test", &|tr: &TypeRef| match tr {
1094 TypeRef::Primitive(p) => match p {
1095 PrimitiveType::U64 => "long".to_string(),
1096 PrimitiveType::Bool => "boolean".to_string(),
1097 _ => "int".to_string(),
1098 },
1099 TypeRef::String | TypeRef::Char => "String".to_string(),
1100 _ => "Object".to_string(),
1101 });
1102
1103 assert!(output.contains("package dev.test;"));
1104 assert!(output.contains("public class ConfigBuilder"));
1105 assert!(output.contains("withTimeout"));
1106 assert!(output.contains("withEnabled"));
1107 assert!(output.contains("withName"));
1108 assert!(output.contains("public Config build()"));
1109 }
1110
1111 #[test]
1112 fn test_gen_csharp_record() {
1113 let typ = make_test_type();
1114 let output = gen_csharp_record(&typ, "MyNamespace", &|tr: &TypeRef| match tr {
1115 TypeRef::Primitive(p) => match p {
1116 PrimitiveType::U64 => "ulong".to_string(),
1117 PrimitiveType::Bool => "bool".to_string(),
1118 _ => "int".to_string(),
1119 },
1120 TypeRef::String | TypeRef::Char => "string".to_string(),
1121 _ => "object".to_string(),
1122 });
1123
1124 assert!(output.contains("namespace MyNamespace;"));
1125 assert!(output.contains("public record Config"));
1126 assert!(output.contains("public ulong Timeout"));
1127 assert!(output.contains("public bool Enabled"));
1128 assert!(output.contains("public string Name"));
1129 assert!(output.contains("init;"));
1130 }
1131
1132 #[test]
1133 fn test_default_value_float_literal() {
1134 let field = FieldDef {
1135 name: "ratio".to_string(),
1136 ty: TypeRef::Primitive(PrimitiveType::F64),
1137 optional: false,
1138 default: None,
1139 doc: String::new(),
1140 sanitized: false,
1141 is_boxed: false,
1142 type_rust_path: None,
1143 cfg: None,
1144 typed_default: Some(DefaultValue::FloatLiteral(1.5)),
1145 core_wrapper: CoreWrapper::None,
1146 vec_inner_core_wrapper: CoreWrapper::None,
1147 newtype_wrapper: None,
1148 };
1149 let result = default_value_for_field(&field, "python");
1150 assert!(result.contains("1.5"));
1151 }
1152
1153 #[test]
1154 fn test_default_value_no_typed_no_default() {
1155 let field = FieldDef {
1156 name: "count".to_string(),
1157 ty: TypeRef::Primitive(PrimitiveType::U32),
1158 optional: false,
1159 default: None,
1160 doc: String::new(),
1161 sanitized: false,
1162 is_boxed: false,
1163 type_rust_path: None,
1164 cfg: None,
1165 typed_default: None,
1166 core_wrapper: CoreWrapper::None,
1167 vec_inner_core_wrapper: CoreWrapper::None,
1168 newtype_wrapper: None,
1169 };
1170 assert_eq!(default_value_for_field(&field, "python"), "0");
1172 assert_eq!(default_value_for_field(&field, "go"), "0");
1173 }
1174}