extern crate proc_macro;
mod attrs;
use crate::attrs::*;
use heck::SnakeCase;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
#[proc_macro_derive(GenerateFFI, attributes(sawp_ffi))]
pub fn derive_sawp_ffi(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let ast: syn::DeriveInput = syn::parse(input).unwrap();
impl_sawp_ffi(&ast).into()
}
fn impl_sawp_ffi(ast: &syn::DeriveInput) -> TokenStream {
let name = &ast.ident;
let ffi_metas: Vec<syn::NestedMeta> = ast
.attrs
.iter()
.flat_map(|attr| get_ffi_meta(attr))
.collect();
let prefix = get_ffi_prefix(&ffi_metas);
match &ast.data {
syn::Data::Struct(data) => gen_struct_accessors(&prefix, &name, &data),
syn::Data::Enum(data) => gen_enum_accessors(&prefix, &name, &data, &ffi_metas),
syn::Data::Union(_) => panic!("syn::Data::Union not supported"),
}
}
const C_FIELDS: &[&str] = &[
"u8", "u16", "u32", "u64", "usize", "i8", "i16", "i32", "i64", "isize",
];
fn is_cpp_type(ty: &syn::Type) -> bool {
if let syn::Type::Path(ty) = ty {
if let Some(ident) = ty.path.get_ident() {
return C_FIELDS.contains(&ident.to_string().as_str());
}
}
false
}
fn is_string_type(ty: &syn::Type) -> bool {
if let syn::Type::Path(ty) = ty {
if let Some(ident) = ty.path.get_ident() {
return ident.to_string().as_str() == "String" || ident.to_string().as_str() == "str";
}
}
false
}
fn split_generic(ty: &syn::Type) -> Option<(syn::Ident, syn::Ident)> {
if let syn::Type::Path(ty) = ty {
let segment = &ty.path.segments.first().expect("Path with no segments");
if let syn::PathArguments::AngleBracketed(arguments) = &segment.arguments {
if arguments.args.len() == 1 {
if let syn::GenericArgument::Type(syn::Type::Path(arg)) = &arguments.args[0] {
if let Some(inner_ident) = arg.path.get_ident() {
return Some((segment.ident.clone(), (*inner_ident).clone()));
}
}
}
}
}
None
}
fn deref(field_name: &syn::Ident) -> TokenStream {
quote! {
let #field_name = if #field_name.is_null() {
panic!("{} is NULL in {}", stringify!(#field_name), line!());
} else {
&*#field_name
};
}
}
type ReturnType = (TokenStream, TokenStream);
fn return_type(
field: &TokenStream,
ty: &syn::Type,
metas: &[syn::NestedMeta],
always_ptr: bool,
) -> ReturnType {
get_ffi_flag(&metas);
if !always_ptr && (is_cpp_type(&ty) || has_ffi_copy_meta(&metas)) {
(quote! {#ty}, quote! {#field})
} else if let Some(flag_repr) = get_ffi_flag(&metas) {
let (outer, _) = split_generic(ty).unwrap_or_else(|| {
panic!(
"sawp_ffi(flag) must be used on sawp_flags::Flags type: {}",
field
)
});
if outer.to_string().as_str() != "Flags" {
panic!(
"sawp_ffi(flag) must be used on sawp_flags::Flags type: {}",
field
);
}
if always_ptr {
(quote! {*const #flag_repr}, quote! { #field.bits_ref()})
} else {
(quote! {#flag_repr}, quote! {#field.bits()})
}
} else if let Some((outer, inner)) = split_generic(ty) {
if outer.to_string().as_str() == "Option" {
(
quote! {*const #inner},
quote! {
match #field.as_ref() {
Some(value) => value,
None => std::ptr::null(),
}
},
)
} else if always_ptr {
(quote! {*const #ty}, quote! {#field})
} else {
(quote! {*const #ty}, quote! {&#field})
}
} else if always_ptr {
(quote! {*const #ty}, quote! {#field})
} else {
(quote! {*const #ty}, quote! {&#field})
}
}
fn gen_struct_accessors(
prefix: &Option<String>,
name: &syn::Ident,
data: &syn::DataStruct,
) -> TokenStream {
let mut stream = TokenStream::new();
if let syn::Fields::Named(fields) = &data.fields {
for field in &fields.named {
match &field.vis {
syn::Visibility::Public(_) => (),
_ => continue,
}
let ffi_metas: Vec<syn::NestedMeta> = field
.attrs
.iter()
.flat_map(|attr| get_ffi_meta(attr))
.collect();
if has_ffi_skip_meta(&ffi_metas) {
continue;
}
stream.extend(gen_field_accessor(
&prefix,
&ffi_metas,
name,
field.ident.as_ref().unwrap(),
&field.ty,
));
}
}
stream
}
fn gen_field_accessor(
prefix: &Option<String>,
metas: &[syn::NestedMeta],
struct_name: &syn::Ident,
field: &syn::Ident,
ty: &syn::Type,
) -> TokenStream {
let struct_variable = struct_name.to_string().to_snake_case();
let struct_variable = syn::Ident::new(&struct_variable, proc_macro2::Span::call_site());
let func_name = match prefix {
Some(prefix) => format_ident!("{}_{}_get_{}", prefix, struct_variable, field),
None => format_ident!("{}_get_{}", struct_variable, field),
};
let deref_variable = deref(&struct_variable);
let (ret_type, ret_var) = return_type("e! {#struct_variable.#field}, ty, metas, false);
let mut accessors = quote! {
#[no_mangle]
pub unsafe extern "C" fn #func_name(#struct_variable: *const #struct_name) -> #ret_type {
#deref_variable
#ret_var
}
};
if is_string_type(ty) {
let ptr_name = format_ident!("{}_ptr", func_name);
let len_name = format_ident!("{}_len", func_name);
accessors.extend(quote! {
#[no_mangle]
pub unsafe extern "C" fn #ptr_name(#struct_variable: *const #struct_name) -> *const u8 {
#deref_variable
(#ret_var).as_ptr()
}
#[no_mangle]
pub unsafe extern "C" fn #len_name(#struct_variable: *const #struct_name) -> usize {
#deref_variable
(#ret_var).len()
}
});
} else if let Some((outer, inner)) = split_generic(ty) {
if outer.to_string().as_str() == "Vec" {
let ptr_name = format_ident!("{}_ptr", func_name);
let len_name = format_ident!("{}_len", func_name);
accessors.extend(quote! {
#[no_mangle]
pub unsafe extern "C" fn #ptr_name(#struct_variable: *const #struct_name) -> *const #inner {
#deref_variable
(#ret_var).as_ptr()
}
#[no_mangle]
pub unsafe extern "C" fn #len_name(#struct_variable: *const #struct_name) -> usize {
#deref_variable
(#ret_var).len()
}
});
if inner.to_string().as_str() != "u8" {
let idx_name = format_ident!("{}_ptr_to_idx", func_name);
accessors.extend(quote! {
#[no_mangle]
pub unsafe extern "C" fn #idx_name(#struct_variable: *const #struct_name, n: usize) -> *const #inner {
#deref_variable
(#ret_var[n])
}
});
}
}
}
accessors
}
fn gen_enum_named_match_branch(
enum_name: &syn::Ident,
variant: &syn::Variant,
match_variant: Option<&syn::Ident>,
) -> TokenStream {
let ident = &variant.ident;
if let syn::Fields::Named(fields) = &variant.fields {
let mut fields_stream = TokenStream::new();
for field in &fields.named {
let field = field.ident.as_ref().unwrap();
match match_variant {
Some(m) if m == field => {
fields_stream.extend(quote! { #field , });
}
_ => {
fields_stream.extend(quote! { #field : _, });
}
}
}
quote! { #enum_name::#ident{ #fields_stream } }
} else {
panic!("gen_enum_named_match_branch called on non-NamedFields");
}
}
fn gen_enum_unnamed_match_branch(
enum_name: &syn::Ident,
variant: &syn::Variant,
match_variant: Option<usize>,
) -> TokenStream {
let ident = &variant.ident;
if let syn::Fields::Unnamed(fields) = &variant.fields {
let mut fields_stream = TokenStream::new();
for (i, _) in (&fields.unnamed).into_iter().enumerate() {
match match_variant {
Some(m) if m == i => fields_stream.extend(quote! { var, }),
_ => fields_stream.extend(quote! { _, }),
}
}
quote! { #enum_name::#ident( #fields_stream ) }
} else {
panic!("gen_enum_unnamed_match_branch called on non-UnnamedFields");
}
}
fn gen_enum_type(
prefix: &Option<String>,
name: &syn::Ident,
variants: &[&syn::Variant],
) -> TokenStream {
let enum_variable = name.to_string().to_snake_case();
let enum_variable = syn::Ident::new(&enum_variable, proc_macro2::Span::call_site());
let enum_name = format_ident!("{}Type", name);
let mut members = TokenStream::new();
let mut matches = TokenStream::new();
for variant in variants {
let ident = &variant.ident;
members.extend(quote! {#ident, });
match &variant.fields {
syn::Fields::Named(_) => {
let match_branch = gen_enum_named_match_branch(name, variant, None);
matches.extend(quote! { #match_branch => #enum_name::#ident, });
}
syn::Fields::Unnamed(_) => {
let match_branch = gen_enum_unnamed_match_branch(name, variant, None);
matches.extend(quote! { #match_branch => #enum_name::#ident, });
}
syn::Fields::Unit => {
matches.extend(quote! { #name::#ident => #enum_name::#ident, });
}
}
}
let func_name = match prefix {
Some(prefix) => format_ident!("{}_{}_get_type", prefix, enum_variable),
None => format_ident!("{}_get_type", enum_variable),
};
let deref_variable = deref(&enum_variable);
quote! {
#[repr(C)]
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum #enum_name {
#members
}
#[no_mangle]
pub unsafe extern "C" fn #func_name(#enum_variable: *const #name) -> #enum_name
{
#deref_variable
match #enum_variable {
#matches
}
}
}
}
fn gen_enum_accessors(
prefix: &Option<String>,
name: &syn::Ident,
data: &syn::DataEnum,
top_level_metas: &[syn::NestedMeta],
) -> TokenStream {
let variants: Vec<&syn::Variant> = data.variants.iter().collect();
let mut stream = gen_enum_type(prefix, name, &variants);
if !has_ffi_type_only_meta(&top_level_metas) {
for variant in variants {
match &variant.fields {
syn::Fields::Named(fields) => {
for field in &fields.named {
let ffi_metas: Vec<syn::NestedMeta> = field
.attrs
.iter()
.flat_map(|attr| get_ffi_meta(attr))
.collect();
if has_ffi_skip_meta(&ffi_metas) {
continue;
}
stream.extend(gen_enum_named_accessor(
prefix,
&ffi_metas,
name,
variant,
field.ident.as_ref().unwrap(),
&field.ty,
));
}
}
syn::Fields::Unnamed(fields) => {
for (i, field) in (&fields.unnamed).into_iter().enumerate() {
let ffi_metas: Vec<syn::NestedMeta> = field
.attrs
.iter()
.flat_map(|attr| get_ffi_meta(attr))
.collect();
if has_ffi_skip_meta(&ffi_metas) {
continue;
}
stream.extend(gen_enum_unnamed_accessor(
prefix, &ffi_metas, name, variant, i, &field.ty,
));
}
}
_ => (),
}
}
}
stream
}
fn gen_enum_named_accessor(
prefix: &Option<String>,
metas: &[syn::NestedMeta],
enum_name: &syn::Ident,
variant: &syn::Variant,
field: &syn::Ident,
ty: &syn::Type,
) -> TokenStream {
let enum_variable = enum_name.to_string().to_snake_case();
let enum_variable = syn::Ident::new(&enum_variable, proc_macro2::Span::call_site());
let variant_name = &variant.ident.to_string().to_snake_case();
let variant_name = syn::Ident::new(&variant_name, proc_macro2::Span::call_site());
let func_name = match prefix {
Some(prefix) => format_ident!(
"{}_{}_get_{}_{}",
prefix,
enum_variable,
variant_name,
field
),
None => format_ident!("{}_get_{}_{}", enum_variable, variant_name, field),
};
let match_branch = gen_enum_named_match_branch(enum_name, variant, Some(field));
let deref_variable = deref(&enum_variable);
let (ret_type, ret_var) = return_type("e! {#field}, ty, metas, true);
let mut accessors = quote! {
#[no_mangle]
pub unsafe extern "C" fn #func_name(#enum_variable: *const #enum_name) -> #ret_type {
#deref_variable
if let #match_branch = #enum_variable {
#ret_var
} else {
std::ptr::null()
}
}
};
if is_string_type(ty) {
let ptr_name = format_ident!("{}_ptr", func_name);
let len_name = format_ident!("{}_len", func_name);
accessors.extend(quote! {
#[no_mangle]
pub unsafe extern "C" fn #ptr_name(#enum_variable: *const #enum_name) -> *const u8 {
#deref_variable
if let #match_branch = #enum_variable {
#ret_var.as_ptr()
} else {
std::ptr::null()
}
}
#[no_mangle]
pub unsafe extern "C" fn #len_name(#enum_variable: *const #enum_name) -> usize {
#deref_variable
if let #match_branch = #enum_variable {
(#ret_var).len()
} else {
0
}
}
});
} else if let Some((outer, inner)) = split_generic(ty) {
if outer.to_string().as_str() == "Vec" {
let ptr_name = format_ident!("{}_ptr", func_name);
let len_name = format_ident!("{}_len", func_name);
accessors.extend(quote! {
#[no_mangle]
pub unsafe extern "C" fn #ptr_name(#enum_variable: *const #enum_name) -> *const #inner {
#deref_variable
if let #match_branch = #enum_variable {
#ret_var.as_ptr()
} else {
std::ptr::null()
}
}
#[no_mangle]
pub unsafe extern "C" fn #len_name(#enum_variable: *const #enum_name) -> usize {
#deref_variable
if let #match_branch = #enum_variable {
(#ret_var).len()
} else {
0
}
}
});
}
}
accessors
}
fn gen_enum_unnamed_accessor(
prefix: &Option<String>,
metas: &[syn::NestedMeta],
enum_name: &syn::Ident,
variant: &syn::Variant,
field: usize,
ty: &syn::Type,
) -> TokenStream {
let enum_variable = enum_name.to_string().to_snake_case();
let enum_variable = syn::Ident::new(&enum_variable, proc_macro2::Span::call_site());
let variant_name = &variant.ident.to_string().to_snake_case();
let variant_name = syn::Ident::new(&variant_name, proc_macro2::Span::call_site());
let func_name = match &variant.fields {
syn::Fields::Unnamed(fields) if fields.unnamed.len() == 1 => match prefix {
Some(prefix) => format_ident!("{}_{}_get_{}", prefix, enum_variable, variant_name),
None => format_ident!("{}_get_{}", enum_variable, variant_name),
},
_ => match prefix {
Some(prefix) => format_ident!(
"{}_{}_get_{}_{}",
prefix,
enum_variable,
variant_name,
field
),
None => format_ident!("{}_get_{}_{}", enum_variable, variant_name, field),
},
};
let match_branch = gen_enum_unnamed_match_branch(enum_name, variant, Some(field));
let deref_variable = deref(&enum_variable);
let (ret_type, ret_var) = return_type("e! {var}, ty, metas, true);
let mut accessors = quote! {
#[no_mangle]
pub unsafe extern "C" fn #func_name(#enum_variable: *const #enum_name) -> #ret_type {
#deref_variable
if let #match_branch = #enum_variable {
#ret_var
} else {
std::ptr::null()
}
}
};
if is_string_type(ty) {
let ptr_name = format_ident!("{}_ptr", func_name);
let len_name = format_ident!("{}_len", func_name);
accessors.extend(quote! {
#[no_mangle]
pub unsafe extern "C" fn #ptr_name(#enum_variable: *const #enum_name) -> *const u8 {
#deref_variable
if let #match_branch = #enum_variable {
#ret_var.as_ptr()
} else {
std::ptr::null()
}
}
#[no_mangle]
pub unsafe extern "C" fn #len_name(#enum_variable: *const #enum_name) -> usize {
#deref_variable
if let #match_branch = #enum_variable {
(#ret_var).len()
} else {
0
}
}
});
} else if let Some((outer, inner)) = split_generic(ty) {
if outer.to_string().as_str() == "Vec" {
let ptr_name = format_ident!("{}_ptr", func_name);
let len_name = format_ident!("{}_len", func_name);
accessors.extend(quote! {
#[no_mangle]
pub unsafe extern "C" fn #ptr_name(#enum_variable: *const #enum_name) -> *const #inner {
#deref_variable
if let #match_branch = #enum_variable {
#ret_var.as_ptr()
} else {
std::ptr::null()
}
}
#[no_mangle]
pub unsafe extern "C" fn #len_name(#enum_variable: *const #enum_name) -> usize {
#deref_variable
if let #match_branch = #enum_variable {
(#ret_var).len()
} else {
0
}
}
});
}
}
accessors
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_macro_struct() {
let input = r#"
#[sawp_ffi(prefix = "sawp")]
pub struct MyStruct {
pub num: usize,
#[sawp_ffi(copy)]
pub version: Version,
#[sawp_ffi(flag = "u8")]
pub file_type: Flags<FileType>,
private: usize,
#[sawp_ffi(skip)]
skipped: usize,
pub complex: Vec<u8>,
pub string: String,
pub option: Option<u8>,
}
"#;
let parsed: syn::DeriveInput = syn::parse_str(input).unwrap();
impl_sawp_ffi(&parsed);
}
#[test]
fn test_macro_enum() {
let input = r#"
pub enum MyEnum {
UnnamedSingle(u8),
UnnamedMultiple(String, Vec<u8>),
Named {
a: u8,
b: Vec<u8>,
c: String,
d: Option<u8>,
#[sawp_ffi(flag = "u8")]
file_type: Flags<FileType>,
},
Empty,
}
"#;
let parsed: syn::DeriveInput = syn::parse_str(input).unwrap();
impl_sawp_ffi(&parsed);
}
#[test]
#[should_panic(expected = "expects string literal")]
fn test_macro_prefix_panic() {
let input = r#"
#[sawp_ffi(prefix = 0)]
pub struct MyStruct {
}
"#;
let parsed: syn::DeriveInput = syn::parse_str(input).unwrap();
impl_sawp_ffi(&parsed);
}
#[test]
#[should_panic(
expected = "sawp_ffi(flag) must be used on sawp_flags::Flags type: my_struct . file_type"
)]
fn test_macro_flag_panic() {
let input = r#"
#[sawp_ffi(prefix = "sawp")]
pub struct MyStruct {
#[sawp_ffi(flag = "u8")]
pub file_type: FileType,
}
"#;
let parsed: syn::DeriveInput = syn::parse_str(input).unwrap();
impl_sawp_ffi(&parsed);
}
#[test]
#[should_panic(expected = "Union not supported")]
fn test_macro_union_panic() {
let input = r#"
pub union MyUnion {
}
"#;
let parsed: syn::DeriveInput = syn::parse_str(input).unwrap();
impl_sawp_ffi(&parsed);
}
}