Skip to main content

alef_codegen/generators/
binding_helpers.rs

1use crate::generators::{AsyncPattern, RustBindingConfig};
2use ahash::AHashSet;
3use alef_core::ir::{CoreWrapper, ParamDef, TypeDef, TypeRef};
4use std::fmt::Write;
5
6/// Helper: wrap an opaque inner value in the correct smart pointer expression.
7///
8/// - Plain opaque types use `Arc::new(val)`.
9/// - Mutex-wrapped opaque types use `Arc::new(std::sync::Mutex::new(val))`.
10fn arc_wrap(val: &str, name: &str, mutex_types: &AHashSet<String>) -> String {
11    if mutex_types.contains(name) {
12        format!("Arc::new(std::sync::Mutex::new({val}))")
13    } else {
14        format!("Arc::new({val})")
15    }
16}
17
18/// Wrap a core-call result for opaque delegation methods.
19///
20/// - `TypeRef::Named(n)` where `n == type_name` → re-wrap in `Self { inner: Arc::new(...) }`
21/// - `TypeRef::Named(n)` where `n` is another opaque type → wrap in `{n} { inner: Arc::new(...) }`
22/// - `TypeRef::Named(n)` where `n` is a non-opaque type → `todo!()` placeholder (From may not exist)
23/// - Everything else (primitives, String, Vec, etc.) → pass through unchanged
24/// - `TypeRef::Unit` → pass through unchanged
25///
26/// When `returns_cow` is true the core method returns `Cow<'_, T>`. `.into_owned()` is emitted
27/// before any further type conversion to obtain an owned `T`.
28///
29/// `mutex_types` identifies opaque types that use `Arc<Mutex<T>>` instead of `Arc<T>`, so
30/// constructor expressions use `Arc::new(Mutex::new(...))` where needed.
31#[allow(clippy::too_many_arguments)]
32pub fn wrap_return_with_mutex(
33    expr: &str,
34    return_type: &TypeRef,
35    type_name: &str,
36    opaque_types: &AHashSet<String>,
37    mutex_types: &AHashSet<String>,
38    self_is_opaque: bool,
39    returns_ref: bool,
40    returns_cow: bool,
41) -> String {
42    let self_arc = arc_wrap("", type_name, mutex_types); // used for pattern matching only
43    let _ = self_arc; // just to reference mutex_types in context
44    match return_type {
45        TypeRef::Named(n) if n == type_name && self_is_opaque => {
46            let inner = if returns_cow {
47                format!("{expr}.into_owned()")
48            } else if returns_ref {
49                format!("{expr}.clone()")
50            } else {
51                expr.to_string()
52            };
53            format!("Self {{ inner: {} }}", arc_wrap(&inner, type_name, mutex_types))
54        }
55        TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
56            let inner = if returns_cow {
57                format!("{expr}.into_owned()")
58            } else if returns_ref {
59                format!("{expr}.clone()")
60            } else {
61                expr.to_string()
62            };
63            format!("{n} {{ inner: {} }}", arc_wrap(&inner, n, mutex_types))
64        }
65        TypeRef::Named(_) => {
66            // Non-opaque Named return type — use .into() for core→binding From conversion.
67            // When the core returns a Cow, call .into_owned() first to get an owned T.
68            // When the core returns a reference, clone first since From<&T> typically doesn't exist.
69            // NOTE: If this type was sanitized to String in the binding, From won't exist.
70            // The calling backend should check method.sanitized before delegating.
71            // This code assumes non-sanitized Named types have From impls.
72            if returns_cow {
73                format!("{expr}.into_owned().into()")
74            } else if returns_ref {
75                format!("{expr}.clone().into()")
76            } else {
77                format!("{expr}.into()")
78            }
79        }
80        // String: only convert when the core returns a reference (&str→String).
81        TypeRef::String => {
82            if returns_ref {
83                format!("{expr}.into()")
84            } else {
85                expr.to_string()
86            }
87        }
88        // Bytes: always use .to_vec() which works for both &Bytes and owned Bytes.
89        // &Bytes does not implement From<&Bytes> for Vec<u8>, so .into() fails.
90        TypeRef::Bytes => format!("{expr}.to_vec()"),
91        // Path: PathBuf→String needs to_string_lossy, &Path→String too
92        TypeRef::Path => format!("{expr}.to_string_lossy().to_string()"),
93        // Duration: core returns std::time::Duration, binding uses u64 (millis)
94        TypeRef::Duration => format!("{expr}.as_millis() as u64"),
95        // Json: serde_json::Value needs serialization to string
96        TypeRef::Json => format!("{expr}.to_string()"),
97        // Optional: wrap inner conversion in .map(...)
98        TypeRef::Optional(inner) => match inner.as_ref() {
99            TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
100                let wrap = arc_wrap("v", n, mutex_types);
101                if returns_ref {
102                    format!(
103                        "{expr}.map(|v| {n} {{ inner: {} }})",
104                        arc_wrap("v.clone()", n, mutex_types)
105                    )
106                } else {
107                    format!("{expr}.map(|v| {n} {{ inner: {wrap} }})")
108                }
109            }
110            TypeRef::Named(_) => {
111                if returns_ref {
112                    format!("{expr}.map(|v| v.clone().into())")
113                } else {
114                    format!("{expr}.map(Into::into)")
115                }
116            }
117            TypeRef::Path => {
118                format!("{expr}.map(Into::into)")
119            }
120            TypeRef::String | TypeRef::Bytes => {
121                if returns_ref {
122                    format!("{expr}.map(Into::into)")
123                } else {
124                    expr.to_string()
125                }
126            }
127            TypeRef::Duration => format!("{expr}.map(|d| d.as_millis() as u64)"),
128            TypeRef::Json => format!("{expr}.map(ToString::to_string)"),
129            // Optional<Vec<Named>>: convert each element in the inner Vec
130            TypeRef::Vec(vec_inner) => match vec_inner.as_ref() {
131                TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
132                    if returns_ref {
133                        let wrap = arc_wrap("x.clone()", n, mutex_types);
134                        format!("{expr}.map(|v| v.into_iter().map(|x| {n} {{ inner: {wrap} }}).collect())")
135                    } else {
136                        let wrap = arc_wrap("x", n, mutex_types);
137                        format!("{expr}.map(|v| v.into_iter().map(|x| {n} {{ inner: {wrap} }}).collect())")
138                    }
139                }
140                TypeRef::Named(_) => {
141                    if returns_ref {
142                        format!("{expr}.map(|v| v.into_iter().map(|x| x.clone().into()).collect())")
143                    } else {
144                        format!("{expr}.map(|v| v.into_iter().map(Into::into).collect())")
145                    }
146                }
147                _ => expr.to_string(),
148            },
149            _ => expr.to_string(),
150        },
151        // Vec: map each element through the appropriate conversion
152        TypeRef::Vec(inner) => match inner.as_ref() {
153            TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
154                if returns_ref {
155                    let wrap = arc_wrap("v.clone()", n, mutex_types);
156                    format!("{expr}.into_iter().map(|v| {n} {{ inner: {wrap} }}).collect()")
157                } else {
158                    let wrap = arc_wrap("v", n, mutex_types);
159                    format!("{expr}.into_iter().map(|v| {n} {{ inner: {wrap} }}).collect()")
160                }
161            }
162            TypeRef::Named(_) => {
163                if returns_ref {
164                    format!("{expr}.into_iter().map(|v| v.clone().into()).collect()")
165                } else {
166                    format!("{expr}.into_iter().map(Into::into).collect()")
167                }
168            }
169            TypeRef::Path => {
170                format!("{expr}.into_iter().map(Into::into).collect()")
171            }
172            TypeRef::String | TypeRef::Bytes => {
173                if returns_ref {
174                    format!("{expr}.into_iter().map(Into::into).collect()")
175                } else {
176                    expr.to_string()
177                }
178            }
179            _ => expr.to_string(),
180        },
181        _ => expr.to_string(),
182    }
183}
184
185/// Wrap a core-call result for opaque delegation methods.
186///
187/// This is the backward-compatible wrapper that passes an empty `mutex_types` set.
188/// Use `wrap_return_with_mutex` when the type set contains mutex-wrapped opaque types.
189pub fn wrap_return(
190    expr: &str,
191    return_type: &TypeRef,
192    type_name: &str,
193    opaque_types: &AHashSet<String>,
194    self_is_opaque: bool,
195    returns_ref: bool,
196    returns_cow: bool,
197) -> String {
198    wrap_return_with_mutex(
199        expr,
200        return_type,
201        type_name,
202        opaque_types,
203        &AHashSet::new(),
204        self_is_opaque,
205        returns_ref,
206        returns_cow,
207    )
208}
209
210/// Unwrap a newtype return value when `return_newtype_wrapper` is set.
211///
212/// Core function returns a newtype (e.g. `NodeIndex(u32)`), but the binding return type
213/// is the inner type (e.g. `u32`). Access `.0` to unwrap the newtype.
214pub fn apply_return_newtype_unwrap(expr: &str, return_newtype_wrapper: &Option<String>) -> String {
215    match return_newtype_wrapper {
216        Some(_) => format!("({expr}).0"),
217        None => expr.to_string(),
218    }
219}
220
221/// Build call argument expressions from parameters.
222/// - Opaque Named types: unwrap Arc wrapper via `(*param.inner).clone()`
223/// - Non-opaque Named types: `.into()` for From conversion
224/// - String/Path/Bytes: `&param` since core functions typically take `&str`/`&Path`/`&[u8]`
225/// - Params with `newtype_wrapper` set: re-wrap the raw value in the newtype constructor
226///   (e.g., `NodeIndex(parent)`) since the binding resolved `NodeIndex(u32)` → `u32`.
227///
228/// NOTE: This function does not perform serde-based conversion. For Named params that lack
229/// From impls (e.g., due to sanitized fields), use `gen_serde_let_bindings` instead when
230/// `cfg.has_serde` is true, or fall back to `gen_unimplemented_body`.
231pub fn gen_call_args(params: &[ParamDef], opaque_types: &AHashSet<String>) -> String {
232    params
233        .iter()
234        .enumerate()
235        .map(|(idx, p)| {
236            let promoted = crate::shared::is_promoted_optional(params, idx);
237            // If a required param was promoted to optional, unwrap it before use.
238            // Note: promoted params that are not Optional<T> will NOT call .expect() because
239            // promoted refers to the PyO3 signature constraint, not the actual Rust type.
240            // The function_params logic wraps promoted params in Option<T>, making them truly optional.
241            let unwrap_suffix = if promoted && p.optional {
242                format!(".expect(\"'{}' is required\")", p.name)
243            } else {
244                String::new()
245            };
246            // If this param's type was resolved from a newtype (e.g. NodeIndex(u32) → u32),
247            // re-wrap the raw value back into the newtype when calling core.
248            if let Some(newtype_path) = &p.newtype_wrapper {
249                return if p.optional {
250                    format!("{}.map({newtype_path})", p.name)
251                } else if promoted {
252                    format!("{newtype_path}({}{})", p.name, unwrap_suffix)
253                } else {
254                    format!("{newtype_path}({})", p.name)
255                };
256            }
257            match &p.ty {
258                TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
259                    // Opaque type: borrow through Arc to get &CoreType
260                    if p.optional {
261                        format!("{}.as_ref().map(|v| &v.inner)", p.name)
262                    } else if promoted {
263                        format!("{}{}.inner.as_ref()", p.name, unwrap_suffix)
264                    } else {
265                        format!("&{}.inner", p.name)
266                    }
267                }
268                TypeRef::Named(_) => {
269                    if p.optional {
270                        if p.is_ref {
271                            // Option<T> (binding) -> Option<&CoreT>: use as_ref() only
272                            // The Into conversion must happen in a let binding to avoid E0716
273                            format!("{}.as_ref()", p.name)
274                        } else {
275                            format!("{}.map(Into::into)", p.name)
276                        }
277                    } else if promoted {
278                        format!("{}{}.into()", p.name, unwrap_suffix)
279                    } else {
280                        format!("{}.into()", p.name)
281                    }
282                }
283                // String → &str for core function calls when is_ref=true,
284                // or pass owned when is_ref=false (core takes String/impl Into<String>).
285                // For optional params: as_deref() when is_ref=true, pass owned when is_ref=false.
286                TypeRef::String | TypeRef::Char => {
287                    if p.optional {
288                        if p.is_ref {
289                            format!("{}.as_deref()", p.name)
290                        } else {
291                            p.name.clone()
292                        }
293                    } else if promoted {
294                        if p.is_ref {
295                            format!("&{}{}", p.name, unwrap_suffix)
296                        } else {
297                            format!("{}{}", p.name, unwrap_suffix)
298                        }
299                    } else if p.is_ref {
300                        format!("&{}", p.name)
301                    } else {
302                        p.name.clone()
303                    }
304                }
305                // Path → PathBuf/&Path for core function calls
306                TypeRef::Path => {
307                    if p.optional && p.is_ref {
308                        format!("{}.as_deref().map(std::path::Path::new)", p.name)
309                    } else if p.optional {
310                        format!("{}.map(std::path::PathBuf::from)", p.name)
311                    } else if promoted {
312                        format!("std::path::PathBuf::from({}{})", p.name, unwrap_suffix)
313                    } else if p.is_ref {
314                        format!("std::path::Path::new(&{})", p.name)
315                    } else {
316                        format!("std::path::PathBuf::from({})", p.name)
317                    }
318                }
319                TypeRef::Bytes => {
320                    if p.optional {
321                        if p.is_ref {
322                            format!("{}.as_deref()", p.name)
323                        } else {
324                            p.name.clone()
325                        }
326                    } else if promoted {
327                        // is_ref=true: pass &Vec<u8> (core takes &[u8])
328                        // is_ref=false: pass Vec<u8> (core takes owned Vec<u8>)
329                        if p.is_ref {
330                            format!("&{}{}", p.name, unwrap_suffix)
331                        } else {
332                            format!("{}{}", p.name, unwrap_suffix)
333                        }
334                    } else {
335                        // is_ref=true: pass &Vec<u8> (core takes &[u8])
336                        // is_ref=false: pass Vec<u8> (core takes owned Vec<u8>)
337                        if p.is_ref {
338                            format!("&{}", p.name)
339                        } else {
340                            p.name.clone()
341                        }
342                    }
343                }
344                // Duration: binding uses u64 (millis), core uses std::time::Duration
345                TypeRef::Duration => {
346                    if p.optional {
347                        format!("{}.map(std::time::Duration::from_millis)", p.name)
348                    } else if promoted {
349                        format!("std::time::Duration::from_millis({}{})", p.name, unwrap_suffix)
350                    } else {
351                        format!("std::time::Duration::from_millis({})", p.name)
352                    }
353                }
354                TypeRef::Json => {
355                    // JSON params: binding has String, core expects serde_json::Value
356                    if p.optional {
357                        format!("{}.as_ref().and_then(|s| serde_json::from_str(s).ok())", p.name)
358                    } else if promoted {
359                        format!("serde_json::from_str(&{}{}).unwrap_or_default()", p.name, unwrap_suffix)
360                    } else {
361                        format!("serde_json::from_str(&{}).unwrap_or_default()", p.name)
362                    }
363                }
364                TypeRef::Vec(inner) => {
365                    // Vec<Named>: convert each element via Into::into when used with let bindings
366                    if matches!(inner.as_ref(), TypeRef::Named(_)) {
367                        if p.optional {
368                            if p.is_ref {
369                                format!("{}.as_deref()", p.name)
370                            } else {
371                                p.name.clone()
372                            }
373                        } else if promoted {
374                            if p.is_ref {
375                                format!("&{}{}", p.name, unwrap_suffix)
376                            } else {
377                                format!("{}{}", p.name, unwrap_suffix)
378                            }
379                        } else if p.is_ref {
380                            format!("&{}", p.name)
381                        } else {
382                            p.name.clone()
383                        }
384                    } else if promoted {
385                        format!("{}{}", p.name, unwrap_suffix)
386                    } else if p.is_mut && p.optional {
387                        format!("{}.as_deref_mut()", p.name)
388                    } else if p.is_mut {
389                        format!("&mut {}", p.name)
390                    } else if p.is_ref && p.optional {
391                        format!("{}.as_deref()", p.name)
392                    } else if p.is_ref {
393                        format!("&{}", p.name)
394                    } else {
395                        p.name.clone()
396                    }
397                }
398                _ => {
399                    if promoted {
400                        format!("{}{}", p.name, unwrap_suffix)
401                    } else if p.is_mut && p.optional {
402                        format!("{}.as_deref_mut()", p.name)
403                    } else if p.is_mut {
404                        format!("&mut {}", p.name)
405                    } else if p.is_ref && p.optional {
406                        // Optional ref params: use as_deref() for slice/str coercion
407                        // Option<Vec<T>> -> Option<&[T]>, Option<String> -> Option<&str>
408                        format!("{}.as_deref()", p.name)
409                    } else if p.is_ref {
410                        format!("&{}", p.name)
411                    } else {
412                        p.name.clone()
413                    }
414                }
415            }
416        })
417        .collect::<Vec<_>>()
418        .join(", ")
419}
420
421/// Build call argument expressions using pre-bound let bindings for non-opaque Named params.
422/// Non-opaque Named params use `&{name}_core` references instead of `.into()`.
423pub fn gen_call_args_with_let_bindings(params: &[ParamDef], opaque_types: &AHashSet<String>) -> String {
424    params
425        .iter()
426        .enumerate()
427        .map(|(idx, p)| {
428            let promoted = crate::shared::is_promoted_optional(params, idx);
429            let unwrap_suffix = if promoted {
430                format!(".expect(\"'{}' is required\")", p.name)
431            } else {
432                String::new()
433            };
434            // If this param's type was resolved from a newtype, re-wrap when calling core.
435            if let Some(newtype_path) = &p.newtype_wrapper {
436                return if p.optional {
437                    format!("{}.map({newtype_path})", p.name)
438                } else if promoted {
439                    format!("{newtype_path}({}{})", p.name, unwrap_suffix)
440                } else {
441                    format!("{newtype_path}({})", p.name)
442                };
443            }
444            match &p.ty {
445                TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
446                    if p.optional {
447                        format!("{}.as_ref().map(|v| &v.inner)", p.name)
448                    } else if promoted {
449                        format!("{}{}.inner.as_ref()", p.name, unwrap_suffix)
450                    } else {
451                        format!("&{}.inner", p.name)
452                    }
453                }
454                TypeRef::Named(_) => {
455                    if p.optional && p.is_ref {
456                        // Let binding already created Option<&T> via .as_ref()
457                        format!("{}_core", p.name)
458                    } else if p.is_ref {
459                        // Let binding created T, need reference for call
460                        format!("&{}_core", p.name)
461                    } else {
462                        format!("{}_core", p.name)
463                    }
464                }
465                TypeRef::String | TypeRef::Char => {
466                    if p.optional {
467                        if p.is_ref {
468                            format!("{}.as_deref()", p.name)
469                        } else {
470                            p.name.clone()
471                        }
472                    } else if promoted {
473                        if p.is_ref {
474                            format!("&{}{}", p.name, unwrap_suffix)
475                        } else {
476                            format!("{}{}", p.name, unwrap_suffix)
477                        }
478                    } else if p.is_ref {
479                        format!("&{}", p.name)
480                    } else {
481                        p.name.clone()
482                    }
483                }
484                TypeRef::Path => {
485                    if promoted {
486                        format!("std::path::PathBuf::from({}{})", p.name, unwrap_suffix)
487                    } else if p.optional && p.is_ref {
488                        format!("{}.as_deref().map(std::path::Path::new)", p.name)
489                    } else if p.optional {
490                        format!("{}.map(std::path::PathBuf::from)", p.name)
491                    } else if p.is_ref {
492                        format!("std::path::Path::new(&{})", p.name)
493                    } else {
494                        format!("std::path::PathBuf::from({})", p.name)
495                    }
496                }
497                TypeRef::Bytes => {
498                    if p.optional {
499                        if p.is_ref {
500                            format!("{}.as_deref()", p.name)
501                        } else {
502                            p.name.clone()
503                        }
504                    } else if promoted {
505                        // is_ref=true: pass &Vec<u8> (core takes &[u8])
506                        // is_ref=false: pass Vec<u8> (core takes owned Vec<u8>)
507                        if p.is_ref {
508                            format!("&{}{}", p.name, unwrap_suffix)
509                        } else {
510                            format!("{}{}", p.name, unwrap_suffix)
511                        }
512                    } else {
513                        // is_ref=true: pass &Vec<u8> (core takes &[u8])
514                        // is_ref=false: pass Vec<u8> (core takes owned Vec<u8>)
515                        if p.is_ref {
516                            format!("&{}", p.name)
517                        } else {
518                            p.name.clone()
519                        }
520                    }
521                }
522                TypeRef::Duration => {
523                    if p.optional {
524                        format!("{}.map(std::time::Duration::from_millis)", p.name)
525                    } else if promoted {
526                        format!("std::time::Duration::from_millis({}{})", p.name, unwrap_suffix)
527                    } else {
528                        format!("std::time::Duration::from_millis({})", p.name)
529                    }
530                }
531                TypeRef::Vec(inner) => {
532                    // Sanitized Vec<tuple>: binding accepts Vec<String> (JSON-encoded tuples).
533                    // Let binding created {name}_core via JSON deserialization.
534                    if matches!(inner.as_ref(), TypeRef::String) && p.sanitized && p.original_type.is_some() {
535                        if p.optional && p.is_ref {
536                            format!("{}_core.as_deref()", p.name)
537                        } else if p.optional {
538                            format!("{}_core", p.name)
539                        } else if p.is_ref {
540                            format!("&{}_core", p.name)
541                        } else {
542                            format!("{}_core", p.name)
543                        }
544                    } else if matches!(inner.as_ref(), TypeRef::Named(_)) {
545                        // Vec<Named>: use let binding that converts each element
546                        if p.optional && p.is_ref {
547                            // Let binding creates Option<Vec<CoreType>>, use as_deref() to get Option<&[CoreType]>
548                            format!("{}_core.as_deref()", p.name)
549                        } else if p.optional {
550                            // Let binding creates Option<Vec<CoreType>>, no ref needed
551                            format!("{}_core", p.name)
552                        } else if p.is_ref {
553                            format!("&{}_core", p.name)
554                        } else {
555                            format!("{}_core", p.name)
556                        }
557                    } else if matches!(inner.as_ref(), TypeRef::String | TypeRef::Char) && p.is_ref {
558                        // Vec<String> with is_ref=true: core expects &[&str].
559                        // Convert via _refs intermediate binding.
560                        if p.optional {
561                            format!("{}.as_deref()", p.name)
562                        } else {
563                            format!("&{}_refs", p.name)
564                        }
565                    } else if promoted {
566                        format!("{}{}", p.name, unwrap_suffix)
567                    } else if p.is_ref && p.optional {
568                        format!("{}.as_deref()", p.name)
569                    } else if p.is_ref {
570                        format!("&{}", p.name)
571                    } else {
572                        p.name.clone()
573                    }
574                }
575                _ => {
576                    if promoted {
577                        format!("{}{}", p.name, unwrap_suffix)
578                    } else if p.is_ref && p.optional {
579                        format!("{}.as_deref()", p.name)
580                    } else if p.is_ref {
581                        format!("&{}", p.name)
582                    } else {
583                        p.name.clone()
584                    }
585                }
586            }
587        })
588        .collect::<Vec<_>>()
589        .join(", ")
590}
591
592/// Generate let bindings for non-opaque Named params, converting them to core types.
593pub fn gen_named_let_bindings_pub(params: &[ParamDef], opaque_types: &AHashSet<String>, core_import: &str) -> String {
594    gen_named_let_bindings(params, opaque_types, core_import)
595}
596
597/// Like `gen_named_let_bindings_pub` but without optional-promotion semantics.
598/// Use this for backends (e.g. WASM) that do not promote non-optional params to `Option<T>`.
599pub fn gen_named_let_bindings_no_promote(
600    params: &[ParamDef],
601    opaque_types: &AHashSet<String>,
602    core_import: &str,
603) -> String {
604    gen_named_let_bindings_inner(params, opaque_types, core_import, false)
605}
606
607pub(super) fn gen_named_let_bindings(
608    params: &[ParamDef],
609    opaque_types: &AHashSet<String>,
610    core_import: &str,
611) -> String {
612    gen_named_let_bindings_inner(params, opaque_types, core_import, true)
613}
614
615fn gen_named_let_bindings_inner(
616    params: &[ParamDef],
617    opaque_types: &AHashSet<String>,
618    core_import: &str,
619    promote: bool,
620) -> String {
621    let mut bindings = String::new();
622    for (idx, p) in params.iter().enumerate() {
623        match &p.ty {
624            TypeRef::Named(name) if !opaque_types.contains(name.as_str()) => {
625                let promoted = promote && crate::shared::is_promoted_optional(params, idx);
626                let core_type_path = format!("{}::{}", core_import, name);
627                if p.optional {
628                    if p.is_ref {
629                        // Option<T> (binding) -> Option<&CoreT> (core expects reference to core type)
630                        // Split into two bindings to avoid temporary value dropped while borrowed (E0716)
631                        write!(
632                            bindings,
633                            "let {name}_owned: Option<{core_type_path}> = {name}.map(Into::into);\n    let {name}_core = {name}_owned.as_ref();\n    ",
634                            name = p.name
635                        )
636                        .ok();
637                    } else {
638                        write!(
639                            bindings,
640                            "let {}_core: Option<{core_type_path}> = {}.map(Into::into);\n    ",
641                            p.name, p.name
642                        )
643                        .ok();
644                    }
645                } else if promoted {
646                    // Promoted-optional: unwrap then convert. Add explicit type annotation to help type inference.
647                    write!(
648                        bindings,
649                        "let {}_core: {core_type_path} = {}.expect(\"'{}' is required\").into();\n    ",
650                        p.name, p.name, p.name
651                    )
652                    .ok();
653                } else {
654                    // Non-optional: add explicit type annotation to help type inference
655                    write!(
656                        bindings,
657                        "let {}_core: {core_type_path} = {}.into();\n    ",
658                        p.name, p.name
659                    )
660                    .ok();
661                }
662            }
663            TypeRef::Vec(inner) if matches!(inner.as_ref(), TypeRef::Named(n) if !opaque_types.contains(n.as_str())) => {
664                let promoted = promote && crate::shared::is_promoted_optional(params, idx);
665                if p.optional && p.is_ref {
666                    // Option<Vec<Named>> with is_ref: convert to Option<Vec<CoreType>>, then use as_deref()
667                    // This ensures elements are converted from binding to core type.
668                    write!(
669                        bindings,
670                        "let {}_core: Option<Vec<_>> = {}.as_ref().map(|v| v.iter().map(|x| x.clone().into()).collect());\n    ",
671                        p.name, p.name
672                    )
673                    .ok();
674                } else if p.optional {
675                    // Option<Vec<Named>> without is_ref: convert to concrete Vec
676                    write!(
677                        bindings,
678                        "let {}_core = {}.as_ref().map(|v| v.iter().map(|x| x.clone().into()).collect()).unwrap_or_default();\n    ",
679                        p.name, p.name
680                    )
681                    .ok();
682                } else if promoted {
683                    // Promoted-optional: unwrap then convert
684                    write!(
685                        bindings,
686                        "let {}_core: Vec<_> = {}.expect(\"'{}' is required\").into_iter().map(Into::into).collect();\n    ",
687                        p.name, p.name, p.name
688                    )
689                    .ok();
690                } else if p.is_ref {
691                    // Non-optional Vec<Named> with is_ref=true: generate let binding for conversion
692                    write!(
693                        bindings,
694                        "let {}_core: Vec<_> = {}.into_iter().map(Into::into).collect();\n    ",
695                        p.name, p.name
696                    )
697                    .ok();
698                } else {
699                    // Vec<Named>: convert each element
700                    write!(
701                        bindings,
702                        "let {}_core: Vec<_> = {}.into_iter().map(Into::into).collect();\n    ",
703                        p.name, p.name
704                    )
705                    .ok();
706                }
707            }
708            // Vec<String> with is_ref=true: core expects &[&str].
709            // Convert Vec<String> to Vec<&str> via intermediate binding.
710            TypeRef::Vec(inner) if matches!(inner.as_ref(), TypeRef::String | TypeRef::Char) && p.is_ref => {
711                write!(
712                    bindings,
713                    "let {n}_refs: Vec<&str> = {n}.iter().map(|s| s.as_str()).collect();\n    ",
714                    n = p.name,
715                )
716                .ok();
717            }
718            // Sanitized Vec<String> (originally Vec<tuple>): deserialize each JSON string.
719            TypeRef::Vec(inner)
720                if matches!(inner.as_ref(), TypeRef::String) && p.sanitized && p.original_type.is_some() =>
721            {
722                if p.optional {
723                    write!(
724                        bindings,
725                        "let {n}_core: Option<Vec<_>> = {n}.map(|strs| \
726                         strs.into_iter()\n    \
727                         .filter_map(|s| serde_json::from_str(&s).ok())\n    \
728                         .collect()\n    \
729                         );\n    ",
730                        n = p.name,
731                    )
732                    .ok();
733                } else {
734                    write!(
735                        bindings,
736                        "let {n}_core: Vec<_> = {n}.into_iter()\n    \
737                         .filter_map(|s| serde_json::from_str(&s).ok())\n    \
738                         .collect();\n    ",
739                        n = p.name,
740                    )
741                    .ok();
742                }
743            }
744            _ => {}
745        }
746    }
747    bindings
748}
749
750/// Generate serde-based let bindings for non-opaque Named params.
751/// Serializes binding types to JSON and deserializes to core types.
752/// Used when From impls don't exist (e.g., types with sanitized fields).
753/// `indent` is the whitespace prefix for each generated line (e.g., "    " for functions, "        " for methods).
754/// NOTE: This function should only be called when `cfg.has_serde` is true.
755/// The caller (functions.rs, methods.rs) must gate the call behind a `has_serde` check.
756pub fn gen_serde_let_bindings(
757    params: &[ParamDef],
758    opaque_types: &AHashSet<String>,
759    core_import: &str,
760    err_conv: &str,
761    indent: &str,
762) -> String {
763    let mut bindings = String::new();
764    for (idx, p) in params.iter().enumerate() {
765        let promoted = crate::shared::is_promoted_optional(params, idx);
766        match &p.ty {
767            TypeRef::Named(name) if !opaque_types.contains(name.as_str()) => {
768                let core_path = format!("{}::{}", core_import, name);
769                if p.optional {
770                    write!(
771                        bindings,
772                        "let {name}_core: Option<{core_path}> = {name}.map(|v| {{\n\
773                         {indent}    let json = serde_json::to_string(&v){err_conv}?;\n\
774                         {indent}    serde_json::from_str(&json){err_conv}\n\
775                         {indent}}}).transpose()?;\n{indent}",
776                        name = p.name,
777                        core_path = core_path,
778                        err_conv = err_conv,
779                        indent = indent,
780                    )
781                    .ok();
782                } else if promoted {
783                    // Promoted-optional: param is required in core but wrapped in Option<T>
784                    // in the binding because an earlier param is optional. Use unwrap_or_default()
785                    // so JS callers can omit it (pass undefined/null) to get default behaviour.
786                    write!(
787                        bindings,
788                        "let {name}_core: {core_path} = {name}.map(|v| {{\n\
789                         {indent}    let json = serde_json::to_string(&v){err_conv}?;\n\
790                         {indent}    serde_json::from_str::<{core_path}>(&json){err_conv}\n\
791                         {indent}}}).transpose()?{indent}.unwrap_or_default();\n{indent}",
792                        name = p.name,
793                        core_path = core_path,
794                        err_conv = err_conv,
795                        indent = indent,
796                    )
797                    .ok();
798                } else {
799                    write!(
800                        bindings,
801                        "let {name}_json = serde_json::to_string(&{name}){err_conv}?;\n\
802                         {indent}let {name}_core: {core_path} = serde_json::from_str(&{name}_json){err_conv}?;\n{indent}",
803                        name = p.name,
804                        core_path = core_path,
805                        err_conv = err_conv,
806                        indent = indent,
807                    )
808                    .ok();
809                }
810            }
811            TypeRef::Vec(inner) => {
812                if let TypeRef::Named(name) = inner.as_ref() {
813                    if !opaque_types.contains(name.as_str()) {
814                        let core_path = format!("{}::{}", core_import, name);
815                        if p.optional {
816                            write!(
817                                bindings,
818                                "let {name}_core: Option<Vec<{core_path}>> = {name}.map(|v| {{\n\
819                                 {indent}    let json = serde_json::to_string(&v){err_conv}?;\n\
820                                 {indent}    serde_json::from_str(&json){err_conv}\n\
821                                 {indent}}}).transpose()?;\n{indent}",
822                                name = p.name,
823                                core_path = core_path,
824                                err_conv = err_conv,
825                                indent = indent,
826                            )
827                            .ok();
828                        } else {
829                            write!(
830                                bindings,
831                                "let {name}_json = serde_json::to_string(&{name}){err_conv}?;\n\
832                                 {indent}let {name}_core: Vec<{core_path}> = serde_json::from_str(&{name}_json){err_conv}?;\n{indent}",
833                                name = p.name,
834                                core_path = core_path,
835                                err_conv = err_conv,
836                                indent = indent,
837                            )
838                            .ok();
839                        }
840                    }
841                } else if matches!(inner.as_ref(), TypeRef::String) && p.sanitized && p.original_type.is_some() {
842                    // Sanitized Vec<tuple>: binding accepts Vec<String> (JSON-encoded tuple items).
843                    // Deserialize each JSON string as a tuple using serde_json.
844                    if p.optional {
845                        write!(
846                            bindings,
847                            "let {n}_core: Option<Vec<_>> = {n}.map(|strs| {{\n\
848                             {indent}    strs.into_iter()\n\
849                             {indent}    .map(|s| serde_json::from_str::<_>(&s){err_conv})\n\
850                             {indent}    .collect::<Result<Vec<_>, _>>()\n\
851                             {indent}}}).transpose()?;\n{indent}",
852                            n = p.name,
853                            err_conv = err_conv,
854                            indent = indent,
855                        )
856                        .ok();
857                    } else {
858                        write!(
859                            bindings,
860                            "let {n}_core: Vec<_> = {n}.into_iter()\n\
861                             {indent}.map(|s| serde_json::from_str::<_>(&s){err_conv})\n\
862                             {indent}.collect::<Result<Vec<_>, _>>()?;\n{indent}",
863                            n = p.name,
864                            err_conv = err_conv,
865                            indent = indent,
866                        )
867                        .ok();
868                    }
869                }
870            }
871            _ => {}
872        }
873    }
874    bindings
875}
876
877/// Check if params contain any non-opaque Named types that need let bindings.
878/// This includes direct Named types, Vec<Named> types, Vec<String> params
879/// with is_ref=true (which need a Vec<&str> intermediate to pass as &[&str]),
880/// and sanitized Vec<String> params (which are JSON-deserialized to tuples).
881pub fn has_named_params(params: &[ParamDef], opaque_types: &AHashSet<String>) -> bool {
882    params.iter().any(|p| match &p.ty {
883        TypeRef::Named(name) if !opaque_types.contains(name.as_str()) => true,
884        TypeRef::Vec(inner) => {
885            // Vec<Named> always needs a conversion let binding.
886            // Sanitized Vec<String> needs JSON deserialization via let binding.
887            // Vec<String> with is_ref=true needs a _refs let binding for &[&str] conversion.
888            matches!(inner.as_ref(), TypeRef::Named(name) if !opaque_types.contains(name.as_str()))
889                || (matches!(inner.as_ref(), TypeRef::String | TypeRef::Char) && p.is_ref)
890                || (matches!(inner.as_ref(), TypeRef::String) && p.sanitized && p.original_type.is_some())
891        }
892        _ => false,
893    })
894}
895
896/// Check if a param type is safe for non-opaque delegation (no complex conversions needed).
897/// Vec and Map params can cause type mismatches (e.g. Vec<String> vs &[&str]).
898pub fn is_simple_non_opaque_param(ty: &TypeRef) -> bool {
899    match ty {
900        TypeRef::Primitive(_)
901        | TypeRef::String
902        | TypeRef::Char
903        | TypeRef::Bytes
904        | TypeRef::Path
905        | TypeRef::Unit
906        | TypeRef::Duration => true,
907        TypeRef::Optional(inner) => is_simple_non_opaque_param(inner),
908        _ => false,
909    }
910}
911
912/// Generate a lossy binding→core struct literal for non-opaque delegation.
913/// Sanitized fields use `Default::default()`, non-sanitized fields are cloned and converted.
914/// Fields are accessed via `self.` (behind &self), so all non-Copy types need `.clone()`.
915///
916/// NOTE: This assumes all binding struct fields implement Clone. If a field type does not
917/// implement Clone (e.g., `Mutex<T>`), it should be marked as `sanitized=true` so that
918/// `Default::default()` is used instead of calling `.clone()`. Backends that exclude types
919/// should mark such fields appropriately.
920pub fn gen_lossy_binding_to_core_fields(typ: &TypeDef, core_import: &str, option_duration_on_defaults: bool) -> String {
921    gen_lossy_binding_to_core_fields_inner(typ, core_import, false, option_duration_on_defaults)
922}
923
924/// Same as `gen_lossy_binding_to_core_fields` but declares `core_self` as mutable.
925pub fn gen_lossy_binding_to_core_fields_mut(
926    typ: &TypeDef,
927    core_import: &str,
928    option_duration_on_defaults: bool,
929) -> String {
930    gen_lossy_binding_to_core_fields_inner(typ, core_import, true, option_duration_on_defaults)
931}
932
933fn gen_lossy_binding_to_core_fields_inner(
934    typ: &TypeDef,
935    core_import: &str,
936    needs_mut: bool,
937    option_duration_on_defaults: bool,
938) -> String {
939    let core_path = crate::conversions::core_type_path(typ, core_import);
940    let mut_kw = if needs_mut { "mut " } else { "" };
941    // When has_stripped_cfg_fields is true we emit ..Default::default() at the end of the
942    // struct literal to fill cfg-gated fields that were stripped from the binding IR.
943    // Suppress clippy::needless_update because the fields only exist when the corresponding
944    // feature is enabled — without the feature, clippy thinks the spread is redundant.
945    let allow = if typ.has_stripped_cfg_fields {
946        "#[allow(clippy::needless_update)]\n        "
947    } else {
948        ""
949    };
950    let mut out = format!("{allow}let {mut_kw}core_self = {core_path} {{\n");
951    for field in &typ.fields {
952        let name = &field.name;
953        if field.sanitized {
954            writeln!(out, "            {name}: Default::default(),").ok();
955        } else {
956            let expr = match &field.ty {
957                TypeRef::Primitive(_) => format!("self.{name}"),
958                TypeRef::Duration => {
959                    if field.optional {
960                        format!("self.{name}.map(std::time::Duration::from_millis)")
961                    } else if option_duration_on_defaults && typ.has_default {
962                        // When option_duration_on_defaults is true, non-optional Duration fields
963                        // on has_default types are stored as Option<u64> in the binding struct.
964                        // Use .map(...).unwrap_or_default() so that None falls back to the core
965                        // type's Default (e.g. Duration::from_secs(30)) rather than Duration::ZERO.
966                        format!("self.{name}.map(std::time::Duration::from_millis).unwrap_or_default()")
967                    } else {
968                        format!("std::time::Duration::from_millis(self.{name})")
969                    }
970                }
971                TypeRef::String => format!("self.{name}.clone()"),
972                // Bytes: binding stores Vec<u8>. When core_wrapper == Bytes, core expects
973                // bytes::Bytes so we must call .into() to convert Vec<u8> → Bytes.
974                // When core_wrapper == None, the core field is also Vec<u8> (plain clone).
975                TypeRef::Bytes => {
976                    if field.core_wrapper == CoreWrapper::Bytes {
977                        format!("self.{name}.clone().into()")
978                    } else {
979                        format!("self.{name}.clone()")
980                    }
981                }
982                TypeRef::Char => {
983                    if field.optional {
984                        format!("self.{name}.as_ref().and_then(|s| s.chars().next())")
985                    } else {
986                        format!("self.{name}.chars().next().unwrap_or('*')")
987                    }
988                }
989                TypeRef::Path => {
990                    if field.optional {
991                        format!("self.{name}.clone().map(Into::into)")
992                    } else {
993                        format!("self.{name}.clone().into()")
994                    }
995                }
996                TypeRef::Named(_) => {
997                    if field.optional {
998                        format!("self.{name}.clone().map(Into::into)")
999                    } else {
1000                        format!("self.{name}.clone().into()")
1001                    }
1002                }
1003                TypeRef::Vec(inner) => match inner.as_ref() {
1004                    TypeRef::Named(_) => {
1005                        if field.optional {
1006                            // Option<Vec<Named(T)>>: map over the Option, then convert each element
1007                            format!("self.{name}.clone().map(|v| v.into_iter().map(Into::into).collect())")
1008                        } else {
1009                            format!("self.{name}.clone().into_iter().map(Into::into).collect()")
1010                        }
1011                    }
1012                    _ => format!("self.{name}.clone()"),
1013                },
1014                TypeRef::Optional(inner) => {
1015                    // When field.optional is also true, the binding field was flattened from
1016                    // Option<Option<T>> to Option<T>. Core expects Option<Option<T>>, so wrap
1017                    // with .map(Some) to reconstruct the double-optional.
1018                    let base = match inner.as_ref() {
1019                        TypeRef::Named(_) => {
1020                            format!("self.{name}.clone().map(Into::into)")
1021                        }
1022                        TypeRef::Duration => {
1023                            format!("self.{name}.map(|v| std::time::Duration::from_millis(v as u64))")
1024                        }
1025                        TypeRef::Vec(vi) if matches!(vi.as_ref(), TypeRef::Named(_)) => {
1026                            format!("self.{name}.clone().map(|v| v.into_iter().map(Into::into).collect())")
1027                        }
1028                        _ => format!("self.{name}.clone()"),
1029                    };
1030                    if field.optional {
1031                        format!("({base}).map(Some)")
1032                    } else {
1033                        base
1034                    }
1035                }
1036                TypeRef::Map(_, v) => match v.as_ref() {
1037                    TypeRef::Json => {
1038                        // HashMap<String, String> (binding) → HashMap<K, Value> (core).
1039                        // Emit `k.into()` so wrapped string keys (`Cow`, `Box<str>`, `Arc<str>`)
1040                        // — which the type resolver collapses to `TypeRef::String` — convert
1041                        // correctly. For a real `String` core key it is a no-op.
1042                        if field.optional {
1043                            format!(
1044                                "self.{name}.clone().map(|m| m.into_iter().map(|(k, v)| \
1045                                 (k.into(), serde_json::from_str(&v).unwrap_or(serde_json::Value::String(v)))).collect())"
1046                            )
1047                        } else {
1048                            format!(
1049                                "self.{name}.clone().into_iter().map(|(k, v)| \
1050                                 (k.into(), serde_json::from_str(&v).unwrap_or(serde_json::Value::String(v)))).collect()"
1051                            )
1052                        }
1053                    }
1054                    // Named values: each value needs Into conversion to bridge the binding wrapper
1055                    // type into the core type (e.g. PyExtractionPattern → ExtractionPattern).
1056                    TypeRef::Named(_) => {
1057                        if field.optional {
1058                            format!(
1059                                "self.{name}.clone().map(|m| m.into_iter().map(|(k, v)| (k.into(), v.into())).collect())"
1060                            )
1061                        } else {
1062                            format!("self.{name}.clone().into_iter().map(|(k, v)| (k.into(), v.into())).collect()")
1063                        }
1064                    }
1065                    // Collect to handle HashMap↔BTreeMap conversion
1066                    _ => {
1067                        if field.optional {
1068                            format!("self.{name}.clone().map(|m| m.into_iter().map(|(k, v)| (k.into(), v)).collect())")
1069                        } else {
1070                            format!("self.{name}.clone().into_iter().map(|(k, v)| (k.into(), v)).collect()")
1071                        }
1072                    }
1073                },
1074                TypeRef::Unit => format!("self.{name}.clone()"),
1075                TypeRef::Json => {
1076                    // String (binding) → serde_json::Value (core)
1077                    if field.optional {
1078                        format!("self.{name}.as_ref().and_then(|s| serde_json::from_str(s).ok())")
1079                    } else {
1080                        format!("serde_json::from_str(&self.{name}).unwrap_or_default()")
1081                    }
1082                }
1083            };
1084            // Newtype wrapping: when the field was resolved from a newtype (e.g. NodeIndex → u32),
1085            // re-wrap the binding value into the newtype for the core struct literal.
1086            // When `optional=true` and `ty` is a plain Primitive (not TypeRef::Optional), the core
1087            // field is actually `Option<NewtypeT>`, so we must use `.map(NewtypeT)` not `NewtypeT(...)`.
1088            let expr = if let Some(newtype_path) = &field.newtype_wrapper {
1089                match &field.ty {
1090                    TypeRef::Optional(_) => format!("({expr}).map({newtype_path})"),
1091                    TypeRef::Vec(_) => format!("({expr}).into_iter().map({newtype_path}).collect()"),
1092                    _ if field.optional => format!("({expr}).map({newtype_path})"),
1093                    _ => format!("{newtype_path}({expr})"),
1094                }
1095            } else {
1096                expr
1097            };
1098            writeln!(out, "            {name}: {expr},").ok();
1099        }
1100    }
1101    // Use ..Default::default() to fill cfg-gated fields stripped from the IR
1102    if typ.has_stripped_cfg_fields {
1103        out.push_str("            ..Default::default()\n");
1104    }
1105    out.push_str("        };\n        ");
1106    out
1107}
1108
1109/// Generate the body for an async call, unified across methods, static methods, and free functions.
1110///
1111/// - `core_call`: the expression to await, e.g. `inner.method(args)` or `CoreType::fn(args)`.
1112///   For Pyo3FutureIntoPy opaque methods this should reference `inner` (the Arc clone);
1113///   for all other patterns it may reference `self.inner` or a static call expression.
1114/// - `cfg`: binding configuration (determines which async pattern to emit)
1115/// - `has_error`: whether the core call returns a `Result`
1116/// - `return_wrap`: expression to produce the binding return value from `result`,
1117///   e.g. `"result"` or `"TypeName::from(result)"`
1118///
1119/// - `is_opaque`: whether the binding type is Arc-wrapped (affects TokioBlockOn wrapping)
1120/// - `inner_clone_line`: optional statement emitted before the pattern-specific body,
1121///   e.g. `"let inner = self.inner.clone();\n        "` for opaque instance methods, or `""`.
1122///   Required when `core_call` references `inner` (Pyo3FutureIntoPy opaque case).
1123#[allow(clippy::too_many_arguments)]
1124pub fn gen_async_body(
1125    core_call: &str,
1126    cfg: &RustBindingConfig,
1127    has_error: bool,
1128    return_wrap: &str,
1129    is_opaque: bool,
1130    inner_clone_line: &str,
1131    is_unit_return: bool,
1132    return_type: Option<&str>,
1133) -> String {
1134    let pattern_body = match cfg.async_pattern {
1135        AsyncPattern::Pyo3FutureIntoPy => {
1136            let result_handling = if has_error {
1137                format!(
1138                    "let result = {core_call}.await\n            \
1139                     .map_err(|e| PyErr::new::<PyRuntimeError, _>(e.to_string()))?;"
1140                )
1141            } else if is_unit_return {
1142                format!("{core_call}.await;")
1143            } else {
1144                format!("let result = {core_call}.await;")
1145            };
1146            let (ok_expr, extra_binding) = if is_unit_return && !has_error {
1147                ("()".to_string(), String::new())
1148            } else if return_wrap.contains(".into()") || return_wrap.contains("::from(") {
1149                // When return_wrap contains type conversions like .into() or ::from(),
1150                // bind to a variable to help type inference for the generic future_into_py.
1151                // This avoids E0283 "type annotations needed".
1152                let wrapped_var = "wrapped_result";
1153                let binding = if let Some(ret_type) = return_type {
1154                    // Add explicit type annotation to help type inference
1155                    format!("let {wrapped_var}: {ret_type} = {return_wrap};\n            ")
1156                } else {
1157                    format!("let {wrapped_var} = {return_wrap};\n            ")
1158                };
1159                (wrapped_var.to_string(), binding)
1160            } else {
1161                (return_wrap.to_string(), String::new())
1162            };
1163            format!(
1164                "pyo3_async_runtimes::tokio::future_into_py(py, async move {{\n            \
1165                 {result_handling}\n            \
1166                 {extra_binding}Ok({ok_expr})\n        }})"
1167            )
1168        }
1169        AsyncPattern::WasmNativeAsync => {
1170            let result_handling = if has_error {
1171                format!(
1172                    "let result = {core_call}.await\n        \
1173                     .map_err(|e| JsValue::from_str(&e.to_string()))?;"
1174                )
1175            } else if is_unit_return {
1176                format!("{core_call}.await;")
1177            } else {
1178                format!("let result = {core_call}.await;")
1179            };
1180            let ok_expr = if is_unit_return && !has_error {
1181                "()"
1182            } else {
1183                return_wrap
1184            };
1185            format!(
1186                "{result_handling}\n        \
1187                 Ok({ok_expr})"
1188            )
1189        }
1190        AsyncPattern::NapiNativeAsync => {
1191            let result_handling = if has_error {
1192                format!(
1193                    "let result = {core_call}.await\n            \
1194                     .map_err(|e| napi::Error::new(napi::Status::GenericFailure, e.to_string()))?;"
1195                )
1196            } else if is_unit_return {
1197                format!("{core_call}.await;")
1198            } else {
1199                format!("let result = {core_call}.await;")
1200            };
1201            if !has_error && !is_unit_return {
1202                // No error type: return value directly without Ok() wrapper
1203                format!(
1204                    "{result_handling}\n            \
1205                     {return_wrap}"
1206                )
1207            } else {
1208                let ok_expr = if is_unit_return && !has_error {
1209                    "()"
1210                } else {
1211                    return_wrap
1212                };
1213                format!(
1214                    "{result_handling}\n            \
1215                     Ok({ok_expr})"
1216                )
1217            }
1218        }
1219        AsyncPattern::TokioBlockOn => {
1220            if has_error {
1221                if is_opaque {
1222                    format!(
1223                        "let rt = tokio::runtime::Runtime::new()?;\n        \
1224                         let result = rt.block_on(async {{ {core_call}.await.map_err(|e| e.into()) }})?;\n        \
1225                         {return_wrap}"
1226                    )
1227                } else {
1228                    format!(
1229                        "let rt = tokio::runtime::Runtime::new()?;\n        \
1230                         rt.block_on(async {{ {core_call}.await.map_err(|e| e.into()) }})"
1231                    )
1232                }
1233            } else if is_opaque {
1234                if is_unit_return {
1235                    format!(
1236                        "let rt = tokio::runtime::Runtime::new()?;\n        \
1237                         rt.block_on(async {{ {core_call}.await }});"
1238                    )
1239                } else {
1240                    format!(
1241                        "let rt = tokio::runtime::Runtime::new()?;\n        \
1242                         let result = rt.block_on(async {{ {core_call}.await }});\n        \
1243                         {return_wrap}"
1244                    )
1245                }
1246            } else {
1247                format!(
1248                    "let rt = tokio::runtime::Runtime::new()?;\n        \
1249                     rt.block_on(async {{ {core_call}.await }})"
1250                )
1251            }
1252        }
1253        AsyncPattern::None => "todo!(\"async not supported by backend\")".to_string(),
1254    };
1255    if inner_clone_line.is_empty() {
1256        pattern_body
1257    } else {
1258        format!("{inner_clone_line}{pattern_body}")
1259    }
1260}
1261
1262/// Generate a compilable body for functions that can't be auto-delegated.
1263/// Returns a default value or error instead of `todo!()` which would panic.
1264///
1265/// `opaque_types` is the set of opaque type names (Arc-wrapped). Opaque types do not
1266/// implement `Default`, so returning `Default::default()` for their Named return types
1267/// would fail to compile. For those cases a `todo!()` body is emitted instead.
1268pub fn gen_unimplemented_body(
1269    return_type: &TypeRef,
1270    fn_name: &str,
1271    has_error: bool,
1272    cfg: &RustBindingConfig,
1273    params: &[ParamDef],
1274    opaque_types: &AHashSet<String>,
1275) -> String {
1276    // Suppress unused_variables by binding all params to `_`
1277    let suppress = if params.is_empty() {
1278        String::new()
1279    } else {
1280        let names: Vec<&str> = params.iter().map(|p| p.name.as_str()).collect();
1281        if names.len() == 1 {
1282            format!("let _ = {};\n        ", names[0])
1283        } else {
1284            format!("let _ = ({});\n        ", names.join(", "))
1285        }
1286    };
1287    let err_msg = format!("Not implemented: {fn_name}");
1288    let body = if has_error {
1289        // Backend-specific error return
1290        match cfg.async_pattern {
1291            AsyncPattern::Pyo3FutureIntoPy => {
1292                format!("Err(pyo3::exceptions::PyNotImplementedError::new_err(\"{err_msg}\"))")
1293            }
1294            AsyncPattern::NapiNativeAsync => {
1295                format!("Err(napi::Error::new(napi::Status::GenericFailure, \"{err_msg}\"))")
1296            }
1297            AsyncPattern::WasmNativeAsync => {
1298                format!("Err(JsValue::from_str(\"{err_msg}\"))")
1299            }
1300            _ => format!("Err(\"{err_msg}\".to_string())"),
1301        }
1302    } else {
1303        // Return type-appropriate default
1304        match return_type {
1305            TypeRef::Unit => "()".to_string(),
1306            TypeRef::String | TypeRef::Char | TypeRef::Path => format!("String::from(\"[unimplemented: {fn_name}]\")"),
1307            TypeRef::Bytes => "Vec::new()".to_string(),
1308            TypeRef::Primitive(p) => match p {
1309                alef_core::ir::PrimitiveType::Bool => "false".to_string(),
1310                alef_core::ir::PrimitiveType::F32 => "0.0f32".to_string(),
1311                alef_core::ir::PrimitiveType::F64 => "0.0f64".to_string(),
1312                _ => "0".to_string(),
1313            },
1314            TypeRef::Optional(_) => "None".to_string(),
1315            TypeRef::Vec(_) => "Vec::new()".to_string(),
1316            TypeRef::Map(_, _) => "Default::default()".to_string(),
1317            TypeRef::Duration => "0".to_string(),
1318            TypeRef::Named(name) => {
1319                // Opaque types (Arc-wrapped) do not implement Default — use todo!() to
1320                // produce a compilable placeholder that panics at runtime if called.
1321                // Non-opaque Named types (config structs) do derive Default, so use that.
1322                if opaque_types.contains(name.as_str()) {
1323                    format!("todo!(\"{err_msg}\")")
1324                } else {
1325                    "Default::default()".to_string()
1326                }
1327            }
1328            TypeRef::Json => {
1329                // Json return without error type: return Default::default()
1330                "Default::default()".to_string()
1331            }
1332        }
1333    };
1334    format!("{suppress}{body}")
1335}