use proc_macro2::{Literal, Span};
use quote::{quote, quote_spanned, ToTokens};
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::*;
const PRIMITIVES: &[&str] = &[
"bool", "u8", "u16", "u32", "u64", "i8", "i16", "i32", "i64", "f32", "f64",
];
#[proc_macro_derive(FfiReflect)]
pub fn derive_ffi_reflect(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let type_expr = input.ident;
let ffi_type_expr = get_ffi_type_expr(&input.data, &input.attrs, &type_expr);
let expanded = quote! {
impl #type_expr {
pub const fn ffi_reflect() -> &'static ::ffi_reflect::FfiType<'static> {
#ffi_type_expr
}
}
};
proc_macro::TokenStream::from(expanded)
}
fn get_ffi_type_expr(
data: &Data,
attrs: &[Attribute],
type_expr: &Ident,
) -> proc_macro2::TokenStream {
match data {
Data::Struct(s) => {
if let Some(repr) = get_repr_type(attrs) {
match repr.as_str() {
"C" => return get_struct_type_expr(s, type_expr),
"transparent" => return get_transparent_type_expr(s),
_ => {}
}
}
panic!("FfiReflect derive macro only works on structs with [repr(C)] or [repr(transparent)]")
}
Data::Enum(e) => {
let enum_reprs = ["u8", "u16", "u32", "u64", "i8", "i16", "i32", "i64"];
if let Some(repr) = get_repr_type(attrs) {
let repr_str = repr.as_str();
if enum_reprs.contains(&repr_str) {
return get_enum_type_expr(e, type_expr, repr_str);
}
}
panic!("FfiReflect derive macro only works on enums with [repr(int)]")
}
Data::Union(u) => {
if let Some(repr) = get_repr_type(attrs) {
if repr.as_str() == "C" {
return get_union_type_expr(u, type_expr);
}
}
panic!("FfiReflect derive macro only works on unions with [repr(C)]");
}
}
}
fn get_repr_type(attributes: &[Attribute]) -> Option<String> {
for attribute in attributes {
if let AttrStyle::Outer = attribute.style {
if !attribute.path().is_ident("repr") {
continue;
}
let parser = Punctuated::<Meta, Token![,]>::parse_terminated;
if let Ok(nested) = attribute.parse_args_with(parser) {
if nested.len() != 1 {
continue;
}
if let Some(Meta::Path(first)) = nested.first() {
if let Some(ident) = first.get_ident() {
return Some(ident.to_string());
}
}
}
}
}
None
}
fn get_struct_type_expr(s: &DataStruct, type_expr: &Ident) -> proc_macro2::TokenStream {
let mut field_exprs = Vec::with_capacity(s.fields.len());
match &s.fields {
Fields::Named(fields) => {
for field in fields.named.iter() {
let field_name_expr = Literal::string(&field.ident.as_ref().unwrap().to_string());
let field_type_expr = get_inner_type_expr(&field.ty);
field_exprs.push(quote_spanned!(field.span() => {
::ffi_reflect::FfiStructField {
field_name: #field_name_expr,
field_type: #field_type_expr
}
}));
}
}
Fields::Unnamed(fields) => {
for (index, field) in fields.unnamed.iter().enumerate() {
let field_name_expr = Literal::string(&format!("item_{}", index));
let field_type_expr = get_inner_type_expr(&field.ty);
field_exprs.push(quote_spanned!(field.span() => {
::ffi_reflect::FfiStructField {
field_name: #field_name_expr,
field_type: #field_type_expr
}
}));
}
}
Fields::Unit => panic!("Unit structs can not derive FfiReflect"),
};
let name_expr = Literal::string(&type_expr.to_string());
quote_spanned!(s.fields.span() => {
const TYPE_SIZE : usize = ::core::mem::size_of::<#type_expr>();
const TYPE_ALIGN : usize = ::core::mem::align_of::<#type_expr>();
const TYPE_INFO : ::ffi_reflect::FfiType<'static> = ::ffi_reflect::FfiType::Struct(::ffi_reflect::FfiStruct{
name: #name_expr,
size: TYPE_SIZE,
align: TYPE_ALIGN,
fields: &[
#(#field_exprs),*
],
});
&TYPE_INFO
})
}
fn get_union_type_expr(u: &DataUnion, type_expr: &Ident) -> proc_macro2::TokenStream {
let mut field_exprs = Vec::with_capacity(u.fields.named.len());
for field in u.fields.named.iter() {
let field_name_expr = Literal::string(&field.ident.as_ref().unwrap().to_string());
let field_type_expr = get_inner_type_expr(&field.ty);
field_exprs.push(quote_spanned!(field.span() => {
::ffi_reflect::FfiStructField {
field_name: #field_name_expr,
field_type: #field_type_expr
}
}));
}
let name_expr = Literal::string(&type_expr.to_string());
quote_spanned!(u.fields.span() => {
const TYPE_SIZE : usize = ::core::mem::size_of::<#type_expr>();
const TYPE_ALIGN : usize = ::core::mem::align_of::<#type_expr>();
const TYPE_INFO : ::ffi_reflect::FfiType<'static> = ::ffi_reflect::FfiType::Union(::ffi_reflect::FfiStruct{
name: #name_expr,
size: TYPE_SIZE,
align: TYPE_ALIGN,
fields: &[
#(#field_exprs),*
],
});
&TYPE_INFO
})
}
fn get_inner_type_expr(t: &Type) -> proc_macro2::TokenStream {
match t {
Type::Array(a) => {
let item_type = a.elem.as_ref();
let array_name_expr = Literal::string(&get_array_type_name(item_type, &a.len));
let item_type_expr = get_inner_type_expr(item_type);
let item_count_expr = &a.len;
return quote_spanned!(t.span() => {
&::ffi_reflect::FfiType::Array(::ffi_reflect::FfiArray{
name: #array_name_expr,
item_type: #item_type_expr,
item_count: #item_count_expr,
})
});
}
Type::Path(p) => {
let last_seg_string = p.path.segments.last().unwrap().ident.to_string();
let last_seg_str = last_seg_string.as_str();
return if PRIMITIVES.contains(&last_seg_str) {
get_primitive_type_expr(t.span(), last_seg_str)
} else {
quote_spanned!(t.span() => #t::ffi_reflect())
};
}
Type::Ptr(ptr) => return get_inner_ptr_type_expr(ptr),
_ => {}
}
panic!("Failed to impl type info")
}
fn get_inner_ptr_type_expr(ptr: &TypePtr) -> proc_macro2::TokenStream {
let is_const = ptr.const_token.is_some();
let t = ptr.elem.as_ref();
let type_expr = match t {
Type::Array(_) | Type::Ptr(_) => get_inner_type_expr(ptr.elem.as_ref()),
Type::Path(p) => {
let last_seg_string = p.path.segments.last().unwrap().ident.to_string();
let last_seg_str = last_seg_string.as_str();
if PRIMITIVES.contains(&last_seg_str) {
get_primitive_type_expr(ptr.span(), last_seg_str)
} else {
quote_spanned!(t.span() => {#p::ffi_reflect()})
}
}
_ => {
panic!("Failed to unwrap pointer")
}
};
quote_spanned!(t.span() => {
&::ffi_reflect::FfiType::Pointer(::ffi_reflect::FfiPointer{
get_type: || #type_expr,
is_const: #is_const
})
})
}
fn get_primitive_type_expr(span: Span, type_name: &str) -> proc_macro2::TokenStream {
match type_name {
"bool" => {
quote_spanned!(span => &::ffi_reflect::FfiType::Primitive(::ffi_reflect::FfiPrimitive::BOOL))
}
"u8" => {
quote_spanned!(span => &::ffi_reflect::FfiType::Primitive(::ffi_reflect::FfiPrimitive::U8))
}
"u16" => {
quote_spanned!(span => &::ffi_reflect::FfiType::Primitive(::ffi_reflect::FfiPrimitive::U16))
}
"u32" => {
quote_spanned!(span => &::ffi_reflect::FfiType::Primitive(::ffi_reflect::FfiPrimitive::U32))
}
"u64" => {
quote_spanned!(span => &::ffi_reflect::FfiType::Primitive(::ffi_reflect::FfiPrimitive::U64))
}
"i8" => {
quote_spanned!(span => &::ffi_reflect::FfiType::Primitive(::ffi_reflect::FfiPrimitive::I8))
}
"i16" => {
quote_spanned!(span => &::ffi_reflect::FfiType::Primitive(::ffi_reflect::FfiPrimitive::I16))
}
"i32" => {
quote_spanned!(span => &::ffi_reflect::FfiType::Primitive(::ffi_reflect::FfiPrimitive::I32))
}
"i64" => {
quote_spanned!(span => &::ffi_reflect::FfiType::Primitive(::ffi_reflect::FfiPrimitive::I64))
}
"f32" => {
quote_spanned!(span => &::ffi_reflect::FfiType::Primitive(::ffi_reflect::FfiPrimitive::F32))
}
"f64" => {
quote_spanned!(span => &::ffi_reflect::FfiType::Primitive(::ffi_reflect::FfiPrimitive::F64))
}
_ => unreachable!(),
}
}
fn get_array_type_name(t: &Type, len_expr: &Expr) -> String {
if let Expr::Lit(lit) = len_expr {
if let Lit::Int(int) = &lit.lit {
let len: usize = int.base10_parse().unwrap();
let type_name = get_type_name(t);
return format!("ArrayOf{}{}", len, type_name);
}
}
panic!("Failed to get array length")
}
fn get_type_name(t: &Type) -> String {
match t {
Type::Array(a) => return get_array_type_name(a.elem.as_ref(), &a.len),
Type::Path(p) => {
if let Some(seg) = p.path.segments.last() {
return seg.ident.to_string();
}
}
_ => {}
}
panic!("Failed to get type name")
}
fn get_transparent_type_expr(s: &DataStruct) -> proc_macro2::TokenStream {
if s.fields.len() != 1 {
unreachable!()
}
let field = match &s.fields {
Fields::Named(fields) => fields.named.first().unwrap(),
Fields::Unnamed(fields) => fields.unnamed.first().unwrap(),
_ => unreachable!(),
};
let type_seg = match &field.ty {
Type::Path(p) => p.path.segments.first().unwrap(),
_ => unreachable!(),
};
let type_string = type_seg.ident.to_string();
let type_str = type_string.as_str();
if !PRIMITIVES.contains(&type_str) {
panic!("[repr(transparent)] only is supported over primitives")
}
get_primitive_type_expr(type_seg.span(), type_str)
}
fn get_enum_type_expr(e: &DataEnum, type_expr: &Ident, repr: &str) -> proc_macro2::TokenStream {
let mut item_exprs = Vec::with_capacity(e.variants.len());
for variant in e.variants.iter() {
let variant_name_expr = Literal::string(&variant.ident.to_string());
let (_, variant_value_expr) = &variant
.discriminant
.as_ref()
.expect("All enum values should be assigned for deriving FfiReflect");
let value_literal = Literal::string(&variant_value_expr.into_token_stream().to_string());
item_exprs.push(quote_spanned!(variant.span() => {
::ffi_reflect::FfiEnumItem {
name: #variant_name_expr,
value: #value_literal
}
}));
}
let underlying_type_expr = get_underlying_type_expr(e.enum_token.span, repr);
let name_expr = Literal::string(&type_expr.to_string());
quote_spanned!(e.variants.span() => {
const TYPE_INFO : ::ffi_reflect::FfiType<'static> = ::ffi_reflect::FfiType::Enum(::ffi_reflect::FfiEnum{
name: #name_expr,
underlying_type: #underlying_type_expr,
values: &[
#(#item_exprs),*
]
});
&TYPE_INFO
})
}
fn get_underlying_type_expr(span: Span, repr: &str) -> proc_macro2::TokenStream {
match repr {
"u8" => quote_spanned!(span => ::ffi_reflect::FfiEnumUnderlyingType::U8),
"u16" => quote_spanned!(span => ::ffi_reflect::FfiEnumUnderlyingType::U16),
"u32" => quote_spanned!(span => ::ffi_reflect::FfiEnumUnderlyingType::U32),
"u64" => quote_spanned!(span => ::ffi_reflect::FfiEnumUnderlyingType::U64),
"i8" => quote_spanned!(span => ::ffi_reflect::FfiEnumUnderlyingType::I8),
"i16" => quote_spanned!(span => ::ffi_reflect::FfiEnumUnderlyingType::I16),
"i32" => quote_spanned!(span => ::ffi_reflect::FfiEnumUnderlyingType::I32),
"i64" => quote_spanned!(span => ::ffi_reflect::FfiEnumUnderlyingType::I64),
_ => unreachable!(),
}
}