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