use proc_macro2::TokenStream;
use quote::quote_spanned;
use syn::spanned::Spanned;
use syn::{ItemFn, ReturnType};
use crate::function::model::FunctionModel;
impl FunctionModel {
pub fn emit_modified_function(&self, original_fn: &ItemFn) -> TokenStream {
let vis = &self.vis;
let name = &self.name;
let export_name = self.generate_export_name();
let generics = &self.signature.generics;
let inputs = &original_fn.sig.inputs;
let output = &self.signature.output;
let block = &original_fn.block;
let unsafety = if self.is_unsafe {
quote_spanned! { self.name.span() => unsafe }
} else {
quote_spanned! { self.name.span() => }
};
let where_clause = &generics.where_clause;
let preserved_attrs: Vec<_> = original_fn
.attrs
.iter()
.filter(|attr| {
let path = attr.path();
!path.is_ident("doc") && !path.is_ident("ffi") && !path.is_ident("no_mangle") && !path.is_ident("unsafe")
})
.collect();
quote_spanned! { self.name.span() =>
#(#preserved_attrs)*
#[unsafe(export_name = #export_name)]
#vis #unsafety extern "C-unwind" fn #name #generics(#inputs) #output #where_clause #block
}
}
pub fn emit_companion_struct(&self) -> TokenStream {
let vis = &self.vis;
let struct_name = &self.name;
let generics = &self.signature.generics;
let where_clause = &generics.where_clause;
let phantom_data_field = if generics.params.is_empty() {
quote_spanned! { self.name.span() => }
} else {
let param_types = generics
.params
.iter()
.map(|param| match param {
syn::GenericParam::Lifetime(lifetime) => {
let lifetime_ident = &lifetime.lifetime;
quote_spanned! { lifetime_ident.span() => &#lifetime_ident () }
}
syn::GenericParam::Type(type_param) => {
let type_ident = &type_param.ident;
quote_spanned! { type_ident.span() => #type_ident }
}
syn::GenericParam::Const(const_param) => {
let const_ident = &const_param.ident;
quote_spanned! { const_ident.span() => [(); #const_ident] }
}
})
.collect::<Vec<_>>();
if param_types.len() == 1 {
quote_spanned! { self.name.span() => _phantom: ::std::marker::PhantomData<#(#param_types)*>, }
} else {
quote_spanned! { self.name.span() => _phantom: ::std::marker::PhantomData<(#(#param_types),*)>, }
}
};
quote_spanned! { self.name.span() =>
#[allow(non_camel_case_types)]
#vis struct #struct_name #generics #where_clause {
#phantom_data_field
}
}
}
pub fn emit_function_info_impl(&self) -> TokenStream {
let struct_name = &self.name;
let export_name = self.generate_export_name();
let generics = &self.signature.generics;
let where_clause = &generics.where_clause;
let arguments = self.emit_arguments();
let parameter_registrations = self.emit_parameter_types();
let return_type = self.emit_return_type();
let return_type_registration = self.emit_return_type_registration();
let emission = self.emit_emission();
let visibility = self.emit_visibility();
let docs_tokens = self.emit_docs();
let validation_guards = self.emit_validation_guards();
quote_spanned! { struct_name.span() =>
#validation_guards
unsafe impl #generics ::interoptopus::lang::function::FunctionInfo for #struct_name #generics #where_clause {
fn id() -> ::interoptopus::inventory::FunctionId {
::interoptopus::inventory::FunctionId::from_id(::interoptopus::id!(#struct_name))
}
fn signature() -> ::interoptopus::lang::function::Signature {
::interoptopus::lang::function::Signature {
arguments: vec![#(#arguments),*],
rval: #return_type,
}
}
fn function() -> ::interoptopus::lang::function::Function {
::interoptopus::lang::function::Function {
name: #export_name.to_string(),
visibility: #visibility,
docs: #docs_tokens,
emission: #emission,
signature: Self::signature(),
}
}
fn register(inventory: &mut impl ::interoptopus::inventory::Inventory) {
#(
#parameter_registrations;
)*
#return_type_registration;
inventory.register_function(Self::id(), Self::function());
}
}
}
}
fn emit_arguments(&self) -> Vec<TokenStream> {
self.signature
.inputs
.iter()
.map(|param| {
let name = param.name.to_string();
let ty = ¶m.ty;
quote_spanned! { param.name.span() =>
::interoptopus::lang::function::Argument::new(
#name,
<#ty as ::interoptopus::lang::types::TypeInfo>::id()
)
}
})
.collect()
}
fn emit_parameter_types(&self) -> Vec<TokenStream> {
self.signature
.inputs
.iter()
.map(|param| {
let ty = ¶m.ty;
quote_spanned! { param.ty.span() =>
<#ty as ::interoptopus::lang::types::TypeInfo>::register(inventory)
}
})
.collect()
}
fn emit_return_type(&self) -> TokenStream {
match &self.signature.output {
ReturnType::Default => quote_spanned! { self.name.span() =>
<() as ::interoptopus::lang::types::TypeInfo>::id()
},
ReturnType::Type(_, ty) => quote_spanned! { ty.span() =>
<#ty as ::interoptopus::lang::types::TypeInfo>::id()
},
}
}
fn emit_return_type_registration(&self) -> TokenStream {
match &self.signature.output {
ReturnType::Default => quote_spanned! { self.name.span() =>
<() as ::interoptopus::lang::types::TypeInfo>::register(inventory)
},
ReturnType::Type(_, ty) => quote_spanned! { ty.span() =>
<#ty as ::interoptopus::lang::types::TypeInfo>::register(inventory)
},
}
}
fn emit_emission(&self) -> TokenStream {
match &self.args.module {
Some(crate::function::args::ModuleKind::Named(name)) => {
quote_spanned! { self.name.span() => ::interoptopus::lang::meta::Emission::FileEmission(::interoptopus::lang::meta::FileEmission::CustomModule(::interoptopus::lang::meta::Module::from_string(#name))) }
}
Some(crate::function::args::ModuleKind::Common) => {
quote_spanned! { self.name.span() => ::interoptopus::lang::meta::Emission::FileEmission(::interoptopus::lang::meta::FileEmission::Common) }
}
None => {
quote_spanned! { self.name.span() => ::interoptopus::lang::meta::Emission::FileEmission(::interoptopus::lang::meta::FileEmission::Default) }
}
}
}
fn emit_visibility(&self) -> TokenStream {
match &self.vis {
syn::Visibility::Public(_) => quote_spanned! { self.name.span() => ::interoptopus::lang::meta::Visibility::Public },
syn::Visibility::Restricted(_) => quote_spanned! { self.name.span() => ::interoptopus::lang::meta::Visibility::Private },
syn::Visibility::Inherited => quote_spanned! { self.name.span() => ::interoptopus::lang::meta::Visibility::Private },
}
}
fn emit_docs(&self) -> TokenStream {
let docs = &self.docs;
quote_spanned! { self.name.span() =>
::interoptopus::lang::meta::Docs::from_lines(vec![#(#docs.to_string()),*])
}
}
fn emit_validation_guards(&self) -> TokenStream {
let parameter_validations = self.signature.inputs.iter().map(|param| {
let param_ty = Self::elide_lifetimes(¶m.ty);
quote_spanned! {param.ty.span()=>
const _: () = const {
::interoptopus::lang::types::assert_raw_safe::<#param_ty>();
};
}
});
let return_type_validation = match &self.signature.output {
syn::ReturnType::Default => quote_spanned! { self.name.span() =>
},
syn::ReturnType::Type(_, return_ty) => {
let elided_return_ty = Self::elide_lifetimes(return_ty);
quote_spanned! {return_ty.span()=>
const _: () = const {
::interoptopus::lang::types::assert_raw_safe::<#elided_return_ty>();
};
}
}
};
quote_spanned! { self.name.span() =>
#(#parameter_validations)*
#return_type_validation
}
}
fn elide_lifetimes(ty: &syn::Type) -> syn::Type {
use syn::visit_mut::VisitMut;
struct LifetimeElisor;
impl VisitMut for LifetimeElisor {
fn visit_lifetime_mut(&mut self, i: &mut syn::Lifetime) {
*i = syn::Lifetime::new("'_", i.span());
}
}
let mut elided_ty = ty.clone();
LifetimeElisor.visit_type_mut(&mut elided_ty);
elided_ty
}
}