use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput, Data, Fields, Ident};
#[proc_macro_derive(EnumReflect)]
pub fn enum_reflection(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
let Data::Enum(data_enum) = &input.data else {
return syn::Error::new_spanned(name, "EnumReflect only works on enums")
.to_compile_error()
.into();
};
let get_fields_arms = data_enum.variants.iter().map(|v| {
let variant_ident = &v.ident;
match &v.fields {
Fields::Named(fields_named) => {
let bindings: Vec<_> = fields_named.named.iter().map(|f| {
let ident = f.ident.as_ref().unwrap();
quote! { #ident }
}).collect();
let refs: Vec<_> = bindings.iter().map(|ident| {
quote! { #ident as &dyn std::any::Any }
}).collect();
quote! {
#name::#variant_ident { #(#bindings),* } => vec![#(#refs),*],
}
}
Fields::Unnamed(fields_unnamed) => {
let bindings: Vec<_> = (0..fields_unnamed.unnamed.len())
.map(|i| syn::Ident::new(&format!("f{}", i), v.ident.span()))
.collect();
let refs: Vec<_> = bindings.iter().map(|ident| {
quote! { #ident as &dyn std::any::Any }
}).collect();
quote! {
#name::#variant_ident( #(#bindings),* ) => vec![#(#refs),*],
}
}
Fields::Unit => {
quote! {
#name::#variant_ident => vec![],
}
}
}
});
let get_named_fields_arms = data_enum.variants.iter().map(|v| {
let variant_ident = &v.ident;
match &v.fields {
Fields::Named(fields_named) => {
let bindings: Vec<_> = fields_named.named.iter().map(|f| {
let ident = f.ident.as_ref().unwrap();
quote! { #ident }
}).collect();
let pairs: Vec<_> = fields_named.named.iter().map(|f| {
let ident = f.ident.as_ref().unwrap();
let name_str = ident.to_string();
quote! { (#name_str, #ident as &dyn std::any::Any) }
}).collect();
quote! {
#name::#variant_ident { #(#bindings),* } => vec![#(#pairs),*],
}
}
Fields::Unnamed(_) => {
quote! {
#name::#variant_ident(..) => vec![],
}
}
Fields::Unit => {
quote! {
#name::#variant_ident => vec![],
}
}
}
});
let get_fields_mut_arms = data_enum.variants.iter().map(|v| {
let variant_ident = &v.ident;
match &v.fields {
Fields::Named(fields_named) => {
let bindings: Vec<_> = fields_named.named.iter().map(|f| {
let ident = f.ident.as_ref().unwrap();
quote! { #ident }
}).collect();
let refs: Vec<_> = bindings.iter().map(|ident| {
quote! { #ident as &mut dyn std::any::Any }
}).collect();
quote! {
#name::#variant_ident { #(#bindings),* } => vec![#(#refs),*]
}
}
Fields::Unnamed(fields_unnamed) => {
let bindings: Vec<_> = (0..fields_unnamed.unnamed.len())
.map(|i| Ident::new(&format!("f{}", i), v.ident.span()))
.map(|ident| quote! { #ident })
.collect();
let refs: Vec<_> = bindings.iter().map(|ident| {
quote! { #ident as &mut dyn std::any::Any }
}).collect();
quote! {
#name::#variant_ident( #(#bindings),* ) => vec![#(#refs),*]
}
}
Fields::Unit => {
quote! {
#name::#variant_ident => vec![]
}
}
}
});
let get_named_fields_mut_arms = data_enum.variants.iter().map(|v| {
let variant_ident = &v.ident;
match &v.fields {
Fields::Named(fields_named) => {
let bindings: Vec<_> = fields_named.named.iter().map(|f| {
let ident = f.ident.as_ref().unwrap();
quote! { #ident }
}).collect();
let pairs: Vec<_> = fields_named.named.iter().map(|f| {
let ident = f.ident.as_ref().unwrap();
let name_str = ident.to_string();
quote! { (#name_str, #ident as &mut dyn std::any::Any) }
}).collect();
quote! {
#name::#variant_ident { #(#bindings),* } => vec![#(#pairs),*]
}
}
Fields::Unnamed(_) => {
quote! {
#name::#variant_ident(..) => vec![]
}
}
Fields::Unit => {
quote! {
#name::#variant_ident => vec![]
}
}
}
});
let expanded = quote! {
impl enum_reflect_extern::EnumReflection for #name {
fn get_fields(&self) -> Vec<&dyn std::any::Any> {
match self {
#(#get_fields_arms)*
}
}
fn get_named_fields(&self) -> Vec<(&'static str, &dyn std::any::Any)> {
match self {
#(#get_named_fields_arms)*
}
}
fn get_fields_mut(&mut self) -> Vec<&mut dyn std::any::Any> {
match self {
#(#get_fields_mut_arms),*
}
}
fn get_named_fields_mut(&mut self) -> Vec<(&'static str, &mut dyn std::any::Any)> {
match self {
#(#get_named_fields_mut_arms),*
}
}
}
};
expanded.into()
}
#[proc_macro_derive(InspectableEnum)]
pub fn derive_inspectable_enum(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
let expanded = quote! {
impl enum_reflect_extern::InspectableEnum for #name {}
};
TokenStream::from(expanded)
}