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