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 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 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 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>, 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 .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 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 None => Ok("ctypes.c_void_p".to_string()),
1333 Some(ty) => {
1334 if ty.ty == "Vec" && ty.generic_ty_args.len() > 0 {
1335 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 let got = trait_func_to_cffi_type(&func).unwrap();
1823 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}