Skip to main content

alef_codegen/conversions/
core_to_binding.rs

1use ahash::AHashSet;
2use alef_core::ir::{CoreWrapper, PrimitiveType, TypeDef, TypeRef};
3
4use super::ConversionConfig;
5use super::binding_to_core::field_conversion_to_core;
6use super::helpers::is_newtype;
7use super::helpers::{binding_prim_str, core_type_path_remapped, needs_f64_cast, needs_i32_cast, needs_i64_cast};
8
9/// Generate `impl From<core::Type> for BindingType` (core -> binding).
10pub fn gen_from_core_to_binding(typ: &TypeDef, core_import: &str, opaque_types: &AHashSet<String>) -> String {
11    gen_from_core_to_binding_cfg(typ, core_import, opaque_types, &ConversionConfig::default())
12}
13
14/// Generate `impl From<core::Type> for BindingType` with backend-specific config.
15pub fn gen_from_core_to_binding_cfg(
16    typ: &TypeDef,
17    core_import: &str,
18    opaque_types: &AHashSet<String>,
19    config: &ConversionConfig,
20) -> String {
21    let core_path = core_type_path_remapped(typ, core_import, config.source_crate_remaps);
22    let binding_name = format!("{}{}", config.type_name_prefix, typ.name);
23
24    // Newtype structs: extract inner value with val.0
25    if is_newtype(typ) {
26        let field = &typ.fields[0];
27        let newtype_inner_expr = match &field.ty {
28            TypeRef::Named(_) => "val.0.into()".to_string(),
29            TypeRef::Path => "val.0.to_string_lossy().to_string()".to_string(),
30            TypeRef::Duration => "val.0.as_millis() as u64".to_string(),
31            _ => "val.0".to_string(),
32        };
33        return crate::template_env::render(
34            "conversions/core_to_binding_impl",
35            minijinja::context! {
36                core_path => core_path,
37                binding_name => binding_name,
38                is_newtype => true,
39                newtype_inner_expr => newtype_inner_expr,
40                fields => vec![] as Vec<String>,
41            },
42        );
43    }
44
45    let optionalized = config.optionalize_defaults && typ.has_default;
46
47    // Pre-compute all field conversions
48    let mut fields = Vec::new();
49    for field in &typ.fields {
50        // Fields referencing excluded types are not present in the binding struct — skip
51        if !config.exclude_types.is_empty()
52            && super::helpers::field_references_excluded_type(&field.ty, config.exclude_types)
53        {
54            continue;
55        }
56        let base_conversion = field_conversion_from_core_cfg(
57            &field.name,
58            &field.ty,
59            field.optional,
60            field.sanitized,
61            opaque_types,
62            config,
63        );
64        // Box<T> fields: dereference before conversion.
65        let base_conversion = if field.is_boxed && matches!(&field.ty, TypeRef::Named(_)) {
66            if field.optional {
67                // Optional<Box<T>>: replace .map(Into::into) with .map(|v| (*v).into())
68                let src = format!("{}: val.{}.map(Into::into)", field.name, field.name);
69                let dst = format!("{}: val.{}.map(|v| (*v).into())", field.name, field.name);
70                if base_conversion == src { dst } else { base_conversion }
71            } else {
72                // Box<T>: replace `val.{name}` with `(*val.{name})`
73                base_conversion.replace(&format!("val.{}", field.name), &format!("(*val.{})", field.name))
74            }
75        } else {
76            base_conversion
77        };
78        // Newtype unwrapping: when the field was resolved from a newtype (e.g. NodeIndex → u32),
79        // unwrap the core newtype by accessing `.0`.
80        // e.g. `source: val.source` → `source: val.source.0`
81        //      `parent: val.parent` → `parent: val.parent.map(|v| v.0)`
82        //      `children: val.children` → `children: val.children.iter().map(|v| v.0).collect()`
83        let base_conversion = if field.newtype_wrapper.is_some() {
84            match &field.ty {
85                TypeRef::Optional(_) => {
86                    // Replace `val.{name}` with `val.{name}.map(|v| v.0)` in the generated expression
87                    base_conversion.replace(
88                        &format!("val.{}", field.name),
89                        &format!("val.{}.map(|v| v.0)", field.name),
90                    )
91                }
92                TypeRef::Vec(_) => {
93                    // Replace `val.{name}` with `val.{name}.iter().map(|v| v.0).collect()` in expression
94                    base_conversion.replace(
95                        &format!("val.{}", field.name),
96                        &format!("val.{}.iter().map(|v| v.0).collect::<Vec<_>>()", field.name),
97                    )
98                }
99                // When `optional=true` and `ty` is a plain Primitive (not TypeRef::Optional), the core
100                // field is actually `Option<NewtypeT>`, so we must use `.map(|v| v.0)` not `.0`.
101                _ if field.optional => base_conversion.replace(
102                    &format!("val.{}", field.name),
103                    &format!("val.{}.map(|v| v.0)", field.name),
104                ),
105                _ => {
106                    // Direct field: append `.0` to access the inner primitive
107                    base_conversion.replace(&format!("val.{}", field.name), &format!("val.{}.0", field.name))
108                }
109            }
110        } else {
111            base_conversion
112        };
113        // When field.optional=true AND field.ty=Optional(T), the binding struct flattens
114        // Option<Option<T>> to Option<T>. Core produces Option<Option<T>>, binding needs
115        // Option<T>. Generate the conversion by treating the pre-flattened field as Option<T>:
116        // call the standard conversion for the inner type T with optional=true, substituting
117        // val.{name}.flatten() for val.{name} so all cast/conversion logic applies to T.
118        let is_flattened_optional = field.optional && matches!(field.ty, TypeRef::Optional(_));
119        let base_conversion = if is_flattened_optional {
120            if let TypeRef::Optional(inner) = &field.ty {
121                // Produce the conversion as if the field is Option<inner> with value val.name.flatten()
122                let inner_conv = field_conversion_from_core_cfg(
123                    &field.name,
124                    inner.as_ref(),
125                    true,
126                    field.sanitized,
127                    opaque_types,
128                    config,
129                );
130                // inner_conv references val.{name}; replace with val.{name}.flatten()
131                inner_conv.replace(&format!("val.{}", field.name), &format!("val.{}.flatten()", field.name))
132            } else {
133                base_conversion
134            }
135        } else {
136            base_conversion
137        };
138        // Optionalized non-optional fields need Some() wrapping in core→binding direction.
139        // This covers both NAPI-style full optionalization and PyO3-style Duration optionalization.
140        // Flattened-optional fields are already handled above with the correct type.
141        let needs_some_wrap = !is_flattened_optional
142            && ((optionalized && !field.optional)
143                || (config.option_duration_on_defaults
144                    && typ.has_default
145                    && !field.optional
146                    && matches!(field.ty, TypeRef::Duration)));
147        let conversion = if needs_some_wrap {
148            // Extract the value expression after "name: " and wrap in Some()
149            if let Some(expr) = base_conversion.strip_prefix(&format!("{}: ", field.name)) {
150                format!("{}: Some({})", field.name, expr)
151            } else {
152                base_conversion
153            }
154        } else {
155            base_conversion
156        };
157        // Opaque Named fields without CoreWrapper::Arc (e.g. visitor: Object<'static>) cannot be
158        // auto-converted via Arc::new — the binding stores a raw host object that needs a bridge.
159        // Emit Default::default() and let the caller (e.g. the convert function) set it separately.
160        let is_opaque_no_wrapper_field = field.core_wrapper == CoreWrapper::None
161            && matches!(&field.ty, TypeRef::Named(n) if config
162                .opaque_types
163                .is_some_and(|opaque| opaque.contains(n.as_str())));
164        // CoreWrapper: unwrap Arc, convert Cow→String, Bytes→Vec<u8>
165        // For sanitized fields, still apply Cow→String conversion: Cow<'_, str> sanitizes to
166        // TypeRef::String and the Debug-formatted fallback produces quotes, but Cow implements
167        // Display so .to_string() (emitted by apply_core_wrapper_from_core for Cow) is correct.
168        // Other sanitized fields (unknown Named types) still fall through to Debug formatting.
169        let conversion = if is_opaque_no_wrapper_field {
170            format!("{}: Default::default()", field.name)
171        } else if !field.sanitized || field.core_wrapper == alef_core::ir::CoreWrapper::Cow {
172            apply_core_wrapper_from_core(
173                &conversion,
174                &field.name,
175                &field.core_wrapper,
176                &field.vec_inner_core_wrapper,
177                field.optional,
178            )
179        } else {
180            conversion
181        };
182        // In core→binding direction, the binding struct field may be keyword-escaped
183        // (e.g. `class_` for `class`). The generated conversion has `field.name: expr`
184        // on the left side — rename it to `binding_name: expr` when needed.
185        let binding_field = config.binding_field_name_owned(&typ.name, &field.name);
186        let conversion = if binding_field != field.name {
187            if let Some(expr) = conversion.strip_prefix(&format!("{}: ", field.name)) {
188                format!("{binding_field}: {expr}")
189            } else {
190                conversion
191            }
192        } else {
193            conversion
194        };
195        // Cfg-gated trait-bridge fields (visitor handles wrapped as opaque types like
196        // PyVisitorRef) have no auto-conversion from core — force Default::default() and
197        // let the caller wire the bridge separately.  Other cfg-gated fields with
198        // concretely convertible types (e.g. `metadata: HtmlMetadata`) flow through
199        // the normal `val.field.into()` conversion.
200        let field_is_trait_bridge = field.cfg.is_some() && config.never_skip_cfg_field_names.contains(&field.name) && {
201            let opaque_vec: Vec<String> = opaque_types.iter().cloned().collect();
202            crate::generators::structs::field_references_opaque_type(&field.ty, &opaque_vec)
203        };
204        let conversion = if field_is_trait_bridge {
205            format!("{}: Default::default()", binding_field)
206        } else {
207            conversion
208        };
209        fields.push(conversion);
210    }
211
212    crate::template_env::render(
213        "conversions/core_to_binding_impl",
214        minijinja::context! {
215            core_path => core_path,
216            binding_name => binding_name,
217            is_newtype => false,
218            newtype_inner_expr => "",
219            fields => fields,
220        },
221    )
222}
223
224/// Same but for core -> binding direction.
225/// Some types are asymmetric (PathBuf→String, sanitized fields need .to_string()).
226pub fn field_conversion_from_core(
227    name: &str,
228    ty: &TypeRef,
229    optional: bool,
230    sanitized: bool,
231    opaque_types: &AHashSet<String>,
232) -> String {
233    // Sanitized fields: the binding type differs from core (e.g. Box<str>→String, Cow<str>→String).
234    // Box<str>, Cow<str>, and Arc<str> all implement Display, so use .to_string() not {:?}.
235    // {:?} on string-like types produces debug-escaped output with surrounding quotes.
236    if sanitized {
237        // Vec<Primitive>: sanitized from tuple types like (u32, u32) → Vec<u32>.
238        // Core has a tuple, binding expects Vec — destructure the tuple.
239        if let TypeRef::Vec(inner) = ty {
240            if matches!(inner.as_ref(), TypeRef::Primitive(_)) {
241                if optional {
242                    return format!(
243                        "{name}: val.{name}.map(|t| {{ let arr: Vec<_> = [t.0, t.1].into_iter().map(|v| v as _).collect(); arr }})"
244                    );
245                }
246                return format!("{name}: vec![val.{name}.0 as _, val.{name}.1 as _]");
247            }
248        }
249        // Optional(Vec<Primitive>): sanitized from Option<(T, T)> → Option<Vec<T>>.
250        if let TypeRef::Optional(opt_inner) = ty {
251            if let TypeRef::Vec(vec_inner) = opt_inner.as_ref() {
252                if matches!(vec_inner.as_ref(), TypeRef::Primitive(_)) {
253                    return format!("{name}: val.{name}.map(|t| vec![t.0 as _, t.1 as _])");
254                }
255            }
256        }
257        // Map(String, String): sanitized from Map(Box<str>, Box<str>) etc.
258        if let TypeRef::Map(k, v) = ty {
259            if matches!(k.as_ref(), TypeRef::String) && matches!(v.as_ref(), TypeRef::String) {
260                if optional {
261                    return format!(
262                        "{name}: val.{name}.as_ref().map(|m| m.iter().map(|(k, v)| (k.to_string(), v.to_string())).collect())"
263                    );
264                }
265                return format!(
266                    "{name}: val.{name}.into_iter().map(|(k, v)| (k.to_string(), v.to_string())).collect()"
267                );
268            }
269        }
270        // Vec<String>: sanitized from Vec<Box<str>>, Vec<Cow<str>>, Vec<Named>, etc.
271        // Use Debug formatting — the original core type may not implement Display.
272        if let TypeRef::Vec(inner) = ty {
273            if matches!(inner.as_ref(), TypeRef::String) {
274                if optional {
275                    return format!(
276                        "{name}: val.{name}.as_ref().map(|v| v.iter().map(|i| format!(\"{{:?}}\", i)).collect())"
277                    );
278                }
279                return format!("{name}: val.{name}.iter().map(|i| format!(\"{{:?}}\", i)).collect()");
280            }
281        }
282        // Optional<Vec<String>>: sanitized from Optional<Vec<Box<str>>>, Optional<Vec<Cow<str>>>, etc.
283        if let TypeRef::Optional(opt_inner) = ty {
284            if let TypeRef::Vec(vec_inner) = opt_inner.as_ref() {
285                if matches!(vec_inner.as_ref(), TypeRef::String) {
286                    return format!(
287                        "{name}: val.{name}.as_ref().map(|v| v.iter().map(|i| format!(\"{{:?}}\", i)).collect())"
288                    );
289                }
290            }
291        }
292        // String: sanitized from Box<str>, Cow<str>, (u32, u32), etc.
293        // Use Debug formatting — it works for all types (including tuples) and avoids Display
294        // trait bound failures when the original core type doesn't implement Display.
295        // Note: Cow<str> is handled before this point via the CoreWrapper::Cow path above.
296        if matches!(ty, TypeRef::String) {
297            if optional {
298                return format!("{name}: val.{name}.as_ref().map(|v| format!(\"{{v:?}}\"))");
299            }
300            return format!("{name}: format!(\"{{:?}}\", val.{name})");
301        }
302        // Fallback for truly unknown sanitized types — the core type may not implement Display,
303        // so use Debug formatting which is always available (required by the sanitized field's derive).
304        if optional {
305            return format!("{name}: val.{name}.as_ref().map(|v| format!(\"{{v:?}}\"))");
306        }
307        return format!("{name}: format!(\"{{:?}}\", val.{name})");
308    }
309    match ty {
310        // Duration: core uses std::time::Duration, binding uses u64 (millis)
311        TypeRef::Duration => {
312            if optional {
313                return format!("{name}: val.{name}.map(|d| d.as_millis() as u64)");
314            }
315            format!("{name}: val.{name}.as_millis() as u64")
316        }
317        // Path: core uses PathBuf, binding uses String — PathBuf→String needs special handling
318        TypeRef::Path => {
319            if optional {
320                format!("{name}: val.{name}.map(|p| p.to_string_lossy().to_string())")
321            } else {
322                format!("{name}: val.{name}.to_string_lossy().to_string()")
323            }
324        }
325        TypeRef::Optional(inner) if matches!(inner.as_ref(), TypeRef::Path) => {
326            format!("{name}: val.{name}.map(|p| p.to_string_lossy().to_string())")
327        }
328        // Char: core uses char, binding uses String — convert char to string
329        TypeRef::Char => {
330            if optional {
331                format!("{name}: val.{name}.map(|c| c.to_string())")
332            } else {
333                format!("{name}: val.{name}.to_string()")
334            }
335        }
336        // Bytes: core uses bytes::Bytes, binding uses Vec<u8> or napi `Buffer`.
337        // `.into()` is a no-op when destination is Vec<u8> (identity From) and
338        // a Vec→Buffer wrap when destination is `napi::bindgen_prelude::Buffer`.
339        TypeRef::Bytes => {
340            if optional {
341                format!("{name}: val.{name}.map(|v| v.to_vec().into())")
342            } else {
343                format!("{name}: val.{name}.to_vec().into()")
344            }
345        }
346        // Opaque Named types: wrap in Arc to create the binding wrapper
347        TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
348            if optional {
349                format!("{name}: val.{name}.map(|v| {n} {{ inner: Arc::new(v) }})")
350            } else {
351                format!("{name}: {n} {{ inner: Arc::new(val.{name}) }}")
352            }
353        }
354        // Json: core uses serde_json::Value, binding uses String — use .to_string()
355        TypeRef::Json => {
356            if optional {
357                format!("{name}: val.{name}.as_ref().map(ToString::to_string)")
358            } else {
359                format!("{name}: val.{name}.to_string()")
360            }
361        }
362        TypeRef::Optional(inner) if matches!(inner.as_ref(), TypeRef::Json) => {
363            format!("{name}: val.{name}.as_ref().map(ToString::to_string)")
364        }
365        TypeRef::Vec(inner) if matches!(inner.as_ref(), TypeRef::Json) => {
366            if optional {
367                format!("{name}: val.{name}.as_ref().map(|v| v.iter().map(|i| i.to_string()).collect())")
368            } else {
369                format!("{name}: val.{name}.iter().map(ToString::to_string).collect()")
370            }
371        }
372        // Vec<Optional<Json>>: each element is Option<Value> → Option<String>
373        TypeRef::Vec(inner) if matches!(inner.as_ref(), TypeRef::Optional(oi) if matches!(oi.as_ref(), TypeRef::Json)) => {
374            if optional {
375                format!(
376                    "{name}: val.{name}.as_ref().map(|v| v.iter().map(|i| i.as_ref().map(ToString::to_string)).collect())"
377                )
378            } else {
379                format!("{name}: val.{name}.iter().map(|i| i.as_ref().map(ToString::to_string)).collect()")
380            }
381        }
382        // Map with Json values: core uses HashMap<K, serde_json::Value>, binding uses HashMap<K, String>.
383        // Always emit `k.to_string()` so Cow<'_, str> / Box<str> / Arc<str> keys (which the type
384        // resolver normalizes to TypeRef::String) convert correctly. For an actual `String` key
385        // this is a clone, accepted under the existing `#[allow(clippy::useless_conversion)]`.
386        TypeRef::Map(_k, v) if matches!(v.as_ref(), TypeRef::Json) => {
387            if optional {
388                format!(
389                    "{name}: val.{name}.map(|m| m.into_iter().map(|(k, v)| (k.to_string(), v.to_string())).collect())"
390                )
391            } else {
392                format!("{name}: val.{name}.into_iter().map(|(k, v)| (k.to_string(), v.to_string())).collect()")
393            }
394        }
395        // Map with Json keys: core uses HashMap<serde_json::Value, V>, binding uses HashMap<String, V>
396        TypeRef::Map(k, _v) if matches!(k.as_ref(), TypeRef::Json) => {
397            if optional {
398                format!("{name}: val.{name}.map(|m| m.into_iter().map(|(k, v)| (k.to_string(), v)).collect())")
399            } else {
400                format!("{name}: val.{name}.into_iter().map(|(k, v)| (k.to_string(), v)).collect()")
401            }
402        }
403        // Map<String, String>: core may have Box<str> keys/values, binding has String keys/values.
404        // Emit .map() with .into() conversions, which are no-ops when both sides are String.
405        // This handles cases like HashMap<Box<str>, Box<str>> (core) → HashMap<String, String> (binding).
406        TypeRef::Map(k, v) if matches!(k.as_ref(), TypeRef::String) && matches!(v.as_ref(), TypeRef::String) => {
407            if optional {
408                format!("{name}: val.{name}.map(|m| m.into_iter().map(|(k, v)| (k.into(), v.into())).collect())")
409            } else {
410                format!("{name}: val.{name}.into_iter().map(|(k, v)| (k.into(), v.into())).collect()")
411            }
412        }
413        // Map<K, Bytes>: core uses bytes::Bytes (or Vec<u8>), binding uses Vec<u8> or napi Buffer.
414        // `.to_vec().into()` converts Bytes→Vec<u8> (identity for Vec<u8>) or Bytes→Buffer (napi).
415        TypeRef::Map(_k, v) if matches!(v.as_ref(), TypeRef::Bytes) => {
416            if optional {
417                format!("{name}: val.{name}.map(|m| m.into_iter().map(|(k, v)| (k, v.to_vec().into())).collect())")
418            } else {
419                format!("{name}: val.{name}.into_iter().map(|(k, v)| (k, v.to_vec().into())).collect()")
420            }
421        }
422        // Map<K, Named>: each value needs .into() to convert core→binding
423        TypeRef::Map(_k, v) if matches!(v.as_ref(), TypeRef::Named(_)) => {
424            if optional {
425                format!("{name}: val.{name}.map(|m| m.into_iter().map(|(k, v)| (k, v.into())).collect())")
426            } else {
427                format!("{name}: val.{name}.into_iter().map(|(k, v)| (k, v.into())).collect()")
428            }
429        }
430        // Optional(Map<K, Named>): same but wrapped in Option
431        TypeRef::Optional(inner) if matches!(inner.as_ref(), TypeRef::Map(_k, v) if matches!(v.as_ref(), TypeRef::Named(_))) =>
432        {
433            format!("{name}: val.{name}.map(|m| m.into_iter().map(|(k, v)| (k, v.into())).collect())")
434        }
435        // Vec<Named>: each element needs .into() to convert core→binding
436        TypeRef::Vec(inner) if matches!(inner.as_ref(), TypeRef::Named(_)) => {
437            if optional {
438                format!("{name}: val.{name}.map(|v| v.into_iter().map(Into::into).collect())")
439            } else {
440                format!("{name}: val.{name}.into_iter().map(Into::into).collect()")
441            }
442        }
443        // Optional(Vec<Named>): same but wrapped in Option
444        TypeRef::Optional(inner) if matches!(inner.as_ref(), TypeRef::Vec(vi) if matches!(vi.as_ref(), TypeRef::Named(_))) =>
445        {
446            format!("{name}: val.{name}.map(|v| v.into_iter().map(Into::into).collect())")
447        }
448        // Everything else is symmetric
449        _ => field_conversion_to_core(name, ty, optional),
450    }
451}
452
453/// Core→binding field conversion with backend-specific config.
454pub fn field_conversion_from_core_cfg(
455    name: &str,
456    ty: &TypeRef,
457    optional: bool,
458    sanitized: bool,
459    opaque_types: &AHashSet<String>,
460    config: &ConversionConfig,
461) -> String {
462    // Sanitized fields: for WASM (map_uses_jsvalue), Map and Vec<Json> fields target JsValue
463    // and need serde_wasm_bindgen::to_value() instead of iterator-based .collect().
464    // Note: Vec<String> sanitized does NOT use the JsValue path because Vec<String> maps to
465    // Vec<String> in WASM (not JsValue) — use the normal sanitized iterator path instead.
466    if sanitized {
467        if config.map_uses_jsvalue {
468            // Map(String, String) sanitized → JsValue (HashMap maps to JsValue in WASM)
469            // Use js_sys::JSON::parse(json_str) to get a plain JS object (not ES6 Map).
470            if let TypeRef::Map(k, v) = ty {
471                if matches!(k.as_ref(), TypeRef::String) && matches!(v.as_ref(), TypeRef::String) {
472                    if optional {
473                        return format!(
474                            "{name}: val.{name}.as_ref().and_then(|v| serde_json::to_string(v).ok()).and_then(|s| js_sys::JSON::parse(&s).ok())"
475                        );
476                    }
477                    return format!(
478                        "{name}: js_sys::JSON::parse(&serde_json::to_string(&val.{name}).unwrap_or_default()).unwrap_or(JsValue::NULL)"
479                    );
480                }
481            }
482            // Vec<Json> sanitized → JsValue (Vec<Json> maps to JsValue in WASM via nested-vec path)
483            if let TypeRef::Vec(inner) = ty {
484                if matches!(inner.as_ref(), TypeRef::Json) {
485                    if optional {
486                        return format!(
487                            "{name}: val.{name}.as_ref().and_then(|v| serde_wasm_bindgen::to_value(v).ok())"
488                        );
489                    }
490                    return format!("{name}: serde_wasm_bindgen::to_value(&val.{name}).unwrap_or(JsValue::NULL)");
491                }
492            }
493        }
494        return field_conversion_from_core(name, ty, optional, sanitized, opaque_types);
495    }
496
497    // Untagged data enum field (core holds the typed enum, binding holds serde_json::Value):
498    // serialize via serde_json::to_value.  Handles direct, Optional, and Vec wrappings.
499    if let Some(untagged_names) = config.untagged_data_enum_names {
500        let direct_named = matches!(ty, TypeRef::Named(n) if untagged_names.contains(n));
501        let optional_named = matches!(ty, TypeRef::Optional(inner)
502            if matches!(inner.as_ref(), TypeRef::Named(n) if untagged_names.contains(n)));
503        let vec_named = matches!(ty, TypeRef::Vec(inner)
504            if matches!(inner.as_ref(), TypeRef::Named(n) if untagged_names.contains(n)));
505        let optional_vec_named = matches!(ty, TypeRef::Optional(outer)
506            if matches!(outer.as_ref(), TypeRef::Vec(inner)
507                if matches!(inner.as_ref(), TypeRef::Named(n) if untagged_names.contains(n))));
508        if direct_named {
509            if optional {
510                return format!("{name}: val.{name}.as_ref().and_then(|v| serde_json::to_value(v).ok())");
511            }
512            return format!("{name}: serde_json::to_value(&val.{name}).unwrap_or(serde_json::Value::Null)");
513        }
514        if optional_named {
515            return format!("{name}: val.{name}.as_ref().and_then(|v| serde_json::to_value(v).ok())");
516        }
517        if vec_named {
518            if optional {
519                return format!(
520                    "{name}: val.{name}.as_ref().map(|v| v.iter().filter_map(|x| serde_json::to_value(x).ok()).collect())"
521                );
522            }
523            return format!("{name}: val.{name}.iter().filter_map(|x| serde_json::to_value(x).ok()).collect()");
524        }
525        if optional_vec_named {
526            return format!(
527                "{name}: val.{name}.as_ref().map(|v| v.iter().filter_map(|x| serde_json::to_value(x).ok()).collect())"
528            );
529        }
530    }
531
532    // Vec<Named>→String core→binding: binding holds JSON string, core has Vec<Named>.
533    // Only apply serde round-trip for Vec<Named> types (complex structs that can't cross FFI).
534    // Vec<String>, Vec<Primitive>, etc. stay as-is since they map directly.
535    if config.vec_named_to_string {
536        if let TypeRef::Vec(inner) = ty {
537            if matches!(inner.as_ref(), TypeRef::Named(_)) {
538                if optional {
539                    return format!("{name}: val.{name}.as_ref().and_then(|v| serde_json::to_string(v).ok())");
540                }
541                return format!("{name}: serde_json::to_string(&val.{name}).unwrap_or_default()");
542            }
543        }
544    }
545
546    // Map→String core→binding: binding holds Debug-formatted string, core has HashMap.
547    // Used by Rustler (Elixir NIFs) where HashMap cannot cross the NIF boundary directly.
548    if config.map_as_string && matches!(ty, TypeRef::Map(_, _)) {
549        if optional {
550            return format!("{name}: val.{name}.as_ref().map(|m| format!(\"{{m:?}}\"))");
551        }
552        return format!("{name}: format!(\"{{:?}}\", val.{name})");
553    }
554    if config.map_as_string {
555        if let TypeRef::Optional(inner) = ty {
556            if matches!(inner.as_ref(), TypeRef::Map(_, _)) {
557                return format!("{name}: val.{name}.as_ref().map(|m| format!(\"{{m:?}}\"))");
558            }
559        }
560    }
561
562    // WASM JsValue: use js_sys::JSON::parse for Map types (produces plain JS objects, not ES6
563    // Maps which serde_wasm_bindgen would produce for serialize_map calls). Use
564    // serde_wasm_bindgen for nested Vec types.
565    if config.map_uses_jsvalue {
566        let is_nested_vec = matches!(ty, TypeRef::Vec(inner) if matches!(inner.as_ref(), TypeRef::Vec(_)));
567        let is_map = matches!(ty, TypeRef::Map(_, _));
568        if is_map {
569            if optional {
570                return format!(
571                    "{name}: val.{name}.as_ref().and_then(|v| serde_json::to_string(v).ok()).and_then(|s| js_sys::JSON::parse(&s).ok())"
572                );
573            }
574            return format!(
575                "{name}: js_sys::JSON::parse(&serde_json::to_string(&val.{name}).unwrap_or_default()).unwrap_or(JsValue::NULL)"
576            );
577        }
578        if is_nested_vec {
579            if optional {
580                return format!("{name}: val.{name}.as_ref().and_then(|v| serde_wasm_bindgen::to_value(v).ok())");
581            }
582            return format!("{name}: serde_wasm_bindgen::to_value(&val.{name}).unwrap_or(JsValue::NULL)");
583        }
584        if let TypeRef::Optional(inner) = ty {
585            let is_inner_nested = matches!(inner.as_ref(), TypeRef::Vec(vi) if matches!(vi.as_ref(), TypeRef::Vec(_)));
586            let is_inner_map = matches!(inner.as_ref(), TypeRef::Map(_, _));
587            if is_inner_map {
588                return format!(
589                    "{name}: val.{name}.as_ref().and_then(|v| serde_json::to_string(v).ok()).and_then(|s| js_sys::JSON::parse(&s).ok())"
590                );
591            }
592            if is_inner_nested {
593                return format!("{name}: val.{name}.as_ref().and_then(|v| serde_wasm_bindgen::to_value(v).ok())");
594            }
595        }
596    }
597
598    let prefix = config.type_name_prefix;
599    let is_enum_string = |n: &str| -> bool { config.enum_string_names.as_ref().is_some_and(|names| names.contains(n)) };
600
601    match ty {
602        // i64 casting for large int primitives
603        TypeRef::Primitive(p) if config.cast_large_ints_to_i64 && needs_i64_cast(p) => {
604            let cast_to = binding_prim_str(p);
605            if optional {
606                format!("{name}: val.{name}.map(|v| v as {cast_to})")
607            } else {
608                format!("{name}: val.{name} as {cast_to}")
609            }
610        }
611        // Optional(large_int) with i64 casting
612        TypeRef::Optional(inner)
613            if config.cast_large_ints_to_i64
614                && matches!(inner.as_ref(), TypeRef::Primitive(p) if needs_i64_cast(p)) =>
615        {
616            if let TypeRef::Primitive(p) = inner.as_ref() {
617                let cast_to = binding_prim_str(p);
618                format!("{name}: val.{name}.map(|v| v as {cast_to})")
619            } else {
620                field_conversion_from_core(name, ty, optional, sanitized, opaque_types)
621            }
622        }
623        // i32 casting for small uint primitives (extendr/R only)
624        TypeRef::Primitive(p) if config.cast_uints_to_i32 && needs_i32_cast(p) => {
625            if optional {
626                format!("{name}: val.{name}.map(|v| v as i32)")
627            } else {
628                format!("{name}: val.{name} as i32")
629            }
630        }
631        // Optional(small_uint) with i32 casting
632        TypeRef::Optional(inner)
633            if config.cast_uints_to_i32 && matches!(inner.as_ref(), TypeRef::Primitive(p) if needs_i32_cast(p)) =>
634        {
635            format!("{name}: val.{name}.map(|v| v as i32)")
636        }
637        // Vec<u8/u16/u32/i8/i16> needs element-wise core→i32 casting (extendr/R only)
638        TypeRef::Vec(inner)
639            if config.cast_uints_to_i32 && matches!(inner.as_ref(), TypeRef::Primitive(p) if needs_i32_cast(p)) =>
640        {
641            if let TypeRef::Primitive(_p) = inner.as_ref() {
642                if optional {
643                    format!("{name}: val.{name}.as_ref().map(|v| v.iter().map(|&x| x as i32).collect())")
644                } else {
645                    format!("{name}: val.{name}.iter().map(|&v| v as i32).collect()")
646                }
647            } else {
648                field_conversion_from_core(name, ty, optional, sanitized, opaque_types)
649            }
650        }
651        // f64 casting for large int primitives (extendr/R only)
652        TypeRef::Primitive(p) if config.cast_large_ints_to_f64 && needs_f64_cast(p) => {
653            if optional {
654                format!("{name}: val.{name}.map(|v| v as f64)")
655            } else {
656                format!("{name}: val.{name} as f64")
657            }
658        }
659        // Optional(large_int) with f64 casting
660        TypeRef::Optional(inner)
661            if config.cast_large_ints_to_f64
662                && matches!(inner.as_ref(), TypeRef::Primitive(p) if needs_f64_cast(p)) =>
663        {
664            format!("{name}: val.{name}.map(|v| v as f64)")
665        }
666        // Vec<usize/u64/i64/isize/f32> needs element-wise f64 cast for extendr/R backend
667        TypeRef::Vec(inner)
668            if config.cast_large_ints_to_f64
669                && matches!(inner.as_ref(), TypeRef::Primitive(p) if needs_f64_cast(p)) =>
670        {
671            if optional {
672                format!("{name}: val.{name}.as_ref().map(|v| v.iter().map(|&x| x as f64).collect())")
673            } else {
674                format!("{name}: val.{name}.iter().map(|&v| v as f64).collect()")
675            }
676        }
677        // Optional(Vec(usize/u64/i64/isize/f32)) needs element-wise f64 cast
678        TypeRef::Optional(inner)
679            if config.cast_large_ints_to_f64
680                && matches!(inner.as_ref(), TypeRef::Vec(vi) if matches!(vi.as_ref(), TypeRef::Primitive(p) if needs_f64_cast(p))) =>
681        {
682            format!("{name}: val.{name}.as_ref().map(|v| v.iter().map(|&x| x as f64).collect())")
683        }
684        // Vec<Vec<usize/u64/i64/isize/f32>> needs nested element-wise f64 cast (embeddings)
685        TypeRef::Vec(outer)
686            if config.cast_large_ints_to_f64
687                && matches!(outer.as_ref(), TypeRef::Vec(inner) if matches!(inner.as_ref(), TypeRef::Primitive(p) if needs_f64_cast(p))) =>
688        {
689            if optional {
690                format!(
691                    "{name}: val.{name}.as_ref().map(|v| v.iter().map(|inner| inner.iter().map(|&x| x as f64).collect()).collect())"
692                )
693            } else {
694                format!("{name}: val.{name}.iter().map(|inner| inner.iter().map(|&x| x as f64).collect()).collect()")
695            }
696        }
697        // Optional(Vec<Vec<usize/u64/i64/isize/f32>>) needs nested element-wise f64 cast
698        TypeRef::Optional(inner)
699            if config.cast_large_ints_to_f64
700                && matches!(inner.as_ref(), TypeRef::Vec(outer) if matches!(outer.as_ref(), TypeRef::Vec(prim) if matches!(prim.as_ref(), TypeRef::Primitive(p) if needs_f64_cast(p)))) =>
701        {
702            format!(
703                "{name}: val.{name}.as_ref().map(|v| v.iter().map(|inner| inner.iter().map(|&x| x as f64).collect()).collect())"
704            )
705        }
706        // Map values that are usize/u64/i64/isize/f32 stored as f64 in binding → cast when reading core
707        TypeRef::Map(_k, v)
708            if config.cast_large_ints_to_f64 && matches!(v.as_ref(), TypeRef::Primitive(p) if needs_f64_cast(p)) =>
709        {
710            if optional {
711                format!("{name}: val.{name}.as_ref().map(|m| m.iter().map(|(k, v)| (k.clone(), *v as f64)).collect())")
712            } else {
713                format!("{name}: val.{name}.iter().map(|(k, v)| (k.clone(), *v as f64)).collect()")
714            }
715        }
716        // Duration with f64 casting (R: no u64, use f64 millis)
717        TypeRef::Duration if config.cast_large_ints_to_f64 => {
718            if optional {
719                format!("{name}: val.{name}.map(|d| d.as_millis() as f64)")
720            } else {
721                format!("{name}: val.{name}.as_millis() as f64")
722            }
723        }
724        // f32→f64 casting (NAPI only)
725        TypeRef::Primitive(PrimitiveType::F32) if config.cast_f32_to_f64 => {
726            if optional {
727                format!("{name}: val.{name}.map(|v| v as f64)")
728            } else {
729                format!("{name}: val.{name} as f64")
730            }
731        }
732        // Duration with i64 casting
733        TypeRef::Duration if config.cast_large_ints_to_i64 => {
734            if optional {
735                format!("{name}: val.{name}.map(|d| d.as_millis() as u64 as i64)")
736            } else {
737                format!("{name}: val.{name}.as_millis() as u64 as i64")
738            }
739        }
740        // Opaque Named types with prefix: wrap in Arc with prefixed binding name
741        TypeRef::Named(n) if opaque_types.contains(n.as_str()) && !prefix.is_empty() => {
742            let prefixed = format!("{prefix}{n}");
743            if optional {
744                format!("{name}: val.{name}.map(|v| {prefixed} {{ inner: Arc::new(v) }})")
745            } else {
746                format!("{name}: {prefixed} {{ inner: Arc::new(val.{name}) }}")
747            }
748        }
749        // Enum-to-String Named types (PHP pattern)
750        TypeRef::Named(n) if is_enum_string(n) => {
751            // Use serde serialization to get the correct serde(rename) value, not Debug format.
752            // serde_json::to_value gives Value::String("auto") which we extract.
753            if optional {
754                format!(
755                    "{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())"
756                )
757            } else {
758                format!(
759                    "{name}: serde_json::to_value(val.{name}).ok().and_then(|s| s.as_str().map(String::from)).unwrap_or_default()"
760                )
761            }
762        }
763        // Vec<Enum-to-String> Named types: element-wise serde serialization
764        TypeRef::Vec(inner) if matches!(inner.as_ref(), TypeRef::Named(n) if is_enum_string(n)) => {
765            if optional {
766                format!(
767                    "{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())"
768                )
769            } else {
770                format!(
771                    "{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()"
772                )
773            }
774        }
775        // Optional(Vec<Enum-to-String>) Named types (PHP pattern)
776        TypeRef::Optional(inner) if matches!(inner.as_ref(), TypeRef::Vec(vi) if matches!(vi.as_ref(), TypeRef::Named(n) if is_enum_string(n))) =>
777        {
778            format!(
779                "{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())"
780            )
781        }
782        // Vec<f32> needs element-wise cast to f64 when f32→f64 mapping is active
783        TypeRef::Vec(inner)
784            if config.cast_f32_to_f64 && matches!(inner.as_ref(), TypeRef::Primitive(PrimitiveType::F32)) =>
785        {
786            if optional {
787                format!("{name}: val.{name}.as_ref().map(|v| v.iter().map(|&x| x as f64).collect())")
788            } else {
789                format!("{name}: val.{name}.iter().map(|&v| v as f64).collect()")
790            }
791        }
792        // Optional(Vec(f32)) needs element-wise cast to f64
793        TypeRef::Optional(inner)
794            if config.cast_f32_to_f64
795                && matches!(inner.as_ref(), TypeRef::Vec(vi) if matches!(vi.as_ref(), TypeRef::Primitive(PrimitiveType::F32))) =>
796        {
797            format!("{name}: val.{name}.as_ref().map(|v| v.iter().map(|&x| x as f64).collect())")
798        }
799        // Optional(Vec(u64/usize/isize)) needs element-wise i64 casting
800        TypeRef::Optional(inner)
801            if config.cast_large_ints_to_i64
802                && matches!(inner.as_ref(), TypeRef::Vec(vi) if matches!(vi.as_ref(), TypeRef::Primitive(p) if needs_i64_cast(p))) =>
803        {
804            if let TypeRef::Vec(vi) = inner.as_ref() {
805                if let TypeRef::Primitive(p) = vi.as_ref() {
806                    let cast_to = binding_prim_str(p);
807                    if sanitized {
808                        // Sanitized from Option<(T, T)> → Option<Vec<T>>: destructure tuple
809                        format!("{name}: val.{name}.map(|(a, b)| vec![a as {cast_to}, b as {cast_to}])")
810                    } else {
811                        format!("{name}: val.{name}.as_ref().map(|v| v.iter().map(|&x| x as {cast_to}).collect())")
812                    }
813                } else {
814                    field_conversion_from_core(name, ty, optional, sanitized, opaque_types)
815                }
816            } else {
817                field_conversion_from_core(name, ty, optional, sanitized, opaque_types)
818            }
819        }
820        // Vec<Vec<f32>> needs nested element-wise cast to f64 (for embeddings, etc.)
821        TypeRef::Vec(outer)
822            if config.cast_f32_to_f64
823                && matches!(outer.as_ref(), TypeRef::Vec(inner) if matches!(inner.as_ref(), TypeRef::Primitive(PrimitiveType::F32))) =>
824        {
825            if optional {
826                format!(
827                    "{name}: val.{name}.as_ref().map(|v| v.iter().map(|inner| inner.iter().map(|&x| x as f64).collect()).collect())"
828                )
829            } else {
830                format!("{name}: val.{name}.iter().map(|inner| inner.iter().map(|&x| x as f64).collect()).collect()")
831            }
832        }
833        // Optional(Vec<Vec<f32>>) needs nested element-wise cast to f64
834        TypeRef::Optional(inner)
835            if config.cast_f32_to_f64
836                && matches!(inner.as_ref(), TypeRef::Vec(outer) if matches!(outer.as_ref(), TypeRef::Vec(prim) if matches!(prim.as_ref(), TypeRef::Primitive(PrimitiveType::F32)))) =>
837        {
838            format!(
839                "{name}: val.{name}.as_ref().map(|v| v.iter().map(|inner| inner.iter().map(|&x| x as f64).collect()).collect())"
840            )
841        }
842        // Optional with i64-cast inner
843        TypeRef::Optional(inner)
844            if config.cast_large_ints_to_i64
845                && matches!(inner.as_ref(), TypeRef::Primitive(p) if needs_i64_cast(p)) =>
846        {
847            if let TypeRef::Primitive(p) = inner.as_ref() {
848                let cast_to = binding_prim_str(p);
849                format!("{name}: val.{name}.map(|v| v as {cast_to})")
850            } else {
851                field_conversion_from_core(name, ty, optional, sanitized, opaque_types)
852            }
853        }
854        // HashMap value type casting: when value type needs i64 casting
855        TypeRef::Map(_k, v)
856            if config.cast_large_ints_to_i64 && matches!(v.as_ref(), TypeRef::Primitive(p) if needs_i64_cast(p)) =>
857        {
858            if let TypeRef::Primitive(p) = v.as_ref() {
859                let cast_to = binding_prim_str(p);
860                if optional {
861                    format!(
862                        "{name}: val.{name}.as_ref().map(|m| m.iter().map(|(k, v)| (k.clone(), *v as {cast_to})).collect())"
863                    )
864                } else {
865                    format!("{name}: val.{name}.iter().map(|(k, v)| (k.clone(), *v as {cast_to})).collect()")
866                }
867            } else {
868                field_conversion_from_core(name, ty, optional, sanitized, opaque_types)
869            }
870        }
871        // Vec<u64/usize/isize> needs element-wise i64 casting (core→binding)
872        TypeRef::Vec(inner)
873            if config.cast_large_ints_to_i64
874                && matches!(inner.as_ref(), TypeRef::Primitive(p) if needs_i64_cast(p)) =>
875        {
876            if let TypeRef::Primitive(p) = inner.as_ref() {
877                let cast_to = binding_prim_str(p);
878                if sanitized {
879                    // Sanitized from tuple (T, T) → Vec<T>: destructure tuple into vec
880                    if optional {
881                        format!("{name}: val.{name}.map(|(a, b)| vec![a as {cast_to}, b as {cast_to}])")
882                    } else {
883                        format!("{name}: {{ let (a, b) = val.{name}; vec![a as {cast_to}, b as {cast_to}] }}")
884                    }
885                } else if optional {
886                    format!("{name}: val.{name}.as_ref().map(|v| v.iter().map(|&x| x as {cast_to}).collect())")
887                } else {
888                    format!("{name}: val.{name}.iter().map(|&v| v as {cast_to}).collect()")
889                }
890            } else {
891                field_conversion_from_core(name, ty, optional, sanitized, opaque_types)
892            }
893        }
894        // Vec<Vec<u64/usize/isize>> needs nested element-wise i64 casting (core→binding)
895        TypeRef::Vec(outer)
896            if config.cast_large_ints_to_i64
897                && matches!(outer.as_ref(), TypeRef::Vec(inner) if matches!(inner.as_ref(), TypeRef::Primitive(p) if needs_i64_cast(p))) =>
898        {
899            if let TypeRef::Vec(inner) = outer.as_ref() {
900                if let TypeRef::Primitive(p) = inner.as_ref() {
901                    let cast_to = binding_prim_str(p);
902                    if optional {
903                        format!(
904                            "{name}: val.{name}.as_ref().map(|v| v.iter().map(|inner| inner.iter().map(|&x| x as {cast_to}).collect()).collect())"
905                        )
906                    } else {
907                        format!(
908                            "{name}: val.{name}.iter().map(|inner| inner.iter().map(|&x| x as {cast_to}).collect()).collect()"
909                        )
910                    }
911                } else {
912                    field_conversion_from_core(name, ty, optional, sanitized, opaque_types)
913                }
914            } else {
915                field_conversion_from_core(name, ty, optional, sanitized, opaque_types)
916            }
917        }
918        // Json→String: core uses serde_json::Value, binding uses String (PHP)
919        TypeRef::Json if config.json_to_string => {
920            if optional {
921                format!("{name}: val.{name}.as_ref().map(ToString::to_string)")
922            } else {
923                format!("{name}: val.{name}.to_string()")
924            }
925        }
926        // Json stays as serde_json::Value: identity passthrough.
927        TypeRef::Json if config.json_as_value => {
928            format!("{name}: val.{name}")
929        }
930        TypeRef::Optional(inner) if config.json_as_value && matches!(inner.as_ref(), TypeRef::Json) => {
931            format!("{name}: val.{name}")
932        }
933        TypeRef::Vec(inner) if config.json_as_value && matches!(inner.as_ref(), TypeRef::Json) => {
934            if optional {
935                format!("{name}: Some(val.{name})")
936            } else {
937                format!("{name}: val.{name}")
938            }
939        }
940        TypeRef::Map(_k, v) if config.json_as_value && matches!(v.as_ref(), TypeRef::Json) => {
941            if optional {
942                format!("{name}: val.{name}.map(|m| m.into_iter().map(|(k, v)| (k.into(), v)).collect())")
943            } else {
944                format!("{name}: val.{name}.into_iter().map(|(k, v)| (k.into(), v)).collect()")
945            }
946        }
947        // Json→JsValue: core uses serde_json::Value, binding uses JsValue (WASM)
948        TypeRef::Json if config.map_uses_jsvalue => {
949            if optional {
950                format!("{name}: val.{name}.as_ref().and_then(|v| serde_wasm_bindgen::to_value(v).ok())")
951            } else {
952                format!("{name}: serde_wasm_bindgen::to_value(&val.{name}).unwrap_or(JsValue::NULL)")
953            }
954        }
955        // Vec<Json>→JsValue: core uses Vec<serde_json::Value>, binding uses JsValue (WASM)
956        TypeRef::Vec(inner) if config.map_uses_jsvalue && matches!(inner.as_ref(), TypeRef::Json) => {
957            if optional {
958                format!("{name}: val.{name}.as_ref().and_then(|v| serde_wasm_bindgen::to_value(v).ok())")
959            } else {
960                format!("{name}: serde_wasm_bindgen::to_value(&val.{name}).unwrap_or(JsValue::NULL)")
961            }
962        }
963        // Optional(Vec<Json>)→JsValue (WASM)
964        TypeRef::Optional(inner)
965            if config.map_uses_jsvalue
966                && matches!(inner.as_ref(), TypeRef::Vec(vi) if matches!(vi.as_ref(), TypeRef::Json)) =>
967        {
968            format!("{name}: val.{name}.as_ref().and_then(|v| serde_wasm_bindgen::to_value(v).ok())")
969        }
970        // Fall through to default (handles paths, opaque without prefix, etc.)
971        _ => field_conversion_from_core(name, ty, optional, sanitized, opaque_types),
972    }
973}
974
975/// Apply CoreWrapper transformations for core→binding direction.
976/// Unwraps Arc, converts Cow→String, Bytes→Vec<u8>.
977fn apply_core_wrapper_from_core(
978    conversion: &str,
979    name: &str,
980    core_wrapper: &CoreWrapper,
981    vec_inner_core_wrapper: &CoreWrapper,
982    optional: bool,
983) -> String {
984    // Handle Vec<Arc<T>>: unwrap Arc elements
985    if *vec_inner_core_wrapper == CoreWrapper::Arc {
986        return conversion
987            .replace(".map(Into::into).collect()", ".map(|v| (*v).clone().into()).collect()")
988            .replace(
989                "map(|v| v.into_iter().map(Into::into)",
990                "map(|v| v.into_iter().map(|v| (*v).clone().into())",
991            );
992    }
993
994    match core_wrapper {
995        CoreWrapper::None => conversion.to_string(),
996        CoreWrapper::Cow => {
997            // Cow<str> → String: core val.name is Cow<'static, str>, binding needs String.
998            // Always emit val.{name}.into_owned() regardless of what the base conversion emits.
999            // This handles both the normal path (base = "name: val.name") and the sanitized path
1000            // (base = "name: format!(\"{:?}\", val.name)") which produces debug-escaped strings.
1001            // When the binding has been optionalized (e.g. NAPI default-optional fields), the
1002            // upstream pass already wrapped the conversion in Some(...) — preserve that wrap.
1003            let prefix = format!("{name}: ");
1004            let already_some_wrapped = conversion
1005                .strip_prefix(&prefix)
1006                .is_some_and(|expr| expr.starts_with("Some("));
1007            if optional {
1008                format!("{name}: val.{name}.as_ref().map(|v| v.to_string())")
1009            } else if already_some_wrapped {
1010                format!("{name}: Some(val.{name}.to_string())")
1011            } else {
1012                format!("{name}: val.{name}.to_string()")
1013            }
1014        }
1015        CoreWrapper::Arc => {
1016            // Arc<T> → T: unwrap via clone.
1017            //
1018            // Special case: opaque Named types build the binding wrapper with
1019            // `{ inner: Arc::new(v) }` in the base conversion, but when the core
1020            // field is `Arc<T>`, `v` IS already the `Arc<T>` — wrapping it again
1021            // with `Arc::new` produces `Arc<Arc<T>>`.  Detect this pattern and
1022            // replace `Arc::new(v)` with `v`, and `Arc::new(val.{name})` with
1023            // `val.{name}`, then return without adding an extra unwrap chain.
1024            if conversion.contains("{ inner: Arc::new(") {
1025                return conversion.replace("{ inner: Arc::new(v) }", "{ inner: v }").replace(
1026                    &format!("{{ inner: Arc::new(val.{name}) }}"),
1027                    &format!("{{ inner: val.{name} }}"),
1028                );
1029            }
1030            if let Some(expr) = conversion.strip_prefix(&format!("{name}: ")) {
1031                if optional {
1032                    // When the base conversion is the simple passthrough `val.{name}`,
1033                    // the Option carries Arc<T> elements; deref-clone each.
1034                    // When the base is already a complex expression (e.g.
1035                    // `val.{name}.as_ref().map(ToString::to_string)` for Json fields),
1036                    // the Arc is transparently handled via Display/Deref coercion;
1037                    // chaining another `.map(|v| (*v).clone().into())` would operate
1038                    // on the already-converted value (e.g. String) and emit invalid
1039                    // codegen such as `(*String).clone()` (since str: !Clone).
1040                    let simple_passthrough = format!("val.{name}");
1041                    if expr == simple_passthrough {
1042                        format!("{name}: {expr}.map(|v| (*v).clone().into())")
1043                    } else {
1044                        format!("{name}: {expr}")
1045                    }
1046                } else {
1047                    let unwrapped = expr.replace(&format!("val.{name}"), &format!("(*val.{name}).clone()"));
1048                    format!("{name}: {unwrapped}")
1049                }
1050            } else {
1051                conversion.to_string()
1052            }
1053        }
1054        CoreWrapper::Bytes => {
1055            // Bytes → Vec<u8> (or napi Buffer via From<Vec<u8>>): .to_vec().into()
1056            // The TypeRef::Bytes field_conversion already emits the correct expression
1057            // (`.to_vec().into()` non-optional, `.map(|v| v.to_vec().into())` optional).
1058            // Detect those forms and pass through unchanged to avoid double conversion.
1059            if let Some(expr) = conversion.strip_prefix(&format!("{name}: ")) {
1060                let already_converted_non_opt = expr == format!("val.{name}.to_vec().into()");
1061                let already_converted_opt = expr == format!("val.{name}.map(|v| v.to_vec().into())");
1062                if already_converted_non_opt || already_converted_opt {
1063                    conversion.to_string()
1064                } else if optional {
1065                    format!("{name}: {expr}.map(|v| v.to_vec().into())")
1066                } else if expr == format!("val.{name}") {
1067                    format!("{name}: val.{name}.to_vec().into()")
1068                } else {
1069                    conversion.to_string()
1070                }
1071            } else {
1072                conversion.to_string()
1073            }
1074        }
1075        CoreWrapper::ArcMutex => {
1076            // Arc<Mutex<T>> → T: lock and clone
1077            if let Some(expr) = conversion.strip_prefix(&format!("{name}: ")) {
1078                if optional {
1079                    format!("{name}: {expr}.map(|v| v.lock().unwrap().clone().into())")
1080                } else if expr == format!("val.{name}") {
1081                    format!("{name}: val.{name}.lock().unwrap().clone().into()")
1082                } else {
1083                    conversion.to_string()
1084                }
1085            } else {
1086                conversion.to_string()
1087            }
1088        }
1089    }
1090}