Skip to main content

alef_codegen/generators/
binding_helpers.rs

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