diff --git a/crates/bevy_asset/macros/src/lib.rs b/crates/bevy_asset/macros/src/lib.rs index 443bd09ab9b36..87f88d179ea22 100644 --- a/crates/bevy_asset/macros/src/lib.rs +++ b/crates/bevy_asset/macros/src/lib.rs @@ -1,10 +1,10 @@ #![expect(missing_docs, reason = "Not all docs are written yet, see #3492.")] #![cfg_attr(docsrs, feature(doc_auto_cfg))] -use bevy_macro_utils::BevyManifest; +use bevy_macro_utils::{as_member, BevyManifest}; use proc_macro::{Span, TokenStream}; use quote::{format_ident, quote}; -use syn::{parse_macro_input, Data, DeriveInput, Path}; +use syn::{parse_macro_input, Data, DataStruct, DeriveInput, Path}; pub(crate) fn bevy_asset_path() -> Path { BevyManifest::shared().get_path("bevy_asset") @@ -23,7 +23,6 @@ pub fn derive_asset(input: TokenStream) -> TokenStream { Ok(dependency_visitor) => dependency_visitor, Err(err) => return err.into_compile_error().into(), }; - TokenStream::from(quote! { impl #impl_generics #bevy_asset_path::Asset for #struct_name #type_generics #where_clause { } #dependency_visitor @@ -52,17 +51,14 @@ fn derive_dependency_visitor_internal( let field_has_dep = |f: &syn::Field| f.attrs.iter().any(is_dep_attribute); let body = match &ast.data { - Data::Struct(data_struct) => { - let fields = data_struct.fields.iter(); - let field_visitors = fields.enumerate().filter(|(_, f)| field_has_dep(f)); - let field_visitors = field_visitors.map(|(i, field)| match &field.ident { - Some(ident) => visit_dep(quote!(&self.#ident)), - None => { - let index = syn::Index::from(i); - visit_dep(quote!(&self.#index)) - } - }); - Some(quote!( #(#field_visitors)* )) + Data::Struct(DataStruct { fields, .. }) => { + let field_visitors = fields + .iter() + .enumerate() + .filter(|(_, f)| field_has_dep(f)) + .map(|(i, field)| as_member(field.ident.as_ref(), i)) + .map(|member| visit_dep(quote!(&self.#member))); + Some(quote!(#(#field_visitors)*)) } Data::Enum(data_enum) => { let variant_has_dep = |v: &syn::Variant| v.fields.iter().any(field_has_dep); @@ -70,34 +66,20 @@ fn derive_dependency_visitor_internal( let cases = data_enum.variants.iter().filter(|v| variant_has_dep(v)); let cases = cases.map(|variant| { let ident = &variant.ident; - let fields = &variant.fields; - - let field_visitors = fields.iter().enumerate().filter(|(_, f)| field_has_dep(f)); - - let field_visitors = field_visitors.map(|(i, field)| match &field.ident { - Some(ident) => visit_dep(quote!(#ident)), - None => { - let ident = format_ident!("member{i}"); - visit_dep(quote!(#ident)) - } - }); - let fields = match fields { - syn::Fields::Named(fields) => { - let named = fields.named.iter().map(|f| f.ident.as_ref()); - quote!({ #(#named,)* .. }) - } - syn::Fields::Unnamed(fields) => { - let named = (0..fields.unnamed.len()).map(|i| format_ident!("member{i}")); - quote!( ( #(#named,)* ) ) - } - syn::Fields::Unit => unreachable!("Can't pass filter is_dep_attribute"), - }; - quote!(Self::#ident #fields => { + let field_members = variant + .fields + .iter() + .enumerate() + .filter(|(_, f)| field_has_dep(f)) + .map(|(i, field)| as_member(field.ident.as_ref(), i)); + let field_locals = field_members.clone().map(|m| format_ident!("__self_{}", m)); + let field_visitors = field_locals.clone().map(|i| visit_dep(quote!(#i))); + quote!(Self::#ident {#(#field_members: #field_locals,)* ..} => { #(#field_visitors)* - }) + },) }); - any_case_required.then(|| quote!(match self { #(#cases)*, _ => {} })) + any_case_required.then(|| quote!(match self { #(#cases)* _ => {} })) } Data::Union(_) => { return Err(syn::Error::new( diff --git a/crates/bevy_ecs/compile_fail/tests/ui/world_query_derive.rs b/crates/bevy_ecs/compile_fail/tests/ui/world_query_derive.rs index 44e43430473f9..9c2c5832e5f26 100644 --- a/crates/bevy_ecs/compile_fail/tests/ui/world_query_derive.rs +++ b/crates/bevy_ecs/compile_fail/tests/ui/world_query_derive.rs @@ -17,20 +17,6 @@ struct MutableInvalidAttribute { a: &'static mut Foo, } -#[derive(QueryData)] -#[query_data(mutable(foo))] -//~^ ERROR: `mutable` does not take any arguments -struct MutableInvalidAttributeParameters { - a: &'static mut Foo, -} - -#[derive(QueryData)] -#[query_data(derive)] -//~^ ERROR: `derive` requires at least one argument -struct MutableMissingAttributeParameters { - a: &'static mut Foo, -} - #[derive(QueryData)] #[query_data(mutable)] struct MutableMarked { diff --git a/crates/bevy_ecs/compile_fail/tests/ui/world_query_derive.stderr b/crates/bevy_ecs/compile_fail/tests/ui/world_query_derive.stderr index 4460ce08a0471..ec71c112a6a05 100644 --- a/crates/bevy_ecs/compile_fail/tests/ui/world_query_derive.stderr +++ b/crates/bevy_ecs/compile_fail/tests/ui/world_query_derive.stderr @@ -4,18 +4,6 @@ error: invalid attribute, expected `mutable` or `derive` 14 | #[query_data(mut)] | ^^^ -error: `mutable` does not take any arguments - --> tests/ui/world_query_derive.rs:21:14 - | -21 | #[query_data(mutable(foo))] - | ^^^^^^^ - -error: `derive` requires at least one argument - --> tests/ui/world_query_derive.rs:28:14 - | -28 | #[query_data(derive)] - | ^^^^^^ - error[E0277]: the trait bound `&'static mut Foo: ReadOnlyQueryData` is not satisfied --> tests/ui/world_query_derive.rs:10:8 | diff --git a/crates/bevy_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs index d3199c090966c..3974a40b1cf4b 100644 --- a/crates/bevy_ecs/macros/src/component.rs +++ b/crates/bevy_ecs/macros/src/component.rs @@ -1,3 +1,4 @@ +use bevy_macro_utils::{as_member, fq_std::FQOption}; use proc_macro::TokenStream; use proc_macro2::{Span, TokenStream as TokenStream2}; use quote::{format_ident, quote, ToTokens}; @@ -324,13 +325,10 @@ pub(crate) fn map_entities( || relationship.is_some_and(|relationship| relationship == *field) }) .for_each(|(index, field)| { - let field_member = field - .ident - .clone() - .map_or(Member::from(index), Member::Named); - - map.push(quote!(#self_ident.#field_member.map_entities(mapper);)); + let member = as_member(field.ident.as_ref(), index); + map.push(quote!(#self_ident.#member.map_entities(mapper);)); }); + if map.is_empty() { return None; }; @@ -347,12 +345,7 @@ pub(crate) fn map_entities( .iter() .enumerate() .filter(|(_, field)| field.attrs.iter().any(|a| a.path().is_ident(ENTITIES))) - .map(|(index, field)| { - field - .ident - .clone() - .map_or(Member::from(index), Member::Named) - }) + .map(|(index, field)| as_member(field.ident.as_ref(), index)) .collect::>(); let ident = &variant.ident; @@ -643,8 +636,8 @@ fn hook_register_function_call( ) -> Option { function.map(|meta| { quote! { - fn #hook() -> ::core::option::Option<#bevy_ecs_path::component::ComponentHook> { - ::core::option::Option::Some(#meta) + fn #hook() -> #FQOption<#bevy_ecs_path::component::ComponentHook> { + #FQOption::Some(#meta) } } }) diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index a657765ac23f9..310df345e4104 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -13,13 +13,15 @@ use crate::{ component::map_entities, query_data::derive_query_data_impl, query_filter::derive_query_filter_impl, }; -use bevy_macro_utils::{derive_label, ensure_no_collision, get_struct_fields, BevyManifest}; +use bevy_macro_utils::{ + as_member, derive_label, ensure_no_collision, get_struct_fields, BevyManifest, +}; use proc_macro::TokenStream; use proc_macro2::{Ident, Span}; use quote::{format_ident, quote}; use syn::{ parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, token::Comma, - ConstParam, Data, DataStruct, DeriveInput, GenericParam, Index, TypeParam, + ConstParam, Data, DataStruct, DeriveInput, GenericParam, TypeParam, }; enum BundleFieldKind { @@ -67,7 +69,8 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { let field = named_fields .iter() - .map(|field| field.ident.as_ref()) + .enumerate() + .map(|(index, field)| as_member(field.ident.as_ref(), index)) .collect::>(); let field_type = named_fields @@ -80,11 +83,8 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { let mut field_get_components = Vec::new(); let mut field_from_components = Vec::new(); let mut field_required_components = Vec::new(); - for (((i, field_type), field_kind), field) in field_type - .iter() - .enumerate() - .zip(field_kind.iter()) - .zip(field.iter()) + for ((field, field_kind), field_type) in + field.iter().zip(field_kind.iter()).zip(field_type.iter()) { match field_kind { BundleFieldKind::Component => { @@ -97,27 +97,13 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { field_get_component_ids.push(quote! { <#field_type as #ecs_path::bundle::Bundle>::get_component_ids(components, &mut *ids); }); - match field { - Some(field) => { - field_get_components.push(quote! { - self.#field.get_components(&mut *func); - }); - field_from_components.push(quote! { - #field: <#field_type as #ecs_path::bundle::BundleFromComponents>::from_components(ctx, &mut *func), - }); - } - None => { - let index = Index::from(i); - field_get_components.push(quote! { - self.#index.get_components(&mut *func); - }); - field_from_components.push(quote! { - #index: <#field_type as #ecs_path::bundle::BundleFromComponents>::from_components(ctx, &mut *func), - }); - } - } + field_get_components.push(quote! { + self.#field.get_components(&mut *func); + }); + field_from_components.push(quote! { + #field: <#field_type as #ecs_path::bundle::BundleFromComponents>::from_components(ctx, &mut *func), + }); } - BundleFieldKind::Ignore => { field_from_components.push(quote! { #field: ::core::default::Default::default(), @@ -208,78 +194,70 @@ pub fn derive_map_entities(input: TokenStream) -> TokenStream { } }) } - /// Implement `SystemParam` to use a struct as a parameter in a system #[proc_macro_derive(SystemParam, attributes(system_param))] pub fn derive_system_param(input: TokenStream) -> TokenStream { let token_stream = input.clone(); let ast = parse_macro_input!(input as DeriveInput); - let Data::Struct(DataStruct { - fields: field_definitions, - .. - }) = ast.data - else { - return syn::Error::new( + match derive_system_param_impl(token_stream, ast) { + Ok(t) => t, + Err(e) => e.into_compile_error().into(), + } +} + +fn derive_system_param_impl( + token_stream: TokenStream, + ast: DeriveInput, +) -> syn::Result { + let Data::Struct(DataStruct { fields, .. }) = ast.data else { + return Err(syn::Error::new( ast.span(), "Invalid `SystemParam` type: expected a `struct`", - ) - .into_compile_error() - .into(); + )); }; let path = bevy_ecs_path(); - let mut field_locals = Vec::new(); - let mut field_names = Vec::new(); - let mut fields = Vec::new(); - let mut field_types = Vec::new(); + let field_locals = fields + .members() + .map(|m| format_ident!("__self{}", m)) + .collect::>(); + let field_members = fields.members().collect::>(); + let field_types = fields.iter().map(|f| &f.ty).collect::>(); + let field_names = fields.members().map(|m| format!("::{}", quote! { #m })); + let mut field_messages = Vec::new(); - for (i, field) in field_definitions.iter().enumerate() { - field_locals.push(format_ident!("f{i}")); - let i = Index::from(i); - let field_value = field - .ident - .as_ref() - .map(|f| quote! { #f }) - .unwrap_or_else(|| quote! { #i }); - field_names.push(format!("::{}", field_value)); - fields.push(field_value); - field_types.push(&field.ty); + for attr in fields + .iter() + .map(|f| f.attrs.iter().find(|a| a.path().is_ident("system_param"))) + { let mut field_message = None; - for meta in field - .attrs - .iter() - .filter(|a| a.path().is_ident("system_param")) - { - if let Err(e) = meta.parse_nested_meta(|nested| { + if let Some(attr) = attr { + attr.parse_nested_meta(|nested| { if nested.path.is_ident("validation_message") { field_message = Some(nested.value()?.parse()?); Ok(()) } else { Err(nested.error("Unsupported attribute")) } - }) { - return e.into_compile_error().into(); - } - } + })?; + }; field_messages.push(field_message.unwrap_or_else(|| quote! { err.message })); } let generics = ast.generics; // Emit an error if there's any unrecognized lifetime names. + let w = format_ident!("w"); + let s = format_ident!("s"); for lt in generics.lifetimes() { let ident = <.lifetime.ident; - let w = format_ident!("w"); - let s = format_ident!("s"); if ident != &w && ident != &s { - return syn::Error::new_spanned( + return Err(syn::Error::new_spanned( lt, r#"invalid lifetime name: expected `'w` or `'s` 'w -- refers to data stored in the World. 's -- refers to data stored in the SystemParam's state.'"#, - ) - .into_compile_error() - .into(); + )); } } @@ -306,14 +284,14 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream { _ => unreachable!(), })); - let mut punctuated_generic_idents = Punctuated::<_, Comma>::new(); - punctuated_generic_idents.extend(lifetimeless_generics.iter().map(|g| match g { + let mut generic_idents = Punctuated::<_, Comma>::new(); + generic_idents.extend(lifetimeless_generics.iter().map(|g| match g { GenericParam::Type(g) => &g.ident, GenericParam::Const(g) => &g.ident, _ => unreachable!(), })); - let punctuated_generics_no_bounds: Punctuated<_, Comma> = lifetimeless_generics + let generics_without_bounds: Punctuated<_, Comma> = lifetimeless_generics .iter() .map(|&g| match g.clone() { GenericParam::Type(mut g) => { @@ -361,16 +339,14 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream { .iter() .filter(|a| a.path().is_ident("system_param")) { - if let Err(e) = meta.parse_nested_meta(|nested| { + meta.parse_nested_meta(|nested| { if nested.path.is_ident("builder") { builder_name = Some(format_ident!("{struct_name}Builder")); Ok(()) } else { Err(nested.error("Unsupported attribute")) } - }) { - return e.into_compile_error().into(); - } + })?; } let builder = builder_name.map(|builder_name| { @@ -379,11 +355,11 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream { let builder_struct = quote! { #[doc = #builder_doc_comment] struct #builder_name<#(#builder_type_parameters,)*> { - #(#fields: #builder_type_parameters,)* + #(#field_members: #builder_type_parameters,)* } }; let lifetimes: Vec<_> = generics.lifetimes().collect(); - let generic_struct = quote!{ #struct_name <#(#lifetimes,)* #punctuated_generic_idents> }; + let generic_struct = quote!{ #struct_name <#(#lifetimes,)* #generic_idents> }; let builder_impl = quote!{ // SAFETY: This delegates to the `SystemParamBuilder` for tuples. unsafe impl< @@ -394,7 +370,7 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream { #where_clause { fn build(self, world: &mut #path::world::World, meta: &mut #path::system::SystemMeta) -> <#generic_struct as #path::system::SystemParam>::State { - let #builder_name { #(#fields: #field_locals,)* } = self; + let #builder_name { #(#field_members: #field_locals,)* } = self; #state_struct_name { state: #path::system::SystemParamBuilder::build((#(#tuple_patterns,)*), world, meta) } @@ -405,43 +381,43 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream { }); let (builder_struct, builder_impl) = builder.unzip(); - TokenStream::from(quote! { + Ok(TokenStream::from(quote! { // We define the FetchState struct in an anonymous scope to avoid polluting the user namespace. // The struct can still be accessed via SystemParam::State, e.g. EventReaderState can be accessed via // as SystemParam>::State const _: () = { // Allows rebinding the lifetimes of each field type. - type #fields_alias <'w, 's, #punctuated_generics_no_bounds> = (#(#tuple_types,)*); + type #fields_alias <'w, 's, #generics_without_bounds> = (#(#tuple_types,)*); #[doc(hidden)] #state_struct_visibility struct #state_struct_name <#(#lifetimeless_generics,)*> #where_clause { - state: <#fields_alias::<'static, 'static, #punctuated_generic_idents> as #path::system::SystemParam>::State, + state: <#fields_alias::<'static, 'static, #generic_idents> as #path::system::SystemParam>::State, } unsafe impl<#punctuated_generics> #path::system::SystemParam for - #struct_name <#(#shadowed_lifetimes,)* #punctuated_generic_idents> #where_clause + #struct_name <#(#shadowed_lifetimes,)* #generic_idents> #where_clause { - type State = #state_struct_name<#punctuated_generic_idents>; + type State = #state_struct_name<#generic_idents>; type Item<'w, 's> = #struct_name #ty_generics; fn init_state(world: &mut #path::world::World, system_meta: &mut #path::system::SystemMeta) -> Self::State { #state_struct_name { - state: <#fields_alias::<'_, '_, #punctuated_generic_idents> as #path::system::SystemParam>::init_state(world, system_meta), + state: <#fields_alias::<'_, '_, #generic_idents> as #path::system::SystemParam>::init_state(world, system_meta), } } unsafe fn new_archetype(state: &mut Self::State, archetype: &#path::archetype::Archetype, system_meta: &mut #path::system::SystemMeta) { // SAFETY: The caller ensures that `archetype` is from the World the state was initialized from in `init_state`. - unsafe { <#fields_alias::<'_, '_, #punctuated_generic_idents> as #path::system::SystemParam>::new_archetype(&mut state.state, archetype, system_meta) } + unsafe { <#fields_alias::<'_, '_, #generic_idents> as #path::system::SystemParam>::new_archetype(&mut state.state, archetype, system_meta) } } fn apply(state: &mut Self::State, system_meta: &#path::system::SystemMeta, world: &mut #path::world::World) { - <#fields_alias::<'_, '_, #punctuated_generic_idents> as #path::system::SystemParam>::apply(&mut state.state, system_meta, world); + <#fields_alias::<'_, '_, #generic_idents> as #path::system::SystemParam>::apply(&mut state.state, system_meta, world); } fn queue(state: &mut Self::State, system_meta: &#path::system::SystemMeta, world: #path::world::DeferredWorld) { - <#fields_alias::<'_, '_, #punctuated_generic_idents> as #path::system::SystemParam>::queue(&mut state.state, system_meta, world); + <#fields_alias::<'_, '_, #generic_idents> as #path::system::SystemParam>::queue(&mut state.state, system_meta, world); } #[inline] @@ -469,7 +445,7 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream { (#(#tuple_types,)*) as #path::system::SystemParam >::get_param(&mut state.state, system_meta, world, change_tick); #struct_name { - #(#fields: #field_locals,)* + #(#field_members: #field_locals,)* } } } @@ -481,7 +457,7 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream { }; #builder_struct - }) + })) } /// Implement `QueryData` to use a struct as a data parameter in a query diff --git a/crates/bevy_ecs/macros/src/query_data.rs b/crates/bevy_ecs/macros/src/query_data.rs index d919d0b05e125..e8a13afefec45 100644 --- a/crates/bevy_ecs/macros/src/query_data.rs +++ b/crates/bevy_ecs/macros/src/query_data.rs @@ -3,8 +3,8 @@ use proc_macro::TokenStream; use proc_macro2::{Ident, Span}; use quote::{format_ident, quote}; use syn::{ - parse_macro_input, parse_quote, punctuated::Punctuated, token, token::Comma, Attribute, Data, - DataStruct, DeriveInput, Field, Index, Meta, + parse_macro_input, parse_quote, punctuated::Punctuated, token::Comma, Data, DataStruct, + DeriveInput, Meta, }; use crate::{ @@ -36,28 +36,17 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream { let mut attributes = QueryDataAttributes::default(); for attr in &ast.attrs { - if attr - .path() - .get_ident() - .is_none_or(|ident| ident != QUERY_DATA_ATTRIBUTE_NAME) - { + if !attr.path().is_ident(QUERY_DATA_ATTRIBUTE_NAME) { continue; } - let result = attr.parse_nested_meta(|meta| { if meta.path.is_ident(MUTABLE_ATTRIBUTE_NAME) { attributes.is_mutable = true; - if meta.input.peek(token::Paren) { - Err(meta.error(format_args!("`{MUTABLE_ATTRIBUTE_NAME}` does not take any arguments"))) - } else { - Ok(()) - } + Ok(()) } else if meta.path.is_ident(DERIVE_ATTRIBUTE_NAME) { meta.parse_nested_meta(|meta| { attributes.derive_args.push(Meta::Path(meta.path)); Ok(()) - }).map_err(|_| { - meta.error(format_args!("`{DERIVE_ATTRIBUTE_NAME}` requires at least one argument")) }) } else { Err(meta.error(format_args!("invalid attribute, expected `{MUTABLE_ATTRIBUTE_NAME}` or `{DERIVE_ATTRIBUTE_NAME}`"))) @@ -122,31 +111,16 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream { }; let mut field_attrs = Vec::new(); - let mut field_visibilities = Vec::new(); let mut field_idents = Vec::new(); - let mut named_field_idents = Vec::new(); + let field_members = fields.members().collect::>(); + let mut field_visibilities = Vec::new(); let mut field_types = Vec::new(); let mut read_only_field_types = Vec::new(); for (i, field) in fields.iter().enumerate() { - let attrs = match read_world_query_field_info(field) { - Ok(QueryDataFieldInfo { attrs }) => attrs, - Err(e) => return e.into_compile_error().into(), - }; - - let named_field_ident = field - .ident - .as_ref() - .cloned() - .unwrap_or_else(|| format_ident!("f{i}")); - let i = Index::from(i); - let field_ident = field - .ident - .as_ref() - .map_or(quote! { #i }, |i| quote! { #i }); - field_idents.push(field_ident); - named_field_idents.push(named_field_ident); - field_attrs.push(attrs); + field_attrs.push(field.attrs.clone()); field_visibilities.push(field.vis.clone()); + field_idents.push(field.ident.clone().unwrap_or_else(|| format_ident!("f{i}"))); + let field_ty = field.ty.clone(); field_types.push(quote!(#field_ty)); read_only_field_types.push(quote!(<#field_ty as #path::query::QueryData>::ReadOnly)); @@ -167,7 +141,7 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream { &user_impl_generics_with_world, &field_attrs, &field_visibilities, - &field_idents, + &field_members, &user_ty_generics, &user_ty_generics_with_world, user_where_clauses_with_world, @@ -182,7 +156,7 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream { &user_impl_generics_with_world, &user_ty_generics, &user_ty_generics_with_world, - &named_field_idents, + &field_idents, &marker_name, &state_struct_name, user_where_clauses, @@ -202,7 +176,7 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream { &user_impl_generics_with_world, &field_attrs, &field_visibilities, - &field_idents, + &field_members, &user_ty_generics, &user_ty_generics_with_world, user_where_clauses_with_world, @@ -217,7 +191,7 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream { &user_impl_generics_with_world, &user_ty_generics, &user_ty_generics_with_world, - &named_field_idents, + &field_idents, &marker_name, &state_struct_name, user_where_clauses, @@ -237,7 +211,7 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream { #[doc = "Automatically generated read-only field for accessing `"] #[doc = stringify!(#field_types)] #[doc = "`."] - #field_visibilities #named_field_idents: #read_only_field_types, + #field_visibilities #field_idents: #read_only_field_types, )* } @@ -263,7 +237,7 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream { ) -> Self::Item<'__wshort> { #read_only_item_struct_name { #( - #field_idents: <#read_only_field_types>::shrink(item.#field_idents), + #field_members: <#read_only_field_types>::shrink(item.#field_members), )* } } @@ -276,7 +250,7 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream { _table_row: #path::storage::TableRow, ) -> Self::Item<'__w> { Self::Item { - #(#field_idents: <#read_only_field_types>::fetch(&mut _fetch.#named_field_idents, _entity, _table_row),)* + #(#field_members: <#read_only_field_types>::fetch(&mut _fetch.#field_idents, _entity, _table_row),)* } } } @@ -300,7 +274,7 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream { ) -> Self::Item<'__wshort> { #item_struct_name { #( - #field_idents: <#field_types>::shrink(item.#field_idents), + #field_members: <#field_types>::shrink(item.#field_members), )* } } @@ -313,7 +287,7 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream { _table_row: #path::storage::TableRow, ) -> Self::Item<'__w> { Self::Item { - #(#field_idents: <#field_types>::fetch(&mut _fetch.#named_field_idents, _entity, _table_row),)* + #(#field_members: <#field_types>::fetch(&mut _fetch.#field_idents, _entity, _table_row),)* } } } @@ -368,7 +342,7 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream { )] #[automatically_derived] #visibility struct #state_struct_name #user_impl_generics #user_where_clauses { - #(#named_field_idents: <#field_types as #path::query::WorldQuery>::State,)* + #(#field_idents: <#field_types as #path::query::WorldQuery>::State,)* } #mutable_world_query_impl @@ -410,33 +384,9 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream { q: #struct_name #user_ty_generics, q2: #read_only_struct_name #user_ty_generics ) #user_where_clauses { - #(q.#field_idents;)* - #(q2.#field_idents;)* + #(q.#field_members;)* + #(q2.#field_members;)* } }; }) } - -struct QueryDataFieldInfo { - /// All field attributes except for `query_data` ones. - attrs: Vec, -} - -fn read_world_query_field_info(field: &Field) -> syn::Result { - let mut attrs = Vec::new(); - for attr in &field.attrs { - if attr - .path() - .get_ident() - .is_some_and(|ident| ident == QUERY_DATA_ATTRIBUTE_NAME) - { - return Err(syn::Error::new_spanned( - attr, - "#[derive(QueryData)] does not support field attributes.", - )); - } - attrs.push(attr.clone()); - } - - Ok(QueryDataFieldInfo { attrs }) -} diff --git a/crates/bevy_ecs/macros/src/query_filter.rs b/crates/bevy_ecs/macros/src/query_filter.rs index c7ddb9cc83521..422ff33530b09 100644 --- a/crates/bevy_ecs/macros/src/query_filter.rs +++ b/crates/bevy_ecs/macros/src/query_filter.rs @@ -2,7 +2,7 @@ use bevy_macro_utils::ensure_no_collision; use proc_macro::TokenStream; use proc_macro2::{Ident, Span}; use quote::{format_ident, quote}; -use syn::{parse_macro_input, parse_quote, Data, DataStruct, DeriveInput, Index}; +use syn::{parse_macro_input, parse_quote, Data, DataStruct, DeriveInput}; use crate::{bevy_ecs_path, world_query::world_query_impl}; @@ -53,24 +53,11 @@ pub fn derive_query_filter_impl(input: TokenStream) -> TokenStream { let mut field_attrs = Vec::new(); let mut field_visibilities = Vec::new(); let mut field_idents = Vec::new(); - let mut named_field_idents = Vec::new(); + let field_members = fields.members().collect::>(); let mut field_types = Vec::new(); for (i, field) in fields.iter().enumerate() { - let attrs = field.attrs.clone(); - - let named_field_ident = field - .ident - .as_ref() - .cloned() - .unwrap_or_else(|| format_ident!("f{i}")); - let i = Index::from(i); - let field_ident = field - .ident - .as_ref() - .map_or(quote! { #i }, |i| quote! { #i }); - field_idents.push(field_ident); - named_field_idents.push(named_field_ident); - field_attrs.push(attrs); + field_attrs.push(field.attrs.clone()); + field_idents.push(field.ident.clone().unwrap_or(format_ident!("f{i}"))); field_visibilities.push(field.vis.clone()); let field_ty = field.ty.clone(); field_types.push(quote!(#field_ty)); @@ -86,7 +73,7 @@ pub fn derive_query_filter_impl(input: TokenStream) -> TokenStream { &user_impl_generics_with_world, &user_ty_generics, &user_ty_generics_with_world, - &named_field_idents, + &field_idents, &marker_name, &state_struct_name, user_where_clauses, @@ -106,7 +93,7 @@ pub fn derive_query_filter_impl(input: TokenStream) -> TokenStream { _entity: #path::entity::Entity, _table_row: #path::storage::TableRow, ) -> bool { - true #(&& <#field_types>::filter_fetch(&mut _fetch.#named_field_idents, _entity, _table_row))* + true #(&& <#field_types>::filter_fetch(&mut _fetch.#field_idents, _entity, _table_row))* } } }; @@ -127,7 +114,7 @@ pub fn derive_query_filter_impl(input: TokenStream) -> TokenStream { )] #[automatically_derived] #visibility struct #state_struct_name #user_impl_generics #user_where_clauses { - #(#named_field_idents: <#field_types as #path::query::WorldQuery>::State,)* + #(#field_idents: <#field_types as #path::query::WorldQuery>::State,)* } #world_query_impl @@ -159,8 +146,8 @@ pub fn derive_query_filter_impl(input: TokenStream) -> TokenStream { q: #struct_name #user_ty_generics, q2: #struct_name #user_ty_generics ) #user_where_clauses { - #(q.#field_idents;)* - #(q2.#field_idents;)* + #(q.#field_members;)* + #(q2.#field_members;)* } }; }) diff --git a/crates/bevy_ecs/macros/src/states.rs b/crates/bevy_ecs/macros/src/states.rs index ff69812aea380..12deb84719eb2 100644 --- a/crates/bevy_ecs/macros/src/states.rs +++ b/crates/bevy_ecs/macros/src/states.rs @@ -117,7 +117,7 @@ pub fn derive_substates(input: TokenStream) -> TokenStream { let source_state_type = sources.source_type; let source_state_value = sources.source_value; - let result = quote! { + quote! { impl #impl_generics #trait_path for #struct_name #ty_generics #where_clause { type SourceStates = #source_state_type; @@ -136,9 +136,5 @@ pub fn derive_substates(input: TokenStream) -> TokenStream { impl #impl_generics #state_mutation_trait_path for #struct_name #ty_generics #where_clause { } - }; - - // panic!("Got Result\n{}", result.to_string()); - - result.into() + }.into() } diff --git a/crates/bevy_ecs/macros/src/world_query.rs b/crates/bevy_ecs/macros/src/world_query.rs index 77ee532a505f7..d111cc6b16834 100644 --- a/crates/bevy_ecs/macros/src/world_query.rs +++ b/crates/bevy_ecs/macros/src/world_query.rs @@ -1,6 +1,6 @@ use proc_macro2::Ident; use quote::quote; -use syn::{Attribute, Fields, ImplGenerics, TypeGenerics, Visibility, WhereClause}; +use syn::{Attribute, Fields, ImplGenerics, Member, TypeGenerics, Visibility, WhereClause}; pub(crate) fn item_struct( path: &syn::Path, @@ -13,7 +13,7 @@ pub(crate) fn item_struct( user_impl_generics_with_world: &ImplGenerics, field_attrs: &Vec>, field_visibilities: &Vec, - field_idents: &Vec, + field_members: &Vec, user_ty_generics: &TypeGenerics, user_ty_generics_with_world: &TypeGenerics, user_where_clauses_with_world: Option<&WhereClause>, @@ -28,13 +28,12 @@ pub(crate) fn item_struct( )] #[automatically_derived] }; - match fields { Fields::Named(_) => quote! { #derive_macro_call #item_attrs #visibility struct #item_struct_name #user_impl_generics_with_world #user_where_clauses_with_world { - #(#(#field_attrs)* #field_visibilities #field_idents: <#field_types as #path::query::QueryData>::Item<'__w>,)* + #(#(#field_attrs)* #field_visibilities #field_members: <#field_types as #path::query::QueryData>::Item<'__w>,)* } }, Fields::Unnamed(_) => quote! { @@ -61,7 +60,7 @@ pub(crate) fn world_query_impl( user_impl_generics_with_world: &ImplGenerics, user_ty_generics: &TypeGenerics, user_ty_generics_with_world: &TypeGenerics, - named_field_idents: &Vec, + field_idents: &Vec, marker_name: &Ident, state_struct_name: &Ident, user_where_clauses: Option<&WhereClause>, @@ -78,7 +77,7 @@ pub(crate) fn world_query_impl( )] #[automatically_derived] #visibility struct #fetch_struct_name #user_impl_generics_with_world #user_where_clauses_with_world { - #(#named_field_idents: <#field_types as #path::query::WorldQuery>::Fetch<'__w>,)* + #(#field_idents: <#field_types as #path::query::WorldQuery>::Fetch<'__w>,)* #marker_name: &'__w (), } @@ -86,7 +85,7 @@ pub(crate) fn world_query_impl( #user_where_clauses_with_world { fn clone(&self) -> Self { Self { - #(#named_field_idents: self.#named_field_idents.clone(),)* + #(#field_idents: self.#field_idents.clone(),)* #marker_name: &(), } } @@ -104,7 +103,7 @@ pub(crate) fn world_query_impl( ) -> <#struct_name #user_ty_generics as #path::query::WorldQuery>::Fetch<'__wshort> { #fetch_struct_name { #( - #named_field_idents: <#field_types>::shrink_fetch(fetch.#named_field_idents), + #field_idents: <#field_types>::shrink_fetch(fetch.#field_idents), )* #marker_name: &(), } @@ -117,10 +116,10 @@ pub(crate) fn world_query_impl( _this_run: #path::component::Tick, ) -> ::Fetch<'__w> { #fetch_struct_name { - #(#named_field_idents: + #(#field_idents: <#field_types>::init_fetch( _world, - &state.#named_field_idents, + &state.#field_idents, _last_run, _this_run, ), @@ -139,7 +138,7 @@ pub(crate) fn world_query_impl( _archetype: &'__w #path::archetype::Archetype, _table: &'__w #path::storage::Table ) { - #(<#field_types>::set_archetype(&mut _fetch.#named_field_idents, &_state.#named_field_idents, _archetype, _table);)* + #(<#field_types>::set_archetype(&mut _fetch.#field_idents, &_state.#field_idents, _archetype, _table);)* } /// SAFETY: we call `set_table` for each member that implements `Fetch` @@ -149,27 +148,27 @@ pub(crate) fn world_query_impl( _state: &Self::State, _table: &'__w #path::storage::Table ) { - #(<#field_types>::set_table(&mut _fetch.#named_field_idents, &_state.#named_field_idents, _table);)* + #(<#field_types>::set_table(&mut _fetch.#field_idents, &_state.#field_idents, _table);)* } fn update_component_access(state: &Self::State, _access: &mut #path::query::FilteredAccess<#path::component::ComponentId>) { - #( <#field_types>::update_component_access(&state.#named_field_idents, _access); )* + #( <#field_types>::update_component_access(&state.#field_idents, _access); )* } fn init_state(world: &mut #path::world::World) -> #state_struct_name #user_ty_generics { #state_struct_name { - #(#named_field_idents: <#field_types>::init_state(world),)* + #(#field_idents: <#field_types>::init_state(world),)* } } fn get_state(components: &#path::component::Components) -> Option<#state_struct_name #user_ty_generics> { Some(#state_struct_name { - #(#named_field_idents: <#field_types>::get_state(components)?,)* + #(#field_idents: <#field_types>::get_state(components)?,)* }) } fn matches_component_set(state: &Self::State, _set_contains_id: &impl Fn(#path::component::ComponentId) -> bool) -> bool { - true #(&& <#field_types>::matches_component_set(&state.#named_field_idents, _set_contains_id))* + true #(&& <#field_types>::matches_component_set(&state.#field_idents, _set_contains_id))* } } } diff --git a/crates/bevy_macro_utils/src/lib.rs b/crates/bevy_macro_utils/src/lib.rs index aa386101f1983..fa75ac6d7ec6f 100644 --- a/crates/bevy_macro_utils/src/lib.rs +++ b/crates/bevy_macro_utils/src/lib.rs @@ -14,11 +14,13 @@ mod attrs; mod bevy_manifest; pub mod fq_std; mod label; +mod member; mod shape; mod symbol; pub use attrs::*; pub use bevy_manifest::*; pub use label::*; +pub use member::*; pub use shape::*; pub use symbol::*; diff --git a/crates/bevy_macro_utils/src/member.rs b/crates/bevy_macro_utils/src/member.rs new file mode 100644 index 0000000000000..5b16309106249 --- /dev/null +++ b/crates/bevy_macro_utils/src/member.rs @@ -0,0 +1,46 @@ +use syn::{Ident, Member}; + +/// Converts an optional identifier or index into a [`syn::Member`] variant. +/// +/// This is useful for when you want to access a field inside a `quote!` block regardless of whether it is an identifier or an index. +/// There is also [`syn::Fields::members`], but this method doesn't work when you're dealing with single / filtered fields. +/// +/// 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. +/// +/// # Example +/// ```rust +/// use syn::{Ident, parse_str, DeriveInput, Data, DataStruct}; +/// use quote::quote; +/// use bevy_macro_utils::as_member; +/// +/// let ast: DeriveInput = syn::parse_str( +/// r#" +/// struct Mystruct { +/// field: usize, +/// #[my_derive] +/// other_field: usize +/// } +/// "#, +/// ) +/// .unwrap(); +/// +/// let Data::Struct(DataStruct { fields, .. }) = &ast.data else { return }; +/// +/// let field_members = fields +/// .iter() +/// .enumerate() +/// .filter(|(_, field)| field.attrs.iter().any(|attr| attr.path().is_ident("my_derive"))) +/// .map(|(i, field)| { as_member(field.ident.as_ref(), i) }); +/// +/// // it won't matter now if it's a named field or a unnamed field. e.g self.field or self.0 +/// quote!( +/// #(self.#field_members.do_something();)* +/// ); +/// +/// ``` +/// +pub fn as_member(ident: Option<&Ident>, index: usize) -> Member { + ident.map_or_else(|| Member::from(index), |ident| Member::Named(ident.clone())) +} diff --git a/crates/bevy_reflect/derive/src/enum_utility.rs b/crates/bevy_reflect/derive/src/enum_utility.rs index 5571b861a6a81..97d8afbd65348 100644 --- a/crates/bevy_reflect/derive/src/enum_utility.rs +++ b/crates/bevy_reflect/derive/src/enum_utility.rs @@ -1,9 +1,12 @@ use crate::field_attributes::CloneBehavior; use crate::{ derive_data::ReflectEnum, derive_data::StructField, field_attributes::DefaultBehavior, - ident::ident_or_index, }; -use bevy_macro_utils::fq_std::{FQClone, FQDefault, FQOption, FQResult}; +use bevy_macro_utils::fq_std::{FQClone, FQResult}; +use bevy_macro_utils::{ + as_member, + fq_std::{FQDefault, FQOption}, +}; use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote, ToTokens}; @@ -158,7 +161,7 @@ pub(crate) trait VariantBuilder: Sized { let mut field_constructors = Vec::with_capacity(fields.len()); for field in fields { - let member = ident_or_index(field.data.ident.as_ref(), field.declaration_index); + let member = as_member(field.data.ident.as_ref(), field.declaration_index); let alias = format_ident!("_{}", member); let variant_field = VariantField { diff --git a/crates/bevy_reflect/derive/src/field_attributes.rs b/crates/bevy_reflect/derive/src/field_attributes.rs index 06d64791c414e..25e2370905382 100644 --- a/crates/bevy_reflect/derive/src/field_attributes.rs +++ b/crates/bevy_reflect/derive/src/field_attributes.rs @@ -9,7 +9,7 @@ use crate::{ REFLECT_ATTRIBUTE_NAME, }; use quote::ToTokens; -use syn::{parse::ParseStream, Attribute, LitStr, Meta, Token, Type}; +use syn::{parse::ParseStream, Attribute, LitStr, Token, Type}; mod kw { syn::custom_keyword!(ignore); @@ -107,10 +107,9 @@ impl FieldAttributes { return None; } - let Meta::List(meta) = &attr.meta else { + let Ok(meta) = &attr.meta.require_list() else { return Some(syn::Error::new_spanned(attr, "expected meta list")); }; - // Parse all attributes inside the list, collecting any errors meta.parse_args_with(terminated_parser(Token![,], |stream| { args.parse_field_attribute(stream) diff --git a/crates/bevy_reflect/derive/src/from_reflect.rs b/crates/bevy_reflect/derive/src/from_reflect.rs index d994cbd2f79a2..0156efb9988f0 100644 --- a/crates/bevy_reflect/derive/src/from_reflect.rs +++ b/crates/bevy_reflect/derive/src/from_reflect.rs @@ -3,11 +3,13 @@ use crate::{ 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 bevy_macro_utils::{ + as_member, + fq_std::{FQClone, FQDefault, FQOption}, +}; use proc_macro2::Span; use quote::{quote, ToTokens}; use syn::{Field, Ident, Lit, LitInt, LitStr, Member}; @@ -200,7 +202,7 @@ fn get_ignored_fields(reflect_struct: &ReflectStruct) -> MemberValuePair { reflect_struct .ignored_fields() .map(|field| { - let member = ident_or_index(field.data.ident.as_ref(), field.declaration_index); + let member = as_member(field.data.ident.as_ref(), field.declaration_index); let value = match &field.attrs.default { DefaultBehavior::Func(path) => quote! {#path()}, @@ -229,7 +231,7 @@ fn get_active_fields( reflect_struct .active_fields() .map(|field| { - let member = ident_or_index(field.data.ident.as_ref(), field.declaration_index); + let member = as_member(field.data.ident.as_ref(), field.declaration_index); let accessor = get_field_accessor( field.data, field.reflection_index.expect("field should be active"), diff --git a/crates/bevy_reflect/derive/src/ident.rs b/crates/bevy_reflect/derive/src/ident.rs index f14c7ff98f68e..931b3e989a8c6 100644 --- a/crates/bevy_reflect/derive/src/ident.rs +++ b/crates/bevy_reflect/derive/src/ident.rs @@ -1,5 +1,4 @@ use proc_macro2::{Ident, Span}; -use syn::Member; /// Returns the "reflected" ident for a given string. /// @@ -19,27 +18,3 @@ pub(crate) fn get_reflect_ident(name: &str) -> Ident { let reflected = format!("Reflect{name}"); Ident::new(&reflected, Span::call_site()) } - -/// 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. -/// -/// ``` -/// 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 contexts where you are declaring either -/// a tuple struct or a struct with named fields. If you don't have a field name, -/// it means that you must access the field through an index. -pub(crate) fn ident_or_index(ident: Option<&Ident>, index: usize) -> Member { - ident.map_or_else( - || Member::Unnamed(index.into()), - |ident| Member::Named(ident.clone()), - ) -} diff --git a/crates/bevy_reflect/derive/src/remote.rs b/crates/bevy_reflect/derive/src/remote.rs index 13cbe681ed4c7..e258b8a43213e 100644 --- a/crates/bevy_reflect/derive/src/remote.rs +++ b/crates/bevy_reflect/derive/src/remote.rs @@ -1,12 +1,10 @@ use crate::{ derive_data::{ReflectImplSource, ReflectProvenance, ReflectTraitToImpl}, - from_reflect, - ident::ident_or_index, - impls, + from_reflect, impls, impls::impl_assertions, ReflectDerive, REFLECT_ATTRIBUTE_NAME, }; -use bevy_macro_utils::fq_std::FQOption; +use bevy_macro_utils::{as_member, fq_std::FQOption}; use proc_macro::TokenStream; use proc_macro2::{Ident, Span}; use quote::{format_ident, quote, quote_spanned}; @@ -262,7 +260,7 @@ pub(crate) fn generate_remote_assertions( .remote .as_ref() .map(|remote_ty| RemoteAssertionData { - ident: ident_or_index(field.data.ident.as_ref(), field.declaration_index), + ident: as_member(field.data.ident.as_ref(), field.declaration_index), variant: None, ty: &field.data.ty, generics: data.meta().type_path().generics(), @@ -276,7 +274,7 @@ pub(crate) fn generate_remote_assertions( .remote .as_ref() .map(|remote_ty| RemoteAssertionData { - ident: ident_or_index(field.data.ident.as_ref(), field.declaration_index), + ident: as_member(field.data.ident.as_ref(), field.declaration_index), variant: Some(&variant.data.ident), ty: &field.data.ty, generics: data.meta().type_path().generics(), @@ -365,8 +363,7 @@ fn generate_remote_definition_assertions(derive_data: &ReflectDerive) -> proc_ma let mut output = proc_macro2::TokenStream::new(); for field in data.fields() { - let field_member = - ident_or_index(field.data.ident.as_ref(), field.declaration_index); + let field_member = as_member(field.data.ident.as_ref(), field.declaration_index); let field_ty = &field.data.ty; let span = create_assertion_span(field_ty.span()); @@ -389,7 +386,7 @@ fn generate_remote_definition_assertions(derive_data: &ReflectDerive) -> proc_ma for field in variant.fields() { let field_member = - ident_or_index(field.data.ident.as_ref(), field.declaration_index); + as_member(field.data.ident.as_ref(), field.declaration_index); let field_ident = format_ident!("field_{}", field_member); let field_ty = &field.data.ty; let span = create_assertion_span(field_ty.span());