diff --git a/crates/bevy_ecs/src/query/mod.rs b/crates/bevy_ecs/src/query/mod.rs index f8602bee6e..0be28ee2cf 100644 --- a/crates/bevy_ecs/src/query/mod.rs +++ b/crates/bevy_ecs/src/query/mod.rs @@ -19,18 +19,19 @@ unsafe fn debug_checked_unreachable() -> ! { #[cfg(test)] mod tests { - use super::AnyOf; + use super::WorldQuery; + use crate::prelude::{AnyOf, Entity, Or, With, Without}; use crate::{self as bevy_ecs, component::Component, world::World}; use std::collections::HashSet; - #[derive(Component, Debug, Hash, Eq, PartialEq)] + #[derive(Component, Debug, Hash, Eq, PartialEq, Clone, Copy)] struct A(usize); - #[derive(Component, Debug, Eq, PartialEq)] + #[derive(Component, Debug, Eq, PartialEq, Clone, Copy)] struct B(usize); - #[derive(Component, Debug, Eq, PartialEq)] + #[derive(Component, Debug, Eq, PartialEq, Clone, Copy)] struct C(usize); - #[derive(Component, Debug, Eq, PartialEq)] + #[derive(Component, Debug, Eq, PartialEq, Clone, Copy)] #[component(storage = "SparseSet")] struct Sparse(usize); @@ -337,4 +338,182 @@ mod tests { vec![(Some(&A(1)), Some(&B(2))), (Some(&A(2)), None),] ); } + + #[test] + #[should_panic = "&mut bevy_ecs::query::tests::A conflicts with a previous access in this query."] + fn self_conflicting_worldquery() { + #[derive(WorldQuery)] + #[world_query(mutable)] + struct SelfConflicting { + a: &'static mut A, + b: &'static mut A, + } + + let mut world = World::new(); + world.query::(); + } + + #[test] + fn derived_worldqueries() { + let mut world = World::new(); + + world.spawn().insert_bundle((A(10), B(18), C(3), Sparse(4))); + + world.spawn().insert_bundle((A(101), B(148), C(13))); + world.spawn().insert_bundle((A(51), B(46), Sparse(72))); + world.spawn().insert_bundle((A(398), C(6), Sparse(9))); + world.spawn().insert_bundle((B(11), C(28), Sparse(92))); + + world.spawn().insert_bundle((C(18348), Sparse(101))); + world.spawn().insert_bundle((B(839), Sparse(5))); + world.spawn().insert_bundle((B(6721), C(122))); + world.spawn().insert_bundle((A(220), Sparse(63))); + world.spawn().insert_bundle((A(1092), C(382))); + world.spawn().insert_bundle((A(2058), B(3019))); + + world.spawn().insert_bundle((B(38), C(8), Sparse(100))); + world.spawn().insert_bundle((A(111), C(52), Sparse(1))); + world.spawn().insert_bundle((A(599), B(39), Sparse(13))); + world.spawn().insert_bundle((A(55), B(66), C(77))); + + world.spawn(); + + { + #[derive(WorldQuery)] + struct CustomAB { + a: &'static A, + b: &'static B, + } + + let custom_param_data = world + .query::() + .iter(&world) + .map(|item| (*item.a, *item.b)) + .collect::>(); + let normal_data = world + .query::<(&A, &B)>() + .iter(&world) + .map(|(a, b)| (*a, *b)) + .collect::>(); + assert_eq!(custom_param_data, normal_data); + } + + { + #[derive(WorldQuery)] + struct FancyParam { + e: Entity, + b: &'static B, + opt: Option<&'static Sparse>, + } + + let custom_param_data = world + .query::() + .iter(&world) + .map(|fancy| (fancy.e, *fancy.b, fancy.opt.copied())) + .collect::>(); + let normal_data = world + .query::<(Entity, &B, Option<&Sparse>)>() + .iter(&world) + .map(|(e, b, opt)| (e, *b, opt.copied())) + .collect::>(); + assert_eq!(custom_param_data, normal_data); + } + + { + #[derive(WorldQuery)] + struct MaybeBSparse { + blah: Option<(&'static B, &'static Sparse)>, + } + #[derive(WorldQuery)] + struct MatchEverything { + abcs: AnyOf<(&'static A, &'static B, &'static C)>, + opt_bsparse: MaybeBSparse, + } + + let custom_param_data = world + .query::() + .iter(&world) + .map( + |MatchEverythingItem { + abcs: (a, b, c), + opt_bsparse: MaybeBSparseItem { blah: bsparse }, + }| { + ( + (a.copied(), b.copied(), c.copied()), + bsparse.map(|(b, sparse)| (*b, *sparse)), + ) + }, + ) + .collect::>(); + let normal_data = world + .query::<(AnyOf<(&A, &B, &C)>, Option<(&B, &Sparse)>)>() + .iter(&world) + .map(|((a, b, c), bsparse)| { + ( + (a.copied(), b.copied(), c.copied()), + bsparse.map(|(b, sparse)| (*b, *sparse)), + ) + }) + .collect::>(); + assert_eq!(custom_param_data, normal_data) + } + + { + #[derive(WorldQuery)] + struct AOrBFilter { + a: Or<(With, With)>, + } + #[derive(WorldQuery)] + struct NoSparseThatsSlow { + no: Without, + } + + let custom_param_entities = world + .query_filtered::() + .iter(&world) + .collect::>(); + let normal_entities = world + .query_filtered::, With)>, Without)>() + .iter(&world) + .collect::>(); + assert_eq!(custom_param_entities, normal_entities); + } + + { + #[derive(WorldQuery)] + struct CSparseFilter { + tuple_structs_pls: With, + ugh: With, + } + + let custom_param_entities = world + .query_filtered::() + .iter(&world) + .collect::>(); + let normal_entities = world + .query_filtered::, With)>() + .iter(&world) + .collect::>(); + assert_eq!(custom_param_entities, normal_entities); + } + + { + #[derive(WorldQuery)] + struct WithoutComps { + _1: Without, + _2: Without, + _3: Without, + } + + let custom_param_entities = world + .query_filtered::() + .iter(&world) + .collect::>(); + let normal_entities = world + .query_filtered::, Without, Without)>() + .iter(&world) + .collect::>(); + assert_eq!(custom_param_entities, normal_entities); + } + } } diff --git a/crates/bevy_ecs_compile_fail_tests/tests/ui/world_query_derive.rs b/crates/bevy_ecs_compile_fail_tests/tests/ui/world_query_derive.rs new file mode 100644 index 0000000000..1619384096 --- /dev/null +++ b/crates/bevy_ecs_compile_fail_tests/tests/ui/world_query_derive.rs @@ -0,0 +1,23 @@ +use bevy_ecs::prelude::*; +use bevy_ecs::query::WorldQuery; + +#[derive(Component)] +struct Foo; + +#[derive(WorldQuery)] +struct MutableUnmarked { + a: &'static mut Foo, +} + +#[derive(WorldQuery)] +#[world_query(mutable)] +struct MutableMarked { + a: &'static mut Foo, +} + +#[derive(WorldQuery)] +struct NestedMutableUnmarked { + a: MutableMarked, +} + +fn main() {} diff --git a/crates/bevy_ecs_compile_fail_tests/tests/ui/world_query_derive.stderr b/crates/bevy_ecs_compile_fail_tests/tests/ui/world_query_derive.stderr new file mode 100644 index 0000000000..6ced5ae9c7 --- /dev/null +++ b/crates/bevy_ecs_compile_fail_tests/tests/ui/world_query_derive.stderr @@ -0,0 +1,25 @@ +error[E0277]: the trait bound `WriteFetch<'_, Foo>: ReadOnlyFetch` is not satisfied + --> tests/ui/world_query_derive.rs:7:10 + | +7 | #[derive(WorldQuery)] + | ^^^^^^^^^^ the trait `ReadOnlyFetch` is not implemented for `WriteFetch<'_, Foo>` + | +note: required by a bound in `_::assert_readonly` + --> tests/ui/world_query_derive.rs:7:10 + | +7 | #[derive(WorldQuery)] + | ^^^^^^^^^^ required by this bound in `_::assert_readonly` + = note: this error originates in the derive macro `WorldQuery` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `MutableMarkedFetch<'_>: ReadOnlyFetch` is not satisfied + --> tests/ui/world_query_derive.rs:18:10 + | +18 | #[derive(WorldQuery)] + | ^^^^^^^^^^ the trait `ReadOnlyFetch` is not implemented for `MutableMarkedFetch<'_>` + | +note: required by a bound in `_::assert_readonly` + --> tests/ui/world_query_derive.rs:18:10 + | +18 | #[derive(WorldQuery)] + | ^^^^^^^^^^ required by this bound in `_::assert_readonly` + = note: this error originates in the derive macro `WorldQuery` (in Nightly builds, run with -Z macro-backtrace for more info)