diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/derive_data.rs b/crates/bevy_reflect/bevy_reflect_derive/src/derive_data.rs index dfbedecafc1fa..3eb9bed751857 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/derive_data.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/derive_data.rs @@ -187,28 +187,18 @@ impl<'a> ReflectDerive<'a> { .iter() .enumerate() .map(|(index, variant)| -> Result { - let attrs = parse_field_attrs(&variant.attrs)?; let fields = Self::collect_struct_fields(&variant.fields)?; - Ok(match variant.fields { - Fields::Named(..) => EnumVariant { - data: variant, - fields: EnumVariantFields::Named(fields), - attrs, - index, - }, - Fields::Unnamed(..) => EnumVariant { - data: variant, - fields: EnumVariantFields::Unnamed(fields), - attrs, - index, - }, - Fields::Unit => EnumVariant { - data: variant, - fields: EnumVariantFields::Unit, - attrs, - index, - }, + let fields = match variant.fields { + Fields::Named(..) => EnumVariantFields::Named(fields), + Fields::Unnamed(..) => EnumVariantFields::Unnamed(fields), + Fields::Unit => EnumVariantFields::Unit, + }; + Ok(EnumVariant { + fields, + attrs: parse_field_attrs(&variant.attrs)?, + data: variant, + index, }) }) .fold( diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/enum_utility.rs b/crates/bevy_reflect/bevy_reflect_derive/src/enum_utility.rs index 64ee3bbdc8396..af6037f6c563c 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/enum_utility.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/enum_utility.rs @@ -1,4 +1,7 @@ -use crate::derive_data::{EnumVariantFields, ReflectEnum}; +use crate::{ + derive_data::{EnumVariantFields, ReflectEnum}, + utility::ident_or_index, +}; use proc_macro2::Ident; use quote::{quote, ToTokens}; @@ -17,106 +20,59 @@ pub(crate) fn get_variant_constructors( can_panic: bool, ) -> EnumVariantConstructors { let bevy_reflect_path = reflect_enum.meta().bevy_reflect_path(); - let mut variant_names: Vec = Vec::new(); - let mut variant_constructors: Vec = Vec::new(); + let variant_count = reflect_enum.variants().len(); + let mut variant_names = Vec::with_capacity(variant_count); + let mut variant_constructors = Vec::with_capacity(variant_count); for variant in reflect_enum.active_variants() { let ident = &variant.data.ident; let name = ident.to_string(); - let unit = reflect_enum.get_unit(ident); - - match &variant.fields { - EnumVariantFields::Unit => { - variant_constructors.push(quote! { - #unit - }); - } - EnumVariantFields::Unnamed(fields) => { - let mut variant_apply = Vec::new(); - let mut field_idx: usize = 0; - for field in fields.iter() { - if field.attrs.ignore { - // Ignored field -> use default value - variant_apply.push(quote! { - Default::default() - }); - continue; - } - - let field_ty = &field.data.ty; - let expect_field = format!("field at index `{}` should exist", field_idx); - let expect_type = format!( - "field at index `{}` should be of type `{}`", - field_idx, - field_ty.to_token_stream() + let variant_constructor = reflect_enum.get_unit(ident); + + let fields = match &variant.fields { + EnumVariantFields::Unit => &[], + EnumVariantFields::Named(fields) => fields.as_slice(), + EnumVariantFields::Unnamed(fields) => fields.as_slice(), + }; + let mut reflect_index: usize = 0; + let constructor_fields = fields.iter().enumerate().map(|(declar_index, field)| { + let field_ident = ident_or_index(field.data.ident.as_ref(), declar_index); + let field_value = if field.attrs.ignore { + quote! { Default::default() } + } else { + let error_repr = field.data.ident.as_ref().map_or_else( + || format!("at index {reflect_index}"), + |name| format!("`{name}`"), + ); + let unwrapper = if can_panic { + let type_err_message = format!( + "the field {error_repr} should be of type `{}`", + field.data.ty.to_token_stream() ); - - let unwrapper = if can_panic { - quote!(.expect(#expect_type)) - } else { - quote!(?) - }; - - variant_apply.push(quote! { - #bevy_reflect_path::FromReflect::from_reflect( - #ref_value - .field_at(#field_idx) - .expect(#expect_field) - ) - #unwrapper - }); - - field_idx += 1; - } - - variant_constructors.push(quote! { - #unit( #(#variant_apply),* ) - }); - } - EnumVariantFields::Named(fields) => { - let mut variant_apply = Vec::new(); - for field in fields.iter() { - let field_ident = field.data.ident.as_ref().unwrap(); - - if field.attrs.ignore { - // Ignored field -> use default value - variant_apply.push(quote! { - #field_ident: Default::default() - }); - continue; + quote!(.expect(#type_err_message)) + } else { + quote!(?) + }; + let field_accessor = match &field.data.ident { + Some(ident) => { + let name = ident.to_string(); + quote!(.field(#name)) } - - let field_name = field_ident.to_string(); - let field_ty = &field.data.ty; - let expect_field = format!("field with name `{}` should exist", field_name); - let expect_type = format!( - "field with name `{}` should be of type `{}`", - field_name, - field_ty.to_token_stream() - ); - - let unwrapper = if can_panic { - quote!(.expect(#expect_type)) - } else { - quote!(?) - }; - - variant_apply.push(quote! { - #field_ident: #bevy_reflect_path::FromReflect::from_reflect( - #ref_value - .field(#field_name) - .expect(#expect_field) - ) - #unwrapper - }); + None => quote!(.field_at(#reflect_index)), + }; + reflect_index += 1; + let missing_field_err_message = format!("the field {error_repr} was not declared"); + let accessor = quote!(#field_accessor .expect(#missing_field_err_message)); + quote! { + #bevy_reflect_path::FromReflect::from_reflect(#ref_value #accessor) + #unwrapper } - - variant_constructors.push(quote! { - #unit{ #(#variant_apply),* } - }); - } - } - + }; + quote! { #field_ident : #field_value } + }); + variant_constructors.push(quote! { + #variant_constructor { #( #constructor_fields ),* } + }); variant_names.push(name); } diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/impls/enums.rs b/crates/bevy_reflect/bevy_reflect_derive/src/impls/enums.rs index 364c0531bd191..3c982f55f8c61 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/impls/enums.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/impls/enums.rs @@ -1,7 +1,6 @@ -use crate::derive_data::{EnumVariantFields, ReflectEnum}; +use crate::derive_data::{EnumVariantFields, ReflectEnum, StructField}; use crate::enum_utility::{get_variant_constructors, EnumVariantConstructors}; use crate::impls::impl_typed; -use crate::utility; use proc_macro::TokenStream; use proc_macro2::{Ident, Span}; use quote::quote; @@ -262,126 +261,94 @@ struct EnumImpls { fn generate_impls(reflect_enum: &ReflectEnum, ref_index: &Ident, ref_name: &Ident) -> EnumImpls { let bevy_reflect_path = reflect_enum.meta().bevy_reflect_path(); - let mut variant_info: Vec = Vec::new(); - let mut enum_field: Vec = Vec::new(); - let mut enum_field_at: Vec = Vec::new(); - let mut enum_index_of: Vec = Vec::new(); - let mut enum_name_at: Vec = Vec::new(); - let mut enum_field_len: Vec = Vec::new(); - let mut enum_variant_name: Vec = Vec::new(); - let mut enum_variant_type: Vec = Vec::new(); + let mut variant_info = Vec::new(); + let mut enum_field = Vec::new(); + let mut enum_field_at = Vec::new(); + let mut enum_index_of = Vec::new(); + let mut enum_name_at = Vec::new(); + let mut enum_field_len = Vec::new(); + let mut enum_variant_name = Vec::new(); + let mut enum_variant_type = Vec::new(); for variant in reflect_enum.active_variants() { let ident = &variant.data.ident; let name = ident.to_string(); let unit = reflect_enum.get_unit(ident); + fn for_fields( + fields: &[StructField], + mut generate_for_field: impl FnMut(usize, usize, &StructField) -> proc_macro2::TokenStream, + ) -> (usize, Vec) { + let mut constructor_argument = Vec::new(); + let mut reflect_idx = 0; + for field in fields.iter() { + if field.attrs.ignore { + // Ignored field + continue; + } + constructor_argument.push(generate_for_field(reflect_idx, field.index, field)); + reflect_idx += 1; + } + (reflect_idx, constructor_argument) + } + let mut add_fields_branch = |variant, info_type, arguments, field_len| { + let variant = Ident::new(variant, Span::call_site()); + let info_type = Ident::new(info_type, Span::call_site()); + variant_info.push(quote! { + #bevy_reflect_path::VariantInfo::#variant( + #bevy_reflect_path::#info_type::new(#arguments) + ) + }); + enum_field_len.push(quote! { + #unit{..} => #field_len + }); + enum_variant_name.push(quote! { + #unit{..} => #name + }); + enum_variant_type.push(quote! { + #unit{..} => #bevy_reflect_path::VariantType::#variant + }); + }; match &variant.fields { EnumVariantFields::Unit => { - variant_info.push(quote! { - #bevy_reflect_path::VariantInfo::Unit( - #bevy_reflect_path::UnitVariantInfo::new(#name) - ) - }); - enum_variant_name.push(quote! { - #unit => #name - }); - enum_variant_type.push(quote! { - #unit => #bevy_reflect_path::VariantType::Unit - }); + add_fields_branch("Unit", "UnitVariantInfo", quote!(#name), 0usize); } EnumVariantFields::Unnamed(fields) => { - let mut field_info = Vec::new(); - let mut field_idx: usize = 0; - for field in fields.iter() { - if field.attrs.ignore { - // Ignored field - continue; - } - - let empties = utility::underscores(field_idx); + let (field_len, argument) = for_fields(fields, |reflect_idx, declar, field| { + let declar_field = syn::Index::from(declar); enum_field_at.push(quote! { - #unit( #empties value, .. ) if #ref_index == #field_idx => Some(value) + #unit { #declar_field : value, .. } if #ref_index == #reflect_idx => Some(value) }); - let field_ty = &field.data.ty; - field_info.push(quote! { - #bevy_reflect_path::UnnamedField::new::<#field_ty>(#field_idx) - }); - - field_idx += 1; - } - - let field_len = field_idx; - enum_field_len.push(quote! { - #unit(..) => #field_len - }); - enum_variant_name.push(quote! { - #unit(..) => #name - }); - enum_variant_type.push(quote! { - #unit(..) => #bevy_reflect_path::VariantType::Tuple - }); - variant_info.push(quote! { - #bevy_reflect_path::VariantInfo::Tuple( - #bevy_reflect_path::TupleVariantInfo::new(#name, &[ - #(#field_info),* - ]) - ) + quote! { #bevy_reflect_path::UnnamedField::new::<#field_ty>(#reflect_idx) } }); + let arguments = quote!(#name, &[ #(#argument),* ]); + add_fields_branch("Tuple", "TupleVariantInfo", arguments, field_len); } EnumVariantFields::Named(fields) => { - let mut field_info = Vec::new(); - let mut field_idx: usize = 0; - for field in fields.iter() { + let (field_len, argument) = for_fields(fields, |reflect_idx, _, field| { let field_ident = field.data.ident.as_ref().unwrap(); - - if field.attrs.ignore { - // Ignored field - continue; - } - let field_name = field_ident.to_string(); enum_field.push(quote! { #unit{ #field_ident, .. } if #ref_name == #field_name => Some(#field_ident) }); enum_field_at.push(quote! { - #unit{ #field_ident, .. } if #ref_index == #field_idx => Some(#field_ident) + #unit{ #field_ident, .. } if #ref_index == #reflect_idx => Some(#field_ident) }); enum_index_of.push(quote! { - #unit{ .. } if #ref_name == #field_name => Some(#field_idx) + #unit{ .. } if #ref_name == #field_name => Some(#reflect_idx) }); enum_name_at.push(quote! { - #unit{ .. } if #ref_index == #field_idx => Some(#field_name) + #unit{ .. } if #ref_index == #reflect_idx => Some(#field_name) }); let field_ty = &field.data.ty; - field_info.push(quote! { - #bevy_reflect_path::NamedField::new::<#field_ty, _>(#field_name) - }); - - field_idx += 1; - } - - let field_len = field_idx; - enum_field_len.push(quote! { - #unit{..} => #field_len - }); - enum_variant_name.push(quote! { - #unit{..} => #name - }); - enum_variant_type.push(quote! { - #unit{..} => #bevy_reflect_path::VariantType::Struct - }); - variant_info.push(quote! { - #bevy_reflect_path::VariantInfo::Struct( - #bevy_reflect_path::StructVariantInfo::new(#name, &[ - #(#field_info),* - ]) - ) + quote! { #bevy_reflect_path::NamedField::new::<#field_ty, _>(#field_name) } }); + let arguments = quote!(#name, &[ #(#argument),* ]); + add_fields_branch("Struct", "StructVariantInfo", arguments, field_len); } - } + }; } EnumImpls { diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/utility.rs b/crates/bevy_reflect/bevy_reflect_derive/src/utility.rs index 3e2a88e03caa3..f8894689a3858 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/utility.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/utility.rs @@ -2,8 +2,7 @@ use bevy_macro_utils::BevyManifest; use proc_macro2::{Ident, Span}; -use quote::quote; -use syn::Path; +use syn::{Member, Path}; /// Returns the correct path for `bevy_reflect`. pub(crate) fn get_bevy_reflect_path() -> Path { @@ -23,30 +22,6 @@ pub(crate) fn get_reflect_ident(name: &str) -> Ident { Ident::new(&reflected, Span::call_site()) } -/// Returns a token stream of comma-separated underscores for the given count. -/// -/// This is useful for creating tuple field accessors: -/// -/// ```ignore -/// let empties = underscores(2); -/// quote! { -/// let (#empties value, ..) = (10, 20, 30); -/// assert_eq!(30, value); -/// } -/// ``` -/// -/// > Note: This automatically handles the trailing comma. -/// -pub(crate) fn underscores(count: usize) -> proc_macro2::TokenStream { - let mut output = proc_macro2::TokenStream::new(); - for _ in 0..count { - output = quote! { - #output _, - } - } - output -} - /// Helper struct used to process an iterator of `Result, syn::Error>`, /// combining errors into one along the way. pub(crate) struct ResultSifter { @@ -54,6 +29,34 @@ pub(crate) struct ResultSifter { errors: Option, } +/// Returns a `Member` made of `ident` or `index` if `ident` is None. +/// +/// Rust struct syntax allows for `Struct { foo: "string" }` with explicitly +/// named fields. It allows the `Struct { 0: "string" }` syntax when the struct +/// is declared as a tuple struct. +/// +/// ``` +/// # fn main() { +/// struct Foo { field: &'static str } +/// struct Bar(&'static str); +/// let Foo { field } = Foo { field: "hi" }; +/// let Bar { 0: field } = Bar { 0: "hello" }; +/// let Bar(field) = Bar("hello"); // more common syntax +/// # } +/// ``` +/// +/// This function helps field access in context where you are declaring either +/// a tuple struct or a struct with named fields. If you don't have a field name, +/// it means you need to access the struct through an index. +pub(crate) fn ident_or_index(ident: Option<&Ident>, index: usize) -> Member { + // TODO(Quality) when #4761 is merged, code that does this should be replaced + // by a call to `ident_or_index`. + ident.map_or_else( + || Member::Unnamed(index.into()), + |ident| Member::Named(ident.clone()), + ) +} + impl Default for ResultSifter { fn default() -> Self { Self {