#![forbid(unsafe_code)]
use autozig_parser::{
AutoZigConfig,
IncludeZigConfig,
};
use proc_macro::TokenStream;
use proc_macro_error::proc_macro_error;
use quote::quote;
use syn::parse_macro_input;
#[proc_macro_error]
#[proc_macro]
pub fn autozig(input: TokenStream) -> TokenStream {
let config = parse_macro_input!(input as AutoZigConfig);
let mod_name = syn::Ident::new(config.get_mod_name(), proc_macro2::Span::call_site());
let output = if config.has_rust_signatures()
|| !config.rust_structs.is_empty()
|| !config.rust_enums.is_empty()
|| !config.rust_trait_impls.is_empty()
{
let enum_defs = generate_enum_definitions(&config);
let struct_defs = generate_struct_definitions(&config);
let trait_impl_types = generate_trait_impl_types(&config);
let (ffi_decls, wrappers) = generate_with_monomorphization(&config);
let trait_ffi_decls = generate_trait_ffi_declarations(&config);
let trait_impls = generate_trait_implementations(&config);
quote! {
#enum_defs
#struct_defs
#trait_impl_types
mod #mod_name {
use super::*; #ffi_decls
#trait_ffi_decls
}
#wrappers
#trait_impls
}
} else {
quote! {
compile_error!("autozig! macro requires Rust function signatures after --- separator");
}
};
TokenStream::from(output)
}
fn generate_enum_definitions(config: &AutoZigConfig) -> proc_macro2::TokenStream {
let enums: Vec<_> = config.rust_enums.iter().map(|e| &e.item).collect();
quote! {
#(#enums)*
}
}
fn generate_struct_definitions(config: &AutoZigConfig) -> proc_macro2::TokenStream {
let structs: Vec<_> = config.rust_structs.iter().map(|s| &s.item).collect();
quote! {
#(#structs)*
}
}
fn is_slice_or_str_ref(ty: &syn::Type) -> Option<(bool, Option<syn::Type>)> {
if let syn::Type::Reference(type_ref) = ty {
let is_mut = type_ref.mutability.is_some();
if let syn::Type::Path(type_path) = &*type_ref.elem {
if type_path.path.is_ident("str") {
return Some((is_mut, None)); }
}
if let syn::Type::Slice(type_slice) = &*type_ref.elem {
return Some((is_mut, Some((*type_slice.elem).clone())));
}
}
None
}
fn generate_trait_impl_types(config: &AutoZigConfig) -> proc_macro2::TokenStream {
let mut type_defs = Vec::new();
let mut generated_types = std::collections::HashSet::new();
for trait_impl in &config.rust_trait_impls {
if generated_types.contains(&trait_impl.target_type) {
continue;
}
generated_types.insert(trait_impl.target_type.clone());
let type_name = syn::Ident::new(&trait_impl.target_type, proc_macro2::Span::call_site());
if trait_impl.is_opaque {
type_defs.push(generate_opaque_struct(&type_name));
} else if trait_impl.is_zst {
type_defs.push(quote! {
#[derive(Default, Debug, Clone, Copy)]
pub struct #type_name;
});
}
}
quote! {
#(#type_defs)*
}
}
fn generate_opaque_struct(type_name: &syn::Ident) -> proc_macro2::TokenStream {
quote! {
pub struct #type_name {
inner: std::ptr::NonNull<std::ffi::c_void>,
_marker: std::marker::PhantomData<*mut ()>,
}
impl Default for #type_name {
fn default() -> Self {
Self::new()
}
}
}
}
fn generate_trait_implementations(config: &AutoZigConfig) -> proc_macro2::TokenStream {
let mut impls = Vec::new();
let mod_name = syn::Ident::new(config.get_mod_name(), proc_macro2::Span::call_site());
for trait_impl in &config.rust_trait_impls {
let type_name = syn::Ident::new(&trait_impl.target_type, proc_macro2::Span::call_site());
if let Some(constructor) = &trait_impl.constructor {
impls.push(generate_constructor(&type_name, constructor, &mod_name));
}
if let Some(destructor) = &trait_impl.destructor {
impls.push(generate_drop_impl(&type_name, destructor, &mod_name));
}
if trait_impl.trait_name.is_empty() {
continue;
}
let trait_name = syn::Ident::new(&trait_impl.trait_name, proc_macro2::Span::call_site());
let mut methods = Vec::new();
for method in &trait_impl.methods {
let method_sig = &method.sig;
let method_name = &method_sig.ident;
let inputs = &method_sig.inputs;
let return_type = &method_sig.output;
let should_generate_ffi_call = trait_impl.is_opaque || method.body.is_none();
if !should_generate_ffi_call {
if let Some(original_body) = &method.body {
methods.push(quote! {
fn #method_name(#inputs) #return_type {
unsafe #original_body
}
});
}
} else {
let zig_fn = syn::Ident::new(&method.zig_function, proc_macro2::Span::call_site());
let mut ffi_args = Vec::new();
if trait_impl.is_opaque {
ffi_args.push(inject_self_pointer(method_sig));
}
for input in &method_sig.inputs {
if let syn::FnArg::Receiver(_) = input {
continue;
}
if let syn::FnArg::Typed(pat_type) = input {
if let syn::Pat::Ident(ident) = &*pat_type.pat {
let param_name = &ident.ident;
if let Some((is_mut, _elem_type)) = is_slice_or_str_ref(&pat_type.ty) {
if is_mut {
ffi_args.push(quote! { #param_name.as_mut_ptr() });
} else {
ffi_args.push(quote! { #param_name.as_ptr() });
}
ffi_args.push(quote! { #param_name.len() });
} else {
ffi_args.push(quote! { #param_name });
}
}
}
}
methods.push(quote! {
fn #method_name(#inputs) #return_type {
unsafe {
#mod_name::#zig_fn(#(#ffi_args),*)
}
}
});
}
}
impls.push(quote! {
impl #trait_name for #type_name {
#(#methods)*
}
});
}
quote! {
#(#impls)*
}
}
fn generate_constructor(
type_name: &syn::Ident,
constructor: &autozig_parser::TraitMethod,
mod_name: &syn::Ident,
) -> proc_macro2::TokenStream {
let zig_fn = syn::Ident::new(&constructor.zig_function, proc_macro2::Span::call_site());
let method_name = syn::Ident::new(&constructor.name, proc_macro2::Span::call_site());
let params: Vec<_> = constructor
.sig
.inputs
.iter()
.filter_map(|input| {
if let syn::FnArg::Typed(pat_type) = input {
Some(pat_type)
} else {
None
}
})
.collect();
let param_names: Vec<_> = params
.iter()
.filter_map(|pat_type| {
if let syn::Pat::Ident(ident) = &*pat_type.pat {
Some(&ident.ident)
} else {
None
}
})
.collect();
let inputs = &constructor.sig.inputs;
quote! {
impl #type_name {
pub fn #method_name(#inputs) -> Self {
unsafe {
let ptr = #mod_name::#zig_fn(#(#param_names),*);
std::ptr::NonNull::new(ptr as *mut std::ffi::c_void)
.map(|inner| Self {
inner,
_marker: std::marker::PhantomData,
})
.expect("Zig allocation failed (OOM)")
}
}
}
}
}
fn generate_drop_impl(
type_name: &syn::Ident,
destructor: &autozig_parser::TraitMethod,
mod_name: &syn::Ident,
) -> proc_macro2::TokenStream {
let zig_fn = syn::Ident::new(&destructor.zig_function, proc_macro2::Span::call_site());
quote! {
impl Drop for #type_name {
fn drop(&mut self) {
unsafe {
#mod_name::#zig_fn(self.inner.as_ptr());
}
}
}
}
}
fn inject_self_pointer(sig: &syn::Signature) -> proc_macro2::TokenStream {
for input in &sig.inputs {
if let syn::FnArg::Receiver(receiver) = input {
if receiver.mutability.is_some() {
return quote! { self.inner.as_ptr() };
} else {
return quote! { self.inner.as_ptr() as *const std::ffi::c_void };
}
}
}
quote! {}
}
fn generate_trait_ffi_declarations(config: &AutoZigConfig) -> proc_macro2::TokenStream {
let mut decls = Vec::new();
for trait_impl in &config.rust_trait_impls {
if let Some(constructor) = &trait_impl.constructor {
let zig_fn = syn::Ident::new(&constructor.zig_function, proc_macro2::Span::call_site());
let params: Vec<_> = constructor
.sig
.inputs
.iter()
.filter_map(|input| {
if let syn::FnArg::Typed(pat_type) = input {
let param_name = &pat_type.pat;
let param_type = &pat_type.ty;
Some(quote! { #param_name: #param_type })
} else {
None
}
})
.collect();
decls.push(quote! {
extern "C" {
pub fn #zig_fn(#(#params),*) -> *mut std::ffi::c_void;
}
});
}
if let Some(destructor) = &trait_impl.destructor {
let zig_fn = syn::Ident::new(&destructor.zig_function, proc_macro2::Span::call_site());
decls.push(quote! {
extern "C" {
pub fn #zig_fn(ptr: *mut std::ffi::c_void);
}
});
}
for method in &trait_impl.methods {
let zig_fn = syn::Ident::new(&method.zig_function, proc_macro2::Span::call_site());
let method_sig = &method.sig;
let mut ffi_params = Vec::new();
if trait_impl.is_opaque {
let self_param = handle_receiver_type(method_sig);
if !self_param.is_empty() {
ffi_params.push(self_param);
}
}
for input in &method_sig.inputs {
if let syn::FnArg::Receiver(_) = input {
continue;
}
if let syn::FnArg::Typed(pat_type) = input {
let param_name = &pat_type.pat;
let param_type = &pat_type.ty;
if let Some((is_mut, elem_type)) = is_slice_or_str_ref(param_type) {
let param_name_str = if let syn::Pat::Ident(ident) = &*pat_type.pat {
ident.ident.to_string()
} else {
continue;
};
let ptr_type = if let Some(elem) = elem_type {
if is_mut {
quote! { *mut #elem }
} else {
quote! { *const #elem }
}
} else if is_mut {
quote! { *mut u8 }
} else {
quote! { *const u8 }
};
let ptr_name = quote::format_ident!("{}_ptr", param_name_str);
let len_name = quote::format_ident!("{}_len", param_name_str);
ffi_params.push(quote! { #ptr_name: #ptr_type });
ffi_params.push(quote! { #len_name: usize });
} else {
ffi_params.push(quote! { #param_name: #param_type });
}
}
}
let zig_return_type = extract_zig_return_type(&config.zig_code, &method.zig_function);
let return_type = if let Some(zig_ret) = zig_return_type {
zig_ret
} else {
method_sig.output.clone()
};
decls.push(quote! {
extern "C" {
pub fn #zig_fn(#(#ffi_params),*) #return_type;
}
});
}
}
quote! {
#(#decls)*
}
}
fn extract_zig_return_type(zig_code: &str, fn_name: &str) -> Option<syn::ReturnType> {
let search_pattern1 = format!("export fn {}", fn_name);
let search_pattern2 = format!("export fn\n{}", fn_name);
let start_pos = if let Some(pos) = zig_code.find(&search_pattern1) {
pos
} else if let Some(pos) = zig_code.find(&search_pattern2) {
pos
} else {
return None;
};
let after_fn = &zig_code[start_pos..];
let paren_start = after_fn.find('(')?;
let mut paren_count = 1;
let mut paren_end = paren_start + 1;
for (i, ch) in after_fn[paren_start + 1..].chars().enumerate() {
match ch {
'(' => paren_count += 1,
')' => {
paren_count -= 1;
if paren_count == 0 {
paren_end = paren_start + 1 + i;
break;
}
},
_ => {},
}
}
let after_paren = &after_fn[paren_end + 1..];
let brace_pos = after_paren.find('{')?;
let return_type_str = after_paren[..brace_pos].trim();
let rust_type = match return_type_str {
"i32" => quote! { -> i32 },
"u32" => quote! { -> u32 },
"i64" => quote! { -> i64 },
"u64" => quote! { -> u64 },
"f32" => quote! { -> f32 },
"f64" => quote! { -> f64 },
"bool" => quote! { -> bool },
"void" => quote! {},
_ => return None, };
syn::parse2(rust_type).ok()
}
fn handle_receiver_type(sig: &syn::Signature) -> proc_macro2::TokenStream {
for input in &sig.inputs {
if let syn::FnArg::Receiver(receiver) = input {
if receiver.mutability.is_some() {
return quote! { self_ptr: *mut std::ffi::c_void };
} else {
return quote! { self_ptr: *const std::ffi::c_void };
}
}
}
quote! {}
}
#[proc_macro_error]
#[proc_macro]
pub fn include_zig(input: TokenStream) -> TokenStream {
let config = parse_macro_input!(input as IncludeZigConfig);
let mod_name = config.get_unique_mod_name();
let mod_name_ident = syn::Ident::new(&mod_name, proc_macro2::Span::call_site());
let file_path = &config.file_path;
let marker_code = format!("// @autozig:include:{}", file_path);
let output = if config.has_rust_signatures()
|| !config.rust_structs.is_empty()
|| !config.rust_enums.is_empty()
{
let enum_defs = generate_enum_definitions_for_include(&config);
let struct_defs = generate_struct_definitions_for_include(&config);
let (ffi_decls, wrappers) = generate_with_monomorphization_for_include(&config);
quote! {
#[doc = #marker_code]
#enum_defs
#struct_defs
mod #mod_name_ident {
use super::*;
#ffi_decls
}
#wrappers
}
} else {
quote! {
#[doc = #marker_code]
compile_error!("include_zig! macro requires Rust function signatures");
}
};
TokenStream::from(output)
}
fn generate_enum_definitions_for_include(config: &IncludeZigConfig) -> proc_macro2::TokenStream {
let enums: Vec<_> = config.rust_enums.iter().map(|e| &e.item).collect();
quote! {
#(#enums)*
}
}
fn generate_struct_definitions_for_include(config: &IncludeZigConfig) -> proc_macro2::TokenStream {
let structs: Vec<_> = config.rust_structs.iter().map(|s| &s.item).collect();
quote! {
#(#structs)*
}
}
#[allow(dead_code)]
fn generate_ffi_declarations_for_include(config: &IncludeZigConfig) -> proc_macro2::TokenStream {
let mut decls = Vec::new();
for rust_sig in &config.rust_signatures {
let sig = &rust_sig.sig;
let fn_name = &sig.ident;
let output = &sig.output;
let mut ffi_params = Vec::new();
for input in &sig.inputs {
if let syn::FnArg::Typed(pat_type) = input {
let param_type = &pat_type.ty;
let param_name_str = if let syn::Pat::Ident(ident) = &*pat_type.pat {
ident.ident.to_string()
} else {
continue;
};
if let Some((is_mut, elem_type)) = is_slice_or_str_ref(param_type) {
let ptr_type = if let Some(elem) = elem_type {
if is_mut {
quote! { *mut #elem }
} else {
quote! { *const #elem }
}
} else if is_mut {
quote! { *mut u8 }
} else {
quote! { *const u8 }
};
let ptr_name = quote::format_ident!("{}_ptr", param_name_str);
let len_name = quote::format_ident!("{}_len", param_name_str);
ffi_params.push(quote! { #ptr_name: #ptr_type });
ffi_params.push(quote! { #len_name: usize });
} else {
let param_name = &pat_type.pat;
ffi_params.push(quote! { #param_name: #param_type });
}
}
}
decls.push(quote! {
extern "C" {
pub fn #fn_name(#(#ffi_params),*) #output;
}
});
}
quote! {
#(#decls)*
}
}
#[allow(dead_code)]
fn generate_safe_wrappers_for_include(config: &IncludeZigConfig) -> proc_macro2::TokenStream {
let mut wrappers = Vec::new();
let mod_name_str = config.get_unique_mod_name();
let mod_name = syn::Ident::new(&mod_name_str, proc_macro2::Span::call_site());
for rust_sig in &config.rust_signatures {
let sig = &rust_sig.sig;
let fn_name = &sig.ident;
let inputs = &sig.inputs;
let output = &sig.output;
let mut ffi_args = Vec::new();
for input in &sig.inputs {
if let syn::FnArg::Typed(pat_type) = input {
if let syn::Pat::Ident(ident) = &*pat_type.pat {
let param_name = &ident.ident;
let param_type = &pat_type.ty;
if let Some((is_mut, _elem_type)) = is_slice_or_str_ref(param_type) {
if is_mut {
ffi_args.push(quote! { #param_name.as_mut_ptr() });
} else {
ffi_args.push(quote! { #param_name.as_ptr() });
}
ffi_args.push(quote! { #param_name.len() });
} else {
ffi_args.push(quote! { #param_name });
}
}
}
}
let wrapper = quote! {
pub fn #fn_name(#inputs) #output {
unsafe {
#mod_name::#fn_name(#(#ffi_args),*)
}
}
};
wrappers.push(wrapper);
}
quote! {
#(#wrappers)*
}
}
#[allow(dead_code)]
fn generate_trait_impl_types_for_include(config: &IncludeZigConfig) -> proc_macro2::TokenStream {
let mut type_defs = Vec::new();
for trait_impl in &config.rust_trait_impls {
if trait_impl.is_zst {
let type_name =
syn::Ident::new(&trait_impl.target_type, proc_macro2::Span::call_site());
type_defs.push(quote! {
#[derive(Default, Debug, Clone, Copy)]
pub struct #type_name;
});
}
}
quote! {
#(#type_defs)*
}
}
#[allow(dead_code)]
fn generate_trait_implementations_for_include(
config: &IncludeZigConfig,
) -> proc_macro2::TokenStream {
let mut impls = Vec::new();
let mod_name_str = config.get_unique_mod_name();
let mod_name = syn::Ident::new(&mod_name_str, proc_macro2::Span::call_site());
for trait_impl in &config.rust_trait_impls {
let trait_name = syn::Ident::new(&trait_impl.trait_name, proc_macro2::Span::call_site());
let type_name = syn::Ident::new(&trait_impl.target_type, proc_macro2::Span::call_site());
let mut methods = Vec::new();
for method in &trait_impl.methods {
let method_sig = &method.sig;
let method_name = &method_sig.ident;
let zig_fn = syn::Ident::new(&method.zig_function, proc_macro2::Span::call_site());
let mut ffi_args = Vec::new();
for input in &method_sig.inputs {
if let syn::FnArg::Typed(pat_type) = input {
if let syn::Pat::Ident(ident) = &*pat_type.pat {
let param_name = &ident.ident;
if let Some((is_mut, _elem_type)) = is_slice_or_str_ref(&pat_type.ty) {
if is_mut {
ffi_args.push(quote! { #param_name.as_mut_ptr() });
} else {
ffi_args.push(quote! { #param_name.as_ptr() });
}
ffi_args.push(quote! { #param_name.len() });
} else {
ffi_args.push(quote! { #param_name });
}
}
}
}
let return_type = &method_sig.output;
methods.push(quote! {
fn #method_name(#method_sig) #return_type {
unsafe {
#mod_name::#zig_fn(#(#ffi_args),*)
}
}
});
}
impls.push(quote! {
impl #trait_name for #type_name {
#(#methods)*
}
});
}
quote! {
#(#impls)*
}
}
fn generate_with_monomorphization(
config: &AutoZigConfig,
) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
let mut all_ffi_decls = Vec::new();
let mut all_wrappers = Vec::new();
for rust_sig in &config.rust_signatures {
if !rust_sig.generic_params.is_empty() && !rust_sig.monomorphize_types.is_empty() {
let (mono_ffi, mono_wrappers) =
generate_monomorphized_versions(rust_sig, config.get_mod_name());
all_ffi_decls.push(mono_ffi);
all_wrappers.push(mono_wrappers);
} else if rust_sig.is_async {
let (async_ffi, async_wrapper) =
generate_async_ffi_and_wrapper(rust_sig, config.get_mod_name());
all_ffi_decls.push(async_ffi);
all_wrappers.push(async_wrapper);
} else {
let ffi_decl = generate_single_ffi_declaration(rust_sig);
let wrapper = generate_single_safe_wrapper(rust_sig, config.get_mod_name());
all_ffi_decls.push(ffi_decl);
all_wrappers.push(wrapper);
}
}
let ffi_decls = quote! { #(#all_ffi_decls)* };
let wrappers = quote! { #(#all_wrappers)* };
(ffi_decls, wrappers)
}
fn generate_single_ffi_declaration(
rust_sig: &autozig_parser::RustFunctionSignature,
) -> proc_macro2::TokenStream {
let sig = &rust_sig.sig;
let fn_name = &sig.ident;
let output = &sig.output;
let mut ffi_params = Vec::new();
for input in &sig.inputs {
if let syn::FnArg::Typed(pat_type) = input {
let param_type = &pat_type.ty;
let param_name_str = if let syn::Pat::Ident(ident) = &*pat_type.pat {
ident.ident.to_string()
} else {
continue;
};
if let Some((is_mut, elem_type)) = is_slice_or_str_ref(param_type) {
let ptr_type = if let Some(elem) = elem_type {
if is_mut {
quote! { *mut #elem }
} else {
quote! { *const #elem }
}
} else if is_mut {
quote! { *mut u8 }
} else {
quote! { *const u8 }
};
let ptr_name = quote::format_ident!("{}_ptr", param_name_str);
let len_name = quote::format_ident!("{}_len", param_name_str);
ffi_params.push(quote! { #ptr_name: #ptr_type });
ffi_params.push(quote! { #len_name: usize });
} else {
let param_name = &pat_type.pat;
ffi_params.push(quote! { #param_name: #param_type });
}
}
}
quote! {
extern "C" {
pub fn #fn_name(#(#ffi_params),*) #output;
}
}
}
fn generate_single_safe_wrapper(
rust_sig: &autozig_parser::RustFunctionSignature,
mod_name: &str,
) -> proc_macro2::TokenStream {
let sig = &rust_sig.sig;
let fn_name = &sig.ident;
let inputs = &sig.inputs;
let output = &sig.output;
let mod_ident = syn::Ident::new(mod_name, proc_macro2::Span::call_site());
let mut ffi_args = Vec::new();
for input in &sig.inputs {
if let syn::FnArg::Typed(pat_type) = input {
if let syn::Pat::Ident(ident) = &*pat_type.pat {
let param_name = &ident.ident;
let param_type = &pat_type.ty;
if let Some((is_mut, _elem_type)) = is_slice_or_str_ref(param_type) {
if is_mut {
ffi_args.push(quote! { #param_name.as_mut_ptr() });
} else {
ffi_args.push(quote! { #param_name.as_ptr() });
}
ffi_args.push(quote! { #param_name.len() });
} else {
ffi_args.push(quote! { #param_name });
}
}
}
}
quote! {
pub fn #fn_name(#inputs) #output {
unsafe {
#mod_ident::#fn_name(#(#ffi_args),*)
}
}
}
}
fn generate_monomorphized_versions(
rust_sig: &autozig_parser::RustFunctionSignature,
mod_name: &str,
) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
let mut ffi_decls = Vec::new();
let mut wrappers = Vec::new();
let base_name = &rust_sig.sig.ident;
for mono_type in &rust_sig.monomorphize_types {
let mono_name = syn::Ident::new(
&format!("{}_{}", base_name, mono_type.replace("::", "_")),
proc_macro2::Span::call_site(),
);
let mono_sig = substitute_generic_type(&rust_sig.sig, mono_type);
let ffi_decl = generate_ffi_declaration_from_sig(&mono_name, &mono_sig);
ffi_decls.push(ffi_decl);
let wrapper = generate_wrapper_from_sig(&mono_name, &mono_sig, mod_name);
wrappers.push(wrapper);
}
let ffi_output = quote! { #(#ffi_decls)* };
let wrapper_output = quote! { #(#wrappers)* };
(ffi_output, wrapper_output)
}
fn substitute_generic_type(sig: &syn::Signature, concrete_type: &str) -> syn::Signature {
let mut new_sig = sig.clone();
let concrete_ty: syn::Type =
syn::parse_str(concrete_type).unwrap_or_else(|_| panic!("Invalid type: {}", concrete_type));
let generic_name =
if let Some(syn::GenericParam::Type(type_param)) = sig.generics.params.first() {
type_param.ident.to_string()
} else {
return new_sig; };
new_sig.generics = syn::Generics::default();
for input in &mut new_sig.inputs {
if let syn::FnArg::Typed(pat_type) = input {
*pat_type.ty = substitute_type_recursive(&pat_type.ty, &generic_name, &concrete_ty);
}
}
if let syn::ReturnType::Type(_, ret_ty) = &mut new_sig.output {
**ret_ty = substitute_type_recursive(ret_ty, &generic_name, &concrete_ty);
}
new_sig
}
fn substitute_type_recursive(
ty: &syn::Type,
generic_name: &str,
concrete_ty: &syn::Type,
) -> syn::Type {
match ty {
syn::Type::Path(type_path) => {
if type_path.path.is_ident(generic_name) {
concrete_ty.clone()
} else {
ty.clone()
}
},
syn::Type::Reference(type_ref) => {
let mut new_ref = type_ref.clone();
*new_ref.elem = substitute_type_recursive(&type_ref.elem, generic_name, concrete_ty);
syn::Type::Reference(new_ref)
},
syn::Type::Slice(type_slice) => {
let mut new_slice = type_slice.clone();
*new_slice.elem =
substitute_type_recursive(&type_slice.elem, generic_name, concrete_ty);
syn::Type::Slice(new_slice)
},
_ => ty.clone(),
}
}
fn generate_ffi_declaration_from_sig(
fn_name: &syn::Ident,
sig: &syn::Signature,
) -> proc_macro2::TokenStream {
let output = &sig.output;
let mut ffi_params = Vec::new();
for input in &sig.inputs {
if let syn::FnArg::Typed(pat_type) = input {
let param_type = &pat_type.ty;
let param_name_str = if let syn::Pat::Ident(ident) = &*pat_type.pat {
ident.ident.to_string()
} else {
continue;
};
if let Some((is_mut, elem_type)) = is_slice_or_str_ref(param_type) {
let ptr_type = if let Some(elem) = elem_type {
if is_mut {
quote! { *mut #elem }
} else {
quote! { *const #elem }
}
} else if is_mut {
quote! { *mut u8 }
} else {
quote! { *const u8 }
};
let ptr_name = quote::format_ident!("{}_ptr", param_name_str);
let len_name = quote::format_ident!("{}_len", param_name_str);
ffi_params.push(quote! { #ptr_name: #ptr_type });
ffi_params.push(quote! { #len_name: usize });
} else {
let param_name = &pat_type.pat;
ffi_params.push(quote! { #param_name: #param_type });
}
}
}
quote! {
extern "C" {
pub fn #fn_name(#(#ffi_params),*) #output;
}
}
}
fn generate_wrapper_from_sig(
fn_name: &syn::Ident,
sig: &syn::Signature,
mod_name: &str,
) -> proc_macro2::TokenStream {
let mod_ident = syn::Ident::new(mod_name, proc_macro2::Span::call_site());
let inputs = &sig.inputs;
let output = &sig.output;
let mut ffi_args = Vec::new();
for input in &sig.inputs {
if let syn::FnArg::Typed(pat_type) = input {
if let syn::Pat::Ident(ident) = &*pat_type.pat {
let param_name = &ident.ident;
let param_type = &pat_type.ty;
if let Some((is_mut, _elem_type)) = is_slice_or_str_ref(param_type) {
if is_mut {
ffi_args.push(quote! { #param_name.as_mut_ptr() });
} else {
ffi_args.push(quote! { #param_name.as_ptr() });
}
ffi_args.push(quote! { #param_name.len() });
} else {
ffi_args.push(quote! { #param_name });
}
}
}
}
quote! {
pub fn #fn_name(#inputs) #output {
unsafe {
#mod_ident::#fn_name(#(#ffi_args),*)
}
}
}
}
fn generate_async_ffi_and_wrapper(
rust_sig: &autozig_parser::RustFunctionSignature,
mod_name: &str,
) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
let fn_name = &rust_sig.sig.ident;
let sig = &rust_sig.sig;
let ffi_decl = generate_ffi_declaration_from_sig(fn_name, sig);
let inputs = &sig.inputs;
let output = &sig.output;
let mod_ident = syn::Ident::new(mod_name, proc_macro2::Span::call_site());
let mut ffi_args = Vec::new();
let mut param_captures = Vec::new();
for input in &sig.inputs {
if let syn::FnArg::Typed(pat_type) = input {
if let syn::Pat::Ident(ident) = &*pat_type.pat {
let param_name = &ident.ident;
let param_type = &pat_type.ty;
if let Some((_is_mut, _elem_type)) = is_slice_or_str_ref(param_type) {
param_captures.push(quote! {
let #param_name = #param_name.to_vec();
});
ffi_args.push(quote! { #param_name.as_ptr() });
ffi_args.push(quote! { #param_name.len() });
} else {
ffi_args.push(quote! { #param_name });
}
}
}
}
let wrapper = quote! {
pub async fn #fn_name(#inputs) #output {
#(#param_captures)*
tokio::task::spawn_blocking(move || {
unsafe {
#mod_ident::#fn_name(#(#ffi_args),*)
}
})
.await
.expect("Zig task panicked or was cancelled")
}
};
(ffi_decl, wrapper)
}
fn generate_with_monomorphization_for_include(
config: &IncludeZigConfig,
) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
let mut all_ffi_decls = Vec::new();
let mut all_wrappers = Vec::new();
let mod_name = config.get_unique_mod_name();
for rust_sig in &config.rust_signatures {
if !rust_sig.generic_params.is_empty() && !rust_sig.monomorphize_types.is_empty() {
let (mono_ffi, mono_wrappers) = generate_monomorphized_versions(rust_sig, &mod_name);
all_ffi_decls.push(mono_ffi);
all_wrappers.push(mono_wrappers);
} else if rust_sig.is_async {
let (async_ffi, async_wrapper) = generate_async_ffi_and_wrapper(rust_sig, &mod_name);
all_ffi_decls.push(async_ffi);
all_wrappers.push(async_wrapper);
} else {
let ffi_decl = generate_single_ffi_declaration(rust_sig);
let wrapper = generate_single_safe_wrapper(rust_sig, &mod_name);
all_ffi_decls.push(ffi_decl);
all_wrappers.push(wrapper);
}
}
let ffi_decls = quote! { #(#all_ffi_decls)* };
let wrappers = quote! { #(#all_wrappers)* };
(ffi_decls, wrappers)
}
#[cfg(test)]
mod tests {
}