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