Skip to content

Commit d9113cc

Browse files
authored
Make #[system_param(ignore)] and #[world_query(ignore)] unnecessary (#8030)
# Objective 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. ## Solution 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 #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. --- ## Changelog + Implemented `SystemParam` and `WorldQuery` for `PhantomData<T>`. + Fixed a miscompilation caused when invariant lifetimes were used with the `SystemParam` macro.
1 parent f219a08 commit d9113cc

File tree

4 files changed

+178
-29
lines changed

4 files changed

+178
-29
lines changed

crates/bevy_ecs/macros/src/fetch.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,7 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream {
396396
#[automatically_derived]
397397
#visibility struct #state_struct_name #user_impl_generics #user_where_clauses {
398398
#(#field_idents: <#field_types as #path::query::WorldQuery>::State,)*
399-
#(#ignored_field_idents: #ignored_field_types,)*
399+
#(#ignored_field_idents: ::std::marker::PhantomData<fn() -> #ignored_field_types>,)*
400400
}
401401

402402
#mutable_impl
@@ -437,7 +437,7 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream {
437437
}
438438

439439
struct WorldQueryFieldInfo {
440-
/// Has `#[fetch(ignore)]` or `#[filter_fetch(ignore)]` attribute.
440+
/// Has the `#[world_query(ignore)]` attribute.
441441
is_ignored: bool,
442442
/// All field attributes except for `world_query` ones.
443443
attrs: Vec<Attribute>,

crates/bevy_ecs/macros/src/lib.rs

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

@@ -264,7 +264,7 @@ static SYSTEM_PARAM_ATTRIBUTE_NAME: &str = "system_param";
264264
pub fn derive_system_param(input: TokenStream) -> TokenStream {
265265
let token_stream = input.clone();
266266
let ast = parse_macro_input!(input as DeriveInput);
267-
let syn::Data::Struct(syn::DataStruct { fields: field_definitions, ..}) = ast.data else {
267+
let syn::Data::Struct(syn::DataStruct { fields: field_definitions, .. }) = ast.data else {
268268
return syn::Error::new(ast.span(), "Invalid `SystemParam` type: expected a `struct`")
269269
.into_compile_error()
270270
.into();
@@ -295,7 +295,8 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
295295
}),
296296
)
297297
})
298-
.collect::<Vec<(&Field, SystemParamFieldAttributes)>>();
298+
.collect::<Vec<_>>();
299+
299300
let mut field_locals = Vec::new();
300301
let mut fields = Vec::new();
301302
let mut field_types = Vec::new();
@@ -346,11 +347,7 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
346347
.filter(|g| !matches!(g, GenericParam::Lifetime(_)))
347348
.collect();
348349

