async_cffi_codegen/
rs_codegen.rs

1use std::{collections::HashMap, fmt::Write as _};
2
3use anyhow::{Context, Result, anyhow, bail};
4
5use handlebars::Handlebars;
6use heck::ToSnakeCase as _;
7use serde_json::json;
8use rs_schema::{
9    FunctionArgSchema, FunctionSchema, GenericParamSchema, TraitSchema, TypeSchema,
10};
11
12use crate::{
13    cffi_type_utils::{cffi_ty_stack_to_rust_cffi_ty, cffi_type_str_to_type_stack},
14    codegen::CodeGen,
15    rs_type_utils::{
16        annotation_transforms_for_arg, fn_arg_to_async_cffi_type_spec, substitute_generic_args,
17    },
18};
19
20/// Schema describing a generated Rust file containing structs and trait impls.
21#[derive(Debug)]
22pub struct RsFileSchema {
23    pub structs: Vec<RsStructSchema>,
24    pub trait_impls: Vec<RsTraitImplSchema>,
25}
26
27impl CodeGen for RsFileSchema {
28    /// Generate the Rust source for a file containing the configured structs and impls.
29    fn codegen(&self, indent: usize) -> String {
30        let mut output = String::new();
31        writeln!(
32            &mut output,
33            "// This file is autogenerated. Do not edit directly."
34        )
35        .unwrap();
36        writeln!(
37            &mut output,
38            "use std::{{ffi::{{c_ulong, c_void}}, sync::Arc}};\n"
39        )
40        .unwrap();
41        writeln!(
42            &mut output,
43            "use async_cffi::{{CffiFuture, CffiPointerBuffer, SafePtr}};\n"
44        )
45        .unwrap();
46        writeln!(&mut output, "use futures::future::BoxFuture;\n").unwrap();
47        writeln!(
48            &mut output,
49            "use n_observer::{{AnyArc, InnerObserverReceiver, Observable, Publisher}};\n"
50        )
51        .unwrap();
52        if self.structs.iter().any(|s| s.name == "CffiObservable") {
53            writeln!(&mut output, "use crate::CffiPublisher;\n").unwrap();
54        }
55        if self
56            .structs
57            .iter()
58            .any(|s| s.name == "CffiPublisher" || s.name == "CffiObservable")
59        {
60            writeln!(&mut output, "use crate::CffiInnerObserverReceiver;\n").unwrap();
61        }
62        for s in &self.structs {
63            writeln!(&mut output, "{}", s.codegen(indent)).unwrap();
64        }
65        writeln!(&mut output).unwrap();
66        for ti in &self.trait_impls {
67            writeln!(&mut output, "{}\n", ti.codegen(indent)).unwrap();
68        }
69        output
70    }
71}
72
73/// Schema describing a Rust struct to emit in generated bindings.
74#[derive(Debug)]
75pub struct RsStructSchema {
76    macros: Vec<String>,
77    name: String,
78    fields: Vec<RsFieldSchema>,
79}
80
81impl CodeGen for RsStructSchema {
82    /// Render the struct definition with the provided indentation level.
83    fn codegen(&self, indent: usize) -> String {
84        let pad = "    ".repeat(indent);
85        let mut output = String::new();
86        for m in &self.macros {
87            writeln!(&mut output, "{}{}", pad, m).unwrap();
88        }
89
90        writeln!(&mut output, "{}pub struct {} {{", pad, self.name).unwrap();
91        for field in &self.fields {
92            writeln!(&mut output, "{}", field.codegen(indent + 1)).unwrap();
93        }
94        write!(&mut output, "{}}}", pad).unwrap();
95        output
96    }
97}
98
99/// Schema describing a field within a generated struct.
100#[derive(Debug)]
101pub struct RsFieldSchema {
102    name: String,
103    field_type: TypeSchema,
104}
105
106impl CodeGen for RsFieldSchema {
107    /// Emit a single field line with indentation.
108    fn codegen(&self, indent: usize) -> String {
109        let pad = "    ".repeat(indent);
110        format!("{}pub {}: {},", pad, self.name, self.field_type.codegen(0))
111    }
112}
113
114/// Schema representing an impl block for a trait, including generated functions.
115#[derive(Debug)]
116pub struct RsTraitImplSchema {
117    pub trait_name: String,
118    pub impl_for: Option<String>,
119    pub generic_args: Vec<String>,
120    pub functions: Vec<FunctionSchema>,
121    pub unsafe_impl: bool,
122    pub comment_out: bool,
123}
124
125impl CodeGen for RsTraitImplSchema {
126    /// Render the impl block and nested function definitions.
127    fn codegen(&self, indent: usize) -> String {
128        let pad = "    ".repeat(indent);
129        let mut output = String::new();
130        if self.comment_out {
131            writeln!(&mut output, "{}/*", pad).unwrap();
132        }
133        let unsafe_str = if self.unsafe_impl { "unsafe " } else { "" };
134        let impl_for_str = if let Some(impl_for) = &self.impl_for {
135            format!("for {} ", impl_for)
136        } else {
137            String::new()
138        };
139        let generic_args_str = if self.generic_args.len() == 0 {
140            String::new()
141        } else {
142            let args = self.generic_args.join(", ");
143            format!("<{}>", args)
144        };
145        writeln!(
146            &mut output,
147            "{}{}impl {}{} {}{{",
148            pad, unsafe_str, self.trait_name, generic_args_str, impl_for_str
149        )
150        .unwrap();
151        for func in &self.functions {
152            let funct = func.codegen(indent + 1);
153            funct
154                .split_inclusive('\n')
155                .for_each(|line| writeln!(&mut output, "{}", line).unwrap());
156        }
157        write!(&mut output, "{}}}", pad).unwrap();
158        if self.comment_out {
159            writeln!(&mut output, "\n{}*/", pad).unwrap();
160        }
161        output
162    }
163}
164
165impl CodeGen for FunctionSchema {
166    /// Render the function schema by prefixing each line with the desired indentation.
167    fn codegen(&self, indent: usize) -> String {
168        let pad = "    ".repeat(indent);
169        let mut output = String::new();
170        format!("{}", self)
171            .split_inclusive('\n')
172            .for_each(|line| write!(&mut output, "{}{}", pad, line).unwrap());
173        output
174    }
175}
176
177/// Collection of transforms applied to an argument when converting to/from CFFI.
178struct FnArgTransforms {
179    pub transforms: Option<String>,
180    pub returns_safe_ptr: bool,
181}
182
183/// Convert a trait schema into the Rust structures needed for async CFFI bindings.
184pub fn trait_to_async_cffi_schema(
185    trait_schema: &TraitSchema,
186    supertrait_schemas: &HashMap<String, TraitSchema>,
187) -> Result<RsFileSchema> {
188    let cffi_struct_schema = create_cffi_struct_schema(trait_schema)?;
189
190    let supertrait_impl_schemas = trait_schema
191        .supertraits
192        .iter()
193        .filter(|st| st.ty != "Send" && st.ty != "Sync")
194        .map(|st| {
195            supertrait_schemas
196                .get(&st.ty)
197                .with_context(|| format!("Missing supertrait schema: {}", st.ty))
198        })
199        .collect::<Result<Vec<_>, _>>()?
200        .into_iter()
201        .map(|st| create_supertrait_impl(st, &cffi_struct_schema))
202        .collect::<Result<Vec<_>, _>>()?;
203
204    let trait_impl_schema =
205        trait_to_async_cffi_trait_impl_schema(trait_schema, &cffi_struct_schema)?;
206
207    let send_impl_schema = RsTraitImplSchema {
208        trait_name: "Send".to_string(),
209        impl_for: Some(cffi_struct_schema.name.clone()),
210        generic_args: vec![],
211        functions: vec![],
212        unsafe_impl: true,
213        comment_out: false,
214    };
215
216    let sync_impl_schema = RsTraitImplSchema {
217        trait_name: "Sync".to_string(),
218        impl_for: Some(cffi_struct_schema.name.clone()),
219        generic_args: vec![],
220        functions: vec![],
221        unsafe_impl: true,
222        comment_out: false,
223    };
224
225    let cffi_struct_impls = cffi_struct_impls(trait_schema, &cffi_struct_schema)?;
226
227    let dyn_rust_trait_to_cffi = RsTraitImplSchema {
228        trait_name: "From".to_string(),
229        impl_for: Some(cffi_struct_schema.name.clone()),
230        functions: vec![trait_schema_to_from_impl(trait_schema)?],
231        generic_args: vec![format!(
232            "&dyn {}",
233            get_trait_name_w_subbed_generics(trait_schema)?
234        )],
235        unsafe_impl: false,
236        comment_out: false,
237    };
238
239    Ok(RsFileSchema {
240        structs: vec![cffi_struct_schema],
241        trait_impls: {
242            let mut trait_impls = supertrait_impl_schemas;
243            trait_impls.extend(vec![
244                trait_impl_schema,
245                send_impl_schema,
246                sync_impl_schema,
247                cffi_struct_impls,
248                dyn_rust_trait_to_cffi,
249            ]);
250            trait_impls
251        },
252    })
253}
254
255fn cffi_struct_impls(
256    trait_schema: &TraitSchema,
257    cffi_struct_schema: &RsStructSchema,
258) -> Result<RsTraitImplSchema> {
259    let as_supertrait_fns = trait_schema
260        .supertraits
261        .iter()
262        .filter(|st| st.ty != "Send" && st.ty != "Sync")
263        .map(|st| create_as_supertrait_fn(st))
264        .collect::<Result<Vec<_>, _>>()?;
265    let trait_cffi_impls = trait_schema
266        .functions
267        .iter()
268        .map(|func| trait_fn_to_cffi_c_fn(func, trait_schema, &trait_schema.generics))
269        .collect::<Result<Vec<_>, _>>()?;
270    Ok(RsTraitImplSchema {
271        trait_name: cffi_struct_schema.name.clone(),
272        impl_for: None,
273        generic_args: vec![],
274        functions: {
275            let mut functions = as_supertrait_fns;
276            functions.extend(trait_cffi_impls);
277            functions
278        },
279        unsafe_impl: false,
280        comment_out: false,
281    })
282}
283
284fn create_as_supertrait_fn(supertrait: &TypeSchema) -> Result<FunctionSchema> {
285    Ok(FunctionSchema {
286        name: format!("as_{}", supertrait.ty.to_snake_case()),
287        args: vec![FunctionArgSchema {
288            name: "self".to_string(),
289            ty: None,
290            annotations: None,
291        }],
292        return_type: TypeSchema {
293            ty: format!("&dyn {}", supertrait.ty),
294            generic_ty_args: supertrait.generic_ty_args.clone(),
295        },
296        body: Some(format!("&self.cffi_{}", supertrait.ty.to_snake_case())),
297        extern_layout: None,
298        annotations: None,
299    })
300}
301
302fn create_supertrait_impl(
303    supertrait: &TraitSchema,
304    cffi_struct_schema: &RsStructSchema,
305) -> Result<RsTraitImplSchema> {
306    let functions = supertrait
307        .functions
308        .iter()
309        .map(|func| supertrait_fn_to_async_cffi_fn(supertrait, func, &supertrait.generics))
310        .collect::<Result<Vec<_>, _>>()?;
311    Ok(RsTraitImplSchema {
312        trait_name: supertrait.name.clone(),
313        impl_for: Some(cffi_struct_schema.name.clone()),
314        generic_args: supertrait
315            .generics
316            .iter()
317            .map(|p| {
318                p.annotations
319                    .as_ref()
320                    .and_then(|a| a.cffi_type.as_ref())
321                    .ok_or_else(|| {
322                        anyhow!("Missing cffi_type annotation for trait generic argument")
323                    })
324                    .and_then(|cffi_ty| {
325                        cffi_type_str_to_type_stack(cffi_ty)
326                            .and_then(|stack| cffi_ty_stack_to_rust_cffi_ty(&stack))
327                    })
328            })
329            .collect::<Result<Vec<_>, _>>()?,
330        functions,
331        unsafe_impl: false,
332        comment_out: false,
333    })
334}
335
336fn trait_to_async_cffi_trait_impl_schema(
337    trait_schema: &TraitSchema,
338    cffi_struct_schema: &RsStructSchema,
339) -> Result<RsTraitImplSchema> {
340    let functions = trait_schema
341        .functions
342        .iter()
343        .map(|func| trait_fn_to_async_cffi_fn(func, &trait_schema.generics))
344        .collect::<Result<Vec<_>, _>>()?;
345    Ok(RsTraitImplSchema {
346        trait_name: trait_schema.name.clone(),
347        impl_for: Some(cffi_struct_schema.name.clone()),
348        generic_args: cffi_substituted_trait_generic_arg_types(trait_schema)?,
349        functions,
350        unsafe_impl: false,
351        comment_out: false,
352    })
353}
354
355fn cffi_substituted_trait_generic_arg_types(trait_schema: &TraitSchema) -> Result<Vec<String>> {
356    Ok(trait_schema
357        .generics
358        .iter()
359        .map(|p| {
360            p.annotations
361                .as_ref()
362                .and_then(|a| a.cffi_type.as_ref())
363                .ok_or_else(|| anyhow!("Missing cffi_type annotation for trait generic argument"))
364                .and_then(|cffi_ty| {
365                    cffi_type_str_to_type_stack(cffi_ty)
366                        .and_then(|stack| cffi_ty_stack_to_rust_cffi_ty(&stack))
367                })
368        })
369        .collect::<Result<Vec<_>, _>>()?)
370}
371
372/// Build the CFFI struct schema that mirrors the provided Rust trait.
373fn create_cffi_struct_schema(trait_schema: &TraitSchema) -> Result<RsStructSchema> {
374    let macros = vec![
375        "#[repr(C)]".to_string(),
376        "#[derive(Debug, Clone)]".to_string(),
377    ];
378    let name = format!("Cffi{}", trait_schema.name);
379
380    let supertrait_fields = trait_schema
381        .supertraits
382        .iter()
383        .filter(|st| st.ty != "Send" && st.ty != "Sync")
384        .map(|st| RsFieldSchema {
385            name: format!("cffi_{}", st.ty.to_snake_case()),
386            field_type: TypeSchema::new_simple(format!("Cffi{}", st.ty)),
387        })
388        .collect::<Vec<_>>();
389
390    let trait_fn_fields = trait_schema
391        .functions
392        .iter()
393        .map(|func| {
394            func.args
395                .iter()
396                .map(arg_to_field_type)
397                .collect::<Result<Vec<TypeSchema>>>()
398                .map(|args| RsFieldSchema {
399                    name: format!("{}_fut", func.name),
400                    field_type: TypeSchema::new_simple(format!(
401                        "extern \"C\" fn({}) -> *const c_void",
402                        args.into_iter()
403                            .map(|ty| ty.codegen(0))
404                            .collect::<Vec<_>>()
405                            .join(", ")
406                    )),
407                })
408        })
409        .collect::<Result<Vec<RsFieldSchema>, _>>()?;
410
411    let fields = {
412        let mut fs = vec![RsFieldSchema {
413            name: "self_ptr".to_string(),
414            field_type: TypeSchema::new_simple("*const c_void".to_string()),
415        }];
416        fs.extend(supertrait_fields);
417        fs.extend(trait_fn_fields);
418        fs
419    };
420
421    let struct_schema = RsStructSchema {
422        macros,
423        name,
424        fields,
425    };
426
427    Ok(struct_schema)
428}
429
430fn supertrait_fn_to_async_cffi_fn(
431    supertrait: &TraitSchema,
432    func: &FunctionSchema,
433    generics: &Vec<GenericParamSchema>,
434) -> Result<FunctionSchema> {
435    let ret_substituted = substitute_generic_args(&func.return_type, generics)?;
436    Ok(FunctionSchema {
437        name: func.name.clone(),
438        args: func
439            .args
440            .iter()
441            .map(|arg| FunctionArgSchema {
442                name: arg.name.clone(),
443                ty: arg.ty.clone(),
444                annotations: None,
445            })
446            .collect(),
447        return_type: ret_substituted,
448        body: Some(supertrait_fn_to_async_cffi_fn_body(supertrait, func)?),
449        extern_layout: None,
450        annotations: None,
451    })
452}
453
454/// Determine the generated field type for a function argument within the CFFI struct.
455fn arg_to_field_type(arg: &FunctionArgSchema) -> Result<TypeSchema> {
456    let cffi_type_stack = fn_arg_to_async_cffi_type_spec(arg)?;
457    let cffi_type = cffi_type_stack
458        .into_iter()
459        .next()
460        .and_then(|ty| ty.explicit_type);
461
462    Ok(TypeSchema {
463        ty: cffi_type.unwrap_or("*const c_void".to_string()),
464        generic_ty_args: vec![],
465    })
466}
467
468/// Convert a trait function signature into the async CFFI-facing function schema.
469fn trait_fn_to_async_cffi_fn(
470    func: &FunctionSchema,
471    generics: &Vec<GenericParamSchema>,
472) -> Result<FunctionSchema> {
473    let ret_substituted = substitute_generic_args(&func.return_type, generics)?;
474    Ok(FunctionSchema {
475        name: func.name.clone(),
476        args: func
477            .args
478            .iter()
479            .map(|arg| FunctionArgSchema {
480                name: arg.name.clone(),
481                ty: arg.ty.clone(),
482                annotations: None,
483            })
484            .collect(),
485        return_type: ret_substituted,
486        body: Some(trait_fn_to_async_cffi_fn_body(func, generics)?),
487        extern_layout: None,
488        annotations: None,
489    })
490}
491
492/// Convert a trait function into the C-compatible trampoline function schema.
493fn trait_fn_to_cffi_c_fn(
494    func: &FunctionSchema,
495    trait_schema: &TraitSchema,
496    generics: &Vec<GenericParamSchema>,
497) -> Result<FunctionSchema> {
498    Ok(FunctionSchema {
499        name: format!("{}_fut_impl", func.name),
500        args: func
501            .args
502            .iter()
503            .map(|a| arg_to_field_type(a).map(|s| (a, s)))
504            .collect::<Result<Vec<_>, _>>()?
505            .into_iter()
506            .map(|(a, at)| FunctionArgSchema {
507                name: if a.name == "self" {
508                    "self_ptr".to_string()
509                } else {
510                    a.name.clone()
511                },
512                ty: Some(at.clone()),
513                annotations: None,
514            })
515            .collect(),
516        return_type: TypeSchema {
517            ty: "*const c_void".to_string(),
518            generic_ty_args: vec![],
519        },
520        body: Some(trait_fn_to_cffi_c_fn_body(func, trait_schema, generics)?),
521        extern_layout: Some("C".to_string()),
522        annotations: None,
523    })
524}
525
526/// Generate the body for the C-compatible trampoline that wraps a trait async function.
527fn trait_fn_to_cffi_c_fn_body(
528    func: &FunctionSchema,
529    trait_schema: &TraitSchema,
530    generics: &Vec<GenericParamSchema>,
531) -> Result<String> {
532    let mut fn_body = String::new();
533
534    if let Some(annotations) = &func.annotations {
535        if annotations.cffi_impl_no_op {
536            writeln!(fn_body, "// no-op").unwrap();
537            writeln!(
538                fn_body,
539                "CffiFuture::from_rust_future_boxed(async move {{}}).into_raw()"
540            )
541            .unwrap();
542            return Ok(fn_body);
543        }
544    }
545
546    let arg_transforms = func
547        .args
548        .iter()
549        .map(|arg| cffi_c_fn_arg_to_transforms(arg, trait_schema))
550        .collect::<Result<Vec<String>>>()?;
551
552    arg_transforms
553        .iter()
554        .try_for_each(|t| writeln!(fn_body, "{}\n", t))
555        .context("Failed to write argument transforms for CFFI trampoline")?;
556
557    // Get CffiFuture pointer for trait fn future
558    let ret_ty = &func.return_type;
559    if ret_ty.ty != "BoxFuture" && ret_ty.generic_ty_args.len() != 2 {
560        bail!("Only BoxFuture returning trait functions are supported");
561    }
562    let async_ret_ty = &ret_ty.generic_ty_args[1];
563    if async_ret_ty.ty == "()" {
564        // Call trait fn
565        writeln!(
566            fn_body,
567            "let fut = self_ref.{}({});",
568            func.name,
569            func.args
570                .iter()
571                .skip(1)
572                .map(|a| a.name.clone())
573                .collect::<Vec<String>>()
574                .join(", ")
575        )
576        .unwrap();
577
578        writeln!(
579            fn_body,
580            "CffiFuture::from_rust_future_boxed(fut).into_raw()"
581        )
582        .unwrap();
583    } else {
584        // Call trait fn
585        writeln!(
586            fn_body,
587            "let fut = Box::pin(async move {{\n    let ret = self_ref.{}({}).await;",
588            func.name,
589            func.args
590                .iter()
591                .skip(1)
592                .map(|a| a.name.clone())
593                .collect::<Vec<String>>()
594                .join(", ")
595        )
596        .unwrap();
597
598        let async_ret_ty_subbed = substitute_generic_args(async_ret_ty, generics)?;
599        writeln!(
600            fn_body,
601            "{}",
602            type_transforms_for_cffi_c_fn_return(&async_ret_ty_subbed, "ret")?
603        )
604        .unwrap();
605
606        writeln!(fn_body, "    SafePtr(ret)").unwrap();
607
608        writeln!(fn_body, "}});").unwrap();
609
610        writeln!(
611            fn_body,
612            "CffiFuture::from_rust_future_boxed(fut).into_raw()"
613        )
614        .unwrap();
615    }
616
617    Ok(fn_body)
618}
619
620/// Build transformation statements needed for each argument passed into a C trampoline.
621fn cffi_c_fn_arg_to_transforms(
622    arg: &FunctionArgSchema,
623    trait_schema: &TraitSchema,
624) -> Result<String> {
625    let target_type = fn_arg_to_async_cffi_type_spec(arg)?;
626    let target_type = target_type.into_iter().next().unwrap();
627
628    if let Some(arg_ty) = &arg.ty {
629        if let Some(explicit_type) = target_type.explicit_type.as_ref() {
630            match explicit_type.as_str() {
631                "CffiPointerBuffer" => {
632                    let mut transform = format!("let {} = {}\n", arg.name, arg.name);
633
634                    writeln!(transform, "    .as_slice()").unwrap();
635                    writeln!(transform, "    .iter()").unwrap();
636                    writeln!(transform, "    .copied()").unwrap();
637                    writeln!(transform, "    .map(|value| {{").unwrap();
638                    writeln!(transform, "        if !value.is_null() {{").unwrap();
639                    writeln!(transform, "            let value = SafePtr(value);").unwrap();
640                    writeln!(transform, "            Some(Arc::new(value) as AnyArc)").unwrap();
641                    writeln!(transform, "        }} else {{").unwrap();
642                    writeln!(transform, "            None").unwrap();
643                    writeln!(transform, "        }}").unwrap();
644                    writeln!(transform, "    }})").unwrap();
645                    writeln!(transform, "    .collect();").unwrap();
646
647                    Ok(transform)
648                }
649                "c_ulong" => {
650                    let transform = format!("let {} = {} as usize;", arg.name, arg.name);
651                    Ok(transform)
652                }
653                explicit_type => {
654                    if explicit_type.starts_with("Cffi") {
655                        let transform = format!("let {} = Box::new({});", arg.name, arg.name);
656                        Ok(transform)
657                    } else {
658                        Ok(String::new())
659                    }
660                }
661            }
662        } else {
663            if arg_ty.ty == "AnyArc" {
664                let transform = format!("let {} = Arc::new(SafePtr({}));", arg.name, arg.name);
665                Ok(transform)
666            } else {
667                Ok(String::new())
668            }
669        }
670    } else {
671        let mut transform = String::new();
672
673        writeln!(transform, "let self_ref = unsafe {{").unwrap();
674        writeln!(
675            transform,
676            "    (self_ptr as *const &(dyn {} + Send + Sync))",
677            get_trait_name_w_subbed_generics(trait_schema)?,
678        )
679        .unwrap();
680        writeln!(transform, "    .as_ref()").unwrap();
681        writeln!(transform, "    .expect(\"Self pointer cannot be null\")").unwrap();
682        writeln!(transform, "}};").unwrap();
683
684        Ok(transform)
685    }
686}
687
688fn get_trait_name_w_subbed_generics(trait_schema: &TraitSchema) -> Result<String> {
689    let generic_args = if trait_schema.generics.len() > 0 {
690        format!(
691            "<{}>",
692            cffi_substituted_trait_generic_arg_types(trait_schema)?.join(", ")
693        )
694    } else {
695        String::new()
696    };
697
698    Ok(format!("{}{}", trait_schema.name, generic_args))
699}
700
701/// Generate the async wrapper body for a trait function exposed over CFFI.
702fn trait_fn_to_async_cffi_fn_body(
703    func: &FunctionSchema,
704    generics: &Vec<GenericParamSchema>,
705) -> Result<String> {
706    let mut fn_body = String::new();
707
708    let arg_assertions = func
709        .args
710        .iter()
711        .map(fn_arg_to_assertions)
712        .collect::<Vec<String>>()
713        .join("");
714    fn_body.push_str(&arg_assertions);
715    fn_body.push('\n');
716
717    let arg_transforms = func
718        .args
719        .iter()
720        .map(fn_arg_to_transforms)
721        .collect::<Result<Vec<FnArgTransforms>>>()?;
722    let safe_ptr_args = arg_transforms
723        .iter()
724        .map(|ts| ts.returns_safe_ptr)
725        .collect::<Vec<_>>();
726    let arg_transforms = arg_transforms
727        .into_iter()
728        .map(|ts| ts.transforms.unwrap_or_default())
729        .collect::<Vec<_>>()
730        .join("");
731    fn_body.push_str(&arg_transforms);
732    fn_body.push('\n');
733
734    let async_fn_isolation = format!("let {}_fn = self.{}_fut;", func.name, func.name);
735    fn_body.push_str(&async_fn_isolation);
736    fn_body.push('\n');
737
738    let wrapped_self_ptr = "let self_ptr = SafePtr(self.self_ptr);";
739    fn_body.push_str(wrapped_self_ptr);
740    fn_body.push('\n');
741
742    let fn_async_block = async_block_for_trait_fn(func, safe_ptr_args, generics)?;
743    fn_body.push_str(&fn_async_block);
744    fn_body.push('\n');
745
746    Ok(fn_body)
747}
748
749fn supertrait_fn_to_async_cffi_fn_body(
750    supertrait: &TraitSchema,
751    func: &FunctionSchema,
752) -> Result<String> {
753    let args = func
754        .args
755        .iter()
756        .skip(1) // Exclude self
757        .map(|a| a.name.clone())
758        .collect::<Vec<_>>()
759        .join(", ");
760    Ok(format!(
761        "self.as_{}().{}({})",
762        supertrait.name.to_snake_case(),
763        func.name,
764        args,
765    ))
766}
767
768/// Construct the async block that invokes the C-side future and handles conversions.
769fn async_block_for_trait_fn(
770    func: &FunctionSchema,
771    safe_ptr_args: Vec<bool>,
772    generics: &Vec<GenericParamSchema>,
773) -> Result<String> {
774    let mut async_block = "Box::pin(async move {\n".to_string();
775    writeln!(&mut async_block, "let self_ptr = self_ptr;").unwrap();
776
777    func.args
778        .iter()
779        .skip(1)
780        .for_each(|arg| writeln!(&mut async_block, "let {} = {};", arg.name, arg.name).unwrap());
781
782    let cffi_call_args = func
783        .args
784        .iter()
785        .zip(safe_ptr_args.iter())
786        .skip(1)
787        .map(|(arg, is_safe_ptr)| {
788            let safe_ptr_access = if *is_safe_ptr { ".0" } else { "" };
789            format!(", {}{}", arg.name, safe_ptr_access)
790        })
791        .collect::<Vec<_>>()
792        .join("");
793
794    writeln!(
795        &mut async_block,
796        "let fut = ({}_fn)(self_ptr.0{});",
797        func.name, cffi_call_args
798    )
799    .unwrap();
800
801    writeln!(
802        &mut async_block,
803        "if fut.is_null() {{\n    panic!(\"C function returned null pointer\");\n}}"
804    )
805    .unwrap();
806
807    writeln!(&mut async_block, "let fut = unsafe {{\n  // Convert the raw pointer to a CffiFuture\n  (fut as *mut CffiFuture)\n  .as_mut()\n  .expect(\"CffiFuture cannot be null\")\n}};").unwrap();
808
809    let ret_ty = &func.return_type;
810    let outer_ty = &ret_ty.ty;
811    if outer_ty != "BoxFuture" {
812        bail!("Only BoxFuture returning trait functions currently supported");
813    }
814
815    if ret_ty.generic_ty_args.len() == 2 && &ret_ty.generic_ty_args[1].ty != "()" {
816        // Get and transform return value
817        writeln!(&mut async_block, "let ret = fut.await;").unwrap();
818        writeln!(&mut async_block, "assert!(!ret.is_null());").unwrap();
819
820        let async_return_ty = &ret_ty.generic_ty_args[1];
821        let async_return_ty = substitute_generic_args(async_return_ty, generics)?;
822
823        async_block.push_str(&type_transforms_for_cffi_fut_ptr(&async_return_ty, "ret")?);
824
825        // Return
826        writeln!(&mut async_block, "ret").unwrap();
827    } else {
828        // No return value from async block
829        writeln!(&mut async_block, "fut.await;").unwrap();
830    }
831
832    writeln!(&mut async_block, "}})").unwrap();
833
834    Ok(async_block)
835}
836
837/// Produce runtime assertions for a function argument based on its annotations.
838fn fn_arg_to_assertions(arg: &FunctionArgSchema) -> String {
839    if let Some(annotations) = &arg.annotations {
840        if let Some(assert_len) = annotations.assert_len {
841            return format!(
842                "if {}.len() != {} {{\n panic!(\"Expected {} to have length {}\"); \n}} \n",
843                arg.name, assert_len, arg.name, assert_len
844            );
845        }
846    }
847    "".to_string()
848}
849
850/// Create the transformation blocks needed to pass a Rust argument across the FFI boundary.
851fn fn_arg_to_transforms(arg: &FunctionArgSchema) -> Result<FnArgTransforms> {
852    let mut transforms = String::new();
853    let target_type = fn_arg_to_async_cffi_type_spec(arg)?;
854
855    let annotation_transforms = annotation_transforms_for_arg(arg, target_type)?;
856    annotation_transforms.map(|t| transforms.push_str(&t));
857
858    let type_transforms = type_transforms_for_arg(arg)?;
859    type_transforms.transforms.map(|t| transforms.push_str(&t));
860
861    Ok(FnArgTransforms {
862        transforms: Some(transforms),
863        returns_safe_ptr: type_transforms.returns_safe_ptr,
864    })
865}
866
867/// Derive transforms for an argument solely from its Rust type schema.
868fn type_transforms_for_arg(arg: &FunctionArgSchema) -> Result<FnArgTransforms> {
869    let mut returns_safe_ptr = true;
870    let type_transform = match arg.ty.as_ref() {
871        None => None,
872        Some(fn_arg_type_schema) => {
873            let reg = Handlebars::new();
874            let transform_block = render_fn_arg_type_schema(
875                &reg,
876                &fn_arg_type_schema,
877                format!("let {} = {{{{{{next}}}}}};\n", arg.name),
878                false,
879                &mut returns_safe_ptr,
880                &arg.name,
881            )?;
882
883            Some(transform_block)
884        }
885    };
886
887    Ok(FnArgTransforms {
888        transforms: type_transform,
889        returns_safe_ptr,
890    })
891}
892
893fn render_fn_arg_type_schema(
894    reg: &Handlebars,
895    type_schema: &TypeSchema,
896    mut transform_block: String,
897    current_is_box: bool,
898    returns_safe_ptr: &mut bool,
899    arg_name: &str,
900) -> Result<String> {
901    let mut next_is_box = current_is_box;
902
903    let next_schema = match type_schema.ty.as_str() {
904        "Option" => {
905            let template_string = format!(
906                "if let Some({}) = {} {{\n    {{{{{{next}}}}}}\n}} else {{\n    SafePtr(std::ptr::null())\n}}",
907                arg_name, arg_name,
908            );
909
910            transform_block =
911                reg.render_template(&transform_block, &json!({"next": template_string}))?;
912
913            type_schema.generic_ty_args.get(0)
914        }
915        "Vec" => {
916            // TODO: Currently assuming Vec always expressed as CffiPointerBuffer
917            let template_string = format!(
918                "CffiPointerBuffer::from_slice({}\n    .into_iter()\n    .map(|{}| {{\n        ({{{{{{next}}}}}}).0\n}})\n    .collect::<Box<[_]>>())",
919                arg_name, arg_name
920            );
921
922            *returns_safe_ptr = false;
923            transform_block =
924                reg.render_template(&transform_block, &json!({"next": template_string}))?;
925
926            type_schema.generic_ty_args.get(0)
927        }
928        "AnyArc" => {
929            let value_string = format!(
930                "*{}.downcast::<SafePtr>().expect(\"{} must be SafePtr\")",
931                arg_name, arg_name
932            );
933
934            transform_block =
935                reg.render_template(&transform_block, &json!({"next": value_string}))?;
936
937            type_schema.generic_ty_args.get(0)
938        }
939        "Arc" => {
940            let value_string = format!("SafePtr(Arc::into_raw({}) as *const c_void)", arg_name);
941
942            transform_block =
943                reg.render_template(&transform_block, &json!({"next": value_string}))?;
944
945            type_schema.generic_ty_args.get(0)
946        }
947        "Box" => {
948            next_is_box = true;
949            type_schema.generic_ty_args.get(0)
950        }
951        "usize" => {
952            let transform = format!("{} as c_ulong", arg_name);
953            transform_block = reg.render_template(&transform_block, &json!({"next": transform}))?;
954            *returns_safe_ptr = false;
955
956            type_schema.generic_ty_args.get(0)
957        }
958        fn_arg_type_elem => {
959            if current_is_box && fn_arg_type_elem.starts_with("dyn ") {
960                let mut transform = "{".to_string();
961                writeln!(
962                    transform,
963                    "    let cffi_{} = (*{}).into();",
964                    arg_name, arg_name
965                )
966                .unwrap();
967                writeln!(
968                    transform,
969                    "    Box::leak({}); // Leak to keep alive",
970                    arg_name
971                )
972                .unwrap();
973                writeln!(transform, "    cffi_{}", arg_name).unwrap();
974                writeln!(transform, "}}").unwrap();
975
976                transform_block =
977                    reg.render_template(&transform_block, &json!({"next": transform}))?;
978                *returns_safe_ptr = false;
979            }
980
981            type_schema.generic_ty_args.get(0)
982        }
983    };
984
985    if let Some(inner_schema) = next_schema {
986        render_fn_arg_type_schema(
987            reg,
988            inner_schema,
989            transform_block,
990            next_is_box,
991            returns_safe_ptr,
992            arg_name,
993        )
994    } else {
995        Ok(reg.render_template(&transform_block, &json!({"next": arg_name}))?)
996    }
997}
998
999/// Build transform code for translating a CFFI future pointer into Rust types.
1000fn type_transforms_for_cffi_fut_ptr(return_ty: &TypeSchema, retvar: &str) -> Result<String> {
1001    let reg = Handlebars::new();
1002    render_cffi_fut_ptr_transforms(
1003        &reg,
1004        return_ty,
1005        format!("let {} = {{{{{{next}}}}}};\n", retvar),
1006        retvar,
1007    )
1008}
1009
1010/// Build transform code for mapping Rust async return values into C-compatible representations.
1011fn type_transforms_for_cffi_c_fn_return(return_ty: &TypeSchema, retvar: &str) -> Result<String> {
1012    let reg = Handlebars::new();
1013    render_cffi_c_fn_return_transforms(
1014        &reg,
1015        return_ty,
1016        format!("let {} = {{{{{{next}}}}}};\n", retvar),
1017        retvar,
1018    )
1019}
1020
1021fn render_cffi_fut_ptr_transforms(
1022    reg: &Handlebars,
1023    type_schema: &TypeSchema,
1024    mut transform_block: String,
1025    retvar: &str,
1026) -> Result<String> {
1027    let next_schema = match type_schema.ty.as_str() {
1028        "Option" => {
1029            let transform = format!(
1030                "{} as *const *const c_void;\nlet {} = *unsafe {{ {}.as_ref().unwrap() }};\nlet {} = if {}.is_null() {{\nNone\n}} else {{\nSome({{{{{{next}}}}}})\n}};\n",
1031                retvar, retvar, retvar, retvar, retvar,
1032            );
1033            transform_block = reg.render_template(&transform_block, &json!({"next": transform}))?;
1034
1035            type_schema.generic_ty_args.get(0)
1036        }
1037        "AnyArc" => {
1038            let transform = format!("Arc::new(SafePtr({})) as AnyArc", retvar);
1039            transform_block = reg.render_template(&transform_block, &json!({"next": transform}))?;
1040
1041            type_schema.generic_ty_args.get(0)
1042        }
1043        "Arc" => {
1044            let transform = format!("Arc::new(SafePtr({}))", retvar);
1045            transform_block = reg.render_template(&transform_block, &json!({"next": transform}))?;
1046
1047            type_schema.generic_ty_args.get(0)
1048        }
1049        "SafePtr" => type_schema.generic_ty_args.get(0),
1050        ty => {
1051            bail!("Unsupported type for return type transform: {}", ty);
1052        }
1053    };
1054
1055    if let Some(inner_schema) = next_schema {
1056        render_cffi_fut_ptr_transforms(reg, inner_schema, transform_block, retvar)
1057    } else {
1058        Ok(reg.render_template(&transform_block, &json!({"next": retvar}))?)
1059    }
1060}
1061
1062fn render_cffi_c_fn_return_transforms(
1063    reg: &Handlebars,
1064    type_schema: &TypeSchema,
1065    mut transform_block: String,
1066    retvar: &str,
1067) -> Result<String> {
1068    let next_schema = match type_schema.ty.as_str() {
1069        "Option" => {
1070            let transform = format!(
1071                "{}\n    .map(|{}| {{{{{{next}}}}}})\n    .unwrap_or(std::ptr::null())",
1072                retvar, retvar,
1073            );
1074            transform_block = reg.render_template(&transform_block, &json!({"next": transform}))?;
1075
1076            type_schema.generic_ty_args.get(0)
1077        }
1078        "AnyArc" => {
1079            let transform = format!("{}.downcast::<SafePtr>().unwrap().0", retvar);
1080            transform_block = reg.render_template(&transform_block, &json!({"next": transform}))?;
1081
1082            type_schema.generic_ty_args.get(0)
1083        }
1084        "Arc" => {
1085            let transform = format!("{{let {} = *{};\n{{{{{{next}}}}}}}}", retvar, retvar);
1086            transform_block = reg.render_template(&transform_block, &json!({"next": transform}))?;
1087
1088            type_schema.generic_ty_args.get(0)
1089        }
1090        "SafePtr" => {
1091            let transform = format!("{}.0", retvar);
1092            transform_block = reg.render_template(&transform_block, &json!({"next": transform}))?;
1093
1094            type_schema.generic_ty_args.get(0)
1095        }
1096        ty => {
1097            bail!("Unsupported type for c fn return type transform: {}", ty);
1098        }
1099    };
1100
1101    if let Some(inner_schema) = next_schema {
1102        render_cffi_c_fn_return_transforms(reg, inner_schema, transform_block, retvar)
1103    } else {
1104        Ok(reg.render_template(&transform_block, &json!({"next": retvar}))?)
1105    }
1106}
1107
1108/// Create a From implementation that builds the generated CFFI struct from a trait object.
1109fn trait_schema_to_from_impl(trait_schema: &TraitSchema) -> Result<FunctionSchema> {
1110    Ok(FunctionSchema {
1111        name: "from".to_string(),
1112        args: vec![FunctionArgSchema {
1113            name: "inner".to_string(),
1114            ty: Some(TypeSchema {
1115                ty: format!("&dyn {}", get_trait_name_w_subbed_generics(trait_schema)?),
1116                generic_ty_args: vec![],
1117            }),
1118            annotations: None,
1119        }],
1120        return_type: TypeSchema {
1121            ty: "Self".to_string(),
1122            generic_ty_args: vec![],
1123        },
1124        body: Some(trait_schema_to_from_impl_body(trait_schema)?),
1125        extern_layout: None,
1126        annotations: None,
1127    })
1128}
1129
1130/// Render the body for the generated From implementation of a trait into its CFFI struct.
1131fn trait_schema_to_from_impl_body(trait_schema: &TraitSchema) -> Result<String> {
1132    let mut fn_body = String::new();
1133
1134    writeln!(fn_body, "Cffi{} {{", trait_schema.name).unwrap();
1135
1136    // self_ptr
1137    writeln!(
1138        fn_body,
1139        "    // Wrap the fat pointer in a Box to get a stable address"
1140    )
1141    .unwrap();
1142    writeln!(fn_body, "    // Leak it to keep [just the wrapper] alive").unwrap();
1143    writeln!(
1144        fn_body,
1145        "    self_ptr: Box::into_raw(Box::new(inner)) as *const c_void,"
1146    )
1147    .unwrap();
1148
1149    // Supertrait structs
1150    for supertrait in trait_schema
1151        .supertraits
1152        .iter()
1153        .filter(|st| st.ty != "Send" && st.ty != "Sync")
1154    {
1155        writeln!(fn_body, "    cffi_{}: {{\n", supertrait.ty.to_snake_case()).unwrap();
1156        writeln!(
1157            fn_body,
1158            "        let {} = Box::new(inner as &dyn {});",
1159            supertrait.ty.to_snake_case(),
1160            supertrait.ty
1161        )
1162        .unwrap();
1163        writeln!(
1164            fn_body,
1165            "        let ret: Cffi{} = (*{}).into();",
1166            supertrait.ty,
1167            supertrait.ty.to_snake_case()
1168        )
1169        .unwrap();
1170        writeln!(
1171            fn_body,
1172            "        Box::leak({}); // Leak to keep alive",
1173            supertrait.ty.to_snake_case()
1174        )
1175        .unwrap();
1176        writeln!(fn_body, "        ret").unwrap();
1177        writeln!(fn_body, "    }},").unwrap();
1178    }
1179
1180    // Trait functions
1181    for func in &trait_schema.functions {
1182        writeln!(
1183            fn_body,
1184            "    {}_fut: Cffi{}::{}_fut_impl,",
1185            func.name, trait_schema.name, func.name
1186        )
1187        .unwrap();
1188    }
1189
1190    writeln!(fn_body, "}}").unwrap();
1191
1192    Ok(fn_body)
1193}
1194
1195/// Unit tests covering the async CFFI Rust code generation helpers.
1196#[cfg(test)]
1197mod tests {
1198    use crate::cffi_type_utils::CffiTypeElementSpec;
1199    use rs_schema::FnArgAnnotations;
1200
1201    use super::*;
1202
1203    /// Verify schema generation when a trait has no functions.
1204    #[test]
1205    fn test_trait_to_async_cffi_schema_with_no_functions() {
1206        let trait_schema = TraitSchema {
1207            name: "TestTrait".to_string(),
1208            functions: vec![],
1209            generics: vec![],
1210            supertraits: vec![],
1211        };
1212
1213        let result = trait_to_async_cffi_schema(&trait_schema, &HashMap::new()).unwrap();
1214
1215        // Verify struct schema
1216        assert_eq!(result.structs.len(), 1);
1217        assert_eq!(result.structs[0].name, "CffiTestTrait");
1218        // Should have only self_ptr field when no functions
1219        assert_eq!(result.structs[0].fields.len(), 1);
1220        assert_eq!(result.structs[0].fields[0].name, "self_ptr");
1221
1222        // Verify trait impls (trait impl, Send, Sync)
1223        assert_eq!(result.trait_impls.len(), 5);
1224        assert_eq!(result.trait_impls[0].trait_name, "TestTrait");
1225        assert_eq!(
1226            result.trait_impls[0].impl_for,
1227            Some("CffiTestTrait".to_string())
1228        );
1229        assert!(!result.trait_impls[0].unsafe_impl);
1230
1231        assert_eq!(result.trait_impls[1].trait_name, "Send");
1232        assert!(result.trait_impls[1].unsafe_impl);
1233
1234        assert_eq!(result.trait_impls[2].trait_name, "Sync");
1235        assert!(result.trait_impls[2].unsafe_impl);
1236    }
1237
1238    /// Verify schema generation when a trait includes async functions.
1239    #[test]
1240    fn test_trait_to_async_cffi_schema_with_functions() {
1241        let trait_schema = TraitSchema {
1242            name: "MyTrait".to_string(),
1243            functions: vec![
1244                FunctionSchema {
1245                    name: "method_one".to_string(),
1246                    args: vec![FunctionArgSchema {
1247                        name: "arg1".to_string(),
1248                        ty: Some(TypeSchema {
1249                            ty: "i32".to_string(),
1250                            generic_ty_args: vec![],
1251                        }),
1252                        annotations: None,
1253                    }],
1254                    return_type: TypeSchema {
1255                        ty: "BoxFuture".to_string(),
1256                        generic_ty_args: vec![
1257                            TypeSchema::new_simple("'_".to_string()),
1258                            TypeSchema {
1259                                ty: "Option".to_string(),
1260                                generic_ty_args: vec![TypeSchema::new_simple("AnyArc".to_string())],
1261                            },
1262                        ],
1263                    },
1264                    body: None,
1265                    extern_layout: None,
1266                    annotations: None,
1267                },
1268                FunctionSchema {
1269                    name: "method_two".to_string(),
1270                    args: vec![],
1271                    return_type: TypeSchema {
1272                        ty: "BoxFuture".to_string(),
1273                        generic_ty_args: vec![
1274                            TypeSchema::new_simple("'_".to_string()),
1275                            TypeSchema::new_simple("()".to_string()),
1276                        ],
1277                    },
1278                    body: None,
1279                    extern_layout: None,
1280                    annotations: None,
1281                },
1282            ],
1283            generics: vec![],
1284            supertraits: vec![],
1285        };
1286
1287        let result = trait_to_async_cffi_schema(&trait_schema, &HashMap::new()).unwrap();
1288
1289        // Verify struct schema
1290        assert_eq!(result.structs.len(), 1);
1291        assert_eq!(result.structs[0].name, "CffiMyTrait");
1292        // Should have self_ptr + 2 function fields
1293        assert_eq!(result.structs[0].fields.len(), 3);
1294        assert_eq!(result.structs[0].fields[0].name, "self_ptr");
1295        assert_eq!(result.structs[0].fields[1].name, "method_one_fut");
1296        assert_eq!(result.structs[0].fields[2].name, "method_two_fut");
1297
1298        // Verify field types for function pointers
1299        assert_eq!(
1300            result.structs[0].fields[1].field_type.ty,
1301            "extern \"C\" fn(*const c_void) -> *const c_void"
1302        );
1303        assert_eq!(
1304            result.structs[0].fields[2].field_type.ty,
1305            "extern \"C\" fn() -> *const c_void"
1306        );
1307
1308        // Verify trait impls
1309        assert_eq!(result.trait_impls.len(), 5);
1310        assert_eq!(result.trait_impls[0].trait_name, "MyTrait");
1311        assert_eq!(result.trait_impls[0].functions.len(), 2);
1312        assert_eq!(result.trait_impls[0].functions[0].name, "method_one");
1313        assert_eq!(result.trait_impls[0].functions[1].name, "method_two");
1314    }
1315
1316    /// Ensure Option arguments with AnyArc types produce transforms.
1317    #[test]
1318    fn test_fn_arg_to_transforms_basic() {
1319        let arg_schema = FunctionArgSchema {
1320            name: "arg1".to_string(),
1321            ty: Some(TypeSchema {
1322                ty: "Option".to_string(),
1323                generic_ty_args: vec![TypeSchema::new_simple("AnyArc".to_string())],
1324            }),
1325            annotations: None,
1326        };
1327
1328        fn_arg_to_transforms(&arg_schema).unwrap();
1329    }
1330
1331    /// Confirm transforms honor explicit cffi_type annotations for nested generics.
1332    #[test]
1333    fn test_fn_arg_to_transforms_with_type_annotation() {
1334        let arg_schema = FunctionArgSchema {
1335            name: "arg1".to_string(),
1336            ty: Some(TypeSchema {
1337                ty: "Vec".to_string(),
1338                generic_ty_args: vec![TypeSchema {
1339                    ty: "Option".to_string(),
1340                    generic_ty_args: vec![TypeSchema::new_simple("AnyArc".to_string())],
1341                }],
1342            }),
1343            annotations: Some({
1344                let mut annotations = FnArgAnnotations::new();
1345                annotations.cffi_type = Some("CffiPointerBuffer<opt_ptr<T>>".to_string());
1346                annotations
1347            }),
1348        };
1349
1350        fn_arg_to_transforms(&arg_schema).unwrap();
1351    }
1352
1353    /// Ensure Arc arguments are converted into SafePtr values.
1354    #[test]
1355    fn test_fn_arg_to_transforms_arc() {
1356        let arg_schema = FunctionArgSchema {
1357            name: "arg1".to_string(),
1358            ty: Some(TypeSchema {
1359                ty: "Arc".to_string(),
1360                generic_ty_args: vec![TypeSchema::new_simple("SomeType".to_string())],
1361            }),
1362            annotations: None,
1363        };
1364
1365        let out = fn_arg_to_transforms(&arg_schema).unwrap();
1366        let out = out.transforms.unwrap();
1367        assert!(out.contains("Arc::into_raw"));
1368        assert!(out.contains("SafePtr("));
1369    }
1370
1371    /// Ensure AnyArc arguments downcast to SafePtr.
1372    #[test]
1373    fn test_fn_arg_to_transforms_anyarc() {
1374        let arg_schema = FunctionArgSchema {
1375            name: "arg1".to_string(),
1376            ty: Some(TypeSchema::new_simple("AnyArc".to_string())),
1377            annotations: None,
1378        };
1379
1380        let out = fn_arg_to_transforms(&arg_schema).unwrap();
1381        let out = out.transforms.unwrap();
1382        assert!(out.contains("arg1.downcast::<SafePtr>()"));
1383        assert!(out.contains("let arg1 ="));
1384    }
1385
1386    /// Ensure Option<Arc> arguments translate into optional raw pointers.
1387    #[test]
1388    fn test_fn_arg_to_transforms_option_arc() {
1389        let arg_schema = FunctionArgSchema {
1390            name: "arg1".to_string(),
1391            ty: Some(TypeSchema {
1392                ty: "Option".to_string(),
1393                generic_ty_args: vec![TypeSchema {
1394                    ty: "Arc".to_string(),
1395                    generic_ty_args: vec![TypeSchema::new_simple("T".to_string())],
1396                }],
1397            }),
1398            annotations: None,
1399        };
1400
1401        let out = fn_arg_to_transforms(&arg_schema).unwrap();
1402        let out = out.transforms.unwrap();
1403        assert!(out.contains("if let Some(arg1) = arg1"));
1404        assert!(out.contains("Arc::into_raw"));
1405    }
1406
1407    /// Confirm Vec arguments generate placeholder transforms for flattening.
1408    #[test]
1409    fn test_type_transforms_for_arg_vec() {
1410        let arg_schema = FunctionArgSchema {
1411            name: "data".to_string(),
1412            ty: Some(TypeSchema {
1413                ty: "Vec".to_string(),
1414                generic_ty_args: vec![TypeSchema::new_simple("i32".to_string())],
1415            }),
1416            annotations: None,
1417        };
1418
1419        let out = type_transforms_for_arg(&arg_schema).unwrap();
1420        let out = out.transforms;
1421        // Vec types should generate transform block with placeholder for flattening
1422        assert!(out.is_some());
1423        let transform = out.unwrap();
1424        assert!(transform.contains("let data = "));
1425    }
1426
1427    /// Check struct codegen indentation and field emission.
1428    #[test]
1429    fn test_codegen_struct_indentation() {
1430        let struct_schema = RsStructSchema {
1431            macros: vec!["#[repr(C)]".to_string()],
1432            name: "Example".to_string(),
1433            fields: vec![RsFieldSchema {
1434                name: "value".to_string(),
1435                field_type: TypeSchema::new_simple("i32".to_string()),
1436            }],
1437        };
1438
1439        let code = struct_schema.codegen(1);
1440        let lines: Vec<_> = code.lines().collect();
1441        assert!(lines.iter().all(|line| line.starts_with("    ")));
1442        assert!(code.contains("pub struct Example"));
1443        assert!(code.contains("pub value: i32"));
1444    }
1445
1446    /// Check trait impl codegen indentation and formatting.
1447    #[test]
1448    fn test_codegen_trait_impl_indentation() {
1449        let function_schema = FunctionSchema {
1450            name: "example".to_string(),
1451            args: vec![],
1452            return_type: TypeSchema::new_simple("()".to_string()),
1453            body: Some("".to_string()),
1454            extern_layout: None,
1455            annotations: None,
1456        };
1457
1458        let trait_impl_schema = RsTraitImplSchema {
1459            trait_name: "ExampleTrait".to_string(),
1460            impl_for: Some("Example".to_string()),
1461            functions: vec![function_schema],
1462            generic_args: vec![],
1463            unsafe_impl: false,
1464            comment_out: false,
1465        };
1466
1467        let code = trait_impl_schema.codegen(1);
1468        let mut lines = code.lines();
1469        assert!(lines.next().unwrap().starts_with("    impl"));
1470        assert!(lines.any(|line| line.starts_with("        ")));
1471    }
1472
1473    /// Ensure file schema codegen stitches together generated children.
1474    #[test]
1475    fn test_rs_file_schema_codegen_uses_children() {
1476        let struct_schema = RsStructSchema {
1477            macros: vec![],
1478            name: "Example".to_string(),
1479            fields: vec![],
1480        };
1481
1482        let file_schema = RsFileSchema {
1483            structs: vec![struct_schema],
1484            trait_impls: vec![],
1485        };
1486
1487        let code = file_schema.codegen(0);
1488        assert!(code.contains("autogenerated"));
1489        assert!(code.contains("pub struct Example"));
1490    }
1491
1492    /// Ensure type spec is generated for arguments without explicit types.
1493    #[test]
1494    fn test_fn_arg_to_async_cffi_type_spec_none() {
1495        let arg = FunctionArgSchema {
1496            name: "arg0".to_string(),
1497            ty: None,
1498            annotations: None,
1499        };
1500
1501        let spec = fn_arg_to_async_cffi_type_spec(&arg).unwrap();
1502        assert_eq!(spec.len(), 2);
1503        assert!(spec[0].is_pointer);
1504        assert!(!spec[1].is_pointer);
1505    }
1506
1507    /// Validate mapping rules for CFFI type annotation strings.
1508    #[test]
1509    fn test_cffi_type_str_to_type_stack_mappings() {
1510        let s = "CffiPointerBuffer<opt_ptr<ptr>>".to_string();
1511        let stack = cffi_type_str_to_type_stack(&s).unwrap();
1512        // Expect first to be CffiPointerBuffer (pointer, explicit)
1513        assert!(stack.len() >= 3);
1514        assert!(stack[0].is_pointer);
1515        assert_eq!(stack[0].explicit_type.as_deref(), Some("CffiPointerBuffer"));
1516        // opt_ptr -> pointer + optional
1517        assert!(stack[1].is_pointer && stack[1].is_optional);
1518        // ptr -> pointer, not optional
1519        assert!(stack[2].is_pointer && !stack[2].is_optional);
1520    }
1521
1522    /// Confirm annotation mismatches are surfaced as errors.
1523    #[test]
1524    fn test_annotation_transforms_for_arg_mismatch_errors() {
1525        let mut annotations = FnArgAnnotations::new();
1526        annotations.cffi_type = Some("ptr".to_string());
1527
1528        let arg = FunctionArgSchema {
1529            name: "a".to_string(),
1530            ty: Some(TypeSchema::new_simple("i32".to_string())),
1531            annotations: Some(annotations),
1532        };
1533
1534        // The annotation expects a pointer-type mapping, but i32 derives to a non-pointer
1535        let res = fn_arg_to_transforms(&arg);
1536        assert!(res.is_err());
1537    }
1538
1539    /// Verify async block generation uses SafePtr arguments and awaits futures.
1540    #[test]
1541    fn test_async_block_for_trait_fn_structure_and_call_args() {
1542        let func = FunctionSchema {
1543            name: "do_something".to_string(),
1544            args: vec![
1545                FunctionArgSchema {
1546                    name: "self".to_string(),
1547                    ty: None,
1548                    annotations: None,
1549                },
1550                FunctionArgSchema {
1551                    name: "x".to_string(),
1552                    ty: None,
1553                    annotations: None,
1554                },
1555            ],
1556            return_type: TypeSchema {
1557                ty: "BoxFuture".to_string(),
1558                generic_ty_args: vec![
1559                    TypeSchema::new_simple("'_".to_string()),
1560                    TypeSchema::new_simple("()".to_string()),
1561                ],
1562            },
1563            body: None,
1564            extern_layout: None,
1565            annotations: None,
1566        };
1567
1568        let block = async_block_for_trait_fn(&func, vec![true, false], &vec![]).unwrap();
1569        assert!(block.contains("Box::pin(async move"));
1570        assert!(block.contains("fut.await"));
1571        // Expect call to cffi function using self_ptr.0 and x
1572        assert!(block.contains("(do_something_fn)(self_ptr.0, x);"));
1573    }
1574
1575    /// Ensure Vec arguments map to CffiPointerBuffer field types.
1576    #[test]
1577    fn test_arg_to_field_type_vec_produces_cffi_pointerbuffer() {
1578        let arg = FunctionArgSchema {
1579            name: "data".to_string(),
1580            ty: Some(TypeSchema {
1581                ty: "Vec".to_string(),
1582                generic_ty_args: vec![TypeSchema::new_simple("i32".to_string())],
1583            }),
1584            annotations: None,
1585        };
1586
1587        let t = arg_to_field_type(&arg).unwrap();
1588        assert_eq!(t.ty, "CffiPointerBuffer");
1589    }
1590
1591    /// Confirm non-BoxFuture returns error during C trampoline generation.
1592    #[test]
1593    fn test_trait_fn_to_cffi_c_fn_body_errors_on_non_boxfuture() {
1594        let func = FunctionSchema {
1595            name: "bad_return".to_string(),
1596            args: vec![FunctionArgSchema {
1597                name: "self".to_string(),
1598                ty: None,
1599                annotations: None,
1600            }],
1601            return_type: TypeSchema::new_simple("String".to_string()),
1602            body: None,
1603            extern_layout: None,
1604            annotations: None,
1605        };
1606
1607        let trait_schema = TraitSchema {
1608            name: "MyTrait".to_string(),
1609            functions: vec![],
1610            generics: vec![],
1611            supertraits: vec![],
1612        };
1613
1614        let err = trait_fn_to_cffi_c_fn_body(&func, &trait_schema, &vec![]).unwrap_err();
1615        assert!(err.to_string().contains("BoxFuture"));
1616    }
1617
1618    /// Ensure Option<AnyArc> return types are mapped for C callers.
1619    #[test]
1620    fn test_type_transforms_for_cffi_c_fn_return_option_anyarc() {
1621        let ty_schema = TypeSchema {
1622            ty: "Option".to_string(),
1623            generic_ty_args: vec![TypeSchema::new_simple("AnyArc".to_string())],
1624        };
1625
1626        let transform = type_transforms_for_cffi_c_fn_return(&ty_schema, "ret").unwrap();
1627
1628        assert!(transform.contains("map(|ret|"));
1629        assert!(transform.contains("downcast::<SafePtr>().unwrap().0"));
1630        assert!(transform.contains("unwrap_or(std::ptr::null())"));
1631    }
1632
1633    /// Validate transformations for pointer buffer arguments in C callbacks.
1634    #[test]
1635    fn test_cffi_c_fn_arg_to_transforms_for_pointer_buffer() {
1636        let mut annotations = FnArgAnnotations::new();
1637        annotations.cffi_type = Some("CffiPointerBuffer".to_string());
1638
1639        let arg = FunctionArgSchema {
1640            name: "values".to_string(),
1641            ty: Some(TypeSchema {
1642                ty: "Vec".to_string(),
1643                generic_ty_args: vec![TypeSchema {
1644                    ty: "Option".to_string(),
1645                    generic_ty_args: vec![TypeSchema::new_simple("AnyArc".to_string())],
1646                }],
1647            }),
1648            annotations: Some(annotations),
1649        };
1650
1651        let trait_schema = TraitSchema {
1652            name: "Trait".to_string(),
1653            functions: vec![],
1654            generics: vec![],
1655            supertraits: vec![],
1656        };
1657
1658        let transforms = cffi_c_fn_arg_to_transforms(&arg, &trait_schema).unwrap();
1659        assert!(transforms.contains(".as_slice()"));
1660        assert!(transforms.contains("SafePtr"));
1661    }
1662
1663    /// Ensure self pointers are recovered into trait references for C callbacks.
1664    #[test]
1665    fn test_cffi_c_fn_arg_to_transforms_for_self_ptr() {
1666        let arg = FunctionArgSchema {
1667            name: "self".to_string(),
1668            ty: None,
1669            annotations: None,
1670        };
1671
1672        let trait_schema = TraitSchema {
1673            name: "Trait".to_string(),
1674            functions: vec![],
1675            generics: vec![],
1676            supertraits: vec![],
1677        };
1678
1679        let transforms = cffi_c_fn_arg_to_transforms(&arg, &trait_schema).unwrap();
1680        assert!(transforms.contains("Self pointer cannot be null"));
1681        assert!(transforms.contains("dyn Trait + Send + Sync"));
1682    }
1683
1684    /// Verify From impl construction includes function pointers.
1685    #[test]
1686    fn test_trait_schema_to_from_impl_includes_function_fields() {
1687        let trait_schema = TraitSchema {
1688            name: "ExampleTrait".to_string(),
1689            functions: vec![FunctionSchema {
1690                name: "do_it".to_string(),
1691                args: vec![],
1692                return_type: TypeSchema {
1693                    ty: "BoxFuture".to_string(),
1694                    generic_ty_args: vec![
1695                        TypeSchema::new_simple("'_".to_string()),
1696                        TypeSchema::new_simple("()".to_string()),
1697                    ],
1698                },
1699                body: None,
1700                extern_layout: None,
1701                annotations: None,
1702            }],
1703            generics: vec![],
1704            supertraits: vec![],
1705        };
1706
1707        let from_impl = trait_schema_to_from_impl(&trait_schema).unwrap();
1708        let body = from_impl.body.unwrap();
1709
1710        assert!(body.contains("CffiExampleTrait"));
1711        assert!(body.contains("do_it_fut_impl"));
1712        assert!(body.contains("Box::into_raw"));
1713    }
1714
1715    /// Ensure cffi type stack to rust type handles explicit and pointer cases and errors.
1716    #[test]
1717    fn test_cffi_ty_stack_to_rust_ty_cases() {
1718        // explicit type on first element returns explicit
1719        let stack = vec![CffiTypeElementSpec {
1720            is_pointer: false,
1721            is_optional: false,
1722            explicit_type: Some("CffiX".to_string()),
1723        }];
1724        let out = cffi_ty_stack_to_rust_cffi_ty(&stack).unwrap();
1725        assert_eq!(out, "CffiX");
1726
1727        // pointer -> SafePtr
1728        let stack2 = vec![CffiTypeElementSpec {
1729            is_pointer: true,
1730            is_optional: false,
1731            explicit_type: None,
1732        }];
1733        let out2 = cffi_ty_stack_to_rust_cffi_ty(&stack2).unwrap();
1734        assert_eq!(out2, "SafePtr");
1735
1736        // empty stack errors
1737        let empty: Vec<CffiTypeElementSpec> = vec![];
1738        assert!(cffi_ty_stack_to_rust_cffi_ty(&empty).is_err());
1739    }
1740
1741    /// substitute_generic_args should return the same schema when no generics are provided.
1742    #[test]
1743    fn test_substitute_generic_args_no_generics() {
1744        let ty = TypeSchema {
1745            ty: "i32".to_string(),
1746            generic_ty_args: vec![],
1747        };
1748
1749        let out = substitute_generic_args(&ty, &vec![]).unwrap();
1750        assert_eq!(out.ty, "i32");
1751        assert!(out.generic_ty_args.is_empty());
1752    }
1753
1754    /// get_trait_name_w_subbed_generics should return the trait name when no generics exist.
1755    #[test]
1756    fn test_get_trait_name_w_subbed_generics_simple() {
1757        let trait_schema = TraitSchema {
1758            name: "SimpleTrait".to_string(),
1759            functions: vec![],
1760            generics: vec![],
1761            supertraits: vec![],
1762        };
1763
1764        let out = get_trait_name_w_subbed_generics(&trait_schema).unwrap();
1765        assert_eq!(out, "SimpleTrait");
1766    }
1767
1768    /// Verify transforms for Option<Arc<...>> returned by C futures are produced.
1769    #[test]
1770    fn test_type_transforms_for_cffi_fut_ptr_option_arc() {
1771        let ty_schema = TypeSchema {
1772            ty: "Option".to_string(),
1773            generic_ty_args: vec![TypeSchema {
1774                ty: "Arc".to_string(),
1775                generic_ty_args: vec![TypeSchema::new_simple("SafePtr".to_string())],
1776            }],
1777        };
1778        let transform = type_transforms_for_cffi_fut_ptr(&ty_schema, "ret").unwrap();
1779        assert!(
1780            transform.contains("Arc::new(SafePtr(ret))")
1781                || transform.contains("Arc::new(SafePtr({ret}))")
1782        );
1783        assert!(transform.contains("as_ref().unwrap") || transform.contains("as_ref()"));
1784    }
1785
1786    /// Unsupported element types in future return transforms should error.
1787    #[test]
1788    fn test_type_transforms_for_cffi_fut_ptr_unsupported_type() {
1789        let ty_schema = TypeSchema::new_simple("String".to_string());
1790        let res = type_transforms_for_cffi_fut_ptr(&ty_schema, "ret");
1791        assert!(res.is_err());
1792        assert!(res.err().unwrap().to_string().contains("Unsupported type"));
1793    }
1794
1795    /// cffi C trampoline should map usize -> c_ulong transform via c_ulong branch.
1796    #[test]
1797    fn test_cffi_c_fn_arg_to_transforms_c_ulong() {
1798        let arg = FunctionArgSchema {
1799            name: "n".to_string(),
1800            ty: Some(TypeSchema::new_simple("usize".to_string())),
1801            annotations: None,
1802        };
1803
1804        let trait_schema = TraitSchema {
1805            name: "Trait".to_string(),
1806            functions: vec![],
1807            generics: vec![],
1808            supertraits: vec![],
1809        };
1810
1811        let t = cffi_c_fn_arg_to_transforms(&arg, &trait_schema).unwrap();
1812        assert!(t.contains("as usize") || t.contains("as usize;"));
1813    }
1814
1815    /// Annotation transform `collection_as_item` should produce an iterator-extraction line.
1816    #[test]
1817    fn test_annotation_transforms_for_arg_collection_as_item() {
1818        let mut annotations = FnArgAnnotations::new();
1819        annotations.collection_as_item = true;
1820
1821        let arg = FunctionArgSchema {
1822            name: "it".to_string(),
1823            ty: Some(TypeSchema {
1824                ty: "Vec".to_string(),
1825                generic_ty_args: vec![TypeSchema::new_simple("AnyArc".to_string())],
1826            }),
1827            annotations: Some(annotations),
1828        };
1829
1830        let out = annotation_transforms_for_arg(&arg, vec![]).unwrap();
1831        assert!(out.is_some());
1832        let s = out.unwrap();
1833        assert!(s.contains("into_iter().next().unwrap()"));
1834    }
1835
1836    /// Boxing a dyn trait argument should generate a leak and cffi conversion.
1837    #[test]
1838    fn test_type_transforms_for_arg_box_dyn() {
1839        let arg = FunctionArgSchema {
1840            name: "b".to_string(),
1841            ty: Some(TypeSchema {
1842                ty: "Box".to_string(),
1843                generic_ty_args: vec![TypeSchema {
1844                    ty: "dyn SomeTrait".to_string(),
1845                    generic_ty_args: vec![],
1846                }],
1847            }),
1848            annotations: None,
1849        };
1850
1851        let out = type_transforms_for_arg(&arg).unwrap();
1852        assert!(!out.returns_safe_ptr);
1853        let t = out.transforms.unwrap();
1854        assert!(t.contains("Box::leak") || t.contains("Box::leak("));
1855        assert!(t.contains("cffi_b") || t.contains("cffi_b"));
1856    }
1857}