async_cffi_codegen/
py_codegen.rs

1use std::collections::{HashMap, HashSet};
2use std::fmt::Write as _;
3
4use anyhow::{Context, Result, bail};
5use heck::ToSnakeCase;
6
7use handlebars::Handlebars;
8use serde_json::json;
9use rs_schema::{FunctionArgSchema, FunctionSchema, TraitSchema, TypeSchema};
10
11use crate::{
12    CodeGen,
13    cffi_type_utils::{
14        CffiTypeElementSpec, CffiTypeSchema, cffi_type_str_to_schema, cffi_type_str_to_type_stack,
15    },
16    rs_type_utils::fn_arg_to_async_cffi_type_spec,
17};
18
19#[derive(Debug)]
20pub struct PyFileSchema {
21    pub imports: Vec<String>,
22    pub classes: Vec<PyClassSchema>,
23}
24
25impl CodeGen for PyFileSchema {
26    fn codegen(&self, indent: usize) -> String {
27        let mut output = String::new();
28
29        // Imports
30        for import in &self.imports {
31            output.push_str(import);
32            output.push('\n');
33        }
34        output.push('\n');
35
36        for class_schema in &self.classes {
37            output.push_str(&class_schema.codegen(indent));
38            output.push('\n');
39        }
40        output
41    }
42}
43
44#[derive(Debug)]
45pub struct PyClassSchema {
46    name: String,
47    parent_types: Vec<PyTypeSchema>,
48    class_vars: Vec<String>,
49    functions: Vec<PyFunctionSchema>,
50}
51
52impl CodeGen for PyClassSchema {
53    fn codegen(&self, indent: usize) -> String {
54        let mut output = String::new();
55        let pad = " ".repeat(indent);
56
57        // Class definition
58        if self.parent_types.is_empty() {
59            writeln!(&mut output, "{}class {}:", pad, self.name).unwrap();
60        } else {
61            writeln!(
62                &mut output,
63                "{}class {}({}):",
64                pad,
65                self.name,
66                self.parent_types
67                    .iter()
68                    .map(|ty| ty.codegen(0))
69                    .collect::<Vec<_>>()
70                    .join(", ")
71            )
72            .unwrap();
73        }
74
75        // Class variables
76        if self.class_vars.is_empty() {
77            if self.functions.is_empty() {
78                writeln!(&mut output, "{}    pass", pad).unwrap();
79            }
80        } else {
81            for var in &self.class_vars {
82                writeln!(&mut output, "{}    {}", pad, var).unwrap();
83            }
84        }
85
86        for f in &self.functions {
87            writeln!(&mut output, "{}", f.codegen(indent + 1)).unwrap();
88        }
89
90        output
91    }
92}
93
94#[derive(Debug)]
95struct PyFunctionSchema {
96    name: String,
97    args: Vec<PyFunctionArgSchema>,
98    return_type: Option<PyTypeSchema>,
99    is_async: bool,
100    body: Vec<String>, // statements
101    py_annotations: Vec<String>,
102}
103
104impl CodeGen for PyFunctionSchema {
105    fn codegen(&self, indent: usize) -> String {
106        let pad = "    ".repeat(indent);
107        let mut output = String::new();
108
109        for annotation in &self.py_annotations {
110            writeln!(&mut output, "{}{}", pad, annotation).unwrap();
111        }
112
113        let async_prefix = if self.is_async { "async " } else { "" };
114
115        let args_str = self
116            .args
117            .iter()
118            .map(|a| a.codegen(0))
119            .collect::<Vec<_>>()
120            .join(", ");
121
122        let ret_str = self
123            .return_type
124            .as_ref()
125            .map(|r| format!(" -> {}", r.codegen(0)))
126            .unwrap_or_default();
127
128        writeln!(
129            &mut output,
130            "{}{}def {}({}){}:",
131            pad, async_prefix, self.name, args_str, ret_str
132        )
133        .unwrap();
134
135        for stmt in &self.body {
136            for line in stmt.split('\n') {
137                writeln!(&mut output, "{}    {}", pad, line).unwrap();
138            }
139        }
140
141        output
142    }
143}
144
145#[derive(Debug)]
146struct PyFunctionArgSchema {
147    name: String,
148    ty: Option<PyTypeSchema>,
149}
150
151impl CodeGen for PyFunctionArgSchema {
152    fn codegen(&self, _indent: usize) -> String {
153        let ty_str = self
154            .ty
155            .as_ref()
156            .map(|ty| format!(": {}", ty.codegen(0)))
157            .unwrap_or_default();
158        format!("{}{}", self.name, ty_str)
159    }
160}
161
162#[derive(Debug)]
163struct PyTypeSchema {
164    ty: String,
165    generic_ty_args: Vec<PyTypeSchema>,
166}
167
168impl CodeGen for PyTypeSchema {
169    fn codegen(&self, _indent: usize) -> String {
170        let args_str = if self.generic_ty_args.len() > 0 {
171            format!(
172                "[{}]",
173                self.generic_ty_args
174                    .iter()
175                    .map(|ty| ty.codegen(0))
176                    .collect::<Vec<_>>()
177                    .join(", ")
178            )
179        } else {
180            String::new()
181        };
182
183        format!("{}{}", self.ty, args_str)
184    }
185}
186
187pub fn trait_to_async_cffi_schema(
188    trait_schema: &TraitSchema,
189    supertrait_schemas: &HashMap<String, TraitSchema>,
190) -> Result<PyFileSchema> {
191    let mut classes = Vec::new();
192
193    let mut imports: Vec<String> = vec![
194        "import asyncio".to_string(),
195        "import ctypes".to_string(),
196        "import typing".to_string(),
197        "from typing import Optional, override".to_string(),
198        "from centconf.async_cffi import ASYNC_CFFI_LIB".to_string(),
199        "from centconf.async_cffi import CffiFutureDriver".to_string(),
200        "from centconf.async_cffi import AsyncCffiLib, CffiPointerBuffer, PyCffiFuture".to_string(),
201    ];
202
203    imports.extend(interface_imports(trait_schema));
204    imports.extend(supertrait_cffi_imports(trait_schema));
205    imports.extend(cffi_type_imports(trait_schema)?);
206    dedup_preserve_order(&mut imports);
207
208    let struct_schema = cffi_struct_schema(trait_schema, supertrait_schemas)?;
209    classes.push(struct_schema);
210
211    let generator_schema = cffi_generator_schema(trait_schema, supertrait_schemas)?;
212    classes.push(generator_schema);
213
214    Ok(PyFileSchema { imports, classes })
215}
216
217fn dedup_preserve_order(values: &mut Vec<String>) {
218    let mut seen = HashSet::new();
219    values.retain(|v| seen.insert(v.clone()));
220}
221
222fn interface_names_from_args(trait_schema: &TraitSchema) -> Vec<String> {
223    trait_schema
224        .functions
225        .iter()
226        .flat_map(|f| {
227            f.args
228                .iter()
229                .filter_map(|arg| arg.ty.as_ref().map(|ty| interface_names_from_ty(ty)))
230        })
231        .flatten()
232        .collect()
233}
234
235struct TypeSchemaIter<'a> {
236    ty: &'a TypeSchema,
237    state: TypeSchemaIterState<'a>,
238}
239
240enum TypeSchemaIterState<'a> {
241    Start,
242    InGeneric(usize, Option<Box<TypeSchemaIter<'a>>>),
243}
244
245impl<'a> TypeSchemaIter<'a> {
246    fn new(ty: &'a TypeSchema) -> Self {
247        Self {
248            ty,
249            state: TypeSchemaIterState::Start,
250        }
251    }
252}
253
254impl<'a> Iterator for TypeSchemaIter<'a> {
255    type Item = &'a TypeSchema;
256
257    fn next(&mut self) -> Option<Self::Item> {
258        match &mut self.state {
259            TypeSchemaIterState::Start => {
260                self.state = TypeSchemaIterState::InGeneric(0, None);
261                Some(self.ty)
262            }
263            TypeSchemaIterState::InGeneric(idx, None) => {
264                if *idx < self.ty.generic_ty_args.len() {
265                    self.state = TypeSchemaIterState::InGeneric(
266                        *idx,
267                        Some(Box::new(TypeSchemaIter::new(
268                            &self.ty.generic_ty_args[*idx],
269                        ))),
270                    );
271                    return self.next();
272                }
273                None
274            }
275            TypeSchemaIterState::InGeneric(idx, Some(iter)) => {
276                if let Some(item) = iter.next() {
277                    Some(item)
278                } else {
279                    self.state = TypeSchemaIterState::InGeneric(*idx + 1, None);
280                    self.next()
281                }
282            }
283        }
284    }
285}
286
287fn interface_names_from_ty(ty: &TypeSchema) -> Vec<String> {
288    TypeSchemaIter::new(ty)
289        .filter_map(|ty| {
290            ty.ty.find("dyn").and_then(|idx| {
291                let after = ty.ty[idx + 4..].trim();
292                let ident: String = after
293                    .chars()
294                    .take_while(|c| c.is_alphanumeric() || *c == '_')
295                    .collect();
296                if !ident.is_empty() {
297                    Some(format!("I{}", ident))
298                } else {
299                    None
300                }
301            })
302        })
303        .collect()
304}
305
306fn interface_imports(trait_schema: &TraitSchema) -> Vec<String> {
307    let mut interfaces = vec![format!("I{}", trait_schema.name)];
308    interfaces.extend(
309        trait_schema
310            .supertraits
311            .iter()
312            .filter(|st| (st.ty != "Send") && (st.ty != "Sync"))
313            .map(|st| format!("I{}", st.ty)),
314    );
315    interfaces.extend(interface_names_from_args(trait_schema));
316    dedup_preserve_order(&mut interfaces);
317
318    if interfaces.is_empty() {
319        Vec::new()
320    } else {
321        vec![format!(
322            "from n_observer.core import {}",
323            interfaces.join(", ")
324        )]
325    }
326}
327
328fn supertrait_cffi_imports(trait_schema: &TraitSchema) -> Vec<String> {
329    trait_schema
330        .supertraits
331        .iter()
332        .filter(|st| (st.ty != "Send") && (st.ty != "Sync"))
333        .map(|st| {
334            let module = cffi_module_for_type(&format!("Cffi{}", st.ty));
335            format!(
336                "from {module} import Cffi{st}, Cffi{st}Generator",
337                module = module,
338                st = st.ty
339            )
340        })
341        .collect()
342}
343
344fn cffi_type_imports(trait_schema: &TraitSchema) -> Result<Vec<String>> {
345    let mut imports = Vec::new();
346    for func in &trait_schema.functions {
347        for arg in &func.args {
348            if let Some(annotations) = &arg.annotations {
349                if let Some(cffi_type) = &annotations.cffi_type {
350                    let stack = cffi_type_str_to_type_stack(cffi_type)?;
351                    if let Some(explicit) = &stack[0].explicit_type {
352                        if explicit == "CffiPointerBuffer" {
353                            continue;
354                        }
355                        let module = cffi_module_for_type(explicit);
356                        let generator = format!("{explicit}Generator");
357                        imports.push(format!(
358                            "from {module} import {explicit}, {generator}",
359                            module = module,
360                            explicit = explicit,
361                            generator = generator
362                        ));
363                    }
364                }
365            }
366        }
367    }
368    dedup_preserve_order(&mut imports);
369    Ok(imports)
370}
371
372fn cffi_module_for_type(cffi_type: &str) -> String {
373    match cffi_type {
374        "CffiInnerObserverReceiver" => "centconf.observer_cffi.ior_cffi_traits".to_string(),
375        _ => format!(
376            "centconf.observer_cffi.{}_cffi_traits",
377            cffi_type.trim_start_matches("Cffi").to_snake_case()
378        ),
379    }
380}
381
382fn cffi_struct_schema(
383    trait_schema: &TraitSchema,
384    supertrait_schemas: &HashMap<String, TraitSchema>,
385) -> Result<PyClassSchema> {
386    let generic_py_types = trait_generic_py_types(trait_schema)?;
387    let cffi_func_types: Vec<(String, String)> = trait_schema
388        .functions
389        .iter()
390        .map(|func| Ok((format!("{}_fut", func.name), trait_func_to_cffi_type(func)?)))
391        .collect::<Result<Vec<_>>>()?;
392
393    let supertrait_fields: Vec<String> = trait_schema
394        .supertraits
395        .iter()
396        .filter(|st| (st.ty != "Send") && (st.ty != "Sync"))
397        .map(|st| {
398            let _ = supertrait_schemas.get(&st.codegen(0)).context(format!(
399                "Missing supertrait schema for {} while generating cffi struct",
400                st
401            ))?;
402            Ok(format!(
403                "(\"{field}\", Cffi{st})",
404                field = format!("cffi_{}", st.ty.to_snake_case()),
405                st = st.ty
406            ))
407        })
408        .collect::<Result<Vec<_>>>()?;
409
410    let mut class_vars: Vec<String> = cffi_func_types
411        .iter()
412        .map(|(name, ty)| format!("_{}_type = {}", name, ty))
413        .collect();
414    class_vars.push(format!(
415        "_fields_ = [{}]",
416        std::iter::once("('self_ptr', ctypes.c_void_p)".to_string())
417            .chain(supertrait_fields.into_iter())
418            .chain(
419                cffi_func_types
420                    .iter()
421                    // TODO: Currently assuming all functions are async
422                    .map(|(name, _ty)| format!("('{}', {})", name, format!("_{}_type", name)))
423            )
424            .collect::<Vec<String>>()
425            .join(",\n    ")
426    ));
427
428    let functions = trait_schema
429        .functions
430        .iter()
431        .filter(|f| {
432            f.annotations
433                .as_ref()
434                .map(|a| !a.cffi_impl_no_op)
435                .unwrap_or(true)
436        })
437        .map(trait_fn_to_py_impl)
438        .collect::<Result<Vec<_>>>()?;
439    let supertrait_passthroughs = trait_schema
440        .supertraits
441        .iter()
442        .filter(|st| (st.ty != "Send") && (st.ty != "Sync"))
443        .map(|st| {
444            let st_schema = supertrait_schemas
445                .get(&st.codegen(0))
446                .with_context(|| format!("Missing supertrait schema for {}", st))?;
447            st_schema
448                .functions
449                .iter()
450                .map(|f| passthrough_to_supertrait_schema(f, st))
451                .collect::<Result<Vec<_>>>()
452        })
453        .collect::<Result<Vec<_>>>()?
454        .into_iter()
455        .flatten()
456        .collect::<Vec<_>>();
457
458    Ok(PyClassSchema {
459        name: format!("Cffi{}", trait_schema.name),
460        parent_types: vec![
461            PyTypeSchema {
462                ty: "ctypes.Structure".to_string(),
463                generic_ty_args: vec![],
464            },
465            PyTypeSchema {
466                ty: format!("I{}", trait_schema.name),
467                generic_ty_args: generic_py_types,
468            },
469        ],
470        class_vars,
471        functions: {
472            let mut funcs = functions;
473            funcs.extend(supertrait_passthroughs);
474            funcs
475        },
476    })
477}
478
479fn trait_fn_to_py_impl(f: &FunctionSchema) -> Result<PyFunctionSchema> {
480    Ok(PyFunctionSchema {
481        name: f.name.clone(),
482        args: f
483            .args
484            .iter()
485            .map(|a| {
486                Ok(PyFunctionArgSchema {
487                    name: a.name.clone(),
488                    ty: rs_ty_arg_to_py_ty(a)?,
489                })
490            })
491            .collect::<Result<Vec<_>>>()?,
492        return_type: rs_return_ty_to_py_ty(&f.return_type)?,
493        is_async: true,
494        body: trait_fn_to_py_impl_body(f)?,
495        py_annotations: vec!["@override".to_string()],
496    })
497}
498
499fn trait_fn_to_py_impl_body(function: &FunctionSchema) -> Result<Vec<String>> {
500    fn arg_name_transform(arg: &FunctionArgSchema) -> String {
501        if arg.ty.is_none() {
502            "self_ptr".to_string()
503        } else {
504            arg.name.clone()
505        }
506    }
507
508    let arg_transforms = function
509        .args
510        .iter()
511        .map(|arg| {
512            let py_input_ty = rs_ty_arg_to_py_ty(arg)?;
513            let py_cffi_ty = arg_to_py_cffi_type(arg)?;
514
515            let arg_name = arg_name_transform(arg);
516
517            let reg = Handlebars::new();
518            let mut transform_block = format!("{} = {{{{{{next}}}}}}\n", arg_name);
519            match py_cffi_ty.as_str() {
520                "CffiPointerBuffer" => {
521                    if let Some(py_input_ty) = py_input_ty {
522                        if py_input_ty.ty != "list" {
523                            bail!(
524                                "Expected list input type for CffiPointerBuffer transform, got {}",
525                                py_input_ty.ty
526                            );
527                        }
528                    } else {
529                        bail!("Expected input type to be present for CffiPointerBuffer transform");
530                    }
531
532                    let template_string = format!(
533                        "CffiPointerBuffer.from_pointer_array([\n    typing.cast(ctypes.c_void_p, {}) if {} is not None else None\n    for {} in {}\n])",
534                        arg_name, arg_name, arg_name, arg.name
535                    );
536                    transform_block = reg
537                            .render_template(&transform_block, &json!({"next": template_string}))
538                            .unwrap()
539                            .to_string();
540                }
541                cffi_ty if cffi_ty.starts_with("Cffi") => {
542                    let generator_name = format!("{}Generator", cffi_ty);
543                    let generator_field = format!(
544                        "cffi_{}",
545                        cffi_ty
546                            .trim_start_matches("Cffi")
547                            .to_snake_case()
548                    );
549                    let template_string = format!(
550                        "{generator}(ASYNC_CFFI_LIB, asyncio.get_event_loop(), {arg}).{field}",
551                        generator = generator_name,
552                        arg = arg.name,
553                        field = generator_field
554                    );
555                    transform_block = reg
556                        .render_template(&transform_block, &json!({"next": template_string}))
557                        .unwrap()
558                        .to_string();
559                }
560                "ctypes.c_void_p" => {
561                    if let Some(_) = py_input_ty {
562                        return Ok(None);
563                    }
564                    // self
565                    transform_block = reg
566                            .render_template(&transform_block, &json!({"next": "self.self_ptr"}))
567                            .unwrap()
568                            .to_string();
569                }
570                _ => return Ok(None),
571            }
572
573            Ok(Some(transform_block))
574        })
575        .collect::<Result<Vec<_>>>()?;
576    let arg_transforms: Vec<String> = arg_transforms.iter().flatten().cloned().collect();
577
578    let async_args_for_call = function
579        .args
580        .iter()
581        .map(arg_name_transform)
582        .collect::<Vec<String>>()
583        .join(", ");
584    let async_call = format!(
585        "await CffiFutureDriver.from_cffi_fut(\n    self.{}_fut({}),\n    asyncio.get_event_loop(),\n    ASYNC_CFFI_LIB)",
586        function.name, async_args_for_call,
587    );
588
589    let mut transforms = arg_transforms;
590    match return_kind(&function.return_type)? {
591        ReturnKind::Unit => transforms.push(async_call),
592        ReturnKind::Option => {
593            transforms.push(format!("fut_result = {}", async_call));
594            transforms.push(
595                "ptr_ptr = ctypes.cast(fut_result, ctypes.POINTER(ctypes.c_void_p))".to_string(),
596            );
597            transforms.push("ptr = ctypes.c_void_p(ptr_ptr.contents.value)".to_string());
598            transforms.push("if ptr:".to_string());
599            transforms.push("    return ptr".to_string());
600            transforms.push("else:".to_string());
601            transforms.push("    return None".to_string());
602        }
603        ReturnKind::Other => transforms.push(format!("return {}", async_call)),
604    }
605    Ok(transforms)
606}
607
608fn cffi_generator_schema(
609    trait_schema: &TraitSchema,
610    supertrait_schemas: &HashMap<String, TraitSchema>,
611) -> Result<PyClassSchema> {
612    let py_impl_field = format!("_py_{}", trait_schema.name.to_snake_case());
613
614    let boxed_functions = trait_schema
615        .functions
616        .iter()
617        .filter(|f| !is_cffi_impl_no_op(f))
618        .map(boxed_function_schema)
619        .collect::<Result<Vec<_>>>()?;
620
621    let init_fn = generator_init_fn_schema(trait_schema, supertrait_schemas)?;
622
623    let passthroughs = trait_schema
624        .functions
625        .iter()
626        .map(|f| passthrough_function_schema(f, &py_impl_field))
627        .collect::<Result<Vec<_>>>()?;
628
629    let supertrait_passthroughs = trait_schema
630        .supertraits
631        .iter()
632        .filter(|st| (st.ty != "Send") && (st.ty != "Sync"))
633        .map(|st| {
634            let st_schema = supertrait_schemas
635                .get(&st.codegen(0))
636                .with_context(|| format!("Missing supertrait schema for {}", st))?;
637            st_schema
638                .functions
639                .iter()
640                .map(|f| passthrough_to_supertrait_schema(f, st))
641                .collect::<Result<Vec<_>>>()
642        })
643        .collect::<Result<Vec<_>>>()?
644        .into_iter()
645        .flatten()
646        .collect::<Vec<_>>();
647
648    let mut functions = Vec::new();
649    functions.push(init_fn);
650    functions.extend(passthroughs);
651    functions.extend(supertrait_passthroughs);
652    functions.extend(boxed_functions);
653
654    Ok(PyClassSchema {
655        name: format!("Cffi{}Generator", trait_schema.name),
656        parent_types: vec![],
657        class_vars: vec![],
658        functions,
659    })
660}
661
662fn generator_init_fn_schema(
663    trait_schema: &TraitSchema,
664    supertrait_schemas: &HashMap<String, TraitSchema>,
665) -> Result<PyFunctionSchema> {
666    let class_field = format!("cffi_{}", trait_schema.name.to_snake_case());
667    let cffi_struct_name = format!("Cffi{}", trait_schema.name);
668    let generic_py_types = trait_generic_py_types(trait_schema)?;
669    let args = vec![
670        PyFunctionArgSchema {
671            name: "self".to_string(),
672            ty: None,
673        },
674        PyFunctionArgSchema {
675            name: "async_cffi".to_string(),
676            ty: Some(PyTypeSchema {
677                ty: "AsyncCffiLib".to_string(),
678                generic_ty_args: vec![],
679            }),
680        },
681        PyFunctionArgSchema {
682            name: "loop".to_string(),
683            ty: Some(PyTypeSchema {
684                ty: "asyncio.AbstractEventLoop".to_string(),
685                generic_ty_args: vec![],
686            }),
687        },
688        PyFunctionArgSchema {
689            name: trait_schema.name.to_snake_case(),
690            ty: Some(PyTypeSchema {
691                ty: format!("I{}", trait_schema.name),
692                generic_ty_args: generic_py_types,
693            }),
694        },
695    ];
696
697    let mut field_setters = vec![
698        "self._async_cffi = async_cffi".to_string(),
699        "self._loop = loop".to_string(),
700        format!(
701            "self.{} = {}",
702            format!("_py_{}", trait_schema.name.to_snake_case()),
703            trait_schema.name.to_snake_case()
704        ),
705    ];
706
707    let supertrait_field_setters = trait_schema
708        .supertraits
709        .iter()
710        .filter(|st| (st.ty != "Send") && (st.ty != "Sync"))
711        .map(|st| {
712            let _ = supertrait_schemas.get(&st.codegen(0)).context(format!(
713                "Missing supertrait schema for {} while generating init",
714                st
715            ))?;
716            Ok(format!(
717                "self.{field} = Cffi{st}Generator(async_cffi, loop, {py_impl}).cffi_{snake}",
718                field = format!("cffi_{}", st.ty.to_snake_case()),
719                st = st,
720                py_impl = trait_schema.name.to_snake_case(),
721                snake = st.ty.to_snake_case()
722            ))
723        })
724        .collect::<Result<Vec<_>>>()?;
725
726    field_setters.extend(supertrait_field_setters.into_iter());
727
728    let fut_fields = trait_schema
729        .functions
730        .iter()
731        .map(|f| {
732            let fut_type_name = format!("{cffi_struct_name}._{}_fut_type", f.name);
733            let lambda_args: Vec<String> = f
734                .args
735                .iter()
736                .map(|arg| {
737                    arg.ty
738                        .as_ref()
739                        .map(|_| arg.name.clone())
740                        .unwrap_or("_self_ptr".to_string())
741                })
742                .collect();
743            let lambda_args = lambda_args.join(", ");
744            let boxed_call = format!(
745                "{}",
746                if is_cffi_impl_no_op(f) {
747                    format!("self.{}()", f.name)
748                } else {
749                    format!(
750                        "self._{}_boxed({})",
751                        f.name,
752                        f.args
753                            .iter()
754                            .filter(|a| a.ty.is_some())
755                            .map(|a| a.name.clone())
756                            .collect::<Vec<_>>()
757                            .join(", ")
758                    )
759                }
760            );
761            format!(
762                "{}_fut={}(\n    lambda {}: PyCffiFuture(\n        self._async_cffi,\n        self._loop,\n        lambda: {}\n    ).cffi_fut_ptr\n)",
763                f.name, fut_type_name, lambda_args, boxed_call
764            )
765        })
766        .collect::<Vec<_>>();
767
768    let mut cffi_struct_args = vec!["self_ptr=ctypes.c_void_p()".to_string()];
769    cffi_struct_args.extend(
770        trait_schema
771            .supertraits
772            .iter()
773            .filter(|st| (st.ty != "Send") && (st.ty != "Sync"))
774            .map(|st| {
775                format!(
776                    "{field}=self.{field}",
777                    field = format!("cffi_{}", st.ty.to_snake_case())
778                )
779            }),
780    );
781    cffi_struct_args.extend(fut_fields);
782
783    let reg = Handlebars::new();
784    let cffi_struct_init = reg
785        .render_template(
786            "self.{{class_field}} = {{cffi_struct_name}}(\n{{{fields}}}\n)",
787            &json!({
788                "class_field": class_field,
789                "cffi_struct_name": cffi_struct_name,
790                "fields": cffi_struct_args
791                    .iter()
792                    .map(|f| format!("    {}", f))
793                    .collect::<Vec<_>>()
794                    .join(",\n"),
795            }),
796        )
797        .unwrap();
798
799    field_setters.push(cffi_struct_init);
800
801    Ok(PyFunctionSchema {
802        name: "__init__".to_string(),
803        args,
804        return_type: None,
805        is_async: false,
806        body: field_setters,
807        py_annotations: vec![],
808    })
809}
810
811fn passthrough_function_schema(
812    function: &FunctionSchema,
813    py_impl_field: &str,
814) -> Result<PyFunctionSchema> {
815    let is_no_op = is_cffi_impl_no_op(function);
816
817    let arg_schemas = if is_no_op {
818        vec![PyFunctionArgSchema {
819            name: "self".to_string(),
820            ty: None,
821        }]
822    } else {
823        std::iter::once(PyFunctionArgSchema {
824            name: "self".to_string(),
825            ty: None,
826        })
827        .chain(
828            function
829                .args
830                .iter()
831                .filter(|a| a.ty.is_some())
832                .map(|a| {
833                    Ok(PyFunctionArgSchema {
834                        name: a.name.clone(),
835                        ty: rs_ty_arg_to_py_ty(a)?,
836                    })
837                })
838                .collect::<Result<Vec<_>>>()?
839                .into_iter(),
840        )
841        .collect()
842    };
843
844    Ok(PyFunctionSchema {
845        name: function.name.clone(),
846        args: arg_schemas,
847        return_type: None,
848        is_async: true,
849        body: if is_no_op {
850            vec!["return self._async_cffi.box_u64(ctypes.c_uint64(1))".to_string()]
851        } else {
852            vec![format!(
853                "return await self.{}.{}({})",
854                py_impl_field,
855                function.name,
856                function
857                    .args
858                    .iter()
859                    .filter(|a| a.ty.is_some())
860                    .map(|a| a.name.clone())
861                    .collect::<Vec<_>>()
862                    .join(", ")
863            )]
864        },
865        py_annotations: vec![],
866    })
867}
868
869fn passthrough_to_supertrait_schema(
870    function: &FunctionSchema,
871    supertrait: &TypeSchema,
872) -> Result<PyFunctionSchema> {
873    let args = std::iter::once(PyFunctionArgSchema {
874        name: "self".to_string(),
875        ty: None,
876    })
877    .chain(
878        function
879            .args
880            .iter()
881            .filter(|a| a.ty.is_some())
882            .map(|a| {
883                Ok(PyFunctionArgSchema {
884                    name: a.name.clone(),
885                    ty: rs_ty_arg_to_py_ty(a)?,
886                })
887            })
888            .collect::<Result<Vec<_>>>()?
889            .into_iter(),
890    )
891    .collect::<Vec<_>>();
892
893    Ok(PyFunctionSchema {
894        name: function.name.clone(),
895        args,
896        return_type: rs_return_ty_to_py_ty(&function.return_type)?,
897        is_async: true,
898        body: vec![format!(
899            "return await self.cffi_{field}.{func}({args})",
900            field = supertrait.ty.to_snake_case(),
901            func = function.name,
902            args = function
903                .args
904                .iter()
905                .filter(|a| a.ty.is_some())
906                .map(|a| a.name.clone())
907                .collect::<Vec<_>>()
908                .join(", ")
909        )],
910        py_annotations: vec![],
911    })
912}
913
914fn boxed_function_schema(function: &FunctionSchema) -> Result<PyFunctionSchema> {
915    if is_cffi_impl_no_op(function) {
916        bail!("boxed_function_schema should not be called for cffi_impl_no_op functions");
917    }
918
919    let args = function
920        .args
921        .iter()
922        .filter(|a| a.ty.is_some())
923        .map(|a| {
924            Ok(PyFunctionArgSchema {
925                name: a.name.clone(),
926                ty: Some(PyTypeSchema {
927                    ty: arg_to_py_cffi_type(a)?,
928                    generic_ty_args: vec![],
929                }),
930            })
931        })
932        .collect::<Result<Vec<_>>>()?;
933
934    let mut body = Vec::new();
935    let arg_transforms = function
936        .args
937        .iter()
938        .filter(|a| a.ty.is_some())
939        .map(boxed_arg_transform)
940        .collect::<Result<Vec<_>>>()?;
941
942    for transform in arg_transforms.into_iter().flatten() {
943        body.push(transform);
944    }
945
946    let call_args = function
947        .args
948        .iter()
949        .filter(|a| a.ty.is_some())
950        .map(|a| a.name.clone())
951        .collect::<Vec<_>>()
952        .join(", ");
953    let call_expr = format!("await self.{}({})", function.name, call_args);
954
955    let return_transform =
956        boxed_return_transform(&function.return_type, call_expr, function.name.as_str())?;
957    for stmt in return_transform {
958        body.push(stmt);
959    }
960
961    Ok(PyFunctionSchema {
962        name: format!("_{}_boxed", function.name),
963        args: std::iter::once(PyFunctionArgSchema {
964            name: "self".to_string(),
965            ty: None,
966        })
967        .chain(args.into_iter())
968        .collect(),
969        return_type: Some(PyTypeSchema {
970            ty: "ctypes.c_void_p".to_string(),
971            generic_ty_args: vec![],
972        }),
973        is_async: true,
974        body,
975        py_annotations: vec![],
976    })
977}
978
979fn boxed_arg_transform(arg: &FunctionArgSchema) -> Result<Option<String>> {
980    let cffi_stack = arg_cffi_type_stack(arg)?;
981    if let Some(explicit) = &cffi_stack[0].explicit_type {
982        if explicit == "CffiPointerBuffer" {
983            let item_var = format!("{}_item", arg.name);
984            let transform = format!(
985                "{arg}_array = {arg}.to_pointer_array()\n{arg}_py_list: list[Optional[object]] = []\nfor {item_var} in {arg}_array:\n    opt_{item_var} = {item_var}\n    if (\n        {item_var} is None\n        or {item_var} == 0\n        or (isinstance({item_var}, ctypes.c_void_p) and {item_var}.value == 0)\n    ):\n        opt_{item_var} = None\n    {arg}_py_list.append(opt_{item_var})\n{arg} = {arg}_py_list",
986                arg = arg.name,
987                item_var = item_var,
988            );
989            return Ok(Some(transform));
990        }
991    }
992
993    if cffi_stack[0].is_optional || cffi_stack.iter().any(|e| e.is_optional) {
994        let transform = format!(
995            "{arg} = None if {arg} is None or {arg} == 0 or (isinstance({arg}, ctypes.c_void_p) and {arg}.value == 0) else {arg}",
996            arg = arg.name
997        );
998        return Ok(Some(transform));
999    }
1000
1001    Ok(None)
1002}
1003
1004fn boxed_return_transform(
1005    ret_ty: &TypeSchema,
1006    call_expr: String,
1007    function_name: &str,
1008) -> Result<Vec<String>> {
1009    let mut stmts = Vec::new();
1010    let inner_ty = inner_return_type(ret_ty)?;
1011
1012    if inner_ty.ty == "()" {
1013        stmts.push(call_expr);
1014        let unit_box_value = if function_name == "hold_strong_publisher_ref" {
1015            1
1016        } else {
1017            42
1018        };
1019        stmts.push(format!(
1020            "return self._async_cffi.box_u64(ctypes.c_uint64({}))",
1021            unit_box_value
1022        ));
1023        return Ok(stmts);
1024    }
1025
1026    if inner_ty.ty == "Option" {
1027        stmts.push(format!("result = {}", call_expr));
1028        stmts.push("if result is None:".to_string());
1029        stmts.push("    ptr = ctypes.c_void_p()".to_string());
1030        stmts.push("else:".to_string());
1031        stmts.push("    ptr = typing.cast(ctypes.c_void_p, result)".to_string());
1032        stmts.push("return self._async_cffi.box_ptr(ptr)".to_string());
1033        return Ok(stmts);
1034    }
1035
1036    stmts.push(format!("result = {}", call_expr));
1037    stmts.push("ptr = typing.cast(ctypes.c_void_p, result)".to_string());
1038    stmts.push("return self._async_cffi.box_ptr(ptr)".to_string());
1039    Ok(stmts)
1040}
1041
1042fn is_cffi_impl_no_op(function: &FunctionSchema) -> bool {
1043    function
1044        .annotations
1045        .as_ref()
1046        .map(|a| a.cffi_impl_no_op)
1047        .unwrap_or(false)
1048}
1049
1050enum ReturnKind {
1051    Unit,
1052    Option,
1053    Other,
1054}
1055
1056fn return_kind(ret_ty: &TypeSchema) -> Result<ReturnKind> {
1057    let inner = inner_return_type(ret_ty)?;
1058    if inner.ty == "()" {
1059        Ok(ReturnKind::Unit)
1060    } else if inner.ty == "Option" {
1061        Ok(ReturnKind::Option)
1062    } else {
1063        Ok(ReturnKind::Other)
1064    }
1065}
1066
1067fn inner_return_type(ret_ty: &TypeSchema) -> Result<TypeSchema> {
1068    if ret_ty.ty != "BoxFuture" {
1069        return Ok(ret_ty.clone());
1070    }
1071
1072    if ret_ty.generic_ty_args.len() != 2 {
1073        bail!(
1074            "BoxFuture should have exactly two generic arguments, but found {}",
1075            ret_ty.generic_ty_args.len()
1076        );
1077    }
1078
1079    let inner = &ret_ty.generic_ty_args[1];
1080    Ok(inner.clone())
1081}
1082
1083fn py_object_type() -> PyTypeSchema {
1084    PyTypeSchema {
1085        ty: "object".to_string(),
1086        generic_ty_args: vec![],
1087    }
1088}
1089
1090fn py_ctypes_void_ptr_type() -> PyTypeSchema {
1091    PyTypeSchema {
1092        ty: "ctypes.c_void_p".to_string(),
1093        generic_ty_args: vec![],
1094    }
1095}
1096
1097fn trait_generic_py_types(trait_schema: &TraitSchema) -> Result<Vec<PyTypeSchema>> {
1098    trait_schema
1099        .generics
1100        .iter()
1101        .map(|g| {
1102            let cffi_type = g
1103                .annotations
1104                .as_ref()
1105                .and_then(|a| a.cffi_type.as_ref())
1106                .context("Missing cffi_type annotation for generic parameter")?;
1107            py_ty_from_cffi_schema(&cffi_type_str_to_schema(cffi_type)?)
1108        })
1109        .collect()
1110}
1111
1112fn py_ty_from_cffi_schema(schema: &CffiTypeSchema) -> Result<PyTypeSchema> {
1113    let elem = cffi_schema_to_elem_spec(schema);
1114
1115    if elem.explicit_type.is_some() {
1116        return cffi_ty_schema_to_py_native_ty(schema);
1117    }
1118
1119    let base = if elem.is_pointer {
1120        py_ctypes_void_ptr_type()
1121    } else {
1122        py_object_type()
1123    };
1124
1125    Ok(if elem.is_optional {
1126        PyTypeSchema {
1127            ty: "Optional".to_string(),
1128            generic_ty_args: vec![base],
1129        }
1130    } else {
1131        base
1132    })
1133}
1134
1135fn rs_return_ty_to_py_ty(ret_ty: &TypeSchema) -> Result<Option<PyTypeSchema>> {
1136    let inner_ty = inner_return_type(ret_ty)?;
1137
1138    if inner_ty.ty == "()" {
1139        return Ok(None);
1140    }
1141
1142    if inner_ty.ty == "Option" && inner_ty.generic_ty_args.len() > 0 {
1143        let inner_arg = &inner_ty.generic_ty_args[0];
1144        if inner_arg.ty == "Arc" && inner_arg.generic_ty_args.len() > 0 {
1145            return Ok(Some(PyTypeSchema {
1146                ty: "Optional".to_string(),
1147                generic_ty_args: vec![py_ctypes_void_ptr_type()],
1148            }));
1149        }
1150    }
1151    if inner_ty.ty == "Option" && inner_ty.generic_ty_args.len() > 0 {
1152        return Ok(Some(PyTypeSchema {
1153            ty: "Optional".to_string(),
1154            generic_ty_args: vec![py_object_type()],
1155        }));
1156    }
1157
1158    Ok(Some(py_object_type()))
1159}
1160
1161fn rs_ty_arg_to_py_ty(arg: &FunctionArgSchema) -> Result<Option<PyTypeSchema>> {
1162    let annotation_ty = if let Some(annotations) = &arg.annotations {
1163        if let Some(cffi_type) = &annotations.cffi_type {
1164            Some(py_ty_from_cffi_schema(&cffi_type_str_to_schema(
1165                cffi_type,
1166            )?)?)
1167        } else {
1168            None
1169        }
1170    } else {
1171        None
1172    };
1173
1174    if let Some(ty) = arg.ty.as_ref() {
1175        if let Some(interface_ty) = interface_names_from_ty(ty).into_iter().next() {
1176            return Ok(Some(PyTypeSchema {
1177                ty: interface_ty,
1178                generic_ty_args: vec![],
1179            }));
1180        }
1181
1182        if ty.ty == "Option" && ty.generic_ty_args.len() > 0 {
1183            return Ok(Some(PyTypeSchema {
1184                ty: "Optional".to_string(),
1185                generic_ty_args: vec![py_object_type()],
1186            }));
1187        }
1188
1189        if ty.ty == "Vec" && ty.generic_ty_args.len() > 0 {
1190            let inner_arg = &ty.generic_ty_args[0];
1191            if inner_arg.ty == "Option" && inner_arg.generic_ty_args.len() > 0 {
1192                return Ok(Some(PyTypeSchema {
1193                    ty: "list".to_string(),
1194                    generic_ty_args: vec![PyTypeSchema {
1195                        ty: "Optional".to_string(),
1196                        generic_ty_args: vec![py_object_type()],
1197                    }],
1198                }));
1199            }
1200        }
1201
1202        if ty.ty == "Vec" && ty.generic_ty_args.len() > 0 {
1203            return Ok(Some(PyTypeSchema {
1204                ty: "list".to_string(),
1205                generic_ty_args: vec![py_object_type()],
1206            }));
1207        }
1208
1209        if is_integer_ty(&ty) {
1210            return Ok(Some(PyTypeSchema {
1211                ty: "int".to_string(),
1212                generic_ty_args: vec![],
1213            }));
1214        }
1215
1216        if let Some(annotation_ty) = annotation_ty {
1217            return Ok(Some(annotation_ty));
1218        }
1219
1220        Ok(Some(py_object_type()))
1221    } else {
1222        Ok(annotation_ty)
1223    }
1224}
1225
1226fn arg_cffi_type_stack(arg: &FunctionArgSchema) -> Result<Vec<CffiTypeElementSpec>> {
1227    if let Some(annotations) = &arg.annotations {
1228        if let Some(cffi_type) = &annotations.cffi_type {
1229            return Ok(cffi_type_str_to_type_stack(cffi_type)?);
1230        }
1231    }
1232
1233    fn_arg_to_async_cffi_type_spec(arg)
1234}
1235
1236fn cffi_schema_to_elem_spec(schema: &CffiTypeSchema) -> CffiTypeElementSpec {
1237    match schema.ty.as_str() {
1238        "opt_ptr" => CffiTypeElementSpec {
1239            is_pointer: true,
1240            is_optional: true,
1241            explicit_type: None,
1242        },
1243        "ptr" => CffiTypeElementSpec {
1244            is_pointer: true,
1245            is_optional: false,
1246            explicit_type: None,
1247        },
1248        "CffiPointerBuffer" => CffiTypeElementSpec {
1249            is_pointer: true,
1250            is_optional: false,
1251            explicit_type: Some("CffiPointerBuffer".to_string()),
1252        },
1253        ty => CffiTypeElementSpec {
1254            is_pointer: false,
1255            is_optional: false,
1256            explicit_type: if ty.starts_with("Cffi") {
1257                Some(ty.to_string())
1258            } else {
1259                None
1260            },
1261        },
1262    }
1263}
1264
1265fn cffi_ty_schema_to_py_native_ty(schema: &CffiTypeSchema) -> Result<PyTypeSchema> {
1266    build_py_type_from_schema(schema)
1267}
1268
1269fn build_py_type_from_schema(schema: &CffiTypeSchema) -> Result<PyTypeSchema> {
1270    let elem = cffi_schema_to_elem_spec(schema);
1271
1272    let mut ty = PyTypeSchema {
1273        ty: cffi_ty_schema_elem_to_py_native_ty(&elem)?,
1274        generic_ty_args: Vec::new(),
1275    };
1276
1277    let next_schema = match schema.ty.as_str() {
1278        "opt_ptr" | "ptr" | "CffiPointerBuffer" => schema.generic_ty_args.get(0),
1279        _ => schema.generic_ty_args.get(0),
1280    };
1281
1282    if let Some(inner_schema) = next_schema {
1283        ty.generic_ty_args
1284            .push(build_py_type_from_schema(inner_schema)?);
1285    }
1286
1287    Ok(ty)
1288}
1289
1290fn cffi_ty_schema_elem_to_py_native_ty(elem: &CffiTypeElementSpec) -> Result<String> {
1291    Ok(if let Some(explicit_ty) = &elem.explicit_type {
1292        match explicit_ty.as_str() {
1293            "CffiPointerBuffer" => "list".to_string(),
1294            _ => "object".to_string(),
1295        }
1296    } else if elem.is_optional {
1297        "Optional".to_string()
1298    } else {
1299        "object".to_string()
1300    })
1301}
1302
1303fn trait_func_to_cffi_type(func: &FunctionSchema) -> Result<String> {
1304    let arg_types: Vec<String> = func
1305        .args
1306        .iter()
1307        .map(arg_to_py_cffi_type)
1308        .collect::<Result<_>>()?;
1309    let ret_type = return_type_to_py_cffi_type(&Some(func.return_type.clone()));
1310    Ok(format!(
1311        "ctypes.CFUNCTYPE({}, {})",
1312        ret_type,
1313        arg_types.join(", ")
1314    ))
1315}
1316
1317fn arg_to_py_cffi_type(arg: &FunctionArgSchema) -> Result<String> {
1318    if let Some(annotations) = &arg.annotations {
1319        if let Some(cffi_type) = &annotations.cffi_type {
1320            let stack = cffi_type_str_to_type_stack(cffi_type)?;
1321            if let Some(explicit) = &stack[0].explicit_type {
1322                return Ok(explicit.clone());
1323            }
1324            if stack[0].is_pointer {
1325                return Ok("ctypes.c_void_p".to_string());
1326            }
1327        }
1328    }
1329
1330    match arg.ty.as_ref() {
1331        // self_ptr
1332        None => Ok("ctypes.c_void_p".to_string()),
1333        Some(ty) => {
1334            if ty.ty == "Vec" && ty.generic_ty_args.len() > 0 {
1335                // TODO: Currently assuming Vecs always map to CffiPointerBuffer
1336                Ok("CffiPointerBuffer".to_string())
1337            } else if let Some(ctypes_ty) = ctypes_for_integer(&ty) {
1338                Ok(ctypes_ty.to_string())
1339            } else {
1340                Ok("ctypes.c_void_p".to_string())
1341            }
1342        }
1343    }
1344}
1345
1346fn is_integer_ty(ty: &TypeSchema) -> bool {
1347    matches!(
1348        ty.ty.as_str(),
1349        "usize" | "isize" | "u64" | "i64" | "u32" | "i32" | "u16" | "i16" | "u8" | "i8"
1350    )
1351}
1352
1353fn ctypes_for_integer(ty: &TypeSchema) -> Option<&'static str> {
1354    match ty.ty.as_str() {
1355        "usize" => Some("ctypes.c_ulong"),
1356        "isize" => Some("ctypes.c_long"),
1357        _ => None,
1358    }
1359}
1360
1361fn return_type_to_py_cffi_type(ret_type: &Option<TypeSchema>) -> String {
1362    match ret_type {
1363        None => "None".to_string(),
1364        Some(_) => "ctypes.c_void_p".to_string(),
1365    }
1366}
1367
1368#[cfg(test)]
1369mod tests {
1370    use std::collections::HashMap;
1371
1372    use super::*;
1373
1374    use rs_schema::{
1375        FnArgAnnotations, FunctionAnnotations, FunctionArgSchema, FunctionSchema, TraitSchema,
1376    };
1377
1378    fn inner_observer_trait_schema() -> TraitSchema {
1379        let mut pointer_buffer_annotations = FnArgAnnotations::new();
1380        pointer_buffer_annotations.cffi_type = Some("CffiPointerBuffer<opt_ptr<T>>".to_string());
1381
1382        let mut hold_strong_annotations = FunctionAnnotations::new();
1383        hold_strong_annotations.cffi_impl_no_op = true;
1384
1385        TraitSchema {
1386            name: "InnerObserverReceiver".to_string(),
1387            functions: vec![
1388                FunctionSchema {
1389                    name: "update".to_string(),
1390                    args: vec![
1391                        FunctionArgSchema {
1392                            name: "self".to_string(),
1393                            ty: None,
1394                            annotations: None,
1395                        },
1396                        FunctionArgSchema {
1397                            name: "data".to_string(),
1398                            ty: Some(TypeSchema {
1399                                ty: "Vec".to_string(),
1400                                generic_ty_args: vec![TypeSchema {
1401                                    ty: "Option".to_string(),
1402                                    generic_ty_args: vec![TypeSchema {
1403                                        ty: "AnyArc".to_string(),
1404                                        generic_ty_args: vec![],
1405                                    }],
1406                                }],
1407                            }),
1408                            annotations: Some(pointer_buffer_annotations),
1409                        },
1410                    ],
1411                    body: None,
1412                    return_type: TypeSchema {
1413                        ty: "BoxFuture".to_string(),
1414                        generic_ty_args: vec![
1415                            TypeSchema {
1416                                ty: "'_".to_string(),
1417                                generic_ty_args: vec![],
1418                            },
1419                            TypeSchema {
1420                                ty: "()".to_string(),
1421                                generic_ty_args: vec![],
1422                            },
1423                        ],
1424                    },
1425                    extern_layout: None,
1426                    annotations: None,
1427                },
1428                FunctionSchema {
1429                    name: "hold_strong_publisher_ref".to_string(),
1430                    args: vec![
1431                        FunctionArgSchema {
1432                            name: "self".to_string(),
1433                            ty: None,
1434                            annotations: None,
1435                        },
1436                        FunctionArgSchema {
1437                            name: "publisher".to_string(),
1438                            ty: Some(TypeSchema {
1439                                ty: "Arc".to_string(),
1440                                generic_ty_args: vec![TypeSchema {
1441                                    ty: "dyn Publisher + Send + Sync".to_string(),
1442                                    generic_ty_args: vec![],
1443                                }],
1444                            }),
1445                            annotations: None,
1446                        },
1447                    ],
1448                    body: None,
1449                    return_type: TypeSchema {
1450                        ty: "BoxFuture".to_string(),
1451                        generic_ty_args: vec![
1452                            TypeSchema {
1453                                ty: "'_".to_string(),
1454                                generic_ty_args: vec![],
1455                            },
1456                            TypeSchema {
1457                                ty: "()".to_string(),
1458                                generic_ty_args: vec![],
1459                            },
1460                        ],
1461                    },
1462                    extern_layout: None,
1463                    annotations: Some(hold_strong_annotations),
1464                },
1465            ],
1466            generics: vec![],
1467            supertraits: vec![],
1468        }
1469    }
1470
1471    fn inner_observer_py_codegen() -> String {
1472        trait_to_async_cffi_schema(&inner_observer_trait_schema(), &HashMap::new())
1473            .expect("should generate schema")
1474            .codegen(4)
1475    }
1476
1477    fn publisher_trait_schema() -> TraitSchema {
1478        let mut inner_observer_annotations = FnArgAnnotations::new();
1479        inner_observer_annotations.cffi_type = Some("CffiInnerObserverReceiver".to_string());
1480
1481        TraitSchema {
1482            name: "Publisher".to_string(),
1483            functions: vec![
1484                FunctionSchema {
1485                    name: "add_observer".to_string(),
1486                    args: vec![
1487                        FunctionArgSchema {
1488                            name: "self".to_string(),
1489                            ty: None,
1490                            annotations: None,
1491                        },
1492                        FunctionArgSchema {
1493                            name: "observer".to_string(),
1494                            ty: Some(TypeSchema {
1495                                ty: "Box".to_string(),
1496                                generic_ty_args: vec![TypeSchema {
1497                                    ty: "dyn InnerObserverReceiver".to_string(),
1498                                    generic_ty_args: vec![],
1499                                }],
1500                            }),
1501                            annotations: Some(inner_observer_annotations),
1502                        },
1503                        FunctionArgSchema {
1504                            name: "input_index".to_string(),
1505                            ty: Some(TypeSchema {
1506                                ty: "usize".to_string(),
1507                                generic_ty_args: vec![],
1508                            }),
1509                            annotations: None,
1510                        },
1511                    ],
1512                    body: None,
1513                    return_type: TypeSchema {
1514                        ty: "BoxFuture".to_string(),
1515                        generic_ty_args: vec![
1516                            TypeSchema {
1517                                ty: "'_".to_string(),
1518                                generic_ty_args: vec![],
1519                            },
1520                            TypeSchema {
1521                                ty: "Option".to_string(),
1522                                generic_ty_args: vec![TypeSchema::new_simple("AnyArc".to_string())],
1523                            },
1524                        ],
1525                    },
1526                    extern_layout: None,
1527                    annotations: None,
1528                },
1529                FunctionSchema {
1530                    name: "notify".to_string(),
1531                    args: vec![
1532                        FunctionArgSchema {
1533                            name: "self".to_string(),
1534                            ty: None,
1535                            annotations: None,
1536                        },
1537                        FunctionArgSchema {
1538                            name: "value".to_string(),
1539                            ty: Some(TypeSchema {
1540                                ty: "AnyArc".to_string(),
1541                                generic_ty_args: vec![],
1542                            }),
1543                            annotations: None,
1544                        },
1545                    ],
1546                    body: None,
1547                    return_type: TypeSchema {
1548                        ty: "BoxFuture".to_string(),
1549                        generic_ty_args: vec![
1550                            TypeSchema {
1551                                ty: "'_".to_string(),
1552                                generic_ty_args: vec![],
1553                            },
1554                            TypeSchema {
1555                                ty: "()".to_string(),
1556                                generic_ty_args: vec![],
1557                            },
1558                        ],
1559                    },
1560                    extern_layout: None,
1561                    annotations: None,
1562                },
1563            ],
1564            generics: vec![],
1565            supertraits: vec![],
1566        }
1567    }
1568
1569    #[test]
1570    fn test_inner_observer_generator_and_boxed_returns() {
1571        let code = inner_observer_py_codegen();
1572
1573        assert!(
1574            code.contains("class CffiInnerObserverReceiverGenerator"),
1575            "generator wrapper should be emitted"
1576        );
1577        assert!(
1578            code.contains("_update_boxed"),
1579            "boxed update helper should be emitted"
1580        );
1581        assert!(
1582            code.contains("return self._async_cffi.box_u64(ctypes.c_uint64(42))"),
1583            "void futures should be boxed to non-null pointers"
1584        );
1585    }
1586
1587    #[test]
1588    fn test_inner_observer_pointer_buffer_handling() {
1589        let code = inner_observer_py_codegen();
1590
1591        assert!(
1592            code.contains("CffiPointerBuffer.from_pointer_array(["),
1593            "update should wrap Python lists into CffiPointerBuffer"
1594        );
1595        assert!(
1596            code.contains("typing.cast(ctypes.c_void_p, data) if data is not None else None"),
1597            "pointer buffer transform should cast non-null entries"
1598        );
1599        assert!(
1600            code.contains("data.to_pointer_array()"),
1601            "boxed helper should expand pointer buffers back to Python lists"
1602        );
1603        assert!(
1604            code.contains("data_item is None") && code.contains("ctypes.c_void_p"),
1605            "boxed helper should treat null and void pointers as None"
1606        );
1607    }
1608
1609    #[test]
1610    fn test_inner_observer_generator_uses_fut_type_fields() {
1611        let code = inner_observer_py_codegen();
1612
1613        assert!(
1614            code.contains("CffiInnerObserverReceiver._update_fut_type"),
1615            "update fut type alias should be referenced"
1616        );
1617        assert!(
1618            code.contains("CffiInnerObserverReceiver._hold_strong_publisher_ref_fut_type"),
1619            "hold strong fut type alias should be referenced"
1620        );
1621    }
1622
1623    #[test]
1624    fn test_inner_observer_passthrough_methods_include_self_and_method_calls() {
1625        let code = inner_observer_py_codegen();
1626
1627        assert!(
1628            code.contains("async def update(self, data"),
1629            "passthrough update should include self parameter"
1630        );
1631        assert!(
1632            code.contains("return await self._py_inner_observer_receiver.update(data)"),
1633            "passthrough should invoke the underlying Python implementation"
1634        );
1635        assert!(
1636            code.contains("async def hold_strong_publisher_ref(self)"),
1637            "no-op passthrough should include only self parameter"
1638        );
1639        assert!(
1640            code.contains("return self._async_cffi.box_u64(ctypes.c_uint64(1))"),
1641            "no-op passthrough should return a boxed non-null pointer"
1642        );
1643    }
1644
1645    #[test]
1646    fn test_inner_observer_boxed_unit_returns_use_non_null_pointer() {
1647        let code = inner_observer_py_codegen();
1648
1649        assert!(
1650            code.contains("return self._async_cffi.box_u64(ctypes.c_uint64(42))"),
1651            "unit return futures should be boxed to a non-null pointer value"
1652        );
1653    }
1654
1655    #[test]
1656    fn test_publisher_option_handling_and_observer_wrapping() {
1657        let code = trait_to_async_cffi_schema(&publisher_trait_schema(), &HashMap::new())
1658            .expect("publisher schema generation should succeed")
1659            .codegen(4);
1660
1661        println!("publisher codegen:\n{}", code);
1662
1663        assert!(
1664            code.contains("from n_observer.core import IPublisher, IInnerObserverReceiver"),
1665            "interface imports should include publisher and dependencies"
1666        );
1667        assert!(
1668            code.contains("CffiInnerObserverReceiverGenerator"),
1669            "publisher bindings should import inner observer receiver generator"
1670        );
1671        assert!(
1672            code.contains(
1673                "observer = CffiInnerObserverReceiverGenerator(ASYNC_CFFI_LIB, asyncio.get_event_loop(), observer).cffi_inner_observer_receiver"
1674            ),
1675            "add_observer should wrap python observers in cffi generators"
1676        );
1677        assert!(
1678            code.contains("ptr_ptr = ctypes.cast(fut_result, ctypes.POINTER(ctypes.c_void_p))"),
1679            "option returns should cast to pointer-to-pointer"
1680        );
1681        assert!(
1682            code.contains("-> Optional[object]"),
1683            "add_observer return signature should surface Optional typing"
1684        );
1685    }
1686
1687    #[test]
1688    fn test_supertrait_interfaces_are_imported() {
1689        let mut schema = inner_observer_trait_schema();
1690        schema.name = "Observable".to_string();
1691        schema.supertraits = vec![
1692            TypeSchema::new_simple("Publisher".to_string()),
1693            TypeSchema::new_simple("InnerObserverReceiver".to_string()),
1694        ];
1695
1696        let supertraits = HashMap::from([
1697            ("Publisher".to_string(), publisher_trait_schema()),
1698            (
1699                "InnerObserverReceiver".to_string(),
1700                inner_observer_trait_schema(),
1701            ),
1702        ]);
1703
1704        let code = trait_to_async_cffi_schema(&schema, &supertraits)
1705            .expect("observable schema generation should succeed")
1706            .codegen(4);
1707
1708        assert!(
1709            code.contains(
1710                "from n_observer.core import IObservable, IPublisher, IInnerObserverReceiver"
1711            ),
1712            "supertrait interface imports should be included for observable traits"
1713        );
1714    }
1715
1716    #[test]
1717    fn test_arg_to_py_cffi_type_none() {
1718        let arg = FunctionArgSchema {
1719            name: "self_ptr".to_string(),
1720            ty: None,
1721            annotations: None,
1722        };
1723        assert_eq!(arg_to_py_cffi_type(&arg).unwrap(), "ctypes.c_void_p");
1724    }
1725
1726    #[test]
1727    fn test_arg_to_py_cffi_type_vec() {
1728        let arg = FunctionArgSchema {
1729            name: "vals".to_string(),
1730            ty: Some(TypeSchema {
1731                ty: "Vec".to_string(),
1732                generic_ty_args: vec![TypeSchema {
1733                    ty: "i32".to_string(),
1734                    generic_ty_args: vec![],
1735                }],
1736            }),
1737            annotations: None,
1738        };
1739        assert_eq!(arg_to_py_cffi_type(&arg).unwrap(), "CffiPointerBuffer");
1740    }
1741
1742    #[test]
1743    fn test_arg_to_py_cffi_type_other() {
1744        let arg = FunctionArgSchema {
1745            name: "x".to_string(),
1746            ty: Some(TypeSchema {
1747                ty: "i32".to_string(),
1748                generic_ty_args: vec![],
1749            }),
1750            annotations: None,
1751        };
1752        assert_eq!(arg_to_py_cffi_type(&arg).unwrap(), "ctypes.c_void_p");
1753    }
1754
1755    #[test]
1756    fn test_arg_to_py_cffi_type_explicit_annotation() {
1757        let arg = FunctionArgSchema {
1758            name: "x".to_string(),
1759            ty: Some(TypeSchema {
1760                ty: "Box".to_string(),
1761                generic_ty_args: vec![TypeSchema {
1762                    ty: "dyn InnerObserverReceiver".to_string(),
1763                    generic_ty_args: vec![],
1764                }],
1765            }),
1766            annotations: Some({
1767                let mut annotations = FnArgAnnotations::new();
1768                annotations.cffi_type = Some("CffiInnerObserverReceiver".to_string());
1769                annotations
1770            }),
1771        };
1772        assert_eq!(
1773            arg_to_py_cffi_type(&arg).unwrap(),
1774            "CffiInnerObserverReceiver"
1775        );
1776    }
1777
1778    #[test]
1779    fn test_return_type_to_py_cffi_type() {
1780        assert_eq!(return_type_to_py_cffi_type(&None), "None");
1781        assert_eq!(
1782            return_type_to_py_cffi_type(&Some(TypeSchema {
1783                ty: "i32".to_string(),
1784                generic_ty_args: vec![]
1785            })),
1786            "ctypes.c_void_p"
1787        );
1788    }
1789
1790    #[test]
1791    fn test_trait_func_to_cffi_type_format() {
1792        let func = FunctionSchema {
1793            name: "foo".to_string(),
1794            args: vec![
1795                FunctionArgSchema {
1796                    name: "self_ptr".to_string(),
1797                    ty: None,
1798                    annotations: None,
1799                },
1800                FunctionArgSchema {
1801                    name: "vals".to_string(),
1802                    ty: Some(TypeSchema {
1803                        ty: "Vec".to_string(),
1804                        generic_ty_args: vec![TypeSchema {
1805                            ty: "u8".to_string(),
1806                            generic_ty_args: vec![],
1807                        }],
1808                    }),
1809                    annotations: None,
1810                },
1811            ],
1812            body: None,
1813            return_type: TypeSchema {
1814                ty: "()".to_string(),
1815                generic_ty_args: vec![],
1816            },
1817            extern_layout: None,
1818            annotations: None,
1819        };
1820
1821        // return_type None -> "None" in our helper, but trait_func_to_cffi_type wraps it
1822        let got = trait_func_to_cffi_type(&func).unwrap();
1823        // Should contain the CFUNCTYPE and the two arg types
1824        assert!(got.starts_with("ctypes.CFUNCTYPE("));
1825        assert!(got.contains("ctypes.c_void_p"));
1826        assert!(got.contains("CffiPointerBuffer"));
1827    }
1828
1829    #[test]
1830    fn test_trait_to_async_cffi_schema_generates_class() {
1831        let func = FunctionSchema {
1832            name: "update".to_string(),
1833            args: vec![FunctionArgSchema {
1834                name: "self_ptr".to_string(),
1835                ty: None,
1836                annotations: None,
1837            }],
1838            body: None,
1839            return_type: TypeSchema {
1840                ty: "()".to_string(),
1841                generic_ty_args: vec![],
1842            },
1843            extern_layout: None,
1844            annotations: None,
1845        };
1846
1847        let trait_schema = TraitSchema {
1848            name: "MyTrait".to_string(),
1849            functions: vec![func],
1850            generics: vec![],
1851            supertraits: vec![],
1852        };
1853
1854        let schema = trait_to_async_cffi_schema(&trait_schema, &HashMap::new())
1855            .expect("should generate schema");
1856        let code = schema.codegen(4);
1857        assert!(code.contains("import ctypes"));
1858        assert!(code.contains("class CffiMyTrait("));
1859        assert!(code.contains("_fields_"));
1860        assert!(code.contains("update"));
1861        assert!(code.contains("class CffiMyTraitGenerator"));
1862        assert!(code.contains("PyCffiFuture"));
1863        assert!(code.contains("_update_boxed"));
1864    }
1865}