Skip to content

Commit f8dd5cb

Browse files
committed
Assert compiler errors for compile_fail tests (#3067)
# Objective bevy_ecs has several compile_fail tests that assert lifetime safety. In the past, these tests have been green for the wrong reasons (see e.g. #2984). This PR makes sure, that they will fail if the compiler error changes. ## Solution Use [trybuild](https://crates.io/crates/trybuild) to assert the compiler errors. The UI tests are in a separate crate that is not part of the Bevy workspace. This is to ensure that they do not break Bevy's crater builds. The tests get executed by the CI workflow on the stable toolchain.
1 parent 94f5d19 commit f8dd5cb

19 files changed

+274
-174
lines changed

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ readme = "README.md"
1212
repository = "https://github.com/bevyengine/bevy"
1313

1414
[workspace]
15-
exclude = ["benches"]
15+
exclude = ["benches", "crates/bevy_ecs_compile_fail_tests"]
1616
members = ["crates/*", "examples/ios", "tools/ci", "errors"]
1717

1818
[features]
@@ -47,7 +47,7 @@ bevy_audio = ["bevy_internal/bevy_audio"]
4747
bevy_dynamic_plugin = ["bevy_internal/bevy_dynamic_plugin"]
4848
bevy_gilrs = ["bevy_internal/bevy_gilrs"]
4949
bevy_gltf = ["bevy_internal/bevy_gltf"]
50-
bevy_wgpu = ["bevy_internal/bevy_wgpu"]
50+
bevy_wgpu = ["bevy_internal/bevy_wgpu"]
5151
bevy_winit = ["bevy_internal/bevy_winit"]
5252

5353
trace_chrome = ["bevy_internal/trace_chrome"]

crates/bevy_ecs/src/system/mod.rs

Lines changed: 0 additions & 169 deletions
Original file line numberDiff line numberDiff line change
@@ -786,172 +786,3 @@ mod tests {
786786
}
787787
}
788788
}
789-
790-
/// ```compile_fail E0499
791-
/// use bevy_ecs::prelude::*;
792-
/// #[derive(Component)]
793-
/// struct A(usize);
794-
/// fn system(mut query: Query<&mut A>, e: Res<Entity>) {
795-
/// let mut iter = query.iter_mut();
796-
/// let a = &mut *iter.next().unwrap();
797-
///
798-
/// let mut iter2 = query.iter_mut();
799-
/// let b = &mut *iter2.next().unwrap();
800-
///
801-
/// // this should fail to compile
802-
/// println!("{}", a.0);
803-
/// }
804-
/// ```
805-
#[allow(unused)]
806-
#[cfg(doctest)]
807-
fn system_query_iter_lifetime_safety_test() {}
808-
809-
/// ```compile_fail E0499
810-
/// use bevy_ecs::prelude::*;
811-
/// #[derive(Component)]
812-
/// struct A(usize);
813-
/// fn system(mut query: Query<&mut A>, e: Res<Entity>) {
814-
/// let mut a1 = query.get_mut(*e).unwrap();
815-
/// let mut a2 = query.get_mut(*e).unwrap();
816-
/// // this should fail to compile
817-
/// println!("{} {}", a1.0, a2.0);
818-
/// }
819-
/// ```
820-
#[allow(unused)]
821-
#[cfg(doctest)]
822-
fn system_query_get_lifetime_safety_test() {}
823-
824-
/// ```compile_fail E0499
825-
/// use bevy_ecs::prelude::*;
826-
/// #[derive(Component)]
827-
/// struct A(usize);
828-
/// fn query_set(mut queries: QuerySet<(QueryState<&mut A>, QueryState<&A>)>, e: Res<Entity>) {
829-
/// let mut q2 = queries.q0();
830-
/// let mut iter2 = q2.iter_mut();
831-
/// let mut b = iter2.next().unwrap();
832-
///
833-
/// let q1 = queries.q1();
834-
/// let mut iter = q1.iter();
835-
/// let a = &*iter.next().unwrap();
836-
///
837-
/// // this should fail to compile
838-
/// b.0 = a.0
839-
/// }
840-
/// ```
841-
#[allow(unused)]
842-
#[cfg(doctest)]
843-
fn system_query_set_iter_lifetime_safety_test() {}
844-
845-
/// ```compile_fail E0499
846-
/// use bevy_ecs::prelude::*;
847-
/// #[derive(Component)]
848-
/// struct A(usize);
849-
/// fn query_set(mut queries: QuerySet<(QueryState<&mut A>, QueryState<&A>)>, e: Res<Entity>) {
850-
/// let q1 = queries.q1();
851-
/// let mut iter = q1.iter();
852-
/// let a = &*iter.next().unwrap();
853-
///
854-
/// let mut q2 = queries.q0();
855-
/// let mut iter2 = q2.iter_mut();
856-
/// let mut b = iter2.next().unwrap();
857-
///
858-
/// // this should fail to compile
859-
/// b.0 = a.0;
860-
/// }
861-
/// ```
862-
#[allow(unused)]
863-
#[cfg(doctest)]
864-
fn system_query_set_iter_flip_lifetime_safety_test() {}
865-
866-
/// ```compile_fail E0499
867-
/// use bevy_ecs::prelude::*;
868-
/// #[derive(Component)]
869-
/// struct A(usize);
870-
/// fn query_set(mut queries: QuerySet<(QueryState<&mut A>, QueryState<&A>)>, e: Res<Entity>) {
871-
/// let mut q2 = queries.q0();
872-
/// let mut b = q2.get_mut(*e).unwrap();
873-
///
874-
/// let q1 = queries.q1();
875-
/// let a = q1.get(*e).unwrap();
876-
///
877-
/// // this should fail to compile
878-
/// b.0 = a.0
879-
/// }
880-
/// ```
881-
#[allow(unused)]
882-
#[cfg(doctest)]
883-
fn system_query_set_get_lifetime_safety_test() {}
884-
885-
/// ```compile_fail E0499
886-
/// use bevy_ecs::prelude::*;
887-
/// #[derive(Component)]
888-
/// struct A(usize);
889-
/// fn query_set(mut queries: QuerySet<(QueryState<&mut A>, QueryState<&A>)>, e: Res<Entity>) {
890-
/// let q1 = queries.q1();
891-
/// let a = q1.get(*e).unwrap();
892-
///
893-
/// let mut q2 = queries.q0();
894-
/// let mut b = q2.get_mut(*e).unwrap();
895-
/// // this should fail to compile
896-
/// b.0 = a.0
897-
/// }
898-
/// ```
899-
#[allow(unused)]
900-
#[cfg(doctest)]
901-
fn system_query_set_get_flip_lifetime_safety_test() {}
902-
903-
/// ```compile_fail E0502
904-
/// use bevy_ecs::prelude::*;
905-
/// use bevy_ecs::system::SystemState;
906-
/// #[derive(Component)]
907-
/// struct A(usize);
908-
/// #[derive(Component)]
909-
/// struct B(usize);
910-
/// struct State {
911-
/// state_r: SystemState<Query<'static, 'static, &'static A>>,
912-
/// state_w: SystemState<Query<'static, 'static, &'static mut A>>,
913-
/// }
914-
///
915-
/// impl State {
916-
/// fn get_component<'w>(&mut self, world: &'w mut World, entity: Entity) {
917-
/// let q1 = self.state_r.get(&world);
918-
/// let a1 = q1.get(entity).unwrap();
919-
///
920-
/// let mut q2 = self.state_w.get_mut(world);
921-
/// let a2 = q2.get_mut(entity).unwrap();
922-
///
923-
/// // this should fail to compile
924-
/// println!("{}", a1.0);
925-
/// }
926-
/// }
927-
/// ```
928-
#[allow(unused)]
929-
#[cfg(doctest)]
930-
fn system_state_get_lifetime_safety_test() {}
931-
932-
/// ```compile_fail E0502
933-
/// use bevy_ecs::prelude::*;
934-
/// use bevy_ecs::system::SystemState;
935-
/// #[derive(Component)]
936-
/// struct A(usize);
937-
/// #[derive(Component)]
938-
/// struct B(usize);
939-
/// struct State {
940-
/// state_r: SystemState<Query<'static, 'static, &'static A>>,
941-
/// state_w: SystemState<Query<'static, 'static, &'static mut A>>,
942-
/// }
943-
///
944-
/// impl State {
945-
/// fn get_components<'w>(&mut self, world: &'w mut World) {
946-
/// let q1 = self.state_r.get(&world);
947-
/// let a1 = q1.iter().next().unwrap();
948-
/// let mut q2 = self.state_w.get_mut(world);
949-
/// let a2 = q2.iter_mut().next().unwrap();
950-
/// // this should fail to compile
951-
/// println!("{}", a1.0);
952-
/// }
953-
/// }
954-
/// ```
955-
#[allow(unused)]
956-
#[cfg(doctest)]
957-
fn system_state_iter_lifetime_safety_test() {}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
name = "bevy_ecs_compile_fail_tests"
3+
version = "0.5.0"
4+
edition = "2021"
5+
description = "Compile fail tests for Bevy Engine's entity component system"
6+
homepage = "https://bevyengine.org"
7+
repository = "https://github.com/bevyengine/bevy"
8+
license = "MIT OR Apache-2.0"
9+
publish = false
10+
11+
[dev-dependencies]
12+
bevy_ecs = { path = "../bevy_ecs", version = "0.5.0" }
13+
trybuild = "1.0"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Compile fail tests for bevy_ecs
2+
3+
This crate is separate from `bevy_ecs` and not part of the Bevy workspace in order to not fail `crater` tests for Bevy. The tests assert on the exact compiler errors and can easily fail for new Rust versions due to updated compiler errors (e.g. changes in spans).
4+
5+
The `CI` workflow executes these tests on the stable rust toolchain (see [tools/ci](../../tools/ci/src/main.rs)).
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
// Nothing here, check out the integration tests
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#[test]
2+
fn test() {
3+
let t = trybuild::TestCases::new();
4+
t.compile_fail("tests/ui/*.rs");
5+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
use bevy_ecs::prelude::*;
2+
3+
#[derive(Component)]
4+
struct A(usize);
5+
6+
fn system(mut query: Query<&mut A>, e: Res<Entity>) {
7+
let a1 = query.get_mut(*e).unwrap();
8+
let a2 = query.get_mut(*e).unwrap();
9+
// this should fail to compile
10+
println!("{} {}", a1.0, a2.0);
11+
}
12+
13+
fn main() {}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
error[E0499]: cannot borrow `query` as mutable more than once at a time
2+
--> tests/ui/system_query_get_lifetime_safety.rs:8:14
3+
|
4+
7 | let a1 = query.get_mut(*e).unwrap();
5+
| ----- first mutable borrow occurs here
6+
8 | let a2 = query.get_mut(*e).unwrap();
7+
| ^^^^^ second mutable borrow occurs here
8+
9 | // this should fail to compile
9+
10 | println!("{} {}", a1.0, a2.0);
10+
| -- first borrow later used here
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
use bevy_ecs::prelude::*;
2+
3+
#[derive(Component)]
4+
struct A(usize);
5+
6+
fn system(mut query: Query<&mut A>) {
7+
let mut iter = query.iter_mut();
8+
let a = &mut *iter.next().unwrap();
9+
10+
let mut iter2 = query.iter_mut();
11+
let _ = &mut *iter2.next().unwrap();
12+
13+
// this should fail to compile
14+
println!("{}", a.0);
15+
}
16+
17+
fn main() {}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
error[E0499]: cannot borrow `query` as mutable more than once at a time
2+
--> tests/ui/system_query_iter_lifetime_safety.rs:10:21
3+
|
4+
7 | let mut iter = query.iter_mut();
5+
| ----- first mutable borrow occurs here
6+
...
7+
10 | let mut iter2 = query.iter_mut();
8+
| ^^^^^ second mutable borrow occurs here
9+
...
10+
14 | println!("{}", a.0);
11+
| --- first borrow later used here
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
use bevy_ecs::prelude::*;
2+
3+
#[derive(Component)]
4+
struct A(usize);
5+
6+
fn query_set(mut queries: QuerySet<(QueryState<&mut A>, QueryState<&A>)>, e: Res<Entity>) {
7+
let mut q2 = queries.q0();
8+
let mut b = q2.get_mut(*e).unwrap();
9+
10+
let q1 = queries.q1();
11+
let a = q1.get(*e).unwrap();
12+
13+
// this should fail to compile
14+
b.0 = a.0
15+
}
16+
17+
fn query_set_flip(mut queries: QuerySet<(QueryState<&mut A>, QueryState<&A>)>, e: Res<Entity>) {
18+
let q1 = queries.q1();
19+
let a = q1.get(*e).unwrap();
20+
21+
let mut q2 = queries.q0();
22+
let mut b = q2.get_mut(*e).unwrap();
23+
24+
// this should fail to compile
25+
b.0 = a.0
26+
}
27+
28+
fn main() {}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
error[E0499]: cannot borrow `queries` as mutable more than once at a time
2+
--> tests/ui/system_query_set_get_lifetime_safety.rs:10:14
3+
|
4+
7 | let mut q2 = queries.q0();
5+
| ------- first mutable borrow occurs here
6+
...
7+
10 | let q1 = queries.q1();
8+
| ^^^^^^^ second mutable borrow occurs here
9+
...
10+
14 | b.0 = a.0
11+
| - first borrow later used here
12+
13+
error[E0499]: cannot borrow `queries` as mutable more than once at a time
14+
--> tests/ui/system_query_set_get_lifetime_safety.rs:21:18
15+
|
16+
18 | let q1 = queries.q1();
17+
| ------- first mutable borrow occurs here
18+
...
19+
21 | let mut q2 = queries.q0();
20+
| ^^^^^^^ second mutable borrow occurs here
21+
...
22+
25 | b.0 = a.0
23+
| --- first borrow later used here
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
use bevy_ecs::prelude::*;
2+
3+
#[derive(Component)]
4+
struct A(usize);
5+
6+
fn query_set(mut queries: QuerySet<(QueryState<&mut A>, QueryState<&A>)>) {
7+
let mut q2 = queries.q0();
8+
let mut iter2 = q2.iter_mut();
9+
let mut b = iter2.next().unwrap();
10+
11+
let q1 = queries.q1();
12+
let mut iter = q1.iter();
13+
let a = &*iter.next().unwrap();
14+
15+
// this should fail to compile
16+
b.0 = a.0
17+
}
18+
19+
fn query_set_flip(mut queries: QuerySet<(QueryState<&mut A>, QueryState<&A>)>) {
20+
let q1 = queries.q1();
21+
let mut iter = q1.iter();
22+
let a = &*iter.next().unwrap();
23+
24+
let mut q2 = queries.q0();
25+
let mut iter2 = q2.iter_mut();
26+
let mut b = iter2.next().unwrap();
27+
28+
// this should fail to compile
29+
b.0 = a.0;
30+
}
31+
32+
fn main() {}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
error[E0499]: cannot borrow `queries` as mutable more than once at a time
2+
--> tests/ui/system_query_set_iter_lifetime_safety.rs:11:14
3+
|
4+
7 | let mut q2 = queries.q0();
5+
| ------- first mutable borrow occurs here
6+
...
7+
11 | let q1 = queries.q1();
8+
| ^^^^^^^ second mutable borrow occurs here
9+
...
10+
16 | b.0 = a.0
11+
| - first borrow later used here
12+
13+
error[E0499]: cannot borrow `queries` as mutable more than once at a time
14+
--> tests/ui/system_query_set_iter_lifetime_safety.rs:24:18
15+
|
16+
20 | let q1 = queries.q1();
17+
| ------- first mutable borrow occurs here
18+
...
19+
24 | let mut q2 = queries.q0();
20+
| ^^^^^^^ second mutable borrow occurs here
21+
...
22+
29 | b.0 = a.0;
23+
| --- first borrow later used here

0 commit comments

Comments
 (0)