1use crate::generators::{AsyncPattern, RustBindingConfig};
2use ahash::AHashSet;
3use alef_core::ir::{ParamDef, TypeDef, TypeRef};
4use std::fmt::Write;
5
6pub fn wrap_return(
17 expr: &str,
18 return_type: &TypeRef,
19 type_name: &str,
20 opaque_types: &AHashSet<String>,
21 self_is_opaque: bool,
22 returns_ref: bool,
23 returns_cow: bool,
24) -> String {
25 match return_type {
26 TypeRef::Named(n) if n == type_name && self_is_opaque => {
27 if returns_cow {
28 format!("Self {{ inner: Arc::new({expr}.into_owned()) }}")
29 } else if returns_ref {
30 format!("Self {{ inner: Arc::new({expr}.clone()) }}")
31 } else {
32 format!("Self {{ inner: Arc::new({expr}) }}")
33 }
34 }
35 TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
36 if returns_cow {
37 format!("{n} {{ inner: Arc::new({expr}.into_owned()) }}")
38 } else if returns_ref {
39 format!("{n} {{ inner: Arc::new({expr}.clone()) }}")
40 } else {
41 format!("{n} {{ inner: Arc::new({expr}) }}")
42 }
43 }
44 TypeRef::Named(_) => {
45 if returns_cow {
49 format!("{expr}.into_owned().into()")
50 } else if returns_ref {
51 format!("{expr}.clone().into()")
52 } else {
53 format!("{expr}.into()")
54 }
55 }
56 TypeRef::String | TypeRef::Bytes => {
59 if returns_ref {
60 format!("{expr}.into()")
61 } else {
62 expr.to_string()
63 }
64 }
65 TypeRef::Path => format!("{expr}.to_string_lossy().to_string()"),
67 TypeRef::Duration => format!("{expr}.as_millis() as u64"),
69 TypeRef::Json => format!("{expr}.to_string()"),
71 TypeRef::Optional(inner) => match inner.as_ref() {
73 TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
74 if returns_ref {
75 format!("{expr}.map(|v| {n} {{ inner: Arc::new(v.clone()) }})")
76 } else {
77 format!("{expr}.map(|v| {n} {{ inner: Arc::new(v) }})")
78 }
79 }
80 TypeRef::Named(_) => {
81 if returns_ref {
82 format!("{expr}.map(|v| v.clone().into())")
83 } else {
84 format!("{expr}.map(Into::into)")
85 }
86 }
87 TypeRef::Path => {
88 format!("{expr}.map(Into::into)")
89 }
90 TypeRef::String | TypeRef::Bytes => {
91 if returns_ref {
92 format!("{expr}.map(Into::into)")
93 } else {
94 expr.to_string()
95 }
96 }
97 TypeRef::Duration => format!("{expr}.map(|d| d.as_millis() as u64)"),
98 TypeRef::Json => format!("{expr}.map(ToString::to_string)"),
99 _ => expr.to_string(),
100 },
101 TypeRef::Vec(inner) => match inner.as_ref() {
103 TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
104 if returns_ref {
105 format!("{expr}.into_iter().map(|v| {n} {{ inner: Arc::new(v.clone()) }}).collect()")
106 } else {
107 format!("{expr}.into_iter().map(|v| {n} {{ inner: Arc::new(v) }}).collect()")
108 }
109 }
110 TypeRef::Named(_) => {
111 if returns_ref {
112 format!("{expr}.into_iter().map(|v| v.clone().into()).collect()")
113 } else {
114 format!("{expr}.into_iter().map(Into::into).collect()")
115 }
116 }
117 TypeRef::Path => {
118 format!("{expr}.into_iter().map(Into::into).collect()")
119 }
120 TypeRef::String | TypeRef::Bytes => {
121 if returns_ref {
122 format!("{expr}.into_iter().map(Into::into).collect()")
123 } else {
124 expr.to_string()
125 }
126 }
127 _ => expr.to_string(),
128 },
129 _ => expr.to_string(),
130 }
131}
132
133pub fn apply_return_newtype_unwrap(expr: &str, return_newtype_wrapper: &Option<String>) -> String {
138 match return_newtype_wrapper {
139 Some(_) => format!("({expr}).0"),
140 None => expr.to_string(),
141 }
142}
143
144pub fn gen_call_args(params: &[ParamDef], opaque_types: &AHashSet<String>) -> String {
151 params
152 .iter()
153 .enumerate()
154 .map(|(idx, p)| {
155 let promoted = crate::shared::is_promoted_optional(params, idx);
156 let unwrap_suffix = if promoted {
158 format!(".expect(\"'{}' is required\")", p.name)
159 } else {
160 String::new()
161 };
162 if let Some(newtype_path) = &p.newtype_wrapper {
165 return if p.optional {
166 format!("{}.map({newtype_path})", p.name)
167 } else if promoted {
168 format!("{newtype_path}({}{})", p.name, unwrap_suffix)
169 } else {
170 format!("{newtype_path}({})", p.name)
171 };
172 }
173 match &p.ty {
174 TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
175 if p.optional {
177 format!("{}.as_ref().map(|v| &v.inner)", p.name)
178 } else if promoted {
179 format!("{}{}.inner.as_ref()", p.name, unwrap_suffix)
180 } else {
181 format!("&{}.inner", p.name)
182 }
183 }
184 TypeRef::Named(_) => {
185 if p.optional {
186 format!("{}.map(Into::into)", p.name)
187 } else if promoted {
188 format!("{}{}.into()", p.name, unwrap_suffix)
189 } else {
190 format!("{}.into()", p.name)
191 }
192 }
193 TypeRef::String | TypeRef::Char => {
197 if p.optional {
198 if p.is_ref {
199 format!("{}.as_deref()", p.name)
200 } else {
201 p.name.clone()
202 }
203 } else if promoted {
204 if p.is_ref {
205 format!("&{}{}", p.name, unwrap_suffix)
206 } else {
207 format!("{}{}", p.name, unwrap_suffix)
208 }
209 } else if p.is_ref {
210 format!("&{}", p.name)
211 } else {
212 p.name.clone()
213 }
214 }
215 TypeRef::Path => {
217 if p.optional && p.is_ref {
218 format!("{}.as_deref().map(std::path::Path::new)", p.name)
219 } else if p.optional {
220 format!("{}.map(std::path::PathBuf::from)", p.name)
221 } else if promoted {
222 format!("std::path::PathBuf::from({}{})", p.name, unwrap_suffix)
223 } else if p.is_ref {
224 format!("std::path::Path::new(&{})", p.name)
225 } else {
226 format!("std::path::PathBuf::from({})", p.name)
227 }
228 }
229 TypeRef::Bytes => {
230 if p.optional {
231 if p.is_ref {
232 format!("{}.as_deref()", p.name)
233 } else {
234 p.name.clone()
235 }
236 } else if promoted {
237 format!("&{}{}", p.name, unwrap_suffix)
238 } else {
239 format!("&{}", p.name)
240 }
241 }
242 TypeRef::Duration => {
244 if p.optional {
245 format!("{}.map(std::time::Duration::from_millis)", p.name)
246 } else if promoted {
247 format!("std::time::Duration::from_millis({}{})", p.name, unwrap_suffix)
248 } else {
249 format!("std::time::Duration::from_millis({})", p.name)
250 }
251 }
252 _ => {
253 if promoted {
254 format!("{}{}", p.name, unwrap_suffix)
255 } else {
256 p.name.clone()
257 }
258 }
259 }
260 })
261 .collect::<Vec<_>>()
262 .join(", ")
263}
264
265pub fn gen_call_args_with_let_bindings(params: &[ParamDef], opaque_types: &AHashSet<String>) -> String {
268 params
269 .iter()
270 .enumerate()
271 .map(|(idx, p)| {
272 let promoted = crate::shared::is_promoted_optional(params, idx);
273 let unwrap_suffix = if promoted {
274 format!(".expect(\"'{}' is required\")", p.name)
275 } else {
276 String::new()
277 };
278 if let Some(newtype_path) = &p.newtype_wrapper {
280 return if p.optional {
281 format!("{}.map({newtype_path})", p.name)
282 } else if promoted {
283 format!("{newtype_path}({}{})", p.name, unwrap_suffix)
284 } else {
285 format!("{newtype_path}({})", p.name)
286 };
287 }
288 match &p.ty {
289 TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
290 if p.optional {
291 format!("{}.as_ref().map(|v| &v.inner)", p.name)
292 } else if promoted {
293 format!("{}{}.inner.as_ref()", p.name, unwrap_suffix)
294 } else {
295 format!("&{}.inner", p.name)
296 }
297 }
298 TypeRef::Named(_) if p.is_ref => {
299 format!("&{}_core", p.name)
300 }
301 TypeRef::Named(_) => {
302 format!("{}_core", p.name)
303 }
304 TypeRef::String | TypeRef::Char => {
305 if p.optional {
306 if p.is_ref {
307 format!("{}.as_deref()", p.name)
308 } else {
309 p.name.clone()
310 }
311 } else if promoted {
312 if p.is_ref {
313 format!("&{}{}", p.name, unwrap_suffix)
314 } else {
315 format!("{}{}", p.name, unwrap_suffix)
316 }
317 } else if p.is_ref {
318 format!("&{}", p.name)
319 } else {
320 p.name.clone()
321 }
322 }
323 TypeRef::Path => {
324 if promoted {
325 format!("std::path::PathBuf::from({}{})", p.name, unwrap_suffix)
326 } else if p.optional && p.is_ref {
327 format!("{}.as_deref().map(std::path::Path::new)", p.name)
328 } else if p.optional {
329 format!("{}.map(std::path::PathBuf::from)", p.name)
330 } else if p.is_ref {
331 format!("std::path::Path::new(&{})", p.name)
332 } else {
333 format!("std::path::PathBuf::from({})", p.name)
334 }
335 }
336 TypeRef::Bytes => {
337 if p.optional {
338 if p.is_ref {
339 format!("{}.as_deref()", p.name)
340 } else {
341 p.name.clone()
342 }
343 } else if promoted {
344 format!("&{}{}", p.name, unwrap_suffix)
345 } else {
346 format!("&{}", p.name)
347 }
348 }
349 TypeRef::Duration => {
350 if p.optional {
351 format!("{}.map(std::time::Duration::from_millis)", p.name)
352 } else if promoted {
353 format!("std::time::Duration::from_millis({}{})", p.name, unwrap_suffix)
354 } else {
355 format!("std::time::Duration::from_millis({})", p.name)
356 }
357 }
358 _ => {
359 if promoted {
360 format!("{}{}", p.name, unwrap_suffix)
361 } else {
362 p.name.clone()
363 }
364 }
365 }
366 })
367 .collect::<Vec<_>>()
368 .join(", ")
369}
370
371pub fn gen_named_let_bindings_pub(params: &[ParamDef], opaque_types: &AHashSet<String>) -> String {
373 gen_named_let_bindings(params, opaque_types)
374}
375
376pub(super) fn gen_named_let_bindings(params: &[ParamDef], opaque_types: &AHashSet<String>) -> String {
377 let mut bindings = String::new();
378 for (idx, p) in params.iter().enumerate() {
379 if let TypeRef::Named(name) = &p.ty {
380 if !opaque_types.contains(name.as_str()) {
381 let promoted = crate::shared::is_promoted_optional(params, idx);
382 if p.optional {
383 write!(bindings, "let {}_core = {}.map(Into::into);\n ", p.name, p.name).ok();
384 } else if promoted {
385 write!(
387 bindings,
388 "let {}_core = {}.expect(\"'{}' is required\").into();\n ",
389 p.name, p.name, p.name
390 )
391 .ok();
392 } else {
393 write!(bindings, "let {}_core = {}.into();\n ", p.name, p.name).ok();
394 }
395 }
396 }
397 }
398 bindings
399}
400
401pub fn gen_serde_let_bindings(
406 params: &[ParamDef],
407 opaque_types: &AHashSet<String>,
408 core_import: &str,
409 err_conv: &str,
410 indent: &str,
411) -> String {
412 let mut bindings = String::new();
413 for p in params {
414 if let TypeRef::Named(name) = &p.ty {
415 if !opaque_types.contains(name.as_str()) {
416 let core_path = format!("{}::{}", core_import, name);
417 if p.optional {
418 write!(
419 bindings,
420 "let {name}_core: Option<{core_path}> = {name}.map(|v| {{\n\
421 {indent} let json = serde_json::to_string(&v){err_conv}?;\n\
422 {indent} serde_json::from_str(&json){err_conv}\n\
423 {indent}}}).transpose()?;\n{indent}",
424 name = p.name,
425 core_path = core_path,
426 err_conv = err_conv,
427 indent = indent,
428 )
429 .ok();
430 } else {
431 write!(
432 bindings,
433 "let {name}_json = serde_json::to_string(&{name}){err_conv}?;\n\
434 {indent}let {name}_core: {core_path} = serde_json::from_str(&{name}_json){err_conv}?;\n{indent}",
435 name = p.name,
436 core_path = core_path,
437 err_conv = err_conv,
438 indent = indent,
439 )
440 .ok();
441 }
442 }
443 }
444 }
445 bindings
446}
447
448pub fn has_named_params(params: &[ParamDef], opaque_types: &AHashSet<String>) -> bool {
450 params
451 .iter()
452 .any(|p| matches!(&p.ty, TypeRef::Named(name) if !opaque_types.contains(name.as_str())))
453}
454
455pub fn is_simple_non_opaque_param(ty: &TypeRef) -> bool {
458 match ty {
459 TypeRef::Primitive(_)
460 | TypeRef::String
461 | TypeRef::Char
462 | TypeRef::Bytes
463 | TypeRef::Path
464 | TypeRef::Unit
465 | TypeRef::Duration => true,
466 TypeRef::Optional(inner) => is_simple_non_opaque_param(inner),
467 _ => false,
468 }
469}
470
471pub fn gen_lossy_binding_to_core_fields(typ: &TypeDef, core_import: &str) -> String {
475 gen_lossy_binding_to_core_fields_inner(typ, core_import, false)
476}
477
478pub fn gen_lossy_binding_to_core_fields_mut(typ: &TypeDef, core_import: &str) -> String {
480 gen_lossy_binding_to_core_fields_inner(typ, core_import, true)
481}
482
483fn gen_lossy_binding_to_core_fields_inner(typ: &TypeDef, core_import: &str, needs_mut: bool) -> String {
484 let core_path = crate::conversions::core_type_path(typ, core_import);
485 let mut_kw = if needs_mut { "mut " } else { "" };
486 let mut out = format!("let {mut_kw}core_self = {core_path} {{\n");
487 for field in &typ.fields {
488 let name = &field.name;
489 if field.sanitized {
490 writeln!(out, " {name}: Default::default(),").ok();
491 } else {
492 let expr = match &field.ty {
493 TypeRef::Primitive(_) => format!("self.{name}"),
494 TypeRef::Duration => {
495 if field.optional {
496 format!("self.{name}.map(std::time::Duration::from_secs)")
497 } else {
498 format!("std::time::Duration::from_millis(self.{name})")
499 }
500 }
501 TypeRef::String | TypeRef::Char | TypeRef::Bytes => format!("self.{name}.clone()"),
502 TypeRef::Path => {
503 if field.optional {
504 format!("self.{name}.clone().map(Into::into)")
505 } else {
506 format!("self.{name}.clone().into()")
507 }
508 }
509 TypeRef::Named(_) => {
510 if field.optional {
511 format!("self.{name}.clone().map(Into::into)")
512 } else {
513 format!("self.{name}.clone().into()")
514 }
515 }
516 TypeRef::Vec(inner) => match inner.as_ref() {
517 TypeRef::Named(_) => {
518 if field.optional {
519 format!("self.{name}.clone().map(|v| v.into_iter().map(Into::into).collect())")
521 } else {
522 format!("self.{name}.clone().into_iter().map(Into::into).collect()")
523 }
524 }
525 _ => format!("self.{name}.clone()"),
526 },
527 TypeRef::Optional(inner) => match inner.as_ref() {
528 TypeRef::Named(_) => {
529 format!("self.{name}.clone().map(Into::into)")
530 }
531 TypeRef::Vec(vi) if matches!(vi.as_ref(), TypeRef::Named(_)) => {
532 format!("self.{name}.clone().map(|v| v.into_iter().map(Into::into).collect())")
533 }
534 _ => format!("self.{name}.clone()"),
535 },
536 TypeRef::Map(_, v) => match v.as_ref() {
537 TypeRef::Json => {
538 if field.optional {
540 format!(
541 "self.{name}.clone().map(|m| m.into_iter().map(|(k, v)| \
542 (k, serde_json::from_str(&v).unwrap_or(serde_json::Value::String(v)))).collect())"
543 )
544 } else {
545 format!(
546 "self.{name}.clone().into_iter().map(|(k, v)| \
547 (k, serde_json::from_str(&v).unwrap_or(serde_json::Value::String(v)))).collect()"
548 )
549 }
550 }
551 _ => {
553 if field.optional {
554 format!("self.{name}.clone().map(|m| m.into_iter().collect())")
555 } else {
556 format!("self.{name}.clone().into_iter().collect()")
557 }
558 }
559 },
560 TypeRef::Unit => format!("self.{name}.clone()"),
561 TypeRef::Json => {
562 if field.optional {
564 format!("self.{name}.as_ref().and_then(|s| serde_json::from_str(s).ok())")
565 } else {
566 format!("serde_json::from_str(&self.{name}).unwrap_or_default()")
567 }
568 }
569 };
570 let expr = if let Some(newtype_path) = &field.newtype_wrapper {
575 match &field.ty {
576 TypeRef::Optional(_) => format!("({expr}).map({newtype_path})"),
577 TypeRef::Vec(_) => format!("({expr}).into_iter().map({newtype_path}).collect()"),
578 _ if field.optional => format!("({expr}).map({newtype_path})"),
579 _ => format!("{newtype_path}({expr})"),
580 }
581 } else {
582 expr
583 };
584 writeln!(out, " {name}: {expr},").ok();
585 }
586 }
587 if typ.has_stripped_cfg_fields {
589 out.push_str(" ..Default::default()\n");
590 }
591 out.push_str(" };\n ");
592 out
593}
594
595pub fn gen_async_body(
610 core_call: &str,
611 cfg: &RustBindingConfig,
612 has_error: bool,
613 return_wrap: &str,
614 is_opaque: bool,
615 inner_clone_line: &str,
616 is_unit_return: bool,
617) -> String {
618 let pattern_body = match cfg.async_pattern {
619 AsyncPattern::Pyo3FutureIntoPy => {
620 let result_handling = if has_error {
621 format!(
622 "let result = {core_call}.await\n \
623 .map_err(|e| PyErr::new::<PyRuntimeError, _>(e.to_string()))?;"
624 )
625 } else if is_unit_return {
626 format!("{core_call}.await;")
627 } else {
628 format!("let result = {core_call}.await;")
629 };
630 let ok_expr = if is_unit_return && !has_error {
631 "()"
632 } else {
633 return_wrap
634 };
635 format!(
636 "pyo3_async_runtimes::tokio::future_into_py(py, async move {{\n \
637 {result_handling}\n \
638 Ok({ok_expr})\n }})"
639 )
640 }
641 AsyncPattern::WasmNativeAsync => {
642 let result_handling = if has_error {
643 format!(
644 "let result = {core_call}.await\n \
645 .map_err(|e| JsValue::from_str(&e.to_string()))?;"
646 )
647 } else if is_unit_return {
648 format!("{core_call}.await;")
649 } else {
650 format!("let result = {core_call}.await;")
651 };
652 let ok_expr = if is_unit_return && !has_error {
653 "()"
654 } else {
655 return_wrap
656 };
657 format!(
658 "{result_handling}\n \
659 Ok({ok_expr})"
660 )
661 }
662 AsyncPattern::NapiNativeAsync => {
663 let result_handling = if has_error {
664 format!(
665 "let result = {core_call}.await\n \
666 .map_err(|e| napi::Error::new(napi::Status::GenericFailure, e.to_string()))?;"
667 )
668 } else if is_unit_return {
669 format!("{core_call}.await;")
670 } else {
671 format!("let result = {core_call}.await;")
672 };
673 if !has_error && !is_unit_return {
674 format!(
676 "{result_handling}\n \
677 {return_wrap}"
678 )
679 } else {
680 let ok_expr = if is_unit_return && !has_error {
681 "()"
682 } else {
683 return_wrap
684 };
685 format!(
686 "{result_handling}\n \
687 Ok({ok_expr})"
688 )
689 }
690 }
691 AsyncPattern::TokioBlockOn => {
692 if has_error {
693 if is_opaque {
694 format!(
695 "let rt = tokio::runtime::Runtime::new()?;\n \
696 let result = rt.block_on(async {{ {core_call}.await.map_err(|e| e.into()) }})?;\n \
697 {return_wrap}"
698 )
699 } else {
700 format!(
701 "let rt = tokio::runtime::Runtime::new()?;\n \
702 rt.block_on(async {{ {core_call}.await.map_err(|e| e.into()) }})"
703 )
704 }
705 } else if is_opaque {
706 if is_unit_return {
707 format!(
708 "let rt = tokio::runtime::Runtime::new()?;\n \
709 rt.block_on(async {{ {core_call}.await }});"
710 )
711 } else {
712 format!(
713 "let rt = tokio::runtime::Runtime::new()?;\n \
714 let result = rt.block_on(async {{ {core_call}.await }});\n \
715 {return_wrap}"
716 )
717 }
718 } else {
719 format!(
720 "let rt = tokio::runtime::Runtime::new()?;\n \
721 rt.block_on(async {{ {core_call}.await }})"
722 )
723 }
724 }
725 AsyncPattern::None => "todo!(\"async not supported by backend\")".to_string(),
726 };
727 if inner_clone_line.is_empty() {
728 pattern_body
729 } else {
730 format!("{inner_clone_line}{pattern_body}")
731 }
732}
733
734pub fn gen_unimplemented_body(
737 return_type: &TypeRef,
738 fn_name: &str,
739 has_error: bool,
740 cfg: &RustBindingConfig,
741 params: &[ParamDef],
742) -> String {
743 let suppress = if params.is_empty() {
745 String::new()
746 } else {
747 let names: Vec<&str> = params.iter().map(|p| p.name.as_str()).collect();
748 if names.len() == 1 {
749 format!("let _ = {};\n ", names[0])
750 } else {
751 format!("let _ = ({});\n ", names.join(", "))
752 }
753 };
754 let err_msg = format!("Not implemented: {fn_name}");
755 let body = if has_error {
756 match cfg.async_pattern {
758 AsyncPattern::Pyo3FutureIntoPy => {
759 format!("Err(pyo3::exceptions::PyNotImplementedError::new_err(\"{err_msg}\"))")
760 }
761 AsyncPattern::NapiNativeAsync => {
762 format!("Err(napi::Error::new(napi::Status::GenericFailure, \"{err_msg}\"))")
763 }
764 AsyncPattern::WasmNativeAsync => {
765 format!("Err(JsValue::from_str(\"{err_msg}\"))")
766 }
767 _ => format!("Err(\"{err_msg}\".to_string())"),
768 }
769 } else {
770 match return_type {
772 TypeRef::Unit => "()".to_string(),
773 TypeRef::String | TypeRef::Char | TypeRef::Path => format!("String::from(\"[unimplemented: {fn_name}]\")"),
774 TypeRef::Bytes => "Vec::new()".to_string(),
775 TypeRef::Primitive(p) => match p {
776 alef_core::ir::PrimitiveType::Bool => "false".to_string(),
777 _ => "0".to_string(),
778 },
779 TypeRef::Optional(_) => "None".to_string(),
780 TypeRef::Vec(_) => "Vec::new()".to_string(),
781 TypeRef::Map(_, _) => "Default::default()".to_string(),
782 TypeRef::Duration => "0".to_string(),
783 TypeRef::Named(_) | TypeRef::Json => {
784 format!(
787 "compile_error!(\"alef: {fn_name} returns a Named/Json type but has no error variant — cannot auto-delegate\")"
788 )
789 }
790 }
791 };
792 format!("{suppress}{body}")
793}