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