Skip to main content

alef_codegen/generators/
binding_helpers.rs

1use crate::generators::{AsyncPattern, RustBindingConfig};
2use ahash::AHashSet;
3use alef_core::ir::{ParamDef, TypeDef, TypeRef};
4use std::fmt::Write;
5
6/// Wrap a core-call result for opaque delegation methods.
7///
8/// - `TypeRef::Named(n)` where `n == type_name` → re-wrap in `Self { inner: Arc::new(...) }`
9/// - `TypeRef::Named(n)` where `n` is another opaque type → wrap in `{n} { inner: Arc::new(...) }`
10/// - `TypeRef::Named(n)` where `n` is a non-opaque type → `todo!()` placeholder (From may not exist)
11/// - Everything else (primitives, String, Vec, etc.) → pass through unchanged
12/// - `TypeRef::Unit` → pass through unchanged
13///
14/// When `returns_cow` is true the core method returns `Cow<'_, T>`. `.into_owned()` is emitted
15/// before any further type conversion to obtain an owned `T`.
16pub 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            // Non-opaque Named return type — use .into() for core→binding From conversion.
46            // When the core returns a Cow, call .into_owned() first to get an owned T.
47            // When the core returns a reference, clone first since From<&T> typically doesn't exist.
48            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        // String/Bytes: only convert when the core returns a reference (&str→String, &[u8]→Vec<u8>).
57        // When owned (returns_ref=false), both sides are already String/Vec<u8> — skip .into().
58        TypeRef::String | TypeRef::Bytes => {
59            if returns_ref {
60                format!("{expr}.into()")
61            } else {
62                expr.to_string()
63            }
64        }
65        // Path: PathBuf→String needs to_string_lossy, &Path→String too
66        TypeRef::Path => format!("{expr}.to_string_lossy().to_string()"),
67        // Duration: core returns std::time::Duration, binding uses u64 (millis)
68        TypeRef::Duration => format!("{expr}.as_millis() as u64"),
69        // Json: serde_json::Value needs serialization to string
70        TypeRef::Json => format!("{expr}.to_string()"),
71        // Optional: wrap inner conversion in .map(...)
72        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        // Vec: map each element through the appropriate conversion
102        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
133/// Unwrap a newtype return value when `return_newtype_wrapper` is set.
134///
135/// Core function returns a newtype (e.g. `NodeIndex(u32)`), but the binding return type
136/// is the inner type (e.g. `u32`). Access `.0` to unwrap the newtype.
137pub 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
144/// Build call argument expressions from parameters.
145/// - Opaque Named types: unwrap Arc wrapper via `(*param.inner).clone()`
146/// - Non-opaque Named types: `.into()` for From conversion
147/// - String/Path/Bytes: `&param` since core functions typically take `&str`/`&Path`/`&[u8]`
148/// - Params with `newtype_wrapper` set: re-wrap the raw value in the newtype constructor
149///   (e.g., `NodeIndex(parent)`) since the binding resolved `NodeIndex(u32)` → `u32`.
150pub 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            // If a required param was promoted to optional, unwrap it before use
157            let unwrap_suffix = if promoted {
158                format!(".expect(\"'{}' is required\")", p.name)
159            } else {
160                String::new()
161            };
162            // If this param's type was resolved from a newtype (e.g. NodeIndex(u32) → u32),
163            // re-wrap the raw value back into the newtype when calling core.
164            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                    // Opaque type: borrow through Arc to get &CoreType
176                    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                // String → &str for core function calls when is_ref=true,
194                // or pass owned when is_ref=false (core takes String/impl Into<String>).
195                // For optional params: as_deref() when is_ref=true, pass owned when is_ref=false.
196                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                // Path → PathBuf/&Path for core function calls
216                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                // Duration: binding uses u64 (millis), core uses std::time::Duration
243                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                        // Optional ref params: use as_deref() for slice/str coercion
257                        // Option<Vec<T>> -> Option<&[T]>, Option<String> -> Option<&str>
258                        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
271/// Build call argument expressions using pre-bound let bindings for non-opaque Named params.
272/// Non-opaque Named params use `&{name}_core` references instead of `.into()`.
273pub 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 this param's type was resolved from a newtype, re-wrap when calling core.
285            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
381/// Generate let bindings for non-opaque Named params, converting them to core types.
382pub 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                    // Promoted-optional: unwrap then convert
396                    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
411/// Generate serde-based let bindings for non-opaque Named params.
412/// Serializes binding types to JSON and deserializes to core types.
413/// Used when From impls don't exist (e.g., types with sanitized fields).
414/// `indent` is the whitespace prefix for each generated line (e.g., "    " for functions, "        " for methods).
415pub 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
458/// Check if params contain any non-opaque Named types that need let bindings.
459pub 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
465/// Check if a param type is safe for non-opaque delegation (no complex conversions needed).
466/// Vec and Map params can cause type mismatches (e.g. Vec<String> vs &[&str]).
467pub 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
481/// Generate a lossy binding→core struct literal for non-opaque delegation.
482/// Sanitized fields use `Default::default()`, non-sanitized fields are cloned and converted.
483/// Fields are accessed via `self.` (behind &self), so all non-Copy types need `.clone()`.
484pub 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
488/// Same as `gen_lossy_binding_to_core_fields` but declares `core_self` as mutable.
489pub 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                            // Option<Vec<Named(T)>>: map over the Option, then convert each element
530                            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                        // HashMap<String, String> (binding) → HashMap<String, Value> (core)
549                        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                    // Collect to handle HashMap↔BTreeMap conversion
562                    _ => {
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                    // String (binding) → serde_json::Value (core)
573                    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            // Newtype wrapping: when the field was resolved from a newtype (e.g. NodeIndex → u32),
581            // re-wrap the binding value into the newtype for the core struct literal.
582            // When `optional=true` and `ty` is a plain Primitive (not TypeRef::Optional), the core
583            // field is actually `Option<NewtypeT>`, so we must use `.map(NewtypeT)` not `NewtypeT(...)`.
584            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    // Use ..Default::default() to fill cfg-gated fields stripped from the IR
598    if typ.has_stripped_cfg_fields {
599        out.push_str("            ..Default::default()\n");
600    }
601    out.push_str("        };\n        ");
602    out
603}
604
605/// Generate the body for an async call, unified across methods, static methods, and free functions.
606///
607/// - `core_call`: the expression to await, e.g. `inner.method(args)` or `CoreType::fn(args)`.
608///   For Pyo3FutureIntoPy opaque methods this should reference `inner` (the Arc clone);
609///   for all other patterns it may reference `self.inner` or a static call expression.
610/// - `cfg`: binding configuration (determines which async pattern to emit)
611/// - `has_error`: whether the core call returns a `Result`
612/// - `return_wrap`: expression to produce the binding return value from `result`,
613///   e.g. `"result"` or `"TypeName::from(result)"`
614///
615/// - `is_opaque`: whether the binding type is Arc-wrapped (affects TokioBlockOn wrapping)
616/// - `inner_clone_line`: optional statement emitted before the pattern-specific body,
617///   e.g. `"let inner = self.inner.clone();\n        "` for opaque instance methods, or `""`.
618///   Required when `core_call` references `inner` (Pyo3FutureIntoPy opaque case).
619pub 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                // No error type: return value directly without Ok() wrapper
685                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
744/// Generate a compilable body for functions that can't be auto-delegated.
745/// Returns a default value or error instead of `todo!()` which would panic.
746pub 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    // Suppress unused_variables by binding all params to `_`
754    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        // Backend-specific error return
767        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        // Return type-appropriate default
781        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                // Named return without error type: can't return Err.
795                // Emit compile_error so this is caught at build time rather than panicking at runtime.
796                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}