1use alef_core::ir::{DefaultValue, FieldDef, PrimitiveType, TypeDef, TypeRef};
2use heck::{ToPascalCase, ToShoutySnakeCase, ToSnakeCase};
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) => {
326 if matches!(field.ty, TypeRef::String) {
330 let snake = v.to_snake_case();
331 return match language {
332 "rust" => format!("\"{}\".to_string()", snake),
333 _ => format!("\"{}\"", snake),
334 };
335 }
336 match language {
337 "python" => format!("{}.{}", field.ty.type_name(), v.to_shouty_snake_case()),
338 "ruby" => format!("{}::{}", field.ty.type_name(), v.to_pascal_case()),
339 "go" => format!("{}{}", field.ty.type_name(), v.to_pascal_case()),
340 "java" => format!("{}.{}", field.ty.type_name(), v.to_shouty_snake_case()),
341 "csharp" => format!("{}.{}", field.ty.type_name(), v.to_pascal_case()),
342 "php" => format!("{}::{}", field.ty.type_name(), v.to_pascal_case()),
343 "r" => format!("{}${}", field.ty.type_name(), v.to_pascal_case()),
344 "rust" => format!("{}::{}", field.ty.type_name(), v.to_pascal_case()),
345 _ => v.clone(),
346 }
347 }
348 DefaultValue::Empty => {
349 match &field.ty {
351 TypeRef::Vec(_) => match language {
352 "python" | "ruby" | "csharp" => "[]".to_string(),
353 "go" => "nil".to_string(),
354 "java" => "List.of()".to_string(),
355 "php" => "[]".to_string(),
356 "r" => "c()".to_string(),
357 "rust" => "vec![]".to_string(),
358 _ => "null".to_string(),
359 },
360 TypeRef::Map(_, _) => match language {
361 "python" => "{}".to_string(),
362 "go" => "nil".to_string(),
363 "java" => "Map.of()".to_string(),
364 "rust" => "Default::default()".to_string(),
365 _ => "null".to_string(),
366 },
367 TypeRef::Primitive(p) => match p {
368 PrimitiveType::Bool => match language {
369 "python" => "False".to_string(),
370 "ruby" => "false".to_string(),
371 _ => "false".to_string(),
372 },
373 PrimitiveType::F32 | PrimitiveType::F64 => "0.0".to_string(),
374 _ => "0".to_string(),
375 },
376 TypeRef::String | TypeRef::Char | TypeRef::Path => match language {
377 "rust" => "String::new()".to_string(),
378 _ => "\"\"".to_string(),
379 },
380 TypeRef::Json => match language {
381 "python" | "ruby" => "{}".to_string(),
382 "go" => "json.RawMessage(nil)".to_string(),
383 "java" => "new com.fasterxml.jackson.databind.node.ObjectNode(null)".to_string(),
384 "csharp" => "JObject.Parse(\"{}\")".to_string(),
385 "php" => "[]".to_string(),
386 "r" => "list()".to_string(),
387 "rust" => "serde_json::json!({})".to_string(),
388 _ => "{}".to_string(),
389 },
390 TypeRef::Duration => "0".to_string(),
391 TypeRef::Bytes => match language {
392 "python" => "b\"\"".to_string(),
393 "go" => "[]byte{}".to_string(),
394 "rust" => "vec![]".to_string(),
395 _ => "\"\"".to_string(),
396 },
397 _ => match language {
398 "python" => "None".to_string(),
399 "ruby" => "nil".to_string(),
400 "go" => "nil".to_string(),
401 "rust" => "Default::default()".to_string(),
402 _ => "null".to_string(),
403 },
404 }
405 }
406 DefaultValue::None => match language {
407 "python" => "None".to_string(),
408 "ruby" => "nil".to_string(),
409 "go" => "nil".to_string(),
410 "java" => "null".to_string(),
411 "csharp" => "null".to_string(),
412 "php" => "null".to_string(),
413 "r" => "NULL".to_string(),
414 "rust" => "None".to_string(),
415 _ => "null".to_string(),
416 },
417 };
418 }
419
420 if let Some(default_str) = &field.default {
422 return default_str.clone();
423 }
424
425 match &field.ty {
427 TypeRef::Primitive(p) => match p {
428 alef_core::ir::PrimitiveType::Bool => match language {
429 "python" => "False".to_string(),
430 "ruby" => "false".to_string(),
431 "csharp" => "false".to_string(),
432 "java" => "false".to_string(),
433 "php" => "false".to_string(),
434 "r" => "FALSE".to_string(),
435 _ => "false".to_string(),
436 },
437 alef_core::ir::PrimitiveType::U8
438 | alef_core::ir::PrimitiveType::U16
439 | alef_core::ir::PrimitiveType::U32
440 | alef_core::ir::PrimitiveType::U64
441 | alef_core::ir::PrimitiveType::I8
442 | alef_core::ir::PrimitiveType::I16
443 | alef_core::ir::PrimitiveType::I32
444 | alef_core::ir::PrimitiveType::I64
445 | alef_core::ir::PrimitiveType::Usize
446 | alef_core::ir::PrimitiveType::Isize => "0".to_string(),
447 alef_core::ir::PrimitiveType::F32 | alef_core::ir::PrimitiveType::F64 => "0.0".to_string(),
448 },
449 TypeRef::String | TypeRef::Char => match language {
450 "python" => "\"\"".to_string(),
451 "ruby" => "\"\"".to_string(),
452 "go" => "\"\"".to_string(),
453 "java" => "\"\"".to_string(),
454 "csharp" => "\"\"".to_string(),
455 "php" => "\"\"".to_string(),
456 "r" => "\"\"".to_string(),
457 "rust" => "String::new()".to_string(),
458 _ => "\"\"".to_string(),
459 },
460 TypeRef::Bytes => match language {
461 "python" => "b\"\"".to_string(),
462 "ruby" => "\"\"".to_string(),
463 "go" => "[]byte{}".to_string(),
464 "java" => "new byte[]{}".to_string(),
465 "csharp" => "new byte[]{}".to_string(),
466 "php" => "\"\"".to_string(),
467 "r" => "raw()".to_string(),
468 "rust" => "vec![]".to_string(),
469 _ => "[]".to_string(),
470 },
471 TypeRef::Optional(_) => match language {
472 "python" => "None".to_string(),
473 "ruby" => "nil".to_string(),
474 "go" => "nil".to_string(),
475 "java" => "null".to_string(),
476 "csharp" => "null".to_string(),
477 "php" => "null".to_string(),
478 "r" => "NULL".to_string(),
479 "rust" => "None".to_string(),
480 _ => "null".to_string(),
481 },
482 TypeRef::Vec(_) => match language {
483 "python" => "[]".to_string(),
484 "ruby" => "[]".to_string(),
485 "go" => "[]interface{}{}".to_string(),
486 "java" => "new java.util.ArrayList<>()".to_string(),
487 "csharp" => "[]".to_string(),
488 "php" => "[]".to_string(),
489 "r" => "c()".to_string(),
490 "rust" => "vec![]".to_string(),
491 _ => "[]".to_string(),
492 },
493 TypeRef::Map(_, _) => match language {
494 "python" => "{}".to_string(),
495 "ruby" => "{}".to_string(),
496 "go" => "make(map[string]interface{})".to_string(),
497 "java" => "new java.util.HashMap<>()".to_string(),
498 "csharp" => "new Dictionary<string, object>()".to_string(),
499 "php" => "[]".to_string(),
500 "r" => "list()".to_string(),
501 "rust" => "std::collections::HashMap::new()".to_string(),
502 _ => "{}".to_string(),
503 },
504 TypeRef::Json => match language {
505 "python" => "{}".to_string(),
506 "ruby" => "{}".to_string(),
507 "go" => "json.RawMessage(nil)".to_string(),
508 "java" => "new com.fasterxml.jackson.databind.JsonNode()".to_string(),
509 "csharp" => "JObject.Parse(\"{}\")".to_string(),
510 "php" => "[]".to_string(),
511 "r" => "list()".to_string(),
512 "rust" => "serde_json::json!({})".to_string(),
513 _ => "{}".to_string(),
514 },
515 TypeRef::Named(name) => match language {
516 "rust" => format!("{name}::default()"),
517 "python" => "None".to_string(),
518 "ruby" => "nil".to_string(),
519 "go" => "nil".to_string(),
520 "java" => "null".to_string(),
521 "csharp" => "null".to_string(),
522 "php" => "null".to_string(),
523 "r" => "NULL".to_string(),
524 _ => "null".to_string(),
525 },
526 _ => match language {
527 "python" => "None".to_string(),
528 "ruby" => "nil".to_string(),
529 "go" => "nil".to_string(),
530 "java" => "null".to_string(),
531 "csharp" => "null".to_string(),
532 "php" => "null".to_string(),
533 "r" => "NULL".to_string(),
534 "rust" => "Default::default()".to_string(),
535 _ => "null".to_string(),
536 },
537 }
538}
539
540trait TypeRefExt {
542 fn type_name(&self) -> String;
543}
544
545impl TypeRefExt for TypeRef {
546 fn type_name(&self) -> String {
547 match self {
548 TypeRef::Named(n) => n.clone(),
549 TypeRef::Primitive(p) => format!("{:?}", p),
550 TypeRef::String | TypeRef::Char => "String".to_string(),
551 TypeRef::Bytes => "Bytes".to_string(),
552 TypeRef::Optional(inner) => format!("Option<{}>", inner.type_name()),
553 TypeRef::Vec(inner) => format!("Vec<{}>", inner.type_name()),
554 TypeRef::Map(k, v) => format!("Map<{}, {}>", k.type_name(), v.type_name()),
555 TypeRef::Path => "Path".to_string(),
556 TypeRef::Unit => "()".to_string(),
557 TypeRef::Json => "Json".to_string(),
558 TypeRef::Duration => "Duration".to_string(),
559 }
560 }
561}
562
563const MAGNUS_MAX_ARITY: usize = 15;
565
566pub fn gen_magnus_kwargs_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
572 if typ.fields.len() > MAGNUS_MAX_ARITY {
573 gen_magnus_hash_constructor(typ, type_mapper)
574 } else {
575 gen_magnus_positional_constructor(typ, type_mapper)
576 }
577}
578
579fn as_type_path_prefix(type_str: &str) -> String {
586 if type_str.contains('<') {
587 format!("<{type_str}>")
588 } else {
589 type_str.to_string()
590 }
591}
592
593fn gen_magnus_hash_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
596 use std::fmt::Write;
597 let mut out = String::with_capacity(1024);
598
599 writeln!(out, "fn new(kwargs: magnus::RHash) -> Result<Self, magnus::Error> {{").ok();
600 writeln!(out, " let ruby = unsafe {{ magnus::Ruby::get_unchecked() }};").ok();
601 writeln!(out, " Ok(Self {{").ok();
602
603 for field in &typ.fields {
604 let is_optional = field_is_optional_in_rust(field);
605 let effective_inner_ty = match &field.ty {
609 TypeRef::Optional(inner) if is_optional => inner.as_ref(),
610 ty => ty,
611 };
612 let inner_type = type_mapper(effective_inner_ty);
613 let type_prefix = as_type_path_prefix(&inner_type);
614 if is_optional {
615 writeln!(
617 out,
618 " {name}: kwargs.get(ruby.to_symbol(\"{name}\")).and_then(|v| {type_prefix}::try_convert(v).ok()),",
619 name = field.name,
620 type_prefix = type_prefix,
621 ).ok();
622 } else if use_unwrap_or_default(field) {
623 writeln!(
624 out,
625 " {name}: kwargs.get(ruby.to_symbol(\"{name}\")).and_then(|v| {type_prefix}::try_convert(v).ok()).unwrap_or_default(),",
626 name = field.name,
627 type_prefix = type_prefix,
628 ).ok();
629 } else {
630 let default_str = if inner_type == "String" {
635 if let Some(DefaultValue::EnumVariant(variant)) = &field.typed_default {
636 use heck::ToSnakeCase;
637 format!("\"{}\".to_string()", variant.to_snake_case())
638 } else {
639 default_value_for_field(field, "rust")
640 }
641 } else {
642 default_value_for_field(field, "rust")
643 };
644 writeln!(
645 out,
646 " {name}: kwargs.get(ruby.to_symbol(\"{name}\")).and_then(|v| {type_prefix}::try_convert(v).ok()).unwrap_or({default}),",
647 name = field.name,
648 type_prefix = type_prefix,
649 default = default_str,
650 ).ok();
651 }
652 }
653
654 writeln!(out, " }})").ok();
655 writeln!(out, "}}").ok();
656
657 out
658}
659
660fn field_is_optional_in_rust(field: &FieldDef) -> bool {
665 field.optional || matches!(&field.ty, TypeRef::Optional(_))
666}
667
668fn gen_magnus_positional_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, "fn new(").ok();
675
676 for (i, field) in typ.fields.iter().enumerate() {
680 let comma = if i < typ.fields.len() - 1 { "," } else { "" };
681 let is_optional = field_is_optional_in_rust(field);
682 if is_optional {
683 let effective_inner_ty = match &field.ty {
687 TypeRef::Optional(inner) => inner.as_ref(),
688 ty => ty,
689 };
690 let inner_type = type_mapper(effective_inner_ty);
691 writeln!(out, " {}: Option<{}>{}", field.name, inner_type, comma).ok();
692 } else {
693 let field_type = type_mapper(&field.ty);
694 writeln!(out, " {}: Option<{}>{}", field.name, field_type, comma).ok();
695 }
696 }
697
698 writeln!(out, ") -> Self {{").ok();
699 writeln!(out, " Self {{").ok();
700
701 for field in &typ.fields {
702 let is_optional = field_is_optional_in_rust(field);
703 if is_optional {
704 writeln!(out, " {},", field.name).ok();
706 } else if use_unwrap_or_default(field) {
707 writeln!(out, " {}: {}.unwrap_or_default(),", field.name, field.name).ok();
708 } else {
709 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_php_kwargs_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
729 use std::fmt::Write;
730 let mut out = String::with_capacity(512);
731
732 writeln!(out, "pub fn __construct(").ok();
733
734 for (i, field) in typ.fields.iter().enumerate() {
736 let mapped = type_mapper(&field.ty);
737 let comma = if i < typ.fields.len() - 1 { "," } else { "" };
738 writeln!(out, " {}: Option<{}>{}", field.name, mapped, comma).ok();
739 }
740
741 writeln!(out, ") -> Self {{").ok();
742 writeln!(out, " Self {{").ok();
743
744 for field in &typ.fields {
745 let is_optional_field = field.optional || matches!(&field.ty, TypeRef::Optional(_));
746 if is_optional_field {
747 writeln!(out, " {},", field.name).ok();
749 } else if use_unwrap_or_default(field) {
750 writeln!(out, " {}: {}.unwrap_or_default(),", field.name, field.name).ok();
752 } else {
753 let default_str = default_value_for_field(field, "rust");
755 writeln!(
756 out,
757 " {}: {}.unwrap_or({}),",
758 field.name, field.name, default_str
759 )
760 .ok();
761 }
762 }
763
764 writeln!(out, " }}").ok();
765 writeln!(out, "}}").ok();
766
767 out
768}
769
770pub fn gen_rustler_kwargs_constructor(typ: &TypeDef, _type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
773 use std::fmt::Write;
774 let mut out = String::with_capacity(512);
775
776 writeln!(
779 out,
780 "pub fn new(opts: std::collections::HashMap<String, rustler::Term>) -> Self {{"
781 )
782 .ok();
783 writeln!(out, " Self {{").ok();
784
785 for field in &typ.fields {
789 if field.optional {
790 writeln!(
792 out,
793 " {}: opts.get(\"{}\").and_then(|t| t.decode().ok()),",
794 field.name, field.name
795 )
796 .ok();
797 } else if use_unwrap_or_default(field) {
798 writeln!(
799 out,
800 " {}: opts.get(\"{}\").and_then(|t| t.decode().ok()).unwrap_or_default(),",
801 field.name, field.name
802 )
803 .ok();
804 } else {
805 let default_str = default_value_for_field(field, "rust");
806 let is_enum_variant_default = default_str.contains("::") || default_str.starts_with("\"");
809
810 if is_enum_variant_default && matches!(&field.ty, TypeRef::String | TypeRef::Char) {
811 writeln!(
813 out,
814 " {}: opts.get(\"{}\").and_then(|t| t.decode().ok()).unwrap_or_default(),",
815 field.name, field.name
816 )
817 .ok();
818 } else if matches!(&field.ty, TypeRef::Named(_)) {
819 writeln!(
822 out,
823 " {}: opts.get(\"{}\").and_then(|t| t.decode().ok()).unwrap_or_default(),",
824 field.name, field.name
825 )
826 .ok();
827 } else {
828 writeln!(
829 out,
830 " {}: opts.get(\"{}\").and_then(|t| t.decode().ok()).unwrap_or({}),",
831 field.name, field.name, default_str
832 )
833 .ok();
834 }
835 }
836 }
837
838 writeln!(out, " }}").ok();
839 writeln!(out, "}}").ok();
840
841 out
842}
843
844pub fn gen_extendr_kwargs_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
847 use std::fmt::Write;
848 let mut out = String::with_capacity(512);
849
850 writeln!(out, "#[extendr]").ok();
851 writeln!(out, "pub fn new_{}(", typ.name.to_lowercase()).ok();
852
853 for (i, field) in typ.fields.iter().enumerate() {
855 let field_type = type_mapper(&field.ty);
856 let default_str = default_value_for_field(field, "r");
857 let comma = if i < typ.fields.len() - 1 { "," } else { "" };
858 writeln!(out, " {}: {} = {}{}", field.name, field_type, default_str, comma).ok();
859 }
860
861 writeln!(out, ") -> {} {{", typ.name).ok();
862 writeln!(out, " {} {{", typ.name).ok();
863
864 for field in &typ.fields {
866 writeln!(out, " {},", field.name).ok();
867 }
868
869 writeln!(out, " }}").ok();
870 writeln!(out, "}}").ok();
871
872 out
873}
874
875#[cfg(test)]
876mod tests {
877 use super::*;
878 use alef_core::ir::{CoreWrapper, FieldDef, PrimitiveType, TypeRef};
879
880 fn make_test_type() -> TypeDef {
881 TypeDef {
882 name: "Config".to_string(),
883 rust_path: "my_crate::Config".to_string(),
884 original_rust_path: String::new(),
885 fields: vec![
886 FieldDef {
887 name: "timeout".to_string(),
888 ty: TypeRef::Primitive(PrimitiveType::U64),
889 optional: false,
890 default: Some("30".to_string()),
891 doc: "Timeout in seconds".to_string(),
892 sanitized: false,
893 is_boxed: false,
894 type_rust_path: None,
895 cfg: None,
896 typed_default: Some(DefaultValue::IntLiteral(30)),
897 core_wrapper: CoreWrapper::None,
898 vec_inner_core_wrapper: CoreWrapper::None,
899 newtype_wrapper: None,
900 },
901 FieldDef {
902 name: "enabled".to_string(),
903 ty: TypeRef::Primitive(PrimitiveType::Bool),
904 optional: false,
905 default: None,
906 doc: "Enable feature".to_string(),
907 sanitized: false,
908 is_boxed: false,
909 type_rust_path: None,
910 cfg: None,
911 typed_default: Some(DefaultValue::BoolLiteral(true)),
912 core_wrapper: CoreWrapper::None,
913 vec_inner_core_wrapper: CoreWrapper::None,
914 newtype_wrapper: None,
915 },
916 FieldDef {
917 name: "name".to_string(),
918 ty: TypeRef::String,
919 optional: false,
920 default: None,
921 doc: "Config name".to_string(),
922 sanitized: false,
923 is_boxed: false,
924 type_rust_path: None,
925 cfg: None,
926 typed_default: Some(DefaultValue::StringLiteral("default".to_string())),
927 core_wrapper: CoreWrapper::None,
928 vec_inner_core_wrapper: CoreWrapper::None,
929 newtype_wrapper: None,
930 },
931 ],
932 methods: vec![],
933 is_opaque: false,
934 is_clone: true,
935 is_copy: false,
936 doc: "Configuration type".to_string(),
937 cfg: None,
938 is_trait: false,
939 has_default: true,
940 has_stripped_cfg_fields: false,
941 is_return_type: false,
942 serde_rename_all: None,
943 has_serde: false,
944 super_traits: vec![],
945 }
946 }
947
948 #[test]
949 fn test_default_value_bool_true_python() {
950 let field = FieldDef {
951 name: "enabled".to_string(),
952 ty: TypeRef::Primitive(PrimitiveType::Bool),
953 optional: false,
954 default: None,
955 doc: String::new(),
956 sanitized: false,
957 is_boxed: false,
958 type_rust_path: None,
959 cfg: None,
960 typed_default: Some(DefaultValue::BoolLiteral(true)),
961 core_wrapper: CoreWrapper::None,
962 vec_inner_core_wrapper: CoreWrapper::None,
963 newtype_wrapper: None,
964 };
965 assert_eq!(default_value_for_field(&field, "python"), "True");
966 }
967
968 #[test]
969 fn test_default_value_bool_false_go() {
970 let field = FieldDef {
971 name: "enabled".to_string(),
972 ty: TypeRef::Primitive(PrimitiveType::Bool),
973 optional: false,
974 default: None,
975 doc: String::new(),
976 sanitized: false,
977 is_boxed: false,
978 type_rust_path: None,
979 cfg: None,
980 typed_default: Some(DefaultValue::BoolLiteral(false)),
981 core_wrapper: CoreWrapper::None,
982 vec_inner_core_wrapper: CoreWrapper::None,
983 newtype_wrapper: None,
984 };
985 assert_eq!(default_value_for_field(&field, "go"), "false");
986 }
987
988 #[test]
989 fn test_default_value_string_literal() {
990 let field = FieldDef {
991 name: "name".to_string(),
992 ty: TypeRef::String,
993 optional: false,
994 default: None,
995 doc: String::new(),
996 sanitized: false,
997 is_boxed: false,
998 type_rust_path: None,
999 cfg: None,
1000 typed_default: Some(DefaultValue::StringLiteral("hello".to_string())),
1001 core_wrapper: CoreWrapper::None,
1002 vec_inner_core_wrapper: CoreWrapper::None,
1003 newtype_wrapper: None,
1004 };
1005 assert_eq!(default_value_for_field(&field, "python"), "\"hello\"");
1006 assert_eq!(default_value_for_field(&field, "java"), "\"hello\"");
1007 }
1008
1009 #[test]
1010 fn test_default_value_int_literal() {
1011 let field = FieldDef {
1012 name: "timeout".to_string(),
1013 ty: TypeRef::Primitive(PrimitiveType::U64),
1014 optional: false,
1015 default: None,
1016 doc: String::new(),
1017 sanitized: false,
1018 is_boxed: false,
1019 type_rust_path: None,
1020 cfg: None,
1021 typed_default: Some(DefaultValue::IntLiteral(42)),
1022 core_wrapper: CoreWrapper::None,
1023 vec_inner_core_wrapper: CoreWrapper::None,
1024 newtype_wrapper: None,
1025 };
1026 let result = default_value_for_field(&field, "python");
1027 assert_eq!(result, "42");
1028 }
1029
1030 #[test]
1031 fn test_default_value_none() {
1032 let field = FieldDef {
1033 name: "maybe".to_string(),
1034 ty: TypeRef::Optional(Box::new(TypeRef::String)),
1035 optional: true,
1036 default: None,
1037 doc: String::new(),
1038 sanitized: false,
1039 is_boxed: false,
1040 type_rust_path: None,
1041 cfg: None,
1042 typed_default: Some(DefaultValue::None),
1043 core_wrapper: CoreWrapper::None,
1044 vec_inner_core_wrapper: CoreWrapper::None,
1045 newtype_wrapper: None,
1046 };
1047 assert_eq!(default_value_for_field(&field, "python"), "None");
1048 assert_eq!(default_value_for_field(&field, "go"), "nil");
1049 assert_eq!(default_value_for_field(&field, "java"), "null");
1050 assert_eq!(default_value_for_field(&field, "csharp"), "null");
1051 }
1052
1053 #[test]
1054 fn test_default_value_fallback_string() {
1055 let field = FieldDef {
1056 name: "name".to_string(),
1057 ty: TypeRef::String,
1058 optional: false,
1059 default: Some("\"custom\"".to_string()),
1060 doc: String::new(),
1061 sanitized: false,
1062 is_boxed: false,
1063 type_rust_path: None,
1064 cfg: None,
1065 typed_default: None,
1066 core_wrapper: CoreWrapper::None,
1067 vec_inner_core_wrapper: CoreWrapper::None,
1068 newtype_wrapper: None,
1069 };
1070 assert_eq!(default_value_for_field(&field, "python"), "\"custom\"");
1071 }
1072
1073 #[test]
1074 fn test_gen_pyo3_kwargs_constructor() {
1075 let typ = make_test_type();
1076 let output = gen_pyo3_kwargs_constructor(&typ, &|tr: &TypeRef| match tr {
1077 TypeRef::Primitive(p) => format!("{:?}", p),
1078 TypeRef::String | TypeRef::Char => "str".to_string(),
1079 _ => "Any".to_string(),
1080 });
1081
1082 assert!(output.contains("#[new]"));
1083 assert!(output.contains("#[pyo3(signature = ("));
1084 assert!(output.contains("timeout=30"));
1085 assert!(output.contains("enabled=True"));
1086 assert!(output.contains("name=\"default\""));
1087 assert!(output.contains("fn new("));
1088 }
1089
1090 #[test]
1091 fn test_gen_napi_defaults_constructor() {
1092 let typ = make_test_type();
1093 let output = gen_napi_defaults_constructor(&typ, &|tr: &TypeRef| match tr {
1094 TypeRef::Primitive(p) => format!("{:?}", p),
1095 TypeRef::String | TypeRef::Char => "String".to_string(),
1096 _ => "Value".to_string(),
1097 });
1098
1099 assert!(output.contains("pub fn new(mut env: napi::Env, obj: napi::Object)"));
1100 assert!(output.contains("timeout"));
1101 assert!(output.contains("enabled"));
1102 assert!(output.contains("name"));
1103 }
1104
1105 #[test]
1106 fn test_gen_go_functional_options() {
1107 let typ = make_test_type();
1108 let output = gen_go_functional_options(&typ, &|tr: &TypeRef| match tr {
1109 TypeRef::Primitive(p) => match p {
1110 PrimitiveType::U64 => "uint64".to_string(),
1111 PrimitiveType::Bool => "bool".to_string(),
1112 _ => "interface{}".to_string(),
1113 },
1114 TypeRef::String | TypeRef::Char => "string".to_string(),
1115 _ => "interface{}".to_string(),
1116 });
1117
1118 assert!(output.contains("type Config struct {"));
1119 assert!(output.contains("type ConfigOption func(*Config)"));
1120 assert!(output.contains("func WithConfigTimeout(val uint64) ConfigOption"));
1121 assert!(output.contains("func WithConfigEnabled(val bool) ConfigOption"));
1122 assert!(output.contains("func WithConfigName(val string) ConfigOption"));
1123 assert!(output.contains("func NewConfig(opts ...ConfigOption) *Config"));
1124 }
1125
1126 #[test]
1127 fn test_gen_java_builder() {
1128 let typ = make_test_type();
1129 let output = gen_java_builder(&typ, "dev.test", &|tr: &TypeRef| match tr {
1130 TypeRef::Primitive(p) => match p {
1131 PrimitiveType::U64 => "long".to_string(),
1132 PrimitiveType::Bool => "boolean".to_string(),
1133 _ => "int".to_string(),
1134 },
1135 TypeRef::String | TypeRef::Char => "String".to_string(),
1136 _ => "Object".to_string(),
1137 });
1138
1139 assert!(output.contains("package dev.test;"));
1140 assert!(output.contains("public class ConfigBuilder"));
1141 assert!(output.contains("withTimeout"));
1142 assert!(output.contains("withEnabled"));
1143 assert!(output.contains("withName"));
1144 assert!(output.contains("public Config build()"));
1145 }
1146
1147 #[test]
1148 fn test_gen_csharp_record() {
1149 let typ = make_test_type();
1150 let output = gen_csharp_record(&typ, "MyNamespace", &|tr: &TypeRef| match tr {
1151 TypeRef::Primitive(p) => match p {
1152 PrimitiveType::U64 => "ulong".to_string(),
1153 PrimitiveType::Bool => "bool".to_string(),
1154 _ => "int".to_string(),
1155 },
1156 TypeRef::String | TypeRef::Char => "string".to_string(),
1157 _ => "object".to_string(),
1158 });
1159
1160 assert!(output.contains("namespace MyNamespace;"));
1161 assert!(output.contains("public record Config"));
1162 assert!(output.contains("public ulong Timeout"));
1163 assert!(output.contains("public bool Enabled"));
1164 assert!(output.contains("public string Name"));
1165 assert!(output.contains("init;"));
1166 }
1167
1168 #[test]
1169 fn test_default_value_float_literal() {
1170 let field = FieldDef {
1171 name: "ratio".to_string(),
1172 ty: TypeRef::Primitive(PrimitiveType::F64),
1173 optional: false,
1174 default: None,
1175 doc: String::new(),
1176 sanitized: false,
1177 is_boxed: false,
1178 type_rust_path: None,
1179 cfg: None,
1180 typed_default: Some(DefaultValue::FloatLiteral(1.5)),
1181 core_wrapper: CoreWrapper::None,
1182 vec_inner_core_wrapper: CoreWrapper::None,
1183 newtype_wrapper: None,
1184 };
1185 let result = default_value_for_field(&field, "python");
1186 assert!(result.contains("1.5"));
1187 }
1188
1189 #[test]
1190 fn test_default_value_no_typed_no_default() {
1191 let field = FieldDef {
1192 name: "count".to_string(),
1193 ty: TypeRef::Primitive(PrimitiveType::U32),
1194 optional: false,
1195 default: None,
1196 doc: String::new(),
1197 sanitized: false,
1198 is_boxed: false,
1199 type_rust_path: None,
1200 cfg: None,
1201 typed_default: None,
1202 core_wrapper: CoreWrapper::None,
1203 vec_inner_core_wrapper: CoreWrapper::None,
1204 newtype_wrapper: None,
1205 };
1206 assert_eq!(default_value_for_field(&field, "python"), "0");
1208 assert_eq!(default_value_for_field(&field, "go"), "0");
1209 }
1210
1211 fn make_field(name: &str, ty: TypeRef) -> FieldDef {
1212 FieldDef {
1213 name: name.to_string(),
1214 ty,
1215 optional: false,
1216 default: None,
1217 doc: String::new(),
1218 sanitized: false,
1219 is_boxed: false,
1220 type_rust_path: None,
1221 cfg: None,
1222 typed_default: None,
1223 core_wrapper: CoreWrapper::None,
1224 vec_inner_core_wrapper: CoreWrapper::None,
1225 newtype_wrapper: None,
1226 }
1227 }
1228
1229 fn simple_type_mapper(tr: &TypeRef) -> String {
1230 match tr {
1231 TypeRef::Primitive(p) => match p {
1232 PrimitiveType::U64 => "u64".to_string(),
1233 PrimitiveType::Bool => "bool".to_string(),
1234 PrimitiveType::U32 => "u32".to_string(),
1235 _ => "i64".to_string(),
1236 },
1237 TypeRef::String | TypeRef::Char => "String".to_string(),
1238 TypeRef::Optional(inner) => format!("Option<{}>", simple_type_mapper(inner)),
1239 TypeRef::Vec(inner) => format!("Vec<{}>", simple_type_mapper(inner)),
1240 TypeRef::Named(n) => n.clone(),
1241 _ => "Value".to_string(),
1242 }
1243 }
1244
1245 #[test]
1250 fn test_default_value_bool_literal_ruby() {
1251 let field = FieldDef {
1252 name: "flag".to_string(),
1253 ty: TypeRef::Primitive(PrimitiveType::Bool),
1254 optional: false,
1255 default: None,
1256 doc: String::new(),
1257 sanitized: false,
1258 is_boxed: false,
1259 type_rust_path: None,
1260 cfg: None,
1261 typed_default: Some(DefaultValue::BoolLiteral(true)),
1262 core_wrapper: CoreWrapper::None,
1263 vec_inner_core_wrapper: CoreWrapper::None,
1264 newtype_wrapper: None,
1265 };
1266 assert_eq!(default_value_for_field(&field, "ruby"), "true");
1267 assert_eq!(default_value_for_field(&field, "php"), "true");
1268 assert_eq!(default_value_for_field(&field, "csharp"), "true");
1269 assert_eq!(default_value_for_field(&field, "java"), "true");
1270 assert_eq!(default_value_for_field(&field, "rust"), "true");
1271 }
1272
1273 #[test]
1274 fn test_default_value_bool_literal_r() {
1275 let field = FieldDef {
1276 name: "flag".to_string(),
1277 ty: TypeRef::Primitive(PrimitiveType::Bool),
1278 optional: false,
1279 default: None,
1280 doc: String::new(),
1281 sanitized: false,
1282 is_boxed: false,
1283 type_rust_path: None,
1284 cfg: None,
1285 typed_default: Some(DefaultValue::BoolLiteral(false)),
1286 core_wrapper: CoreWrapper::None,
1287 vec_inner_core_wrapper: CoreWrapper::None,
1288 newtype_wrapper: None,
1289 };
1290 assert_eq!(default_value_for_field(&field, "r"), "FALSE");
1291 }
1292
1293 #[test]
1294 fn test_default_value_string_literal_rust() {
1295 let field = FieldDef {
1296 name: "label".to_string(),
1297 ty: TypeRef::String,
1298 optional: false,
1299 default: None,
1300 doc: String::new(),
1301 sanitized: false,
1302 is_boxed: false,
1303 type_rust_path: None,
1304 cfg: None,
1305 typed_default: Some(DefaultValue::StringLiteral("hello".to_string())),
1306 core_wrapper: CoreWrapper::None,
1307 vec_inner_core_wrapper: CoreWrapper::None,
1308 newtype_wrapper: None,
1309 };
1310 assert_eq!(default_value_for_field(&field, "rust"), "\"hello\".to_string()");
1311 }
1312
1313 #[test]
1314 fn test_default_value_string_literal_escapes_quotes() {
1315 let field = FieldDef {
1316 name: "label".to_string(),
1317 ty: TypeRef::String,
1318 optional: false,
1319 default: None,
1320 doc: String::new(),
1321 sanitized: false,
1322 is_boxed: false,
1323 type_rust_path: None,
1324 cfg: None,
1325 typed_default: Some(DefaultValue::StringLiteral("say \"hi\"".to_string())),
1326 core_wrapper: CoreWrapper::None,
1327 vec_inner_core_wrapper: CoreWrapper::None,
1328 newtype_wrapper: None,
1329 };
1330 assert_eq!(default_value_for_field(&field, "python"), "\"say \\\"hi\\\"\"");
1331 }
1332
1333 #[test]
1334 fn test_default_value_float_literal_whole_number() {
1335 let field = FieldDef {
1337 name: "scale".to_string(),
1338 ty: TypeRef::Primitive(PrimitiveType::F32),
1339 optional: false,
1340 default: None,
1341 doc: String::new(),
1342 sanitized: false,
1343 is_boxed: false,
1344 type_rust_path: None,
1345 cfg: None,
1346 typed_default: Some(DefaultValue::FloatLiteral(2.0)),
1347 core_wrapper: CoreWrapper::None,
1348 vec_inner_core_wrapper: CoreWrapper::None,
1349 newtype_wrapper: None,
1350 };
1351 let result = default_value_for_field(&field, "python");
1352 assert!(result.contains('.'), "whole-number float should contain '.': {result}");
1353 }
1354
1355 #[test]
1356 fn test_default_value_enum_variant_per_language() {
1357 let field = FieldDef {
1358 name: "format".to_string(),
1359 ty: TypeRef::Named("OutputFormat".to_string()),
1360 optional: false,
1361 default: None,
1362 doc: String::new(),
1363 sanitized: false,
1364 is_boxed: false,
1365 type_rust_path: None,
1366 cfg: None,
1367 typed_default: Some(DefaultValue::EnumVariant("JsonOutput".to_string())),
1368 core_wrapper: CoreWrapper::None,
1369 vec_inner_core_wrapper: CoreWrapper::None,
1370 newtype_wrapper: None,
1371 };
1372 assert_eq!(default_value_for_field(&field, "python"), "OutputFormat.JSON_OUTPUT");
1373 assert_eq!(default_value_for_field(&field, "ruby"), "OutputFormat::JsonOutput");
1374 assert_eq!(default_value_for_field(&field, "go"), "OutputFormatJsonOutput");
1375 assert_eq!(default_value_for_field(&field, "java"), "OutputFormat.JSON_OUTPUT");
1376 assert_eq!(default_value_for_field(&field, "csharp"), "OutputFormat.JsonOutput");
1377 assert_eq!(default_value_for_field(&field, "php"), "OutputFormat::JsonOutput");
1378 assert_eq!(default_value_for_field(&field, "r"), "OutputFormat$JsonOutput");
1379 assert_eq!(default_value_for_field(&field, "rust"), "OutputFormat::JsonOutput");
1380 }
1381
1382 #[test]
1383 fn test_default_value_empty_vec_per_language() {
1384 let field = FieldDef {
1385 name: "items".to_string(),
1386 ty: TypeRef::Vec(Box::new(TypeRef::String)),
1387 optional: false,
1388 default: None,
1389 doc: String::new(),
1390 sanitized: false,
1391 is_boxed: false,
1392 type_rust_path: None,
1393 cfg: None,
1394 typed_default: Some(DefaultValue::Empty),
1395 core_wrapper: CoreWrapper::None,
1396 vec_inner_core_wrapper: CoreWrapper::None,
1397 newtype_wrapper: None,
1398 };
1399 assert_eq!(default_value_for_field(&field, "python"), "[]");
1400 assert_eq!(default_value_for_field(&field, "ruby"), "[]");
1401 assert_eq!(default_value_for_field(&field, "csharp"), "[]");
1402 assert_eq!(default_value_for_field(&field, "go"), "nil");
1403 assert_eq!(default_value_for_field(&field, "java"), "List.of()");
1404 assert_eq!(default_value_for_field(&field, "php"), "[]");
1405 assert_eq!(default_value_for_field(&field, "r"), "c()");
1406 assert_eq!(default_value_for_field(&field, "rust"), "vec![]");
1407 }
1408
1409 #[test]
1410 fn test_default_value_empty_map_per_language() {
1411 let field = FieldDef {
1412 name: "meta".to_string(),
1413 ty: TypeRef::Map(Box::new(TypeRef::String), Box::new(TypeRef::String)),
1414 optional: false,
1415 default: None,
1416 doc: String::new(),
1417 sanitized: false,
1418 is_boxed: false,
1419 type_rust_path: None,
1420 cfg: None,
1421 typed_default: Some(DefaultValue::Empty),
1422 core_wrapper: CoreWrapper::None,
1423 vec_inner_core_wrapper: CoreWrapper::None,
1424 newtype_wrapper: None,
1425 };
1426 assert_eq!(default_value_for_field(&field, "python"), "{}");
1427 assert_eq!(default_value_for_field(&field, "go"), "nil");
1428 assert_eq!(default_value_for_field(&field, "java"), "Map.of()");
1429 assert_eq!(default_value_for_field(&field, "rust"), "Default::default()");
1430 }
1431
1432 #[test]
1433 fn test_default_value_empty_bool_primitive() {
1434 let field = FieldDef {
1435 name: "flag".to_string(),
1436 ty: TypeRef::Primitive(PrimitiveType::Bool),
1437 optional: false,
1438 default: None,
1439 doc: String::new(),
1440 sanitized: false,
1441 is_boxed: false,
1442 type_rust_path: None,
1443 cfg: None,
1444 typed_default: Some(DefaultValue::Empty),
1445 core_wrapper: CoreWrapper::None,
1446 vec_inner_core_wrapper: CoreWrapper::None,
1447 newtype_wrapper: None,
1448 };
1449 assert_eq!(default_value_for_field(&field, "python"), "False");
1450 assert_eq!(default_value_for_field(&field, "ruby"), "false");
1451 assert_eq!(default_value_for_field(&field, "go"), "false");
1452 }
1453
1454 #[test]
1455 fn test_default_value_empty_float_primitive() {
1456 let field = FieldDef {
1457 name: "ratio".to_string(),
1458 ty: TypeRef::Primitive(PrimitiveType::F64),
1459 optional: false,
1460 default: None,
1461 doc: String::new(),
1462 sanitized: false,
1463 is_boxed: false,
1464 type_rust_path: None,
1465 cfg: None,
1466 typed_default: Some(DefaultValue::Empty),
1467 core_wrapper: CoreWrapper::None,
1468 vec_inner_core_wrapper: CoreWrapper::None,
1469 newtype_wrapper: None,
1470 };
1471 assert_eq!(default_value_for_field(&field, "python"), "0.0");
1472 }
1473
1474 #[test]
1475 fn test_default_value_empty_string_type() {
1476 let field = FieldDef {
1477 name: "label".to_string(),
1478 ty: TypeRef::String,
1479 optional: false,
1480 default: None,
1481 doc: String::new(),
1482 sanitized: false,
1483 is_boxed: false,
1484 type_rust_path: None,
1485 cfg: None,
1486 typed_default: Some(DefaultValue::Empty),
1487 core_wrapper: CoreWrapper::None,
1488 vec_inner_core_wrapper: CoreWrapper::None,
1489 newtype_wrapper: None,
1490 };
1491 assert_eq!(default_value_for_field(&field, "rust"), "String::new()");
1492 assert_eq!(default_value_for_field(&field, "python"), "\"\"");
1493 }
1494
1495 #[test]
1496 fn test_default_value_empty_bytes_type() {
1497 let field = FieldDef {
1498 name: "data".to_string(),
1499 ty: TypeRef::Bytes,
1500 optional: false,
1501 default: None,
1502 doc: String::new(),
1503 sanitized: false,
1504 is_boxed: false,
1505 type_rust_path: None,
1506 cfg: None,
1507 typed_default: Some(DefaultValue::Empty),
1508 core_wrapper: CoreWrapper::None,
1509 vec_inner_core_wrapper: CoreWrapper::None,
1510 newtype_wrapper: None,
1511 };
1512 assert_eq!(default_value_for_field(&field, "python"), "b\"\"");
1513 assert_eq!(default_value_for_field(&field, "go"), "[]byte{}");
1514 assert_eq!(default_value_for_field(&field, "rust"), "vec![]");
1515 }
1516
1517 #[test]
1518 fn test_default_value_empty_json_type() {
1519 let field = FieldDef {
1520 name: "payload".to_string(),
1521 ty: TypeRef::Json,
1522 optional: false,
1523 default: None,
1524 doc: String::new(),
1525 sanitized: false,
1526 is_boxed: false,
1527 type_rust_path: None,
1528 cfg: None,
1529 typed_default: Some(DefaultValue::Empty),
1530 core_wrapper: CoreWrapper::None,
1531 vec_inner_core_wrapper: CoreWrapper::None,
1532 newtype_wrapper: None,
1533 };
1534 assert_eq!(default_value_for_field(&field, "python"), "{}");
1535 assert_eq!(default_value_for_field(&field, "ruby"), "{}");
1536 assert_eq!(default_value_for_field(&field, "go"), "json.RawMessage(nil)");
1537 assert_eq!(default_value_for_field(&field, "r"), "list()");
1538 assert_eq!(default_value_for_field(&field, "rust"), "serde_json::json!({})");
1539 }
1540
1541 #[test]
1542 fn test_default_value_none_ruby_php_r() {
1543 let field = FieldDef {
1544 name: "maybe".to_string(),
1545 ty: TypeRef::Optional(Box::new(TypeRef::String)),
1546 optional: true,
1547 default: None,
1548 doc: String::new(),
1549 sanitized: false,
1550 is_boxed: false,
1551 type_rust_path: None,
1552 cfg: None,
1553 typed_default: Some(DefaultValue::None),
1554 core_wrapper: CoreWrapper::None,
1555 vec_inner_core_wrapper: CoreWrapper::None,
1556 newtype_wrapper: None,
1557 };
1558 assert_eq!(default_value_for_field(&field, "ruby"), "nil");
1559 assert_eq!(default_value_for_field(&field, "php"), "null");
1560 assert_eq!(default_value_for_field(&field, "r"), "NULL");
1561 assert_eq!(default_value_for_field(&field, "rust"), "None");
1562 }
1563
1564 #[test]
1569 fn test_default_value_fallback_bool_all_languages() {
1570 let field = make_field("flag", TypeRef::Primitive(PrimitiveType::Bool));
1571 assert_eq!(default_value_for_field(&field, "python"), "False");
1572 assert_eq!(default_value_for_field(&field, "ruby"), "false");
1573 assert_eq!(default_value_for_field(&field, "csharp"), "false");
1574 assert_eq!(default_value_for_field(&field, "java"), "false");
1575 assert_eq!(default_value_for_field(&field, "php"), "false");
1576 assert_eq!(default_value_for_field(&field, "r"), "FALSE");
1577 assert_eq!(default_value_for_field(&field, "rust"), "false");
1578 }
1579
1580 #[test]
1581 fn test_default_value_fallback_float() {
1582 let field = make_field("ratio", TypeRef::Primitive(PrimitiveType::F64));
1583 assert_eq!(default_value_for_field(&field, "python"), "0.0");
1584 assert_eq!(default_value_for_field(&field, "rust"), "0.0");
1585 }
1586
1587 #[test]
1588 fn test_default_value_fallback_string_all_languages() {
1589 let field = make_field("name", TypeRef::String);
1590 assert_eq!(default_value_for_field(&field, "python"), "\"\"");
1591 assert_eq!(default_value_for_field(&field, "ruby"), "\"\"");
1592 assert_eq!(default_value_for_field(&field, "go"), "\"\"");
1593 assert_eq!(default_value_for_field(&field, "java"), "\"\"");
1594 assert_eq!(default_value_for_field(&field, "csharp"), "\"\"");
1595 assert_eq!(default_value_for_field(&field, "php"), "\"\"");
1596 assert_eq!(default_value_for_field(&field, "r"), "\"\"");
1597 assert_eq!(default_value_for_field(&field, "rust"), "String::new()");
1598 }
1599
1600 #[test]
1601 fn test_default_value_fallback_bytes_all_languages() {
1602 let field = make_field("data", TypeRef::Bytes);
1603 assert_eq!(default_value_for_field(&field, "python"), "b\"\"");
1604 assert_eq!(default_value_for_field(&field, "ruby"), "\"\"");
1605 assert_eq!(default_value_for_field(&field, "go"), "[]byte{}");
1606 assert_eq!(default_value_for_field(&field, "java"), "new byte[]{}");
1607 assert_eq!(default_value_for_field(&field, "csharp"), "new byte[]{}");
1608 assert_eq!(default_value_for_field(&field, "php"), "\"\"");
1609 assert_eq!(default_value_for_field(&field, "r"), "raw()");
1610 assert_eq!(default_value_for_field(&field, "rust"), "vec![]");
1611 }
1612
1613 #[test]
1614 fn test_default_value_fallback_optional() {
1615 let field = make_field("maybe", TypeRef::Optional(Box::new(TypeRef::String)));
1616 assert_eq!(default_value_for_field(&field, "python"), "None");
1617 assert_eq!(default_value_for_field(&field, "ruby"), "nil");
1618 assert_eq!(default_value_for_field(&field, "go"), "nil");
1619 assert_eq!(default_value_for_field(&field, "java"), "null");
1620 assert_eq!(default_value_for_field(&field, "csharp"), "null");
1621 assert_eq!(default_value_for_field(&field, "php"), "null");
1622 assert_eq!(default_value_for_field(&field, "r"), "NULL");
1623 assert_eq!(default_value_for_field(&field, "rust"), "None");
1624 }
1625
1626 #[test]
1627 fn test_default_value_fallback_vec_all_languages() {
1628 let field = make_field("items", TypeRef::Vec(Box::new(TypeRef::String)));
1629 assert_eq!(default_value_for_field(&field, "python"), "[]");
1630 assert_eq!(default_value_for_field(&field, "ruby"), "[]");
1631 assert_eq!(default_value_for_field(&field, "go"), "[]interface{}{}");
1632 assert_eq!(default_value_for_field(&field, "java"), "new java.util.ArrayList<>()");
1633 assert_eq!(default_value_for_field(&field, "csharp"), "[]");
1634 assert_eq!(default_value_for_field(&field, "php"), "[]");
1635 assert_eq!(default_value_for_field(&field, "r"), "c()");
1636 assert_eq!(default_value_for_field(&field, "rust"), "vec![]");
1637 }
1638
1639 #[test]
1640 fn test_default_value_fallback_map_all_languages() {
1641 let field = make_field(
1642 "meta",
1643 TypeRef::Map(Box::new(TypeRef::String), Box::new(TypeRef::String)),
1644 );
1645 assert_eq!(default_value_for_field(&field, "python"), "{}");
1646 assert_eq!(default_value_for_field(&field, "ruby"), "{}");
1647 assert_eq!(default_value_for_field(&field, "go"), "make(map[string]interface{})");
1648 assert_eq!(default_value_for_field(&field, "java"), "new java.util.HashMap<>()");
1649 assert_eq!(
1650 default_value_for_field(&field, "csharp"),
1651 "new Dictionary<string, object>()"
1652 );
1653 assert_eq!(default_value_for_field(&field, "php"), "[]");
1654 assert_eq!(default_value_for_field(&field, "r"), "list()");
1655 assert_eq!(
1656 default_value_for_field(&field, "rust"),
1657 "std::collections::HashMap::new()"
1658 );
1659 }
1660
1661 #[test]
1662 fn test_default_value_fallback_json_all_languages() {
1663 let field = make_field("payload", TypeRef::Json);
1664 assert_eq!(default_value_for_field(&field, "python"), "{}");
1665 assert_eq!(default_value_for_field(&field, "ruby"), "{}");
1666 assert_eq!(default_value_for_field(&field, "go"), "json.RawMessage(nil)");
1667 assert_eq!(default_value_for_field(&field, "r"), "list()");
1668 assert_eq!(default_value_for_field(&field, "rust"), "serde_json::json!({})");
1669 }
1670
1671 #[test]
1672 fn test_default_value_fallback_named_type() {
1673 let field = make_field("config", TypeRef::Named("MyConfig".to_string()));
1674 assert_eq!(default_value_for_field(&field, "rust"), "MyConfig::default()");
1675 assert_eq!(default_value_for_field(&field, "python"), "None");
1676 assert_eq!(default_value_for_field(&field, "ruby"), "nil");
1677 assert_eq!(default_value_for_field(&field, "go"), "nil");
1678 assert_eq!(default_value_for_field(&field, "java"), "null");
1679 assert_eq!(default_value_for_field(&field, "csharp"), "null");
1680 assert_eq!(default_value_for_field(&field, "php"), "null");
1681 assert_eq!(default_value_for_field(&field, "r"), "NULL");
1682 }
1683
1684 #[test]
1685 fn test_default_value_fallback_duration() {
1686 let field = make_field("timeout", TypeRef::Duration);
1688 assert_eq!(default_value_for_field(&field, "python"), "None");
1689 assert_eq!(default_value_for_field(&field, "rust"), "Default::default()");
1690 }
1691
1692 #[test]
1697 fn test_gen_magnus_kwargs_constructor_positional_basic() {
1698 let typ = make_test_type();
1699 let output = gen_magnus_kwargs_constructor(&typ, &simple_type_mapper);
1700
1701 assert!(output.contains("fn new("), "should have fn new");
1702 assert!(output.contains("Option<u64>"), "timeout should be Option<u64>");
1704 assert!(output.contains("Option<bool>"), "enabled should be Option<bool>");
1705 assert!(output.contains("Option<String>"), "name should be Option<String>");
1706 assert!(output.contains("-> Self {"), "should return Self");
1707 assert!(
1709 output.contains("timeout: timeout.unwrap_or(30),"),
1710 "should apply int default"
1711 );
1712 assert!(
1714 output.contains("enabled: enabled.unwrap_or(true),"),
1715 "should apply bool default"
1716 );
1717 assert!(
1719 output.contains("name: name.unwrap_or(\"default\".to_string()),"),
1720 "should apply string default"
1721 );
1722 }
1723
1724 #[test]
1725 fn test_gen_magnus_kwargs_constructor_positional_optional_field() {
1726 let mut typ = make_test_type();
1728 typ.fields.push(FieldDef {
1729 name: "extra".to_string(),
1730 ty: TypeRef::String,
1731 optional: true,
1732 default: None,
1733 doc: String::new(),
1734 sanitized: false,
1735 is_boxed: false,
1736 type_rust_path: None,
1737 cfg: None,
1738 typed_default: None,
1739 core_wrapper: CoreWrapper::None,
1740 vec_inner_core_wrapper: CoreWrapper::None,
1741 newtype_wrapper: None,
1742 });
1743 let output = gen_magnus_kwargs_constructor(&typ, &simple_type_mapper);
1744 assert!(output.contains("extra,"), "optional field should be assigned directly");
1746 assert!(!output.contains("extra.unwrap"), "optional field should not use unwrap");
1747 }
1748
1749 #[test]
1750 fn test_gen_magnus_kwargs_constructor_unwrap_or_default() {
1751 let mut typ = make_test_type();
1753 typ.fields.push(FieldDef {
1754 name: "count".to_string(),
1755 ty: TypeRef::Primitive(PrimitiveType::U32),
1756 optional: false,
1757 default: None,
1758 doc: String::new(),
1759 sanitized: false,
1760 is_boxed: false,
1761 type_rust_path: None,
1762 cfg: None,
1763 typed_default: None,
1764 core_wrapper: CoreWrapper::None,
1765 vec_inner_core_wrapper: CoreWrapper::None,
1766 newtype_wrapper: None,
1767 });
1768 let output = gen_magnus_kwargs_constructor(&typ, &simple_type_mapper);
1769 assert!(
1770 output.contains("count: count.unwrap_or_default(),"),
1771 "plain primitive with no default should use unwrap_or_default"
1772 );
1773 }
1774
1775 #[test]
1776 fn test_gen_magnus_kwargs_constructor_hash_path_for_many_fields() {
1777 let mut fields: Vec<FieldDef> = (0..16)
1779 .map(|i| FieldDef {
1780 name: format!("field_{i}"),
1781 ty: TypeRef::Primitive(PrimitiveType::U32),
1782 optional: false,
1783 default: None,
1784 doc: String::new(),
1785 sanitized: false,
1786 is_boxed: false,
1787 type_rust_path: None,
1788 cfg: None,
1789 typed_default: None,
1790 core_wrapper: CoreWrapper::None,
1791 vec_inner_core_wrapper: CoreWrapper::None,
1792 newtype_wrapper: None,
1793 })
1794 .collect();
1795 fields[0].optional = true;
1797
1798 let typ = TypeDef {
1799 name: "BigConfig".to_string(),
1800 rust_path: "crate::BigConfig".to_string(),
1801 original_rust_path: String::new(),
1802 fields,
1803 methods: vec![],
1804 is_opaque: false,
1805 is_clone: true,
1806 is_copy: false,
1807 doc: String::new(),
1808 cfg: None,
1809 is_trait: false,
1810 has_default: true,
1811 has_stripped_cfg_fields: false,
1812 is_return_type: false,
1813 serde_rename_all: None,
1814 has_serde: false,
1815 super_traits: vec![],
1816 };
1817 let output = gen_magnus_kwargs_constructor(&typ, &simple_type_mapper);
1818
1819 assert!(output.contains("kwargs: magnus::RHash"), "should accept RHash");
1820 assert!(output.contains("ruby.to_symbol("), "should use symbol lookup");
1821 assert!(
1823 output.contains("field_0: kwargs.get(ruby.to_symbol(\"field_0\")).and_then(|v|"),
1824 "optional field should use and_then"
1825 );
1826 assert!(
1827 output.contains("field_0:").then_some(()).is_some(),
1828 "field_0 should appear in output"
1829 );
1830 }
1831
1832 #[test]
1837 fn test_gen_php_kwargs_constructor_basic() {
1838 let typ = make_test_type();
1839 let output = gen_php_kwargs_constructor(&typ, &simple_type_mapper);
1840
1841 assert!(
1842 output.contains("pub fn __construct("),
1843 "should use PHP constructor name"
1844 );
1845 assert!(
1847 output.contains("timeout: Option<u64>"),
1848 "timeout param should be Option<u64>"
1849 );
1850 assert!(
1851 output.contains("enabled: Option<bool>"),
1852 "enabled param should be Option<bool>"
1853 );
1854 assert!(
1855 output.contains("name: Option<String>"),
1856 "name param should be Option<String>"
1857 );
1858 assert!(output.contains("-> Self {"), "should return Self");
1859 assert!(
1860 output.contains("timeout: timeout.unwrap_or(30),"),
1861 "should apply int default for timeout"
1862 );
1863 assert!(
1864 output.contains("enabled: enabled.unwrap_or(true),"),
1865 "should apply bool default for enabled"
1866 );
1867 assert!(
1868 output.contains("name: name.unwrap_or(\"default\".to_string()),"),
1869 "should apply string default for name"
1870 );
1871 }
1872
1873 #[test]
1874 fn test_gen_php_kwargs_constructor_optional_field_passthrough() {
1875 let mut typ = make_test_type();
1876 typ.fields.push(FieldDef {
1877 name: "tag".to_string(),
1878 ty: TypeRef::String,
1879 optional: true,
1880 default: None,
1881 doc: String::new(),
1882 sanitized: false,
1883 is_boxed: false,
1884 type_rust_path: None,
1885 cfg: None,
1886 typed_default: None,
1887 core_wrapper: CoreWrapper::None,
1888 vec_inner_core_wrapper: CoreWrapper::None,
1889 newtype_wrapper: None,
1890 });
1891 let output = gen_php_kwargs_constructor(&typ, &simple_type_mapper);
1892 assert!(
1893 output.contains("tag,"),
1894 "optional field should be passed through directly"
1895 );
1896 assert!(!output.contains("tag.unwrap"), "optional field should not call unwrap");
1897 }
1898
1899 #[test]
1900 fn test_gen_php_kwargs_constructor_unwrap_or_default_for_primitive() {
1901 let mut typ = make_test_type();
1902 typ.fields.push(FieldDef {
1903 name: "retries".to_string(),
1904 ty: TypeRef::Primitive(PrimitiveType::U32),
1905 optional: false,
1906 default: None,
1907 doc: String::new(),
1908 sanitized: false,
1909 is_boxed: false,
1910 type_rust_path: None,
1911 cfg: None,
1912 typed_default: None,
1913 core_wrapper: CoreWrapper::None,
1914 vec_inner_core_wrapper: CoreWrapper::None,
1915 newtype_wrapper: None,
1916 });
1917 let output = gen_php_kwargs_constructor(&typ, &simple_type_mapper);
1918 assert!(
1919 output.contains("retries: retries.unwrap_or_default(),"),
1920 "primitive with no default should use unwrap_or_default"
1921 );
1922 }
1923
1924 #[test]
1929 fn test_gen_rustler_kwargs_constructor_basic() {
1930 let typ = make_test_type();
1931 let output = gen_rustler_kwargs_constructor(&typ, &simple_type_mapper);
1932
1933 assert!(
1934 output.contains("pub fn new(opts: std::collections::HashMap<String, rustler::Term>)"),
1935 "should accept HashMap of Terms"
1936 );
1937 assert!(output.contains("Self {"), "should construct Self");
1938 assert!(
1940 output.contains("timeout: opts.get(\"timeout\").and_then(|t| t.decode().ok()).unwrap_or(30),"),
1941 "should apply int default for timeout"
1942 );
1943 assert!(
1945 output.contains("enabled: opts.get(\"enabled\").and_then(|t| t.decode().ok()).unwrap_or(true),"),
1946 "should apply bool default for enabled"
1947 );
1948 }
1949
1950 #[test]
1951 fn test_gen_rustler_kwargs_constructor_optional_field() {
1952 let mut typ = make_test_type();
1953 typ.fields.push(FieldDef {
1954 name: "extra".to_string(),
1955 ty: TypeRef::String,
1956 optional: true,
1957 default: None,
1958 doc: String::new(),
1959 sanitized: false,
1960 is_boxed: false,
1961 type_rust_path: None,
1962 cfg: None,
1963 typed_default: None,
1964 core_wrapper: CoreWrapper::None,
1965 vec_inner_core_wrapper: CoreWrapper::None,
1966 newtype_wrapper: None,
1967 });
1968 let output = gen_rustler_kwargs_constructor(&typ, &simple_type_mapper);
1969 assert!(
1970 output.contains("extra: opts.get(\"extra\").and_then(|t| t.decode().ok()),"),
1971 "optional field should decode without unwrap"
1972 );
1973 }
1974
1975 #[test]
1976 fn test_gen_rustler_kwargs_constructor_named_type_uses_unwrap_or_default() {
1977 let mut typ = make_test_type();
1978 typ.fields.push(FieldDef {
1979 name: "inner".to_string(),
1980 ty: TypeRef::Named("InnerConfig".to_string()),
1981 optional: false,
1982 default: None,
1983 doc: String::new(),
1984 sanitized: false,
1985 is_boxed: false,
1986 type_rust_path: None,
1987 cfg: None,
1988 typed_default: None,
1989 core_wrapper: CoreWrapper::None,
1990 vec_inner_core_wrapper: CoreWrapper::None,
1991 newtype_wrapper: None,
1992 });
1993 let output = gen_rustler_kwargs_constructor(&typ, &simple_type_mapper);
1994 assert!(
1995 output.contains("inner: opts.get(\"inner\").and_then(|t| t.decode().ok()).unwrap_or_default(),"),
1996 "Named type with no default should use unwrap_or_default"
1997 );
1998 }
1999
2000 #[test]
2001 fn test_gen_rustler_kwargs_constructor_string_field_uses_unwrap_or_default() {
2002 let mut typ = make_test_type();
2005 let output = gen_rustler_kwargs_constructor(&typ, &simple_type_mapper);
2007 assert!(
2008 output.contains("name: opts.get(\"name\").and_then(|t| t.decode().ok()).unwrap_or_default(),"),
2009 "String field with quoted default should use unwrap_or_default"
2010 );
2011 typ.fields.push(FieldDef {
2013 name: "label".to_string(),
2014 ty: TypeRef::String,
2015 optional: false,
2016 default: None,
2017 doc: String::new(),
2018 sanitized: false,
2019 is_boxed: false,
2020 type_rust_path: None,
2021 cfg: None,
2022 typed_default: None,
2023 core_wrapper: CoreWrapper::None,
2024 vec_inner_core_wrapper: CoreWrapper::None,
2025 newtype_wrapper: None,
2026 });
2027 let output2 = gen_rustler_kwargs_constructor(&typ, &simple_type_mapper);
2028 assert!(
2029 output2.contains("label: opts.get(\"label\").and_then(|t| t.decode().ok()).unwrap_or_default(),"),
2030 "String field with no default should use unwrap_or_default"
2031 );
2032 }
2033
2034 #[test]
2039 fn test_gen_extendr_kwargs_constructor_basic() {
2040 let typ = make_test_type();
2041 let output = gen_extendr_kwargs_constructor(&typ, &simple_type_mapper);
2042
2043 assert!(output.contains("#[extendr]"), "should have extendr attribute");
2044 assert!(
2045 output.contains("pub fn new_config("),
2046 "function name should be lowercase type name"
2047 );
2048 assert!(
2050 output.contains("timeout: u64 = 30"),
2051 "should include timeout with default"
2052 );
2053 assert!(
2055 output.contains("enabled: bool = TRUE"),
2056 "should include enabled with R bool default"
2057 );
2058 assert!(
2059 output.contains("name: String = \"default\""),
2060 "should include name with default"
2061 );
2062 assert!(output.contains("-> Config {"), "should return Config");
2063 assert!(output.contains("Config {"), "should construct Config");
2064 assert!(output.contains("timeout,"), "should include timeout in struct literal");
2065 assert!(output.contains("enabled,"), "should include enabled in struct literal");
2066 assert!(output.contains("name,"), "should include name in struct literal");
2067 }
2068
2069 #[test]
2070 fn test_gen_extendr_kwargs_constructor_r_bool_default() {
2071 let mut typ = make_test_type();
2073 typ.fields.retain(|f| f.name != "enabled");
2075 typ.fields.push(FieldDef {
2076 name: "verbose".to_string(),
2077 ty: TypeRef::Primitive(PrimitiveType::Bool),
2078 optional: false,
2079 default: None,
2080 doc: String::new(),
2081 sanitized: false,
2082 is_boxed: false,
2083 type_rust_path: None,
2084 cfg: None,
2085 typed_default: None,
2086 core_wrapper: CoreWrapper::None,
2087 vec_inner_core_wrapper: CoreWrapper::None,
2088 newtype_wrapper: None,
2089 });
2090 let output = gen_extendr_kwargs_constructor(&typ, &simple_type_mapper);
2091 assert!(
2092 output.contains("verbose: bool = FALSE"),
2093 "R bool default should be FALSE: {output}"
2094 );
2095 }
2096
2097 #[test]
2102 fn test_gen_go_functional_options_skips_tuple_fields() {
2103 let mut typ = make_test_type();
2104 typ.fields.push(FieldDef {
2105 name: "_0".to_string(),
2106 ty: TypeRef::Primitive(PrimitiveType::U32),
2107 optional: false,
2108 default: None,
2109 doc: String::new(),
2110 sanitized: false,
2111 is_boxed: false,
2112 type_rust_path: None,
2113 cfg: None,
2114 typed_default: None,
2115 core_wrapper: CoreWrapper::None,
2116 vec_inner_core_wrapper: CoreWrapper::None,
2117 newtype_wrapper: None,
2118 });
2119 let output = gen_go_functional_options(&typ, &simple_type_mapper);
2120 assert!(
2121 !output.contains("_0"),
2122 "tuple field _0 should be filtered out from Go output"
2123 );
2124 }
2125
2126 #[test]
2131 fn test_gen_magnus_hash_constructor_generic_type_prefix() {
2132 let fields: Vec<FieldDef> = (0..16)
2134 .map(|i| FieldDef {
2135 name: format!("field_{i}"),
2136 ty: if i == 0 {
2137 TypeRef::Vec(Box::new(TypeRef::String))
2138 } else {
2139 TypeRef::Primitive(PrimitiveType::U32)
2140 },
2141 optional: false,
2142 default: None,
2143 doc: String::new(),
2144 sanitized: false,
2145 is_boxed: false,
2146 type_rust_path: None,
2147 cfg: None,
2148 typed_default: None,
2149 core_wrapper: CoreWrapper::None,
2150 vec_inner_core_wrapper: CoreWrapper::None,
2151 newtype_wrapper: None,
2152 })
2153 .collect();
2154 let typ = TypeDef {
2155 name: "WideConfig".to_string(),
2156 rust_path: "crate::WideConfig".to_string(),
2157 original_rust_path: String::new(),
2158 fields,
2159 methods: vec![],
2160 is_opaque: false,
2161 is_clone: true,
2162 is_copy: false,
2163 doc: String::new(),
2164 cfg: None,
2165 is_trait: false,
2166 has_default: true,
2167 has_stripped_cfg_fields: false,
2168 is_return_type: false,
2169 serde_rename_all: None,
2170 has_serde: false,
2171 super_traits: vec![],
2172 };
2173 let output = gen_magnus_kwargs_constructor(&typ, &simple_type_mapper);
2174 assert!(
2176 output.contains("<Vec<String>>::try_convert"),
2177 "generic types should use UFCS angle-bracket prefix: {output}"
2178 );
2179 }
2180
2181 #[test]
2188 fn test_magnus_hash_constructor_no_double_option_when_ty_is_optional() {
2189 let field = FieldDef {
2193 name: "max_depth".to_string(),
2194 ty: TypeRef::Optional(Box::new(TypeRef::Primitive(PrimitiveType::Usize))),
2195 optional: true,
2196 default: None,
2197 doc: String::new(),
2198 sanitized: false,
2199 is_boxed: false,
2200 type_rust_path: None,
2201 cfg: None,
2202 typed_default: None,
2203 core_wrapper: CoreWrapper::None,
2204 vec_inner_core_wrapper: CoreWrapper::None,
2205 newtype_wrapper: None,
2206 };
2207 let mut fields: Vec<FieldDef> = (0..15)
2209 .map(|i| FieldDef {
2210 name: format!("field_{i}"),
2211 ty: TypeRef::Primitive(PrimitiveType::U32),
2212 optional: false,
2213 default: None,
2214 doc: String::new(),
2215 sanitized: false,
2216 is_boxed: false,
2217 type_rust_path: None,
2218 cfg: None,
2219 typed_default: None,
2220 core_wrapper: CoreWrapper::None,
2221 vec_inner_core_wrapper: CoreWrapper::None,
2222 newtype_wrapper: None,
2223 })
2224 .collect();
2225 fields.push(field);
2226 let typ = TypeDef {
2227 name: "UpdateConfig".to_string(),
2228 rust_path: "crate::UpdateConfig".to_string(),
2229 original_rust_path: String::new(),
2230 fields,
2231 methods: vec![],
2232 is_opaque: false,
2233 is_clone: true,
2234 is_copy: false,
2235 doc: String::new(),
2236 cfg: None,
2237 is_trait: false,
2238 has_default: true,
2239 has_stripped_cfg_fields: false,
2240 is_return_type: false,
2241 serde_rename_all: None,
2242 has_serde: false,
2243 super_traits: vec![],
2244 };
2245 let output = gen_magnus_kwargs_constructor(&typ, &simple_type_mapper);
2246 assert!(
2249 !output.contains("Option<Option<"),
2250 "hash constructor must not emit double Option: {output}"
2251 );
2252 assert!(
2253 output.contains("i64::try_convert"),
2254 "hash constructor should call inner-type::try_convert, not Option<T>::try_convert: {output}"
2255 );
2256 }
2257
2258 #[test]
2259 fn test_magnus_positional_constructor_no_double_option_when_ty_is_optional() {
2260 let field = FieldDef {
2263 name: "max_depth".to_string(),
2264 ty: TypeRef::Optional(Box::new(TypeRef::Primitive(PrimitiveType::Usize))),
2265 optional: true,
2266 default: None,
2267 doc: String::new(),
2268 sanitized: false,
2269 is_boxed: false,
2270 type_rust_path: None,
2271 cfg: None,
2272 typed_default: None,
2273 core_wrapper: CoreWrapper::None,
2274 vec_inner_core_wrapper: CoreWrapper::None,
2275 newtype_wrapper: None,
2276 };
2277 let typ = TypeDef {
2278 name: "SmallUpdate".to_string(),
2279 rust_path: "crate::SmallUpdate".to_string(),
2280 original_rust_path: String::new(),
2281 fields: vec![field],
2282 methods: vec![],
2283 is_opaque: false,
2284 is_clone: true,
2285 is_copy: false,
2286 doc: String::new(),
2287 cfg: None,
2288 is_trait: false,
2289 has_default: true,
2290 has_stripped_cfg_fields: false,
2291 is_return_type: false,
2292 serde_rename_all: None,
2293 has_serde: false,
2294 super_traits: vec![],
2295 };
2296 let output = gen_magnus_kwargs_constructor(&typ, &simple_type_mapper);
2297 assert!(
2300 !output.contains("Option<Option<"),
2301 "positional constructor must not emit double Option: {output}"
2302 );
2303 assert!(
2304 output.contains("Option<i64>"),
2305 "positional constructor should emit Option<inner> for optional Optional(T): {output}"
2306 );
2307 }
2308}