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 gen_call_args(params: &[ParamDef], opaque_types: &AHashSet<String>) -> String {
140 params
141 .iter()
142 .enumerate()
143 .map(|(idx, p)| {
144 let promoted = crate::shared::is_promoted_optional(params, idx);
145 let unwrap_suffix = if promoted {
147 format!(".expect(\"'{}' is required\")", p.name)
148 } else {
149 String::new()
150 };
151 if let Some(newtype_path) = &p.newtype_wrapper {
154 return if p.optional {
155 format!("{}.map({newtype_path})", p.name)
156 } else if promoted {
157 format!("{newtype_path}({}{})", p.name, unwrap_suffix)
158 } else {
159 format!("{newtype_path}({})", p.name)
160 };
161 }
162 match &p.ty {
163 TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
164 if p.optional {
166 format!("{}.as_ref().map(|v| &v.inner)", p.name)
167 } else if promoted {
168 format!("{}{}.inner.as_ref()", p.name, unwrap_suffix)
169 } else {
170 format!("&{}.inner", p.name)
171 }
172 }
173 TypeRef::Named(_) => {
174 if p.optional {
175 format!("{}.map(Into::into)", p.name)
176 } else if promoted {
177 format!("{}{}.into()", p.name, unwrap_suffix)
178 } else {
179 format!("{}.into()", p.name)
180 }
181 }
182 TypeRef::String | TypeRef::Char => {
186 if p.optional {
187 if p.is_ref {
188 format!("{}.as_deref()", p.name)
189 } else {
190 p.name.clone()
191 }
192 } else if promoted {
193 if p.is_ref {
194 format!("&{}{}", p.name, unwrap_suffix)
195 } else {
196 format!("{}{}", p.name, unwrap_suffix)
197 }
198 } else if p.is_ref {
199 format!("&{}", p.name)
200 } else {
201 p.name.clone()
202 }
203 }
204 TypeRef::Path => {
206 if promoted {
207 format!("std::path::PathBuf::from({}{})", p.name, unwrap_suffix)
208 } else {
209 format!("std::path::PathBuf::from({})", p.name)
210 }
211 }
212 TypeRef::Bytes => {
213 if p.optional {
214 if p.is_ref {
215 format!("{}.as_deref()", p.name)
216 } else {
217 p.name.clone()
218 }
219 } else if promoted {
220 format!("&{}{}", p.name, unwrap_suffix)
221 } else {
222 format!("&{}", p.name)
223 }
224 }
225 TypeRef::Duration => {
227 if promoted {
228 format!("std::time::Duration::from_millis({}{})", p.name, unwrap_suffix)
229 } else {
230 format!("std::time::Duration::from_millis({})", p.name)
231 }
232 }
233 _ => {
234 if promoted {
235 format!("{}{}", p.name, unwrap_suffix)
236 } else {
237 p.name.clone()
238 }
239 }
240 }
241 })
242 .collect::<Vec<_>>()
243 .join(", ")
244}
245
246pub fn gen_call_args_with_let_bindings(params: &[ParamDef], opaque_types: &AHashSet<String>) -> String {
249 params
250 .iter()
251 .enumerate()
252 .map(|(idx, p)| {
253 let promoted = crate::shared::is_promoted_optional(params, idx);
254 let unwrap_suffix = if promoted {
255 format!(".expect(\"'{}' is required\")", p.name)
256 } else {
257 String::new()
258 };
259 if let Some(newtype_path) = &p.newtype_wrapper {
261 return if p.optional {
262 format!("{}.map({newtype_path})", p.name)
263 } else if promoted {
264 format!("{newtype_path}({}{})", p.name, unwrap_suffix)
265 } else {
266 format!("{newtype_path}({})", p.name)
267 };
268 }
269 match &p.ty {
270 TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
271 if p.optional {
272 format!("{}.as_ref().map(|v| &v.inner)", p.name)
273 } else if promoted {
274 format!("{}{}.inner.as_ref()", p.name, unwrap_suffix)
275 } else {
276 format!("&{}.inner", p.name)
277 }
278 }
279 TypeRef::Named(_) if p.is_ref => {
280 format!("&{}_core", p.name)
281 }
282 TypeRef::Named(_) => {
283 format!("{}_core", p.name)
284 }
285 TypeRef::String | TypeRef::Char => {
286 if p.optional {
287 if p.is_ref {
288 format!("{}.as_deref()", p.name)
289 } else {
290 p.name.clone()
291 }
292 } else if promoted {
293 if p.is_ref {
294 format!("&{}{}", p.name, unwrap_suffix)
295 } else {
296 format!("{}{}", p.name, unwrap_suffix)
297 }
298 } else if p.is_ref {
299 format!("&{}", p.name)
300 } else {
301 p.name.clone()
302 }
303 }
304 TypeRef::Path => {
305 if promoted {
306 format!("std::path::PathBuf::from({}{})", p.name, unwrap_suffix)
307 } else {
308 format!("std::path::PathBuf::from({})", p.name)
309 }
310 }
311 TypeRef::Bytes => {
312 if p.optional {
313 if p.is_ref {
314 format!("{}.as_deref()", p.name)
315 } else {
316 p.name.clone()
317 }
318 } else if promoted {
319 format!("&{}{}", p.name, unwrap_suffix)
320 } else {
321 format!("&{}", p.name)
322 }
323 }
324 TypeRef::Duration => {
325 if promoted {
326 format!("std::time::Duration::from_millis({}{})", p.name, unwrap_suffix)
327 } else {
328 format!("std::time::Duration::from_millis({})", p.name)
329 }
330 }
331 _ => {
332 if promoted {
333 format!("{}{}", p.name, unwrap_suffix)
334 } else {
335 p.name.clone()
336 }
337 }
338 }
339 })
340 .collect::<Vec<_>>()
341 .join(", ")
342}
343
344pub fn gen_named_let_bindings_pub(params: &[ParamDef], opaque_types: &AHashSet<String>) -> String {
346 gen_named_let_bindings(params, opaque_types)
347}
348
349pub(super) fn gen_named_let_bindings(params: &[ParamDef], opaque_types: &AHashSet<String>) -> String {
350 let mut bindings = String::new();
351 for (idx, p) in params.iter().enumerate() {
352 if let TypeRef::Named(name) = &p.ty {
353 if !opaque_types.contains(name.as_str()) {
354 let promoted = crate::shared::is_promoted_optional(params, idx);
355 if p.optional {
356 write!(bindings, "let {}_core = {}.map(Into::into);\n ", p.name, p.name).ok();
357 } else if promoted {
358 write!(
360 bindings,
361 "let {}_core = {}.expect(\"'{}' is required\").into();\n ",
362 p.name, p.name, p.name
363 )
364 .ok();
365 } else {
366 write!(bindings, "let {}_core = {}.into();\n ", p.name, p.name).ok();
367 }
368 }
369 }
370 }
371 bindings
372}
373
374pub fn gen_serde_let_bindings(
379 params: &[ParamDef],
380 opaque_types: &AHashSet<String>,
381 core_import: &str,
382 err_conv: &str,
383 indent: &str,
384) -> String {
385 let mut bindings = String::new();
386 for p in params {
387 if let TypeRef::Named(name) = &p.ty {
388 if !opaque_types.contains(name.as_str()) {
389 let core_path = format!("{}::{}", core_import, name);
390 if p.optional {
391 write!(
392 bindings,
393 "let {name}_core: Option<{core_path}> = {name}.map(|v| {{\n\
394 {indent} let json = serde_json::to_string(&v){err_conv}?;\n\
395 {indent} serde_json::from_str(&json){err_conv}\n\
396 {indent}}}).transpose()?;\n{indent}",
397 name = p.name,
398 core_path = core_path,
399 err_conv = err_conv,
400 indent = indent,
401 )
402 .ok();
403 } else {
404 write!(
405 bindings,
406 "let {name}_json = serde_json::to_string(&{name}){err_conv}?;\n\
407 {indent}let {name}_core: {core_path} = serde_json::from_str(&{name}_json){err_conv}?;\n{indent}",
408 name = p.name,
409 core_path = core_path,
410 err_conv = err_conv,
411 indent = indent,
412 )
413 .ok();
414 }
415 }
416 }
417 }
418 bindings
419}
420
421pub fn has_named_params(params: &[ParamDef], opaque_types: &AHashSet<String>) -> bool {
423 params
424 .iter()
425 .any(|p| matches!(&p.ty, TypeRef::Named(name) if !opaque_types.contains(name.as_str())))
426}
427
428pub fn is_simple_non_opaque_param(ty: &TypeRef) -> bool {
431 match ty {
432 TypeRef::Primitive(_)
433 | TypeRef::String
434 | TypeRef::Char
435 | TypeRef::Bytes
436 | TypeRef::Path
437 | TypeRef::Unit
438 | TypeRef::Duration => true,
439 TypeRef::Optional(inner) => is_simple_non_opaque_param(inner),
440 _ => false,
441 }
442}
443
444pub fn gen_lossy_binding_to_core_fields(typ: &TypeDef, core_import: &str) -> String {
448 gen_lossy_binding_to_core_fields_inner(typ, core_import, false)
449}
450
451pub fn gen_lossy_binding_to_core_fields_mut(typ: &TypeDef, core_import: &str) -> String {
453 gen_lossy_binding_to_core_fields_inner(typ, core_import, true)
454}
455
456fn gen_lossy_binding_to_core_fields_inner(typ: &TypeDef, core_import: &str, needs_mut: bool) -> String {
457 let core_path = crate::conversions::core_type_path(typ, core_import);
458 let mut_kw = if needs_mut { "mut " } else { "" };
459 let mut out = format!("let {mut_kw}core_self = {core_path} {{\n");
460 for field in &typ.fields {
461 let name = &field.name;
462 if field.sanitized {
463 writeln!(out, " {name}: Default::default(),").ok();
464 } else {
465 let expr = match &field.ty {
466 TypeRef::Primitive(_) => format!("self.{name}"),
467 TypeRef::Duration => {
468 if field.optional {
469 format!("self.{name}.map(std::time::Duration::from_secs)")
470 } else {
471 format!("std::time::Duration::from_millis(self.{name})")
472 }
473 }
474 TypeRef::String | TypeRef::Char | TypeRef::Bytes => format!("self.{name}.clone()"),
475 TypeRef::Path => {
476 if field.optional {
477 format!("self.{name}.clone().map(Into::into)")
478 } else {
479 format!("self.{name}.clone().into()")
480 }
481 }
482 TypeRef::Named(_) => {
483 if field.optional {
484 format!("self.{name}.clone().map(Into::into)")
485 } else {
486 format!("self.{name}.clone().into()")
487 }
488 }
489 TypeRef::Vec(inner) => match inner.as_ref() {
490 TypeRef::Named(_) => {
491 if field.optional {
492 format!("self.{name}.clone().map(|v| v.into_iter().map(Into::into).collect())")
494 } else {
495 format!("self.{name}.clone().into_iter().map(Into::into).collect()")
496 }
497 }
498 _ => format!("self.{name}.clone()"),
499 },
500 TypeRef::Optional(inner) => match inner.as_ref() {
501 TypeRef::Named(_) => {
502 format!("self.{name}.clone().map(Into::into)")
503 }
504 TypeRef::Vec(vi) if matches!(vi.as_ref(), TypeRef::Named(_)) => {
505 format!("self.{name}.clone().map(|v| v.into_iter().map(Into::into).collect())")
506 }
507 _ => format!("self.{name}.clone()"),
508 },
509 TypeRef::Map(_, v) => match v.as_ref() {
510 TypeRef::Json => {
511 if field.optional {
513 format!(
514 "self.{name}.clone().map(|m| m.into_iter().map(|(k, v)| \
515 (k, serde_json::from_str(&v).unwrap_or(serde_json::Value::String(v)))).collect())"
516 )
517 } else {
518 format!(
519 "self.{name}.clone().into_iter().map(|(k, v)| \
520 (k, serde_json::from_str(&v).unwrap_or(serde_json::Value::String(v)))).collect()"
521 )
522 }
523 }
524 _ => format!("self.{name}.clone()"),
525 },
526 TypeRef::Unit => format!("self.{name}.clone()"),
527 TypeRef::Json => {
528 if field.optional {
530 format!("self.{name}.as_ref().and_then(|s| serde_json::from_str(s).ok())")
531 } else {
532 format!("serde_json::from_str(&self.{name}).unwrap_or_default()")
533 }
534 }
535 };
536 let expr = if let Some(newtype_path) = &field.newtype_wrapper {
541 match &field.ty {
542 TypeRef::Optional(_) => format!("({expr}).map({newtype_path})"),
543 TypeRef::Vec(_) => format!("({expr}).into_iter().map({newtype_path}).collect()"),
544 _ if field.optional => format!("({expr}).map({newtype_path})"),
545 _ => format!("{newtype_path}({expr})"),
546 }
547 } else {
548 expr
549 };
550 writeln!(out, " {name}: {expr},").ok();
551 }
552 }
553 if typ.has_stripped_cfg_fields {
555 out.push_str(" ..Default::default()\n");
556 }
557 out.push_str(" };\n ");
558 out
559}
560
561pub fn gen_async_body(
576 core_call: &str,
577 cfg: &RustBindingConfig,
578 has_error: bool,
579 return_wrap: &str,
580 is_opaque: bool,
581 inner_clone_line: &str,
582 is_unit_return: bool,
583) -> String {
584 let pattern_body = match cfg.async_pattern {
585 AsyncPattern::Pyo3FutureIntoPy => {
586 let result_handling = if has_error {
587 format!(
588 "let result = {core_call}.await\n \
589 .map_err(|e| PyErr::new::<PyRuntimeError, _>(e.to_string()))?;"
590 )
591 } else if is_unit_return {
592 format!("{core_call}.await;")
593 } else {
594 format!("let result = {core_call}.await;")
595 };
596 let ok_expr = if is_unit_return && !has_error {
597 "()"
598 } else {
599 return_wrap
600 };
601 format!(
602 "pyo3_async_runtimes::tokio::future_into_py(py, async move {{\n \
603 {result_handling}\n \
604 Ok({ok_expr})\n }})"
605 )
606 }
607 AsyncPattern::WasmNativeAsync => {
608 let result_handling = if has_error {
609 format!(
610 "let result = {core_call}.await\n \
611 .map_err(|e| JsValue::from_str(&e.to_string()))?;"
612 )
613 } else if is_unit_return {
614 format!("{core_call}.await;")
615 } else {
616 format!("let result = {core_call}.await;")
617 };
618 let ok_expr = if is_unit_return && !has_error {
619 "()"
620 } else {
621 return_wrap
622 };
623 format!(
624 "{result_handling}\n \
625 Ok({ok_expr})"
626 )
627 }
628 AsyncPattern::NapiNativeAsync => {
629 let result_handling = if has_error {
630 format!(
631 "let result = {core_call}.await\n \
632 .map_err(|e| napi::Error::new(napi::Status::GenericFailure, e.to_string()))?;"
633 )
634 } else if is_unit_return {
635 format!("{core_call}.await;")
636 } else {
637 format!("let result = {core_call}.await;")
638 };
639 if !has_error && !is_unit_return {
640 format!(
642 "{result_handling}\n \
643 {return_wrap}"
644 )
645 } else {
646 let ok_expr = if is_unit_return && !has_error {
647 "()"
648 } else {
649 return_wrap
650 };
651 format!(
652 "{result_handling}\n \
653 Ok({ok_expr})"
654 )
655 }
656 }
657 AsyncPattern::TokioBlockOn => {
658 if has_error {
659 if is_opaque {
660 format!(
661 "let rt = tokio::runtime::Runtime::new()?;\n \
662 let result = rt.block_on(async {{ {core_call}.await.map_err(|e| e.into()) }})?;\n \
663 {return_wrap}"
664 )
665 } else {
666 format!(
667 "let rt = tokio::runtime::Runtime::new()?;\n \
668 rt.block_on(async {{ {core_call}.await.map_err(|e| e.into()) }})"
669 )
670 }
671 } else if is_opaque {
672 if is_unit_return {
673 format!(
674 "let rt = tokio::runtime::Runtime::new()?;\n \
675 rt.block_on(async {{ {core_call}.await }});"
676 )
677 } else {
678 format!(
679 "let rt = tokio::runtime::Runtime::new()?;\n \
680 let result = rt.block_on(async {{ {core_call}.await }});\n \
681 {return_wrap}"
682 )
683 }
684 } else {
685 format!(
686 "let rt = tokio::runtime::Runtime::new()?;\n \
687 rt.block_on(async {{ {core_call}.await }})"
688 )
689 }
690 }
691 AsyncPattern::None => "todo!(\"async not supported by backend\")".to_string(),
692 };
693 if inner_clone_line.is_empty() {
694 pattern_body
695 } else {
696 format!("{inner_clone_line}{pattern_body}")
697 }
698}
699
700pub fn gen_unimplemented_body(
703 return_type: &TypeRef,
704 fn_name: &str,
705 has_error: bool,
706 cfg: &RustBindingConfig,
707 params: &[ParamDef],
708) -> String {
709 let suppress = if params.is_empty() {
711 String::new()
712 } else {
713 let names: Vec<&str> = params.iter().map(|p| p.name.as_str()).collect();
714 if names.len() == 1 {
715 format!("let _ = {};\n ", names[0])
716 } else {
717 format!("let _ = ({});\n ", names.join(", "))
718 }
719 };
720 let err_msg = format!("Not implemented: {fn_name}");
721 let body = if has_error {
722 match cfg.async_pattern {
724 AsyncPattern::Pyo3FutureIntoPy => {
725 format!("Err(pyo3::exceptions::PyNotImplementedError::new_err(\"{err_msg}\"))")
726 }
727 AsyncPattern::NapiNativeAsync => {
728 format!("Err(napi::Error::new(napi::Status::GenericFailure, \"{err_msg}\"))")
729 }
730 AsyncPattern::WasmNativeAsync => {
731 format!("Err(JsValue::from_str(\"{err_msg}\"))")
732 }
733 _ => format!("Err(\"{err_msg}\".to_string())"),
734 }
735 } else {
736 match return_type {
738 TypeRef::Unit => "()".to_string(),
739 TypeRef::String | TypeRef::Char | TypeRef::Path => format!("String::from(\"[unimplemented: {fn_name}]\")"),
740 TypeRef::Bytes => "Vec::new()".to_string(),
741 TypeRef::Primitive(p) => match p {
742 alef_core::ir::PrimitiveType::Bool => "false".to_string(),
743 _ => "0".to_string(),
744 },
745 TypeRef::Optional(_) => "None".to_string(),
746 TypeRef::Vec(_) => "Vec::new()".to_string(),
747 TypeRef::Map(_, _) => "Default::default()".to_string(),
748 TypeRef::Duration => "0".to_string(),
749 TypeRef::Named(_) | TypeRef::Json => {
750 format!(
753 "compile_error!(\"alef: {fn_name} returns a Named/Json type but has no error variant — cannot auto-delegate\")"
754 )
755 }
756 }
757 };
758 format!("{suppress}{body}")
759}