
Takes the first two commits from #15375 and adds suggestions from this comment: https://github.com/bevyengine/bevy/pull/15375#issuecomment-2366968300 See #15375 for more reasoning/motivation. ## Rebasing (rerunning) ```rust git switch simpler-lint-fixes git reset --hard main cargo fmt --all -- --unstable-features --config normalize_comments=true,imports_granularity=Crate cargo fmt --all git add --update git commit --message "rustfmt" cargo clippy --workspace --all-targets --all-features --fix cargo fmt --all -- --unstable-features --config normalize_comments=true,imports_granularity=Crate cargo fmt --all git add --update git commit --message "clippy" git cherry-pick e6c0b94f6795222310fb812fa5c4512661fc7887 ```
331 lines
12 KiB
Rust
331 lines
12 KiB
Rust
use crate::{
|
|
container_attributes::REFLECT_DEFAULT,
|
|
derive_data::ReflectEnum,
|
|
enum_utility::{EnumVariantOutputData, FromReflectVariantBuilder, VariantBuilder},
|
|
field_attributes::DefaultBehavior,
|
|
ident::ident_or_index,
|
|
where_clause_options::WhereClauseOptions,
|
|
ReflectMeta, ReflectStruct,
|
|
};
|
|
use bevy_macro_utils::fq_std::{FQClone, FQDefault, FQOption};
|
|
use proc_macro2::Span;
|
|
use quote::{quote, ToTokens};
|
|
use syn::{Field, Ident, Lit, LitInt, LitStr, Member};
|
|
|
|
/// Implements `FromReflect` for the given struct
|
|
pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> proc_macro2::TokenStream {
|
|
impl_struct_internal(reflect_struct, false)
|
|
}
|
|
|
|
/// Implements `FromReflect` for the given tuple struct
|
|
pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> proc_macro2::TokenStream {
|
|
impl_struct_internal(reflect_struct, true)
|
|
}
|
|
|
|
pub(crate) fn impl_opaque(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::PartialReflect) -> #FQOption<Self> {
|
|
#FQOption::Some(
|
|
#FQClone::clone(
|
|
<dyn #bevy_reflect_path::PartialReflect>::try_downcast_ref::<#type_path #ty_generics>(reflect)?
|
|
)
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Implements `FromReflect` for the given enum type
|
|
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 EnumVariantOutputData {
|
|
variant_names,
|
|
variant_constructors,
|
|
..
|
|
} = FromReflectVariantBuilder::new(reflect_enum).build(&ref_value);
|
|
|
|
let match_branches = if reflect_enum.meta().is_remote_wrapper() {
|
|
quote! {
|
|
#(#variant_names => #fqoption::Some(Self(#variant_constructors)),)*
|
|
}
|
|
} else {
|
|
quote! {
|
|
#(#variant_names => #fqoption::Some(#variant_constructors),)*
|
|
}
|
|
};
|
|
|
|
let (impl_generics, ty_generics, where_clause) = enum_path.generics().split_for_impl();
|
|
|
|
// Add FromReflect bound for each active field
|
|
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::PartialReflect) -> #FQOption<Self> {
|
|
if let #bevy_reflect_path::ReflectRef::Enum(#ref_value) =
|
|
#bevy_reflect_path::PartialReflect::reflect_ref(#ref_value)
|
|
{
|
|
match #bevy_reflect_path::Enum::variant_name(#ref_value) {
|
|
#match_branches
|
|
name => panic!("variant with name `{}` does not exist on enum `{}`", name, <Self as #bevy_reflect_path::TypePath>::type_path()),
|
|
}
|
|
} else {
|
|
#FQOption::None
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Container for a struct's members (field name or index) and their
|
|
/// corresponding values.
|
|
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 remote_ty = reflect_struct.meta().remote_ty();
|
|
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);
|
|
|
|
// The constructed "Self" ident
|
|
let __this = Ident::new("__this", Span::call_site());
|
|
|
|
// The reflected type: either `Self` or a remote type
|
|
let (reflect_ty, constructor, retval) = if let Some(remote_ty) = remote_ty {
|
|
let constructor = match remote_ty.as_expr_path() {
|
|
Ok(path) => path,
|
|
Err(err) => return err.into_compile_error(),
|
|
};
|
|
let remote_ty = remote_ty.type_path();
|
|
|
|
(
|
|
quote!(#remote_ty),
|
|
quote!(#constructor),
|
|
quote!(Self(#__this)),
|
|
)
|
|
} else {
|
|
(quote!(Self), quote!(Self), quote!(#__this))
|
|
};
|
|
|
|
let constructor = if is_defaultable {
|
|
quote! {
|
|
let mut #__this = <#reflect_ty as #FQDefault>::default();
|
|
#(
|
|
if let #fqoption::Some(__field) = #active_values() {
|
|
// Iff field exists -> use its value
|
|
#__this.#active_members = __field;
|
|
}
|
|
)*
|
|
#FQOption::Some(#retval)
|
|
}
|
|
} else {
|
|
let MemberValuePair(ignored_members, ignored_values) = get_ignored_fields(reflect_struct);
|
|
|
|
quote! {
|
|
let #__this = #constructor {
|
|
#(#active_members: #active_values()?,)*
|
|
#(#ignored_members: #ignored_values,)*
|
|
};
|
|
#FQOption::Some(#retval)
|
|
}
|
|
};
|
|
|
|
let (impl_generics, ty_generics, where_clause) = reflect_struct
|
|
.meta()
|
|
.type_path()
|
|
.generics()
|
|
.split_for_impl();
|
|
|
|
// Add FromReflect bound for each active field
|
|
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::PartialReflect) -> #FQOption<Self> {
|
|
if let #bevy_reflect_path::ReflectRef::#ref_struct_type(#ref_struct)
|
|
= #bevy_reflect_path::PartialReflect::reflect_ref(reflect)
|
|
{
|
|
#constructor
|
|
} else {
|
|
#FQOption::None
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Get the collection of ignored field definitions
|
|
///
|
|
/// Each value of the `MemberValuePair` is a token stream that generates a
|
|
/// a default value for the ignored field.
|
|
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(),
|
|
)
|
|
}
|
|
|
|
/// Get the collection of active field definitions.
|
|
///
|
|
/// Each value of the `MemberValuePair` is a token stream that generates a
|
|
/// closure of type `fn() -> Option<T>` where `T` is that field's type.
|
|
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.reflected_type().clone();
|
|
let real_ty = &field.data.ty;
|
|
|
|
let get_field = quote! {
|
|
#bevy_reflect_path::#struct_type::field(#dyn_struct_name, #accessor)
|
|
};
|
|
|
|
let into_remote = |value: proc_macro2::TokenStream| {
|
|
if field.attrs.is_remote_generic().unwrap_or_default() {
|
|
quote! {
|
|
#FQOption::Some(
|
|
// SAFETY: The remote type should always be a `#[repr(transparent)]` for the actual field type
|
|
unsafe {
|
|
::core::mem::transmute_copy::<#ty, #real_ty>(
|
|
&::core::mem::ManuallyDrop::new(#value?)
|
|
)
|
|
}
|
|
)
|
|
}
|
|
} else if field.attrs().remote.is_some() {
|
|
quote! {
|
|
#FQOption::Some(
|
|
// SAFETY: The remote type should always be a `#[repr(transparent)]` for the actual field type
|
|
unsafe {
|
|
::core::mem::transmute::<#ty, #real_ty>(#value?)
|
|
}
|
|
)
|
|
}
|
|
} else {
|
|
value
|
|
}
|
|
};
|
|
|
|
let value = match &field.attrs.default {
|
|
DefaultBehavior::Func(path) => {
|
|
let value = into_remote(quote! {
|
|
<#ty as #bevy_reflect_path::FromReflect>::from_reflect(field)
|
|
});
|
|
quote! {
|
|
(||
|
|
if let #FQOption::Some(field) = #get_field {
|
|
#value
|
|
} else {
|
|
#FQOption::Some(#path())
|
|
}
|
|
)
|
|
}
|
|
}
|
|
DefaultBehavior::Default => {
|
|
let value = into_remote(quote! {
|
|
<#ty as #bevy_reflect_path::FromReflect>::from_reflect(field)
|
|
});
|
|
quote! {
|
|
(||
|
|
if let #FQOption::Some(field) = #get_field {
|
|
#value
|
|
} else {
|
|
#FQOption::Some(#FQDefault::default())
|
|
}
|
|
)
|
|
}
|
|
}
|
|
DefaultBehavior::Required => {
|
|
let value = into_remote(quote! {
|
|
<#ty as #bevy_reflect_path::FromReflect>::from_reflect(#get_field?)
|
|
});
|
|
quote! {
|
|
(|| #value)
|
|
}
|
|
}
|
|
};
|
|
|
|
(member, value)
|
|
})
|
|
.unzip(),
|
|
)
|
|
}
|
|
|
|
/// Returns the accessor for a given field of a struct or tuple struct.
|
|
///
|
|
/// This differs from a member in that it needs to be a number for tuple structs
|
|
/// and a string for standard structs.
|
|
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())))
|
|
}
|
|
}
|