Skip to main content

alef_codegen/conversions/
core_to_binding.rs

1use ahash::AHashSet;
2use alef_core::ir::{CoreWrapper, PrimitiveType, TypeDef, TypeRef};
3use std::fmt::Write;
4
5use super::ConversionConfig;
6use super::binding_to_core::field_conversion_to_core;
7use super::helpers::is_newtype;
8use super::helpers::{binding_prim_str, core_type_path, needs_i64_cast};
9
10/// Generate `impl From<core::Type> for BindingType` (core -> binding).
11pub fn gen_from_core_to_binding(typ: &TypeDef, core_import: &str, opaque_types: &AHashSet<String>) -> String {
12    gen_from_core_to_binding_cfg(typ, core_import, opaque_types, &ConversionConfig::default())
13}
14
15/// Generate `impl From<core::Type> for BindingType` with backend-specific config.
16pub fn gen_from_core_to_binding_cfg(
17    typ: &TypeDef,
18    core_import: &str,
19    opaque_types: &AHashSet<String>,
20    config: &ConversionConfig,
21) -> String {
22    let core_path = core_type_path(typ, core_import);
23    let binding_name = format!("{}{}", config.type_name_prefix, typ.name);
24    let mut out = String::with_capacity(256);
25    writeln!(out, "impl From<{core_path}> for {binding_name} {{").ok();
26    writeln!(out, "    fn from(val: {core_path}) -> Self {{").ok();
27
28    // Newtype structs: extract inner value with val.0
29    if is_newtype(typ) {
30        let field = &typ.fields[0];
31        let inner_expr = match &field.ty {
32            TypeRef::Named(_) => "val.0.into()".to_string(),
33            TypeRef::Path => "val.0.to_string_lossy().to_string()".to_string(),
34            TypeRef::Duration => "val.0.as_millis() as u64".to_string(),
35            _ => "val.0".to_string(),
36        };
37        writeln!(out, "        Self {{ _0: {inner_expr} }}").ok();
38        writeln!(out, "    }}").ok();
39        write!(out, "}}").ok();
40        return out;
41    }
42
43    let optionalized = config.optionalize_defaults && typ.has_default;
44    writeln!(out, "        Self {{").ok();
45    for field in &typ.fields {
46        // Fields referencing excluded types are not present in the binding struct — skip
47        if !config.exclude_types.is_empty()
48            && super::helpers::field_references_excluded_type(&field.ty, config.exclude_types)
49        {
50            continue;
51        }
52        let base_conversion = field_conversion_from_core_cfg(
53            &field.name,
54            &field.ty,
55            field.optional,
56            field.sanitized,
57            opaque_types,
58            config,
59        );
60        // Box<T> fields: dereference before conversion.
61        let base_conversion = if field.is_boxed && matches!(&field.ty, TypeRef::Named(_)) {
62            if field.optional {
63                // Optional<Box<T>>: replace .map(Into::into) with .map(|v| (*v).into())
64                let src = format!("{}: val.{}.map(Into::into)", field.name, field.name);
65                let dst = format!("{}: val.{}.map(|v| (*v).into())", field.name, field.name);
66                if base_conversion == src { dst } else { base_conversion }
67            } else {
68                // Box<T>: replace `val.{name}` with `(*val.{name})`
69                base_conversion.replace(&format!("val.{}", field.name), &format!("(*val.{})", field.name))
70            }
71        } else {
72            base_conversion
73        };
74        // Newtype unwrapping: when the field was resolved from a newtype (e.g. NodeIndex → u32),
75        // unwrap the core newtype by accessing `.0`.
76        // e.g. `source: val.source` → `source: val.source.0`
77        //      `parent: val.parent` → `parent: val.parent.map(|v| v.0)`
78        //      `children: val.children` → `children: val.children.iter().map(|v| v.0).collect()`
79        let base_conversion = if field.newtype_wrapper.is_some() {
80            match &field.ty {
81                TypeRef::Optional(_) => {
82                    // Replace `val.{name}` with `val.{name}.map(|v| v.0)` in the generated expression
83                    base_conversion.replace(
84                        &format!("val.{}", field.name),
85                        &format!("val.{}.map(|v| v.0)", field.name),
86                    )
87                }
88                TypeRef::Vec(_) => {
89                    // Replace `val.{name}` with `val.{name}.iter().map(|v| v.0).collect()` in expression
90                    base_conversion.replace(
91                        &format!("val.{}", field.name),
92                        &format!("val.{}.iter().map(|v| v.0).collect::<Vec<_>>()", field.name),
93                    )
94                }
95                // When `optional=true` and `ty` is a plain Primitive (not TypeRef::Optional), the core
96                // field is actually `Option<NewtypeT>`, so we must use `.map(|v| v.0)` not `.0`.
97                _ if field.optional => base_conversion.replace(
98                    &format!("val.{}", field.name),
99                    &format!("val.{}.map(|v| v.0)", field.name),
100                ),
101                _ => {
102                    // Direct field: append `.0` to access the inner primitive
103                    base_conversion.replace(&format!("val.{}", field.name), &format!("val.{}.0", field.name))
104                }
105            }
106        } else {
107            base_conversion
108        };
109        // When field.optional=true AND field.ty=Optional(T), the binding struct flattens
110        // Option<Option<T>> to Option<T>. Core produces Option<Option<T>>, binding needs
111        // Option<T>. Generate the conversion by treating the pre-flattened field as Option<T>:
112        // call the standard conversion for the inner type T with optional=true, substituting
113        // val.{name}.flatten() for val.{name} so all cast/conversion logic applies to T.
114        let is_flattened_optional = field.optional && matches!(field.ty, TypeRef::Optional(_));
115        let base_conversion = if is_flattened_optional {
116            if let TypeRef::Optional(inner) = &field.ty {
117                // Produce the conversion as if the field is Option<inner> with value val.name.flatten()
118                let inner_conv = field_conversion_from_core_cfg(
119                    &field.name,
120                    inner.as_ref(),
121                    true,
122                    field.sanitized,
123                    opaque_types,
124                    config,
125                );
126                // inner_conv references val.{name}; replace with val.{name}.flatten()
127                inner_conv.replace(&format!("val.{}", field.name), &format!("val.{}.flatten()", field.name))
128            } else {
129                base_conversion
130            }
131        } else {
132            base_conversion
133        };
134        // Optionalized non-optional fields need Some() wrapping in core→binding direction.
135        // This covers both NAPI-style full optionalization and PyO3-style Duration optionalization.
136        // Flattened-optional fields are already handled above with the correct type.
137        let needs_some_wrap = !is_flattened_optional
138            && ((optionalized && !field.optional)
139                || (config.option_duration_on_defaults
140                    && typ.has_default
141                    && !field.optional
142                    && matches!(field.ty, TypeRef::Duration)));
143        let conversion = if needs_some_wrap {
144            // Extract the value expression after "name: " and wrap in Some()
145            if let Some(expr) = base_conversion.strip_prefix(&format!("{}: ", field.name)) {
146                format!("{}: Some({})", field.name, expr)
147            } else {
148                base_conversion
149            }
150        } else {
151            base_conversion
152        };
153        // CoreWrapper: unwrap Arc, convert Cow→String, Bytes→Vec<u8>
154        // Skip for sanitized fields since their conversion already handles the type mismatch via format!("{:?}", ...)
155        let conversion = if !field.sanitized {
156            apply_core_wrapper_from_core(
157                &conversion,
158                &field.name,
159                &field.core_wrapper,
160                &field.vec_inner_core_wrapper,
161                field.optional,
162            )
163        } else {
164            conversion
165        };
166        // Skip cfg-gated fields — they don't exist in the binding struct
167        if field.cfg.is_some() {
168            continue;
169        }
170        // In core→binding direction, the binding struct field may be keyword-escaped
171        // (e.g. `class_` for `class`). The generated conversion has `field.name: expr`
172        // on the left side — rename it to `binding_name: expr` when needed.
173        let binding_field = config.binding_field_name_owned(&typ.name, &field.name);
174        let conversion = if binding_field != field.name {
175            if let Some(expr) = conversion.strip_prefix(&format!("{}: ", field.name)) {
176                format!("{binding_field}: {expr}")
177            } else {
178                conversion
179            }
180        } else {
181            conversion
182        };
183        writeln!(out, "            {conversion},").ok();
184    }
185
186    writeln!(out, "        }}").ok();
187    writeln!(out, "    }}").ok();
188    write!(out, "}}").ok();
189    out
190}
191
192/// Same but for core -> binding direction.
193/// Some types are asymmetric (PathBuf→String, sanitized fields need .to_string()).
194pub fn field_conversion_from_core(
195    name: &str,
196    ty: &TypeRef,
197    optional: bool,
198    sanitized: bool,
199    opaque_types: &AHashSet<String>,
200) -> String {
201    // Sanitized fields: the binding type differs from core (e.g. Box<str>→String, Cow<str>→String).
202    // Box<str>, Cow<str>, and Arc<str> all implement Display, so use .to_string() not {:?}.
203    // {:?} on string-like types produces debug-escaped output with surrounding quotes.
204    if sanitized {
205        // Vec<Primitive>: sanitized from tuple types like (u32, u32) → Vec<u32>.
206        // Core has a tuple, binding expects Vec — destructure the tuple.
207        if let TypeRef::Vec(inner) = ty {
208            if matches!(inner.as_ref(), TypeRef::Primitive(_)) {
209                if optional {
210                    return format!(
211                        "{name}: val.{name}.map(|t| {{ let arr: Vec<_> = [t.0, t.1].into_iter().map(|v| v as _).collect(); arr }})"
212                    );
213                }
214                return format!("{name}: vec![val.{name}.0 as _, val.{name}.1 as _]");
215            }
216        }
217        // Optional(Vec<Primitive>): sanitized from Option<(T, T)> → Option<Vec<T>>.
218        if let TypeRef::Optional(opt_inner) = ty {
219            if let TypeRef::Vec(vec_inner) = opt_inner.as_ref() {
220                if matches!(vec_inner.as_ref(), TypeRef::Primitive(_)) {
221                    return format!("{name}: val.{name}.map(|t| vec![t.0 as _, t.1 as _])");
222                }
223            }
224        }
225        // Map(String, String): sanitized from Map(Box<str>, Box<str>) etc.
226        if let TypeRef::Map(k, v) = ty {
227            if matches!(k.as_ref(), TypeRef::String) && matches!(v.as_ref(), TypeRef::String) {
228                if optional {
229                    return format!(
230                        "{name}: val.{name}.as_ref().map(|m| m.iter().map(|(k, v)| (k.to_string(), v.to_string())).collect())"
231                    );
232                }
233                return format!(
234                    "{name}: val.{name}.into_iter().map(|(k, v)| (k.to_string(), v.to_string())).collect()"
235                );
236            }
237        }
238        // Vec<String>: sanitized from Vec<Box<str>>, Vec<Cow<str>>, Vec<Named>, etc.
239        // Use Debug formatting — the original core type may not implement Display.
240        if let TypeRef::Vec(inner) = ty {
241            if matches!(inner.as_ref(), TypeRef::String) {
242                if optional {
243                    return format!(
244                        "{name}: val.{name}.as_ref().map(|v| v.iter().map(|i| format!(\"{{:?}}\", i)).collect())"
245                    );
246                }
247                return format!("{name}: val.{name}.iter().map(|i| format!(\"{{:?}}\", i)).collect()");
248            }
249        }
250        // Optional<Vec<String>>: sanitized from Optional<Vec<Box<str>>>, Optional<Vec<Cow<str>>>, etc.
251        if let TypeRef::Optional(opt_inner) = ty {
252            if let TypeRef::Vec(vec_inner) = opt_inner.as_ref() {
253                if matches!(vec_inner.as_ref(), TypeRef::String) {
254                    return format!(
255                        "{name}: val.{name}.as_ref().map(|v| v.iter().map(|i| format!(\"{{:?}}\", i)).collect())"
256                    );
257                }
258            }
259        }
260        // String: sanitized from Box<str>, Cow<str>, (u32, u32), etc.
261        // Use Debug formatting — it works for all types (including tuples) and avoids Display
262        // trait bound failures when the original core type doesn't implement Display.
263        if matches!(ty, TypeRef::String) {
264            if optional {
265                return format!("{name}: val.{name}.as_ref().map(|v| format!(\"{{v:?}}\"))");
266            }
267            return format!("{name}: format!(\"{{:?}}\", val.{name})");
268        }
269        // Fallback for truly unknown sanitized types — the core type may not implement Display,
270        // so use Debug formatting which is always available (required by the sanitized field's derive).
271        if optional {
272            return format!("{name}: val.{name}.as_ref().map(|v| format!(\"{{v:?}}\"))");
273        }
274        return format!("{name}: format!(\"{{:?}}\", val.{name})");
275    }
276    match ty {
277        // Duration: core uses std::time::Duration, binding uses u64 (millis)
278        TypeRef::Duration => {
279            if optional {
280                return format!("{name}: val.{name}.map(|d| d.as_millis() as u64)");
281            }
282            format!("{name}: val.{name}.as_millis() as u64")
283        }
284        // Path: core uses PathBuf, binding uses String — PathBuf→String needs special handling
285        TypeRef::Path => {
286            if optional {
287                format!("{name}: val.{name}.map(|p| p.to_string_lossy().to_string())")
288            } else {
289                format!("{name}: val.{name}.to_string_lossy().to_string()")
290            }
291        }
292        TypeRef::Optional(inner) if matches!(inner.as_ref(), TypeRef::Path) => {
293            format!("{name}: val.{name}.map(|p| p.to_string_lossy().to_string())")
294        }
295        // Char: core uses char, binding uses String — convert char to string
296        TypeRef::Char => {
297            if optional {
298                format!("{name}: val.{name}.map(|c| c.to_string())")
299            } else {
300                format!("{name}: val.{name}.to_string()")
301            }
302        }
303        // Bytes: core uses bytes::Bytes, binding uses Vec<u8>
304        TypeRef::Bytes => {
305            if optional {
306                format!("{name}: val.{name}.map(|v| v.to_vec())")
307            } else {
308                format!("{name}: val.{name}.to_vec()")
309            }
310        }
311        // Opaque Named types: wrap in Arc to create the binding wrapper
312        TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
313            if optional {
314                format!("{name}: val.{name}.map(|v| {n} {{ inner: Arc::new(v) }})")
315            } else {
316                format!("{name}: {n} {{ inner: Arc::new(val.{name}) }}")
317            }
318        }
319        // Json: core uses serde_json::Value, binding uses String — use .to_string()
320        TypeRef::Json => {
321            if optional {
322                format!("{name}: val.{name}.as_ref().map(ToString::to_string)")
323            } else {
324                format!("{name}: val.{name}.to_string()")
325            }
326        }
327        TypeRef::Optional(inner) if matches!(inner.as_ref(), TypeRef::Json) => {
328            format!("{name}: val.{name}.as_ref().map(ToString::to_string)")
329        }
330        TypeRef::Vec(inner) if matches!(inner.as_ref(), TypeRef::Json) => {
331            if optional {
332                format!("{name}: val.{name}.as_ref().map(|v| v.iter().map(|i| i.to_string()).collect())")
333            } else {
334                format!("{name}: val.{name}.iter().map(ToString::to_string).collect()")
335            }
336        }
337        // Vec<Optional<Json>>: each element is Option<Value> → Option<String>
338        TypeRef::Vec(inner) if matches!(inner.as_ref(), TypeRef::Optional(oi) if matches!(oi.as_ref(), TypeRef::Json)) => {
339            if optional {
340                format!(
341                    "{name}: val.{name}.as_ref().map(|v| v.iter().map(|i| i.as_ref().map(ToString::to_string)).collect())"
342                )
343            } else {
344                format!("{name}: val.{name}.iter().map(|i| i.as_ref().map(ToString::to_string)).collect()")
345            }
346        }
347        // Map with Json values: core uses HashMap<K, serde_json::Value>, binding uses HashMap<K, String>
348        TypeRef::Map(k, v) if matches!(v.as_ref(), TypeRef::Json) => {
349            let k_is_json = matches!(k.as_ref(), TypeRef::Json);
350            let k_expr = if k_is_json { "k.to_string()" } else { "k" };
351            if optional {
352                format!("{name}: val.{name}.map(|m| m.into_iter().map(|(k, v)| ({k_expr}, v.to_string())).collect())")
353            } else {
354                format!("{name}: val.{name}.into_iter().map(|(k, v)| ({k_expr}, v.to_string())).collect()")
355            }
356        }
357        // Map with Json keys: core uses HashMap<serde_json::Value, V>, binding uses HashMap<String, V>
358        TypeRef::Map(k, _v) if matches!(k.as_ref(), TypeRef::Json) => {
359            if optional {
360                format!("{name}: val.{name}.map(|m| m.into_iter().map(|(k, v)| (k.to_string(), v)).collect())")
361            } else {
362                format!("{name}: val.{name}.into_iter().map(|(k, v)| (k.to_string(), v)).collect()")
363            }
364        }
365        // Everything else is symmetric
366        _ => field_conversion_to_core(name, ty, optional),
367    }
368}
369
370/// Core→binding field conversion with backend-specific config.
371pub fn field_conversion_from_core_cfg(
372    name: &str,
373    ty: &TypeRef,
374    optional: bool,
375    sanitized: bool,
376    opaque_types: &AHashSet<String>,
377    config: &ConversionConfig,
378) -> String {
379    // Sanitized fields: for WASM (map_uses_jsvalue), Map and Vec<Json> fields target JsValue
380    // and need serde_wasm_bindgen::to_value() instead of iterator-based .collect().
381    // Note: Vec<String> sanitized does NOT use the JsValue path because Vec<String> maps to
382    // Vec<String> in WASM (not JsValue) — use the normal sanitized iterator path instead.
383    if sanitized {
384        if config.map_uses_jsvalue {
385            // Map(String, String) sanitized → JsValue (HashMap maps to JsValue in WASM)
386            if let TypeRef::Map(k, v) = ty {
387                if matches!(k.as_ref(), TypeRef::String) && matches!(v.as_ref(), TypeRef::String) {
388                    if optional {
389                        return format!(
390                            "{name}: val.{name}.as_ref().and_then(|v| serde_wasm_bindgen::to_value(v).ok())"
391                        );
392                    }
393                    return format!("{name}: serde_wasm_bindgen::to_value(&val.{name}).unwrap_or(JsValue::NULL)");
394                }
395            }
396            // Vec<Json> sanitized → JsValue (Vec<Json> maps to JsValue in WASM via nested-vec path)
397            if let TypeRef::Vec(inner) = ty {
398                if matches!(inner.as_ref(), TypeRef::Json) {
399                    if optional {
400                        return format!(
401                            "{name}: val.{name}.as_ref().and_then(|v| serde_wasm_bindgen::to_value(v).ok())"
402                        );
403                    }
404                    return format!("{name}: serde_wasm_bindgen::to_value(&val.{name}).unwrap_or(JsValue::NULL)");
405                }
406            }
407        }
408        return field_conversion_from_core(name, ty, optional, sanitized, opaque_types);
409    }
410
411    // Vec<Named>→String core→binding: binding holds JSON string, core has Vec<Named>.
412    // Only apply serde round-trip for Vec<Named> types (complex structs that can't cross FFI).
413    // Vec<String>, Vec<Primitive>, etc. stay as-is since they map directly.
414    if config.vec_named_to_string {
415        if let TypeRef::Vec(inner) = ty {
416            if matches!(inner.as_ref(), TypeRef::Named(_)) {
417                if optional {
418                    return format!("{name}: val.{name}.as_ref().and_then(|v| serde_json::to_string(v).ok())");
419                }
420                return format!("{name}: serde_json::to_string(&val.{name}).unwrap_or_default()");
421            }
422        }
423    }
424
425    // WASM JsValue: use serde_wasm_bindgen for Map and nested Vec types
426    if config.map_uses_jsvalue {
427        let is_nested_vec = matches!(ty, TypeRef::Vec(inner) if matches!(inner.as_ref(), TypeRef::Vec(_)));
428        let is_map = matches!(ty, TypeRef::Map(_, _));
429        if is_nested_vec || is_map {
430            if optional {
431                return format!("{name}: val.{name}.as_ref().and_then(|v| serde_wasm_bindgen::to_value(v).ok())");
432            }
433            return format!("{name}: serde_wasm_bindgen::to_value(&val.{name}).unwrap_or(JsValue::NULL)");
434        }
435        if let TypeRef::Optional(inner) = ty {
436            let is_inner_nested = matches!(inner.as_ref(), TypeRef::Vec(vi) if matches!(vi.as_ref(), TypeRef::Vec(_)));
437            let is_inner_map = matches!(inner.as_ref(), TypeRef::Map(_, _));
438            if is_inner_nested || is_inner_map {
439                return format!("{name}: val.{name}.as_ref().and_then(|v| serde_wasm_bindgen::to_value(v).ok())");
440            }
441        }
442    }
443
444    let prefix = config.type_name_prefix;
445    let is_enum_string = |n: &str| -> bool { config.enum_string_names.as_ref().is_some_and(|names| names.contains(n)) };
446
447    match ty {
448        // i64 casting for large int primitives
449        TypeRef::Primitive(p) if config.cast_large_ints_to_i64 && needs_i64_cast(p) => {
450            let cast_to = binding_prim_str(p);
451            if optional {
452                format!("{name}: val.{name}.map(|v| v as {cast_to})")
453            } else {
454                format!("{name}: val.{name} as {cast_to}")
455            }
456        }
457        // Optional(large_int) with i64 casting
458        TypeRef::Optional(inner)
459            if config.cast_large_ints_to_i64
460                && matches!(inner.as_ref(), TypeRef::Primitive(p) if needs_i64_cast(p)) =>
461        {
462            if let TypeRef::Primitive(p) = inner.as_ref() {
463                let cast_to = binding_prim_str(p);
464                format!("{name}: val.{name}.map(|v| v as {cast_to})")
465            } else {
466                field_conversion_from_core(name, ty, optional, sanitized, opaque_types)
467            }
468        }
469        // f32→f64 casting (NAPI only)
470        TypeRef::Primitive(PrimitiveType::F32) if config.cast_f32_to_f64 => {
471            if optional {
472                format!("{name}: val.{name}.map(|v| v as f64)")
473            } else {
474                format!("{name}: val.{name} as f64")
475            }
476        }
477        // Duration with i64 casting
478        TypeRef::Duration if config.cast_large_ints_to_i64 => {
479            if optional {
480                format!("{name}: val.{name}.map(|d| d.as_millis() as u64 as i64)")
481            } else {
482                format!("{name}: val.{name}.as_millis() as u64 as i64")
483            }
484        }
485        // Opaque Named types with prefix: wrap in Arc with prefixed binding name
486        TypeRef::Named(n) if opaque_types.contains(n.as_str()) && !prefix.is_empty() => {
487            let prefixed = format!("{prefix}{n}");
488            if optional {
489                format!("{name}: val.{name}.map(|v| {prefixed} {{ inner: Arc::new(v) }})")
490            } else {
491                format!("{name}: {prefixed} {{ inner: Arc::new(val.{name}) }}")
492            }
493        }
494        // Enum-to-String Named types (PHP pattern)
495        TypeRef::Named(n) if is_enum_string(n) => {
496            // Use serde serialization to get the correct serde(rename) value, not Debug format.
497            // serde_json::to_value gives Value::String("auto") which we extract.
498            if optional {
499                format!(
500                    "{name}: val.{name}.as_ref().map(|v| serde_json::to_value(v).ok().and_then(|s| s.as_str().map(String::from)).unwrap_or_default())"
501                )
502            } else {
503                format!(
504                    "{name}: serde_json::to_value(val.{name}).ok().and_then(|s| s.as_str().map(String::from)).unwrap_or_default()"
505                )
506            }
507        }
508        // Vec<Enum-to-String> Named types: element-wise serde serialization
509        TypeRef::Vec(inner) if matches!(inner.as_ref(), TypeRef::Named(n) if is_enum_string(n)) => {
510            if optional {
511                format!(
512                    "{name}: val.{name}.as_ref().map(|v| v.iter().map(|x| serde_json::to_value(x).ok().and_then(|s| s.as_str().map(String::from)).unwrap_or_default()).collect())"
513                )
514            } else {
515                format!(
516                    "{name}: val.{name}.iter().map(|v| serde_json::to_value(v).ok().and_then(|s| s.as_str().map(String::from)).unwrap_or_default()).collect()"
517                )
518            }
519        }
520        // Optional(Vec<Enum-to-String>) Named types (PHP pattern)
521        TypeRef::Optional(inner) if matches!(inner.as_ref(), TypeRef::Vec(vi) if matches!(vi.as_ref(), TypeRef::Named(n) if is_enum_string(n))) =>
522        {
523            format!(
524                "{name}: val.{name}.as_ref().map(|v| v.iter().map(|x| serde_json::to_value(x).ok().and_then(|s| s.as_str().map(String::from)).unwrap_or_default()).collect())"
525            )
526        }
527        // Vec<f32> needs element-wise cast to f64 when f32→f64 mapping is active
528        TypeRef::Vec(inner)
529            if config.cast_f32_to_f64 && matches!(inner.as_ref(), TypeRef::Primitive(PrimitiveType::F32)) =>
530        {
531            if optional {
532                format!("{name}: val.{name}.as_ref().map(|v| v.iter().map(|&x| x as f64).collect())")
533            } else {
534                format!("{name}: val.{name}.iter().map(|&v| v as f64).collect()")
535            }
536        }
537        // Optional(Vec(f32)) needs element-wise cast to f64
538        TypeRef::Optional(inner)
539            if config.cast_f32_to_f64
540                && matches!(inner.as_ref(), TypeRef::Vec(vi) if matches!(vi.as_ref(), TypeRef::Primitive(PrimitiveType::F32))) =>
541        {
542            format!("{name}: val.{name}.as_ref().map(|v| v.iter().map(|&x| x as f64).collect())")
543        }
544        // Optional(Vec(u64/usize/isize)) needs element-wise i64 casting
545        TypeRef::Optional(inner)
546            if config.cast_large_ints_to_i64
547                && matches!(inner.as_ref(), TypeRef::Vec(vi) if matches!(vi.as_ref(), TypeRef::Primitive(p) if needs_i64_cast(p))) =>
548        {
549            if let TypeRef::Vec(vi) = inner.as_ref() {
550                if let TypeRef::Primitive(p) = vi.as_ref() {
551                    let cast_to = binding_prim_str(p);
552                    if sanitized {
553                        // Sanitized from Option<(T, T)> → Option<Vec<T>>: destructure tuple
554                        format!("{name}: val.{name}.map(|(a, b)| vec![a as {cast_to}, b as {cast_to}])")
555                    } else {
556                        format!("{name}: val.{name}.as_ref().map(|v| v.iter().map(|&x| x as {cast_to}).collect())")
557                    }
558                } else {
559                    field_conversion_from_core(name, ty, optional, sanitized, opaque_types)
560                }
561            } else {
562                field_conversion_from_core(name, ty, optional, sanitized, opaque_types)
563            }
564        }
565        // Vec<Vec<f32>> needs nested element-wise cast to f64 (for embeddings, etc.)
566        TypeRef::Vec(outer)
567            if config.cast_f32_to_f64
568                && matches!(outer.as_ref(), TypeRef::Vec(inner) if matches!(inner.as_ref(), TypeRef::Primitive(PrimitiveType::F32))) =>
569        {
570            if optional {
571                format!(
572                    "{name}: val.{name}.as_ref().map(|v| v.iter().map(|inner| inner.iter().map(|&x| x as f64).collect()).collect())"
573                )
574            } else {
575                format!("{name}: val.{name}.iter().map(|inner| inner.iter().map(|&x| x as f64).collect()).collect()")
576            }
577        }
578        // Optional(Vec<Vec<f32>>) needs nested element-wise cast to f64
579        TypeRef::Optional(inner)
580            if config.cast_f32_to_f64
581                && matches!(inner.as_ref(), TypeRef::Vec(outer) if matches!(outer.as_ref(), TypeRef::Vec(prim) if matches!(prim.as_ref(), TypeRef::Primitive(PrimitiveType::F32)))) =>
582        {
583            format!(
584                "{name}: val.{name}.as_ref().map(|v| v.iter().map(|inner| inner.iter().map(|&x| x as f64).collect()).collect())"
585            )
586        }
587        // Optional with i64-cast inner
588        TypeRef::Optional(inner)
589            if config.cast_large_ints_to_i64
590                && matches!(inner.as_ref(), TypeRef::Primitive(p) if needs_i64_cast(p)) =>
591        {
592            if let TypeRef::Primitive(p) = inner.as_ref() {
593                let cast_to = binding_prim_str(p);
594                format!("{name}: val.{name}.map(|v| v as {cast_to})")
595            } else {
596                field_conversion_from_core(name, ty, optional, sanitized, opaque_types)
597            }
598        }
599        // HashMap value type casting: when value type needs i64 casting
600        TypeRef::Map(_k, v)
601            if config.cast_large_ints_to_i64 && matches!(v.as_ref(), TypeRef::Primitive(p) if needs_i64_cast(p)) =>
602        {
603            if let TypeRef::Primitive(p) = v.as_ref() {
604                let cast_to = binding_prim_str(p);
605                if optional {
606                    format!(
607                        "{name}: val.{name}.as_ref().map(|m| m.iter().map(|(k, v)| (k.clone(), *v as {cast_to})).collect())"
608                    )
609                } else {
610                    format!("{name}: val.{name}.iter().map(|(k, v)| (k.clone(), *v as {cast_to})).collect()")
611                }
612            } else {
613                field_conversion_from_core(name, ty, optional, sanitized, opaque_types)
614            }
615        }
616        // Vec<u64/usize/isize> needs element-wise i64 casting (core→binding)
617        TypeRef::Vec(inner)
618            if config.cast_large_ints_to_i64
619                && matches!(inner.as_ref(), TypeRef::Primitive(p) if needs_i64_cast(p)) =>
620        {
621            if let TypeRef::Primitive(p) = inner.as_ref() {
622                let cast_to = binding_prim_str(p);
623                if sanitized {
624                    // Sanitized from tuple (T, T) → Vec<T>: destructure tuple into vec
625                    if optional {
626                        format!("{name}: val.{name}.map(|(a, b)| vec![a as {cast_to}, b as {cast_to}])")
627                    } else {
628                        format!("{name}: {{ let (a, b) = val.{name}; vec![a as {cast_to}, b as {cast_to}] }}")
629                    }
630                } else if optional {
631                    format!("{name}: val.{name}.as_ref().map(|v| v.iter().map(|&x| x as {cast_to}).collect())")
632                } else {
633                    format!("{name}: val.{name}.iter().map(|&v| v as {cast_to}).collect()")
634                }
635            } else {
636                field_conversion_from_core(name, ty, optional, sanitized, opaque_types)
637            }
638        }
639        // Vec<Vec<u64/usize/isize>> needs nested element-wise i64 casting (core→binding)
640        TypeRef::Vec(outer)
641            if config.cast_large_ints_to_i64
642                && matches!(outer.as_ref(), TypeRef::Vec(inner) if matches!(inner.as_ref(), TypeRef::Primitive(p) if needs_i64_cast(p))) =>
643        {
644            if let TypeRef::Vec(inner) = outer.as_ref() {
645                if let TypeRef::Primitive(p) = inner.as_ref() {
646                    let cast_to = binding_prim_str(p);
647                    if optional {
648                        format!(
649                            "{name}: val.{name}.as_ref().map(|v| v.iter().map(|inner| inner.iter().map(|&x| x as {cast_to}).collect()).collect())"
650                        )
651                    } else {
652                        format!(
653                            "{name}: val.{name}.iter().map(|inner| inner.iter().map(|&x| x as {cast_to}).collect()).collect()"
654                        )
655                    }
656                } else {
657                    field_conversion_from_core(name, ty, optional, sanitized, opaque_types)
658                }
659            } else {
660                field_conversion_from_core(name, ty, optional, sanitized, opaque_types)
661            }
662        }
663        // Json→String: core uses serde_json::Value, binding uses String (PHP)
664        TypeRef::Json if config.json_to_string => {
665            if optional {
666                format!("{name}: val.{name}.as_ref().map(ToString::to_string)")
667            } else {
668                format!("{name}: val.{name}.to_string()")
669            }
670        }
671        // Json→JsValue: core uses serde_json::Value, binding uses JsValue (WASM)
672        TypeRef::Json if config.map_uses_jsvalue => {
673            if optional {
674                format!("{name}: val.{name}.as_ref().and_then(|v| serde_wasm_bindgen::to_value(v).ok())")
675            } else {
676                format!("{name}: serde_wasm_bindgen::to_value(&val.{name}).unwrap_or(JsValue::NULL)")
677            }
678        }
679        // Vec<Json>→JsValue: core uses Vec<serde_json::Value>, binding uses JsValue (WASM)
680        TypeRef::Vec(inner) if config.map_uses_jsvalue && matches!(inner.as_ref(), TypeRef::Json) => {
681            if optional {
682                format!("{name}: val.{name}.as_ref().and_then(|v| serde_wasm_bindgen::to_value(v).ok())")
683            } else {
684                format!("{name}: serde_wasm_bindgen::to_value(&val.{name}).unwrap_or(JsValue::NULL)")
685            }
686        }
687        // Optional(Vec<Json>)→JsValue (WASM)
688        TypeRef::Optional(inner)
689            if config.map_uses_jsvalue
690                && matches!(inner.as_ref(), TypeRef::Vec(vi) if matches!(vi.as_ref(), TypeRef::Json)) =>
691        {
692            format!("{name}: val.{name}.as_ref().and_then(|v| serde_wasm_bindgen::to_value(v).ok())")
693        }
694        // Fall through to default (handles paths, opaque without prefix, etc.)
695        _ => field_conversion_from_core(name, ty, optional, sanitized, opaque_types),
696    }
697}
698
699/// Apply CoreWrapper transformations for core→binding direction.
700/// Unwraps Arc, converts Cow→String, Bytes→Vec<u8>.
701fn apply_core_wrapper_from_core(
702    conversion: &str,
703    name: &str,
704    core_wrapper: &CoreWrapper,
705    vec_inner_core_wrapper: &CoreWrapper,
706    optional: bool,
707) -> String {
708    // Handle Vec<Arc<T>>: unwrap Arc elements
709    if *vec_inner_core_wrapper == CoreWrapper::Arc {
710        return conversion
711            .replace(".map(Into::into).collect()", ".map(|v| (*v).clone().into()).collect()")
712            .replace(
713                "map(|v| v.into_iter().map(Into::into)",
714                "map(|v| v.into_iter().map(|v| (*v).clone().into())",
715            );
716    }
717
718    match core_wrapper {
719        CoreWrapper::None => conversion.to_string(),
720        CoreWrapper::Cow => {
721            // Cow<str> → String: core val.name is Cow, binding needs String
722            // The conversion already emits "name: val.name" for strings which works
723            // since Cow<str> derefs to &str and String: From<Cow<str>> exists.
724            // But if it's "val.name" directly, add .into_owned() or .to_string()
725            if let Some(expr) = conversion.strip_prefix(&format!("{name}: ")) {
726                if optional {
727                    // Already handled by map
728                    conversion.to_string()
729                } else if expr == format!("val.{name}") {
730                    format!("{name}: val.{name}.into_owned()")
731                } else {
732                    conversion.to_string()
733                }
734            } else {
735                conversion.to_string()
736            }
737        }
738        CoreWrapper::Arc => {
739            // Arc<T> → T: unwrap via clone.
740            //
741            // Special case: opaque Named types build the binding wrapper with
742            // `{ inner: Arc::new(v) }` in the base conversion, but when the core
743            // field is `Arc<T>`, `v` IS already the `Arc<T>` — wrapping it again
744            // with `Arc::new` produces `Arc<Arc<T>>`.  Detect this pattern and
745            // replace `Arc::new(v)` with `v`, and `Arc::new(val.{name})` with
746            // `val.{name}`, then return without adding an extra unwrap chain.
747            if conversion.contains("{ inner: Arc::new(") {
748                return conversion.replace("{ inner: Arc::new(v) }", "{ inner: v }").replace(
749                    &format!("{{ inner: Arc::new(val.{name}) }}"),
750                    &format!("{{ inner: val.{name} }}"),
751                );
752            }
753            if let Some(expr) = conversion.strip_prefix(&format!("{name}: ")) {
754                if optional {
755                    format!("{name}: {expr}.map(|v| (*v).clone().into())")
756                } else {
757                    let unwrapped = expr.replace(&format!("val.{name}"), &format!("(*val.{name}).clone()"));
758                    format!("{name}: {unwrapped}")
759                }
760            } else {
761                conversion.to_string()
762            }
763        }
764        CoreWrapper::Bytes => {
765            // Bytes → Vec<u8>: .to_vec()
766            if let Some(expr) = conversion.strip_prefix(&format!("{name}: ")) {
767                if optional {
768                    format!("{name}: {expr}.map(|v| v.to_vec())")
769                } else if expr == format!("val.{name}") {
770                    format!("{name}: val.{name}.to_vec()")
771                } else {
772                    conversion.to_string()
773                }
774            } else {
775                conversion.to_string()
776            }
777        }
778        CoreWrapper::ArcMutex => {
779            // Arc<Mutex<T>> → T: lock and clone
780            if let Some(expr) = conversion.strip_prefix(&format!("{name}: ")) {
781                if optional {
782                    format!("{name}: {expr}.map(|v| v.lock().unwrap().clone().into())")
783                } else if expr == format!("val.{name}") {
784                    format!("{name}: val.{name}.lock().unwrap().clone().into()")
785                } else {
786                    conversion.to_string()
787                }
788            } else {
789                conversion.to_string()
790            }
791        }
792    }
793}