use crate::container_attributes::REFLECT_DEFAULT;
use crate::derive_data::ReflectEnum;
use crate::enum_utility::{get_variant_constructors, EnumVariantConstructors};
use crate::field_attributes::DefaultBehavior;
use crate::utility::{ident_or_index, WhereClauseOptions};
use crate::{ReflectMeta, ReflectStruct};
use bevy_macro_utils::fq_std::{FQAny, FQClone, FQDefault, FQOption};
use proc_macro2::Span;
use quote::{quote, ToTokens};
use syn::{Field, Ident, Lit, LitInt, LitStr, Member};
pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> proc_macro2::TokenStream {
impl_struct_internal(reflect_struct, false)
}
pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> proc_macro2::TokenStream {
impl_struct_internal(reflect_struct, true)
}
pub(crate) fn impl_value(meta: &ReflectMeta) -> proc_macro2::TokenStream {
let type_path = meta.type_path();
let bevy_reflect_path = meta.bevy_reflect_path();
let (impl_generics, ty_generics, where_clause) = type_path.generics().split_for_impl();
let where_from_reflect_clause = WhereClauseOptions::new(meta).extend_where_clause(where_clause);
quote! {
impl #impl_generics #bevy_reflect_path::FromReflect for #type_path #ty_generics #where_from_reflect_clause {
fn from_reflect(reflect: &dyn #bevy_reflect_path::Reflect) -> #FQOption<Self> {
#FQOption::Some(#FQClone::clone(<dyn #FQAny>::downcast_ref::<#type_path #ty_generics>(<dyn #bevy_reflect_path::Reflect>::as_any(reflect))?))
}
}
}
}
pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> proc_macro2::TokenStream {
let fqoption = FQOption.into_token_stream();
let enum_path = reflect_enum.meta().type_path();
let bevy_reflect_path = reflect_enum.meta().bevy_reflect_path();
let ref_value = Ident::new("__param0", Span::call_site());
let EnumVariantConstructors {
variant_names,
variant_constructors,
} = get_variant_constructors(reflect_enum, &ref_value, false);
let (impl_generics, ty_generics, where_clause) = enum_path.generics().split_for_impl();
let where_from_reflect_clause = reflect_enum
.where_clause_options()
.extend_where_clause(where_clause);
quote! {
impl #impl_generics #bevy_reflect_path::FromReflect for #enum_path #ty_generics #where_from_reflect_clause {
fn from_reflect(#ref_value: &dyn #bevy_reflect_path::Reflect) -> #FQOption<Self> {
if let #bevy_reflect_path::ReflectRef::Enum(#ref_value) = #bevy_reflect_path::Reflect::reflect_ref(#ref_value) {
match #bevy_reflect_path::Enum::variant_name(#ref_value) {
#(#variant_names => #fqoption::Some(#variant_constructors),)*
name => panic!("variant with name `{}` does not exist on enum `{}`", name, <Self as #bevy_reflect_path::TypePath>::type_path()),
}
} else {
#FQOption::None
}
}
}
}
}
struct MemberValuePair(Vec<Member>, Vec<proc_macro2::TokenStream>);
impl MemberValuePair {
pub fn new(items: (Vec<Member>, Vec<proc_macro2::TokenStream>)) -> Self {
Self(items.0, items.1)
}
}
fn impl_struct_internal(
reflect_struct: &ReflectStruct,
is_tuple: bool,
) -> proc_macro2::TokenStream {
let fqoption = FQOption.into_token_stream();
let struct_path = reflect_struct.meta().type_path();
let bevy_reflect_path = reflect_struct.meta().bevy_reflect_path();
let ref_struct = Ident::new("__ref_struct", Span::call_site());
let ref_struct_type = if is_tuple {
Ident::new("TupleStruct", Span::call_site())
} else {
Ident::new("Struct", Span::call_site())
};
let MemberValuePair(active_members, active_values) =
get_active_fields(reflect_struct, &ref_struct, &ref_struct_type, is_tuple);
let is_defaultable = reflect_struct.meta().attrs().contains(REFLECT_DEFAULT);
let constructor = if is_defaultable {
quote!(
let mut __this: Self = #FQDefault::default();
#(
if let #fqoption::Some(__field) = #active_values() {
__this.#active_members = __field;
}
)*
#FQOption::Some(__this)
)
} else {
let MemberValuePair(ignored_members, ignored_values) = get_ignored_fields(reflect_struct);
quote!(
#FQOption::Some(
Self {
#(#active_members: #active_values()?,)*
#(#ignored_members: #ignored_values,)*
}
)
)
};
let (impl_generics, ty_generics, where_clause) = reflect_struct
.meta()
.type_path()
.generics()
.split_for_impl();
let where_from_reflect_clause = reflect_struct
.where_clause_options()
.extend_where_clause(where_clause);
quote! {
impl #impl_generics #bevy_reflect_path::FromReflect for #struct_path #ty_generics #where_from_reflect_clause {
fn from_reflect(reflect: &dyn #bevy_reflect_path::Reflect) -> #FQOption<Self> {
if let #bevy_reflect_path::ReflectRef::#ref_struct_type(#ref_struct) = #bevy_reflect_path::Reflect::reflect_ref(reflect) {
#constructor
} else {
#FQOption::None
}
}
}
}
}
fn get_ignored_fields(reflect_struct: &ReflectStruct) -> MemberValuePair {
MemberValuePair::new(
reflect_struct
.ignored_fields()
.map(|field| {
let member = ident_or_index(field.data.ident.as_ref(), field.declaration_index);
let value = match &field.attrs.default {
DefaultBehavior::Func(path) => quote! {#path()},
_ => quote! {#FQDefault::default()},
};
(member, value)
})
.unzip(),
)
}
fn get_active_fields(
reflect_struct: &ReflectStruct,
dyn_struct_name: &Ident,
struct_type: &Ident,
is_tuple: bool,
) -> MemberValuePair {
let bevy_reflect_path = reflect_struct.meta().bevy_reflect_path();
MemberValuePair::new(
reflect_struct
.active_fields()
.map(|field| {
let member = ident_or_index(field.data.ident.as_ref(), field.declaration_index);
let accessor = get_field_accessor(
field.data,
field.reflection_index.expect("field should be active"),
is_tuple,
);
let ty = field.data.ty.clone();
let get_field = quote! {
#bevy_reflect_path::#struct_type::field(#dyn_struct_name, #accessor)
};
let value = match &field.attrs.default {
DefaultBehavior::Func(path) => quote! {
(||
if let #FQOption::Some(field) = #get_field {
<#ty as #bevy_reflect_path::FromReflect>::from_reflect(field)
} else {
#FQOption::Some(#path())
}
)
},
DefaultBehavior::Default => quote! {
(||
if let #FQOption::Some(field) = #get_field {
<#ty as #bevy_reflect_path::FromReflect>::from_reflect(field)
} else {
#FQOption::Some(#FQDefault::default())
}
)
},
DefaultBehavior::Required => quote! {
(|| <#ty as #bevy_reflect_path::FromReflect>::from_reflect(#get_field?))
},
};
(member, value)
})
.unzip(),
)
}
fn get_field_accessor(field: &Field, index: usize, is_tuple: bool) -> Lit {
if is_tuple {
Lit::Int(LitInt::new(&index.to_string(), Span::call_site()))
} else {
field
.ident
.as_ref()
.map(|ident| Lit::Str(LitStr::new(&ident.to_string(), Span::call_site())))
.unwrap_or_else(|| Lit::Str(LitStr::new(&index.to_string(), Span::call_site())))
}
}