Skip to content

Commit b423e6e

Browse files
authored
Extend the WorldQuery macro to tuple structs (#8119)
# Objective The `#[derive(WorldQuery)]` macro currently only supports structs with named fields. Same motivation as #6957. Remove sharp edges from the derive macro, make it just work more often. ## Solution Support tuple structs. --- ## Changelog + Added support for tuple structs to the `#[derive(WorldQuery)]` macro.
1 parent 2aaaed7 commit b423e6e

File tree

2 files changed

+99
-37
lines changed

2 files changed

+99
-37
lines changed

crates/bevy_ecs/macros/src/fetch.rs

Lines changed: 71 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
use bevy_macro_utils::ensure_no_collision;
22
use proc_macro::TokenStream;
33
use proc_macro2::{Ident, Span};
4-
use quote::{quote, ToTokens};
4+
use quote::{format_ident, quote, ToTokens};
55
use syn::{
66
parse::{Parse, ParseStream},
77
parse_macro_input, parse_quote,
88
punctuated::Punctuated,
9-
Attribute, Data, DataStruct, DeriveInput, Field, Fields,
9+
Attribute, Data, DataStruct, DeriveInput, Field, Index,
1010
};
1111

1212
use crate::bevy_ecs_path;
@@ -116,34 +116,49 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream {
116116
fetch_struct_name.clone()
117117
};
118118

119+
let marker_name =
120+
ensure_no_collision(format_ident!("_world_query_derive_marker"), tokens.clone());
121+
119122
// Generate a name for the state struct that doesn't conflict
120123
// with the struct definition.
121124
let state_struct_name = Ident::new(&format!("{struct_name}State"), Span::call_site());
122125
let state_struct_name = ensure_no_collision(state_struct_name, tokens);
123126

124-
let fields = match &ast.data {
125-
Data::Struct(DataStruct {
126-
fields: Fields::Named(fields),
127-
..
128-
}) => &fields.named,
129-
_ => panic!("Expected a struct with named fields"),
127+
let Data::Struct(DataStruct { fields, .. }) = &ast.data else {
128+
return syn::Error::new(
129+
Span::call_site(),
130+
"#[derive(WorldQuery)]` only supports structs",
131+
)
132+
.into_compile_error()
133+
.into()
130134
};
131135

132136
let mut field_attrs = Vec::new();
133137
let mut field_visibilities = Vec::new();
134138
let mut field_idents = Vec::new();
139+
let mut named_field_idents = Vec::new();
135140
let mut field_types = Vec::new();
136141
let mut read_only_field_types = Vec::new();
137-
138-
for field in fields {
142+
for (i, field) in fields.iter().enumerate() {
139143
let attrs = match read_world_query_field_info(field) {
140144
Ok(WorldQueryFieldInfo { attrs }) => attrs,
141145
Err(e) => return e.into_compile_error().into(),
142146
};
143147

148+
let named_field_ident = field
149+
.ident
150+
.as_ref()
151+
.cloned()
152+
.unwrap_or_else(|| format_ident!("f{i}"));
153+
let i = Index::from(i);
154+
let field_ident = field
155+
.ident
156+
.as_ref()
157+
.map_or(quote! { #i }, |i| quote! { #i });
158+
field_idents.push(field_ident);
159+
named_field_idents.push(named_field_ident);
144160
field_attrs.push(attrs);
145161
field_visibilities.push(field.vis.clone());
146-
field_idents.push(field.ident.as_ref().unwrap().clone());
147162
let field_ty = field.ty.clone();
148163
field_types.push(quote!(#field_ty));
149164
read_only_field_types.push(quote!(<#field_ty as #path::query::WorldQuery>::ReadOnly));
@@ -176,15 +191,34 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream {
176191
&field_types
177192
};
178193

179-
let item_struct = quote! {
180-
#derive_macro_call
181-
#[doc = "Automatically generated [`WorldQuery`] item type for [`"]
182-
#[doc = stringify!(#struct_name)]
183-
#[doc = "`], returned when iterating over query results."]
184-
#[automatically_derived]
185-
#visibility struct #item_struct_name #user_impl_generics_with_world #user_where_clauses_with_world {
186-
#(#(#field_attrs)* #field_visibilities #field_idents: <#field_types as #path::query::WorldQuery>::Item<'__w>,)*
187-
}
194+
let item_struct = match fields {
195+
syn::Fields::Named(_) => quote! {
196+
#derive_macro_call
197+
#[doc = "Automatically generated [`WorldQuery`] item type for [`"]
198+
#[doc = stringify!(#struct_name)]
199+
#[doc = "`], returned when iterating over query results."]
200+
#[automatically_derived]
201+
#visibility struct #item_struct_name #user_impl_generics_with_world #user_where_clauses_with_world {
202+
#(#(#field_attrs)* #field_visibilities #field_idents: <#field_types as #path::query::WorldQuery>::Item<'__w>,)*
203+
}
204+
},
205+
syn::Fields::Unnamed(_) => quote! {
206+
#derive_macro_call
207+
#[doc = "Automatically generated [`WorldQuery`] item type for [`"]
208+
#[doc = stringify!(#struct_name)]
209+
#[doc = "`], returned when iterating over query results."]
210+
#[automatically_derived]
211+
#visibility struct #item_struct_name #user_impl_generics_with_world #user_where_clauses_with_world(
212+
#( #field_visibilities <#field_types as #path::query::WorldQuery>::Item<'__w>, )*
213+
);
214+
},
215+
syn::Fields::Unit => quote! {
216+
#[doc = "Automatically generated [`WorldQuery`] item type for [`"]
217+
#[doc = stringify!(#struct_name)]
218+
#[doc = "`], returned when iterating over query results."]
219+
#[automatically_derived]
220+
#visibility type #item_struct_name #user_ty_generics_with_world = #struct_name #user_ty_generics;
221+
},
188222
};
189223

190224
let query_impl = quote! {
@@ -194,7 +228,8 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream {
194228
#[doc = "`], used to define the world data accessed by this query."]
195229
#[automatically_derived]
196230
#visibility struct #fetch_struct_name #user_impl_generics_with_world #user_where_clauses_with_world {
197-
#(#field_idents: <#field_types as #path::query::WorldQuery>::Fetch<'__w>,)*
231+
#(#named_field_idents: <#field_types as #path::query::WorldQuery>::Fetch<'__w>,)*
232+
#marker_name: &'__w (),
198233
}
199234

200235
// SAFETY: `update_component_access` and `update_archetype_component_access` are called on every field
@@ -223,14 +258,15 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream {
223258
_this_run: #path::component::Tick,
224259
) -> <Self as #path::query::WorldQuery>::Fetch<'__w> {
225260
#fetch_struct_name {
226-
#(#field_idents:
261+
#(#named_field_idents:
227262
<#field_types>::init_fetch(
228263
_world,
229-
&state.#field_idents,
264+
&state.#named_field_idents,
230265
_last_run,
231266
_this_run,
232267
),
233268
)*
269+
#marker_name: &(),
234270
}
235271
}
236272

@@ -239,8 +275,9 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream {
239275
) -> <Self as #path::query::WorldQuery>::Fetch<'__w> {
240276
#fetch_struct_name {
241277
#(
242-
#field_idents: <#field_types>::clone_fetch(& _fetch. #field_idents),
278+
#named_field_idents: <#field_types>::clone_fetch(& _fetch. #named_field_idents),
243279
)*
280+
#marker_name: &(),
244281
}
245282
}
246283

@@ -256,7 +293,7 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream {
256293
_archetype: &'__w #path::archetype::Archetype,
257294
_table: &'__w #path::storage::Table
258295
) {
259-
#(<#field_types>::set_archetype(&mut _fetch.#field_idents, &_state.#field_idents, _archetype, _table);)*
296+
#(<#field_types>::set_archetype(&mut _fetch.#named_field_idents, &_state.#named_field_idents, _archetype, _table);)*
260297
}
261298

262299
/// SAFETY: we call `set_table` for each member that implements `Fetch`
@@ -266,7 +303,7 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream {
266303
_state: &Self::State,
267304
_table: &'__w #path::storage::Table
268305
) {
269-
#(<#field_types>::set_table(&mut _fetch.#field_idents, &_state.#field_idents, _table);)*
306+
#(<#field_types>::set_table(&mut _fetch.#named_field_idents, &_state.#named_field_idents, _table);)*
270307
}
271308

272309
/// SAFETY: we call `fetch` for each member that implements `Fetch`.
@@ -277,7 +314,7 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream {
277314
_table_row: #path::storage::TableRow,
278315
) -> <Self as #path::query::WorldQuery>::Item<'__w> {
279316
Self::Item {
280-
#(#field_idents: <#field_types>::fetch(&mut _fetch.#field_idents, _entity, _table_row),)*
317+
#(#field_idents: <#field_types>::fetch(&mut _fetch.#named_field_idents, _entity, _table_row),)*
281318
}
282319
}
283320

@@ -288,11 +325,11 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream {
288325
_entity: #path::entity::Entity,
289326
_table_row: #path::storage::TableRow,
290327
) -> bool {
291-
true #(&& <#field_types>::filter_fetch(&mut _fetch.#field_idents, _entity, _table_row))*
328+
true #(&& <#field_types>::filter_fetch(&mut _fetch.#named_field_idents, _entity, _table_row))*
292329
}
293330

294331
fn update_component_access(state: &Self::State, _access: &mut #path::query::FilteredAccess<#path::component::ComponentId>) {
295-
#( <#field_types>::update_component_access(&state.#field_idents, _access); )*
332+
#( <#field_types>::update_component_access(&state.#named_field_idents, _access); )*
296333
}
297334

298335
fn update_archetype_component_access(
@@ -301,18 +338,18 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream {
301338
_access: &mut #path::query::Access<#path::archetype::ArchetypeComponentId>
302339
) {
303340
#(
304-
<#field_types>::update_archetype_component_access(&state.#field_idents, _archetype, _access);
341+
<#field_types>::update_archetype_component_access(&state.#named_field_idents, _archetype, _access);
305342
)*
306343
}
307344

308345
fn init_state(world: &mut #path::world::World) -> #state_struct_name #user_ty_generics {
309346
#state_struct_name {
310-
#(#field_idents: <#field_types>::init_state(world),)*
347+
#(#named_field_idents: <#field_types>::init_state(world),)*
311348
}
312349
}
313350

