Skip to main content

alef_codegen/conversions/
core_to_binding.rs

1use ahash::AHashSet;
2use alef_core::ir::{CoreWrapper, PrimitiveType, TypeDef, TypeRef};
3use std::fmt::Write;
4
5use super::ConversionConfig;
6use super::binding_to_core::field_conversion_to_core;
7use super::helpers::is_newtype;
8use super::helpers::{binding_prim_str, core_type_path, needs_i64_cast};
9
10/// Generate `impl From<core::Type> for BindingType` (core -> binding).
11pub fn gen_from_core_to_binding(typ: &TypeDef, core_import: &str, opaque_types: &AHashSet<String>) -> String {
12    gen_from_core_to_binding_cfg(typ, core_import, opaque_types, &ConversionConfig::default())
13}
14
15/// Generate `impl From<core::Type> for BindingType` with backend-specific config.
16pub fn gen_from_core_to_binding_cfg(
17    typ: &TypeDef,
18    core_import: &str,
19    opaque_types: &AHashSet<String>,
20    config: &ConversionConfig,
21) -> String {
22    let core_path = core_type_path(typ, core_import);
23    let binding_name = format!("{}{}", config.type_name_prefix, typ.name);
24    let mut out = String::with_capacity(256);
25    writeln!(out, "impl From<{core_path}> for {binding_name} {{").ok();
26    writeln!(out, "    fn from(val: {core_path}) -> Self {{").ok();
27
28    // Newtype structs: extract inner value with val.0
29    if is_newtype(typ) {
30        let field = &typ.fields[0];
31        let inner_expr = match &field.ty {
32            TypeRef::Named(_) => "val.0.into()".to_string(),
33            TypeRef::Path => "val.0.to_string_lossy().to_string()".to_string(),
34            TypeRef::Duration => "val.0.as_millis() as u64".to_string(),
35            _ => "val.0".to_string(),
36        };
37        writeln!(out, "        Self {{ _0: {inner_expr} }}").ok();
38        writeln!(out, "    }}").ok();
39        write!(out, "}}").ok();
40        return out;
41    }
42
43    let optionalized = config.optionalize_defaults && typ.has_default;
44    writeln!(out, "        Self {{").ok();
45    for field in &typ.fields {
46        let base_conversion = field_conversion_from_core_cfg(
47            &field.name,
48            &field.ty,
49            field.optional,
50            field.sanitized,
51            opaque_types,
52            config,
53        );
54        // Box<T> fields: dereference before conversion.
55        let base_conversion = if field.is_boxed && matches!(&field.ty, TypeRef::Named(_)) {
56            if field.optional {
57                // Optional<Box<T>>: replace .map(Into::into) with .map(|v| (*v).into())
58                let src = format!("{}: val.{}.map(Into::into)", field.name, field.name);
59                let dst = format!("{}: val.{}.map(|v| (*v).into())", field.name, field.name);
60                if base_conversion == src { dst } else { base_conversion }
61            } else {
62                // Box<T>: replace `val.{name}` with `(*val.{name})`
63                base_conversion.replace(&format!("val.{}", field.name), &format!("(*val.{})", field.name))
64            }
65        } else {
66            base_conversion
67        };
68        // Newtype unwrapping: when the field was resolved from a newtype (e.g. NodeIndex → u32),
69        // unwrap the core newtype by accessing `.0`.
70        // e.g. `source: val.source` → `source: val.source.0`
71        //      `parent: val.parent` → `parent: val.parent.map(|v| v.0)`
72        //      `children: val.children` → `children: val.children.iter().map(|v| v.0).collect()`
73        let base_conversion = if field.newtype_wrapper.is_some() {
74            match &field.ty {
75                TypeRef::Optional(_) => {
76                    // Replace `val.{name}` with `val.{name}.map(|v| v.0)` in the generated expression
77                    base_conversion.replace(
78                        &format!("val.{}", field.name),
79                        &format!("val.{}.map(|v| v.0)", field.name),
80                    )
81                }
82                TypeRef::Vec(_) => {
83                    // Replace `val.{name}` with `val.{name}.iter().map(|v| v.0).collect()` in expression
84                    base_conversion.replace(
85                        &format!("val.{}", field.name),
86                        &format!("val.{}.iter().map(|v| v.0).collect::<Vec<_>>()", field.name),
87                    )
88                }
89                // When `optional=true` and `ty` is a plain Primitive (not TypeRef::Optional), the core
90                // field is actually `Option<NewtypeT>`, so we must use `.map(|v| v.0)` not `.0`.
91                _ if field.optional => base_conversion.replace(
92                    &format!("val.{}", field.name),
93                    &format!("val.{}.map(|v| v.0)", field.name),
94                ),
95                _ => {
96                    // Direct field: append `.0` to access the inner primitive
97                    base_conversion.replace(&format!("val.{}", field.name), &format!("val.{}.0", field.name))
98                }
99            }
100        } else {
101            base_conversion
102        };
103        // Optionalized non-optional fields need Some() wrapping in core→binding direction.
104        // This covers both NAPI-style full optionalization and PyO3-style Duration optionalization.
105        let needs_some_wrap = (optionalized && !field.optional)
106            || (config.option_duration_on_defaults
107                && typ.has_default
108                && !field.optional
109                && matches!(field.ty, TypeRef::Duration));
110        let conversion = if needs_some_wrap {
111            // Extract the value expression after "name: " and wrap in Some()
112            if let Some(expr) = base_conversion.strip_prefix(&format!("{}: ", field.name)) {
113                format!("{}: Some({})", field.name, expr)
114            } else {
115                base_conversion
116            }
117        } else {
118            base_conversion
119        };
120        // CoreWrapper: unwrap Arc, convert Cow→String, Bytes→Vec<u8>
121        let conversion = apply_core_wrapper_from_core(
122            &conversion,
123            &field.name,
124            &field.core_wrapper,
125            &field.vec_inner_core_wrapper,
126            field.optional,
127        );
128        // Skip cfg-gated fields — they don't exist in the binding struct
129        if field.cfg.is_some() {
130            continue;
131        }
132        writeln!(out, "            {conversion},").ok();
133    }
134
135    writeln!(out, "        }}").ok();
136    writeln!(out, "    }}").ok();
137    write!(out, "}}").ok();
138    out
139}
140
141/// Same but for core -> binding direction.
142/// Some types are asymmetric (PathBuf→String, sanitized fields need .to_string()).
143pub fn field_conversion_from_core(
144    name: &str,
145    ty: &TypeRef,
146    optional: bool,
147    sanitized: bool,
148    opaque_types: &AHashSet<String>,
149) -> String {
150    // Sanitized fields: the binding type differs from core (e.g. Box<str>→String, Cow<str>→String).
151    // Use .to_string() for String targets, proper iteration for Vec/Map, format!("{:?}") as last resort.
152    if sanitized {
153        // Map(String, String): sanitized from Map(Box<str>, Box<str>) etc.
154        if let TypeRef::Map(k, v) = ty {
155            if matches!(k.as_ref(), TypeRef::String) && matches!(v.as_ref(), TypeRef::String) {
156                if optional {
157                    return format!(
158                        "{name}: val.{name}.as_ref().map(|m| m.iter().map(|(k, v)| (format!(\"{{:?}}\", k), format!(\"{{:?}}\", v))).collect())"
159                    );
160                }
161                return format!(
162                    "{name}: val.{name}.into_iter().map(|(k, v)| (format!(\"{{:?}}\", k), format!(\"{{:?}}\", v))).collect()"
163                );
164            }
165        }
166        // Vec<String>: sanitized from Vec<Box<str>>, Vec<(T, U)>, etc.
167        if let TypeRef::Vec(inner) = ty {
168            if matches!(inner.as_ref(), TypeRef::String) {
169                if optional {
170                    return format!(
171                        "{name}: val.{name}.as_ref().map(|v| v.iter().map(|i| format!(\"{{:?}}\", i)).collect())"
172                    );
173                }
174                return format!("{name}: val.{name}.iter().map(|i| format!(\"{{:?}}\", i)).collect()");
175            }
176        }
177        // Optional<Vec<String>>: sanitized from Optional<Vec<Box<str>>>, Optional<Vec<(T, U)>>, etc.
178        // Use format!("{:?}", i) because source elements may not impl Display (e.g. tuples).
179        if let TypeRef::Optional(opt_inner) = ty {
180            if let TypeRef::Vec(vec_inner) = opt_inner.as_ref() {
181                if matches!(vec_inner.as_ref(), TypeRef::String) {
182                    return format!(
183                        "{name}: val.{name}.as_ref().map(|v| v.iter().map(|i| format!(\"{{:?}}\", i)).collect())"
184                    );
185                }
186            }
187        }
188        // String: sanitized from Box<str>, Cow<str>, tuple, etc.
189        // Use format!("{:?}") since the source type may not impl Display (e.g., tuples).
190        if matches!(ty, TypeRef::String) {
191            if optional {
192                return format!("{name}: val.{name}.as_ref().map(|v| format!(\"{{:?}}\", v))");
193            }
194            return format!("{name}: format!(\"{{:?}}\", val.{name})");
195        }
196        // Fallback for truly unknown sanitized types
197        if optional {
198            return format!("{name}: val.{name}.as_ref().map(|v| format!(\"{{:?}}\", v))");
199        }
200        return format!("{name}: format!(\"{{:?}}\", val.{name})");
201    }
202    match ty {
203        // Duration: core uses std::time::Duration, binding uses u64 (millis)
204        TypeRef::Duration => {
205            if optional {
206                return format!("{name}: val.{name}.map(|d| d.as_millis() as u64)");
207            }
208            format!("{name}: val.{name}.as_millis() as u64")
209        }
210        // Path: core uses PathBuf, binding uses String — PathBuf→String needs special handling
211        TypeRef::Path => {
212            if optional {
213                format!("{name}: val.{name}.map(|p| p.to_string_lossy().to_string())")
214            } else {
215                format!("{name}: val.{name}.to_string_lossy().to_string()")
216            }
217        }
218        TypeRef::Optional(inner) if matches!(inner.as_ref(), TypeRef::Path) => {
219            format!("{name}: val.{name}.map(|p| p.to_string_lossy().to_string())")
220        }
221        // Char: core uses char, binding uses String — convert char to string
222        TypeRef::Char => {
223            if optional {
224                format!("{name}: val.{name}.map(|c| c.to_string())")
225            } else {
226                format!("{name}: val.{name}.to_string()")
227            }
228        }
229        // Bytes: core uses bytes::Bytes, binding uses Vec<u8>
230        TypeRef::Bytes => {
231            if optional {
232                format!("{name}: val.{name}.map(|v| v.to_vec())")
233            } else {
234                format!("{name}: val.{name}.to_vec()")
235            }
236        }
237        // Opaque Named types: wrap in Arc to create the binding wrapper
238        TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
239            if optional {
240                format!("{name}: val.{name}.map(|v| {n} {{ inner: Arc::new(v) }})")
241            } else {
242                format!("{name}: {n} {{ inner: Arc::new(val.{name}) }}")
243            }
244        }
245        // Json: core uses serde_json::Value, binding uses String — use .to_string()
246        TypeRef::Json => {
247            if optional {
248                format!("{name}: val.{name}.as_ref().map(ToString::to_string)")
249            } else {
250                format!("{name}: val.{name}.to_string()")
251            }
252        }
253        TypeRef::Optional(inner) if matches!(inner.as_ref(), TypeRef::Json) => {
254            format!("{name}: val.{name}.as_ref().map(ToString::to_string)")
255        }
256        TypeRef::Vec(inner) if matches!(inner.as_ref(), TypeRef::Json) => {
257            if optional {
258                format!("{name}: val.{name}.as_ref().map(|v| v.iter().map(|i| i.to_string()).collect())")
259            } else {
260                format!("{name}: val.{name}.iter().map(ToString::to_string).collect()")
261            }
262        }
263        // Map with Json values: core uses HashMap<K, serde_json::Value>, binding uses HashMap<K, String>
264        TypeRef::Map(k, v) if matches!(v.as_ref(), TypeRef::Json) => {
265            let k_is_json = matches!(k.as_ref(), TypeRef::Json);
266            let k_expr = if k_is_json { "k.to_string()" } else { "k" };
267            if optional {
268                format!("{name}: val.{name}.map(|m| m.into_iter().map(|(k, v)| ({k_expr}, v.to_string())).collect())")
269            } else {
270                format!("{name}: val.{name}.into_iter().map(|(k, v)| ({k_expr}, v.to_string())).collect()")
271            }
272        }
273        // Map with Json keys: core uses HashMap<serde_json::Value, V>, binding uses HashMap<String, V>
274        TypeRef::Map(k, _v) if matches!(k.as_ref(), TypeRef::Json) => {
275            if optional {
276                format!("{name}: val.{name}.map(|m| m.into_iter().map(|(k, v)| (k.to_string(), v)).collect())")
277            } else {
278                format!("{name}: val.{name}.into_iter().map(|(k, v)| (k.to_string(), v)).collect()")
279            }
280        }
281        // Everything else is symmetric
282        _ => field_conversion_to_core(name, ty, optional),
283    }
284}
285
286/// Core→binding field conversion with backend-specific config.
287pub fn field_conversion_from_core_cfg(
288    name: &str,
289    ty: &TypeRef,
290    optional: bool,
291    sanitized: bool,
292    opaque_types: &AHashSet<String>,
293    config: &ConversionConfig,
294) -> String {
295    // Sanitized fields: for WASM (map_uses_jsvalue), Map and Vec<Json> fields target JsValue
296    // and need serde_wasm_bindgen::to_value() instead of iterator-based .collect().
297    // Note: Vec<String> sanitized does NOT use the JsValue path because Vec<String> maps to
298    // Vec<String> in WASM (not JsValue) — use the normal sanitized iterator path instead.
299    if sanitized {
300        if config.map_uses_jsvalue {
301            // Map(String, String) sanitized → JsValue (HashMap maps to JsValue in WASM)
302            if let TypeRef::Map(k, v) = ty {
303                if matches!(k.as_ref(), TypeRef::String) && matches!(v.as_ref(), TypeRef::String) {
304                    if optional {
305                        return format!(
306                            "{name}: val.{name}.as_ref().and_then(|v| serde_wasm_bindgen::to_value(v).ok())"
307                        );
308                    }
309                    return format!("{name}: serde_wasm_bindgen::to_value(&val.{name}).unwrap_or(JsValue::NULL)");
310                }
311            }
312            // Vec<Json> sanitized → JsValue (Vec<Json> maps to JsValue in WASM via nested-vec path)
313            if let TypeRef::Vec(inner) = ty {
314                if matches!(inner.as_ref(), TypeRef::Json) {
315                    if optional {
316                        return format!(
317                            "{name}: val.{name}.as_ref().and_then(|v| serde_wasm_bindgen::to_value(v).ok())"
318                        );
319                    }
320                    return format!("{name}: serde_wasm_bindgen::to_value(&val.{name}).unwrap_or(JsValue::NULL)");
321                }
322            }
323        }
324        return field_conversion_from_core(name, ty, optional, sanitized, opaque_types);
325    }
326
327    // WASM JsValue: use serde_wasm_bindgen for Map and nested Vec types
328    if config.map_uses_jsvalue {
329        let is_nested_vec = matches!(ty, TypeRef::Vec(inner) if matches!(inner.as_ref(), TypeRef::Vec(_)));
330        let is_map = matches!(ty, TypeRef::Map(_, _));
331        if is_nested_vec || is_map {
332            if optional {
333                return format!("{name}: val.{name}.as_ref().and_then(|v| serde_wasm_bindgen::to_value(v).ok())");
334            }
335            return format!("{name}: serde_wasm_bindgen::to_value(&val.{name}).unwrap_or(JsValue::NULL)");
336        }
337        if let TypeRef::Optional(inner) = ty {
338            let is_inner_nested = matches!(inner.as_ref(), TypeRef::Vec(vi) if matches!(vi.as_ref(), TypeRef::Vec(_)));
339            let is_inner_map = matches!(inner.as_ref(), TypeRef::Map(_, _));
340            if is_inner_nested || is_inner_map {
341                return format!("{name}: val.{name}.as_ref().and_then(|v| serde_wasm_bindgen::to_value(v).ok())");
342            }
343        }
344    }
345
346    let prefix = config.type_name_prefix;
347    let is_enum_string = |n: &str| -> bool { config.enum_string_names.as_ref().is_some_and(|names| names.contains(n)) };
348
349    match ty {
350        // i64 casting for large int primitives
351        TypeRef::Primitive(p) if config.cast_large_ints_to_i64 && needs_i64_cast(p) => {
352            let cast_to = binding_prim_str(p);
353            if optional {
354                format!("{name}: val.{name}.map(|v| v as {cast_to})")
355            } else {
356                format!("{name}: val.{name} as {cast_to}")
357            }
358        }
359        // f32→f64 casting (NAPI only)
360        TypeRef::Primitive(PrimitiveType::F32) if config.cast_f32_to_f64 => {
361            if optional {
362                format!("{name}: val.{name}.map(|v| v as f64)")
363            } else {
364                format!("{name}: val.{name} as f64")
365            }
366        }
367        // Duration with i64 casting
368        TypeRef::Duration if config.cast_large_ints_to_i64 => {
369            if optional {
370                format!("{name}: val.{name}.map(|d| d.as_millis() as u64 as i64)")
371            } else {
372                format!("{name}: val.{name}.as_millis() as u64 as i64")
373            }
374        }
375        // Opaque Named types with prefix: wrap in Arc with prefixed binding name
376        TypeRef::Named(n) if opaque_types.contains(n.as_str()) && !prefix.is_empty() => {
377            let prefixed = format!("{prefix}{n}");
378            if optional {
379                format!("{name}: val.{name}.map(|v| {prefixed} {{ inner: Arc::new(v) }})")
380            } else {
381                format!("{name}: {prefixed} {{ inner: Arc::new(val.{name}) }}")
382            }
383        }
384        // Enum-to-String Named types (PHP pattern)
385        TypeRef::Named(n) if is_enum_string(n) => {
386            // Use serde serialization to get the correct serde(rename) value, not Debug format.
387            // serde_json::to_value gives Value::String("auto") which we extract.
388            if optional {
389                format!(
390                    "{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())"
391                )
392            } else {
393                format!(
394                    "{name}: serde_json::to_value(&val.{name}).ok().and_then(|s| s.as_str().map(String::from)).unwrap_or_default()"
395                )
396            }
397        }
398        // Vec<Enum-to-String> Named types: element-wise serde serialization
399        TypeRef::Vec(inner) if matches!(inner.as_ref(), TypeRef::Named(n) if is_enum_string(n)) => {
400            if optional {
401                format!(
402                    "{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())"
403                )
404            } else {
405                format!(
406                    "{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()"
407                )
408            }
409        }
410        // Optional(Vec<Enum-to-String>) Named types (PHP pattern)
411        TypeRef::Optional(inner) if matches!(inner.as_ref(), TypeRef::Vec(vi) if matches!(vi.as_ref(), TypeRef::Named(n) if is_enum_string(n))) =>
412        {
413            format!(
414                "{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())"
415            )
416        }
417        // Vec<f32> needs element-wise cast to f64 when f32→f64 mapping is active
418        TypeRef::Vec(inner)
419            if config.cast_f32_to_f64 && matches!(inner.as_ref(), TypeRef::Primitive(PrimitiveType::F32)) =>
420        {
421            if optional {
422                format!("{name}: val.{name}.as_ref().map(|v| v.iter().map(|&x| x as f64).collect())")
423            } else {
424                format!("{name}: val.{name}.iter().map(|&v| v as f64).collect()")
425            }
426        }
427        // Optional(Vec(f32)) needs element-wise cast to f64
428        TypeRef::Optional(inner)
429            if config.cast_f32_to_f64
430                && matches!(inner.as_ref(), TypeRef::Vec(vi) if matches!(vi.as_ref(), TypeRef::Primitive(PrimitiveType::F32))) =>
431        {
432            format!("{name}: val.{name}.as_ref().map(|v| v.iter().map(|&x| x as f64).collect())")
433        }
434        // Optional with i64-cast inner
435        TypeRef::Optional(inner)
436            if config.cast_large_ints_to_i64
437                && matches!(inner.as_ref(), TypeRef::Primitive(p) if needs_i64_cast(p)) =>
438        {
439            if let TypeRef::Primitive(p) = inner.as_ref() {
440                let cast_to = binding_prim_str(p);
441                format!("{name}: val.{name}.map(|v| v as {cast_to})")
442            } else {
443                field_conversion_from_core(name, ty, optional, sanitized, opaque_types)
444            }
445        }
446        // Vec<u64/usize/isize> needs element-wise i64 casting (core→binding)
447        TypeRef::Vec(inner)
448            if config.cast_large_ints_to_i64
449                && matches!(inner.as_ref(), TypeRef::Primitive(p) if needs_i64_cast(p)) =>
450        {
451            if let TypeRef::Primitive(p) = inner.as_ref() {
452                let cast_to = binding_prim_str(p);
453                if optional {
454                    format!("{name}: val.{name}.as_ref().map(|v| v.iter().map(|&x| x as {cast_to}).collect())")
455                } else {
456                    format!("{name}: val.{name}.iter().map(|&v| v as {cast_to}).collect()")
457                }
458            } else {
459                field_conversion_from_core(name, ty, optional, sanitized, opaque_types)
460            }
461        }
462        // Json→String: core uses serde_json::Value, binding uses String (PHP)
463        TypeRef::Json if config.json_to_string => {
464            if optional {
465                format!("{name}: val.{name}.as_ref().map(ToString::to_string)")
466            } else {
467                format!("{name}: val.{name}.to_string()")
468            }
469        }
470        // Json→JsValue: core uses serde_json::Value, binding uses JsValue (WASM)
471        TypeRef::Json if config.map_uses_jsvalue => {
472            if optional {
473                format!("{name}: val.{name}.as_ref().and_then(|v| serde_wasm_bindgen::to_value(v).ok())")
474            } else {
475                format!("{name}: serde_wasm_bindgen::to_value(&val.{name}).unwrap_or(JsValue::NULL)")
476            }
477        }
478        // Vec<Json>→JsValue: core uses Vec<serde_json::Value>, binding uses JsValue (WASM)
479        TypeRef::Vec(inner) if config.map_uses_jsvalue && matches!(inner.as_ref(), TypeRef::Json) => {
480            if optional {
481                format!("{name}: val.{name}.as_ref().and_then(|v| serde_wasm_bindgen::to_value(v).ok())")
482            } else {
483                format!("{name}: serde_wasm_bindgen::to_value(&val.{name}).unwrap_or(JsValue::NULL)")
484            }
485        }
486        // Optional(Vec<Json>)→JsValue (WASM)
487        TypeRef::Optional(inner)
488            if config.map_uses_jsvalue
489                && matches!(inner.as_ref(), TypeRef::Vec(vi) if matches!(vi.as_ref(), TypeRef::Json)) =>
490        {
491            format!("{name}: val.{name}.as_ref().and_then(|v| serde_wasm_bindgen::to_value(v).ok())")
492        }
493        // Fall through to default (handles paths, opaque without prefix, etc.)
494        _ => field_conversion_from_core(name, ty, optional, sanitized, opaque_types),
495    }
496}
497
498/// Apply CoreWrapper transformations for core→binding direction.
499/// Unwraps Arc, converts Cow→String, Bytes→Vec<u8>.
500fn apply_core_wrapper_from_core(
501    conversion: &str,
502    name: &str,
503    core_wrapper: &CoreWrapper,
504    vec_inner_core_wrapper: &CoreWrapper,
505    optional: bool,
506) -> String {
507    // Handle Vec<Arc<T>>: unwrap Arc elements
508    if *vec_inner_core_wrapper == CoreWrapper::Arc {
509        return conversion
510            .replace(".map(Into::into).collect()", ".map(|v| (*v).clone().into()).collect()")
511            .replace(
512                "map(|v| v.into_iter().map(Into::into)",
513                "map(|v| v.into_iter().map(|v| (*v).clone().into())",
514            );
515    }
516
517    match core_wrapper {
518        CoreWrapper::None => conversion.to_string(),
519        CoreWrapper::Cow => {
520            // Cow<str> → String: core val.name is Cow, binding needs String
521            // The conversion already emits "name: val.name" for strings which works
522            // since Cow<str> derefs to &str and String: From<Cow<str>> exists.
523            // But if it's "val.name" directly, add .into_owned() or .to_string()
524            if let Some(expr) = conversion.strip_prefix(&format!("{name}: ")) {
525                if optional {
526                    // Already handled by map
527                    conversion.to_string()
528                } else if expr == format!("val.{name}") {
529                    format!("{name}: val.{name}.into_owned()")
530                } else {
531                    conversion.to_string()
532                }
533            } else {
534                conversion.to_string()
535            }
536        }
537        CoreWrapper::Arc => {
538            // Arc<T> → T: unwrap via clone
539            if let Some(expr) = conversion.strip_prefix(&format!("{name}: ")) {
540                if optional {
541                    format!("{name}: {expr}.map(|v| (*v).clone().into())")
542                } else {
543                    let unwrapped = expr.replace(&format!("val.{name}"), &format!("(*val.{name}).clone()"));
544                    format!("{name}: {unwrapped}")
545                }
546            } else {
547                conversion.to_string()
548            }
549        }
550        CoreWrapper::Bytes => {
551            // Bytes → Vec<u8>: .to_vec()
552            if let Some(expr) = conversion.strip_prefix(&format!("{name}: ")) {
553                if optional {
554                    format!("{name}: {expr}.map(|v| v.to_vec())")
555                } else if expr == format!("val.{name}") {
556                    format!("{name}: val.{name}.to_vec()")
557                } else {
558                    conversion.to_string()
559                }
560            } else {
561                conversion.to_string()
562            }
563        }
564    }
565}