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 inner_type = type_mapper(&field.ty);
607 let type_prefix = as_type_path_prefix(&inner_type);
608 if is_optional {
609 writeln!(
611 out,
612 " {name}: kwargs.get(ruby.to_symbol(\"{name}\")).and_then(|v| {type_prefix}::try_convert(v).ok()),",
613 name = field.name,
614 type_prefix = type_prefix,
615 ).ok();
616 } else if use_unwrap_or_default(field) {
617 writeln!(
618 out,
619 " {name}: kwargs.get(ruby.to_symbol(\"{name}\")).and_then(|v| {type_prefix}::try_convert(v).ok()).unwrap_or_default(),",
620 name = field.name,
621 type_prefix = type_prefix,
622 ).ok();
623 } else {
624 let default_str = if inner_type == "String" {
629 if let Some(DefaultValue::EnumVariant(variant)) = &field.typed_default {
630 use heck::ToSnakeCase;
631 format!("\"{}\".to_string()", variant.to_snake_case())
632 } else {
633 default_value_for_field(field, "rust")
634 }
635 } else {
636 default_value_for_field(field, "rust")
637 };
638 writeln!(
639 out,
640 " {name}: kwargs.get(ruby.to_symbol(\"{name}\")).and_then(|v| {type_prefix}::try_convert(v).ok()).unwrap_or({default}),",
641 name = field.name,
642 type_prefix = type_prefix,
643 default = default_str,
644 ).ok();
645 }
646 }
647
648 writeln!(out, " }})").ok();
649 writeln!(out, "}}").ok();
650
651 out
652}
653
654fn field_is_optional_in_rust(field: &FieldDef) -> bool {
659 field.optional || matches!(&field.ty, TypeRef::Optional(_))
660}
661
662fn gen_magnus_positional_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
665 use std::fmt::Write;
666 let mut out = String::with_capacity(512);
667
668 writeln!(out, "fn new(").ok();
669
670 for (i, field) in typ.fields.iter().enumerate() {
674 let comma = if i < typ.fields.len() - 1 { "," } else { "" };
675 let is_optional = field_is_optional_in_rust(field);
676 if is_optional {
677 let inner_type = type_mapper(&field.ty);
681 writeln!(out, " {}: Option<{}>{}", field.name, inner_type, comma).ok();
682 } else {
683 let field_type = type_mapper(&field.ty);
684 writeln!(out, " {}: Option<{}>{}", field.name, field_type, comma).ok();
685 }
686 }
687
688 writeln!(out, ") -> Self {{").ok();
689 writeln!(out, " Self {{").ok();
690
691 for field in &typ.fields {
692 let is_optional = field_is_optional_in_rust(field);
693 if is_optional {
694 writeln!(out, " {},", field.name).ok();
696 } else if use_unwrap_or_default(field) {
697 writeln!(out, " {}: {}.unwrap_or_default(),", field.name, field.name).ok();
698 } else {
699 let default_str = default_value_for_field(field, "rust");
700 writeln!(
701 out,
702 " {}: {}.unwrap_or({}),",
703 field.name, field.name, default_str
704 )
705 .ok();
706 }
707 }
708
709 writeln!(out, " }}").ok();
710 writeln!(out, "}}").ok();
711
712 out
713}
714
715pub fn gen_php_kwargs_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
719 use std::fmt::Write;
720 let mut out = String::with_capacity(512);
721
722 writeln!(out, "pub fn __construct(").ok();
723
724 for (i, field) in typ.fields.iter().enumerate() {
726 let mapped = type_mapper(&field.ty);
727 let comma = if i < typ.fields.len() - 1 { "," } else { "" };
728 writeln!(out, " {}: Option<{}>{}", field.name, mapped, comma).ok();
729 }
730
731 writeln!(out, ") -> Self {{").ok();
732 writeln!(out, " Self {{").ok();
733
734 for field in &typ.fields {
735 let is_optional_field = field.optional || matches!(&field.ty, TypeRef::Optional(_));
736 if is_optional_field {
737 writeln!(out, " {},", field.name).ok();
739 } else if use_unwrap_or_default(field) {
740 writeln!(out, " {}: {}.unwrap_or_default(),", field.name, field.name).ok();
742 } else {
743 let default_str = default_value_for_field(field, "rust");
745 writeln!(
746 out,
747 " {}: {}.unwrap_or({}),",
748 field.name, field.name, default_str
749 )
750 .ok();
751 }
752 }
753
754 writeln!(out, " }}").ok();
755 writeln!(out, "}}").ok();
756
757 out
758}
759
760pub fn gen_rustler_kwargs_constructor(typ: &TypeDef, _type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
763 use std::fmt::Write;
764 let mut out = String::with_capacity(512);
765
766 writeln!(
769 out,
770 "pub fn new(opts: std::collections::HashMap<String, rustler::Term>) -> Self {{"
771 )
772 .ok();
773 writeln!(out, " Self {{").ok();
774
775 for field in &typ.fields {
779 if field.optional {
780 writeln!(
782 out,
783 " {}: opts.get(\"{}\").and_then(|t| t.decode().ok()),",
784 field.name, field.name
785 )
786 .ok();
787 } else if use_unwrap_or_default(field) {
788 writeln!(
789 out,
790 " {}: opts.get(\"{}\").and_then(|t| t.decode().ok()).unwrap_or_default(),",
791 field.name, field.name
792 )
793 .ok();
794 } else {
795 let default_str = default_value_for_field(field, "rust");
796 let is_enum_variant_default = default_str.contains("::") || default_str.starts_with("\"");
799
800 if is_enum_variant_default && matches!(&field.ty, TypeRef::String | TypeRef::Char) {
801 writeln!(
803 out,
804 " {}: opts.get(\"{}\").and_then(|t| t.decode().ok()).unwrap_or_default(),",
805 field.name, field.name
806 )
807 .ok();
808 } else if matches!(&field.ty, TypeRef::Named(_)) {
809 writeln!(
812 out,
813 " {}: opts.get(\"{}\").and_then(|t| t.decode().ok()).unwrap_or_default(),",
814 field.name, field.name
815 )
816 .ok();
817 } else {
818 writeln!(
819 out,
820 " {}: opts.get(\"{}\").and_then(|t| t.decode().ok()).unwrap_or({}),",
821 field.name, field.name, default_str
822 )
823 .ok();
824 }
825 }
826 }
827
828 writeln!(out, " }}").ok();
829 writeln!(out, "}}").ok();
830
831 out
832}
833
834pub fn gen_extendr_kwargs_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
837 use std::fmt::Write;
838 let mut out = String::with_capacity(512);
839
840 writeln!(out, "#[extendr]").ok();
841 writeln!(out, "pub fn new_{}(", typ.name.to_lowercase()).ok();
842
843 for (i, field) in typ.fields.iter().enumerate() {
845 let field_type = type_mapper(&field.ty);
846 let default_str = default_value_for_field(field, "r");
847 let comma = if i < typ.fields.len() - 1 { "," } else { "" };
848 writeln!(out, " {}: {} = {}{}", field.name, field_type, default_str, comma).ok();
849 }
850
851 writeln!(out, ") -> {} {{", typ.name).ok();
852 writeln!(out, " {} {{", typ.name).ok();
853
854 for field in &typ.fields {
856 writeln!(out, " {},", field.name).ok();
857 }
858
859 writeln!(out, " }}").ok();
860 writeln!(out, "}}").ok();
861
862 out
863}
864
865#[cfg(test)]
866mod tests {
867 use super::*;
868 use alef_core::ir::{CoreWrapper, FieldDef, PrimitiveType, TypeRef};
869
870 fn make_test_type() -> TypeDef {
871 TypeDef {
872 name: "Config".to_string(),
873 rust_path: "my_crate::Config".to_string(),
874 original_rust_path: String::new(),
875 fields: vec![
876 FieldDef {
877 name: "timeout".to_string(),
878 ty: TypeRef::Primitive(PrimitiveType::U64),
879 optional: false,
880 default: Some("30".to_string()),
881 doc: "Timeout in seconds".to_string(),
882 sanitized: false,
883 is_boxed: false,
884 type_rust_path: None,
885 cfg: None,
886 typed_default: Some(DefaultValue::IntLiteral(30)),
887 core_wrapper: CoreWrapper::None,
888 vec_inner_core_wrapper: CoreWrapper::None,
889 newtype_wrapper: None,
890 },
891 FieldDef {
892 name: "enabled".to_string(),
893 ty: TypeRef::Primitive(PrimitiveType::Bool),
894 optional: false,
895 default: None,
896 doc: "Enable feature".to_string(),
897 sanitized: false,
898 is_boxed: false,
899 type_rust_path: None,
900 cfg: None,
901 typed_default: Some(DefaultValue::BoolLiteral(true)),
902 core_wrapper: CoreWrapper::None,
903 vec_inner_core_wrapper: CoreWrapper::None,
904 newtype_wrapper: None,
905 },
906 FieldDef {
907 name: "name".to_string(),
908 ty: TypeRef::String,
909 optional: false,
910 default: None,
911 doc: "Config name".to_string(),
912 sanitized: false,
913 is_boxed: false,
914 type_rust_path: None,
915 cfg: None,
916 typed_default: Some(DefaultValue::StringLiteral("default".to_string())),
917 core_wrapper: CoreWrapper::None,
918 vec_inner_core_wrapper: CoreWrapper::None,
919 newtype_wrapper: None,
920 },
921 ],
922 methods: vec![],
923 is_opaque: false,
924 is_clone: true,
925 doc: "Configuration type".to_string(),
926 cfg: None,
927 is_trait: false,
928 has_default: true,
929 has_stripped_cfg_fields: false,
930 is_return_type: false,
931 serde_rename_all: None,
932 has_serde: false,
933 super_traits: vec![],
934 }
935 }
936
937 #[test]
938 fn test_default_value_bool_true_python() {
939 let field = FieldDef {
940 name: "enabled".to_string(),
941 ty: TypeRef::Primitive(PrimitiveType::Bool),
942 optional: false,
943 default: None,
944 doc: String::new(),
945 sanitized: false,
946 is_boxed: false,
947 type_rust_path: None,
948 cfg: None,
949 typed_default: Some(DefaultValue::BoolLiteral(true)),
950 core_wrapper: CoreWrapper::None,
951 vec_inner_core_wrapper: CoreWrapper::None,
952 newtype_wrapper: None,
953 };
954 assert_eq!(default_value_for_field(&field, "python"), "True");
955 }
956
957 #[test]
958 fn test_default_value_bool_false_go() {
959 let field = FieldDef {
960 name: "enabled".to_string(),
961 ty: TypeRef::Primitive(PrimitiveType::Bool),
962 optional: false,
963 default: None,
964 doc: String::new(),
965 sanitized: false,
966 is_boxed: false,
967 type_rust_path: None,
968 cfg: None,
969 typed_default: Some(DefaultValue::BoolLiteral(false)),
970 core_wrapper: CoreWrapper::None,
971 vec_inner_core_wrapper: CoreWrapper::None,
972 newtype_wrapper: None,
973 };
974 assert_eq!(default_value_for_field(&field, "go"), "false");
975 }
976
977 #[test]
978 fn test_default_value_string_literal() {
979 let field = FieldDef {
980 name: "name".to_string(),
981 ty: TypeRef::String,
982 optional: false,
983 default: None,
984 doc: String::new(),
985 sanitized: false,
986 is_boxed: false,
987 type_rust_path: None,
988 cfg: None,
989 typed_default: Some(DefaultValue::StringLiteral("hello".to_string())),
990 core_wrapper: CoreWrapper::None,
991 vec_inner_core_wrapper: CoreWrapper::None,
992 newtype_wrapper: None,
993 };
994 assert_eq!(default_value_for_field(&field, "python"), "\"hello\"");
995 assert_eq!(default_value_for_field(&field, "java"), "\"hello\"");
996 }
997
998 #[test]
999 fn test_default_value_int_literal() {
1000 let field = FieldDef {
1001 name: "timeout".to_string(),
1002 ty: TypeRef::Primitive(PrimitiveType::U64),
1003 optional: false,
1004 default: None,
1005 doc: String::new(),
1006 sanitized: false,
1007 is_boxed: false,
1008 type_rust_path: None,
1009 cfg: None,
1010 typed_default: Some(DefaultValue::IntLiteral(42)),
1011 core_wrapper: CoreWrapper::None,
1012 vec_inner_core_wrapper: CoreWrapper::None,
1013 newtype_wrapper: None,
1014 };
1015 let result = default_value_for_field(&field, "python");
1016 assert_eq!(result, "42");
1017 }
1018
1019 #[test]
1020 fn test_default_value_none() {
1021 let field = FieldDef {
1022 name: "maybe".to_string(),
1023 ty: TypeRef::Optional(Box::new(TypeRef::String)),
1024 optional: true,
1025 default: None,
1026 doc: String::new(),
1027 sanitized: false,
1028 is_boxed: false,
1029 type_rust_path: None,
1030 cfg: None,
1031 typed_default: Some(DefaultValue::None),
1032 core_wrapper: CoreWrapper::None,
1033 vec_inner_core_wrapper: CoreWrapper::None,
1034 newtype_wrapper: None,
1035 };
1036 assert_eq!(default_value_for_field(&field, "python"), "None");
1037 assert_eq!(default_value_for_field(&field, "go"), "nil");
1038 assert_eq!(default_value_for_field(&field, "java"), "null");
1039 assert_eq!(default_value_for_field(&field, "csharp"), "null");
1040 }
1041
1042 #[test]
1043 fn test_default_value_fallback_string() {
1044 let field = FieldDef {
1045 name: "name".to_string(),
1046 ty: TypeRef::String,
1047 optional: false,
1048 default: Some("\"custom\"".to_string()),
1049 doc: String::new(),
1050 sanitized: false,
1051 is_boxed: false,
1052 type_rust_path: None,
1053 cfg: None,
1054 typed_default: None,
1055 core_wrapper: CoreWrapper::None,
1056 vec_inner_core_wrapper: CoreWrapper::None,
1057 newtype_wrapper: None,
1058 };
1059 assert_eq!(default_value_for_field(&field, "python"), "\"custom\"");
1060 }
1061
1062 #[test]
1063 fn test_gen_pyo3_kwargs_constructor() {
1064 let typ = make_test_type();
1065 let output = gen_pyo3_kwargs_constructor(&typ, &|tr: &TypeRef| match tr {
1066 TypeRef::Primitive(p) => format!("{:?}", p),
1067 TypeRef::String | TypeRef::Char => "str".to_string(),
1068 _ => "Any".to_string(),
1069 });
1070
1071 assert!(output.contains("#[new]"));
1072 assert!(output.contains("#[pyo3(signature = ("));
1073 assert!(output.contains("timeout=30"));
1074 assert!(output.contains("enabled=True"));
1075 assert!(output.contains("name=\"default\""));
1076 assert!(output.contains("fn new("));
1077 }
1078
1079 #[test]
1080 fn test_gen_napi_defaults_constructor() {
1081 let typ = make_test_type();
1082 let output = gen_napi_defaults_constructor(&typ, &|tr: &TypeRef| match tr {
1083 TypeRef::Primitive(p) => format!("{:?}", p),
1084 TypeRef::String | TypeRef::Char => "String".to_string(),
1085 _ => "Value".to_string(),
1086 });
1087
1088 assert!(output.contains("pub fn new(mut env: napi::Env, obj: napi::Object)"));
1089 assert!(output.contains("timeout"));
1090 assert!(output.contains("enabled"));
1091 assert!(output.contains("name"));
1092 }
1093
1094 #[test]
1095 fn test_gen_go_functional_options() {
1096 let typ = make_test_type();
1097 let output = gen_go_functional_options(&typ, &|tr: &TypeRef| match tr {
1098 TypeRef::Primitive(p) => match p {
1099 PrimitiveType::U64 => "uint64".to_string(),
1100 PrimitiveType::Bool => "bool".to_string(),
1101 _ => "interface{}".to_string(),
1102 },
1103 TypeRef::String | TypeRef::Char => "string".to_string(),
1104 _ => "interface{}".to_string(),
1105 });
1106
1107 assert!(output.contains("type Config struct {"));
1108 assert!(output.contains("type ConfigOption func(*Config)"));
1109 assert!(output.contains("func WithConfigTimeout(val uint64) ConfigOption"));
1110 assert!(output.contains("func WithConfigEnabled(val bool) ConfigOption"));
1111 assert!(output.contains("func WithConfigName(val string) ConfigOption"));
1112 assert!(output.contains("func NewConfig(opts ...ConfigOption) *Config"));
1113 }
1114
1115 #[test]
1116 fn test_gen_java_builder() {
1117 let typ = make_test_type();
1118 let output = gen_java_builder(&typ, "dev.test", &|tr: &TypeRef| match tr {
1119 TypeRef::Primitive(p) => match p {
1120 PrimitiveType::U64 => "long".to_string(),
1121 PrimitiveType::Bool => "boolean".to_string(),
1122 _ => "int".to_string(),
1123 },
1124 TypeRef::String | TypeRef::Char => "String".to_string(),
1125 _ => "Object".to_string(),
1126 });
1127
1128 assert!(output.contains("package dev.test;"));
1129 assert!(output.contains("public class ConfigBuilder"));
1130 assert!(output.contains("withTimeout"));
1131 assert!(output.contains("withEnabled"));
1132 assert!(output.contains("withName"));
1133 assert!(output.contains("public Config build()"));
1134 }
1135
1136 #[test]
1137 fn test_gen_csharp_record() {
1138 let typ = make_test_type();
1139 let output = gen_csharp_record(&typ, "MyNamespace", &|tr: &TypeRef| match tr {
1140 TypeRef::Primitive(p) => match p {
1141 PrimitiveType::U64 => "ulong".to_string(),
1142 PrimitiveType::Bool => "bool".to_string(),
1143 _ => "int".to_string(),
1144 },
1145 TypeRef::String | TypeRef::Char => "string".to_string(),
1146 _ => "object".to_string(),
1147 });
1148
1149 assert!(output.contains("namespace MyNamespace;"));
1150 assert!(output.contains("public record Config"));
1151 assert!(output.contains("public ulong Timeout"));
1152 assert!(output.contains("public bool Enabled"));
1153 assert!(output.contains("public string Name"));
1154 assert!(output.contains("init;"));
1155 }
1156
1157 #[test]
1158 fn test_default_value_float_literal() {
1159 let field = FieldDef {
1160 name: "ratio".to_string(),
1161 ty: TypeRef::Primitive(PrimitiveType::F64),
1162 optional: false,
1163 default: None,
1164 doc: String::new(),
1165 sanitized: false,
1166 is_boxed: false,
1167 type_rust_path: None,
1168 cfg: None,
1169 typed_default: Some(DefaultValue::FloatLiteral(1.5)),
1170 core_wrapper: CoreWrapper::None,
1171 vec_inner_core_wrapper: CoreWrapper::None,
1172 newtype_wrapper: None,
1173 };
1174 let result = default_value_for_field(&field, "python");
1175 assert!(result.contains("1.5"));
1176 }
1177
1178 #[test]
1179 fn test_default_value_no_typed_no_default() {
1180 let field = FieldDef {
1181 name: "count".to_string(),
1182 ty: TypeRef::Primitive(PrimitiveType::U32),
1183 optional: false,
1184 default: None,
1185 doc: String::new(),
1186 sanitized: false,
1187 is_boxed: false,
1188 type_rust_path: None,
1189 cfg: None,
1190 typed_default: None,
1191 core_wrapper: CoreWrapper::None,
1192 vec_inner_core_wrapper: CoreWrapper::None,
1193 newtype_wrapper: None,
1194 };
1195 assert_eq!(default_value_for_field(&field, "python"), "0");
1197 assert_eq!(default_value_for_field(&field, "go"), "0");
1198 }
1199
1200 fn make_field(name: &str, ty: TypeRef) -> FieldDef {
1201 FieldDef {
1202 name: name.to_string(),
1203 ty,
1204 optional: false,
1205 default: None,
1206 doc: String::new(),
1207 sanitized: false,
1208 is_boxed: false,
1209 type_rust_path: None,
1210 cfg: None,
1211 typed_default: None,
1212 core_wrapper: CoreWrapper::None,
1213 vec_inner_core_wrapper: CoreWrapper::None,
1214 newtype_wrapper: None,
1215 }
1216 }
1217
1218 fn simple_type_mapper(tr: &TypeRef) -> String {
1219 match tr {
1220 TypeRef::Primitive(p) => match p {
1221 PrimitiveType::U64 => "u64".to_string(),
1222 PrimitiveType::Bool => "bool".to_string(),
1223 PrimitiveType::U32 => "u32".to_string(),
1224 _ => "i64".to_string(),
1225 },
1226 TypeRef::String | TypeRef::Char => "String".to_string(),
1227 TypeRef::Optional(inner) => format!("Option<{}>", simple_type_mapper(inner)),
1228 TypeRef::Vec(inner) => format!("Vec<{}>", simple_type_mapper(inner)),
1229 TypeRef::Named(n) => n.clone(),
1230 _ => "Value".to_string(),
1231 }
1232 }
1233
1234 #[test]
1239 fn test_default_value_bool_literal_ruby() {
1240 let field = FieldDef {
1241 name: "flag".to_string(),
1242 ty: TypeRef::Primitive(PrimitiveType::Bool),
1243 optional: false,
1244 default: None,
1245 doc: String::new(),
1246 sanitized: false,
1247 is_boxed: false,
1248 type_rust_path: None,
1249 cfg: None,
1250 typed_default: Some(DefaultValue::BoolLiteral(true)),
1251 core_wrapper: CoreWrapper::None,
1252 vec_inner_core_wrapper: CoreWrapper::None,
1253 newtype_wrapper: None,
1254 };
1255 assert_eq!(default_value_for_field(&field, "ruby"), "true");
1256 assert_eq!(default_value_for_field(&field, "php"), "true");
1257 assert_eq!(default_value_for_field(&field, "csharp"), "true");
1258 assert_eq!(default_value_for_field(&field, "java"), "true");
1259 assert_eq!(default_value_for_field(&field, "rust"), "true");
1260 }
1261
1262 #[test]
1263 fn test_default_value_bool_literal_r() {
1264 let field = FieldDef {
1265 name: "flag".to_string(),
1266 ty: TypeRef::Primitive(PrimitiveType::Bool),
1267 optional: false,
1268 default: None,
1269 doc: String::new(),
1270 sanitized: false,
1271 is_boxed: false,
1272 type_rust_path: None,
1273 cfg: None,
1274 typed_default: Some(DefaultValue::BoolLiteral(false)),
1275 core_wrapper: CoreWrapper::None,
1276 vec_inner_core_wrapper: CoreWrapper::None,
1277 newtype_wrapper: None,
1278 };
1279 assert_eq!(default_value_for_field(&field, "r"), "FALSE");
1280 }
1281
1282 #[test]
1283 fn test_default_value_string_literal_rust() {
1284 let field = FieldDef {
1285 name: "label".to_string(),
1286 ty: TypeRef::String,
1287 optional: false,
1288 default: None,
1289 doc: String::new(),
1290 sanitized: false,
1291 is_boxed: false,
1292 type_rust_path: None,
1293 cfg: None,
1294 typed_default: Some(DefaultValue::StringLiteral("hello".to_string())),
1295 core_wrapper: CoreWrapper::None,
1296 vec_inner_core_wrapper: CoreWrapper::None,
1297 newtype_wrapper: None,
1298 };
1299 assert_eq!(default_value_for_field(&field, "rust"), "\"hello\".to_string()");
1300 }
1301
1302 #[test]
1303 fn test_default_value_string_literal_escapes_quotes() {
1304 let field = FieldDef {
1305 name: "label".to_string(),
1306 ty: TypeRef::String,
1307 optional: false,
1308 default: None,
1309 doc: String::new(),
1310 sanitized: false,
1311 is_boxed: false,
1312 type_rust_path: None,
1313 cfg: None,
1314 typed_default: Some(DefaultValue::StringLiteral("say \"hi\"".to_string())),
1315 core_wrapper: CoreWrapper::None,
1316 vec_inner_core_wrapper: CoreWrapper::None,
1317 newtype_wrapper: None,
1318 };
1319 assert_eq!(default_value_for_field(&field, "python"), "\"say \\\"hi\\\"\"");
1320 }
1321
1322 #[test]
1323 fn test_default_value_float_literal_whole_number() {
1324 let field = FieldDef {
1326 name: "scale".to_string(),
1327 ty: TypeRef::Primitive(PrimitiveType::F32),
1328 optional: false,
1329 default: None,
1330 doc: String::new(),
1331 sanitized: false,
1332 is_boxed: false,
1333 type_rust_path: None,
1334 cfg: None,
1335 typed_default: Some(DefaultValue::FloatLiteral(2.0)),
1336 core_wrapper: CoreWrapper::None,
1337 vec_inner_core_wrapper: CoreWrapper::None,
1338 newtype_wrapper: None,
1339 };
1340 let result = default_value_for_field(&field, "python");
1341 assert!(result.contains('.'), "whole-number float should contain '.': {result}");
1342 }
1343
1344 #[test]
1345 fn test_default_value_enum_variant_per_language() {
1346 let field = FieldDef {
1347 name: "format".to_string(),
1348 ty: TypeRef::Named("OutputFormat".to_string()),
1349 optional: false,
1350 default: None,
1351 doc: String::new(),
1352 sanitized: false,
1353 is_boxed: false,
1354 type_rust_path: None,
1355 cfg: None,
1356 typed_default: Some(DefaultValue::EnumVariant("JsonOutput".to_string())),
1357 core_wrapper: CoreWrapper::None,
1358 vec_inner_core_wrapper: CoreWrapper::None,
1359 newtype_wrapper: None,
1360 };
1361 assert_eq!(default_value_for_field(&field, "python"), "OutputFormat.JSON_OUTPUT");
1362 assert_eq!(default_value_for_field(&field, "ruby"), "OutputFormat::JsonOutput");
1363 assert_eq!(default_value_for_field(&field, "go"), "OutputFormatJsonOutput");
1364 assert_eq!(default_value_for_field(&field, "java"), "OutputFormat.JSON_OUTPUT");
1365 assert_eq!(default_value_for_field(&field, "csharp"), "OutputFormat.JsonOutput");
1366 assert_eq!(default_value_for_field(&field, "php"), "OutputFormat::JsonOutput");
1367 assert_eq!(default_value_for_field(&field, "r"), "OutputFormat$JsonOutput");
1368 assert_eq!(default_value_for_field(&field, "rust"), "OutputFormat::JsonOutput");
1369 }
1370
1371 #[test]
1372 fn test_default_value_empty_vec_per_language() {
1373 let field = FieldDef {
1374 name: "items".to_string(),
1375 ty: TypeRef::Vec(Box::new(TypeRef::String)),
1376 optional: false,
1377 default: None,
1378 doc: String::new(),
1379 sanitized: false,
1380 is_boxed: false,
1381 type_rust_path: None,
1382 cfg: None,
1383 typed_default: Some(DefaultValue::Empty),
1384 core_wrapper: CoreWrapper::None,
1385 vec_inner_core_wrapper: CoreWrapper::None,
1386 newtype_wrapper: None,
1387 };
1388 assert_eq!(default_value_for_field(&field, "python"), "[]");
1389 assert_eq!(default_value_for_field(&field, "ruby"), "[]");
1390 assert_eq!(default_value_for_field(&field, "csharp"), "[]");
1391 assert_eq!(default_value_for_field(&field, "go"), "nil");
1392 assert_eq!(default_value_for_field(&field, "java"), "List.of()");
1393 assert_eq!(default_value_for_field(&field, "php"), "[]");
1394 assert_eq!(default_value_for_field(&field, "r"), "c()");
1395 assert_eq!(default_value_for_field(&field, "rust"), "vec![]");
1396 }
1397
1398 #[test]
1399 fn test_default_value_empty_map_per_language() {
1400 let field = FieldDef {
1401 name: "meta".to_string(),
1402 ty: TypeRef::Map(Box::new(TypeRef::String), Box::new(TypeRef::String)),
1403 optional: false,
1404 default: None,
1405 doc: String::new(),
1406 sanitized: false,
1407 is_boxed: false,
1408 type_rust_path: None,
1409 cfg: None,
1410 typed_default: Some(DefaultValue::Empty),
1411 core_wrapper: CoreWrapper::None,
1412 vec_inner_core_wrapper: CoreWrapper::None,
1413 newtype_wrapper: None,
1414 };
1415 assert_eq!(default_value_for_field(&field, "python"), "{}");
1416 assert_eq!(default_value_for_field(&field, "go"), "nil");
1417 assert_eq!(default_value_for_field(&field, "java"), "Map.of()");
1418 assert_eq!(default_value_for_field(&field, "rust"), "Default::default()");
1419 }
1420
1421 #[test]
1422 fn test_default_value_empty_bool_primitive() {
1423 let field = FieldDef {
1424 name: "flag".to_string(),
1425 ty: TypeRef::Primitive(PrimitiveType::Bool),
1426 optional: false,
1427 default: None,
1428 doc: String::new(),
1429 sanitized: false,
1430 is_boxed: false,
1431 type_rust_path: None,
1432 cfg: None,
1433 typed_default: Some(DefaultValue::Empty),
1434 core_wrapper: CoreWrapper::None,
1435 vec_inner_core_wrapper: CoreWrapper::None,
1436 newtype_wrapper: None,
1437 };
1438 assert_eq!(default_value_for_field(&field, "python"), "False");
1439 assert_eq!(default_value_for_field(&field, "ruby"), "false");
1440 assert_eq!(default_value_for_field(&field, "go"), "false");
1441 }
1442
1443 #[test]
1444 fn test_default_value_empty_float_primitive() {
1445 let field = FieldDef {
1446 name: "ratio".to_string(),
1447 ty: TypeRef::Primitive(PrimitiveType::F64),
1448 optional: false,
1449 default: None,
1450 doc: String::new(),
1451 sanitized: false,
1452 is_boxed: false,
1453 type_rust_path: None,
1454 cfg: None,
1455 typed_default: Some(DefaultValue::Empty),
1456 core_wrapper: CoreWrapper::None,
1457 vec_inner_core_wrapper: CoreWrapper::None,
1458 newtype_wrapper: None,
1459 };
1460 assert_eq!(default_value_for_field(&field, "python"), "0.0");
1461 }
1462
1463 #[test]
1464 fn test_default_value_empty_string_type() {
1465 let field = FieldDef {
1466 name: "label".to_string(),
1467 ty: TypeRef::String,
1468 optional: false,
1469 default: None,
1470 doc: String::new(),
1471 sanitized: false,
1472 is_boxed: false,
1473 type_rust_path: None,
1474 cfg: None,
1475 typed_default: Some(DefaultValue::Empty),
1476 core_wrapper: CoreWrapper::None,
1477 vec_inner_core_wrapper: CoreWrapper::None,
1478 newtype_wrapper: None,
1479 };
1480 assert_eq!(default_value_for_field(&field, "rust"), "String::new()");
1481 assert_eq!(default_value_for_field(&field, "python"), "\"\"");
1482 }
1483
1484 #[test]
1485 fn test_default_value_empty_bytes_type() {
1486 let field = FieldDef {
1487 name: "data".to_string(),
1488 ty: TypeRef::Bytes,
1489 optional: false,
1490 default: None,
1491 doc: String::new(),
1492 sanitized: false,
1493 is_boxed: false,
1494 type_rust_path: None,
1495 cfg: None,
1496 typed_default: Some(DefaultValue::Empty),
1497 core_wrapper: CoreWrapper::None,
1498 vec_inner_core_wrapper: CoreWrapper::None,
1499 newtype_wrapper: None,
1500 };
1501 assert_eq!(default_value_for_field(&field, "python"), "b\"\"");
1502 assert_eq!(default_value_for_field(&field, "go"), "[]byte{}");
1503 assert_eq!(default_value_for_field(&field, "rust"), "vec![]");
1504 }
1505
1506 #[test]
1507 fn test_default_value_empty_json_type() {
1508 let field = FieldDef {
1509 name: "payload".to_string(),
1510 ty: TypeRef::Json,
1511 optional: false,
1512 default: None,
1513 doc: String::new(),
1514 sanitized: false,
1515 is_boxed: false,
1516 type_rust_path: None,
1517 cfg: None,
1518 typed_default: Some(DefaultValue::Empty),
1519 core_wrapper: CoreWrapper::None,
1520 vec_inner_core_wrapper: CoreWrapper::None,
1521 newtype_wrapper: None,
1522 };
1523 assert_eq!(default_value_for_field(&field, "python"), "{}");
1524 assert_eq!(default_value_for_field(&field, "ruby"), "{}");
1525 assert_eq!(default_value_for_field(&field, "go"), "json.RawMessage(nil)");
1526 assert_eq!(default_value_for_field(&field, "r"), "list()");
1527 assert_eq!(default_value_for_field(&field, "rust"), "serde_json::json!({})");
1528 }
1529
1530 #[test]
1531 fn test_default_value_none_ruby_php_r() {
1532 let field = FieldDef {
1533 name: "maybe".to_string(),
1534 ty: TypeRef::Optional(Box::new(TypeRef::String)),
1535 optional: true,
1536 default: None,
1537 doc: String::new(),
1538 sanitized: false,
1539 is_boxed: false,
1540 type_rust_path: None,
1541 cfg: None,
1542 typed_default: Some(DefaultValue::None),
1543 core_wrapper: CoreWrapper::None,
1544 vec_inner_core_wrapper: CoreWrapper::None,
1545 newtype_wrapper: None,
1546 };
1547 assert_eq!(default_value_for_field(&field, "ruby"), "nil");
1548 assert_eq!(default_value_for_field(&field, "php"), "null");
1549 assert_eq!(default_value_for_field(&field, "r"), "NULL");
1550 assert_eq!(default_value_for_field(&field, "rust"), "None");
1551 }
1552
1553 #[test]
1558 fn test_default_value_fallback_bool_all_languages() {
1559 let field = make_field("flag", TypeRef::Primitive(PrimitiveType::Bool));
1560 assert_eq!(default_value_for_field(&field, "python"), "False");
1561 assert_eq!(default_value_for_field(&field, "ruby"), "false");
1562 assert_eq!(default_value_for_field(&field, "csharp"), "false");
1563 assert_eq!(default_value_for_field(&field, "java"), "false");
1564 assert_eq!(default_value_for_field(&field, "php"), "false");
1565 assert_eq!(default_value_for_field(&field, "r"), "FALSE");
1566 assert_eq!(default_value_for_field(&field, "rust"), "false");
1567 }
1568
1569 #[test]
1570 fn test_default_value_fallback_float() {
1571 let field = make_field("ratio", TypeRef::Primitive(PrimitiveType::F64));
1572 assert_eq!(default_value_for_field(&field, "python"), "0.0");
1573 assert_eq!(default_value_for_field(&field, "rust"), "0.0");
1574 }
1575
1576 #[test]
1577 fn test_default_value_fallback_string_all_languages() {
1578 let field = make_field("name", TypeRef::String);
1579 assert_eq!(default_value_for_field(&field, "python"), "\"\"");
1580 assert_eq!(default_value_for_field(&field, "ruby"), "\"\"");
1581 assert_eq!(default_value_for_field(&field, "go"), "\"\"");
1582 assert_eq!(default_value_for_field(&field, "java"), "\"\"");
1583 assert_eq!(default_value_for_field(&field, "csharp"), "\"\"");
1584 assert_eq!(default_value_for_field(&field, "php"), "\"\"");
1585 assert_eq!(default_value_for_field(&field, "r"), "\"\"");
1586 assert_eq!(default_value_for_field(&field, "rust"), "String::new()");
1587 }
1588
1589 #[test]
1590 fn test_default_value_fallback_bytes_all_languages() {
1591 let field = make_field("data", TypeRef::Bytes);
1592 assert_eq!(default_value_for_field(&field, "python"), "b\"\"");
1593 assert_eq!(default_value_for_field(&field, "ruby"), "\"\"");
1594 assert_eq!(default_value_for_field(&field, "go"), "[]byte{}");
1595 assert_eq!(default_value_for_field(&field, "java"), "new byte[]{}");
1596 assert_eq!(default_value_for_field(&field, "csharp"), "new byte[]{}");
1597 assert_eq!(default_value_for_field(&field, "php"), "\"\"");
1598 assert_eq!(default_value_for_field(&field, "r"), "raw()");
1599 assert_eq!(default_value_for_field(&field, "rust"), "vec![]");
1600 }
1601
1602 #[test]
1603 fn test_default_value_fallback_optional() {
1604 let field = make_field("maybe", TypeRef::Optional(Box::new(TypeRef::String)));
1605 assert_eq!(default_value_for_field(&field, "python"), "None");
1606 assert_eq!(default_value_for_field(&field, "ruby"), "nil");
1607 assert_eq!(default_value_for_field(&field, "go"), "nil");
1608 assert_eq!(default_value_for_field(&field, "java"), "null");
1609 assert_eq!(default_value_for_field(&field, "csharp"), "null");
1610 assert_eq!(default_value_for_field(&field, "php"), "null");
1611 assert_eq!(default_value_for_field(&field, "r"), "NULL");
1612 assert_eq!(default_value_for_field(&field, "rust"), "None");
1613 }
1614
1615 #[test]
1616 fn test_default_value_fallback_vec_all_languages() {
1617 let field = make_field("items", TypeRef::Vec(Box::new(TypeRef::String)));
1618 assert_eq!(default_value_for_field(&field, "python"), "[]");
1619 assert_eq!(default_value_for_field(&field, "ruby"), "[]");
1620 assert_eq!(default_value_for_field(&field, "go"), "[]interface{}{}");
1621 assert_eq!(default_value_for_field(&field, "java"), "new java.util.ArrayList<>()");
1622 assert_eq!(default_value_for_field(&field, "csharp"), "[]");
1623 assert_eq!(default_value_for_field(&field, "php"), "[]");
1624 assert_eq!(default_value_for_field(&field, "r"), "c()");
1625 assert_eq!(default_value_for_field(&field, "rust"), "vec![]");
1626 }
1627
1628 #[test]
1629 fn test_default_value_fallback_map_all_languages() {
1630 let field = make_field(
1631 "meta",
1632 TypeRef::Map(Box::new(TypeRef::String), Box::new(TypeRef::String)),
1633 );
1634 assert_eq!(default_value_for_field(&field, "python"), "{}");
1635 assert_eq!(default_value_for_field(&field, "ruby"), "{}");
1636 assert_eq!(default_value_for_field(&field, "go"), "make(map[string]interface{})");
1637 assert_eq!(default_value_for_field(&field, "java"), "new java.util.HashMap<>()");
1638 assert_eq!(
1639 default_value_for_field(&field, "csharp"),
1640 "new Dictionary<string, object>()"
1641 );
1642 assert_eq!(default_value_for_field(&field, "php"), "[]");
1643 assert_eq!(default_value_for_field(&field, "r"), "list()");
1644 assert_eq!(
1645 default_value_for_field(&field, "rust"),
1646 "std::collections::HashMap::new()"
1647 );
1648 }
1649
1650 #[test]
1651 fn test_default_value_fallback_json_all_languages() {
1652 let field = make_field("payload", TypeRef::Json);
1653 assert_eq!(default_value_for_field(&field, "python"), "{}");
1654 assert_eq!(default_value_for_field(&field, "ruby"), "{}");
1655 assert_eq!(default_value_for_field(&field, "go"), "json.RawMessage(nil)");
1656 assert_eq!(default_value_for_field(&field, "r"), "list()");
1657 assert_eq!(default_value_for_field(&field, "rust"), "serde_json::json!({})");
1658 }
1659
1660 #[test]
1661 fn test_default_value_fallback_named_type() {
1662 let field = make_field("config", TypeRef::Named("MyConfig".to_string()));
1663 assert_eq!(default_value_for_field(&field, "rust"), "MyConfig::default()");
1664 assert_eq!(default_value_for_field(&field, "python"), "None");
1665 assert_eq!(default_value_for_field(&field, "ruby"), "nil");
1666 assert_eq!(default_value_for_field(&field, "go"), "nil");
1667 assert_eq!(default_value_for_field(&field, "java"), "null");
1668 assert_eq!(default_value_for_field(&field, "csharp"), "null");
1669 assert_eq!(default_value_for_field(&field, "php"), "null");
1670 assert_eq!(default_value_for_field(&field, "r"), "NULL");
1671 }
1672
1673 #[test]
1674 fn test_default_value_fallback_duration() {
1675 let field = make_field("timeout", TypeRef::Duration);
1677 assert_eq!(default_value_for_field(&field, "python"), "None");
1678 assert_eq!(default_value_for_field(&field, "rust"), "Default::default()");
1679 }
1680
1681 #[test]
1686 fn test_gen_magnus_kwargs_constructor_positional_basic() {
1687 let typ = make_test_type();
1688 let output = gen_magnus_kwargs_constructor(&typ, &simple_type_mapper);
1689
1690 assert!(output.contains("fn new("), "should have fn new");
1691 assert!(output.contains("Option<u64>"), "timeout should be Option<u64>");
1693 assert!(output.contains("Option<bool>"), "enabled should be Option<bool>");
1694 assert!(output.contains("Option<String>"), "name should be Option<String>");
1695 assert!(output.contains("-> Self {"), "should return Self");
1696 assert!(
1698 output.contains("timeout: timeout.unwrap_or(30),"),
1699 "should apply int default"
1700 );
1701 assert!(
1703 output.contains("enabled: enabled.unwrap_or(true),"),
1704 "should apply bool default"
1705 );
1706 assert!(
1708 output.contains("name: name.unwrap_or(\"default\".to_string()),"),
1709 "should apply string default"
1710 );
1711 }
1712
1713 #[test]
1714 fn test_gen_magnus_kwargs_constructor_positional_optional_field() {
1715 let mut typ = make_test_type();
1717 typ.fields.push(FieldDef {
1718 name: "extra".to_string(),
1719 ty: TypeRef::String,
1720 optional: true,
1721 default: None,
1722 doc: String::new(),
1723 sanitized: false,
1724 is_boxed: false,
1725 type_rust_path: None,
1726 cfg: None,
1727 typed_default: None,
1728 core_wrapper: CoreWrapper::None,
1729 vec_inner_core_wrapper: CoreWrapper::None,
1730 newtype_wrapper: None,
1731 });
1732 let output = gen_magnus_kwargs_constructor(&typ, &simple_type_mapper);
1733 assert!(output.contains("extra,"), "optional field should be assigned directly");
1735 assert!(!output.contains("extra.unwrap"), "optional field should not use unwrap");
1736 }
1737
1738 #[test]
1739 fn test_gen_magnus_kwargs_constructor_unwrap_or_default() {
1740 let mut typ = make_test_type();
1742 typ.fields.push(FieldDef {
1743 name: "count".to_string(),
1744 ty: TypeRef::Primitive(PrimitiveType::U32),
1745 optional: false,
1746 default: None,
1747 doc: String::new(),
1748 sanitized: false,
1749 is_boxed: false,
1750 type_rust_path: None,
1751 cfg: None,
1752 typed_default: None,
1753 core_wrapper: CoreWrapper::None,
1754 vec_inner_core_wrapper: CoreWrapper::None,
1755 newtype_wrapper: None,
1756 });
1757 let output = gen_magnus_kwargs_constructor(&typ, &simple_type_mapper);
1758 assert!(
1759 output.contains("count: count.unwrap_or_default(),"),
1760 "plain primitive with no default should use unwrap_or_default"
1761 );
1762 }
1763
1764 #[test]
1765 fn test_gen_magnus_kwargs_constructor_hash_path_for_many_fields() {
1766 let mut fields: Vec<FieldDef> = (0..16)
1768 .map(|i| FieldDef {
1769 name: format!("field_{i}"),
1770 ty: TypeRef::Primitive(PrimitiveType::U32),
1771 optional: false,
1772 default: None,
1773 doc: String::new(),
1774 sanitized: false,
1775 is_boxed: false,
1776 type_rust_path: None,
1777 cfg: None,
1778 typed_default: None,
1779 core_wrapper: CoreWrapper::None,
1780 vec_inner_core_wrapper: CoreWrapper::None,
1781 newtype_wrapper: None,
1782 })
1783 .collect();
1784 fields[0].optional = true;
1786
1787 let typ = TypeDef {
1788 name: "BigConfig".to_string(),
1789 rust_path: "crate::BigConfig".to_string(),
1790 original_rust_path: String::new(),
1791 fields,
1792 methods: vec![],
1793 is_opaque: false,
1794 is_clone: true,
1795 doc: String::new(),
1796 cfg: None,
1797 is_trait: false,
1798 has_default: true,
1799 has_stripped_cfg_fields: false,
1800 is_return_type: false,
1801 serde_rename_all: None,
1802 has_serde: false,
1803 super_traits: vec![],
1804 };
1805 let output = gen_magnus_kwargs_constructor(&typ, &simple_type_mapper);
1806
1807 assert!(output.contains("kwargs: magnus::RHash"), "should accept RHash");
1808 assert!(output.contains("ruby.to_symbol("), "should use symbol lookup");
1809 assert!(
1811 output.contains("field_0: kwargs.get(ruby.to_symbol(\"field_0\")).and_then(|v|"),
1812 "optional field should use and_then"
1813 );
1814 assert!(
1815 output.contains("field_0:").then_some(()).is_some(),
1816 "field_0 should appear in output"
1817 );
1818 }
1819
1820 #[test]
1825 fn test_gen_php_kwargs_constructor_basic() {
1826 let typ = make_test_type();
1827 let output = gen_php_kwargs_constructor(&typ, &simple_type_mapper);
1828
1829 assert!(
1830 output.contains("pub fn __construct("),
1831 "should use PHP constructor name"
1832 );
1833 assert!(
1835 output.contains("timeout: Option<u64>"),
1836 "timeout param should be Option<u64>"
1837 );
1838 assert!(
1839 output.contains("enabled: Option<bool>"),
1840 "enabled param should be Option<bool>"
1841 );
1842 assert!(
1843 output.contains("name: Option<String>"),
1844 "name param should be Option<String>"
1845 );
1846 assert!(output.contains("-> Self {"), "should return Self");
1847 assert!(
1848 output.contains("timeout: timeout.unwrap_or(30),"),
1849 "should apply int default for timeout"
1850 );
1851 assert!(
1852 output.contains("enabled: enabled.unwrap_or(true),"),
1853 "should apply bool default for enabled"
1854 );
1855 assert!(
1856 output.contains("name: name.unwrap_or(\"default\".to_string()),"),
1857 "should apply string default for name"
1858 );
1859 }
1860
1861 #[test]
1862 fn test_gen_php_kwargs_constructor_optional_field_passthrough() {
1863 let mut typ = make_test_type();
1864 typ.fields.push(FieldDef {
1865 name: "tag".to_string(),
1866 ty: TypeRef::String,
1867 optional: true,
1868 default: None,
1869 doc: String::new(),
1870 sanitized: false,
1871 is_boxed: false,
1872 type_rust_path: None,
1873 cfg: None,
1874 typed_default: None,
1875 core_wrapper: CoreWrapper::None,
1876 vec_inner_core_wrapper: CoreWrapper::None,
1877 newtype_wrapper: None,
1878 });
1879 let output = gen_php_kwargs_constructor(&typ, &simple_type_mapper);
1880 assert!(
1881 output.contains("tag,"),
1882 "optional field should be passed through directly"
1883 );
1884 assert!(!output.contains("tag.unwrap"), "optional field should not call unwrap");
1885 }
1886
1887 #[test]
1888 fn test_gen_php_kwargs_constructor_unwrap_or_default_for_primitive() {
1889 let mut typ = make_test_type();
1890 typ.fields.push(FieldDef {
1891 name: "retries".to_string(),
1892 ty: TypeRef::Primitive(PrimitiveType::U32),
1893 optional: false,
1894 default: None,
1895 doc: String::new(),
1896 sanitized: false,
1897 is_boxed: false,
1898 type_rust_path: None,
1899 cfg: None,
1900 typed_default: None,
1901 core_wrapper: CoreWrapper::None,
1902 vec_inner_core_wrapper: CoreWrapper::None,
1903 newtype_wrapper: None,
1904 });
1905 let output = gen_php_kwargs_constructor(&typ, &simple_type_mapper);
1906 assert!(
1907 output.contains("retries: retries.unwrap_or_default(),"),
1908 "primitive with no default should use unwrap_or_default"
1909 );
1910 }
1911
1912 #[test]
1917 fn test_gen_rustler_kwargs_constructor_basic() {
1918 let typ = make_test_type();
1919 let output = gen_rustler_kwargs_constructor(&typ, &simple_type_mapper);
1920
1921 assert!(
1922 output.contains("pub fn new(opts: std::collections::HashMap<String, rustler::Term>)"),
1923 "should accept HashMap of Terms"
1924 );
1925 assert!(output.contains("Self {"), "should construct Self");
1926 assert!(
1928 output.contains("timeout: opts.get(\"timeout\").and_then(|t| t.decode().ok()).unwrap_or(30),"),
1929 "should apply int default for timeout"
1930 );
1931 assert!(
1933 output.contains("enabled: opts.get(\"enabled\").and_then(|t| t.decode().ok()).unwrap_or(true),"),
1934 "should apply bool default for enabled"
1935 );
1936 }
1937
1938 #[test]
1939 fn test_gen_rustler_kwargs_constructor_optional_field() {
1940 let mut typ = make_test_type();
1941 typ.fields.push(FieldDef {
1942 name: "extra".to_string(),
1943 ty: TypeRef::String,
1944 optional: true,
1945 default: None,
1946 doc: String::new(),
1947 sanitized: false,
1948 is_boxed: false,
1949 type_rust_path: None,
1950 cfg: None,
1951 typed_default: None,
1952 core_wrapper: CoreWrapper::None,
1953 vec_inner_core_wrapper: CoreWrapper::None,
1954 newtype_wrapper: None,
1955 });
1956 let output = gen_rustler_kwargs_constructor(&typ, &simple_type_mapper);
1957 assert!(
1958 output.contains("extra: opts.get(\"extra\").and_then(|t| t.decode().ok()),"),
1959 "optional field should decode without unwrap"
1960 );
1961 }
1962
1963 #[test]
1964 fn test_gen_rustler_kwargs_constructor_named_type_uses_unwrap_or_default() {
1965 let mut typ = make_test_type();
1966 typ.fields.push(FieldDef {
1967 name: "inner".to_string(),
1968 ty: TypeRef::Named("InnerConfig".to_string()),
1969 optional: false,
1970 default: None,
1971 doc: String::new(),
1972 sanitized: false,
1973 is_boxed: false,
1974 type_rust_path: None,
1975 cfg: None,
1976 typed_default: None,
1977 core_wrapper: CoreWrapper::None,
1978 vec_inner_core_wrapper: CoreWrapper::None,
1979 newtype_wrapper: None,
1980 });
1981 let output = gen_rustler_kwargs_constructor(&typ, &simple_type_mapper);
1982 assert!(
1983 output.contains("inner: opts.get(\"inner\").and_then(|t| t.decode().ok()).unwrap_or_default(),"),
1984 "Named type with no default should use unwrap_or_default"
1985 );
1986 }
1987
1988 #[test]
1989 fn test_gen_rustler_kwargs_constructor_string_field_uses_unwrap_or_default() {
1990 let mut typ = make_test_type();
1993 let output = gen_rustler_kwargs_constructor(&typ, &simple_type_mapper);
1995 assert!(
1996 output.contains("name: opts.get(\"name\").and_then(|t| t.decode().ok()).unwrap_or_default(),"),
1997 "String field with quoted default should use unwrap_or_default"
1998 );
1999 typ.fields.push(FieldDef {
2001 name: "label".to_string(),
2002 ty: TypeRef::String,
2003 optional: false,
2004 default: None,
2005 doc: String::new(),
2006 sanitized: false,
2007 is_boxed: false,
2008 type_rust_path: None,
2009 cfg: None,
2010 typed_default: None,
2011 core_wrapper: CoreWrapper::None,
2012 vec_inner_core_wrapper: CoreWrapper::None,
2013 newtype_wrapper: None,
2014 });
2015 let output2 = gen_rustler_kwargs_constructor(&typ, &simple_type_mapper);
2016 assert!(
2017 output2.contains("label: opts.get(\"label\").and_then(|t| t.decode().ok()).unwrap_or_default(),"),
2018 "String field with no default should use unwrap_or_default"
2019 );
2020 }
2021
2022 #[test]
2027 fn test_gen_extendr_kwargs_constructor_basic() {
2028 let typ = make_test_type();
2029 let output = gen_extendr_kwargs_constructor(&typ, &simple_type_mapper);
2030
2031 assert!(output.contains("#[extendr]"), "should have extendr attribute");
2032 assert!(
2033 output.contains("pub fn new_config("),
2034 "function name should be lowercase type name"
2035 );
2036 assert!(
2038 output.contains("timeout: u64 = 30"),
2039 "should include timeout with default"
2040 );
2041 assert!(
2043 output.contains("enabled: bool = TRUE"),
2044 "should include enabled with R bool default"
2045 );
2046 assert!(
2047 output.contains("name: String = \"default\""),
2048 "should include name with default"
2049 );
2050 assert!(output.contains("-> Config {"), "should return Config");
2051 assert!(output.contains("Config {"), "should construct Config");
2052 assert!(output.contains("timeout,"), "should include timeout in struct literal");
2053 assert!(output.contains("enabled,"), "should include enabled in struct literal");
2054 assert!(output.contains("name,"), "should include name in struct literal");
2055 }
2056
2057 #[test]
2058 fn test_gen_extendr_kwargs_constructor_r_bool_default() {
2059 let mut typ = make_test_type();
2061 typ.fields.retain(|f| f.name != "enabled");
2063 typ.fields.push(FieldDef {
2064 name: "verbose".to_string(),
2065 ty: TypeRef::Primitive(PrimitiveType::Bool),
2066 optional: false,
2067 default: None,
2068 doc: String::new(),
2069 sanitized: false,
2070 is_boxed: false,
2071 type_rust_path: None,
2072 cfg: None,
2073 typed_default: None,
2074 core_wrapper: CoreWrapper::None,
2075 vec_inner_core_wrapper: CoreWrapper::None,
2076 newtype_wrapper: None,
2077 });
2078 let output = gen_extendr_kwargs_constructor(&typ, &simple_type_mapper);
2079 assert!(
2080 output.contains("verbose: bool = FALSE"),
2081 "R bool default should be FALSE: {output}"
2082 );
2083 }
2084
2085 #[test]
2090 fn test_gen_go_functional_options_skips_tuple_fields() {
2091 let mut typ = make_test_type();
2092 typ.fields.push(FieldDef {
2093 name: "_0".to_string(),
2094 ty: TypeRef::Primitive(PrimitiveType::U32),
2095 optional: false,
2096 default: None,
2097 doc: String::new(),
2098 sanitized: false,
2099 is_boxed: false,
2100 type_rust_path: None,
2101 cfg: None,
2102 typed_default: None,
2103 core_wrapper: CoreWrapper::None,
2104 vec_inner_core_wrapper: CoreWrapper::None,
2105 newtype_wrapper: None,
2106 });
2107 let output = gen_go_functional_options(&typ, &simple_type_mapper);
2108 assert!(
2109 !output.contains("_0"),
2110 "tuple field _0 should be filtered out from Go output"
2111 );
2112 }
2113
2114 #[test]
2119 fn test_gen_magnus_hash_constructor_generic_type_prefix() {
2120 let fields: Vec<FieldDef> = (0..16)
2122 .map(|i| FieldDef {
2123 name: format!("field_{i}"),
2124 ty: if i == 0 {
2125 TypeRef::Vec(Box::new(TypeRef::String))
2126 } else {
2127 TypeRef::Primitive(PrimitiveType::U32)
2128 },
2129 optional: false,
2130 default: None,
2131 doc: String::new(),
2132 sanitized: false,
2133 is_boxed: false,
2134 type_rust_path: None,
2135 cfg: None,
2136 typed_default: None,
2137 core_wrapper: CoreWrapper::None,
2138 vec_inner_core_wrapper: CoreWrapper::None,
2139 newtype_wrapper: None,
2140 })
2141 .collect();
2142 let typ = TypeDef {
2143 name: "WideConfig".to_string(),
2144 rust_path: "crate::WideConfig".to_string(),
2145 original_rust_path: String::new(),
2146 fields,
2147 methods: vec![],
2148 is_opaque: false,
2149 is_clone: true,
2150 doc: String::new(),
2151 cfg: None,
2152 is_trait: false,
2153 has_default: true,
2154 has_stripped_cfg_fields: false,
2155 is_return_type: false,
2156 serde_rename_all: None,
2157 has_serde: false,
2158 super_traits: vec![],
2159 };
2160 let output = gen_magnus_kwargs_constructor(&typ, &simple_type_mapper);
2161 assert!(
2163 output.contains("<Vec<String>>::try_convert"),
2164 "generic types should use UFCS angle-bracket prefix: {output}"
2165 );
2166 }
2167}