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