Skip to main content

alef_backend_php/
trait_bridge.rs

1//! PHP (ext-php-rs) specific trait bridge code generation.
2//!
3//! Generates Rust wrapper structs that implement Rust traits by delegating
4//! to PHP objects via ext-php-rs Zval method calls.
5
6use alef_core::config::TraitBridgeConfig;
7use alef_core::ir::{ApiSurface, MethodDef, TypeDef, TypeRef};
8use std::fmt::Write;
9
10/// Generate all trait bridge code for a given trait type and bridge config.
11pub fn gen_trait_bridge(
12    trait_type: &TypeDef,
13    bridge_cfg: &TraitBridgeConfig,
14    core_import: &str,
15    api: &ApiSurface,
16) -> String {
17    let mut out = String::with_capacity(8192);
18    let struct_name = format!("Php{}Bridge", bridge_cfg.trait_name);
19    let trait_path = trait_type.rust_path.replace('-', "_");
20
21    // Build type name → rust_path lookup
22    let type_paths: std::collections::HashMap<&str, &str> = api
23        .types
24        .iter()
25        .map(|t| (t.name.as_str(), t.rust_path.as_str()))
26        .chain(api.enums.iter().map(|e| (e.name.as_str(), e.rust_path.as_str())))
27        .collect();
28
29    // Visitor-style bridge: all methods have defaults, no registry, no super-trait.
30    let is_visitor_bridge = bridge_cfg.type_alias.is_some()
31        && bridge_cfg.register_fn.is_none()
32        && bridge_cfg.super_trait.is_none()
33        && trait_type.methods.iter().all(|m| m.has_default_impl);
34
35    if is_visitor_bridge {
36        gen_visitor_bridge(
37            &mut out,
38            trait_type,
39            bridge_cfg,
40            &struct_name,
41            &trait_path,
42            core_import,
43            &type_paths,
44        );
45    }
46
47    out
48}
49
50/// Generate a visitor-style bridge wrapping a PHP `Zval` object reference.
51///
52/// Every trait method checks if the PHP object has a matching camelCase method,
53/// then calls it and maps the PHP return value to `VisitResult`.
54fn gen_visitor_bridge(
55    out: &mut String,
56    trait_type: &TypeDef,
57    _bridge_cfg: &TraitBridgeConfig,
58    struct_name: &str,
59    trait_path: &str,
60    core_crate: &str,
61    type_paths: &std::collections::HashMap<&str, &str>,
62) {
63    // Helper: convert NodeContext to a PHP array (Zval)
64    writeln!(out, "fn nodecontext_to_php_array(").unwrap();
65    writeln!(out, "    ctx: &{core_crate}::visitor::NodeContext,").unwrap();
66    writeln!(out, ") -> ext_php_rs::boxed::ZBox<ext_php_rs::types::ZendHashTable> {{").unwrap();
67    writeln!(out, "    let mut arr = ext_php_rs::types::ZendHashTable::new();").unwrap();
68    writeln!(
69        out,
70        "    arr.insert(\"nodeType\", ext_php_rs::types::Zval::try_from(format!(\"{{:?}}\", ctx.node_type)).unwrap_or_default()).ok();"
71    )
72    .unwrap();
73    writeln!(
74        out,
75        "    arr.insert(\"tagName\", ext_php_rs::types::Zval::try_from(ctx.tag_name.clone()).unwrap_or_default()).ok();"
76    )
77    .unwrap();
78    writeln!(
79        out,
80        "    arr.insert(\"depth\", ext_php_rs::types::Zval::try_from(ctx.depth as i64).unwrap_or_default()).ok();"
81    )
82    .unwrap();
83    writeln!(
84        out,
85        "    arr.insert(\"indexInParent\", ext_php_rs::types::Zval::try_from(ctx.index_in_parent as i64).unwrap_or_default()).ok();"
86    )
87    .unwrap();
88    writeln!(
89        out,
90        "    arr.insert(\"isInline\", ext_php_rs::types::Zval::try_from(ctx.is_inline).unwrap_or_default()).ok();"
91    )
92    .unwrap();
93    writeln!(out, "    if let Some(ref pt) = ctx.parent_tag {{").unwrap();
94    writeln!(
95        out,
96        "        arr.insert(\"parentTag\", ext_php_rs::types::Zval::try_from(pt.clone()).unwrap_or_default()).ok();"
97    )
98    .unwrap();
99    writeln!(out, "    }}").unwrap();
100    writeln!(out, "    let mut attrs = ext_php_rs::types::ZendHashTable::new();").unwrap();
101    writeln!(out, "    for (k, v) in &ctx.attributes {{").unwrap();
102    writeln!(
103        out,
104        "        attrs.insert(k.as_str(), ext_php_rs::types::Zval::try_from(v.clone()).unwrap_or_default()).ok();"
105    )
106    .unwrap();
107    writeln!(out, "    }}").unwrap();
108    writeln!(out, "    let mut attrs_zval = ext_php_rs::types::Zval::new();").unwrap();
109    writeln!(out, "    attrs_zval.set_hashtable(attrs);").unwrap();
110    writeln!(out, "    arr.insert(\"attributes\", attrs_zval).ok();").unwrap();
111    writeln!(out, "    arr").unwrap();
112    writeln!(out, "}}").unwrap();
113    writeln!(out).unwrap();
114
115    // Bridge struct — stores a raw pointer to the PHP object.
116    // The pointer is valid for the duration of the PHP function call that
117    // created the bridge, which spans the entire Rust trait method dispatch.
118    writeln!(out, "pub struct {struct_name} {{").unwrap();
119    writeln!(out, "    php_obj: *mut ext_php_rs::types::ZendObject,").unwrap();
120    writeln!(out, "}}").unwrap();
121    writeln!(out).unwrap();
122
123    // SAFETY: The raw pointer is only used while the PHP call stack frame is
124    // alive. The bridge is consumed before the PHP function returns.
125    writeln!(out, "// SAFETY: PHP objects are single-threaded; the bridge is used").unwrap();
126    writeln!(out, "// only within a single PHP request, never across threads.").unwrap();
127    writeln!(out, "unsafe impl Send for {struct_name} {{}}").unwrap();
128    writeln!(out, "unsafe impl Sync for {struct_name} {{}}").unwrap();
129    writeln!(out).unwrap();
130
131    // Manual Debug impl
132    writeln!(out, "impl std::fmt::Debug for {struct_name} {{").unwrap();
133    writeln!(
134        out,
135        "    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {{"
136    )
137    .unwrap();
138    writeln!(out, "        write!(f, \"{struct_name}\")").unwrap();
139    writeln!(out, "    }}").unwrap();
140    writeln!(out, "}}").unwrap();
141    writeln!(out).unwrap();
142
143    // Constructor takes &mut ZendObject, which is what ext-php-rs exposes via
144    // FromZvalMut. We store the raw pointer; the caller guarantees the object
145    // outlives this bridge.
146    writeln!(out, "impl {struct_name} {{").unwrap();
147    writeln!(
148        out,
149        "    pub fn new(php_obj: &mut ext_php_rs::types::ZendObject) -> Self {{"
150    )
151    .unwrap();
152    writeln!(out, "        Self {{ php_obj: php_obj as *mut _ }}").unwrap();
153    writeln!(out, "    }}").unwrap();
154    writeln!(out, "}}").unwrap();
155    writeln!(out).unwrap();
156
157    // Trait impl
158    writeln!(out, "impl {trait_path} for {struct_name} {{").unwrap();
159    for method in &trait_type.methods {
160        if method.trait_source.is_some() {
161            continue;
162        }
163        gen_visitor_method_php(out, method, type_paths);
164    }
165    writeln!(out, "}}").unwrap();
166    writeln!(out).unwrap();
167}
168
169/// Map a visitor method parameter type to the correct Rust type string.
170fn visitor_param_type(
171    ty: &TypeRef,
172    is_ref: bool,
173    optional: bool,
174    tp: &std::collections::HashMap<&str, &str>,
175) -> String {
176    if optional && matches!(ty, TypeRef::String) && is_ref {
177        return "Option<&str>".to_string();
178    }
179    if is_ref {
180        if let TypeRef::Vec(inner) = ty {
181            let inner_str = param_type(inner, "", false, tp);
182            return format!("&[{inner_str}]");
183        }
184    }
185    param_type(ty, "", is_ref, tp)
186}
187
188/// Generate a single visitor method that checks for a camelCase PHP method and calls it.
189fn gen_visitor_method_php(out: &mut String, method: &MethodDef, type_paths: &std::collections::HashMap<&str, &str>) {
190    let name = &method.name;
191    let php_name = to_camel_case(name);
192
193    let mut sig_parts = vec!["&mut self".to_string()];
194    for p in &method.params {
195        let ty_str = visitor_param_type(&p.ty, p.is_ref, p.optional, type_paths);
196        sig_parts.push(format!("{}: {}", p.name, ty_str));
197    }
198    let sig = sig_parts.join(", ");
199
200    let ret_ty = match &method.return_type {
201        TypeRef::Named(n) => type_paths
202            .get(n.as_str())
203            .map(|p| p.replace('-', "_"))
204            .unwrap_or_else(|| n.clone()),
205        other => param_type(other, "", false, type_paths),
206    };
207
208    writeln!(out, "    fn {name}({sig}) -> {ret_ty} {{").unwrap();
209
210    // SAFETY: php_obj pointer is valid for the lifetime of the PHP call frame.
211    writeln!(
212        out,
213        "        // SAFETY: php_obj is a valid ZendObject pointer for the duration of this call."
214    )
215    .unwrap();
216    writeln!(out, "        let php_obj_ref = unsafe {{ &*self.php_obj }};").unwrap();
217
218    // Build args array
219    let has_args = !method.params.is_empty();
220    if has_args {
221        writeln!(out, "        let mut args: Vec<ext_php_rs::types::Zval> = Vec::new();").unwrap();
222        for p in &method.params {
223            if let TypeRef::Named(n) = &p.ty {
224                if n == "NodeContext" {
225                    writeln!(
226                        out,
227                        "        let ctx_arr = nodecontext_to_php_array({}{});",
228                        if p.is_ref { "" } else { "&" },
229                        p.name
230                    )
231                    .unwrap();
232                    writeln!(
233                        out,
234                        "        args.push(ext_php_rs::convert::IntoZval::into_zval(ctx_arr, false).unwrap_or_default());"
235                    )
236                    .unwrap();
237                    continue;
238                }
239            }
240            // Check optional string ref BEFORE non-optional string, since visitor_param_type
241            // returns Option<&str> for optional string ref params.
242            if p.optional && matches!(&p.ty, TypeRef::String) && p.is_ref {
243                writeln!(
244                    out,
245                    "        args.push(match {0} {{ Some(s) => ext_php_rs::types::Zval::try_from(s.to_string()).unwrap_or_default(), None => ext_php_rs::types::Zval::new() }});",
246                    p.name
247                )
248                .unwrap();
249                continue;
250            }
251            if matches!(&p.ty, TypeRef::String) {
252                if p.is_ref {
253                    writeln!(
254                        out,
255                        "        args.push(ext_php_rs::types::Zval::try_from({}.to_string()).unwrap_or_default());",
256                        p.name
257                    )
258                    .unwrap();
259                } else {
260                    writeln!(
261                        out,
262                        "        args.push(ext_php_rs::types::Zval::try_from({}.clone()).unwrap_or_default());",
263                        p.name
264                    )
265                    .unwrap();
266                }
267                continue;
268            }
269            if matches!(&p.ty, TypeRef::Primitive(alef_core::ir::PrimitiveType::Bool)) {
270                writeln!(
271                    out,
272                    "        {{ let mut _zv = ext_php_rs::types::Zval::new(); _zv.set_bool({}); args.push(_zv); }}",
273                    p.name
274                )
275                .unwrap();
276                continue;
277            }
278            // Default: format as string
279            writeln!(
280                out,
281                "        args.push(ext_php_rs::types::Zval::try_from(format!(\"{{:?}}\", {})).unwrap_or_default());",
282                p.name
283            )
284            .unwrap();
285        }
286    }
287
288    // Call the PHP method via try_call_method which takes Vec<&dyn IntoZvalDyn>.
289    // If the method does not exist, try_call_method returns Err(Error::Callable),
290    // which we treat as a "no-op, return Continue" (same as the default impl).
291    if has_args {
292        writeln!(
293            out,
294            "        let dyn_args: Vec<&dyn ext_php_rs::convert::IntoZvalDyn> = args.iter().map(|z| z as &dyn ext_php_rs::convert::IntoZvalDyn).collect();"
295        )
296        .unwrap();
297    }
298    let args_expr = if has_args { "dyn_args" } else { "vec![]" };
299    writeln!(
300        out,
301        "        let result = php_obj_ref.try_call_method(\"{php_name}\", {args_expr});"
302    )
303    .unwrap();
304
305    // Parse result — try_call_method returns Result<Zval> (not Result<Option<Zval>>)
306    writeln!(out, "        match result {{").unwrap();
307    writeln!(out, "            Err(_) => {ret_ty}::Continue,").unwrap();
308    writeln!(out, "            Ok(val) => {{").unwrap();
309    writeln!(
310        out,
311        "                let s = val.string().unwrap_or_default().to_lowercase();"
312    )
313    .unwrap();
314    writeln!(out, "                match s.as_str() {{").unwrap();
315    writeln!(out, "                    \"continue\" => {ret_ty}::Continue,").unwrap();
316    writeln!(out, "                    \"skip\" => {ret_ty}::Skip,").unwrap();
317    writeln!(
318        out,
319        "                    \"preserve_html\" | \"preservehtml\" => {ret_ty}::PreserveHtml,"
320    )
321    .unwrap();
322    writeln!(out, "                    other => {ret_ty}::Custom(other.to_string()),").unwrap();
323    writeln!(out, "                }}").unwrap();
324    writeln!(out, "            }}").unwrap();
325    writeln!(out, "        }}").unwrap();
326    writeln!(out, "    }}").unwrap();
327    writeln!(out).unwrap();
328}
329
330/// Convert snake_case to camelCase.
331fn to_camel_case(name: &str) -> String {
332    let mut result = String::with_capacity(name.len());
333    let mut capitalize_next = false;
334    for (i, c) in name.chars().enumerate() {
335        if c == '_' {
336            capitalize_next = true;
337        } else if capitalize_next {
338            result.extend(c.to_uppercase());
339            capitalize_next = false;
340        } else if i == 0 {
341            result.extend(c.to_lowercase());
342        } else {
343            result.push(c);
344        }
345    }
346    result
347}
348
349/// Map TypeRef to a Rust type string.
350fn param_type(ty: &TypeRef, ci: &str, is_ref: bool, tp: &std::collections::HashMap<&str, &str>) -> String {
351    match ty {
352        TypeRef::Bytes if is_ref => "&[u8]".into(),
353        TypeRef::Bytes => "Vec<u8>".into(),
354        TypeRef::String if is_ref => "&str".into(),
355        TypeRef::String => "String".into(),
356        TypeRef::Path if is_ref => "&std::path::Path".into(),
357        TypeRef::Path => "std::path::PathBuf".into(),
358        TypeRef::Named(n) => {
359            let qualified = tp
360                .get(n.as_str())
361                .map(|p| p.replace('-', "_"))
362                .unwrap_or_else(|| format!("{ci}::{n}"));
363            if is_ref { format!("&{qualified}") } else { qualified }
364        }
365        TypeRef::Vec(inner) => format!("Vec<{}>", param_type(inner, ci, false, tp)),
366        TypeRef::Optional(inner) => format!("Option<{}>", param_type(inner, ci, false, tp)),
367        TypeRef::Primitive(p) => prim(p).into(),
368        TypeRef::Unit => "()".into(),
369        TypeRef::Char => "char".into(),
370        TypeRef::Map(k, v) => format!(
371            "std::collections::HashMap<{}, {}>",
372            param_type(k, ci, false, tp),
373            param_type(v, ci, false, tp)
374        ),
375        TypeRef::Json => "serde_json::Value".into(),
376        TypeRef::Duration => "std::time::Duration".into(),
377    }
378}
379
380fn prim(p: &alef_core::ir::PrimitiveType) -> &'static str {
381    use alef_core::ir::PrimitiveType::*;
382    match p {
383        Bool => "bool",
384        U8 => "u8",
385        U16 => "u16",
386        U32 => "u32",
387        U64 => "u64",
388        I8 => "i8",
389        I16 => "i16",
390        I32 => "i32",
391        I64 => "i64",
392        F32 => "f32",
393        F64 => "f64",
394        Usize => "usize",
395        Isize => "isize",
396    }
397}
398
399/// Find the first parameter index and bridge config where the parameter's named type
400/// matches a trait bridge's `type_alias`.
401///
402/// Returns `None` when no bridge applies.
403pub fn find_bridge_param<'a>(
404    func: &alef_core::ir::FunctionDef,
405    bridges: &'a [TraitBridgeConfig],
406) -> Option<(usize, &'a TraitBridgeConfig)> {
407    for (idx, param) in func.params.iter().enumerate() {
408        let named = match &param.ty {
409            TypeRef::Named(n) => Some(n.as_str()),
410            TypeRef::Optional(inner) => {
411                if let TypeRef::Named(n) = inner.as_ref() {
412                    Some(n.as_str())
413                } else {
414                    None
415                }
416            }
417            _ => None,
418        };
419        for bridge in bridges {
420            if let Some(type_name) = named {
421                if bridge.type_alias.as_deref() == Some(type_name) {
422                    return Some((idx, bridge));
423                }
424            }
425            if bridge.param_name.as_deref() == Some(param.name.as_str()) {
426                return Some((idx, bridge));
427            }
428        }
429    }
430    None
431}
432
433/// Generate a PHP static method that has one parameter replaced by
434/// `Option<ext_php_rs::boxed::ZBox<ext_php_rs::types::ZendObject>>` (a trait bridge).
435#[allow(clippy::too_many_arguments)]
436pub fn gen_bridge_function(
437    func: &alef_core::ir::FunctionDef,
438    bridge_param_idx: usize,
439    bridge_cfg: &TraitBridgeConfig,
440    mapper: &dyn alef_codegen::type_mapper::TypeMapper,
441    opaque_types: &ahash::AHashSet<String>,
442    core_import: &str,
443) -> String {
444    use alef_core::ir::TypeRef;
445
446    let struct_name = format!("Php{}Bridge", bridge_cfg.trait_name);
447    let handle_path = format!("{core_import}::visitor::VisitorHandle");
448    let param_name = &func.params[bridge_param_idx].name;
449    let bridge_param = &func.params[bridge_param_idx];
450    let is_optional = bridge_param.optional || matches!(&bridge_param.ty, TypeRef::Optional(_));
451
452    // Build parameter list, hiding bridge params from signature
453    let mut sig_parts = Vec::new();
454    for (idx, p) in func.params.iter().enumerate() {
455        if idx == bridge_param_idx {
456            // Bridge param: &mut ZendObject implements FromZvalMut in ext-php-rs 0.15,
457            // allowing PHP to pass any object. ZBox<ZendObject> does NOT implement
458            // FromZvalMut, so we must use the reference form here.
459            let php_obj_ty = "&mut ext_php_rs::types::ZendObject";
460            if is_optional {
461                sig_parts.push(format!("{}: Option<{php_obj_ty}>", p.name));
462            } else {
463                sig_parts.push(format!("{}: {php_obj_ty}", p.name));
464            }
465        } else {
466            let promoted = idx > bridge_param_idx || func.params[..idx].iter().any(|pp| pp.optional);
467            let base = mapper.map_type(&p.ty);
468            // #[php_class] types (non-opaque Named) only implement FromZvalMut for &mut T,
469            // not for owned T — so we must use &mut T in the function signature.
470            let ty = match &p.ty {
471                TypeRef::Named(n) if !opaque_types.contains(n.as_str()) => {
472                    if p.optional || promoted {
473                        format!("Option<&mut {base}>")
474                    } else {
475                        format!("&mut {base}")
476                    }
477                }
478                TypeRef::Optional(inner) => {
479                    if let TypeRef::Named(n) = inner.as_ref() {
480                        if !opaque_types.contains(n.as_str()) {
481                            format!("Option<&mut {base}>")
482                        } else if p.optional || promoted {
483                            format!("Option<{base}>")
484                        } else {
485                            base
486                        }
487                    } else if p.optional || promoted {
488                        format!("Option<{base}>")
489                    } else {
490                        base
491                    }
492                }
493                _ => {
494                    if p.optional || promoted {
495                        format!("Option<{base}>")
496                    } else {
497                        base
498                    }
499                }
500            };
501            sig_parts.push(format!("{}: {}", p.name, ty));
502        }
503    }
504
505    let params_str = sig_parts.join(", ");
506    let return_type = mapper.map_type(&func.return_type);
507    let ret = mapper.wrap_return(&return_type, func.error_type.is_some());
508
509    let err_conv = ".map_err(|e| ext_php_rs::exception::PhpException::default(e.to_string()))";
510
511    // Bridge wrapping code
512    let bridge_wrap = if is_optional {
513        format!(
514            "let {param_name} = {param_name}.map(|v| {{\n        \
515             let bridge = {struct_name}::new(v);\n        \
516             std::rc::Rc::new(std::cell::RefCell::new(bridge)) as {handle_path}\n    \
517             }});"
518        )
519    } else {
520        format!(
521            "let {param_name} = {{\n        \
522             let bridge = {struct_name}::new({param_name});\n        \
523             std::rc::Rc::new(std::cell::RefCell::new(bridge)) as {handle_path}\n    \
524             }};"
525        )
526    };
527
528    // Serde let bindings for non-bridge Named params
529    let serde_bindings: String = func
530        .params
531        .iter()
532        .enumerate()
533        .filter(|(idx, p)| {
534            if *idx == bridge_param_idx {
535                return false;
536            }
537            let named = match &p.ty {
538                TypeRef::Named(n) => Some(n.as_str()),
539                TypeRef::Optional(inner) => {
540                    if let TypeRef::Named(n) = inner.as_ref() {
541                        Some(n.as_str())
542                    } else {
543                        None
544                    }
545                }
546                _ => None,
547            };
548            named.is_some_and(|n| !opaque_types.contains(n))
549        })
550        .map(|(_, p)| {
551            let name = &p.name;
552            let core_path = format!(
553                "{core_import}::{}",
554                match &p.ty {
555                    TypeRef::Named(n) => n.clone(),
556                    TypeRef::Optional(inner) =>
557                        if let TypeRef::Named(n) = inner.as_ref() {
558                            n.clone()
559                        } else {
560                            String::new()
561                        },
562                    _ => String::new(),
563                }
564            );
565            if p.optional || matches!(&p.ty, TypeRef::Optional(_)) {
566                format!(
567                    "let {name}_core: Option<{core_path}> = {name}.map(|v| {{\n        \
568                     let json = serde_json::to_string(&v){err_conv}?;\n        \
569                     serde_json::from_str(&json){err_conv}\n    \
570                     }}).transpose()?;\n    "
571                )
572            } else {
573                format!(
574                    "let {name}_json = serde_json::to_string(&{name}){err_conv}?;\n    \
575                     let {name}_core: {core_path} = serde_json::from_str(&{name}_json){err_conv}?;\n    "
576                )
577            }
578        })
579        .collect();
580
581    // Build call args
582    let call_args: Vec<String> = func
583        .params
584        .iter()
585        .enumerate()
586        .map(|(idx, p)| {
587            if idx == bridge_param_idx {
588                return p.name.clone();
589            }
590            match &p.ty {
591                TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
592                    if p.optional {
593                        format!("{}.as_ref().map(|v| &v.inner)", p.name)
594                    } else {
595                        format!("&{}.inner", p.name)
596                    }
597                }
598                TypeRef::Named(_) => format!("{}_core", p.name),
599                TypeRef::Optional(inner) => {
600                    if let TypeRef::Named(n) = inner.as_ref() {
601                        if opaque_types.contains(n.as_str()) {
602                            format!("{}.as_ref().map(|v| &v.inner)", p.name)
603                        } else {
604                            format!("{}_core", p.name)
605                        }
606                    } else {
607                        p.name.clone()
608                    }
609                }
610                TypeRef::String | TypeRef::Char => {
611                    if p.is_ref {
612                        format!("&{}", p.name)
613                    } else {
614                        p.name.clone()
615                    }
616                }
617                _ => p.name.clone(),
618            }
619        })
620        .collect();
621    let call_args_str = call_args.join(", ");
622
623    let core_fn_path = {
624        let path = func.rust_path.replace('-', "_");
625        if path.starts_with(core_import) {
626            path
627        } else {
628            format!("{core_import}::{}", func.name)
629        }
630    };
631    let core_call = format!("{core_fn_path}({call_args_str})");
632
633    let return_wrap = match &func.return_type {
634        TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
635            format!("{name} {{ inner: std::sync::Arc::new(val) }}")
636        }
637        TypeRef::Named(_) => "val.into()".to_string(),
638        TypeRef::String | TypeRef::Bytes => "val.into()".to_string(),
639        _ => "val".to_string(),
640    };
641
642    let body = if func.error_type.is_some() {
643        if return_wrap == "val" {
644            format!("{bridge_wrap}\n    {serde_bindings}{core_call}{err_conv}")
645        } else {
646            format!("{bridge_wrap}\n    {serde_bindings}{core_call}.map(|val| {return_wrap}){err_conv}")
647        }
648    } else {
649        format!("{bridge_wrap}\n    {serde_bindings}{core_call}")
650    };
651
652    let func_name = &func.name;
653    let mut out = String::with_capacity(1024);
654    if func.error_type.is_some() {
655        writeln!(out, "#[allow(clippy::missing_errors_doc)]").ok();
656    }
657    writeln!(out, "pub fn {func_name}({params_str}) -> {ret} {{").ok();
658    writeln!(out, "    {body}").ok();
659    writeln!(out, "}}").ok();
660
661    out
662}