use crate::docs::extract_docs;
use crate::forbidden::is_forbidden_name;
use crate::function::args::FfiFunctionArgs;
use quote::ToTokens;
use syn::spanned::Spanned;
use syn::{FnArg, Ident, ItemFn, Pat, Type, Visibility};
#[derive(Clone)]
pub struct FunctionModel {
pub name: Ident,
pub vis: Visibility,
pub args: FfiFunctionArgs,
pub docs: Vec<String>,
pub signature: FunctionSignature,
pub is_unsafe: bool,
}
#[derive(Clone)]
pub struct FunctionSignature {
pub inputs: Vec<FunctionParameter>,
pub output: syn::ReturnType,
pub generics: syn::Generics,
}
#[derive(Clone)]
#[allow(dead_code)]
pub struct FunctionParameter {
pub name: Ident,
pub ty: Type,
}
impl FunctionModel {
pub fn from_item_fn(input: ItemFn, args: FfiFunctionArgs) -> syn::Result<Self> {
let docs = extract_docs(&input.attrs);
let has_extern = input.sig.abi.is_some();
let no_mangle_attr = input.attrs.iter().find(|attr| {
if attr.path().is_ident("no_mangle") {
true
} else if attr.path().is_ident("unsafe") {
match &attr.meta {
syn::Meta::List(list) => list.tokens.to_string().trim() == "no_mangle",
_ => false,
}
} else {
false
}
});
if has_extern {
return Err(syn::Error::new_spanned(
input.sig.abi.as_ref().unwrap(),
"Functions with explicit extern declarations are not supported. Remove the declaration and let #[ffi] handle it.",
));
}
if let Some(attr) = no_mangle_attr {
let message = "Functions with explicit #[no_mangle] are not supported, remove the attribute and let #[ffi] handle it.";
return Err(syn::Error::new_spanned(attr, message));
}
let mut inputs = Vec::new();
for (index, input_arg) in input.sig.inputs.iter().enumerate() {
match input_arg {
FnArg::Typed(typed_arg) => {
let param_name = if let Pat::Ident(pat_ident) = typed_arg.pat.as_ref() {
pat_ident.ident.clone()
} else {
syn::Ident::new(&format!("_{index}"), typed_arg.pat.span())
};
inputs.push(FunctionParameter { name: param_name, ty: (*typed_arg.ty).clone() });
}
FnArg::Receiver(_) => {
return Err(syn::Error::new_spanned(input_arg, "Methods with self parameters are not supported"));
}
}
}
let signature = FunctionSignature { inputs, output: input.sig.output.clone(), generics: input.sig.generics.clone() };
for param in &signature.generics.params {
if let syn::GenericParam::Type(_) = param {
return Err(syn::Error::new_spanned(param, "Functions with type generics are not supported at FFI boundaries"));
}
}
if is_forbidden_name(input.sig.ident.to_string()) {
return Err(syn::Error::new_spanned(&input.sig.ident, format!("Using the name '{}' can cause conflicts in generated code.", input.sig.ident)));
}
for param in &signature.inputs {
if is_forbidden_name(param.name.to_string()) {
return Err(syn::Error::new_spanned(¶m.name, format!("Using the name '{}' can cause conflicts in generated code.", param.name)));
}
}
let model = Self { name: input.sig.ident.clone(), vis: input.vis.clone(), args, docs, signature, is_unsafe: input.sig.unsafety.is_some() };
Ok(model)
}
pub fn generate_export_name(&self) -> String {
match &self.args.export {
Some(crate::function::args::ExportKind::Custom(name)) => name.clone(),
Some(crate::function::args::ExportKind::Unique) => {
let base_name = self.name.to_string();
let hash = {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
base_name.hash(&mut hasher);
for param in &self.signature.inputs {
param.ty.to_token_stream().to_string().hash(&mut hasher);
}
match &self.signature.output {
syn::ReturnType::Default => "()".hash(&mut hasher),
syn::ReturnType::Type(_, ty) => ty.to_token_stream().to_string().hash(&mut hasher),
}
for param in &self.signature.generics.params {
param.to_token_stream().to_string().hash(&mut hasher);
}
hasher.finish()
};
format!("{}_{}", base_name, hash % 100000)
}
None => self.name.to_string(),
}
}
}