use std::{collections::HashMap, fmt::Write as _};
use anyhow::{Context, Result, anyhow, bail};
use handlebars::Handlebars;
use heck::ToSnakeCase as _;
use serde_json::json;
use rs_schema::{
FunctionArgSchema, FunctionSchema, GenericParamSchema, TraitSchema, TypeSchema,
};
use crate::{
cffi_type_utils::{cffi_ty_stack_to_rust_cffi_ty, cffi_type_str_to_type_stack},
codegen::CodeGen,
rs_type_utils::{
annotation_transforms_for_arg, fn_arg_to_async_cffi_type_spec, substitute_generic_args,
},
};
#[derive(Debug)]
pub struct RsFileSchema {
pub structs: Vec<RsStructSchema>,
pub trait_impls: Vec<RsTraitImplSchema>,
}
impl CodeGen for RsFileSchema {
fn codegen(&self, indent: usize) -> String {
let mut output = String::new();
writeln!(
&mut output,
"// This file is autogenerated. Do not edit directly."
)
.unwrap();
writeln!(
&mut output,
"use std::{{ffi::{{c_ulong, c_void}}, sync::Arc}};\n"
)
.unwrap();
writeln!(
&mut output,
"use async_cffi::{{CffiFuture, CffiPointerBuffer, SafePtr}};\n"
)
.unwrap();
writeln!(&mut output, "use futures::future::BoxFuture;\n").unwrap();
writeln!(
&mut output,
"use n_observer::{{AnyArc, InnerObserverReceiver, Observable, Publisher}};\n"
)
.unwrap();
if self.structs.iter().any(|s| s.name == "CffiObservable") {
writeln!(&mut output, "use crate::CffiPublisher;\n").unwrap();
}
if self
.structs
.iter()
.any(|s| s.name == "CffiPublisher" || s.name == "CffiObservable")
{
writeln!(&mut output, "use crate::CffiInnerObserverReceiver;\n").unwrap();
}
for s in &self.structs {
writeln!(&mut output, "{}", s.codegen(indent)).unwrap();
}
writeln!(&mut output).unwrap();
for ti in &self.trait_impls {
writeln!(&mut output, "{}\n", ti.codegen(indent)).unwrap();
}
output
}
}
#[derive(Debug)]
pub struct RsStructSchema {
macros: Vec<String>,
name: String,
fields: Vec<RsFieldSchema>,
}
impl CodeGen for RsStructSchema {
fn codegen(&self, indent: usize) -> String {
let pad = " ".repeat(indent);
let mut output = String::new();
for m in &self.macros {
writeln!(&mut output, "{}{}", pad, m).unwrap();
}
writeln!(&mut output, "{}pub struct {} {{", pad, self.name).unwrap();
for field in &self.fields {
writeln!(&mut output, "{}", field.codegen(indent + 1)).unwrap();
}
write!(&mut output, "{}}}", pad).unwrap();
output
}
}
#[derive(Debug)]
pub struct RsFieldSchema {
name: String,
field_type: TypeSchema,
}
impl CodeGen for RsFieldSchema {
fn codegen(&self, indent: usize) -> String {
let pad = " ".repeat(indent);
format!("{}pub {}: {},", pad, self.name, self.field_type.codegen(0))
}
}
#[derive(Debug)]
pub struct RsTraitImplSchema {
pub trait_name: String,
pub impl_for: Option<String>,
pub generic_args: Vec<String>,
pub functions: Vec<FunctionSchema>,
pub unsafe_impl: bool,
pub comment_out: bool,
}
impl CodeGen for RsTraitImplSchema {
fn codegen(&self, indent: usize) -> String {
let pad = " ".repeat(indent);
let mut output = String::new();
if self.comment_out {
writeln!(&mut output, "{}/*", pad).unwrap();
}
let unsafe_str = if self.unsafe_impl { "unsafe " } else { "" };
let impl_for_str = if let Some(impl_for) = &self.impl_for {
format!("for {} ", impl_for)
} else {
String::new()
};
let generic_args_str = if self.generic_args.len() == 0 {
String::new()
} else {
let args = self.generic_args.join(", ");
format!("<{}>", args)
};
writeln!(
&mut output,
"{}{}impl {}{} {}{{",
pad, unsafe_str, self.trait_name, generic_args_str, impl_for_str
)
.unwrap();
for func in &self.functions {
let funct = func.codegen(indent + 1);
funct
.split_inclusive('\n')
.for_each(|line| writeln!(&mut output, "{}", line).unwrap());
}
write!(&mut output, "{}}}", pad).unwrap();
if self.comment_out {
writeln!(&mut output, "\n{}*/", pad).unwrap();
}
output
}
}
impl CodeGen for FunctionSchema {
fn codegen(&self, indent: usize) -> String {
let pad = " ".repeat(indent);
let mut output = String::new();
format!("{}", self)
.split_inclusive('\n')
.for_each(|line| write!(&mut output, "{}{}", pad, line).unwrap());
output
}
}
struct FnArgTransforms {
pub transforms: Option<String>,
pub returns_safe_ptr: bool,
}
pub fn trait_to_async_cffi_schema(
trait_schema: &TraitSchema,
supertrait_schemas: &HashMap<String, TraitSchema>,
) -> Result<RsFileSchema> {
let cffi_struct_schema = create_cffi_struct_schema(trait_schema)?;
let supertrait_impl_schemas = trait_schema
.supertraits
.iter()
.filter(|st| st.ty != "Send" && st.ty != "Sync")
.map(|st| {
supertrait_schemas
.get(&st.ty)
.with_context(|| format!("Missing supertrait schema: {}", st.ty))
})
.collect::<Result<Vec<_>, _>>()?
.into_iter()
.map(|st| create_supertrait_impl(st, &cffi_struct_schema))
.collect::<Result<Vec<_>, _>>()?;
let trait_impl_schema =
trait_to_async_cffi_trait_impl_schema(trait_schema, &cffi_struct_schema)?;
let send_impl_schema = RsTraitImplSchema {
trait_name: "Send".to_string(),
impl_for: Some(cffi_struct_schema.name.clone()),
generic_args: vec![],
functions: vec![],
unsafe_impl: true,
comment_out: false,
};
let sync_impl_schema = RsTraitImplSchema {
trait_name: "Sync".to_string(),
impl_for: Some(cffi_struct_schema.name.clone()),
generic_args: vec![],
functions: vec![],
unsafe_impl: true,
comment_out: false,
};
let cffi_struct_impls = cffi_struct_impls(trait_schema, &cffi_struct_schema)?;
let dyn_rust_trait_to_cffi = RsTraitImplSchema {
trait_name: "From".to_string(),
impl_for: Some(cffi_struct_schema.name.clone()),
functions: vec![trait_schema_to_from_impl(trait_schema)?],
generic_args: vec![format!(
"&dyn {}",
get_trait_name_w_subbed_generics(trait_schema)?
)],
unsafe_impl: false,
comment_out: false,
};
Ok(RsFileSchema {
structs: vec![cffi_struct_schema],
trait_impls: {
let mut trait_impls = supertrait_impl_schemas;
trait_impls.extend(vec![
trait_impl_schema,
send_impl_schema,
sync_impl_schema,
cffi_struct_impls,
dyn_rust_trait_to_cffi,
]);
trait_impls
},
})
}
fn cffi_struct_impls(
trait_schema: &TraitSchema,
cffi_struct_schema: &RsStructSchema,
) -> Result<RsTraitImplSchema> {
let as_supertrait_fns = trait_schema
.supertraits
.iter()
.filter(|st| st.ty != "Send" && st.ty != "Sync")
.map(|st| create_as_supertrait_fn(st))
.collect::<Result<Vec<_>, _>>()?;
let trait_cffi_impls = trait_schema
.functions
.iter()
.map(|func| trait_fn_to_cffi_c_fn(func, trait_schema, &trait_schema.generics))
.collect::<Result<Vec<_>, _>>()?;
Ok(RsTraitImplSchema {
trait_name: cffi_struct_schema.name.clone(),
impl_for: None,
generic_args: vec![],
functions: {
let mut functions = as_supertrait_fns;
functions.extend(trait_cffi_impls);
functions
},
unsafe_impl: false,
comment_out: false,
})
}
fn create_as_supertrait_fn(supertrait: &TypeSchema) -> Result<FunctionSchema> {
Ok(FunctionSchema {
name: format!("as_{}", supertrait.ty.to_snake_case()),
args: vec![FunctionArgSchema {
name: "self".to_string(),
ty: None,
annotations: None,
}],
return_type: TypeSchema {
ty: format!("&dyn {}", supertrait.ty),
generic_ty_args: supertrait.generic_ty_args.clone(),
},
body: Some(format!("&self.cffi_{}", supertrait.ty.to_snake_case())),
extern_layout: None,
annotations: None,
})
}
fn create_supertrait_impl(
supertrait: &TraitSchema,
cffi_struct_schema: &RsStructSchema,
) -> Result<RsTraitImplSchema> {
let functions = supertrait
.functions
.iter()
.map(|func| supertrait_fn_to_async_cffi_fn(supertrait, func, &supertrait.generics))
.collect::<Result<Vec<_>, _>>()?;
Ok(RsTraitImplSchema {
trait_name: supertrait.name.clone(),
impl_for: Some(cffi_struct_schema.name.clone()),
generic_args: supertrait
.generics
.iter()
.map(|p| {
p.annotations
.as_ref()
.and_then(|a| a.cffi_type.as_ref())
.ok_or_else(|| {
anyhow!("Missing cffi_type annotation for trait generic argument")
})
.and_then(|cffi_ty| {
cffi_type_str_to_type_stack(cffi_ty)
.and_then(|stack| cffi_ty_stack_to_rust_cffi_ty(&stack))
})
})
.collect::<Result<Vec<_>, _>>()?,
functions,
unsafe_impl: false,
comment_out: false,
})
}
fn trait_to_async_cffi_trait_impl_schema(
trait_schema: &TraitSchema,
cffi_struct_schema: &RsStructSchema,
) -> Result<RsTraitImplSchema> {
let functions = trait_schema
.functions
.iter()
.map(|func| trait_fn_to_async_cffi_fn(func, &trait_schema.generics))
.collect::<Result<Vec<_>, _>>()?;
Ok(RsTraitImplSchema {
trait_name: trait_schema.name.clone(),
impl_for: Some(cffi_struct_schema.name.clone()),
generic_args: cffi_substituted_trait_generic_arg_types(trait_schema)?,
functions,
unsafe_impl: false,
comment_out: false,
})
}
fn cffi_substituted_trait_generic_arg_types(trait_schema: &TraitSchema) -> Result<Vec<String>> {
Ok(trait_schema
.generics
.iter()
.map(|p| {
p.annotations
.as_ref()
.and_then(|a| a.cffi_type.as_ref())
.ok_or_else(|| anyhow!("Missing cffi_type annotation for trait generic argument"))
.and_then(|cffi_ty| {
cffi_type_str_to_type_stack(cffi_ty)
.and_then(|stack| cffi_ty_stack_to_rust_cffi_ty(&stack))
})
})
.collect::<Result<Vec<_>, _>>()?)
}
fn create_cffi_struct_schema(trait_schema: &TraitSchema) -> Result<RsStructSchema> {
let macros = vec![
"#[repr(C)]".to_string(),
"#[derive(Debug, Clone)]".to_string(),
];
let name = format!("Cffi{}", trait_schema.name);
let supertrait_fields = trait_schema
.supertraits
.iter()
.filter(|st| st.ty != "Send" && st.ty != "Sync")
.map(|st| RsFieldSchema {
name: format!("cffi_{}", st.ty.to_snake_case()),
field_type: TypeSchema::new_simple(format!("Cffi{}", st.ty)),
})
.collect::<Vec<_>>();
let trait_fn_fields = trait_schema
.functions
.iter()
.map(|func| {
func.args
.iter()
.map(arg_to_field_type)
.collect::<Result<Vec<TypeSchema>>>()
.map(|args| RsFieldSchema {
name: format!("{}_fut", func.name),
field_type: TypeSchema::new_simple(format!(
"extern \"C\" fn({}) -> *const c_void",
args.into_iter()
.map(|ty| ty.codegen(0))
.collect::<Vec<_>>()
.join(", ")
)),
})
})
.collect::<Result<Vec<RsFieldSchema>, _>>()?;
let fields = {
let mut fs = vec![RsFieldSchema {
name: "self_ptr".to_string(),
field_type: TypeSchema::new_simple("*const c_void".to_string()),
}];
fs.extend(supertrait_fields);
fs.extend(trait_fn_fields);
fs
};
let struct_schema = RsStructSchema {
macros,
name,
fields,
};
Ok(struct_schema)
}
fn supertrait_fn_to_async_cffi_fn(
supertrait: &TraitSchema,
func: &FunctionSchema,
generics: &Vec<GenericParamSchema>,
) -> Result<FunctionSchema> {
let ret_substituted = substitute_generic_args(&func.return_type, generics)?;
Ok(FunctionSchema {
name: func.name.clone(),
args: func
.args
.iter()
.map(|arg| FunctionArgSchema {
name: arg.name.clone(),
ty: arg.ty.clone(),
annotations: None,
})
.collect(),
return_type: ret_substituted,
body: Some(supertrait_fn_to_async_cffi_fn_body(supertrait, func)?),
extern_layout: None,
annotations: None,
})
}
fn arg_to_field_type(arg: &FunctionArgSchema) -> Result<TypeSchema> {
let cffi_type_stack = fn_arg_to_async_cffi_type_spec(arg)?;
let cffi_type = cffi_type_stack
.into_iter()
.next()
.and_then(|ty| ty.explicit_type);
Ok(TypeSchema {
ty: cffi_type.unwrap_or("*const c_void".to_string()),
generic_ty_args: vec![],
})
}
fn trait_fn_to_async_cffi_fn(
func: &FunctionSchema,
generics: &Vec<GenericParamSchema>,
) -> Result<FunctionSchema> {
let ret_substituted = substitute_generic_args(&func.return_type, generics)?;
Ok(FunctionSchema {
name: func.name.clone(),
args: func
.args
.iter()
.map(|arg| FunctionArgSchema {
name: arg.name.clone(),
ty: arg.ty.clone(),
annotations: None,
})
.collect(),
return_type: ret_substituted,
body: Some(trait_fn_to_async_cffi_fn_body(func, generics)?),
extern_layout: None,
annotations: None,
})
}
fn trait_fn_to_cffi_c_fn(
func: &FunctionSchema,
trait_schema: &TraitSchema,
generics: &Vec<GenericParamSchema>,
) -> Result<FunctionSchema> {
Ok(FunctionSchema {
name: format!("{}_fut_impl", func.name),
args: func
.args
.iter()
.map(|a| arg_to_field_type(a).map(|s| (a, s)))
.collect::<Result<Vec<_>, _>>()?
.into_iter()
.map(|(a, at)| FunctionArgSchema {
name: if a.name == "self" {
"self_ptr".to_string()
} else {
a.name.clone()
},
ty: Some(at.clone()),
annotations: None,
})
.collect(),
return_type: TypeSchema {
ty: "*const c_void".to_string(),
generic_ty_args: vec![],
},
body: Some(trait_fn_to_cffi_c_fn_body(func, trait_schema, generics)?),
extern_layout: Some("C".to_string()),
annotations: None,
})
}
fn trait_fn_to_cffi_c_fn_body(
func: &FunctionSchema,
trait_schema: &TraitSchema,
generics: &Vec<GenericParamSchema>,
) -> Result<String> {
let mut fn_body = String::new();
if let Some(annotations) = &func.annotations {
if annotations.cffi_impl_no_op {
writeln!(fn_body, "// no-op").unwrap();
writeln!(
fn_body,
"CffiFuture::from_rust_future_boxed(async move {{}}).into_raw()"
)
.unwrap();
return Ok(fn_body);
}
}
let arg_transforms = func
.args
.iter()
.map(|arg| cffi_c_fn_arg_to_transforms(arg, trait_schema))
.collect::<Result<Vec<String>>>()?;
arg_transforms
.iter()
.try_for_each(|t| writeln!(fn_body, "{}\n", t))
.context("Failed to write argument transforms for CFFI trampoline")?;
let ret_ty = &func.return_type;
if ret_ty.ty != "BoxFuture" && ret_ty.generic_ty_args.len() != 2 {
bail!("Only BoxFuture returning trait functions are supported");
}
let async_ret_ty = &ret_ty.generic_ty_args[1];
if async_ret_ty.ty == "()" {
writeln!(
fn_body,
"let fut = self_ref.{}({});",
func.name,
func.args
.iter()
.skip(1)
.map(|a| a.name.clone())
.collect::<Vec<String>>()
.join(", ")
)
.unwrap();
writeln!(
fn_body,
"CffiFuture::from_rust_future_boxed(fut).into_raw()"
)
.unwrap();
} else {
writeln!(
fn_body,
"let fut = Box::pin(async move {{\n let ret = self_ref.{}({}).await;",
func.name,
func.args
.iter()
.skip(1)
.map(|a| a.name.clone())
.collect::<Vec<String>>()
.join(", ")
)
.unwrap();
let async_ret_ty_subbed = substitute_generic_args(async_ret_ty, generics)?;
writeln!(
fn_body,
"{}",
type_transforms_for_cffi_c_fn_return(&async_ret_ty_subbed, "ret")?
)
.unwrap();
writeln!(fn_body, " SafePtr(ret)").unwrap();
writeln!(fn_body, "}});").unwrap();
writeln!(
fn_body,
"CffiFuture::from_rust_future_boxed(fut).into_raw()"
)
.unwrap();
}
Ok(fn_body)
}
fn cffi_c_fn_arg_to_transforms(
arg: &FunctionArgSchema,
trait_schema: &TraitSchema,
) -> Result<String> {
let target_type = fn_arg_to_async_cffi_type_spec(arg)?;
let target_type = target_type.into_iter().next().unwrap();
if let Some(arg_ty) = &arg.ty {
if let Some(explicit_type) = target_type.explicit_type.as_ref() {
match explicit_type.as_str() {
"CffiPointerBuffer" => {
let mut transform = format!("let {} = {}\n", arg.name, arg.name);
writeln!(transform, " .as_slice()").unwrap();
writeln!(transform, " .iter()").unwrap();
writeln!(transform, " .copied()").unwrap();
writeln!(transform, " .map(|value| {{").unwrap();
writeln!(transform, " if !value.is_null() {{").unwrap();
writeln!(transform, " let value = SafePtr(value);").unwrap();
writeln!(transform, " Some(Arc::new(value) as AnyArc)").unwrap();
writeln!(transform, " }} else {{").unwrap();
writeln!(transform, " None").unwrap();
writeln!(transform, " }}").unwrap();
writeln!(transform, " }})").unwrap();
writeln!(transform, " .collect();").unwrap();
Ok(transform)
}
"c_ulong" => {
let transform = format!("let {} = {} as usize;", arg.name, arg.name);
Ok(transform)
}
explicit_type => {
if explicit_type.starts_with("Cffi") {
let transform = format!("let {} = Box::new({});", arg.name, arg.name);
Ok(transform)
} else {
Ok(String::new())
}
}
}
} else {
if arg_ty.ty == "AnyArc" {
let transform = format!("let {} = Arc::new(SafePtr({}));", arg.name, arg.name);
Ok(transform)
} else {
Ok(String::new())
}
}
} else {
let mut transform = String::new();
writeln!(transform, "let self_ref = unsafe {{").unwrap();
writeln!(
transform,
" (self_ptr as *const &(dyn {} + Send + Sync))",
get_trait_name_w_subbed_generics(trait_schema)?,
)
.unwrap();
writeln!(transform, " .as_ref()").unwrap();
writeln!(transform, " .expect(\"Self pointer cannot be null\")").unwrap();
writeln!(transform, "}};").unwrap();
Ok(transform)
}
}
fn get_trait_name_w_subbed_generics(trait_schema: &TraitSchema) -> Result<String> {
let generic_args = if trait_schema.generics.len() > 0 {
format!(
"<{}>",
cffi_substituted_trait_generic_arg_types(trait_schema)?.join(", ")
)
} else {
String::new()
};
Ok(format!("{}{}", trait_schema.name, generic_args))
}
fn trait_fn_to_async_cffi_fn_body(
func: &FunctionSchema,
generics: &Vec<GenericParamSchema>,
) -> Result<String> {
let mut fn_body = String::new();
let arg_assertions = func
.args
.iter()
.map(fn_arg_to_assertions)
.collect::<Vec<String>>()
.join("");
fn_body.push_str(&arg_assertions);
fn_body.push('\n');
let arg_transforms = func
.args
.iter()
.map(fn_arg_to_transforms)
.collect::<Result<Vec<FnArgTransforms>>>()?;
let safe_ptr_args = arg_transforms
.iter()
.map(|ts| ts.returns_safe_ptr)
.collect::<Vec<_>>();
let arg_transforms = arg_transforms
.into_iter()
.map(|ts| ts.transforms.unwrap_or_default())
.collect::<Vec<_>>()
.join("");
fn_body.push_str(&arg_transforms);
fn_body.push('\n');
let async_fn_isolation = format!("let {}_fn = self.{}_fut;", func.name, func.name);
fn_body.push_str(&async_fn_isolation);
fn_body.push('\n');
let wrapped_self_ptr = "let self_ptr = SafePtr(self.self_ptr);";
fn_body.push_str(wrapped_self_ptr);
fn_body.push('\n');
let fn_async_block = async_block_for_trait_fn(func, safe_ptr_args, generics)?;
fn_body.push_str(&fn_async_block);
fn_body.push('\n');
Ok(fn_body)
}
fn supertrait_fn_to_async_cffi_fn_body(
supertrait: &TraitSchema,
func: &FunctionSchema,
) -> Result<String> {
let args = func
.args
.iter()
.skip(1) .map(|a| a.name.clone())
.collect::<Vec<_>>()
.join(", ");
Ok(format!(
"self.as_{}().{}({})",
supertrait.name.to_snake_case(),
func.name,
args,
))
}
fn async_block_for_trait_fn(
func: &FunctionSchema,
safe_ptr_args: Vec<bool>,
generics: &Vec<GenericParamSchema>,
) -> Result<String> {
let mut async_block = "Box::pin(async move {\n".to_string();
writeln!(&mut async_block, "let self_ptr = self_ptr;").unwrap();
func.args
.iter()
.skip(1)
.for_each(|arg| writeln!(&mut async_block, "let {} = {};", arg.name, arg.name).unwrap());
let cffi_call_args = func
.args
.iter()
.zip(safe_ptr_args.iter())
.skip(1)
.map(|(arg, is_safe_ptr)| {
let safe_ptr_access = if *is_safe_ptr { ".0" } else { "" };
format!(", {}{}", arg.name, safe_ptr_access)
})
.collect::<Vec<_>>()
.join("");
writeln!(
&mut async_block,
"let fut = ({}_fn)(self_ptr.0{});",
func.name, cffi_call_args
)
.unwrap();
writeln!(
&mut async_block,
"if fut.is_null() {{\n panic!(\"C function returned null pointer\");\n}}"
)
.unwrap();
writeln!(&mut async_block, "let fut = unsafe {{\n // Convert the raw pointer to a CffiFuture\n (fut as *mut CffiFuture)\n .as_mut()\n .expect(\"CffiFuture cannot be null\")\n}};").unwrap();
let ret_ty = &func.return_type;
let outer_ty = &ret_ty.ty;
if outer_ty != "BoxFuture" {
bail!("Only BoxFuture returning trait functions currently supported");
}
if ret_ty.generic_ty_args.len() == 2 && &ret_ty.generic_ty_args[1].ty != "()" {
writeln!(&mut async_block, "let ret = fut.await;").unwrap();
writeln!(&mut async_block, "assert!(!ret.is_null());").unwrap();
let async_return_ty = &ret_ty.generic_ty_args[1];
let async_return_ty = substitute_generic_args(async_return_ty, generics)?;
async_block.push_str(&type_transforms_for_cffi_fut_ptr(&async_return_ty, "ret")?);
writeln!(&mut async_block, "ret").unwrap();
} else {
writeln!(&mut async_block, "fut.await;").unwrap();
}
writeln!(&mut async_block, "}})").unwrap();
Ok(async_block)
}
fn fn_arg_to_assertions(arg: &FunctionArgSchema) -> String {
if let Some(annotations) = &arg.annotations {
if let Some(assert_len) = annotations.assert_len {
return format!(
"if {}.len() != {} {{\n panic!(\"Expected {} to have length {}\"); \n}} \n",
arg.name, assert_len, arg.name, assert_len
);
}
}
"".to_string()
}
fn fn_arg_to_transforms(arg: &FunctionArgSchema) -> Result<FnArgTransforms> {
let mut transforms = String::new();
let target_type = fn_arg_to_async_cffi_type_spec(arg)?;
let annotation_transforms = annotation_transforms_for_arg(arg, target_type)?;
annotation_transforms.map(|t| transforms.push_str(&t));
let type_transforms = type_transforms_for_arg(arg)?;
type_transforms.transforms.map(|t| transforms.push_str(&t));
Ok(FnArgTransforms {
transforms: Some(transforms),
returns_safe_ptr: type_transforms.returns_safe_ptr,
})
}
fn type_transforms_for_arg(arg: &FunctionArgSchema) -> Result<FnArgTransforms> {
let mut returns_safe_ptr = true;
let type_transform = match arg.ty.as_ref() {
None => None,
Some(fn_arg_type_schema) => {
let reg = Handlebars::new();
let transform_block = render_fn_arg_type_schema(
®,
&fn_arg_type_schema,
format!("let {} = {{{{{{next}}}}}};\n", arg.name),
false,
&mut returns_safe_ptr,
&arg.name,
)?;
Some(transform_block)
}
};
Ok(FnArgTransforms {
transforms: type_transform,
returns_safe_ptr,
})
}
fn render_fn_arg_type_schema(
reg: &Handlebars,
type_schema: &TypeSchema,
mut transform_block: String,
current_is_box: bool,
returns_safe_ptr: &mut bool,
arg_name: &str,
) -> Result<String> {
let mut next_is_box = current_is_box;
let next_schema = match type_schema.ty.as_str() {
"Option" => {
let template_string = format!(
"if let Some({}) = {} {{\n {{{{{{next}}}}}}\n}} else {{\n SafePtr(std::ptr::null())\n}}",
arg_name, arg_name,
);
transform_block =
reg.render_template(&transform_block, &json!({"next": template_string}))?;
type_schema.generic_ty_args.get(0)
}
"Vec" => {
let template_string = format!(
"CffiPointerBuffer::from_slice({}\n .into_iter()\n .map(|{}| {{\n ({{{{{{next}}}}}}).0\n}})\n .collect::<Box<[_]>>())",
arg_name, arg_name
);
*returns_safe_ptr = false;
transform_block =
reg.render_template(&transform_block, &json!({"next": template_string}))?;
type_schema.generic_ty_args.get(0)
}
"AnyArc" => {
let value_string = format!(
"*{}.downcast::<SafePtr>().expect(\"{} must be SafePtr\")",
arg_name, arg_name
);
transform_block =
reg.render_template(&transform_block, &json!({"next": value_string}))?;
type_schema.generic_ty_args.get(0)
}
"Arc" => {
let value_string = format!("SafePtr(Arc::into_raw({}) as *const c_void)", arg_name);
transform_block =
reg.render_template(&transform_block, &json!({"next": value_string}))?;
type_schema.generic_ty_args.get(0)
}
"Box" => {
next_is_box = true;
type_schema.generic_ty_args.get(0)
}
"usize" => {
let transform = format!("{} as c_ulong", arg_name);
transform_block = reg.render_template(&transform_block, &json!({"next": transform}))?;
*returns_safe_ptr = false;
type_schema.generic_ty_args.get(0)
}
fn_arg_type_elem => {
if current_is_box && fn_arg_type_elem.starts_with("dyn ") {
let mut transform = "{".to_string();
writeln!(
transform,
" let cffi_{} = (*{}).into();",
arg_name, arg_name
)
.unwrap();
writeln!(
transform,
" Box::leak({}); // Leak to keep alive",
arg_name
)
.unwrap();
writeln!(transform, " cffi_{}", arg_name).unwrap();
writeln!(transform, "}}").unwrap();
transform_block =
reg.render_template(&transform_block, &json!({"next": transform}))?;
*returns_safe_ptr = false;
}
type_schema.generic_ty_args.get(0)
}
};
if let Some(inner_schema) = next_schema {
render_fn_arg_type_schema(
reg,
inner_schema,
transform_block,
next_is_box,
returns_safe_ptr,
arg_name,
)
} else {
Ok(reg.render_template(&transform_block, &json!({"next": arg_name}))?)
}
}
fn type_transforms_for_cffi_fut_ptr(return_ty: &TypeSchema, retvar: &str) -> Result<String> {
let reg = Handlebars::new();
render_cffi_fut_ptr_transforms(
®,
return_ty,
format!("let {} = {{{{{{next}}}}}};\n", retvar),
retvar,
)
}
fn type_transforms_for_cffi_c_fn_return(return_ty: &TypeSchema, retvar: &str) -> Result<String> {
let reg = Handlebars::new();
render_cffi_c_fn_return_transforms(
®,
return_ty,
format!("let {} = {{{{{{next}}}}}};\n", retvar),
retvar,
)
}
fn render_cffi_fut_ptr_transforms(
reg: &Handlebars,
type_schema: &TypeSchema,
mut transform_block: String,
retvar: &str,
) -> Result<String> {
let next_schema = match type_schema.ty.as_str() {
"Option" => {
let transform = format!(
"{} as *const *const c_void;\nlet {} = *unsafe {{ {}.as_ref().unwrap() }};\nlet {} = if {}.is_null() {{\nNone\n}} else {{\nSome({{{{{{next}}}}}})\n}};\n",
retvar, retvar, retvar, retvar, retvar,
);
transform_block = reg.render_template(&transform_block, &json!({"next": transform}))?;
type_schema.generic_ty_args.get(0)
}
"AnyArc" => {
let transform = format!("Arc::new(SafePtr({})) as AnyArc", retvar);
transform_block = reg.render_template(&transform_block, &json!({"next": transform}))?;
type_schema.generic_ty_args.get(0)
}
"Arc" => {
let transform = format!("Arc::new(SafePtr({}))", retvar);
transform_block = reg.render_template(&transform_block, &json!({"next": transform}))?;
type_schema.generic_ty_args.get(0)
}
"SafePtr" => type_schema.generic_ty_args.get(0),
ty => {
bail!("Unsupported type for return type transform: {}", ty);
}
};
if let Some(inner_schema) = next_schema {
render_cffi_fut_ptr_transforms(reg, inner_schema, transform_block, retvar)
} else {
Ok(reg.render_template(&transform_block, &json!({"next": retvar}))?)
}
}
fn render_cffi_c_fn_return_transforms(
reg: &Handlebars,
type_schema: &TypeSchema,
mut transform_block: String,
retvar: &str,
) -> Result<String> {
let next_schema = match type_schema.ty.as_str() {
"Option" => {
let transform = format!(
"{}\n .map(|{}| {{{{{{next}}}}}})\n .unwrap_or(std::ptr::null())",
retvar, retvar,
);
transform_block = reg.render_template(&transform_block, &json!({"next": transform}))?;
type_schema.generic_ty_args.get(0)
}
"AnyArc" => {
let transform = format!("{}.downcast::<SafePtr>().unwrap().0", retvar);
transform_block = reg.render_template(&transform_block, &json!({"next": transform}))?;
type_schema.generic_ty_args.get(0)
}
"Arc" => {
let transform = format!("{{let {} = *{};\n{{{{{{next}}}}}}}}", retvar, retvar);
transform_block = reg.render_template(&transform_block, &json!({"next": transform}))?;
type_schema.generic_ty_args.get(0)
}
"SafePtr" => {
let transform = format!("{}.0", retvar);
transform_block = reg.render_template(&transform_block, &json!({"next": transform}))?;
type_schema.generic_ty_args.get(0)
}
ty => {
bail!("Unsupported type for c fn return type transform: {}", ty);
}
};
if let Some(inner_schema) = next_schema {
render_cffi_c_fn_return_transforms(reg, inner_schema, transform_block, retvar)
} else {
Ok(reg.render_template(&transform_block, &json!({"next": retvar}))?)
}
}
fn trait_schema_to_from_impl(trait_schema: &TraitSchema) -> Result<FunctionSchema> {
Ok(FunctionSchema {
name: "from".to_string(),
args: vec![FunctionArgSchema {
name: "inner".to_string(),
ty: Some(TypeSchema {
ty: format!("&dyn {}", get_trait_name_w_subbed_generics(trait_schema)?),
generic_ty_args: vec![],
}),
annotations: None,
}],
return_type: TypeSchema {
ty: "Self".to_string(),
generic_ty_args: vec![],
},
body: Some(trait_schema_to_from_impl_body(trait_schema)?),
extern_layout: None,
annotations: None,
})
}
fn trait_schema_to_from_impl_body(trait_schema: &TraitSchema) -> Result<String> {
let mut fn_body = String::new();
writeln!(fn_body, "Cffi{} {{", trait_schema.name).unwrap();
writeln!(
fn_body,
" // Wrap the fat pointer in a Box to get a stable address"
)
.unwrap();
writeln!(fn_body, " // Leak it to keep [just the wrapper] alive").unwrap();
writeln!(
fn_body,
" self_ptr: Box::into_raw(Box::new(inner)) as *const c_void,"
)
.unwrap();
for supertrait in trait_schema
.supertraits
.iter()
.filter(|st| st.ty != "Send" && st.ty != "Sync")
{
writeln!(fn_body, " cffi_{}: {{\n", supertrait.ty.to_snake_case()).unwrap();
writeln!(
fn_body,
" let {} = Box::new(inner as &dyn {});",
supertrait.ty.to_snake_case(),
supertrait.ty
)
.unwrap();
writeln!(
fn_body,
" let ret: Cffi{} = (*{}).into();",
supertrait.ty,
supertrait.ty.to_snake_case()
)
.unwrap();
writeln!(
fn_body,
" Box::leak({}); // Leak to keep alive",
supertrait.ty.to_snake_case()
)
.unwrap();
writeln!(fn_body, " ret").unwrap();
writeln!(fn_body, " }},").unwrap();
}
for func in &trait_schema.functions {
writeln!(
fn_body,
" {}_fut: Cffi{}::{}_fut_impl,",
func.name, trait_schema.name, func.name
)
.unwrap();
}
writeln!(fn_body, "}}").unwrap();
Ok(fn_body)
}
#[cfg(test)]
mod tests {
use crate::cffi_type_utils::CffiTypeElementSpec;
use rs_schema::FnArgAnnotations;
use super::*;
#[test]
fn test_trait_to_async_cffi_schema_with_no_functions() {
let trait_schema = TraitSchema {
name: "TestTrait".to_string(),
functions: vec![],
generics: vec![],
supertraits: vec![],
};
let result = trait_to_async_cffi_schema(&trait_schema, &HashMap::new()).unwrap();
assert_eq!(result.structs.len(), 1);
assert_eq!(result.structs[0].name, "CffiTestTrait");
assert_eq!(result.structs[0].fields.len(), 1);
assert_eq!(result.structs[0].fields[0].name, "self_ptr");
assert_eq!(result.trait_impls.len(), 5);
assert_eq!(result.trait_impls[0].trait_name, "TestTrait");
assert_eq!(
result.trait_impls[0].impl_for,
Some("CffiTestTrait".to_string())
);
assert!(!result.trait_impls[0].unsafe_impl);
assert_eq!(result.trait_impls[1].trait_name, "Send");
assert!(result.trait_impls[1].unsafe_impl);
assert_eq!(result.trait_impls[2].trait_name, "Sync");
assert!(result.trait_impls[2].unsafe_impl);
}
#[test]
fn test_trait_to_async_cffi_schema_with_functions() {
let trait_schema = TraitSchema {
name: "MyTrait".to_string(),
functions: vec![
FunctionSchema {
name: "method_one".to_string(),
args: vec![FunctionArgSchema {
name: "arg1".to_string(),
ty: Some(TypeSchema {
ty: "i32".to_string(),
generic_ty_args: vec![],
}),
annotations: None,
}],
return_type: TypeSchema {
ty: "BoxFuture".to_string(),
generic_ty_args: vec![
TypeSchema::new_simple("'_".to_string()),
TypeSchema {
ty: "Option".to_string(),
generic_ty_args: vec![TypeSchema::new_simple("AnyArc".to_string())],
},
],
},
body: None,
extern_layout: None,
annotations: None,
},
FunctionSchema {
name: "method_two".to_string(),
args: vec![],
return_type: TypeSchema {
ty: "BoxFuture".to_string(),
generic_ty_args: vec![
TypeSchema::new_simple("'_".to_string()),
TypeSchema::new_simple("()".to_string()),
],
},
body: None,
extern_layout: None,
annotations: None,
},
],
generics: vec![],
supertraits: vec![],
};
let result = trait_to_async_cffi_schema(&trait_schema, &HashMap::new()).unwrap();
assert_eq!(result.structs.len(), 1);
assert_eq!(result.structs[0].name, "CffiMyTrait");
assert_eq!(result.structs[0].fields.len(), 3);
assert_eq!(result.structs[0].fields[0].name, "self_ptr");
assert_eq!(result.structs[0].fields[1].name, "method_one_fut");
assert_eq!(result.structs[0].fields[2].name, "method_two_fut");
assert_eq!(
result.structs[0].fields[1].field_type.ty,
"extern \"C\" fn(*const c_void) -> *const c_void"
);
assert_eq!(
result.structs[0].fields[2].field_type.ty,
"extern \"C\" fn() -> *const c_void"
);
assert_eq!(result.trait_impls.len(), 5);
assert_eq!(result.trait_impls[0].trait_name, "MyTrait");
assert_eq!(result.trait_impls[0].functions.len(), 2);
assert_eq!(result.trait_impls[0].functions[0].name, "method_one");
assert_eq!(result.trait_impls[0].functions[1].name, "method_two");
}
#[test]
fn test_fn_arg_to_transforms_basic() {
let arg_schema = FunctionArgSchema {
name: "arg1".to_string(),
ty: Some(TypeSchema {
ty: "Option".to_string(),
generic_ty_args: vec![TypeSchema::new_simple("AnyArc".to_string())],
}),
annotations: None,
};
fn_arg_to_transforms(&arg_schema).unwrap();
}
#[test]
fn test_fn_arg_to_transforms_with_type_annotation() {
let arg_schema = FunctionArgSchema {
name: "arg1".to_string(),
ty: Some(TypeSchema {
ty: "Vec".to_string(),
generic_ty_args: vec![TypeSchema {
ty: "Option".to_string(),
generic_ty_args: vec![TypeSchema::new_simple("AnyArc".to_string())],
}],
}),
annotations: Some({
let mut annotations = FnArgAnnotations::new();
annotations.cffi_type = Some("CffiPointerBuffer<opt_ptr<T>>".to_string());
annotations
}),
};
fn_arg_to_transforms(&arg_schema).unwrap();
}
#[test]
fn test_fn_arg_to_transforms_arc() {
let arg_schema = FunctionArgSchema {
name: "arg1".to_string(),
ty: Some(TypeSchema {
ty: "Arc".to_string(),
generic_ty_args: vec![TypeSchema::new_simple("SomeType".to_string())],
}),
annotations: None,
};
let out = fn_arg_to_transforms(&arg_schema).unwrap();
let out = out.transforms.unwrap();
assert!(out.contains("Arc::into_raw"));
assert!(out.contains("SafePtr("));
}
#[test]
fn test_fn_arg_to_transforms_anyarc() {
let arg_schema = FunctionArgSchema {
name: "arg1".to_string(),
ty: Some(TypeSchema::new_simple("AnyArc".to_string())),
annotations: None,
};
let out = fn_arg_to_transforms(&arg_schema).unwrap();
let out = out.transforms.unwrap();
assert!(out.contains("arg1.downcast::<SafePtr>()"));
assert!(out.contains("let arg1 ="));
}
#[test]
fn test_fn_arg_to_transforms_option_arc() {
let arg_schema = FunctionArgSchema {
name: "arg1".to_string(),
ty: Some(TypeSchema {
ty: "Option".to_string(),
generic_ty_args: vec![TypeSchema {
ty: "Arc".to_string(),
generic_ty_args: vec![TypeSchema::new_simple("T".to_string())],
}],
}),
annotations: None,
};
let out = fn_arg_to_transforms(&arg_schema).unwrap();
let out = out.transforms.unwrap();
assert!(out.contains("if let Some(arg1) = arg1"));
assert!(out.contains("Arc::into_raw"));
}
#[test]
fn test_type_transforms_for_arg_vec() {
let arg_schema = FunctionArgSchema {
name: "data".to_string(),
ty: Some(TypeSchema {
ty: "Vec".to_string(),
generic_ty_args: vec![TypeSchema::new_simple("i32".to_string())],
}),
annotations: None,
};
let out = type_transforms_for_arg(&arg_schema).unwrap();
let out = out.transforms;
assert!(out.is_some());
let transform = out.unwrap();
assert!(transform.contains("let data = "));
}
#[test]
fn test_codegen_struct_indentation() {
let struct_schema = RsStructSchema {
macros: vec!["#[repr(C)]".to_string()],
name: "Example".to_string(),
fields: vec![RsFieldSchema {
name: "value".to_string(),
field_type: TypeSchema::new_simple("i32".to_string()),
}],
};
let code = struct_schema.codegen(1);
let lines: Vec<_> = code.lines().collect();
assert!(lines.iter().all(|line| line.starts_with(" ")));
assert!(code.contains("pub struct Example"));
assert!(code.contains("pub value: i32"));
}
#[test]
fn test_codegen_trait_impl_indentation() {
let function_schema = FunctionSchema {
name: "example".to_string(),
args: vec![],
return_type: TypeSchema::new_simple("()".to_string()),
body: Some("".to_string()),
extern_layout: None,
annotations: None,
};
let trait_impl_schema = RsTraitImplSchema {
trait_name: "ExampleTrait".to_string(),
impl_for: Some("Example".to_string()),
functions: vec![function_schema],
generic_args: vec![],
unsafe_impl: false,
comment_out: false,
};
let code = trait_impl_schema.codegen(1);
let mut lines = code.lines();
assert!(lines.next().unwrap().starts_with(" impl"));
assert!(lines.any(|line| line.starts_with(" ")));
}
#[test]
fn test_rs_file_schema_codegen_uses_children() {
let struct_schema = RsStructSchema {
macros: vec![],
name: "Example".to_string(),
fields: vec![],
};
let file_schema = RsFileSchema {
structs: vec![struct_schema],
trait_impls: vec![],
};
let code = file_schema.codegen(0);
assert!(code.contains("autogenerated"));
assert!(code.contains("pub struct Example"));
}
#[test]
fn test_fn_arg_to_async_cffi_type_spec_none() {
let arg = FunctionArgSchema {
name: "arg0".to_string(),
ty: None,
annotations: None,
};
let spec = fn_arg_to_async_cffi_type_spec(&arg).unwrap();
assert_eq!(spec.len(), 2);
assert!(spec[0].is_pointer);
assert!(!spec[1].is_pointer);
}
#[test]
fn test_cffi_type_str_to_type_stack_mappings() {
let s = "CffiPointerBuffer<opt_ptr<ptr>>".to_string();
let stack = cffi_type_str_to_type_stack(&s).unwrap();
assert!(stack.len() >= 3);
assert!(stack[0].is_pointer);
assert_eq!(stack[0].explicit_type.as_deref(), Some("CffiPointerBuffer"));
assert!(stack[1].is_pointer && stack[1].is_optional);
assert!(stack[2].is_pointer && !stack[2].is_optional);
}
#[test]
fn test_annotation_transforms_for_arg_mismatch_errors() {
let mut annotations = FnArgAnnotations::new();
annotations.cffi_type = Some("ptr".to_string());
let arg = FunctionArgSchema {
name: "a".to_string(),
ty: Some(TypeSchema::new_simple("i32".to_string())),
annotations: Some(annotations),
};
let res = fn_arg_to_transforms(&arg);
assert!(res.is_err());
}
#[test]
fn test_async_block_for_trait_fn_structure_and_call_args() {
let func = FunctionSchema {
name: "do_something".to_string(),
args: vec![
FunctionArgSchema {
name: "self".to_string(),
ty: None,
annotations: None,
},
FunctionArgSchema {
name: "x".to_string(),
ty: None,
annotations: None,
},
],
return_type: TypeSchema {
ty: "BoxFuture".to_string(),
generic_ty_args: vec![
TypeSchema::new_simple("'_".to_string()),
TypeSchema::new_simple("()".to_string()),
],
},
body: None,
extern_layout: None,
annotations: None,
};
let block = async_block_for_trait_fn(&func, vec![true, false], &vec![]).unwrap();
assert!(block.contains("Box::pin(async move"));
assert!(block.contains("fut.await"));
assert!(block.contains("(do_something_fn)(self_ptr.0, x);"));
}
#[test]
fn test_arg_to_field_type_vec_produces_cffi_pointerbuffer() {
let arg = FunctionArgSchema {
name: "data".to_string(),
ty: Some(TypeSchema {
ty: "Vec".to_string(),
generic_ty_args: vec![TypeSchema::new_simple("i32".to_string())],
}),
annotations: None,
};
let t = arg_to_field_type(&arg).unwrap();
assert_eq!(t.ty, "CffiPointerBuffer");
}
#[test]
fn test_trait_fn_to_cffi_c_fn_body_errors_on_non_boxfuture() {
let func = FunctionSchema {
name: "bad_return".to_string(),
args: vec![FunctionArgSchema {
name: "self".to_string(),
ty: None,
annotations: None,
}],
return_type: TypeSchema::new_simple("String".to_string()),
body: None,
extern_layout: None,
annotations: None,
};
let trait_schema = TraitSchema {
name: "MyTrait".to_string(),
functions: vec![],
generics: vec![],
supertraits: vec![],
};
let err = trait_fn_to_cffi_c_fn_body(&func, &trait_schema, &vec![]).unwrap_err();
assert!(err.to_string().contains("BoxFuture"));
}
#[test]
fn test_type_transforms_for_cffi_c_fn_return_option_anyarc() {
let ty_schema = TypeSchema {
ty: "Option".to_string(),
generic_ty_args: vec![TypeSchema::new_simple("AnyArc".to_string())],
};
let transform = type_transforms_for_cffi_c_fn_return(&ty_schema, "ret").unwrap();
assert!(transform.contains("map(|ret|"));
assert!(transform.contains("downcast::<SafePtr>().unwrap().0"));
assert!(transform.contains("unwrap_or(std::ptr::null())"));
}
#[test]
fn test_cffi_c_fn_arg_to_transforms_for_pointer_buffer() {
let mut annotations = FnArgAnnotations::new();
annotations.cffi_type = Some("CffiPointerBuffer".to_string());
let arg = FunctionArgSchema {
name: "values".to_string(),
ty: Some(TypeSchema {
ty: "Vec".to_string(),
generic_ty_args: vec![TypeSchema {
ty: "Option".to_string(),
generic_ty_args: vec![TypeSchema::new_simple("AnyArc".to_string())],
}],
}),
annotations: Some(annotations),
};
let trait_schema = TraitSchema {
name: "Trait".to_string(),
functions: vec![],
generics: vec![],
supertraits: vec![],
};
let transforms = cffi_c_fn_arg_to_transforms(&arg, &trait_schema).unwrap();
assert!(transforms.contains(".as_slice()"));
assert!(transforms.contains("SafePtr"));
}
#[test]
fn test_cffi_c_fn_arg_to_transforms_for_self_ptr() {
let arg = FunctionArgSchema {
name: "self".to_string(),
ty: None,
annotations: None,
};
let trait_schema = TraitSchema {
name: "Trait".to_string(),
functions: vec![],
generics: vec![],
supertraits: vec![],
};
let transforms = cffi_c_fn_arg_to_transforms(&arg, &trait_schema).unwrap();
assert!(transforms.contains("Self pointer cannot be null"));
assert!(transforms.contains("dyn Trait + Send + Sync"));
}
#[test]
fn test_trait_schema_to_from_impl_includes_function_fields() {
let trait_schema = TraitSchema {
name: "ExampleTrait".to_string(),
functions: vec![FunctionSchema {
name: "do_it".to_string(),
args: vec![],
return_type: TypeSchema {
ty: "BoxFuture".to_string(),
generic_ty_args: vec![
TypeSchema::new_simple("'_".to_string()),
TypeSchema::new_simple("()".to_string()),
],
},
body: None,
extern_layout: None,
annotations: None,
}],
generics: vec![],
supertraits: vec![],
};
let from_impl = trait_schema_to_from_impl(&trait_schema).unwrap();
let body = from_impl.body.unwrap();
assert!(body.contains("CffiExampleTrait"));
assert!(body.contains("do_it_fut_impl"));
assert!(body.contains("Box::into_raw"));
}
#[test]
fn test_cffi_ty_stack_to_rust_ty_cases() {
let stack = vec![CffiTypeElementSpec {
is_pointer: false,
is_optional: false,
explicit_type: Some("CffiX".to_string()),
}];
let out = cffi_ty_stack_to_rust_cffi_ty(&stack).unwrap();
assert_eq!(out, "CffiX");
let stack2 = vec![CffiTypeElementSpec {
is_pointer: true,
is_optional: false,
explicit_type: None,
}];
let out2 = cffi_ty_stack_to_rust_cffi_ty(&stack2).unwrap();
assert_eq!(out2, "SafePtr");
let empty: Vec<CffiTypeElementSpec> = vec![];
assert!(cffi_ty_stack_to_rust_cffi_ty(&empty).is_err());
}
#[test]
fn test_substitute_generic_args_no_generics() {
let ty = TypeSchema {
ty: "i32".to_string(),
generic_ty_args: vec![],
};
let out = substitute_generic_args(&ty, &vec![]).unwrap();
assert_eq!(out.ty, "i32");
assert!(out.generic_ty_args.is_empty());
}
#[test]
fn test_get_trait_name_w_subbed_generics_simple() {
let trait_schema = TraitSchema {
name: "SimpleTrait".to_string(),
functions: vec![],
generics: vec![],
supertraits: vec![],
};
let out = get_trait_name_w_subbed_generics(&trait_schema).unwrap();
assert_eq!(out, "SimpleTrait");
}
#[test]
fn test_type_transforms_for_cffi_fut_ptr_option_arc() {
let ty_schema = TypeSchema {
ty: "Option".to_string(),
generic_ty_args: vec![TypeSchema {
ty: "Arc".to_string(),
generic_ty_args: vec![TypeSchema::new_simple("SafePtr".to_string())],
}],
};
let transform = type_transforms_for_cffi_fut_ptr(&ty_schema, "ret").unwrap();
assert!(
transform.contains("Arc::new(SafePtr(ret))")
|| transform.contains("Arc::new(SafePtr({ret}))")
);
assert!(transform.contains("as_ref().unwrap") || transform.contains("as_ref()"));
}
#[test]
fn test_type_transforms_for_cffi_fut_ptr_unsupported_type() {
let ty_schema = TypeSchema::new_simple("String".to_string());
let res = type_transforms_for_cffi_fut_ptr(&ty_schema, "ret");
assert!(res.is_err());
assert!(res.err().unwrap().to_string().contains("Unsupported type"));
}
#[test]
fn test_cffi_c_fn_arg_to_transforms_c_ulong() {
let arg = FunctionArgSchema {
name: "n".to_string(),
ty: Some(TypeSchema::new_simple("usize".to_string())),
annotations: None,
};
let trait_schema = TraitSchema {
name: "Trait".to_string(),
functions: vec![],
generics: vec![],
supertraits: vec![],
};
let t = cffi_c_fn_arg_to_transforms(&arg, &trait_schema).unwrap();
assert!(t.contains("as usize") || t.contains("as usize;"));
}
#[test]
fn test_annotation_transforms_for_arg_collection_as_item() {
let mut annotations = FnArgAnnotations::new();
annotations.collection_as_item = true;
let arg = FunctionArgSchema {
name: "it".to_string(),
ty: Some(TypeSchema {
ty: "Vec".to_string(),
generic_ty_args: vec![TypeSchema::new_simple("AnyArc".to_string())],
}),
annotations: Some(annotations),
};
let out = annotation_transforms_for_arg(&arg, vec![]).unwrap();
assert!(out.is_some());
let s = out.unwrap();
assert!(s.contains("into_iter().next().unwrap()"));
}
#[test]
fn test_type_transforms_for_arg_box_dyn() {
let arg = FunctionArgSchema {
name: "b".to_string(),
ty: Some(TypeSchema {
ty: "Box".to_string(),
generic_ty_args: vec![TypeSchema {
ty: "dyn SomeTrait".to_string(),
generic_ty_args: vec![],
}],
}),
annotations: None,
};
let out = type_transforms_for_arg(&arg).unwrap();
assert!(!out.returns_safe_ptr);
let t = out.transforms.unwrap();
assert!(t.contains("Box::leak") || t.contains("Box::leak("));
assert!(t.contains("cffi_b") || t.contains("cffi_b"));
}
}