349-
let mut shadowed_lifetimes: Vec<_> = generics.lifetimes().map(|x| x.lifetime.clone()).collect();
350-
for lifetime in &mut shadowed_lifetimes {
351-
let shadowed_ident = format_ident!("_{}", lifetime.ident);
352-
lifetime.ident = shadowed_ident;
353-
}
350+
let shadowed_lifetimes: Vec<_> = generics.lifetimes().map(|_| quote!('_)).collect();
354351

355352
let mut punctuated_generics = Punctuated::<_, Token![,]>::new();
356353
punctuated_generics.extend(lifetimeless_generics.iter().map(|g| match g {
@@ -372,9 +369,27 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
372369
_ => unreachable!(),
373370
}));
374371

372+
let punctuated_generics_no_bounds: Punctuated<_, Token![,]> = lifetimeless_generics
373+
.iter()
374+
.map(|&g| match g.clone() {
375+
GenericParam::Type(mut g) => {
376+
g.bounds.clear();
377+
GenericParam::Type(g)
378+
}
379+
g => g,
380+
})
381+
.collect();
382+
375383
let mut tuple_types: Vec<_> = field_types.iter().map(|x| quote! { #x }).collect();
376384
let mut tuple_patterns: Vec<_> = field_locals.iter().map(|x| quote! { #x }).collect();
377385

386+
tuple_types.extend(
387+
ignored_field_types
388+
.iter()
389+
.map(|ty| parse_quote!(::std::marker::PhantomData::<#ty>)),
390+
);
391+
tuple_patterns.extend(ignored_field_types.iter().map(|_| parse_quote!(_)));
392+
378393
// If the number of fields exceeds the 16-parameter limit,
379394
// fold the fields into tuples of tuples until we are below the limit.
380395
const LIMIT: usize = 16;
@@ -385,6 +400,7 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
385400
let end = Vec::from_iter(tuple_patterns.drain(..LIMIT));
386401
tuple_patterns.push(parse_quote!( (#(#end,)*) ));
387402
}
403+
388404
// Create a where clause for the `ReadOnlySystemParam` impl.
389405
// Ensure that each field implements `ReadOnlySystemParam`.
390406
let mut read_only_generics = generics.clone();
@@ -395,6 +411,9 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
395411
.push(syn::parse_quote!(#field_type: #path::system::ReadOnlySystemParam));
396412
}
397413

414+
let fields_alias =
415+
ensure_no_collision(format_ident!("__StructFieldsAlias"), token_stream.clone());
416+
398417
let struct_name = &ast.ident;
399418
let state_struct_visibility = &ast.vis;
400419
let state_struct_name = ensure_no_collision(format_ident!("FetchState"), token_stream);
@@ -404,41 +423,41 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
404423
// The struct can still be accessed via SystemParam::State, e.g. EventReaderState can be accessed via
405424
// <EventReader<'static, 'static, T> as SystemParam>::State
406425
const _: () = {
426+
// Allows rebinding the lifetimes of each field type.
427+
type #fields_alias <'w, 's, #punctuated_generics_no_bounds> = (#(#tuple_types,)*);
428+
407429
#[doc(hidden)]
408-
#state_struct_visibility struct #state_struct_name <'w, 's, #(#lifetimeless_generics,)*>
430+
#state_struct_visibility struct #state_struct_name <#(#lifetimeless_generics,)*>
409431
#where_clause {
410-
state: (#(<#tuple_types as #path::system::SystemParam>::State,)*),
411-
marker: std::marker::PhantomData<(
412-
<#path::prelude::Query<'w, 's, ()> as #path::system::SystemParam>::State,
413-
#(fn() -> #ignored_field_types,)*
414-
)>,
432+
state: <#fields_alias::<'static, 'static, #punctuated_generic_idents> as #path::system::SystemParam>::State,
415433
}
416434

417-
unsafe impl<'w, 's, #punctuated_generics> #path::system::SystemParam for #struct_name #ty_generics #where_clause {
418-
type State = #state_struct_name<'static, 'static, #punctuated_generic_idents>;
419-
type Item<'_w, '_s> = #struct_name <#(#shadowed_lifetimes,)* #punctuated_generic_idents>;
435+
unsafe impl<#punctuated_generics> #path::system::SystemParam for
436+
#struct_name <#(#shadowed_lifetimes,)* #punctuated_generic_idents> #where_clause
437+
{
438+
type State = #state_struct_name<#punctuated_generic_idents>;
439+
type Item<'w, 's> = #struct_name #ty_generics;
420440

421441
fn init_state(world: &mut #path::world::World, system_meta: &mut #path::system::SystemMeta) -> Self::State {
422442
#state_struct_name {
423-
state: <(#(#tuple_types,)*) as #path::system::SystemParam>::init_state(world, system_meta),
424-
marker: std::marker::PhantomData,
443+
state: <#fields_alias::<'_, '_, #punctuated_generic_idents> as #path::system::SystemParam>::init_state(world, system_meta),
425444
}
426445
}
427446

428447
fn new_archetype(state: &mut Self::State, archetype: &#path::archetype::Archetype, system_meta: &mut #path::system::SystemMeta) {
429-
<(#(#tuple_types,)*) as #path::system::SystemParam>::new_archetype(&mut state.state, archetype, system_meta)
448+
<#fields_alias::<'_, '_, #punctuated_generic_idents> as #path::system::SystemParam>::new_archetype(&mut state.state, archetype, system_meta)
430449
}
431450

432451
fn apply(state: &mut Self::State, system_meta: &#path::system::SystemMeta, world: &mut #path::world::World) {
433-
<(#(#tuple_types,)*) as #path::system::SystemParam>::apply(&mut state.state, system_meta, world);
452+
<#fields_alias::<'_, '_, #punctuated_generic_idents> as #path::system::SystemParam>::apply(&mut state.state, system_meta, world);
434453
}
435454

436-
unsafe fn get_param<'w2, 's2>(
437-
state: &'s2 mut Self::State,
455+
unsafe fn get_param<'w, 's>(
456+
state: &'s mut Self::State,
438457
system_meta: &#path::system::SystemMeta,
439-
world: &'w2 #path::world::World,
458+
world: &'w #path::world::World,
440459
change_tick: #path::component::Tick,
441-
) -> Self::Item<'w2, 's2> {
460+
) -> Self::Item<'w, 's> {
442461
let (#(#tuple_patterns,)*) = <
443462
(#(#tuple_types,)*) as #path::system::SystemParam
444463
>::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
@@ -278,6 +278,24 @@ use std::{cell::UnsafeCell, marker::PhantomData};
278278
/// # bevy_ecs::system::assert_is_system(my_system);
279279
/// ```
280280
///
281+
/// # Generic Queries
282+
///
283+
/// When writing generic code, it is often necessary to use [`PhantomData`]
284+
/// to constrain type parameters. Since `WorldQuery` is implemented for all
285+
/// `PhantomData<T>` types, this pattern can be used with this macro.
286+
///
287+
/// ```
288+
/// # use bevy_ecs::{prelude::*, query::WorldQuery};
289+
/// # use std::marker::PhantomData;
290+
/// #[derive(WorldQuery)]
291+
/// pub struct GenericQuery<T> {
292+
/// id: Entity,
293+
/// marker: PhantomData<T>,
294+
/// }
295+
/// # fn my_system(q: Query<GenericQuery<()>>) {}
296+
/// # bevy_ecs::system::assert_is_system(my_system);
297+
/// ```
298+
///
281299
/// # Safety
282300
///
283301
/// Component access of `Self::ReadOnly` must be a subset of `Self`
@@ -1315,7 +1333,6 @@ macro_rules! impl_anytuple_fetch {
13151333

13161334
/// SAFETY: each item in the tuple is read only
13171335
unsafe impl<$($name: ReadOnlyWorldQuery),*> ReadOnlyWorldQuery for AnyOf<($($name,)*)> {}
1318-
13191336
};
13201337
}
13211338

@@ -1389,6 +1406,71 @@ unsafe impl<Q: WorldQuery> WorldQuery for NopWorldQuery<Q> {
13891406
/// SAFETY: `NopFetch` never accesses any data
13901407
unsafe impl<Q: WorldQuery> ReadOnlyWorldQuery for NopWorldQuery<Q> {}
13911408

1409+
/// SAFETY: `PhantomData` never accesses any world data.
1410+
unsafe impl<T: ?Sized> WorldQuery for PhantomData<T> {
1411+
type Item<'a> = ();
1412+
type Fetch<'a> = ();
1413+
type ReadOnly = Self;
1414+
type State = ();
1415+
1416+
fn shrink<'wlong: 'wshort, 'wshort>(_item: Self::Item<'wlong>) -> Self::Item<'wshort> {}
1417+
1418+
unsafe fn init_fetch<'w>(
1419+
_world: &'w World,
1420+
_state: &Self::State,
1421+
_last_run: Tick,
1422+
_this_run: Tick,
1423+
) -> Self::Fetch<'w> {
1424+
}
1425+
1426+
unsafe fn clone_fetch<'w>(_fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> {}
1427+
1428+
// `PhantomData` does not match any components, so all components it matches
1429+
// are stored in a Table (vacuous truth).
1430+
const IS_DENSE: bool = true;
1431+
// `PhantomData` matches every entity in each archetype.
1432+
const IS_ARCHETYPAL: bool = true;
1433+
1434+
unsafe fn set_archetype<'w>(
1435+
_fetch: &mut Self::Fetch<'w>,
1436+
_state: &Self::State,
1437+
_archetype: &'w Archetype,
1438+
_table: &'w Table,
1439+
) {
1440+
}
1441+
1442+
unsafe fn set_table<'w>(_fetch: &mut Self::Fetch<'w>, _state: &Self::State, _table: &'w Table) {
1443+
}
1444+
1445+
unsafe fn fetch<'w>(
1446+
_fetch: &mut Self::Fetch<'w>,
1447+
_entity: Entity,
1448+
_table_row: TableRow,
1449+
) -> Self::Item<'w> {
1450+
}
1451+
1452+
fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess<ComponentId>) {}
1453+
1454+
fn update_archetype_component_access(
1455+
_state: &Self::State,
1456+
_archetype: &Archetype,
1457+
_access: &mut Access<ArchetypeComponentId>,
1458+
) {
1459+
}
1460+
1461+
fn init_state(_world: &mut World) -> Self::State {}
1462+
1463+
fn matches_component_set(
1464+
_state: &Self::State,
1465+
_set_contains_id: &impl Fn(ComponentId) -> bool,
1466+
) -> bool {
1467+
true
1468+
}
1469+
}
1470+
1471+
/// SAFETY: `PhantomData` never accesses any world data.
1472+
unsafe impl<T: ?Sized> ReadOnlyWorldQuery for PhantomData<T> {}
1473+
13921474
#[cfg(test)]
13931475
mod tests {
13941476
use super::*;
@@ -1397,6 +1479,22 @@ mod tests {
13971479
#[derive(Component)]
13981480
pub struct A;
13991481

1482+
// Compile test for https://github.com/bevyengine/bevy/pull/8030.
1483+
#[test]
1484+
fn world_query_phantom_data() {
1485+
#[derive(WorldQuery)]
1486+
pub struct IgnoredQuery<Marker> {
1487+
id: Entity,
1488+
#[world_query(ignore)]
1489+
_marker: PhantomData<Marker>,
1490+
_marker2: PhantomData<Marker>,
1491+
}
1492+
1493+
fn ignored_system(_: Query<IgnoredQuery<()>>) {}
1494+
1495+
crate::system::assert_is_system(ignored_system);
1496+
}
1497+
14001498
// Ensures that each field of a `WorldQuery` struct's read-only variant
14011499
// has the same visibility as its corresponding mutable field.
14021500
#[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:
@@ -1466,7 +1472,6 @@ pub mod lifetimeless {
14661472
/// #[derive(SystemParam)]
14671473
/// struct GenericParam<'w, 's, T: SystemParam> {
14681474
/// field: T,
1469-
/// #[system_param(ignore)]
14701475
/// // Use the lifetimes in this type, or they will be unbound.
14711476
/// phantom: core::marker::PhantomData<&'w &'s ()>
14721477
/// }
@@ -1532,6 +1537,26 @@ unsafe impl<P: SystemParam + 'static> SystemParam for StaticSystemParam<'_, '_,
15321537
}
15331538
}
15341539

1540+
// SAFETY: No world access.
1541+
unsafe impl<T: ?Sized> SystemParam for PhantomData<T> {
1542+
type State = ();
1543+
type Item<'world, 'state> = Self;
1544+
1545+
fn init_state(_world: &mut World, _system_meta: &mut SystemMeta) -> Self::State {}
1546+
1547+
unsafe fn get_param<'world, 'state>(
1548+
_state: &'state mut Self::State,
1549+
_system_meta: &SystemMeta,
1550+
_world: &'world World,
1551+
_change_tick: Tick,
1552+
) -> Self::Item<'world, 'state> {
1553+
PhantomData
1554+
}
1555+
}
1556+
1557+
// SAFETY: No world access.
1558+
unsafe impl<T: ?Sized> ReadOnlySystemParam for PhantomData<T> {}
1559+
15351560
#[cfg(test)]
15361561
mod tests {
15371562
use super::*;
@@ -1606,6 +1631,7 @@ mod tests {
16061631
_foo: Res<'w, T>,
16071632
#[system_param(ignore)]
16081633
marker: PhantomData<&'w Marker>,
1634+
marker2: PhantomData<&'w Marker>,
16091635
}
16101636

16111637
// Compile tests for https://github.com/bevyengine/bevy/pull/6957.
@@ -1643,4 +1669,10 @@ mod tests {
16431669

16441670
#[derive(Resource)]
16451671
pub struct FetchState;
1672+
1673+
// Regression test for https://github.com/bevyengine/bevy/issues/8192.
1674+
#[derive(SystemParam)]
1675+
pub struct InvariantParam<'w, 's> {
1676+
_set: ParamSet<'w, 's, (Query<'w, 's, ()>,)>,
1677+
}
16461678
}

0 commit comments

Comments
 (0)