use alloc::borrow::Cow;
use proc_macro2::TokenStream;
use quote::quote;
use syn::DataEnum;
use syn::Fields;
use syn::Ident;
use syn::Type;
use super::attrs::InnerAttrs;
use super::attrs::ItemAttrs;
use super::Generics;
use crate::stringify;
pub(super) fn expand(
ident: &Ident,
enum_: DataEnum,
attrs: ItemAttrs,
generics: &Generics<'_>,
) -> syn::Result<TokenStream> {
let variants = VariantData::try_from_enum(&enum_)?;
let reflect = expand_reflect(ident, &variants, &attrs, generics)?;
let from_reflect = (!attrs.from_reflect_opt_out)
.then(|| expand_from_reflect(ident, &variants, &attrs, generics));
let enum_ = expand_enum(ident, &variants, &attrs, generics);
Ok(quote! {
#reflect
#from_reflect
#enum_
})
}
fn expand_reflect(
ident: &Ident,
variants: &[VariantData<'_>],
attrs: &ItemAttrs,
generics: &Generics<'_>,
) -> syn::Result<TokenStream> {
let fn_patch = {
let match_arms = variants.iter().filter(filter_out_skipped).map(|variant| {
let variant_ident = &variant.ident;
let field_names = variant.field_names();
match &variant.fields {
FieldsData::Named(fields) => {
let set_fields = fields.iter().filter(filter_out_skipped).map(|field| {
let ident = field.ident;
let ident_string = stringify(ident);
quote! {
if let Some(new_value) = enum_.field(#ident_string) {
#ident.patch(new_value);
}
}
});
quote! {
Self::#variant_ident { #(#field_names,)* } => {
if variant_matches {
#(#set_fields)*
}
}
}
}
FieldsData::Unnamed(fields) => {
let set_fields = fields.iter().enumerate().filter(filter_out_skipped).map(
|(index, field)| {
let ident = &field.fake_ident;
quote! {
if let Some(new_value) = enum_.field_at(#index) {
#ident.patch(new_value);
}
}
},
);
quote! {
Self::#variant_ident(#(#field_names,)*) => {
if variant_matches {
#(#set_fields)*
}
}
}
}
FieldsData::Unit => {
quote! {
Self::#variant_ident => {
}
}
}
}
});
if attrs.clone_opt_out {
quote! {
fn patch(&mut self, value: &dyn Reflect) {
if let Some(enum_) = value.reflect_ref().as_enum() {
if let Some(new) = Self::from_reflect(value) {
*self = new;
} else {
let variant_matches = self.variant_name() == enum_.variant_name();
match self {
#(#match_arms)*
_ => {}
}
}
}
}
}
} else {
quote! {
fn patch(&mut self, value: &dyn Reflect) {
if let Some(new) = value.downcast_ref::<Self>() {
*self = new.clone();
} else if let Some(enum_) = value.reflect_ref().as_enum() {
if let Some(new) = Self::from_reflect(value) {
*self = new;
} else {
let variant_matches = self.variant_name() == enum_.variant_name();
match self {
#(#match_arms)*
_ => {}
}
}
}
}
}
}
};
let fn_to_value = {
let match_arms = variants.iter().filter(filter_out_skipped).map(|variant| {
let variant_ident = &variant.ident;
let variant_ident_string = stringify(variant_ident);
let field_names = variant.field_names();
match &variant.fields {
FieldsData::Named(fields) => {
let set_fields = fields.iter().filter(filter_out_skipped).map(|field| {
let ident = &field.ident;
let ident_string = stringify(ident);
quote! {
value.set_struct_field(#ident_string, #ident.to_value());
}
});
quote! {
Self::#variant_ident { #(#field_names,)* } => {
let mut value = EnumValue::new_struct_variant(#variant_ident_string);
#(#set_fields)*
value.finish().into()
}
}
}
FieldsData::Unnamed(fields) => {
let field_names = field_names.collect::<Vec<_>>();
let included_fields = fields
.iter()
.filter(filter_out_skipped)
.map(|field| &field.fake_ident);
quote! {
Self::#variant_ident(#(#field_names,)*) => {
let mut value = EnumValue::new_tuple_variant(#variant_ident_string);
#(
value.push_tuple_field(#included_fields.to_value());
)*
value.finish().into()
}
}
}
FieldsData::Unit => {
quote! {
Self::#variant_ident => {
EnumValue::new_unit_variant(#variant_ident_string).into()
}
}
}
}
});
quote! {
fn to_value(&self) -> Value {
match self {
#(#match_arms)*
other => {
panic!("`Reflection::to_value` called on `{:?}` which doesn't suport reflection", other.as_reflect())
}
}
}
}
};
let fn_type_info = {
let code_for_variants = variants.iter().filter(filter_out_skipped).map(|variant| {
let variant_ident_string = stringify(&variant.ident);
let meta = variant.attrs.meta();
let docs = variant.attrs.docs();
match &variant.fields {
FieldsData::Named(fields) => {
let fields = fields.iter().filter(filter_out_skipped).map(|field| {
let ident = &field.ident;
let field_name = stringify(ident);
let field_ty = &field.ty;
let meta = field.attrs.meta();
let docs = field.attrs.docs();
quote! {
NamedFieldNode::new::<#field_ty>(#field_name, #meta, #docs, graph)
}
});
quote! {
VariantNode::Struct(
StructVariantNode::new(
#variant_ident_string,
&[#(#fields),*],
#meta,
#docs,
)
)
}
}
FieldsData::Unnamed(fields) => {
let fields = fields.iter().filter(filter_out_skipped).map(|field| {
let field_ty = &field.ty;
let meta = field.attrs.meta();
let docs = field.attrs.docs();
quote! {
UnnamedFieldNode::new::<#field_ty>(#meta, #docs, graph)
}
});
quote! {
VariantNode::Tuple(
TupleVariantNode::new(
#variant_ident_string,
&[#(#fields),*],
#meta,
#docs,
)
)
}
}
FieldsData::Unit => quote! {
VariantNode::Unit(UnitVariantNode::new(
#variant_ident_string,
#meta,
#docs,
))
},
}
});
let meta = attrs.meta();
let docs = attrs.docs();
let Generics {
impl_generics,
type_generics,
where_clause,
} = generics;
quote! {
fn type_descriptor(&self) -> Cow<'static, TypeDescriptor> {
impl #impl_generics DescribeType for #ident #type_generics #where_clause {
fn build(graph: &mut TypeGraph) -> NodeId {
let variants = &[#(#code_for_variants),*];
graph.get_or_build_node_with::<Self, _>(|graph| {
EnumNode::new::<Self>(variants, #meta, #docs)
})
}
}
<Self as DescribeType>::type_descriptor()
}
}
};
let fn_debug = attrs.fn_debug_tokens();
let fn_clone_reflect = attrs.fn_clone_reflect_tokens();
let Generics {
impl_generics,
type_generics,
where_clause,
} = generics;
Ok(quote! {
impl #impl_generics Reflect for #ident #type_generics #where_clause {
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
fn as_reflect(&self) -> &dyn Reflect {
self
}
fn as_reflect_mut(&mut self) -> &mut dyn Reflect {
self
}
#fn_type_info
#fn_patch
#fn_to_value
#fn_clone_reflect
#fn_debug
fn reflect_owned(self: Box<Self>) -> ReflectOwned {
ReflectOwned::Enum(self)
}
fn reflect_ref(&self) -> ReflectRef<'_> {
ReflectRef::Enum(self)
}
fn reflect_mut(&mut self) -> ReflectMut<'_> {
ReflectMut::Enum(self)
}
}
})
}
fn expand_from_reflect(
ident: &Ident,
variants: &[VariantData<'_>],
attrs: &ItemAttrs,
generics: &Generics<'_>,
) -> TokenStream {
let match_arms = variants.iter().filter(filter_out_skipped).map(|variant| {
let variant_ident = &variant.ident;
let variant_ident_string = stringify(&variant.ident);
let expr = match &variant.fields {
FieldsData::Named(fields) => {
let set_fields = fields.iter().map(|field| {
let ident = &field.ident;
if field.skip() {
quote! {
#ident: ::core::default::Default::default(),
}
} else {
let ident_string = stringify(ident);
let ty = &field.ty;
if let Some(from_reflect_with) = field.from_reflect_with() {
quote! {
#ident: {
let value = enum_.field(#ident_string)?;
#from_reflect_with(value)?
},
}
} else if attrs.clone_opt_out {
quote! {
#ident: {
let value = enum_.field(#ident_string)?;
FromReflect::from_reflect(value)?
},
}
} else {
quote! {
#ident: {
let value = enum_.field(#ident_string)?;
if let Some(value) = value.downcast_ref::<#ty>() {
value.to_owned()
} else {
FromReflect::from_reflect(value)?
}
},
}
}
}
});
quote! {
Some(Self::#variant_ident {
#(#set_fields)*
}),
}
}
FieldsData::Unnamed(fields) => {
let set_fields = fields.iter().enumerate().map(|(idx, field)| {
if field.skip() {
quote! {
::core::default::Default::default(),
}
} else {
let ty = &field.ty;
if let Some(from_reflect_with) = field.from_reflect_with() {
quote! {
{
let value = enum_.field_at(#idx)?;
#from_reflect_with(value)?
},
}
} else if attrs.clone_opt_out {
quote! {
{
let value = enum_.field_at(#idx)?;
FromReflect::from_reflect(value)?
},
}
} else {
quote! {
{
let value = enum_.field_at(#idx)?;
if let Some(value) = value.downcast_ref::<#ty>() {
value.to_owned()
} else {
FromReflect::from_reflect(value)?
}
},
}
}
}
});
quote! {
Some(Self::#variant_ident(#(#set_fields)*)),
}
}
FieldsData::Unit => {
quote! {
Some(Self::#variant_ident),
}
}
};
quote! {
#variant_ident_string => #expr
}
});
let Generics {
impl_generics,
type_generics,
where_clause,
} = generics;
quote! {
impl #impl_generics FromReflect for #ident #type_generics #where_clause {
fn from_reflect(reflect: &dyn Reflect) -> Option<Self> {
let enum_ = reflect.reflect_ref().as_enum()?;
match enum_.variant_name() {
#(#match_arms)*
_ => None,
}
}
}
}
}
fn expand_enum(
ident: &Ident,
variants: &[VariantData<'_>],
attrs: &ItemAttrs,
generics: &Generics<'_>,
) -> TokenStream {
let fn_variant_name = {
let match_arms = variants.iter().map(|variant| {
let ident = &variant.ident;
let ident_string = stringify(ident);
quote! {
Self::#ident { .. } => #ident_string,
}
});
quote! {
fn variant_name(&self) -> &str {
match self {
#(#match_arms)*
}
}
}
};
let fn_variant_kind = {
let match_arms = variants.iter().map(|variant| {
let ident = &variant.ident;
let kind = match &variant.fields {
FieldsData::Named(_) => quote! { VariantKind::Struct },
FieldsData::Unnamed(_) => quote! { VariantKind::Tuple },
FieldsData::Unit => quote! { VariantKind::Unit },
};
quote! {
Self::#ident { .. } => #kind,
}
});
quote! {
fn variant_kind(&self) -> VariantKind {
match self {
#(#match_arms)*
}
}
}
};
let fn_field = {
let match_arms = variants.iter().filter(filter_out_skipped).map(|variant| {
let variant_ident = &variant.ident;
match &variant.fields {
FieldsData::Named(fields) => {
let field_names = variant.field_names();
let return_if_name_matches =
fields.iter().filter(filter_out_skipped).map(|field| {
let ident = &field.ident;
let ident_string = stringify(ident);
quote! {
if name == #ident_string {
return Some(#ident);
}
}
});
quote! {
Self::#variant_ident { #(#field_names,)* } => {
#(#return_if_name_matches)*
}
}
}
FieldsData::Unnamed(_) => quote! {
Self::#variant_ident(..) => return None,
},
FieldsData::Unit => quote! {
Self::#variant_ident => return None,
},
}
});
quote! {
#[allow(unused_variables, unreachable_code)]
fn field(&self, name: &str) -> Option<&dyn Reflect> {
match self {
#(#match_arms)*
_ => {}
}
None
}
}
};
let fn_field_mut = {
let match_arms = variants.iter().filter(filter_out_skipped).map(|variant| {
let variant_ident = &variant.ident;
match &variant.fields {
FieldsData::Named(fields) => {
let field_names = variant.field_names();
let return_if_name_matches =
fields.iter().filter(filter_out_skipped).map(|field| {
let ident = &field.ident;
let ident_string = stringify(ident);
quote! {
if name == #ident_string {
return Some(#ident);
}
}
});
quote! {
Self::#variant_ident { #(#field_names,)* } => {
#(#return_if_name_matches)*
},
}
}
FieldsData::Unnamed(_) => quote! {
Self::#variant_ident(..) => return None,
},
FieldsData::Unit => quote! {
Self::#variant_ident => return None,
},
}
});
quote! {
#[allow(unused_variables, unreachable_code)]
fn field_mut(&mut self, name: &str) -> Option<&mut dyn Reflect> {
match self {
#(#match_arms)*
_ => {}
}
None
}
}
};
let fn_field_at = {
let match_arms =
variants.iter().filter(filter_out_skipped).map(|variant| {
let variant_ident = &variant.ident;
let field_names = variant.field_names();
match &variant.fields {
FieldsData::Named(fields) => {
let return_if_index_matches =
fields.iter().enumerate().filter(filter_out_skipped).map(
|(idx, field)| {
let field_name = &field.ident;
quote! {
if #idx == index {
return Some(#field_name.as_reflect());
}
}
},
);
quote! {
Self::#variant_ident { #(#field_names,)* } => {
#(#return_if_index_matches)*
},
}
}
FieldsData::Unnamed(fields) => {
let return_if_index_matches =
fields.iter().enumerate().filter(filter_out_skipped).map(
|(idx, field)| {
let field_name = &field.fake_ident;
quote! {
if #idx == index {
return Some(#field_name.as_reflect());
}
}
},
);
quote! {
Self::#variant_ident(#(#field_names,)*) => {
#(#return_if_index_matches)*
},
}
}
FieldsData::Unit => quote! {
Self::#variant_ident => return None,
},
}
});
quote! {
#[allow(unused_variables, unreachable_code)]
fn field_at(&self, index: usize) -> Option<&dyn Reflect> {
match self {
#(#match_arms)*
_ => {}
}
None
}
}
};
let fn_field_at_mut = {
let match_arms =
variants.iter().filter(filter_out_skipped).map(|variant| {
let variant_ident = &variant.ident;
let field_names = variant.field_names();
match &variant.fields {
FieldsData::Named(fields) => {
let return_if_index_matches =
fields.iter().enumerate().filter(filter_out_skipped).map(
|(idx, field)| {
let field_name = &field.ident;
quote! {
if #idx == index {
return Some(#field_name.as_reflect_mut());
}
}
},
);
quote! {
Self::#variant_ident { #(#field_names,)* } => {
#(#return_if_index_matches)*
},
}
}
FieldsData::Unnamed(fields) => {
let return_if_index_matches =
fields.iter().enumerate().filter(filter_out_skipped).map(
|(idx, field)| {
let field_name = &field.fake_ident;
quote! {
if #idx == index {
return Some(#field_name.as_reflect_mut());
}
}
},
);
quote! {
Self::#variant_ident(#(#field_names,)*) => {
#(#return_if_index_matches)*
},
}
}
FieldsData::Unit => quote! {
Self::#variant_ident => return None,
},
}
});
quote! {
#[allow(unused_variables, unreachable_code)]
fn field_at_mut(&mut self, index: usize) -> Option<&mut dyn Reflect> {
match self {
#(#match_arms)*
_ => {}
}
None
}
}
};
let fn_fields = {
let crate_name = &attrs.crate_name;
quote! {
fn fields(&self) -> #crate_name::enum_::VariantFieldIter<'_> {
#crate_name::enum_::VariantFieldIter::new(self)
}
}
};
let fn_fields_mut = {
let match_arms = variants.iter().filter(filter_out_skipped).map(|variant| {
let variant_ident = &variant.ident;
let field_names = variant.field_names().collect::<Vec<_>>();
match &variant.fields {
FieldsData::Named(fields) => {
let code_for_fields = fields.iter().filter(filter_out_skipped).map(|field| {
let ident = &field.ident;
let field = stringify(ident);
quote! {
(#field, #ident.as_reflect_mut()),
}
});
quote! {
Self::#variant_ident { #(#field_names,)* } => {
let iter = [#(#code_for_fields)*];
VariantFieldIterMut::new_struct_variant(iter)
},
}
}
FieldsData::Unnamed(fields) => {
let included_fields = fields
.iter()
.filter(filter_out_skipped)
.map(|field| &field.fake_ident);
quote! {
Self::#variant_ident(#(#field_names,)*) => {
let iter = [#(#included_fields.as_reflect_mut(),)*];
VariantFieldIterMut::new_tuple_variant(iter)
},
}
}
FieldsData::Unit => quote! {
Self::#variant_ident => {
VariantFieldIterMut::empty()
},
},
}
});
quote! {
fn fields_mut(&mut self) -> VariantFieldIterMut<'_> {
match self {
#(#match_arms)*
_ => VariantFieldIterMut::empty(),
}
}
}
};
let fn_variants_len = {
let len = variants.iter().filter(filter_out_skipped).count();
quote! {
fn variants_len(&self) -> usize {
#len
}
}
};
let fn_fields_len = {
let match_arms = variants.iter().filter(filter_out_skipped).map(|variant| {
let variant_ident = &variant.ident;
match &variant.fields {
FieldsData::Named(fields) => {
let len = fields.iter().filter(filter_out_skipped).count();
quote! {
Self::#variant_ident { .. } => {
#len
},
}
}
FieldsData::Unnamed(fields) => {
let len = fields.iter().filter(filter_out_skipped).count();
quote! {
Self::#variant_ident { .. } => {
#len
},
}
}
FieldsData::Unit => quote! {
Self::#variant_ident => 0,
},
}
});
quote! {
fn fields_len(&self) -> usize {
match self {
#(#match_arms)*
_ => 0,
}
}
}
};
let fn_name_at = {
let match_arms =
variants.iter().filter(filter_out_skipped).map(|variant| {
let variant_ident = &variant.ident;
let field_names = variant.field_names();
match &variant.fields {
FieldsData::Named(fields) => {
let return_if_index_matches =
fields.iter().enumerate().filter(filter_out_skipped).map(
|(idx, field)| {
let field_name = &field.ident;
quote! {
if #idx == index {
return Some(::core::stringify!(#field_name));
}
}
},
);
quote! {
Self::#variant_ident { #(#field_names,)* } => {
#(#return_if_index_matches)*
},
}
}
FieldsData::Unnamed(fields) => {
let return_if_index_matches =
fields.iter().enumerate().filter(filter_out_skipped).map(
|(idx, field)| {
let field_name = &field.fake_ident;
quote! {
if #idx == index {
return Some(::core::stringify!(#field_name));
}
}
},
);
quote! {
Self::#variant_ident(#(#field_names,)*) => {
#(#return_if_index_matches)*
},
}
}
FieldsData::Unit => quote! {
Self::#variant_ident => {},
},
}
});
quote! {
fn name_at(&self, index: usize) -> Option<&str> {
match self {
#(#match_arms)*
_ => {}
}
None
}
}
};
let Generics {
impl_generics,
type_generics,
where_clause,
} = generics;
quote! {
impl #impl_generics Enum for #ident #type_generics #where_clause {
#fn_variant_name
#fn_variant_kind
#fn_field
#fn_field_mut
#fn_field_at
#fn_field_at_mut
#fn_fields
#fn_fields_mut
#fn_variants_len
#fn_fields_len
#fn_name_at
}
}
}
struct VariantData<'a> {
ident: &'a Ident,
attrs: InnerAttrs,
fields: FieldsData<'a>,
}
impl<'a> VariantData<'a> {
fn try_from_enum(enum_: &'a DataEnum) -> syn::Result<Vec<Self>> {
enum_
.variants
.iter()
.map(|variant| -> syn::Result<VariantData<'_>> {
let fields: FieldsData<'a> = match &variant.fields {
Fields::Named(fields) => {
let fields = fields
.named
.iter()
.map(|field| {
let ident = field.ident.as_ref().unwrap();
let ty = &field.ty;
let attrs = InnerAttrs::parse(&field.attrs)?;
Ok(NamedField { ident, ty, attrs })
})
.collect::<syn::Result<Vec<_>>>()?;
FieldsData::Named(fields)
}
Fields::Unnamed(fields) => {
let fields = fields
.unnamed
.iter()
.enumerate()
.map(|(index, field)| {
let ty = &field.ty;
let attrs = InnerAttrs::parse(&field.attrs)?;
let fake_ident = quote::format_ident!("field_{index}");
Ok(UnnamedField {
ty,
attrs,
fake_ident,
})
})
.collect::<syn::Result<Vec<_>>>()?;
FieldsData::Unnamed(fields)
}
Fields::Unit => FieldsData::Unit,
};
let attrs = InnerAttrs::parse(&variant.attrs)?;
Ok(VariantData {
ident: &variant.ident,
fields,
attrs,
})
})
.collect::<syn::Result<Vec<_>>>()
}
}
impl<'a> VariantData<'a> {
fn field_names<'this>(&'this self) -> Box<dyn Iterator<Item = Cow<'a, Ident>> + 'this> {
match &self.fields {
FieldsData::Named(fields) => {
Box::new(fields.iter().map(|field| Cow::Borrowed(field.ident)))
}
FieldsData::Unnamed(fields) => Box::new(
fields
.iter()
.enumerate()
.map(|(index, _)| Cow::Owned(quote::format_ident!("field_{index}"))),
),
FieldsData::Unit => Box::new(core::iter::empty()),
}
}
}
enum FieldsData<'a> {
Named(Vec<NamedField<'a>>),
Unnamed(Vec<UnnamedField<'a>>),
Unit,
}
struct NamedField<'a> {
ident: &'a Ident,
ty: &'a Type,
attrs: InnerAttrs,
}
impl<'a> NamedField<'a> {
#[allow(clippy::wrong_self_convention)]
fn from_reflect_with(&self) -> Option<&Ident> {
self.attrs.from_reflect_with.as_ref()
}
}
struct UnnamedField<'a> {
ty: &'a Type,
attrs: InnerAttrs,
fake_ident: Ident,
}
impl<'a> UnnamedField<'a> {
#[allow(clippy::wrong_self_convention)]
fn from_reflect_with(&self) -> Option<&Ident> {
self.attrs.from_reflect_with.as_ref()
}
}
fn filter_out_skipped<T>(skippable: &T) -> bool
where
T: Skippable,
{
!skippable.skip()
}
trait Skippable {
fn skip(&self) -> bool;
}
impl<T> Skippable for &T
where
T: Skippable + ?Sized,
{
fn skip(&self) -> bool {
T::skip(self)
}
}
impl<T> Skippable for (usize, T)
where
T: Skippable + ?Sized,
{
fn skip(&self) -> bool {
self.1.skip()
}
}
impl Skippable for VariantData<'_> {
fn skip(&self) -> bool {
self.attrs.skip
}
}
impl Skippable for NamedField<'_> {
fn skip(&self) -> bool {
self.attrs.skip
}
}
impl Skippable for UnnamedField<'_> {
fn skip(&self) -> bool {
self.attrs.skip
}
}