use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::spanned::Spanned;
use syn::Error;
use crate::util::{self, Result};
pub(crate) fn derive(input: TokenStream) -> Result<TokenStream> {
let mut input: syn::DeriveInput = syn::parse2(input)?;
entity_ref(
&mut input,
quote!(::dynec),
quote! {
this_field_references_an_entity_so_it_should_have_the_entity_attribute
},
)
}
pub(crate) fn entity_ref(
input: &mut syn::DeriveInput,
crate_name: TokenStream,
not_referrer_trait: TokenStream,
) -> Result<TokenStream> {
let generics = util::parse_generics(input);
let mut assert_not_referrer_types = Vec::new();
let output = match &mut input.data {
syn::Data::Struct(s) => {
let mut field_values = Vec::new();
let mut field_types = Vec::new();
for (i, field) in s.fields.iter_mut().enumerate() {
if drain_attr(&mut field.attrs, "entity") {
let i_field = syn::Index::from(i);
field_values.push(match &field.ident {
Some(ident) => quote!(self.#ident),
None => quote!(self.#i_field),
});
field_types.push(&field.ty);
} else if !drain_attr(&mut field.attrs, "not_entity") {
assert_not_referrer_types.push(&field.ty);
}
}
generics.impl_trait(
quote!(#crate_name::entity::referrer::Referrer),
quote! {
#[inline]
fn visit_type(arg: &mut #crate_name::entity::referrer::VisitTypeArg) {
if arg.mark::<Self>().is_continue() {
#(<#field_types as #crate_name::entity::referrer::Referrer>::visit_type(arg);)*
}
}
#[inline]
fn visit_mut<V: #crate_name::entity::referrer::VisitMutArg>(
&mut self,
arg: &mut V,
) {
#(
#crate_name::entity::referrer::Referrer::visit_mut(
&mut #field_values,
arg,
);
)*
}
},
)
}
syn::Data::Enum(e) => {
let mut arms = Vec::new();
let mut all_types = Vec::new();
for variant in &mut e.variants {
let variant_ident = &variant.ident;
let (pattern, fields) = match &mut variant.fields {
syn::Fields::Unit => (quote!(), Vec::new()),
syn::Fields::Unnamed(fields) => {
let mut field_names = Vec::new();
let mut entity_fields = Vec::new();
for (i, field) in fields.unnamed.iter_mut().enumerate() {
let field_name = format_ident!("field_{}", i);
field_names.push(field_name.clone());
if drain_attr(&mut field.attrs, "entity") {
entity_fields.push(field_name);
all_types.push(&field.ty);
} else if !drain_attr(&mut field.attrs, "not_entity") {
assert_not_referrer_types.push(&field.ty);
}
}
(quote!((#(#field_names),*)), entity_fields)
}
syn::Fields::Named(fields) => {
let mut entity_fields = Vec::new();
for field in &mut fields.named {
let field_name = field.ident.as_ref().expect("named fields");
if drain_attr(&mut field.attrs, "entity") {
entity_fields.push(field_name.clone());
all_types.push(&field.ty);
} else if !drain_attr(&mut field.attrs, "not_entity") {
assert_not_referrer_types.push(&field.ty);
}
}
(quote!({ #(#entity_fields,)* .. }), entity_fields)
}
};
arms.push(quote! {
Self::#variant_ident #pattern => {
#(
#crate_name::entity::referrer::Referrer::visit_mut(
#fields,
arg,
);
)*
},
})
}
generics.impl_trait(
quote!(#crate_name::entity::Referrer),
quote! {
#[inline]
fn visit_type(arg: &mut #crate_name::entity::referrer::VisitTypeArg) {
if arg.mark::<Self>().is_continue() {
#(<#all_types as #crate_name::entity::referrer::Referrer>::visit_type(arg);)*
}
}
#[inline]
fn visit_mut<V: #crate_name::entity::referrer::VisitMutArg>(
&mut self,
arg: &mut V,
) {
match self {
#(#arms)*
}
}
},
)
}
syn::Data::Union(u) => {
return Err(Error::new_spanned(u.union_token, "only structs and enums are supported"))
}
};
let assert_not_referrer_types = assert_not_referrer_types.into_iter().map(|ty| quote::quote_spanned! { ty.span() =>
const _: fn() = || {
trait #not_referrer_trait<A> {
fn some_item() {}
}
impl<T: ?Sized> #not_referrer_trait<()> for T {}
#[allow(dead_code)]
struct Invalid;
impl<T: ?Sized + #crate_name::entity::Referrer> #not_referrer_trait<Invalid> for T {}
let _ = <#ty as #not_referrer_trait<_>>::some_item;
};
});
Ok(quote! {
#output
#(#assert_not_referrer_types)*
})
}
fn drain_attr(vec: &mut Vec<syn::Attribute>, ident: &str) -> bool {
match vec.iter().position(|attr| attr.path.is_ident(ident)) {
Some(index) => {
vec.remove(index);
true
}
None => false,
}
}