Skip to main content

alef_codegen/generators/
binding_helpers.rs

1use crate::generators::{AsyncPattern, RustBindingConfig};
2use ahash::AHashSet;
3use alef_core::ir::{ParamDef, TypeDef, TypeRef};
4use std::fmt::Write;
5
6/// Wrap a core-call result for opaque delegation methods.
7///
8/// - `TypeRef::Named(n)` where `n == type_name` → re-wrap in `Self { inner: Arc::new(...) }`
9/// - `TypeRef::Named(n)` where `n` is another opaque type → wrap in `{n} { inner: Arc::new(...) }`
10/// - `TypeRef::Named(n)` where `n` is a non-opaque type → `todo!()` placeholder (From may not exist)
11/// - Everything else (primitives, String, Vec, etc.) → pass through unchanged
12/// - `TypeRef::Unit` → pass through unchanged
13///
14/// When `returns_cow` is true the core method returns `Cow<'_, T>`. `.into_owned()` is emitted
15/// before any further type conversion to obtain an owned `T`.
16pub fn wrap_return(
17    expr: &str,
18    return_type: &TypeRef,
19    type_name: &str,
20    opaque_types: &AHashSet<String>,
21    self_is_opaque: bool,
22    returns_ref: bool,
23    returns_cow: bool,
24) -> String {
25    match return_type {
26        TypeRef::Named(n) if n == type_name && self_is_opaque => {
27            if returns_cow {
28                format!("Self {{ inner: Arc::new({expr}.into_owned()) }}")
29            } else if returns_ref {
30                format!("Self {{ inner: Arc::new({expr}.clone()) }}")
31            } else {
32                format!("Self {{ inner: Arc::new({expr}) }}")
33            }
34        }
35        TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
36            if returns_cow {
37                format!("{n} {{ inner: Arc::new({expr}.into_owned()) }}")
38            } else if returns_ref {
39                format!("{n} {{ inner: Arc::new({expr}.clone()) }}")
40            } else {
41                format!("{n} {{ inner: Arc::new({expr}) }}")
42            }
43        }
44        TypeRef::Named(_) => {
45            // Non-opaque Named return type — use .into() for core→binding From conversion.
46            // When the core returns a Cow, call .into_owned() first to get an owned T.
47            // When the core returns a reference, clone first since From<&T> typically doesn't exist.
48            // NOTE: If this type was sanitized to String in the binding, From won't exist.
49            // The calling backend should check method.sanitized before delegating.
50            // This code assumes non-sanitized Named types have From impls.
51            if returns_cow {
52                format!("{expr}.into_owned().into()")
53            } else if returns_ref {
54                format!("{expr}.clone().into()")
55            } else {
56                format!("{expr}.into()")
57            }
58        }
59        // String/Bytes: only convert when the core returns a reference (&str→String, &[u8]→Vec<u8>).
60        // When owned (returns_ref=false), both sides are already String/Vec<u8> — skip .into().
61        TypeRef::String | TypeRef::Bytes => {
62            if returns_ref {
63                format!("{expr}.into()")
64            } else {
65                expr.to_string()
66            }
67        }
68        // Path: PathBuf→String needs to_string_lossy, &Path→String too
69        TypeRef::Path => format!("{expr}.to_string_lossy().to_string()"),
70        // Duration: core returns std::time::Duration, binding uses u64 (millis)
71        TypeRef::Duration => format!("{expr}.as_millis() as u64"),
72        // Json: serde_json::Value needs serialization to string
73        TypeRef::Json => format!("{expr}.to_string()"),
74        // Optional: wrap inner conversion in .map(...)
75        TypeRef::Optional(inner) => match inner.as_ref() {
76            TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
77                if returns_ref {
78                    format!("{expr}.map(|v| {n} {{ inner: Arc::new(v.clone()) }})")
79                } else {
80                    format!("{expr}.map(|v| {n} {{ inner: Arc::new(v) }})")
81                }
82            }
83            TypeRef::Named(_) => {
84                if returns_ref {
85                    format!("{expr}.map(|v| v.clone().into())")
86                } else {
87                    format!("{expr}.map(Into::into)")
88                }
89            }
90            TypeRef::Path => {
91                format!("{expr}.map(Into::into)")
92            }
93            TypeRef::String | TypeRef::Bytes => {
94                if returns_ref {
95                    format!("{expr}.map(Into::into)")
96                } else {
97                    expr.to_string()
98                }
99            }
100            TypeRef::Duration => format!("{expr}.map(|d| d.as_millis() as u64)"),
101            TypeRef::Json => format!("{expr}.map(ToString::to_string)"),
102            // Optional<Vec<Named>>: convert each element in the inner Vec
103            TypeRef::Vec(vec_inner) => match vec_inner.as_ref() {
104                TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
105                    if returns_ref {
106                        format!("{expr}.map(|v| v.into_iter().map(|x| {n} {{ inner: Arc::new(x.clone()) }}).collect())")
107                    } else {
108                        format!("{expr}.map(|v| v.into_iter().map(|x| {n} {{ inner: Arc::new(x) }}).collect())")
109                    }
110                }
111                TypeRef::Named(_) => {
112                    if returns_ref {
113                        format!("{expr}.map(|v| v.into_iter().map(|x| x.clone().into()).collect())")
114                    } else {
115                        format!("{expr}.map(|v| v.into_iter().map(Into::into).collect())")
116                    }
117                }
118                _ => expr.to_string(),
119            },
120            _ => expr.to_string(),
121        },
122        // Vec: map each element through the appropriate conversion
123        TypeRef::Vec(inner) => match inner.as_ref() {
124            TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
125                if returns_ref {
126                    format!("{expr}.into_iter().map(|v| {n} {{ inner: Arc::new(v.clone()) }}).collect()")
127                } else {
128                    format!("{expr}.into_iter().map(|v| {n} {{ inner: Arc::new(v) }}).collect()")
129                }
130            }
131            TypeRef::Named(_) => {
132                if returns_ref {
133                    format!("{expr}.into_iter().map(|v| v.clone().into()).collect()")
134                } else {
135                    format!("{expr}.into_iter().map(Into::into).collect()")
136                }
137            }
138            TypeRef::Path => {
139                format!("{expr}.into_iter().map(Into::into).collect()")
140            }
141            TypeRef::String | TypeRef::Bytes => {
142                if returns_ref {
143                    format!("{expr}.into_iter().map(Into::into).collect()")
144                } else {
145                    expr.to_string()
146                }
147            }
148            _ => expr.to_string(),
149        },
150        _ => expr.to_string(),
151    }
152}
153
154/// Unwrap a newtype return value when `return_newtype_wrapper` is set.
155///
156/// Core function returns a newtype (e.g. `NodeIndex(u32)`), but the binding return type
157/// is the inner type (e.g. `u32`). Access `.0` to unwrap the newtype.
158pub fn apply_return_newtype_unwrap(expr: &str, return_newtype_wrapper: &Option<String>) -> String {
159    match return_newtype_wrapper {
160        Some(_) => format!("({expr}).0"),
161        None => expr.to_string(),
162    }
163}
164
165/// Build call argument expressions from parameters.
166/// - Opaque Named types: unwrap Arc wrapper via `(*param.inner).clone()`
167/// - Non-opaque Named types: `.into()` for From conversion
168/// - String/Path/Bytes: `&param` since core functions typically take `&str`/`&Path`/`&[u8]`
169/// - Params with `newtype_wrapper` set: re-wrap the raw value in the newtype constructor
170///   (e.g., `NodeIndex(parent)`) since the binding resolved `NodeIndex(u32)` → `u32`.
171///
172/// NOTE: This function does not perform serde-based conversion. For Named params that lack
173/// From impls (e.g., due to sanitized fields), use `gen_serde_let_bindings` instead when
174/// `cfg.has_serde` is true, or fall back to `gen_unimplemented_body`.
175pub fn gen_call_args(params: &[ParamDef], opaque_types: &AHashSet<String>) -> String {
176    params
177        .iter()
178        .enumerate()
179        .map(|(idx, p)| {
180            let promoted = crate::shared::is_promoted_optional(params, idx);
181            // If a required param was promoted to optional, unwrap it before use.
182            // Note: promoted params that are not Optional<T> will NOT call .expect() because
183            // promoted refers to the PyO3 signature constraint, not the actual Rust type.
184            // The function_params logic wraps promoted params in Option<T>, making them truly optional.
185            let unwrap_suffix = if promoted && p.optional {
186                format!(".expect(\"'{}' is required\")", p.name)
187            } else {
188                String::new()
189            };
190            // If this param's type was resolved from a newtype (e.g. NodeIndex(u32) → u32),
191            // re-wrap the raw value back into the newtype when calling core.
192            if let Some(newtype_path) = &p.newtype_wrapper {
193                return if p.optional {
194                    format!("{}.map({newtype_path})", p.name)
195                } else if promoted {
196                    format!("{newtype_path}({}{})", p.name, unwrap_suffix)
197                } else {
198                    format!("{newtype_path}({})", p.name)
199                };
200            }
201            match &p.ty {
202                TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
203                    // Opaque type: borrow through Arc to get &CoreType
204                    if p.optional {
205                        format!("{}.as_ref().map(|v| &v.inner)", p.name)
206                    } else if promoted {
207                        format!("{}{}.inner.as_ref()", p.name, unwrap_suffix)
208                    } else {
209                        format!("&{}.inner", p.name)
210                    }
211                }
212                TypeRef::Named(_) => {
213                    if p.optional {
214                        if p.is_ref {
215                            // Option<T> (binding) -> Option<&CoreT>: use as_ref() only
216                            // The Into conversion must happen in a let binding to avoid E0716
217                            format!("{}.as_ref()", p.name)
218                        } else {
219                            format!("{}.map(Into::into)", p.name)
220                        }
221                    } else if promoted {
222                        format!("{}{}.into()", p.name, unwrap_suffix)
223                    } else {
224                        format!("{}.into()", p.name)
225                    }
226                }
227                // String → &str for core function calls when is_ref=true,
228                // or pass owned when is_ref=false (core takes String/impl Into<String>).
229                // For optional params: as_deref() when is_ref=true, pass owned when is_ref=false.
230                TypeRef::String | TypeRef::Char => {
231                    if p.optional {
232                        if p.is_ref {
233                            format!("{}.as_deref()", p.name)
234                        } else {
235                            p.name.clone()
236                        }
237                    } else if promoted {
238                        if p.is_ref {
239                            format!("&{}{}", p.name, unwrap_suffix)
240                        } else {
241                            format!("{}{}", p.name, unwrap_suffix)
242                        }
243                    } else if p.is_ref {
244                        format!("&{}", p.name)
245                    } else {
246                        p.name.clone()
247                    }
248                }
249                // Path → PathBuf/&Path for core function calls
250                TypeRef::Path => {
251                    if p.optional && p.is_ref {
252                        format!("{}.as_deref().map(std::path::Path::new)", p.name)
253                    } else if p.optional {
254                        format!("{}.map(std::path::PathBuf::from)", p.name)
255                    } else if promoted {
256                        format!("std::path::PathBuf::from({}{})", p.name, unwrap_suffix)
257                    } else if p.is_ref {
258                        format!("std::path::Path::new(&{})", p.name)
259                    } else {
260                        format!("std::path::PathBuf::from({})", p.name)
261                    }
262                }
263                TypeRef::Bytes => {
264                    if p.optional {
265                        if p.is_ref {
266                            format!("{}.as_deref()", p.name)
267                        } else {
268                            p.name.clone()
269                        }
270                    } else if promoted {
271                        // is_ref=true: pass &Vec<u8> (core takes &[u8])
272                        // is_ref=false: pass Vec<u8> (core takes owned Vec<u8>)
273                        if p.is_ref {
274                            format!("&{}{}", p.name, unwrap_suffix)
275                        } else {
276                            format!("{}{}", p.name, unwrap_suffix)
277                        }
278                    } else {
279                        // is_ref=true: pass &Vec<u8> (core takes &[u8])
280                        // is_ref=false: pass Vec<u8> (core takes owned Vec<u8>)
281                        if p.is_ref {
282                            format!("&{}", p.name)
283                        } else {
284                            p.name.clone()
285                        }
286                    }
287                }
288                // Duration: binding uses u64 (millis), core uses std::time::Duration
289                TypeRef::Duration => {
290                    if p.optional {
291                        format!("{}.map(std::time::Duration::from_millis)", p.name)
292                    } else if promoted {
293                        format!("std::time::Duration::from_millis({}{})", p.name, unwrap_suffix)
294                    } else {
295                        format!("std::time::Duration::from_millis({})", p.name)
296                    }
297                }
298                TypeRef::Json => {
299                    // JSON params: binding has String, core expects serde_json::Value
300                    if p.optional {
301                        format!("{}.as_ref().and_then(|s| serde_json::from_str(s).ok())", p.name)
302                    } else if promoted {
303                        format!("serde_json::from_str(&{}{}).unwrap_or_default()", p.name, unwrap_suffix)
304                    } else {
305                        format!("serde_json::from_str(&{}).unwrap_or_default()", p.name)
306                    }
307                }
308                TypeRef::Vec(inner) => {
309                    // Vec<Named>: convert each element via Into::into when used with let bindings
310                    if matches!(inner.as_ref(), TypeRef::Named(_)) {
311                        if p.optional {
312                            if p.is_ref {
313                                format!("{}.as_deref()", p.name)
314                            } else {
315                                p.name.clone()
316                            }
317                        } else if promoted {
318                            if p.is_ref {
319                                format!("&{}{}", p.name, unwrap_suffix)
320                            } else {
321                                format!("{}{}", p.name, unwrap_suffix)
322                            }
323                        } else if p.is_ref {
324                            format!("&{}", p.name)
325                        } else {
326                            p.name.clone()
327                        }
328                    } else {
329                        if promoted {
330                            format!("{}{}", p.name, unwrap_suffix)
331                        } else if p.is_mut && p.optional {
332                            format!("{}.as_deref_mut()", p.name)
333                        } else if p.is_mut {
334                            format!("&mut {}", p.name)
335                        } else if p.is_ref && p.optional {
336                            format!("{}.as_deref()", p.name)
337                        } else if p.is_ref {
338                            format!("&{}", p.name)
339                        } else {
340                            p.name.clone()
341                        }
342                    }
343                }
344                _ => {
345                    if promoted {
346                        format!("{}{}", p.name, unwrap_suffix)
347                    } else if p.is_mut && p.optional {
348                        format!("{}.as_deref_mut()", p.name)
349                    } else if p.is_mut {
350                        format!("&mut {}", p.name)
351                    } else if p.is_ref && p.optional {
352                        // Optional ref params: use as_deref() for slice/str coercion
353                        // Option<Vec<T>> -> Option<&[T]>, Option<String> -> Option<&str>
354                        format!("{}.as_deref()", p.name)
355                    } else if p.is_ref {
356                        format!("&{}", p.name)
357                    } else {
358                        p.name.clone()
359                    }
360                }
361            }
362        })
363        .collect::<Vec<_>>()
364        .join(", ")
365}
366
367/// Build call argument expressions using pre-bound let bindings for non-opaque Named params.
368/// Non-opaque Named params use `&{name}_core` references instead of `.into()`.
369pub fn gen_call_args_with_let_bindings(params: &[ParamDef], opaque_types: &AHashSet<String>) -> String {
370    params
371        .iter()
372        .enumerate()
373        .map(|(idx, p)| {
374            let promoted = crate::shared::is_promoted_optional(params, idx);
375            let unwrap_suffix = if promoted {
376                format!(".expect(\"'{}' is required\")", p.name)
377            } else {
378                String::new()
379            };
380            // If this param's type was resolved from a newtype, re-wrap when calling core.
381            if let Some(newtype_path) = &p.newtype_wrapper {
382                return if p.optional {
383                    format!("{}.map({newtype_path})", p.name)
384                } else if promoted {
385                    format!("{newtype_path}({}{})", p.name, unwrap_suffix)
386                } else {
387                    format!("{newtype_path}({})", p.name)
388                };
389            }
390            match &p.ty {
391                TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
392                    if p.optional {
393                        format!("{}.as_ref().map(|v| &v.inner)", p.name)
394                    } else if promoted {
395                        format!("{}{}.inner.as_ref()", p.name, unwrap_suffix)
396                    } else {
397                        format!("&{}.inner", p.name)
398                    }
399                }
400                TypeRef::Named(_) => {
401                    if p.optional && p.is_ref {
402                        // Let binding already created Option<&T> via .as_ref()
403                        format!("{}_core", p.name)
404                    } else if p.is_ref {
405                        // Let binding created T, need reference for call
406                        format!("&{}_core", p.name)
407                    } else {
408                        format!("{}_core", p.name)
409                    }
410                }
411                TypeRef::String | TypeRef::Char => {
412                    if p.optional {
413                        if p.is_ref {
414                            format!("{}.as_deref()", p.name)
415                        } else {
416                            p.name.clone()
417                        }
418                    } else if promoted {
419                        if p.is_ref {
420                            format!("&{}{}", p.name, unwrap_suffix)
421                        } else {
422                            format!("{}{}", p.name, unwrap_suffix)
423                        }
424                    } else if p.is_ref {
425                        format!("&{}", p.name)
426                    } else {
427                        p.name.clone()
428                    }
429                }
430                TypeRef::Path => {
431                    if promoted {
432                        format!("std::path::PathBuf::from({}{})", p.name, unwrap_suffix)
433                    } else if p.optional && p.is_ref {
434                        format!("{}.as_deref().map(std::path::Path::new)", p.name)
435                    } else if p.optional {
436                        format!("{}.map(std::path::PathBuf::from)", p.name)
437                    } else if p.is_ref {
438                        format!("std::path::Path::new(&{})", p.name)
439                    } else {
440                        format!("std::path::PathBuf::from({})", p.name)
441                    }
442                }
443                TypeRef::Bytes => {
444                    if p.optional {
445                        if p.is_ref {
446                            format!("{}.as_deref()", p.name)
447                        } else {
448                            p.name.clone()
449                        }
450                    } else if promoted {
451                        // is_ref=true: pass &Vec<u8> (core takes &[u8])
452                        // is_ref=false: pass Vec<u8> (core takes owned Vec<u8>)
453                        if p.is_ref {
454                            format!("&{}{}", p.name, unwrap_suffix)
455                        } else {
456                            format!("{}{}", p.name, unwrap_suffix)
457                        }
458                    } else {
459                        // is_ref=true: pass &Vec<u8> (core takes &[u8])
460                        // is_ref=false: pass Vec<u8> (core takes owned Vec<u8>)
461                        if p.is_ref {
462                            format!("&{}", p.name)
463                        } else {
464                            p.name.clone()
465                        }
466                    }
467                }
468                TypeRef::Duration => {
469                    if p.optional {
470                        format!("{}.map(std::time::Duration::from_millis)", p.name)
471                    } else if promoted {
472                        format!("std::time::Duration::from_millis({}{})", p.name, unwrap_suffix)
473                    } else {
474                        format!("std::time::Duration::from_millis({})", p.name)
475                    }
476                }
477                TypeRef::Vec(inner) => {
478                    // Vec<Named>: use let binding that converts each element
479                    if matches!(inner.as_ref(), TypeRef::Named(_)) {
480                        if p.optional && p.is_ref {
481                            // Let binding creates Option<Vec<CoreType>>, use as_deref() to get Option<&[CoreType]>
482                            format!("{}_core.as_deref()", p.name)
483                        } else if p.optional {
484                            // Let binding creates Option<Vec<CoreType>>, no ref needed
485                            format!("{}_core", p.name)
486                        } else if p.is_ref {
487                            format!("&{}_core", p.name)
488                        } else {
489                            format!("{}_core", p.name)
490                        }
491                    } else {
492                        if promoted {
493                            format!("{}{}", p.name, unwrap_suffix)
494                        } else if p.is_ref && p.optional {
495                            format!("{}.as_deref()", p.name)
496                        } else if p.is_ref {
497                            format!("&{}", p.name)
498                        } else {
499                            p.name.clone()
500                        }
501                    }
502                }
503                _ => {
504                    if promoted {
505                        format!("{}{}", p.name, unwrap_suffix)
506                    } else if p.is_ref && p.optional {
507                        format!("{}.as_deref()", p.name)
508                    } else if p.is_ref {
509                        format!("&{}", p.name)
510                    } else {
511                        p.name.clone()
512                    }
513                }
514            }
515        })
516        .collect::<Vec<_>>()
517        .join(", ")
518}
519
520/// Generate let bindings for non-opaque Named params, converting them to core types.
521pub fn gen_named_let_bindings_pub(params: &[ParamDef], opaque_types: &AHashSet<String>, core_import: &str) -> String {
522    gen_named_let_bindings(params, opaque_types, core_import)
523}
524
525/// Like `gen_named_let_bindings_pub` but without optional-promotion semantics.
526/// Use this for backends (e.g. WASM) that do not promote non-optional params to `Option<T>`.
527pub fn gen_named_let_bindings_no_promote(
528    params: &[ParamDef],
529    opaque_types: &AHashSet<String>,
530    core_import: &str,
531) -> String {
532    gen_named_let_bindings_inner(params, opaque_types, core_import, false)
533}
534
535pub(super) fn gen_named_let_bindings(
536    params: &[ParamDef],
537    opaque_types: &AHashSet<String>,
538    core_import: &str,
539) -> String {
540    gen_named_let_bindings_inner(params, opaque_types, core_import, true)
541}
542
543fn gen_named_let_bindings_inner(
544    params: &[ParamDef],
545    opaque_types: &AHashSet<String>,
546    core_import: &str,
547    promote: bool,
548) -> String {
549    let mut bindings = String::new();
550    for (idx, p) in params.iter().enumerate() {
551        match &p.ty {
552            TypeRef::Named(name) if !opaque_types.contains(name.as_str()) => {
553                let promoted = promote && crate::shared::is_promoted_optional(params, idx);
554                let core_type_path = format!("{}::{}", core_import, name);
555                if p.optional {
556                    if p.is_ref {
557                        // Option<T> (binding) -> Option<&CoreT> (core expects reference to core type)
558                        // Split into two bindings to avoid temporary value dropped while borrowed (E0716)
559                        write!(
560                            bindings,
561                            "let {name}_owned: Option<{core_type_path}> = {name}.map(Into::into);\n    let {name}_core = {name}_owned.as_ref();\n    ",
562                            name = p.name
563                        )
564                        .ok();
565                    } else {
566                        write!(
567                            bindings,
568                            "let {}_core: Option<{core_type_path}> = {}.map(Into::into);\n    ",
569                            p.name, p.name
570                        )
571                        .ok();
572                    }
573                } else if promoted {
574                    // Promoted-optional: unwrap then convert. Add explicit type annotation to help type inference.
575                    write!(
576                        bindings,
577                        "let {}_core: {core_type_path} = {}.expect(\"'{}' is required\").into();\n    ",
578                        p.name, p.name, p.name
579                    )
580                    .ok();
581                } else {
582                    // Non-optional: add explicit type annotation to help type inference
583                    write!(
584                        bindings,
585                        "let {}_core: {core_type_path} = {}.into();\n    ",
586                        p.name, p.name
587                    )
588                    .ok();
589                }
590            }
591            TypeRef::Vec(inner) if matches!(inner.as_ref(), TypeRef::Named(n) if !opaque_types.contains(n.as_str())) => {
592                let promoted = promote && crate::shared::is_promoted_optional(params, idx);
593                if p.optional && p.is_ref {
594                    // Option<Vec<Named>> with is_ref: convert to Option<Vec<CoreType>>, then use as_deref()
595                    // This ensures elements are converted from binding to core type.
596                    write!(
597                        bindings,
598                        "let {}_core: Option<Vec<_>> = {}.as_ref().map(|v| v.iter().map(|x| x.clone().into()).collect());\n    ",
599                        p.name, p.name
600                    )
601                    .ok();
602                } else if p.optional {
603                    // Option<Vec<Named>> without is_ref: convert to concrete Vec
604                    write!(
605                        bindings,
606                        "let {}_core = {}.as_ref().map(|v| v.iter().map(|x| x.clone().into()).collect()).unwrap_or_default();\n    ",
607                        p.name, p.name
608                    )
609                    .ok();
610                } else if promoted {
611                    // Promoted-optional: unwrap then convert
612                    write!(
613                        bindings,
614                        "let {}_core: Vec<_> = {}.expect(\"'{}' is required\").into_iter().map(Into::into).collect();\n    ",
615                        p.name, p.name, p.name
616                    )
617                    .ok();
618                } else if p.is_ref {
619                    // Non-optional Vec<Named> with is_ref=true: generate let binding for conversion
620                    write!(
621                        bindings,
622                        "let {}_core: Vec<_> = {}.into_iter().map(Into::into).collect();\n    ",
623                        p.name, p.name
624                    )
625                    .ok();
626                } else {
627                    // Vec<Named>: convert each element
628                    write!(
629                        bindings,
630                        "let {}_core: Vec<_> = {}.into_iter().map(Into::into).collect();\n    ",
631                        p.name, p.name
632                    )
633                    .ok();
634                }
635            }
636            _ => {}
637        }
638    }
639    bindings
640}
641
642/// Generate serde-based let bindings for non-opaque Named params.
643/// Serializes binding types to JSON and deserializes to core types.
644/// Used when From impls don't exist (e.g., types with sanitized fields).
645/// `indent` is the whitespace prefix for each generated line (e.g., "    " for functions, "        " for methods).
646/// NOTE: This function should only be called when `cfg.has_serde` is true.
647/// The caller (functions.rs, methods.rs) must gate the call behind a `has_serde` check.
648pub fn gen_serde_let_bindings(
649    params: &[ParamDef],
650    opaque_types: &AHashSet<String>,
651    core_import: &str,
652    err_conv: &str,
653    indent: &str,
654) -> String {
655    let mut bindings = String::new();
656    for p in params {
657        if let TypeRef::Named(name) = &p.ty {
658            if !opaque_types.contains(name.as_str()) {
659                let core_path = format!("{}::{}", core_import, name);
660                if p.optional {
661                    write!(
662                        bindings,
663                        "let {name}_core: Option<{core_path}> = {name}.map(|v| {{\n\
664                         {indent}    let json = serde_json::to_string(&v){err_conv}?;\n\
665                         {indent}    serde_json::from_str(&json){err_conv}\n\
666                         {indent}}}).transpose()?;\n{indent}",
667                        name = p.name,
668                        core_path = core_path,
669                        err_conv = err_conv,
670                        indent = indent,
671                    )
672                    .ok();
673                } else {
674                    write!(
675                        bindings,
676                        "let {name}_json = serde_json::to_string(&{name}){err_conv}?;\n\
677                         {indent}let {name}_core: {core_path} = serde_json::from_str(&{name}_json){err_conv}?;\n{indent}",
678                        name = p.name,
679                        core_path = core_path,
680                        err_conv = err_conv,
681                        indent = indent,
682                    )
683                    .ok();
684                }
685            }
686        }
687    }
688    bindings
689}
690
691/// Check if params contain any non-opaque Named types that need let bindings.
692/// This includes both direct Named types and Vec<Named> types.
693pub fn has_named_params(params: &[ParamDef], opaque_types: &AHashSet<String>) -> bool {
694    params.iter().any(|p| match &p.ty {
695        TypeRef::Named(name) if !opaque_types.contains(name.as_str()) => true,
696        TypeRef::Vec(inner) => {
697            matches!(inner.as_ref(), TypeRef::Named(name) if !opaque_types.contains(name.as_str()))
698        }
699        _ => false,
700    })
701}
702
703/// Check if a param type is safe for non-opaque delegation (no complex conversions needed).
704/// Vec and Map params can cause type mismatches (e.g. Vec<String> vs &[&str]).
705pub fn is_simple_non_opaque_param(ty: &TypeRef) -> bool {
706    match ty {
707        TypeRef::Primitive(_)
708        | TypeRef::String
709        | TypeRef::Char
710        | TypeRef::Bytes
711        | TypeRef::Path
712        | TypeRef::Unit
713        | TypeRef::Duration => true,
714        TypeRef::Optional(inner) => is_simple_non_opaque_param(inner),
715        _ => false,
716    }
717}
718
719/// Generate a lossy binding→core struct literal for non-opaque delegation.
720/// Sanitized fields use `Default::default()`, non-sanitized fields are cloned and converted.
721/// Fields are accessed via `self.` (behind &self), so all non-Copy types need `.clone()`.
722///
723/// NOTE: This assumes all binding struct fields implement Clone. If a field type does not
724/// implement Clone (e.g., `Mutex<T>`), it should be marked as `sanitized=true` so that
725/// `Default::default()` is used instead of calling `.clone()`. Backends that exclude types
726/// should mark such fields appropriately.
727pub fn gen_lossy_binding_to_core_fields(typ: &TypeDef, core_import: &str, option_duration_on_defaults: bool) -> String {
728    gen_lossy_binding_to_core_fields_inner(typ, core_import, false, option_duration_on_defaults)
729}
730
731/// Same as `gen_lossy_binding_to_core_fields` but declares `core_self` as mutable.
732pub fn gen_lossy_binding_to_core_fields_mut(
733    typ: &TypeDef,
734    core_import: &str,
735    option_duration_on_defaults: bool,
736) -> String {
737    gen_lossy_binding_to_core_fields_inner(typ, core_import, true, option_duration_on_defaults)
738}
739
740fn gen_lossy_binding_to_core_fields_inner(
741    typ: &TypeDef,
742    core_import: &str,
743    needs_mut: bool,
744    option_duration_on_defaults: bool,
745) -> String {
746    let core_path = crate::conversions::core_type_path(typ, core_import);
747    let mut_kw = if needs_mut { "mut " } else { "" };
748    // When has_stripped_cfg_fields is true we emit ..Default::default() at the end of the
749    // struct literal to fill cfg-gated fields that were stripped from the binding IR.
750    // Suppress clippy::needless_update because the fields only exist when the corresponding
751    // feature is enabled — without the feature, clippy thinks the spread is redundant.
752    let allow = if typ.has_stripped_cfg_fields {
753        "#[allow(clippy::needless_update)]\n        "
754    } else {
755        ""
756    };
757    let mut out = format!("{allow}let {mut_kw}core_self = {core_path} {{\n");
758    for field in &typ.fields {
759        let name = &field.name;
760        if field.sanitized {
761            writeln!(out, "            {name}: Default::default(),").ok();
762        } else {
763            let expr = match &field.ty {
764                TypeRef::Primitive(_) => format!("self.{name}"),
765                TypeRef::Duration => {
766                    if field.optional {
767                        format!("self.{name}.map(std::time::Duration::from_millis)")
768                    } else if option_duration_on_defaults && typ.has_default {
769                        // When option_duration_on_defaults is true, non-optional Duration fields
770                        // on has_default types are stored as Option<u64> in the binding struct.
771                        // Use .map(...).unwrap_or_default() so that None falls back to the core
772                        // type's Default (e.g. Duration::from_secs(30)) rather than Duration::ZERO.
773                        format!("self.{name}.map(std::time::Duration::from_millis).unwrap_or_default()")
774                    } else {
775                        format!("std::time::Duration::from_millis(self.{name})")
776                    }
777                }
778                TypeRef::String | TypeRef::Bytes => format!("self.{name}.clone()"),
779                TypeRef::Char => {
780                    if field.optional {
781                        format!("self.{name}.as_ref().and_then(|s| s.chars().next())")
782                    } else {
783                        format!("self.{name}.chars().next().unwrap_or('*')")
784                    }
785                }
786                TypeRef::Path => {
787                    if field.optional {
788                        format!("self.{name}.clone().map(Into::into)")
789                    } else {
790                        format!("self.{name}.clone().into()")
791                    }
792                }
793                TypeRef::Named(_) => {
794                    if field.optional {
795                        format!("self.{name}.clone().map(Into::into)")
796                    } else {
797                        format!("self.{name}.clone().into()")
798                    }
799                }
800                TypeRef::Vec(inner) => match inner.as_ref() {
801                    TypeRef::Named(_) => {
802                        if field.optional {
803                            // Option<Vec<Named(T)>>: map over the Option, then convert each element
804                            format!("self.{name}.clone().map(|v| v.into_iter().map(Into::into).collect())")
805                        } else {
806                            format!("self.{name}.clone().into_iter().map(Into::into).collect()")
807                        }
808                    }
809                    _ => format!("self.{name}.clone()"),
810                },
811                TypeRef::Optional(inner) => {
812                    // When field.optional is also true, the binding field was flattened from
813                    // Option<Option<T>> to Option<T>. Core expects Option<Option<T>>, so wrap
814                    // with .map(Some) to reconstruct the double-optional.
815                    let base = match inner.as_ref() {
816                        TypeRef::Named(_) => {
817                            format!("self.{name}.clone().map(Into::into)")
818                        }
819                        TypeRef::Duration => {
820                            format!("self.{name}.map(|v| std::time::Duration::from_millis(v as u64))")
821                        }
822                        TypeRef::Vec(vi) if matches!(vi.as_ref(), TypeRef::Named(_)) => {
823                            format!("self.{name}.clone().map(|v| v.into_iter().map(Into::into).collect())")
824                        }
825                        _ => format!("self.{name}.clone()"),
826                    };
827                    if field.optional {
828                        format!("({base}).map(Some)")
829                    } else {
830                        base
831                    }
832                }
833                TypeRef::Map(_, v) => match v.as_ref() {
834                    TypeRef::Json => {
835                        // HashMap<String, String> (binding) → HashMap<String, Value> (core)
836                        if field.optional {
837                            format!(
838                                "self.{name}.clone().map(|m| m.into_iter().map(|(k, v)| \
839                                 (k, serde_json::from_str(&v).unwrap_or(serde_json::Value::String(v)))).collect())"
840                            )
841                        } else {
842                            format!(
843                                "self.{name}.clone().into_iter().map(|(k, v)| \
844                                 (k, serde_json::from_str(&v).unwrap_or(serde_json::Value::String(v)))).collect()"
845                            )
846                        }
847                    }
848                    // Collect to handle HashMap↔BTreeMap conversion
849                    _ => {
850                        if field.optional {
851                            format!("self.{name}.clone().map(|m| m.into_iter().collect())")
852                        } else {
853                            format!("self.{name}.clone().into_iter().collect()")
854                        }
855                    }
856                },
857                TypeRef::Unit => format!("self.{name}.clone()"),
858                TypeRef::Json => {
859                    // String (binding) → serde_json::Value (core)
860                    if field.optional {
861                        format!("self.{name}.as_ref().and_then(|s| serde_json::from_str(s).ok())")
862                    } else {
863                        format!("serde_json::from_str(&self.{name}).unwrap_or_default()")
864                    }
865                }
866            };
867            // Newtype wrapping: when the field was resolved from a newtype (e.g. NodeIndex → u32),
868            // re-wrap the binding value into the newtype for the core struct literal.
869            // When `optional=true` and `ty` is a plain Primitive (not TypeRef::Optional), the core
870            // field is actually `Option<NewtypeT>`, so we must use `.map(NewtypeT)` not `NewtypeT(...)`.
871            let expr = if let Some(newtype_path) = &field.newtype_wrapper {
872                match &field.ty {
873                    TypeRef::Optional(_) => format!("({expr}).map({newtype_path})"),
874                    TypeRef::Vec(_) => format!("({expr}).into_iter().map({newtype_path}).collect()"),
875                    _ if field.optional => format!("({expr}).map({newtype_path})"),
876                    _ => format!("{newtype_path}({expr})"),
877                }
878            } else {
879                expr
880            };
881            writeln!(out, "            {name}: {expr},").ok();
882        }
883    }
884    // Use ..Default::default() to fill cfg-gated fields stripped from the IR
885    if typ.has_stripped_cfg_fields {
886        out.push_str("            ..Default::default()\n");
887    }
888    out.push_str("        };\n        ");
889    out
890}
891
892/// Generate the body for an async call, unified across methods, static methods, and free functions.
893///
894/// - `core_call`: the expression to await, e.g. `inner.method(args)` or `CoreType::fn(args)`.
895///   For Pyo3FutureIntoPy opaque methods this should reference `inner` (the Arc clone);
896///   for all other patterns it may reference `self.inner` or a static call expression.
897/// - `cfg`: binding configuration (determines which async pattern to emit)
898/// - `has_error`: whether the core call returns a `Result`
899/// - `return_wrap`: expression to produce the binding return value from `result`,
900///   e.g. `"result"` or `"TypeName::from(result)"`
901///
902/// - `is_opaque`: whether the binding type is Arc-wrapped (affects TokioBlockOn wrapping)
903/// - `inner_clone_line`: optional statement emitted before the pattern-specific body,
904///   e.g. `"let inner = self.inner.clone();\n        "` for opaque instance methods, or `""`.
905///   Required when `core_call` references `inner` (Pyo3FutureIntoPy opaque case).
906#[allow(clippy::too_many_arguments)]
907pub fn gen_async_body(
908    core_call: &str,
909    cfg: &RustBindingConfig,
910    has_error: bool,
911    return_wrap: &str,
912    is_opaque: bool,
913    inner_clone_line: &str,
914    is_unit_return: bool,
915    return_type: Option<&str>,
916) -> String {
917    let pattern_body = match cfg.async_pattern {
918        AsyncPattern::Pyo3FutureIntoPy => {
919            let result_handling = if has_error {
920                format!(
921                    "let result = {core_call}.await\n            \
922                     .map_err(|e| PyErr::new::<PyRuntimeError, _>(e.to_string()))?;"
923                )
924            } else if is_unit_return {
925                format!("{core_call}.await;")
926            } else {
927                format!("let result = {core_call}.await;")
928            };
929            let (ok_expr, extra_binding) = if is_unit_return && !has_error {
930                ("()".to_string(), String::new())
931            } else if return_wrap.contains(".into()") || return_wrap.contains("::from(") {
932                // When return_wrap contains type conversions like .into() or ::from(),
933                // bind to a variable to help type inference for the generic future_into_py.
934                // This avoids E0283 "type annotations needed".
935                let wrapped_var = "wrapped_result";
936                let binding = if let Some(ret_type) = return_type {
937                    // Add explicit type annotation to help type inference
938                    format!("let {wrapped_var}: {ret_type} = {return_wrap};\n            ")
939                } else {
940                    format!("let {wrapped_var} = {return_wrap};\n            ")
941                };
942                (wrapped_var.to_string(), binding)
943            } else {
944                (return_wrap.to_string(), String::new())
945            };
946            format!(
947                "pyo3_async_runtimes::tokio::future_into_py(py, async move {{\n            \
948                 {result_handling}\n            \
949                 {extra_binding}Ok({ok_expr})\n        }})"
950            )
951        }
952        AsyncPattern::WasmNativeAsync => {
953            let result_handling = if has_error {
954                format!(
955                    "let result = {core_call}.await\n        \
956                     .map_err(|e| JsValue::from_str(&e.to_string()))?;"
957                )
958            } else if is_unit_return {
959                format!("{core_call}.await;")
960            } else {
961                format!("let result = {core_call}.await;")
962            };
963            let ok_expr = if is_unit_return && !has_error {
964                "()"
965            } else {
966                return_wrap
967            };
968            format!(
969                "{result_handling}\n        \
970                 Ok({ok_expr})"
971            )
972        }
973        AsyncPattern::NapiNativeAsync => {
974            let result_handling = if has_error {
975                format!(
976                    "let result = {core_call}.await\n            \
977                     .map_err(|e| napi::Error::new(napi::Status::GenericFailure, e.to_string()))?;"
978                )
979            } else if is_unit_return {
980                format!("{core_call}.await;")
981            } else {
982                format!("let result = {core_call}.await;")
983            };
984            if !has_error && !is_unit_return {
985                // No error type: return value directly without Ok() wrapper
986                format!(
987                    "{result_handling}\n            \
988                     {return_wrap}"
989                )
990            } else {
991                let ok_expr = if is_unit_return && !has_error {
992                    "()"
993                } else {
994                    return_wrap
995                };
996                format!(
997                    "{result_handling}\n            \
998                     Ok({ok_expr})"
999                )
1000            }
1001        }
1002        AsyncPattern::TokioBlockOn => {
1003            if has_error {
1004                if is_opaque {
1005                    format!(
1006                        "let rt = tokio::runtime::Runtime::new()?;\n        \
1007                         let result = rt.block_on(async {{ {core_call}.await.map_err(|e| e.into()) }})?;\n        \
1008                         {return_wrap}"
1009                    )
1010                } else {
1011                    format!(
1012                        "let rt = tokio::runtime::Runtime::new()?;\n        \
1013                         rt.block_on(async {{ {core_call}.await.map_err(|e| e.into()) }})"
1014                    )
1015                }
1016            } else if is_opaque {
1017                if is_unit_return {
1018                    format!(
1019                        "let rt = tokio::runtime::Runtime::new()?;\n        \
1020                         rt.block_on(async {{ {core_call}.await }});"
1021                    )
1022                } else {
1023                    format!(
1024                        "let rt = tokio::runtime::Runtime::new()?;\n        \
1025                         let result = rt.block_on(async {{ {core_call}.await }});\n        \
1026                         {return_wrap}"
1027                    )
1028                }
1029            } else {
1030                format!(
1031                    "let rt = tokio::runtime::Runtime::new()?;\n        \
1032                     rt.block_on(async {{ {core_call}.await }})"
1033                )
1034            }
1035        }
1036        AsyncPattern::None => "todo!(\"async not supported by backend\")".to_string(),
1037    };
1038    if inner_clone_line.is_empty() {
1039        pattern_body
1040    } else {
1041        format!("{inner_clone_line}{pattern_body}")
1042    }
1043}
1044
1045/// Generate a compilable body for functions that can't be auto-delegated.
1046/// Returns a default value or error instead of `todo!()` which would panic.
1047pub fn gen_unimplemented_body(
1048    return_type: &TypeRef,
1049    fn_name: &str,
1050    has_error: bool,
1051    cfg: &RustBindingConfig,
1052    params: &[ParamDef],
1053) -> String {
1054    // Suppress unused_variables by binding all params to `_`
1055    let suppress = if params.is_empty() {
1056        String::new()
1057    } else {
1058        let names: Vec<&str> = params.iter().map(|p| p.name.as_str()).collect();
1059        if names.len() == 1 {
1060            format!("let _ = {};\n        ", names[0])
1061        } else {
1062            format!("let _ = ({});\n        ", names.join(", "))
1063        }
1064    };
1065    let err_msg = format!("Not implemented: {fn_name}");
1066    let body = if has_error {
1067        // Backend-specific error return
1068        match cfg.async_pattern {
1069            AsyncPattern::Pyo3FutureIntoPy => {
1070                format!("Err(pyo3::exceptions::PyNotImplementedError::new_err(\"{err_msg}\"))")
1071            }
1072            AsyncPattern::NapiNativeAsync => {
1073                format!("Err(napi::Error::new(napi::Status::GenericFailure, \"{err_msg}\"))")
1074            }
1075            AsyncPattern::WasmNativeAsync => {
1076                format!("Err(JsValue::from_str(\"{err_msg}\"))")
1077            }
1078            _ => format!("Err(\"{err_msg}\".to_string())"),
1079        }
1080    } else {
1081        // Return type-appropriate default
1082        match return_type {
1083            TypeRef::Unit => "()".to_string(),
1084            TypeRef::String | TypeRef::Char | TypeRef::Path => format!("String::from(\"[unimplemented: {fn_name}]\")"),
1085            TypeRef::Bytes => "Vec::new()".to_string(),
1086            TypeRef::Primitive(p) => match p {
1087                alef_core::ir::PrimitiveType::Bool => "false".to_string(),
1088                alef_core::ir::PrimitiveType::F32 => "0.0f32".to_string(),
1089                alef_core::ir::PrimitiveType::F64 => "0.0f64".to_string(),
1090                _ => "0".to_string(),
1091            },
1092            TypeRef::Optional(_) => "None".to_string(),
1093            TypeRef::Vec(_) => "Vec::new()".to_string(),
1094            TypeRef::Map(_, _) => "Default::default()".to_string(),
1095            TypeRef::Duration => "0".to_string(),
1096            TypeRef::Named(_) | TypeRef::Json => {
1097                // Named/Json return without error type: return Default::default()
1098                // This works for builder methods (return Self) and getter methods returning complex types
1099                "Default::default()".to_string()
1100            }
1101        }
1102    };
1103    format!("{suppress}{body}")
1104}