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