diff --git a/crates/bevy_ecs/macros/src/query_filter.rs b/crates/bevy_ecs/macros/src/query_filter.rs index 2a2916905ae20..378e26df101a7 100644 --- a/crates/bevy_ecs/macros/src/query_filter.rs +++ b/crates/bevy_ecs/macros/src/query_filter.rs @@ -120,7 +120,8 @@ pub fn derive_query_filter_impl(input: TokenStream) -> TokenStream { ); let filter_impl = quote! { - impl #user_impl_generics #path::query::QueryFilter + // SAFETY: This only performs access that subqueries perform, and they impl `QueryFilter` and so perform no mutable access. + unsafe impl #user_impl_generics #path::query::QueryFilter for #struct_name #user_ty_generics #user_where_clauses { const IS_ARCHETYPAL: bool = true #(&& <#field_types>::IS_ARCHETYPAL)*; diff --git a/crates/bevy_ecs/src/query/filter.rs b/crates/bevy_ecs/src/query/filter.rs index affdaf2828b63..0385191053a65 100644 --- a/crates/bevy_ecs/src/query/filter.rs +++ b/crates/bevy_ecs/src/query/filter.rs @@ -70,12 +70,17 @@ use std::{cell::UnsafeCell, marker::PhantomData}; /// [`matches_component_set`]: Self::matches_component_set /// [`Query`]: crate::system::Query /// [`State`]: Self::State +/// +/// # Safety +/// +/// The [`WorldQuery`] implementation must not take any mutable access. +/// This is the same safety requirement as [`ReadOnlyQueryData`](crate::query::ReadOnlyQueryData). #[diagnostic::on_unimplemented( message = "`{Self}` is not a valid `Query` filter", label = "invalid `Query` filter", note = "a `QueryFilter` typically uses a combination of `With` and `Without` statements" )] -pub trait QueryFilter: WorldQuery { +pub unsafe trait QueryFilter: WorldQuery { /// Returns true if (and only if) this Filter relies strictly on archetypes to limit which /// components are accessed by the Query. /// @@ -201,7 +206,8 @@ unsafe impl WorldQuery for With { } } -impl QueryFilter for With { +// SAFETY: WorldQuery impl performs no access at all +unsafe impl QueryFilter for With { const IS_ARCHETYPAL: bool = true; #[inline(always)] @@ -311,7 +317,8 @@ unsafe impl WorldQuery for Without { } } -impl QueryFilter for Without { +// SAFETY: WorldQuery impl performs no access at all +unsafe impl QueryFilter for Without { const IS_ARCHETYPAL: bool = true; #[inline(always)] @@ -490,7 +497,8 @@ macro_rules! impl_or_query_filter { } } - impl<$($filter: QueryFilter),*> QueryFilter for Or<($($filter,)*)> { + // SAFETY: This only performs access that subqueries perform, and they impl `QueryFilter` and so perform no mutable access. + unsafe impl<$($filter: QueryFilter),*> QueryFilter for Or<($($filter,)*)> { const IS_ARCHETYPAL: bool = true $(&& $filter::IS_ARCHETYPAL)*; #[inline(always)] @@ -512,7 +520,8 @@ macro_rules! impl_tuple_query_filter { #[allow(non_snake_case)] #[allow(clippy::unused_unit)] $(#[$meta])* - impl<$($name: QueryFilter),*> QueryFilter for ($($name,)*) { + // SAFETY: This only performs access that subqueries perform, and they impl `QueryFilter` and so perform no mutable access. + unsafe impl<$($name: QueryFilter),*> QueryFilter for ($($name,)*) { const IS_ARCHETYPAL: bool = true $(&& $name::IS_ARCHETYPAL)*; #[inline(always)] @@ -734,7 +743,8 @@ unsafe impl WorldQuery for Added { } } -impl QueryFilter for Added { +// SAFETY: WorldQuery impl performs only read access on ticks +unsafe impl QueryFilter for Added { const IS_ARCHETYPAL: bool = false; #[inline(always)] unsafe fn filter_fetch( @@ -949,7 +959,8 @@ unsafe impl WorldQuery for Changed { } } -impl QueryFilter for Changed { +// SAFETY: WorldQuery impl performs only read access on ticks +unsafe impl QueryFilter for Changed { const IS_ARCHETYPAL: bool = false; #[inline(always)]