From 56686a89628105332a5764a6856039a29febe506 Mon Sep 17 00:00:00 2001
From: Gino Valente <49806985+MrGVSV@users.noreply.github.com>
Date: Tue, 16 May 2023 11:29:09 -0700
Subject: [PATCH] bevy_derive: Add `#[deref]` attribute (#8552)
# Objective
Bevy code tends to make heavy use of the [newtype](
https://doc.rust-lang.org/rust-by-example/generics/new_types.html)
pattern, which is why we have a dedicated derive for
[`Deref`](https://doc.rust-lang.org/std/ops/trait.Deref.html) and
[`DerefMut`](https://doc.rust-lang.org/std/ops/trait.DerefMut.html).
This derive works for any struct with a single field:
```rust
#[derive(Component, Deref, DerefMut)]
struct MyNewtype(usize);
```
One reason for the single-field limitation is to prevent confusion and
footguns related that would arise from allowing multi-field structs:
Similar structs, different derefs
|
```rust
#[derive(Deref, DerefMut)]
struct MyStruct {
foo: usize, // <- Derefs usize
bar: String,
}
```
|
```rust
#[derive(Deref, DerefMut)]
struct MyStruct {
bar: String, // <- Derefs String
foo: usize,
}
```
|
Why `.1`?
|
```rust
#[derive(Deref, DerefMut)]
struct MyStruct(Vec, Vec);
let mut foo = MyStruct(vec![123], vec![1.23]);
// Why can we skip the `.0` here?
foo.push(456);
// But not here?
foo.1.push(4.56);
```
|
However, there are certainly cases where it's useful to allow for
structs with multiple fields. Such as for structs with one "real" field
and one `PhantomData` to allow for generics:
```rust
#[derive(Deref, DerefMut)]
struct MyStruct(
// We want use this field for the `Deref`/`DerefMut` impls
String,
// But we need this field so that we can make this struct generic
PhantomData
);
// ERROR: Deref can only be derived for structs with a single field
// ERROR: DerefMut can only be derived for structs with a single field
```
Additionally, the possible confusion and footguns are mainly an issue
for newer Rust/Bevy users. Those familiar with `Deref` and `DerefMut`
understand what adding the derive really means and can anticipate its
behavior.
## Solution
Allow users to opt into multi-field `Deref`/`DerefMut` derives using a
`#[deref]` attribute:
```rust
#[derive(Deref, DerefMut)]
struct MyStruct(
// Use this field for the `Deref`/`DerefMut` impls
#[deref] String,
// We can freely include any other field without a compile error
PhantomData
);
```
This prevents the footgun pointed out in the first issue described in
the previous section, but it still leaves the possible confusion
surrounding `.0`-vs-`.#`. However, the idea is that by making this
behavior explicit with an attribute, users will be more aware of it and
can adapt appropriately.
---
## Changelog
- Added `#[deref]` attribute to `Deref` and `DerefMut` derives
---
Cargo.toml | 1 +
crates/bevy_derive/src/derefs.rs | 72 ++++++++--
crates/bevy_derive/src/lib.rs | 131 +++++++++++++++++-
.../bevy_macros_compile_fail_tests/Cargo.toml | 13 ++
.../bevy_macros_compile_fail_tests/README.md | 6 +
.../bevy_macros_compile_fail_tests/src/lib.rs | 1 +
.../tests/deref_derive.rs | 6 +
.../deref_derive/invalid_attribute.fail.rs | 15 ++
.../invalid_attribute.fail.stderr | 11 ++
.../tests/deref_derive/invalid_item.fail.rs | 9 ++
.../deref_derive/invalid_item.fail.stderr | 15 ++
.../deref_derive/missing_attribute.fail.rs | 12 ++
.../missing_attribute.fail.stderr | 15 ++
.../deref_derive/multiple_attributes.fail.rs | 14 ++
.../multiple_attributes.fail.stderr | 11 ++
.../deref_derive/multiple_fields.pass.rs | 22 +++
.../tests/deref_derive/single_field.pass.rs | 19 +++
.../tests/deref_mut_derive.rs | 6 +
.../invalid_attribute.fail.rs | 32 +++++
.../invalid_attribute.fail.stderr | 11 ++
.../deref_mut_derive/invalid_item.fail.rs | 9 ++
.../deref_mut_derive/invalid_item.fail.stderr | 15 ++
.../mismatched_target_type.fail.rs | 30 ++++
.../mismatched_target_type.fail.stderr | 25 ++++
.../missing_attribute.fail.rs | 29 ++++
.../missing_attribute.fail.stderr | 15 ++
.../deref_mut_derive/missing_deref.fail.rs | 13 ++
.../missing_deref.fail.stderr | 33 +++++
.../multiple_attributes.fail.rs | 31 +++++
.../multiple_attributes.fail.stderr | 11 ++
.../deref_mut_derive/multiple_fields.pass.rs | 39 ++++++
.../deref_mut_derive/single_field.pass.rs | 37 +++++
tools/ci/src/main.rs | 9 ++
33 files changed, 696 insertions(+), 22 deletions(-)
create mode 100644 crates/bevy_macros_compile_fail_tests/Cargo.toml
create mode 100644 crates/bevy_macros_compile_fail_tests/README.md
create mode 100644 crates/bevy_macros_compile_fail_tests/src/lib.rs
create mode 100644 crates/bevy_macros_compile_fail_tests/tests/deref_derive.rs
create mode 100644 crates/bevy_macros_compile_fail_tests/tests/deref_derive/invalid_attribute.fail.rs
create mode 100644 crates/bevy_macros_compile_fail_tests/tests/deref_derive/invalid_attribute.fail.stderr
create mode 100644 crates/bevy_macros_compile_fail_tests/tests/deref_derive/invalid_item.fail.rs
create mode 100644 crates/bevy_macros_compile_fail_tests/tests/deref_derive/invalid_item.fail.stderr
create mode 100644 crates/bevy_macros_compile_fail_tests/tests/deref_derive/missing_attribute.fail.rs
create mode 100644 crates/bevy_macros_compile_fail_tests/tests/deref_derive/missing_attribute.fail.stderr
create mode 100644 crates/bevy_macros_compile_fail_tests/tests/deref_derive/multiple_attributes.fail.rs
create mode 100644 crates/bevy_macros_compile_fail_tests/tests/deref_derive/multiple_attributes.fail.stderr
create mode 100644 crates/bevy_macros_compile_fail_tests/tests/deref_derive/multiple_fields.pass.rs
create mode 100644 crates/bevy_macros_compile_fail_tests/tests/deref_derive/single_field.pass.rs
create mode 100644 crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive.rs
create mode 100644 crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/invalid_attribute.fail.rs
create mode 100644 crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/invalid_attribute.fail.stderr
create mode 100644 crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/invalid_item.fail.rs
create mode 100644 crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/invalid_item.fail.stderr
create mode 100644 crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/mismatched_target_type.fail.rs
create mode 100644 crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/mismatched_target_type.fail.stderr
create mode 100644 crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/missing_attribute.fail.rs
create mode 100644 crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/missing_attribute.fail.stderr
create mode 100644 crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/missing_deref.fail.rs
create mode 100644 crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/missing_deref.fail.stderr
create mode 100644 crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/multiple_attributes.fail.rs
create mode 100644 crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/multiple_attributes.fail.stderr
create mode 100644 crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/multiple_fields.pass.rs
create mode 100644 crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/single_field.pass.rs
diff --git a/Cargo.toml b/Cargo.toml
index 88b42749dbc08..15dee5c5e3f3f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -16,6 +16,7 @@ rust-version = "1.67.0"
exclude = [
"benches",
"crates/bevy_ecs_compile_fail_tests",
+ "crates/bevy_macros_compile_fail_tests",
"crates/bevy_reflect_compile_fail_tests",
]
members = [
diff --git a/crates/bevy_derive/src/derefs.rs b/crates/bevy_derive/src/derefs.rs
index 559b22be19c4c..0e3dfc1bf0f75 100644
--- a/crates/bevy_derive/src/derefs.rs
+++ b/crates/bevy_derive/src/derefs.rs
@@ -1,12 +1,16 @@
use proc_macro::{Span, TokenStream};
use quote::quote;
-use syn::{parse_macro_input, Data, DeriveInput, Index, Member, Type};
+use syn::{parse_macro_input, Data, DeriveInput, Field, Index, Member, Type};
+
+const DEREF: &str = "Deref";
+const DEREF_MUT: &str = "DerefMut";
+const DEREF_ATTR: &str = "deref";
pub fn derive_deref(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let ident = &ast.ident;
- let (field_member, field_type) = match get_inner_field(&ast, false) {
+ let (field_member, field_type) = match get_deref_field(&ast, false) {
Ok(items) => items,
Err(err) => {
return err.into_compile_error().into();
@@ -29,7 +33,7 @@ pub fn derive_deref_mut(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let ident = &ast.ident;
- let (field_member, _) = match get_inner_field(&ast, true) {
+ let (field_member, _) = match get_deref_field(&ast, true) {
Ok(items) => items,
Err(err) => {
return err.into_compile_error().into();
@@ -46,24 +50,62 @@ pub fn derive_deref_mut(input: TokenStream) -> TokenStream {
})
}
-fn get_inner_field(ast: &DeriveInput, is_mut: bool) -> syn::Result<(Member, &Type)> {
+fn get_deref_field(ast: &DeriveInput, is_mut: bool) -> syn::Result<(Member, &Type)> {
+ let deref_kind = if is_mut { DEREF_MUT } else { DEREF };
+ let deref_attr_str = format!("`#[{DEREF_ATTR}]`");
+
match &ast.data {
+ Data::Struct(data_struct) if data_struct.fields.is_empty() => Err(syn::Error::new(
+ Span::call_site().into(),
+ format!("{deref_kind} cannot be derived on field-less structs"),
+ )),
Data::Struct(data_struct) if data_struct.fields.len() == 1 => {
let field = data_struct.fields.iter().next().unwrap();
- let member = field
- .ident
- .as_ref()
- .map(|name| Member::Named(name.clone()))
- .unwrap_or_else(|| Member::Unnamed(Index::from(0)));
+ let member = to_member(field, 0);
Ok((member, &field.ty))
}
- _ => {
- let msg = if is_mut {
- "DerefMut can only be derived for structs with a single field"
+ Data::Struct(data_struct) => {
+ let mut selected_field: Option<(Member, &Type)> = None;
+ for (index, field) in data_struct.fields.iter().enumerate() {
+ for attr in &field.attrs {
+ if !attr.meta.require_path_only()?.is_ident(DEREF_ATTR) {
+ continue;
+ }
+
+ if selected_field.is_some() {
+ return Err(syn::Error::new_spanned(
+ attr,
+ format!(
+ "{deref_attr_str} attribute can only be used on a single field"
+ ),
+ ));
+ }
+
+ let member = to_member(field, index);
+ selected_field = Some((member, &field.ty));
+ }
+ }
+
+ if let Some(selected_field) = selected_field {
+ Ok(selected_field)
} else {
- "Deref can only be derived for structs with a single field"
- };
- Err(syn::Error::new(Span::call_site().into(), msg))
+ Err(syn::Error::new(
+ Span::call_site().into(),
+ format!("deriving {deref_kind} on multi-field structs requires one field to have the {deref_attr_str} attribute"),
+ ))
+ }
}
+ _ => Err(syn::Error::new(
+ Span::call_site().into(),
+ format!("{deref_kind} can only be derived on structs"),
+ )),
}
}
+
+fn to_member(field: &Field, index: usize) -> Member {
+ field
+ .ident
+ .as_ref()
+ .map(|name| Member::Named(name.clone()))
+ .unwrap_or_else(|| Member::Unnamed(Index::from(index)))
+}
diff --git a/crates/bevy_derive/src/lib.rs b/crates/bevy_derive/src/lib.rs
index 7b66edb491948..9030d8791ba71 100644
--- a/crates/bevy_derive/src/lib.rs
+++ b/crates/bevy_derive/src/lib.rs
@@ -17,14 +17,20 @@ pub fn derive_dynamic_plugin(input: TokenStream) -> TokenStream {
app_plugin::derive_dynamic_plugin(input)
}
-/// Implements [`Deref`] for _single-item_ structs. This is especially useful when
-/// utilizing the [newtype] pattern.
+/// Implements [`Deref`] for structs. This is especially useful when utilizing the [newtype] pattern.
+///
+/// For single-field structs, the implementation automatically uses that field.
+/// For multi-field structs, you must specify which field to use with the `#[deref]` attribute.
///
/// If you need [`DerefMut`] as well, consider using the other [derive] macro alongside
/// this one.
///
/// # Example
///
+/// ## Tuple Structs
+///
+/// Using a single-field struct:
+///
/// ```
/// use bevy_derive::Deref;
///
@@ -32,26 +38,83 @@ pub fn derive_dynamic_plugin(input: TokenStream) -> TokenStream {
/// struct MyNewtype(String);
///
/// let foo = MyNewtype(String::from("Hello"));
-/// assert_eq!(5, foo.len());
+/// assert_eq!("Hello", *foo);
+/// ```
+///
+/// Using a multi-field struct:
+///
+/// ```
+/// # use std::marker::PhantomData;
+/// use bevy_derive::Deref;
+///
+/// #[derive(Deref)]
+/// struct MyStruct(#[deref] String, PhantomData);
+///
+/// let foo = MyStruct(String::from("Hello"), PhantomData::);
+/// assert_eq!("Hello", *foo);
+/// ```
+///
+/// ## Named Structs
+///
+/// Using a single-field struct:
+///
+/// ```
+/// use bevy_derive::{Deref, DerefMut};
+///
+/// #[derive(Deref, DerefMut)]
+/// struct MyStruct {
+/// value: String,
+/// }
+///
+/// let foo = MyStruct {
+/// value: String::from("Hello")
+/// };
+/// assert_eq!("Hello", *foo);
+/// ```
+///
+/// Using a multi-field struct:
+///
+/// ```
+/// # use std::marker::PhantomData;
+/// use bevy_derive::{Deref, DerefMut};
+///
+/// #[derive(Deref, DerefMut)]
+/// struct MyStruct {
+/// #[deref]
+/// value: String,
+/// _phantom: PhantomData,
+/// }
+///
+/// let foo = MyStruct {
+/// value:String::from("Hello"),
+/// _phantom:PhantomData::
+/// };
+/// assert_eq!("Hello", *foo);
/// ```
///
/// [`Deref`]: std::ops::Deref
/// [newtype]: https://doc.rust-lang.org/rust-by-example/generics/new_types.html
/// [`DerefMut`]: std::ops::DerefMut
/// [derive]: crate::derive_deref_mut
-#[proc_macro_derive(Deref)]
+#[proc_macro_derive(Deref, attributes(deref))]
pub fn derive_deref(input: TokenStream) -> TokenStream {
derefs::derive_deref(input)
}
-/// Implements [`DerefMut`] for _single-item_ structs. This is especially useful when
-/// utilizing the [newtype] pattern.
+/// Implements [`DerefMut`] for structs. This is especially useful when utilizing the [newtype] pattern.
+///
+/// For single-field structs, the implementation automatically uses that field.
+/// For multi-field structs, you must specify which field to use with the `#[deref]` attribute.
///
/// [`DerefMut`] requires a [`Deref`] implementation. You can implement it manually or use
/// Bevy's [derive] macro for convenience.
///
/// # Example
///
+/// ## Tuple Structs
+///
+/// Using a single-field struct:
+///
/// ```
/// use bevy_derive::{Deref, DerefMut};
///
@@ -63,11 +126,65 @@ pub fn derive_deref(input: TokenStream) -> TokenStream {
/// assert_eq!("Hello World!", *foo);
/// ```
///
+/// Using a multi-field struct:
+///
+/// ```
+/// # use std::marker::PhantomData;
+/// use bevy_derive::{Deref, DerefMut};
+///
+/// #[derive(Deref, DerefMut)]
+/// struct MyStruct(#[deref] String, PhantomData);
+///
+/// let mut foo = MyStruct(String::from("Hello"), PhantomData::);
+/// foo.push_str(" World!");
+/// assert_eq!("Hello World!", *foo);
+/// ```
+///
+/// ## Named Structs
+///
+/// Using a single-field struct:
+///
+/// ```
+/// use bevy_derive::{Deref, DerefMut};
+///
+/// #[derive(Deref, DerefMut)]
+/// struct MyStruct {
+/// value: String,
+/// }
+///
+/// let mut foo = MyStruct {
+/// value: String::from("Hello")
+/// };
+/// foo.push_str(" World!");
+/// assert_eq!("Hello World!", *foo);
+/// ```
+///
+/// Using a multi-field struct:
+///
+/// ```
+/// # use std::marker::PhantomData;
+/// use bevy_derive::{Deref, DerefMut};
+///
+/// #[derive(Deref, DerefMut)]
+/// struct MyStruct {
+/// #[deref]
+/// value: String,
+/// _phantom: PhantomData,
+/// }
+///
+/// let mut foo = MyStruct {
+/// value:String::from("Hello"),
+/// _phantom:PhantomData::
+/// };
+/// foo.push_str(" World!");
+/// assert_eq!("Hello World!", *foo);
+/// ```
+///
/// [`DerefMut`]: std::ops::DerefMut
/// [newtype]: https://doc.rust-lang.org/rust-by-example/generics/new_types.html
/// [`Deref`]: std::ops::Deref
/// [derive]: crate::derive_deref
-#[proc_macro_derive(DerefMut)]
+#[proc_macro_derive(DerefMut, attributes(deref))]
pub fn derive_deref_mut(input: TokenStream) -> TokenStream {
derefs::derive_deref_mut(input)
}
diff --git a/crates/bevy_macros_compile_fail_tests/Cargo.toml b/crates/bevy_macros_compile_fail_tests/Cargo.toml
new file mode 100644
index 0000000000000..e5d0d19bec361
--- /dev/null
+++ b/crates/bevy_macros_compile_fail_tests/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "bevy_macros_compile_fail_tests"
+version = "0.1.0"
+edition = "2021"
+description = "Compile fail tests for Bevy Engine's various macros"
+homepage = "https://bevyengine.org"
+repository = "https://github.com/bevyengine/bevy"
+license = "MIT OR Apache-2.0"
+publish = false
+
+[dependencies]
+bevy_derive = { path = "../bevy_derive" }
+trybuild = "1.0.71"
\ No newline at end of file
diff --git a/crates/bevy_macros_compile_fail_tests/README.md b/crates/bevy_macros_compile_fail_tests/README.md
new file mode 100644
index 0000000000000..b1ec35cef3789
--- /dev/null
+++ b/crates/bevy_macros_compile_fail_tests/README.md
@@ -0,0 +1,6 @@
+# Compile fail tests for Bevy macros
+
+This crate is 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).
+
+The `CI` workflow executes these tests on the stable rust toolchain (see [tools/ci](../../tools/ci/src/main.rs)).
diff --git a/crates/bevy_macros_compile_fail_tests/src/lib.rs b/crates/bevy_macros_compile_fail_tests/src/lib.rs
new file mode 100644
index 0000000000000..d0d1683dd6b97
--- /dev/null
+++ b/crates/bevy_macros_compile_fail_tests/src/lib.rs
@@ -0,0 +1 @@
+// Nothing here, check out the integration tests
diff --git a/crates/bevy_macros_compile_fail_tests/tests/deref_derive.rs b/crates/bevy_macros_compile_fail_tests/tests/deref_derive.rs
new file mode 100644
index 0000000000000..520f13757fa73
--- /dev/null
+++ b/crates/bevy_macros_compile_fail_tests/tests/deref_derive.rs
@@ -0,0 +1,6 @@
+#[test]
+fn test() {
+ let t = trybuild::TestCases::new();
+ t.compile_fail("tests/deref_derive/*.fail.rs");
+ t.pass("tests/deref_derive/*.pass.rs");
+}
diff --git a/crates/bevy_macros_compile_fail_tests/tests/deref_derive/invalid_attribute.fail.rs b/crates/bevy_macros_compile_fail_tests/tests/deref_derive/invalid_attribute.fail.rs
new file mode 100644
index 0000000000000..753a832d0186c
--- /dev/null
+++ b/crates/bevy_macros_compile_fail_tests/tests/deref_derive/invalid_attribute.fail.rs
@@ -0,0 +1,15 @@
+use bevy_derive::Deref;
+
+// Reason: `#[deref]` doesn't take any arguments
+
+#[derive(Deref)]
+struct TupleStruct(usize, #[deref()] String);
+
+#[derive(Deref)]
+struct Struct {
+ foo: usize,
+ #[deref()]
+ bar: String,
+}
+
+fn main() {}
diff --git a/crates/bevy_macros_compile_fail_tests/tests/deref_derive/invalid_attribute.fail.stderr b/crates/bevy_macros_compile_fail_tests/tests/deref_derive/invalid_attribute.fail.stderr
new file mode 100644
index 0000000000000..ffa0440a1f4c3
--- /dev/null
+++ b/crates/bevy_macros_compile_fail_tests/tests/deref_derive/invalid_attribute.fail.stderr
@@ -0,0 +1,11 @@
+error: unexpected token in attribute
+ --> tests/deref_derive/invalid_attribute.fail.rs:6:34
+ |
+6 | struct TupleStruct(usize, #[deref()] String);
+ | ^
+
+error: unexpected token in attribute
+ --> tests/deref_derive/invalid_attribute.fail.rs:11:12
+ |
+11 | #[deref()]
+ | ^
diff --git a/crates/bevy_macros_compile_fail_tests/tests/deref_derive/invalid_item.fail.rs b/crates/bevy_macros_compile_fail_tests/tests/deref_derive/invalid_item.fail.rs
new file mode 100644
index 0000000000000..20ffe0dea7210
--- /dev/null
+++ b/crates/bevy_macros_compile_fail_tests/tests/deref_derive/invalid_item.fail.rs
@@ -0,0 +1,9 @@
+use bevy_derive::Deref;
+
+#[derive(Deref)]
+struct UnitStruct;
+
+#[derive(Deref)]
+enum Enum {}
+
+fn main() {}
diff --git a/crates/bevy_macros_compile_fail_tests/tests/deref_derive/invalid_item.fail.stderr b/crates/bevy_macros_compile_fail_tests/tests/deref_derive/invalid_item.fail.stderr
new file mode 100644
index 0000000000000..73fce8cb04fcc
--- /dev/null
+++ b/crates/bevy_macros_compile_fail_tests/tests/deref_derive/invalid_item.fail.stderr
@@ -0,0 +1,15 @@
+error: Deref cannot be derived on field-less structs
+ --> tests/deref_derive/invalid_item.fail.rs:3:10
+ |
+3 | #[derive(Deref)]
+ | ^^^^^
+ |
+ = note: this error originates in the derive macro `Deref` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: Deref can only be derived on structs
+ --> tests/deref_derive/invalid_item.fail.rs:6:10
+ |
+6 | #[derive(Deref)]
+ | ^^^^^
+ |
+ = note: this error originates in the derive macro `Deref` (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/crates/bevy_macros_compile_fail_tests/tests/deref_derive/missing_attribute.fail.rs b/crates/bevy_macros_compile_fail_tests/tests/deref_derive/missing_attribute.fail.rs
new file mode 100644
index 0000000000000..cb1e5a73417c5
--- /dev/null
+++ b/crates/bevy_macros_compile_fail_tests/tests/deref_derive/missing_attribute.fail.rs
@@ -0,0 +1,12 @@
+use bevy_derive::Deref;
+
+#[derive(Deref)]
+struct TupleStruct(usize, String);
+
+#[derive(Deref)]
+struct Struct {
+ foo: usize,
+ bar: String,
+}
+
+fn main() {}
diff --git a/crates/bevy_macros_compile_fail_tests/tests/deref_derive/missing_attribute.fail.stderr b/crates/bevy_macros_compile_fail_tests/tests/deref_derive/missing_attribute.fail.stderr
new file mode 100644
index 0000000000000..537e8251d1cd1
--- /dev/null
+++ b/crates/bevy_macros_compile_fail_tests/tests/deref_derive/missing_attribute.fail.stderr
@@ -0,0 +1,15 @@
+error: deriving Deref on multi-field structs requires one field to have the `#[deref]` attribute
+ --> tests/deref_derive/missing_attribute.fail.rs:3:10
+ |
+3 | #[derive(Deref)]
+ | ^^^^^
+ |
+ = note: this error originates in the derive macro `Deref` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: deriving Deref on multi-field structs requires one field to have the `#[deref]` attribute
+ --> tests/deref_derive/missing_attribute.fail.rs:6:10
+ |
+6 | #[derive(Deref)]
+ | ^^^^^
+ |
+ = note: this error originates in the derive macro `Deref` (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/crates/bevy_macros_compile_fail_tests/tests/deref_derive/multiple_attributes.fail.rs b/crates/bevy_macros_compile_fail_tests/tests/deref_derive/multiple_attributes.fail.rs
new file mode 100644
index 0000000000000..f81b552410a8c
--- /dev/null
+++ b/crates/bevy_macros_compile_fail_tests/tests/deref_derive/multiple_attributes.fail.rs
@@ -0,0 +1,14 @@
+use bevy_derive::Deref;
+
+#[derive(Deref)]
+struct TupleStruct(#[deref] usize, #[deref] String);
+
+#[derive(Deref)]
+struct Struct {
+ #[deref]
+ foo: usize,
+ #[deref]
+ bar: String,
+}
+
+fn main() {}
diff --git a/crates/bevy_macros_compile_fail_tests/tests/deref_derive/multiple_attributes.fail.stderr b/crates/bevy_macros_compile_fail_tests/tests/deref_derive/multiple_attributes.fail.stderr
new file mode 100644
index 0000000000000..a1834cc17825e
--- /dev/null
+++ b/crates/bevy_macros_compile_fail_tests/tests/deref_derive/multiple_attributes.fail.stderr
@@ -0,0 +1,11 @@
+error: `#[deref]` attribute can only be used on a single field
+ --> tests/deref_derive/multiple_attributes.fail.rs:4:36
+ |
+4 | struct TupleStruct(#[deref] usize, #[deref] String);
+ | ^^^^^^^^
+
+error: `#[deref]` attribute can only be used on a single field
+ --> tests/deref_derive/multiple_attributes.fail.rs:10:5
+ |
+10 | #[deref]
+ | ^^^^^^^^
diff --git a/crates/bevy_macros_compile_fail_tests/tests/deref_derive/multiple_fields.pass.rs b/crates/bevy_macros_compile_fail_tests/tests/deref_derive/multiple_fields.pass.rs
new file mode 100644
index 0000000000000..2244e89b78385
--- /dev/null
+++ b/crates/bevy_macros_compile_fail_tests/tests/deref_derive/multiple_fields.pass.rs
@@ -0,0 +1,22 @@
+use bevy_derive::Deref;
+
+#[derive(Deref)]
+struct TupleStruct(usize, #[deref] String);
+
+#[derive(Deref)]
+struct Struct {
+ foo: usize,
+ #[deref]
+ bar: String,
+}
+
+fn main() {
+ let value = TupleStruct(123, "Hello world!".to_string());
+ let _: &String = &*value;
+
+ let value = Struct {
+ foo: 123,
+ bar: "Hello world!".to_string(),
+ };
+ let _: &String = &*value;
+}
diff --git a/crates/bevy_macros_compile_fail_tests/tests/deref_derive/single_field.pass.rs b/crates/bevy_macros_compile_fail_tests/tests/deref_derive/single_field.pass.rs
new file mode 100644
index 0000000000000..c9155659f0c29
--- /dev/null
+++ b/crates/bevy_macros_compile_fail_tests/tests/deref_derive/single_field.pass.rs
@@ -0,0 +1,19 @@
+use bevy_derive::Deref;
+
+#[derive(Deref)]
+struct TupleStruct(String);
+
+#[derive(Deref)]
+struct Struct {
+ bar: String,
+}
+
+fn main() {
+ let value = TupleStruct("Hello world!".to_string());
+ let _: &String = &*value;
+
+ let value = Struct {
+ bar: "Hello world!".to_string(),
+ };
+ let _: &String = &*value;
+}
diff --git a/crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive.rs b/crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive.rs
new file mode 100644
index 0000000000000..71a52be670eef
--- /dev/null
+++ b/crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive.rs
@@ -0,0 +1,6 @@
+#[test]
+fn test() {
+ let t = trybuild::TestCases::new();
+ t.compile_fail("tests/deref_mut_derive/*.fail.rs");
+ t.pass("tests/deref_mut_derive/*.pass.rs");
+}
diff --git a/crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/invalid_attribute.fail.rs b/crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/invalid_attribute.fail.rs
new file mode 100644
index 0000000000000..1c32faf072f3b
--- /dev/null
+++ b/crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/invalid_attribute.fail.rs
@@ -0,0 +1,32 @@
+use bevy_derive::DerefMut;
+use std::ops::Deref;
+
+// Reason: `#[deref]` doesn't take any arguments
+
+#[derive(DerefMut)]
+struct TupleStruct(usize, #[deref()] String);
+
+impl Deref for TupleStruct {
+ type Target = String;
+
+ fn deref(&self) -> &Self::Target {
+ &self.1
+ }
+}
+
+#[derive(DerefMut)]
+struct Struct {
+ foo: usize,
+ #[deref()]
+ bar: String,
+}
+
+impl Deref for Struct {
+ type Target = String;
+
+ fn deref(&self) -> &Self::Target {
+ &self.bar
+ }
+}
+
+fn main() {}
diff --git a/crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/invalid_attribute.fail.stderr b/crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/invalid_attribute.fail.stderr
new file mode 100644
index 0000000000000..013dcfa970113
--- /dev/null
+++ b/crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/invalid_attribute.fail.stderr
@@ -0,0 +1,11 @@
+error: unexpected token in attribute
+ --> tests/deref_mut_derive/invalid_attribute.fail.rs:7:34
+ |
+7 | struct TupleStruct(usize, #[deref()] String);
+ | ^
+
+error: unexpected token in attribute
+ --> tests/deref_mut_derive/invalid_attribute.fail.rs:20:12
+ |
+20 | #[deref()]
+ | ^
diff --git a/crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/invalid_item.fail.rs b/crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/invalid_item.fail.rs
new file mode 100644
index 0000000000000..f1b5da4d9257b
--- /dev/null
+++ b/crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/invalid_item.fail.rs
@@ -0,0 +1,9 @@
+use bevy_derive::DerefMut;
+
+#[derive(DerefMut)]
+struct UnitStruct;
+
+#[derive(DerefMut)]
+enum Enum {}
+
+fn main() {}
diff --git a/crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/invalid_item.fail.stderr b/crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/invalid_item.fail.stderr
new file mode 100644
index 0000000000000..b089fc3d940fb
--- /dev/null
+++ b/crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/invalid_item.fail.stderr
@@ -0,0 +1,15 @@
+error: DerefMut cannot be derived on field-less structs
+ --> tests/deref_mut_derive/invalid_item.fail.rs:3:10
+ |
+3 | #[derive(DerefMut)]
+ | ^^^^^^^^
+ |
+ = note: this error originates in the derive macro `DerefMut` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: DerefMut can only be derived on structs
+ --> tests/deref_mut_derive/invalid_item.fail.rs:6:10
+ |
+6 | #[derive(DerefMut)]
+ | ^^^^^^^^
+ |
+ = note: this error originates in the derive macro `DerefMut` (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/mismatched_target_type.fail.rs b/crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/mismatched_target_type.fail.rs
new file mode 100644
index 0000000000000..fff8232bdfcf6
--- /dev/null
+++ b/crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/mismatched_target_type.fail.rs
@@ -0,0 +1,30 @@
+use bevy_derive::DerefMut;
+use std::ops::Deref;
+
+#[derive(DerefMut)]
+struct TupleStruct(#[deref] usize, String);
+
+impl Deref for TupleStruct {
+ type Target = String;
+
+ fn deref(&self) -> &Self::Target {
+ &self.1
+ }
+}
+
+#[derive(DerefMut)]
+struct Struct {
+ #[deref]
+ foo: usize,
+ bar: String,
+}
+
+impl Deref for Struct {
+ type Target = String;
+
+ fn deref(&self) -> &Self::Target {
+ &self.bar
+ }
+}
+
+fn main() {}
diff --git a/crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/mismatched_target_type.fail.stderr b/crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/mismatched_target_type.fail.stderr
new file mode 100644
index 0000000000000..c3c007a984be2
--- /dev/null
+++ b/crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/mismatched_target_type.fail.stderr
@@ -0,0 +1,25 @@
+error[E0308]: mismatched types
+ --> tests/deref_mut_derive/mismatched_target_type.fail.rs:4:10
+ |
+4 | #[derive(DerefMut)]
+ | ^^^^^^^^
+ | |
+ | expected `&mut String`, found `&mut usize`
+ | expected `&mut String` because of return type
+ |
+ = note: expected mutable reference `&mut String`
+ found mutable reference `&mut usize`
+ = note: this error originates in the derive macro `DerefMut` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error[E0308]: mismatched types
+ --> tests/deref_mut_derive/mismatched_target_type.fail.rs:15:10
+ |
+15 | #[derive(DerefMut)]
+ | ^^^^^^^^
+ | |
+ | expected `&mut String`, found `&mut usize`
+ | expected `&mut String` because of return type
+ |
+ = note: expected mutable reference `&mut String`
+ found mutable reference `&mut usize`
+ = note: this error originates in the derive macro `DerefMut` (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/missing_attribute.fail.rs b/crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/missing_attribute.fail.rs
new file mode 100644
index 0000000000000..3a8e09dfe5ec2
--- /dev/null
+++ b/crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/missing_attribute.fail.rs
@@ -0,0 +1,29 @@
+use bevy_derive::DerefMut;
+use std::ops::Deref;
+
+#[derive(DerefMut)]
+struct TupleStruct(usize, String);
+
+impl Deref for TupleStruct {
+ type Target = String;
+
+ fn deref(&self) -> &Self::Target {
+ &self.1
+ }
+}
+
+#[derive(DerefMut)]
+struct Struct {
+ foo: usize,
+ bar: String,
+}
+
+impl Deref for Struct {
+ type Target = String;
+
+ fn deref(&self) -> &Self::Target {
+ &self.bar
+ }
+}
+
+fn main() {}
diff --git a/crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/missing_attribute.fail.stderr b/crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/missing_attribute.fail.stderr
new file mode 100644
index 0000000000000..ce3d17133affe
--- /dev/null
+++ b/crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/missing_attribute.fail.stderr
@@ -0,0 +1,15 @@
+error: deriving DerefMut on multi-field structs requires one field to have the `#[deref]` attribute
+ --> tests/deref_mut_derive/missing_attribute.fail.rs:4:10
+ |
+4 | #[derive(DerefMut)]
+ | ^^^^^^^^
+ |
+ = note: this error originates in the derive macro `DerefMut` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: deriving DerefMut on multi-field structs requires one field to have the `#[deref]` attribute
+ --> tests/deref_mut_derive/missing_attribute.fail.rs:15:10
+ |
+15 | #[derive(DerefMut)]
+ | ^^^^^^^^
+ |
+ = note: this error originates in the derive macro `DerefMut` (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/missing_deref.fail.rs b/crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/missing_deref.fail.rs
new file mode 100644
index 0000000000000..1b2da5adfac87
--- /dev/null
+++ b/crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/missing_deref.fail.rs
@@ -0,0 +1,13 @@
+use bevy_derive::DerefMut;
+
+#[derive(DerefMut)]
+struct TupleStruct(usize, #[deref] String);
+
+#[derive(DerefMut)]
+struct Struct {
+ foo: usize,
+ #[deref]
+ bar: String,
+}
+
+fn main() {}
diff --git a/crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/missing_deref.fail.stderr b/crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/missing_deref.fail.stderr
new file mode 100644
index 0000000000000..3e11d49532e94
--- /dev/null
+++ b/crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/missing_deref.fail.stderr
@@ -0,0 +1,33 @@
+error[E0277]: the trait bound `TupleStruct: Deref` is not satisfied
+ --> tests/deref_mut_derive/missing_deref.fail.rs:4:8
+ |
+4 | struct TupleStruct(usize, #[deref] String);
+ | ^^^^^^^^^^^ the trait `Deref` is not implemented for `TupleStruct`
+ |
+note: required by a bound in `DerefMut`
+ --> $RUST/core/src/ops/deref.rs
+
+error[E0277]: the trait bound `Struct: Deref` is not satisfied
+ --> tests/deref_mut_derive/missing_deref.fail.rs:7:8
+ |
+7 | struct Struct {
+ | ^^^^^^ the trait `Deref` is not implemented for `Struct`
+ |
+note: required by a bound in `DerefMut`
+ --> $RUST/core/src/ops/deref.rs
+
+error[E0277]: the trait bound `TupleStruct: Deref` is not satisfied
+ --> tests/deref_mut_derive/missing_deref.fail.rs:3:10
+ |
+3 | #[derive(DerefMut)]
+ | ^^^^^^^^ the trait `Deref` is not implemented for `TupleStruct`
+ |
+ = note: this error originates in the derive macro `DerefMut` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error[E0277]: the trait bound `Struct: Deref` is not satisfied
+ --> tests/deref_mut_derive/missing_deref.fail.rs:6:10
+ |
+6 | #[derive(DerefMut)]
+ | ^^^^^^^^ the trait `Deref` is not implemented for `Struct`
+ |
+ = note: this error originates in the derive macro `DerefMut` (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/multiple_attributes.fail.rs b/crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/multiple_attributes.fail.rs
new file mode 100644
index 0000000000000..afe1e0a876b64
--- /dev/null
+++ b/crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/multiple_attributes.fail.rs
@@ -0,0 +1,31 @@
+use bevy_derive::DerefMut;
+use std::ops::Deref;
+
+#[derive(DerefMut)]
+struct TupleStruct(#[deref] usize, #[deref] String);
+
+impl Deref for TupleStruct {
+ type Target = String;
+
+ fn deref(&self) -> &Self::Target {
+ &self.1
+ }
+}
+
+#[derive(DerefMut)]
+struct Struct {
+ #[deref]
+ foo: usize,
+ #[deref]
+ bar: String,
+}
+
+impl Deref for Struct {
+ type Target = String;
+
+ fn deref(&self) -> &Self::Target {
+ &self.bar
+ }
+}
+
+fn main() {}
diff --git a/crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/multiple_attributes.fail.stderr b/crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/multiple_attributes.fail.stderr
new file mode 100644
index 0000000000000..3d4cabca91b1f
--- /dev/null
+++ b/crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/multiple_attributes.fail.stderr
@@ -0,0 +1,11 @@
+error: `#[deref]` attribute can only be used on a single field
+ --> tests/deref_mut_derive/multiple_attributes.fail.rs:5:36
+ |
+5 | struct TupleStruct(#[deref] usize, #[deref] String);
+ | ^^^^^^^^
+
+error: `#[deref]` attribute can only be used on a single field
+ --> tests/deref_mut_derive/multiple_attributes.fail.rs:19:5
+ |
+19 | #[deref]
+ | ^^^^^^^^
diff --git a/crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/multiple_fields.pass.rs b/crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/multiple_fields.pass.rs
new file mode 100644
index 0000000000000..c05dc808c120d
--- /dev/null
+++ b/crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/multiple_fields.pass.rs
@@ -0,0 +1,39 @@
+use bevy_derive::DerefMut;
+use std::ops::Deref;
+
+#[derive(DerefMut)]
+struct TupleStruct(usize, #[deref] String);
+
+impl Deref for TupleStruct {
+ type Target = String;
+
+ fn deref(&self) -> &Self::Target {
+ &self.1
+ }
+}
+
+#[derive(DerefMut)]
+struct Struct {
+ foo: usize,
+ #[deref]
+ bar: String,
+}
+
+impl Deref for Struct {
+ type Target = String;
+
+ fn deref(&self) -> &Self::Target {
+ &self.bar
+ }
+}
+
+fn main() {
+ let mut value = TupleStruct(123, "Hello world!".to_string());
+ let _: &mut String = &mut *value;
+
+ let mut value = Struct {
+ foo: 123,
+ bar: "Hello world!".to_string(),
+ };
+ let _: &mut String = &mut *value;
+}
diff --git a/crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/single_field.pass.rs b/crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/single_field.pass.rs
new file mode 100644
index 0000000000000..42cbda8436fac
--- /dev/null
+++ b/crates/bevy_macros_compile_fail_tests/tests/deref_mut_derive/single_field.pass.rs
@@ -0,0 +1,37 @@
+use bevy_derive::DerefMut;
+use std::ops::Deref;
+
+#[derive(DerefMut)]
+struct TupleStruct(#[deref] String);
+
+impl Deref for TupleStruct {
+ type Target = String;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+#[derive(DerefMut)]
+struct Struct {
+ #[deref]
+ bar: String,
+}
+
+impl Deref for Struct {
+ type Target = String;
+
+ fn deref(&self) -> &Self::Target {
+ &self.bar
+ }
+}
+
+fn main() {
+ let mut value = TupleStruct("Hello world!".to_string());
+ let _: &mut String = &mut *value;
+
+ let mut value = Struct {
+ bar: "Hello world!".to_string(),
+ };
+ let _: &mut String = &mut *value;
+}
diff --git a/tools/ci/src/main.rs b/tools/ci/src/main.rs
index e8c1608ba8c17..c0105ed54312e 100644
--- a/tools/ci/src/main.rs
+++ b/tools/ci/src/main.rs
@@ -106,6 +106,15 @@ fn main() {
.run()
.expect("Compiler errors of the Reflect compile fail tests seem to be different than expected! Check locally and compare rust versions.");
}
+ {
+ // Macro Compile Fail Tests
+ // Run tests (they do not get executed with the workspace tests)
+ // - See crates/bevy_macros_compile_fail_tests/README.md
+ let _subdir = sh.push_dir("crates/bevy_macros_compile_fail_tests");
+ cmd!(sh, "cargo test --target-dir ../../target")
+ .run()
+ .expect("Compiler errors of the macros compile fail tests seem to be different than expected! Check locally and compare rust versions.");
+ }
}
if what_to_run.contains(Check::TEST) {