Skip to content

Commit 218c30c

Browse files
committed
Make #[system_param(ignore)] and #[world_query(ignore)] unnecessary (bevyengine#8030)
When using `PhantomData` fields with the `#[derive(SystemParam)]` or `#[derive(WorldQuery)]` macros, the user is required to add the `#[system_param(ignore)]` attribute so that the macro knows to treat that field specially. This is undesirable, since it makes the macro more fragile and less consistent. Implement `SystemParam` and `WorldQuery` for `PhantomData`. This makes the `ignore` attributes unnecessary. Some internal changes make the derive macro compatible with types that have invariant lifetimes, which fixes bevyengine#8192. From what I can tell, this fix requires `PhantomData` to implement `SystemParam` in order to ensure that all of a type's generic parameters are always constrained. --- + Implemented `SystemParam` and `WorldQuery` for `PhantomData<T>`. + Fixed a miscompilation caused when invariant lifetimes were used with the `SystemParam` macro.
1 parent 02b0ea2 commit 218c30c

File tree

4 files changed

+177
-29
lines changed

4 files changed

+177
-29
lines changed

crates/bevy_ecs/macros/src/fetch.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream {
378378
#[automatically_derived]
379379
#visibility struct #state_struct_name #user_impl_generics #user_where_clauses {
380380
#(#field_idents: <#field_types as #path::query::WorldQuery>::State,)*
381-
#(#ignored_field_idents: #ignored_field_types,)*
381+
#(#ignored_field_idents: ::std::marker::PhantomData<fn() -> #ignored_field_types>,)*
382382
}
383383

384384
/// SAFETY: we assert fields are readonly below
@@ -419,7 +419,7 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream {
419419
}
420420

421421
struct WorldQueryFieldInfo {
422-
/// Has `#[fetch(ignore)]` or `#[filter_fetch(ignore)]` attribute.
422+
/// Has the `#[world_query(ignore)]` attribute.
423423
is_ignored: bool,
424424
/// All field attributes except for `world_query` ones.
425425
attrs: Vec<Attribute>,

crates/bevy_ecs/macros/src/lib.rs

Lines changed: 43 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use proc_macro2::Span;
1212
use quote::{format_ident, quote};
1313
use syn::{
1414
parse::ParseStream, parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned,
15-
ConstParam, DeriveInput, Field, GenericParam, Ident, Index, Meta, MetaList, NestedMeta, Token,
15+
ConstParam, DeriveInput, GenericParam, Ident, Index, Meta, MetaList, NestedMeta, Token,
1616
TypeParam,
1717
};
1818

@@ -259,7 +259,7 @@ static SYSTEM_PARAM_ATTRIBUTE_NAME: &str = "system_param";
259259
#[proc_macro_derive(SystemParam, attributes(system_param))]
260260
pub fn derive_system_param(input: TokenStream) -> TokenStream {
261261
let ast = parse_macro_input!(input as DeriveInput);
262-
let syn::Data::Struct(syn::DataStruct { fields: field_definitions, ..}) = ast.data else {
262+
let syn::Data::Struct(syn::DataStruct { fields: field_definitions, .. }) = ast.data else {
263263
return syn::Error::new(ast.span(), "Invalid `SystemParam` type: expected a `struct`")
264264
.into_compile_error()
265265
.into();
@@ -290,7 +290,8 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
290290
}),
291291
)
292292
})
293-
.collect::<Vec<(&Field, SystemParamFieldAttributes)>>();
293+
.collect::<Vec<_>>();
294+
294295
let mut field_locals = Vec::new();
295296
let mut fields = Vec::new();
296297
let mut field_types = Vec::new();
@@ -341,11 +342,7 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
341342
.filter(|g| !matches!(g, GenericParam::Lifetime(_)))
342343
.collect();
343344

