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 if p.is_ref && p.optional {
256 format!("{}.as_deref()", p.name)
259 } else if p.is_ref {
260 format!("&{}", p.name)
261 } else {
262 p.name.clone()
263 }
264 }
265 }
266 })
267 .collect::<Vec<_>>()
268 .join(", ")
269}
270
271pub fn gen_call_args_with_let_bindings(params: &[ParamDef], opaque_types: &AHashSet<String>) -> String {
274 params
275 .iter()
276 .enumerate()
277 .map(|(idx, p)| {
278 let promoted = crate::shared::is_promoted_optional(params, idx);
279 let unwrap_suffix = if promoted {
280 format!(".expect(\"'{}' is required\")", p.name)
281 } else {
282 String::new()
283 };
284 if let Some(newtype_path) = &p.newtype_wrapper {
286 return if p.optional {
287 format!("{}.map({newtype_path})", p.name)
288 } else if promoted {
289 format!("{newtype_path}({}{})", p.name, unwrap_suffix)
290 } else {
291 format!("{newtype_path}({})", p.name)
292 };
293 }
294 match &p.ty {
295 TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
296 if p.optional {
297 format!("{}.as_ref().map(|v| &v.inner)", p.name)
298 } else if promoted {
299 format!("{}{}.inner.as_ref()", p.name, unwrap_suffix)
300 } else {
301 format!("&{}.inner", p.name)
302 }
303 }
304 TypeRef::Named(_) if p.is_ref => {
305 format!("&{}_core", p.name)
306 }
307 TypeRef::Named(_) => {
308 format!("{}_core", p.name)
309 }
310 TypeRef::String | TypeRef::Char => {
311 if p.optional {
312 if p.is_ref {
313 format!("{}.as_deref()", p.name)
314 } else {
315 p.name.clone()
316 }
317 } else if promoted {
318 if p.is_ref {
319 format!("&{}{}", p.name, unwrap_suffix)
320 } else {
321 format!("{}{}", p.name, unwrap_suffix)
322 }
323 } else if p.is_ref {
324 format!("&{}", p.name)
325 } else {
326 p.name.clone()
327 }
328 }
329 TypeRef::Path => {
330 if promoted {
331 format!("std::path::PathBuf::from({}{})", p.name, unwrap_suffix)
332 } else if p.optional && p.is_ref {
333 format!("{}.as_deref().map(std::path::Path::new)", p.name)
334 } else if p.optional {
335 format!("{}.map(std::path::PathBuf::from)", p.name)
336 } else if p.is_ref {
337 format!("std::path::Path::new(&{})", p.name)
338 } else {
339 format!("std::path::PathBuf::from({})", p.name)
340 }
341 }
342 TypeRef::Bytes => {
343 if p.optional {
344 if p.is_ref {
345 format!("{}.as_deref()", p.name)
346 } else {
347 p.name.clone()
348 }
349 } else if promoted {
350 format!("&{}{}", p.name, unwrap_suffix)
351 } else {
352 format!("&{}", p.name)
353 }
354 }
355 TypeRef::Duration => {
356 if p.optional {
357 format!("{}.map(std::time::Duration::from_millis)", p.name)
358 } else if promoted {
359 format!("std::time::Duration::from_millis({}{})", p.name, unwrap_suffix)
360 } else {
361 format!("std::time::Duration::from_millis({})", p.name)
362 }
363 }
364 _ => {
365 if promoted {
366 format!("{}{}", p.name, unwrap_suffix)
367 } else if p.is_ref && p.optional {
368 format!("{}.as_deref()", p.name)
369 } else if p.is_ref {
370 format!("&{}", p.name)
371 } else {
372 p.name.clone()
373 }
374 }
375 }
376 })
377 .collect::<Vec<_>>()
378 .join(", ")
379}
380
381pub fn gen_named_let_bindings_pub(params: &[ParamDef], opaque_types: &AHashSet<String>) -> String {
383 gen_named_let_bindings(params, opaque_types)
384}
385
386pub(super) fn gen_named_let_bindings(params: &[ParamDef], opaque_types: &AHashSet<String>) -> String {
387 let mut bindings = String::new();
388 for (idx, p) in params.iter().enumerate() {
389 if let TypeRef::Named(name) = &p.ty {
390 if !opaque_types.contains(name.as_str()) {
391 let promoted = crate::shared::is_promoted_optional(params, idx);
392 if p.optional {
393 write!(bindings, "let {}_core = {}.map(Into::into);\n ", p.name, p.name).ok();
394 } else if promoted {
395 write!(
397 bindings,
398 "let {}_core = {}.expect(\"'{}' is required\").into();\n ",
399 p.name, p.name, p.name
400 )
401 .ok();
402 } else {
403 write!(bindings, "let {}_core = {}.into();\n ", p.name, p.name).ok();
404 }
405 }
406 }
407 }
408 bindings
409}
410
411pub fn gen_serde_let_bindings(
416 params: &[ParamDef],
417 opaque_types: &AHashSet<String>,
418 core_import: &str,
419 err_conv: &str,
420 indent: &str,
421) -> String {
422 let mut bindings = String::new();
423 for p in params {
424 if let TypeRef::Named(name) = &p.ty {
425 if !opaque_types.contains(name.as_str()) {
426 let core_path = format!("{}::{}", core_import, name);
427 if p.optional {
428 write!(
429 bindings,
430 "let {name}_core: Option<{core_path}> = {name}.map(|v| {{\n\
431 {indent} let json = serde_json::to_string(&v){err_conv}?;\n\
432 {indent} serde_json::from_str(&json){err_conv}\n\
433 {indent}}}).transpose()?;\n{indent}",
434 name = p.name,
435 core_path = core_path,
436 err_conv = err_conv,
437 indent = indent,
438 )
439 .ok();
440 } else {
441 write!(
442 bindings,
443 "let {name}_json = serde_json::to_string(&{name}){err_conv}?;\n\
444 {indent}let {name}_core: {core_path} = serde_json::from_str(&{name}_json){err_conv}?;\n{indent}",
445 name = p.name,
446 core_path = core_path,
447 err_conv = err_conv,
448 indent = indent,
449 )
450 .ok();
451 }
452 }
453 }
454 }
455 bindings
456}
457
458pub fn has_named_params(params: &[ParamDef], opaque_types: &AHashSet<String>) -> bool {
460 params
461 .iter()
462 .any(|p| matches!(&p.ty, TypeRef::Named(name) if !opaque_types.contains(name.as_str())))
463}
464
465pub fn is_simple_non_opaque_param(ty: &TypeRef) -> bool {
468 match ty {
469 TypeRef::Primitive(_)
470 | TypeRef::String
471 | TypeRef::Char
472 | TypeRef::Bytes
473 | TypeRef::Path
474 | TypeRef::Unit
475 | TypeRef::Duration => true,
476 TypeRef::Optional(inner) => is_simple_non_opaque_param(inner),
477 _ => false,
478 }
479}
480
481pub fn gen_lossy_binding_to_core_fields(typ: &TypeDef, core_import: &str) -> String {
485 gen_lossy_binding_to_core_fields_inner(typ, core_import, false)
486}
487
488pub fn gen_lossy_binding_to_core_fields_mut(typ: &TypeDef, core_import: &str) -> String {
490 gen_lossy_binding_to_core_fields_inner(typ, core_import, true)
491}
492
493fn gen_lossy_binding_to_core_fields_inner(typ: &TypeDef, core_import: &str, needs_mut: bool) -> String {
494 let core_path = crate::conversions::core_type_path(typ, core_import);
495 let mut_kw = if needs_mut { "mut " } else { "" };
496 let mut out = format!("let {mut_kw}core_self = {core_path} {{\n");
497 for field in &typ.fields {
498 let name = &field.name;
499 if field.sanitized {
500 writeln!(out, " {name}: Default::default(),").ok();
501 } else {
502 let expr = match &field.ty {
503 TypeRef::Primitive(_) => format!("self.{name}"),
504 TypeRef::Duration => {
505 if field.optional {
506 format!("self.{name}.map(std::time::Duration::from_secs)")
507 } else {
508 format!("std::time::Duration::from_millis(self.{name})")
509 }
510 }
511 TypeRef::String | TypeRef::Char | TypeRef::Bytes => format!("self.{name}.clone()"),
512 TypeRef::Path => {
513 if field.optional {
514 format!("self.{name}.clone().map(Into::into)")
515 } else {
516 format!("self.{name}.clone().into()")
517 }
518 }
519 TypeRef::Named(_) => {
520 if field.optional {
521 format!("self.{name}.clone().map(Into::into)")
522 } else {
523 format!("self.{name}.clone().into()")
524 }
525 }
526 TypeRef::Vec(inner) => match inner.as_ref() {
527 TypeRef::Named(_) => {
528 if field.optional {
529 format!("self.{name}.clone().map(|v| v.into_iter().map(Into::into).collect())")
531 } else {
532 format!("self.{name}.clone().into_iter().map(Into::into).collect()")
533 }
534 }
535 _ => format!("self.{name}.clone()"),
536 },
537 TypeRef::Optional(inner) => match inner.as_ref() {
538 TypeRef::Named(_) => {
539 format!("self.{name}.clone().map(Into::into)")
540 }
541 TypeRef::Vec(vi) if matches!(vi.as_ref(), TypeRef::Named(_)) => {
542 format!("self.{name}.clone().map(|v| v.into_iter().map(Into::into).collect())")
543 }
544 _ => format!("self.{name}.clone()"),
545 },
546 TypeRef::Map(_, v) => match v.as_ref() {
547 TypeRef::Json => {
548 if field.optional {
550 format!(
551 "self.{name}.clone().map(|m| m.into_iter().map(|(k, v)| \
552 (k, serde_json::from_str(&v).unwrap_or(serde_json::Value::String(v)))).collect())"
553 )
554 } else {
555 format!(
556 "self.{name}.clone().into_iter().map(|(k, v)| \
557 (k, serde_json::from_str(&v).unwrap_or(serde_json::Value::String(v)))).collect()"
558 )
559 }
560 }
561 _ => {
563 if field.optional {
564 format!("self.{name}.clone().map(|m| m.into_iter().collect())")
565 } else {
566 format!("self.{name}.clone().into_iter().collect()")
567 }
568 }
569 },
570 TypeRef::Unit => format!("self.{name}.clone()"),
571 TypeRef::Json => {
572 if field.optional {
574 format!("self.{name}.as_ref().and_then(|s| serde_json::from_str(s).ok())")
575 } else {
576 format!("serde_json::from_str(&self.{name}).unwrap_or_default()")
577 }
578 }
579 };
580 let expr = if let Some(newtype_path) = &field.newtype_wrapper {
585 match &field.ty {
586 TypeRef::Optional(_) => format!("({expr}).map({newtype_path})"),
587 TypeRef::Vec(_) => format!("({expr}).into_iter().map({newtype_path}).collect()"),
588 _ if field.optional => format!("({expr}).map({newtype_path})"),
589 _ => format!("{newtype_path}({expr})"),
590 }
591 } else {
592 expr
593 };
594 writeln!(out, " {name}: {expr},").ok();
595 }
596 }
597 if typ.has_stripped_cfg_fields {
599 out.push_str(" ..Default::default()\n");
600 }
601 out.push_str(" };\n ");
602 out
603}
604
605pub fn gen_async_body(
620 core_call: &str,
621 cfg: &RustBindingConfig,
622 has_error: bool,
623 return_wrap: &str,
624 is_opaque: bool,
625 inner_clone_line: &str,
626 is_unit_return: bool,
627) -> String {
628 let pattern_body = match cfg.async_pattern {
629 AsyncPattern::Pyo3FutureIntoPy => {
630 let result_handling = if has_error {
631 format!(
632 "let result = {core_call}.await\n \
633 .map_err(|e| PyErr::new::<PyRuntimeError, _>(e.to_string()))?;"
634 )
635 } else if is_unit_return {
636 format!("{core_call}.await;")
637 } else {
638 format!("let result = {core_call}.await;")
639 };
640 let ok_expr = if is_unit_return && !has_error {
641 "()"
642 } else {
643 return_wrap
644 };
645 format!(
646 "pyo3_async_runtimes::tokio::future_into_py(py, async move {{\n \
647 {result_handling}\n \
648 Ok({ok_expr})\n }})"
649 )
650 }
651 AsyncPattern::WasmNativeAsync => {
652 let result_handling = if has_error {
653 format!(
654 "let result = {core_call}.await\n \
655 .map_err(|e| JsValue::from_str(&e.to_string()))?;"
656 )
657 } else if is_unit_return {
658 format!("{core_call}.await;")
659 } else {
660 format!("let result = {core_call}.await;")
661 };
662 let ok_expr = if is_unit_return && !has_error {
663 "()"
664 } else {
665 return_wrap
666 };
667 format!(
668 "{result_handling}\n \
669 Ok({ok_expr})"
670 )
671 }
672 AsyncPattern::NapiNativeAsync => {
673 let result_handling = if has_error {
674 format!(
675 "let result = {core_call}.await\n \
676 .map_err(|e| napi::Error::new(napi::Status::GenericFailure, e.to_string()))?;"
677 )
678 } else if is_unit_return {
679 format!("{core_call}.await;")
680 } else {
681 format!("let result = {core_call}.await;")
682 };
683 if !has_error && !is_unit_return {
684 format!(
686 "{result_handling}\n \
687 {return_wrap}"
688 )
689 } else {
690 let ok_expr = if is_unit_return && !has_error {
691 "()"
692 } else {
693 return_wrap
694 };
695 format!(
696 "{result_handling}\n \
697 Ok({ok_expr})"
698 )
699 }
700 }
701 AsyncPattern::TokioBlockOn => {
702 if has_error {
703 if is_opaque {
704 format!(
705 "let rt = tokio::runtime::Runtime::new()?;\n \
706 let result = rt.block_on(async {{ {core_call}.await.map_err(|e| e.into()) }})?;\n \
707 {return_wrap}"
708 )
709 } else {
710 format!(
711 "let rt = tokio::runtime::Runtime::new()?;\n \
712 rt.block_on(async {{ {core_call}.await.map_err(|e| e.into()) }})"
713 )
714 }
715 } else if is_opaque {
716 if is_unit_return {
717 format!(
718 "let rt = tokio::runtime::Runtime::new()?;\n \
719 rt.block_on(async {{ {core_call}.await }});"
720 )
721 } else {
722 format!(
723 "let rt = tokio::runtime::Runtime::new()?;\n \
724 let result = rt.block_on(async {{ {core_call}.await }});\n \
725 {return_wrap}"
726 )
727 }
728 } else {
729 format!(
730 "let rt = tokio::runtime::Runtime::new()?;\n \
731 rt.block_on(async {{ {core_call}.await }})"
732 )
733 }
734 }
735 AsyncPattern::None => "todo!(\"async not supported by backend\")".to_string(),
736 };
737 if inner_clone_line.is_empty() {
738 pattern_body
739 } else {
740 format!("{inner_clone_line}{pattern_body}")
741 }
742}
743
744pub fn gen_unimplemented_body(
747 return_type: &TypeRef,
748 fn_name: &str,
749 has_error: bool,
750 cfg: &RustBindingConfig,
751 params: &[ParamDef],
752) -> String {
753 let suppress = if params.is_empty() {
755 String::new()
756 } else {
757 let names: Vec<&str> = params.iter().map(|p| p.name.as_str()).collect();
758 if names.len() == 1 {
759 format!("let _ = {};\n ", names[0])
760 } else {
761 format!("let _ = ({});\n ", names.join(", "))
762 }
763 };
764 let err_msg = format!("Not implemented: {fn_name}");
765 let body = if has_error {
766 match cfg.async_pattern {
768 AsyncPattern::Pyo3FutureIntoPy => {
769 format!("Err(pyo3::exceptions::PyNotImplementedError::new_err(\"{err_msg}\"))")
770 }
771 AsyncPattern::NapiNativeAsync => {
772 format!("Err(napi::Error::new(napi::Status::GenericFailure, \"{err_msg}\"))")
773 }
774 AsyncPattern::WasmNativeAsync => {
775 format!("Err(JsValue::from_str(\"{err_msg}\"))")
776 }
777 _ => format!("Err(\"{err_msg}\".to_string())"),
778 }
779 } else {
780 match return_type {
782 TypeRef::Unit => "()".to_string(),
783 TypeRef::String | TypeRef::Char | TypeRef::Path => format!("String::from(\"[unimplemented: {fn_name}]\")"),
784 TypeRef::Bytes => "Vec::new()".to_string(),
785 TypeRef::Primitive(p) => match p {
786 alef_core::ir::PrimitiveType::Bool => "false".to_string(),
787 _ => "0".to_string(),
788 },
789 TypeRef::Optional(_) => "None".to_string(),
790 TypeRef::Vec(_) => "Vec::new()".to_string(),
791 TypeRef::Map(_, _) => "Default::default()".to_string(),
792 TypeRef::Duration => "0".to_string(),
793 TypeRef::Named(_) | TypeRef::Json => {
794 format!(
797 "compile_error!(\"alef: {fn_name} returns a Named/Json type but has no error variant — cannot auto-delegate\")"
798 )
799 }
800 }
801 };
802 format!("{suppress}{body}")
803}