314351
fn matches_component_set(state: &Self::State, _set_contains_id: &impl Fn(#path::component::ComponentId) -> bool) -> bool {
315-
true #(&& <#field_types>::matches_component_set(&state.#field_idents, _set_contains_id))*
352+
true #(&& <#field_types>::matches_component_set(&state.#named_field_idents, _set_contains_id))*
316353
}
317354
}
318355
};
@@ -328,7 +365,7 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream {
328365
#[doc = "`]."]
329366
#[automatically_derived]
330367
#visibility struct #read_only_struct_name #user_impl_generics #user_where_clauses {
331-
#( #field_visibilities #field_idents: #read_only_field_types, )*
368+
#( #field_visibilities #named_field_idents: #read_only_field_types, )*
332369
}
333370

334371
#readonly_state
@@ -374,7 +411,7 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream {
374411
#[doc = "`], used for caching."]
375412
#[automatically_derived]
376413
#visibility struct #state_struct_name #user_impl_generics #user_where_clauses {
377-
#(#field_idents: <#field_types as #path::query::WorldQuery>::State,)*
414+
#(#named_field_idents: <#field_types as #path::query::WorldQuery>::State,)*
378415
}
379416

380417
#mutable_impl

crates/bevy_ecs/src/query/fetch.rs

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,7 @@ use std::{cell::UnsafeCell, marker::PhantomData};
5555
/// - Methods can be implemented for the query items.
5656
/// - There is no hardcoded limit on the number of elements.
5757
///
58-
/// This trait can only be derived if each field also implements `WorldQuery`.
59-
/// The derive macro only supports regular structs (structs with named fields).
58+
/// This trait can only be derived for structs, if each field also implements `WorldQuery`.
6059
///
6160
/// ```
6261
/// # use bevy_ecs::prelude::*;
@@ -1468,11 +1467,37 @@ unsafe impl<T: ?Sized> ReadOnlyWorldQuery for PhantomData<T> {}
14681467
#[cfg(test)]
14691468
mod tests {
14701469
use super::*;
1471-
use crate::{self as bevy_ecs, system::Query};
1470+
use crate::{
1471+
self as bevy_ecs,
1472+
system::{assert_is_system, Query},
1473+
};
14721474

14731475
#[derive(Component)]
14741476
pub struct A;
14751477

1478+
#[derive(Component)]
1479+
pub struct B;
1480+
1481+
// Tests that each variant of struct can be used as a `WorldQuery`.
1482+
#[test]
1483+
fn world_query_struct_variants() {
1484+
#[derive(WorldQuery)]
1485+
pub struct NamedQuery {
1486+
id: Entity,
1487+
a: &'static A,
1488+
}
1489+
1490+
#[derive(WorldQuery)]
1491+
pub struct TupleQuery(&'static A, &'static B);
1492+
1493+
#[derive(WorldQuery)]
1494+
pub struct UnitQuery;
1495+
1496+
fn my_system(_: Query<(NamedQuery, TupleQuery, UnitQuery)>) {}
1497+
1498+
assert_is_system(my_system);
1499+
}
1500+
14761501
// Compile test for https://github.com/bevyengine/bevy/pull/8030.
14771502
#[test]
14781503
fn world_query_phantom_data() {

0 commit comments

Comments
 (0)