344-
let mut shadowed_lifetimes: Vec<_> = generics.lifetimes().map(|x| x.lifetime.clone()).collect();
345-
for lifetime in &mut shadowed_lifetimes {
346-
let shadowed_ident = format_ident!("_{}", lifetime.ident);
347-
lifetime.ident = shadowed_ident;
348-
}
345+
let shadowed_lifetimes: Vec<_> = generics.lifetimes().map(|_| quote!('_)).collect();
349346

350347
let mut punctuated_generics = Punctuated::<_, Token![,]>::new();
351348
punctuated_generics.extend(lifetimeless_generics.iter().map(|g| match g {
@@ -367,9 +364,27 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
367364
_ => unreachable!(),
368365
}));
369366

367+
let punctuated_generics_no_bounds: Punctuated<_, Token![,]> = lifetimeless_generics
368+
.iter()
369+
.map(|&g| match g.clone() {
370+
GenericParam::Type(mut g) => {
371+
g.bounds.clear();
372+
GenericParam::Type(g)
373+
}
374+
g => g,
375+
})
376+
.collect();
377+
370378
let mut tuple_types: Vec<_> = field_types.iter().map(|x| quote! { #x }).collect();
371379
let mut tuple_patterns: Vec<_> = field_locals.iter().map(|x| quote! { #x }).collect();
372380

381+
tuple_types.extend(
382+
ignored_field_types
383+
.iter()
384+
.map(|ty| parse_quote!(::std::marker::PhantomData::<#ty>)),
385+
);
386+
tuple_patterns.extend(ignored_field_types.iter().map(|_| parse_quote!(_)));
387+
373388
// If the number of fields exceeds the 16-parameter limit,
374389
// fold the fields into tuples of tuples until we are below the limit.
375390
const LIMIT: usize = 16;
@@ -380,6 +395,7 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
380395
let end = Vec::from_iter(tuple_patterns.drain(..LIMIT));
381396
tuple_patterns.push(parse_quote!( (#(#end,)*) ));
382397
}
398+
383399
// Create a where clause for the `ReadOnlySystemParam` impl.
384400
// Ensure that each field implements `ReadOnlySystemParam`.
385401
let mut read_only_generics = generics.clone();
@@ -390,6 +406,8 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
390406
.push(syn::parse_quote!(#field_type: #path::system::ReadOnlySystemParam));
391407
}
392408

409+
let fields_alias = format_ident!("__StructFieldsAlias");
410+
393411
let struct_name = &ast.ident;
394412
let state_struct_visibility = &ast.vis;
395413

@@ -398,41 +416,41 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
398416
// The struct can still be accessed via SystemParam::State, e.g. EventReaderState can be accessed via
399417
// <EventReader<'static, 'static, T> as SystemParam>::State
400418
const _: () = {
419+
// Allows rebinding the lifetimes of each field type.
420+
type #fields_alias <'w, 's, #punctuated_generics_no_bounds> = (#(#tuple_types,)*);
421+
401422
#[doc(hidden)]
402-
#state_struct_visibility struct FetchState <'w, 's, #(#lifetimeless_generics,)*>
423+
#state_struct_visibility struct FetchState <#(#lifetimeless_generics,)*>
403424
#where_clause {
404-
state: (#(<#tuple_types as #path::system::SystemParam>::State,)*),
405-
marker: std::marker::PhantomData<(
406-
<#path::prelude::Query<'w, 's, ()> as #path::system::SystemParam>::State,
407-
#(fn() -> #ignored_field_types,)*
408-
)>,
425+
state: <#fields_alias::<'static, 'static, #punctuated_generic_idents> as #path::system::SystemParam>::State,
409426
}
410427

411-
unsafe impl<'w, 's, #punctuated_generics> #path::system::SystemParam for #struct_name #ty_generics #where_clause {
412-
type State = FetchState<'static, 'static, #punctuated_generic_idents>;
413-
type Item<'_w, '_s> = #struct_name <#(#shadowed_lifetimes,)* #punctuated_generic_idents>;
428+
unsafe impl<#punctuated_generics> #path::system::SystemParam for
429+
#struct_name <#(#shadowed_lifetimes,)* #punctuated_generic_idents> #where_clause
430+
{
431+
type State = FetchState<#punctuated_generic_idents>;
432+
type Item<'w, 's> = #struct_name #ty_generics;
414433

415434
fn init_state(world: &mut #path::world::World, system_meta: &mut #path::system::SystemMeta) -> Self::State {
416435
FetchState {
417-
state: <(#(#tuple_types,)*) as #path::system::SystemParam>::init_state(world, system_meta),
418-
marker: std::marker::PhantomData,
436+
state: <#fields_alias::<'_, '_, #punctuated_generic_idents> as #path::system::SystemParam>::init_state(world, system_meta),
419437
}
420438
}
421439

422440
fn new_archetype(state: &mut Self::State, archetype: &#path::archetype::Archetype, system_meta: &mut #path::system::SystemMeta) {
423-
<(#(#tuple_types,)*) as #path::system::SystemParam>::new_archetype(&mut state.state, archetype, system_meta)
441+
<#fields_alias::<'_, '_, #punctuated_generic_idents> as #path::system::SystemParam>::new_archetype(&mut state.state, archetype, system_meta)
424442
}
425443

426444
fn apply(state: &mut Self::State, system_meta: &#path::system::SystemMeta, world: &mut #path::world::World) {
427-
<(#(#tuple_types,)*) as #path::system::SystemParam>::apply(&mut state.state, system_meta, world);
445+
<#fields_alias::<'_, '_, #punctuated_generic_idents> as #path::system::SystemParam>::apply(&mut state.state, system_meta, world);
428446
}
429447

430-
unsafe fn get_param<'w2, 's2>(
431-
state: &'s2 mut Self::State,
448+
unsafe fn get_param<'w, 's>(
449+
state: &'s mut Self::State,
432450
system_meta: &#path::system::SystemMeta,
433-
world: &'w2 #path::world::World,
451+
world: &'w #path::world::World,
434452
change_tick: u32,
435-
) -> Self::Item<'w2, 's2> {
453+
) -> Self::Item<'w, 's> {
436454
let (#(#tuple_patterns,)*) = <
437455
(#(#tuple_types,)*) as #path::system::SystemParam
438456
>::get_param(&mut state.state, system_meta, world, change_tick);

crates/bevy_ecs/src/query/fetch.rs

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,24 @@ use std::{cell::UnsafeCell, marker::PhantomData};
281281
/// # bevy_ecs::system::assert_is_system(my_system);
282282
/// ```
283283
///
284+
/// # Generic Queries
285+
///
286+
/// When writing generic code, it is often necessary to use [`PhantomData`]
287+
/// to constrain type parameters. Since `WorldQuery` is implemented for all
288+
/// `PhantomData<T>` types, this pattern can be used with this macro.
289+
///
290+
/// ```
291+
/// # use bevy_ecs::{prelude::*, query::WorldQuery};
292+
/// # use std::marker::PhantomData;
293+
/// #[derive(WorldQuery)]
294+
/// pub struct GenericQuery<T> {
295+
/// id: Entity,
296+
/// marker: PhantomData<T>,
297+
/// }
298+
/// # fn my_system(q: Query<GenericQuery<()>>) {}
299+
/// # bevy_ecs::system::assert_is_system(my_system);
300+
/// ```
301+
///
284302
/// # Safety
285303
///
286304
/// Component access of `Self::ReadOnly` must be a subset of `Self`
@@ -1563,7 +1581,6 @@ macro_rules! impl_anytuple_fetch {
15631581

15641582
/// SAFETY: each item in the tuple is read only
15651583
unsafe impl<$($name: ReadOnlyWorldQuery),*> ReadOnlyWorldQuery for AnyOf<($($name,)*)> {}
1566-
15671584
};
15681585
}
15691586

@@ -1643,6 +1660,71 @@ unsafe impl<Q: WorldQuery> WorldQuery for NopWorldQuery<Q> {
16431660
/// SAFETY: `NopFetch` never accesses any data
16441661
unsafe impl<Q: WorldQuery> ReadOnlyWorldQuery for NopWorldQuery<Q> {}
16451662

1663+
/// SAFETY: `PhantomData` never accesses any world data.
1664+
unsafe impl<T: ?Sized> WorldQuery for PhantomData<T> {
1665+
type Item<'a> = ();
1666+
type Fetch<'a> = ();
1667+
type ReadOnly = Self;
1668+
type State = ();
1669+
1670+
fn shrink<'wlong: 'wshort, 'wshort>(_item: Self::Item<'wlong>) -> Self::Item<'wshort> {}
1671+
1672+
unsafe fn init_fetch<'w>(
1673+
_world: &'w World,
1674+
_state: &Self::State,
1675+
_last_change_tick: u32,
1676+
_change_tick: u32,
1677+
) -> Self::Fetch<'w> {
1678+
}
1679+
1680+
unsafe fn clone_fetch<'w>(_fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> {}
1681+
1682+
// `PhantomData` does not match any components, so all components it matches
1683+
// are stored in a Table (vacuous truth).
1684+
const IS_DENSE: bool = true;
1685+
// `PhantomData` matches every entity in each archetype.
1686+
const IS_ARCHETYPAL: bool = true;
1687+
1688+
unsafe fn set_archetype<'w>(
1689+
_fetch: &mut Self::Fetch<'w>,
1690+
_state: &Self::State,
1691+
_archetype: &'w Archetype,
1692+
_table: &'w Table,
1693+
) {
1694+
}
1695+
1696+
unsafe fn set_table<'w>(_fetch: &mut Self::Fetch<'w>, _state: &Self::State, _table: &'w Table) {
1697+
}
1698+
1699+
unsafe fn fetch<'w>(
1700+
_fetch: &mut Self::Fetch<'w>,
1701+
_entity: Entity,
1702+
_table_row: TableRow,
1703+
) -> Self::Item<'w> {
1704+
}
1705+
1706+
fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess<ComponentId>) {}
1707+
1708+
fn update_archetype_component_access(
1709+
_state: &Self::State,
1710+
_archetype: &Archetype,
1711+
_access: &mut Access<ArchetypeComponentId>,
1712+
) {
1713+
}
1714+
1715+
fn init_state(_world: &mut World) -> Self::State {}
1716+
1717+
fn matches_component_set(
1718+
_state: &Self::State,
1719+
_set_contains_id: &impl Fn(ComponentId) -> bool,
1720+
) -> bool {
1721+
true
1722+
}
1723+
}
1724+
1725+
/// SAFETY: `PhantomData` never accesses any world data.
1726+
unsafe impl<T: ?Sized> ReadOnlyWorldQuery for PhantomData<T> {}
1727+
16461728
#[cfg(test)]
16471729
mod tests {
16481730
use super::*;
@@ -1651,6 +1733,22 @@ mod tests {
16511733
#[derive(Component)]
16521734
pub struct A;
16531735

1736+
// Compile test for https://github.com/bevyengine/bevy/pull/8030.
1737+
#[test]
1738+
fn world_query_phantom_data() {
1739+
#[derive(WorldQuery)]
1740+
pub struct IgnoredQuery<Marker> {
1741+
id: Entity,
1742+
#[world_query(ignore)]
1743+
_marker: PhantomData<Marker>,
1744+
_marker2: PhantomData<Marker>,
1745+
}
1746+
1747+
fn ignored_system(_: Query<IgnoredQuery<()>>) {}
1748+
1749+
crate::system::assert_is_system(ignored_system);
1750+
}
1751+
16541752
// Ensures that each field of a `WorldQuery` struct's read-only variant
16551753
// has the same visibility as its corresponding mutable field.
16561754
#[test]

crates/bevy_ecs/src/system/system_param.rs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use bevy_utils::{all_tuples, synccell::SyncCell};
1919
use std::{
2020
borrow::Cow,
2121
fmt::Debug,
22+
marker::PhantomData,
2223
ops::{Deref, DerefMut},
2324
};
2425

@@ -65,6 +66,11 @@ use std::{
6566
/// # bevy_ecs::system::assert_is_system(my_system::<()>);
6667
/// ```
6768
///
69+
/// ## `PhantomData`
70+
///
71+
/// [`PhantomData`] is a special type of `SystemParam` that does nothing.
72+
/// This is useful for constraining generic types or lifetimes.
73+
///
6874
/// # Generic `SystemParam`s
6975
///
7076
/// When using the derive macro, you may see an error in the form of:
@@ -1476,7 +1482,6 @@ pub mod lifetimeless {
14761482
/// #[derive(SystemParam)]
14771483
/// struct GenericParam<'w, 's, T: SystemParam> {
14781484
/// field: T,
1479-
/// #[system_param(ignore)]
14801485
/// // Use the lifetimes in this type, or they will be unbound.
14811486
/// phantom: core::marker::PhantomData<&'w &'s ()>
14821487
/// }
@@ -1542,6 +1547,26 @@ unsafe impl<P: SystemParam + 'static> SystemParam for StaticSystemParam<'_, '_,
15421547
}
15431548
}
15441549

1550+
// SAFETY: No world access.
1551+
unsafe impl<T: ?Sized> SystemParam for PhantomData<T> {
1552+
type State = ();
1553+
type Item<'world, 'state> = Self;
1554+
1555+
fn init_state(_world: &mut World, _system_meta: &mut SystemMeta) -> Self::State {}
1556+
1557+
unsafe fn get_param<'world, 'state>(
1558+
_state: &'state mut Self::State,
1559+
_system_meta: &SystemMeta,
1560+
_world: &'world World,
1561+
_change_tick: u32,
1562+
) -> Self::Item<'world, 'state> {
1563+
PhantomData
1564+
}
1565+
}
1566+
1567+
// SAFETY: No world access.
1568+
unsafe impl<T: ?Sized> ReadOnlySystemParam for PhantomData<T> {}
1569+
15451570
#[cfg(test)]
15461571
mod tests {
15471572
use super::*;
@@ -1616,6 +1641,7 @@ mod tests {
16161641
_foo: Res<'w, T>,
16171642
#[system_param(ignore)]
16181643
marker: PhantomData<&'w Marker>,
1644+
marker2: PhantomData<&'w Marker>,
16191645
}
16201646

16211647
// Compile tests for https://github.com/bevyengine/bevy/pull/6957.
@@ -1644,4 +1670,10 @@ mod tests {
16441670
{
16451671
_q: Query<'w, 's, Q, ()>,
16461672
}
1673+
1674+
// Regression test for https://github.com/bevyengine/bevy/issues/8192.
1675+
#[derive(SystemParam)]
1676+
pub struct InvariantParam<'w, 's> {
1677+
_set: ParamSet<'w, 's, (Query<'w, 's, ()>,)>,
1678+
}
16471679
}

0 commit comments

Comments
 (0)