1use crate::generators::{AsyncPattern, RustBindingConfig};
2use ahash::AHashSet;
3use alef_core::ir::{ParamDef, TypeDef, TypeRef};
4use std::fmt::Write;
5
6pub fn wrap_return(
14 expr: &str,
15 return_type: &TypeRef,
16 type_name: &str,
17 opaque_types: &AHashSet<String>,
18 self_is_opaque: bool,
19 returns_ref: bool,
20) -> String {
21 match return_type {
22 TypeRef::Named(n) if n == type_name && self_is_opaque => {
23 if returns_ref {
24 format!("Self {{ inner: Arc::new({expr}.clone()) }}")
25 } else {
26 format!("Self {{ inner: Arc::new({expr}) }}")
27 }
28 }
29 TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
30 if returns_ref {
31 format!("{n} {{ inner: Arc::new({expr}.clone()) }}")
32 } else {
33 format!("{n} {{ inner: Arc::new({expr}) }}")
34 }
35 }
36 TypeRef::Named(_) => {
37 if returns_ref {
40 format!("{expr}.clone().into()")
41 } else {
42 format!("{expr}.into()")
43 }
44 }
45 TypeRef::String | TypeRef::Bytes => {
48 if returns_ref {
49 format!("{expr}.into()")
50 } else {
51 expr.to_string()
52 }
53 }
54 TypeRef::Path => format!("{expr}.to_string_lossy().to_string()"),
56 TypeRef::Duration => format!("{expr}.as_secs()"),
58 TypeRef::Json => format!("{expr}.to_string()"),
60 TypeRef::Optional(inner) => match inner.as_ref() {
62 TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
63 if returns_ref {
64 format!("{expr}.map(|v| {n} {{ inner: Arc::new(v.clone()) }})")
65 } else {
66 format!("{expr}.map(|v| {n} {{ inner: Arc::new(v) }})")
67 }
68 }
69 TypeRef::Named(_) => {
70 if returns_ref {
71 format!("{expr}.map(|v| v.clone().into())")
72 } else {
73 format!("{expr}.map(Into::into)")
74 }
75 }
76 TypeRef::Path => {
77 format!("{expr}.map(Into::into)")
78 }
79 TypeRef::String | TypeRef::Bytes => {
80 if returns_ref {
81 format!("{expr}.map(Into::into)")
82 } else {
83 expr.to_string()
84 }
85 }
86 TypeRef::Duration => format!("{expr}.map(|d| d.as_secs())"),
87 TypeRef::Json => format!("{expr}.map(|v| v.to_string())"),
88 _ => expr.to_string(),
89 },
90 TypeRef::Vec(inner) => match inner.as_ref() {
92 TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
93 if returns_ref {
94 format!("{expr}.into_iter().map(|v| {n} {{ inner: Arc::new(v.clone()) }}).collect()")
95 } else {
96 format!("{expr}.into_iter().map(|v| {n} {{ inner: Arc::new(v) }}).collect()")
97 }
98 }
99 TypeRef::Named(_) => {
100 if returns_ref {
101 format!("{expr}.into_iter().map(|v| v.clone().into()).collect()")
102 } else {
103 format!("{expr}.into_iter().map(Into::into).collect()")
104 }
105 }
106 TypeRef::Path => {
107 format!("{expr}.into_iter().map(Into::into).collect()")
108 }
109 TypeRef::String | TypeRef::Bytes => {
110 if returns_ref {
111 format!("{expr}.into_iter().map(Into::into).collect()")
112 } else {
113 expr.to_string()
114 }
115 }
116 _ => expr.to_string(),
117 },
118 _ => expr.to_string(),
119 }
120}
121
122pub fn gen_call_args(params: &[ParamDef], opaque_types: &AHashSet<String>) -> String {
127 params
128 .iter()
129 .enumerate()
130 .map(|(idx, p)| {
131 let promoted = crate::shared::is_promoted_optional(params, idx);
132 let unwrap_suffix = if promoted {
134 format!(".expect(\"'{}' is required\")", p.name)
135 } else {
136 String::new()
137 };
138 match &p.ty {
139 TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
140 if p.optional {
142 format!("{}.as_ref().map(|v| &v.inner)", p.name)
143 } else if promoted {
144 format!("{}{}.inner.as_ref()", p.name, unwrap_suffix)
145 } else {
146 format!("&{}.inner", p.name)
147 }
148 }
149 TypeRef::Named(_) => {
150 if p.optional {
151 format!("{}.map(Into::into)", p.name)
152 } else if promoted {
153 format!("{}{}.into()", p.name, unwrap_suffix)
154 } else {
155 format!("{}.into()", p.name)
156 }
157 }
158 TypeRef::String | TypeRef::Char => {
160 if promoted {
161 format!("&{}{}", p.name, unwrap_suffix)
162 } else {
163 format!("&{}", p.name)
164 }
165 }
166 TypeRef::Path => {
168 if promoted {
169 format!("std::path::PathBuf::from({}{})", p.name, unwrap_suffix)
170 } else {
171 format!("std::path::PathBuf::from({})", p.name)
172 }
173 }
174 TypeRef::Bytes => {
175 if promoted {
176 format!("&{}{}", p.name, unwrap_suffix)
177 } else {
178 format!("&{}", p.name)
179 }
180 }
181 TypeRef::Duration => {
183 if promoted {
184 format!("std::time::Duration::from_secs({}{})", p.name, unwrap_suffix)
185 } else {
186 format!("std::time::Duration::from_secs({})", p.name)
187 }
188 }
189 _ => {
190 if promoted {
191 format!("{}{}", p.name, unwrap_suffix)
192 } else {
193 p.name.clone()
194 }
195 }
196 }
197 })
198 .collect::<Vec<_>>()
199 .join(", ")
200}
201
202pub fn gen_call_args_with_let_bindings(params: &[ParamDef], opaque_types: &AHashSet<String>) -> String {
205 params
206 .iter()
207 .enumerate()
208 .map(|(idx, p)| {
209 let promoted = crate::shared::is_promoted_optional(params, idx);
210 let unwrap_suffix = if promoted {
211 format!(".expect(\"'{}' is required\")", p.name)
212 } else {
213 String::new()
214 };
215 match &p.ty {
216 TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
217 if p.optional {
218 format!("{}.as_ref().map(|v| &v.inner)", p.name)
219 } else if promoted {
220 format!("{}{}.inner.as_ref()", p.name, unwrap_suffix)
221 } else {
222 format!("&{}.inner", p.name)
223 }
224 }
225 TypeRef::Named(_) => {
226 format!("{}_core", p.name)
227 }
228 TypeRef::String | TypeRef::Char => {
229 if promoted {
230 format!("&{}{}", p.name, unwrap_suffix)
231 } else {
232 format!("&{}", p.name)
233 }
234 }
235 TypeRef::Path => {
236 if promoted {
237 format!("std::path::PathBuf::from({}{})", p.name, unwrap_suffix)
238 } else {
239 format!("std::path::PathBuf::from({})", p.name)
240 }
241 }
242 TypeRef::Bytes => {
243 if promoted {
244 format!("&{}{}", p.name, unwrap_suffix)
245 } else {
246 format!("&{}", p.name)
247 }
248 }
249 TypeRef::Duration => {
250 if promoted {
251 format!("std::time::Duration::from_secs({}{})", p.name, unwrap_suffix)
252 } else {
253 format!("std::time::Duration::from_secs({})", p.name)
254 }
255 }
256 _ => {
257 if promoted {
258 format!("{}{}", p.name, unwrap_suffix)
259 } else {
260 p.name.clone()
261 }
262 }
263 }
264 })
265 .collect::<Vec<_>>()
266 .join(", ")
267}
268
269pub fn gen_named_let_bindings_pub(params: &[ParamDef], opaque_types: &AHashSet<String>) -> String {
271 gen_named_let_bindings(params, opaque_types)
272}
273
274pub(super) fn gen_named_let_bindings(params: &[ParamDef], opaque_types: &AHashSet<String>) -> String {
275 let mut bindings = String::new();
276 for (idx, p) in params.iter().enumerate() {
277 if let TypeRef::Named(name) = &p.ty {
278 if !opaque_types.contains(name.as_str()) {
279 let promoted = crate::shared::is_promoted_optional(params, idx);
280 if p.optional {
281 write!(bindings, "let {}_core = {}.map(Into::into);\n ", p.name, p.name).ok();
282 } else if promoted {
283 write!(
285 bindings,
286 "let {}_core = {}.expect(\"'{}' is required\").into();\n ",
287 p.name, p.name, p.name
288 )
289 .ok();
290 } else {
291 write!(bindings, "let {}_core = {}.into();\n ", p.name, p.name).ok();
292 }
293 }
294 }
295 }
296 bindings
297}
298
299pub fn gen_serde_let_bindings(
304 params: &[ParamDef],
305 opaque_types: &AHashSet<String>,
306 core_import: &str,
307 err_conv: &str,
308 indent: &str,
309) -> String {
310 let mut bindings = String::new();
311 for p in params {
312 if let TypeRef::Named(name) = &p.ty {
313 if !opaque_types.contains(name.as_str()) {
314 let core_path = format!("{}::{}", core_import, name);
315 if p.optional {
316 write!(
317 bindings,
318 "let {name}_core: Option<{core_path}> = {name}.map(|v| {{\n\
319 {indent} let json = serde_json::to_string(&v){err_conv}?;\n\
320 {indent} serde_json::from_str(&json){err_conv}\n\
321 {indent}}}).transpose()?;\n{indent}",
322 name = p.name,
323 core_path = core_path,
324 err_conv = err_conv,
325 indent = indent,
326 )
327 .ok();
328 } else {
329 write!(
330 bindings,
331 "let {name}_json = serde_json::to_string(&{name}){err_conv}?;\n\
332 {indent}let {name}_core: {core_path} = serde_json::from_str(&{name}_json){err_conv}?;\n{indent}",
333 name = p.name,
334 core_path = core_path,
335 err_conv = err_conv,
336 indent = indent,
337 )
338 .ok();
339 }
340 }
341 }
342 }
343 bindings
344}
345
346pub fn has_named_params(params: &[ParamDef], opaque_types: &AHashSet<String>) -> bool {
348 params
349 .iter()
350 .any(|p| matches!(&p.ty, TypeRef::Named(name) if !opaque_types.contains(name.as_str())))
351}
352
353pub fn is_simple_non_opaque_param(ty: &TypeRef) -> bool {
356 match ty {
357 TypeRef::Primitive(_)
358 | TypeRef::String
359 | TypeRef::Char
360 | TypeRef::Bytes
361 | TypeRef::Path
362 | TypeRef::Unit
363 | TypeRef::Duration => true,
364 TypeRef::Optional(inner) => is_simple_non_opaque_param(inner),
365 _ => false,
366 }
367}
368
369pub fn gen_lossy_binding_to_core_fields(typ: &TypeDef, core_import: &str) -> String {
373 let core_path = crate::conversions::core_type_path(typ, core_import);
374 let mut out = format!("let core_self = {core_path} {{\n");
375 for field in &typ.fields {
376 let name = &field.name;
377 if field.sanitized {
378 writeln!(out, " {name}: Default::default(),").ok();
379 } else {
380 let expr = match &field.ty {
381 TypeRef::Primitive(_) => format!("self.{name}"),
382 TypeRef::Duration => {
383 if field.optional {
384 format!("self.{name}.map(std::time::Duration::from_secs)")
385 } else {
386 format!("std::time::Duration::from_secs(self.{name})")
387 }
388 }
389 TypeRef::String | TypeRef::Char | TypeRef::Bytes => format!("self.{name}.clone()"),
390 TypeRef::Path => {
391 if field.optional {
392 format!("self.{name}.clone().map(Into::into)")
393 } else {
394 format!("self.{name}.clone().into()")
395 }
396 }
397 TypeRef::Named(_) => {
398 if field.optional {
399 format!("self.{name}.clone().map(Into::into)")
400 } else {
401 format!("self.{name}.clone().into()")
402 }
403 }
404 TypeRef::Vec(inner) => match inner.as_ref() {
405 TypeRef::Named(_) => {
406 format!("self.{name}.clone().into_iter().map(Into::into).collect()")
407 }
408 _ => format!("self.{name}.clone()"),
409 },
410 TypeRef::Optional(inner) => match inner.as_ref() {
411 TypeRef::Named(_) => {
412 format!("self.{name}.clone().map(Into::into)")
413 }
414 TypeRef::Vec(vi) if matches!(vi.as_ref(), TypeRef::Named(_)) => {
415 format!("self.{name}.clone().map(|v| v.into_iter().map(Into::into).collect())")
416 }
417 _ => format!("self.{name}.clone()"),
418 },
419 TypeRef::Map(_, _) => format!("self.{name}.clone()"),
420 TypeRef::Unit | TypeRef::Json => format!("self.{name}.clone()"),
421 };
422 writeln!(out, " {name}: {expr},").ok();
423 }
424 }
425 if typ.has_stripped_cfg_fields {
427 out.push_str(" ..Default::default()\n");
428 }
429 out.push_str(" };\n ");
430 out
431}
432
433pub fn gen_async_body(
448 core_call: &str,
449 cfg: &RustBindingConfig,
450 has_error: bool,
451 return_wrap: &str,
452 is_opaque: bool,
453 inner_clone_line: &str,
454 is_unit_return: bool,
455) -> String {
456 let pattern_body = match cfg.async_pattern {
457 AsyncPattern::Pyo3FutureIntoPy => {
458 let result_handling = if has_error {
459 format!(
460 "let result = {core_call}.await\n \
461 .map_err(|e| PyErr::new::<PyRuntimeError, _>(e.to_string()))?;"
462 )
463 } else if is_unit_return {
464 format!("{core_call}.await;")
465 } else {
466 format!("let result = {core_call}.await;")
467 };
468 let ok_expr = if is_unit_return && !has_error {
469 "()"
470 } else {
471 return_wrap
472 };
473 format!(
474 "pyo3_async_runtimes::tokio::future_into_py(py, async move {{\n \
475 {result_handling}\n \
476 Ok({ok_expr})\n }})"
477 )
478 }
479 AsyncPattern::WasmNativeAsync => {
480 let result_handling = if has_error {
481 format!(
482 "let result = {core_call}.await\n \
483 .map_err(|e| JsValue::from_str(&e.to_string()))?;"
484 )
485 } else if is_unit_return {
486 format!("{core_call}.await;")
487 } else {
488 format!("let result = {core_call}.await;")
489 };
490 let ok_expr = if is_unit_return && !has_error {
491 "()"
492 } else {
493 return_wrap
494 };
495 format!(
496 "{result_handling}\n \
497 Ok({ok_expr})"
498 )
499 }
500 AsyncPattern::NapiNativeAsync => {
501 let result_handling = if has_error {
502 format!(
503 "let result = {core_call}.await\n \
504 .map_err(|e| napi::Error::new(napi::Status::GenericFailure, e.to_string()))?;"
505 )
506 } else if is_unit_return {
507 format!("{core_call}.await;")
508 } else {
509 format!("let result = {core_call}.await;")
510 };
511 let ok_expr = if is_unit_return && !has_error {
512 "()"
513 } else {
514 return_wrap
515 };
516 format!(
517 "{result_handling}\n \
518 Ok({ok_expr})"
519 )
520 }
521 AsyncPattern::TokioBlockOn => {
522 if has_error {
523 if is_opaque {
524 format!(
525 "let rt = tokio::runtime::Runtime::new()?;\n \
526 let result = rt.block_on(async {{ {core_call}.await.map_err(|e| e.into()) }})?;\n \
527 {return_wrap}"
528 )
529 } else {
530 format!(
531 "let rt = tokio::runtime::Runtime::new()?;\n \
532 rt.block_on(async {{ {core_call}.await.map_err(|e| e.into()) }})"
533 )
534 }
535 } else if is_opaque {
536 if is_unit_return {
537 format!(
538 "let rt = tokio::runtime::Runtime::new()?;\n \
539 rt.block_on(async {{ {core_call}.await }});"
540 )
541 } else {
542 format!(
543 "let rt = tokio::runtime::Runtime::new()?;\n \
544 let result = rt.block_on(async {{ {core_call}.await }});\n \
545 {return_wrap}"
546 )
547 }
548 } else {
549 format!(
550 "let rt = tokio::runtime::Runtime::new()?;\n \
551 rt.block_on(async {{ {core_call}.await }})"
552 )
553 }
554 }
555 AsyncPattern::None => "todo!(\"async not supported by backend\")".to_string(),
556 };
557 if inner_clone_line.is_empty() {
558 pattern_body
559 } else {
560 format!("{inner_clone_line}{pattern_body}")
561 }
562}
563
564pub fn gen_unimplemented_body(
567 return_type: &TypeRef,
568 fn_name: &str,
569 has_error: bool,
570 cfg: &RustBindingConfig,
571 params: &[ParamDef],
572) -> String {
573 let suppress = if params.is_empty() {
575 String::new()
576 } else {
577 let names: Vec<&str> = params.iter().map(|p| p.name.as_str()).collect();
578 if names.len() == 1 {
579 format!("let _ = {};\n ", names[0])
580 } else {
581 format!("let _ = ({});\n ", names.join(", "))
582 }
583 };
584 let err_msg = format!("Not implemented: {fn_name}");
585 let body = if has_error {
586 match cfg.async_pattern {
588 AsyncPattern::Pyo3FutureIntoPy => {
589 format!("Err(pyo3::exceptions::PyNotImplementedError::new_err(\"{err_msg}\"))")
590 }
591 AsyncPattern::NapiNativeAsync => {
592 format!("Err(napi::Error::new(napi::Status::GenericFailure, \"{err_msg}\"))")
593 }
594 AsyncPattern::WasmNativeAsync => {
595 format!("Err(JsValue::from_str(\"{err_msg}\"))")
596 }
597 _ => format!("Err(\"{err_msg}\".to_string())"),
598 }
599 } else {
600 match return_type {
602 TypeRef::Unit => "()".to_string(),
603 TypeRef::String | TypeRef::Char | TypeRef::Path => format!("String::from(\"[unimplemented: {fn_name}]\")"),
604 TypeRef::Bytes => "Vec::new()".to_string(),
605 TypeRef::Primitive(p) => match p {
606 alef_core::ir::PrimitiveType::Bool => "false".to_string(),
607 _ => "0".to_string(),
608 },
609 TypeRef::Optional(_) => "None".to_string(),
610 TypeRef::Vec(_) => "Vec::new()".to_string(),
611 TypeRef::Map(_, _) => "Default::default()".to_string(),
612 TypeRef::Duration => "0".to_string(),
613 TypeRef::Named(_) | TypeRef::Json => {
614 format!("panic!(\"alef: {fn_name} not auto-delegatable\")")
616 }
617 }
618 };
619 format!("{suppress}{body}")
620}