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:

<table align="center">
<tr>
<th colspan="2">
Similar structs, different derefs
</th>
</tr>
<tr>
<td>

```rust
#[derive(Deref, DerefMut)]
struct MyStruct {
  foo: usize, // <- Derefs usize
  bar: String,
}
```

</td>
<td>

```rust
#[derive(Deref, DerefMut)]
struct MyStruct {
  bar: String, // <- Derefs String
  foo: usize,
}
```

</td>
</tr>
<tr>
<th colspan="2">
Why `.1`?
</th>
</tr>
<tr>
<td colspan="2">

```rust
#[derive(Deref, DerefMut)]
struct MyStruct(Vec<usize>, Vec<f32>);

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);
```

</td>
</tr>
</table>

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<T>(
  // 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<T>
);

// 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<T>(
  // Use this field for the `Deref`/`DerefMut` impls
  #[deref] String,
  // We can freely include any other field without a compile error
  PhantomData<T>
);
```

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
This commit is contained in:
Gino Valente 2023-05-16 11:29:09 -07:00 committed by GitHub
parent 1da726e046
commit 56686a8962
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 696 additions and 22 deletions

View file

@ -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 = [

View file

@ -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)))
}

View file

@ -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<T>(#[deref] String, PhantomData<T>);
///
/// let foo = MyStruct(String::from("Hello"), PhantomData::<usize>);
/// 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<T> {
/// #[deref]
/// value: String,
/// _phantom: PhantomData<T>,
/// }
///
/// let foo = MyStruct {
/// value:String::from("Hello"),
/// _phantom:PhantomData::<usize>
/// };
/// 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<T>(#[deref] String, PhantomData<T>);
///
/// let mut foo = MyStruct(String::from("Hello"), PhantomData::<usize>);
/// 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<T> {
/// #[deref]
/// value: String,
/// _phantom: PhantomData<T>,
/// }
///
/// let mut foo = MyStruct {
/// value:String::from("Hello"),
/// _phantom:PhantomData::<usize>
/// };
/// 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)
}

View file

@ -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"

View file

@ -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)).

View file

@ -0,0 +1 @@
// Nothing here, check out the integration tests

View file

@ -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");
}

View file

@ -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() {}

View file

@ -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()]
| ^

View file

@ -0,0 +1,9 @@
use bevy_derive::Deref;
#[derive(Deref)]
struct UnitStruct;
#[derive(Deref)]
enum Enum {}
fn main() {}

View file

@ -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)

View file

@ -0,0 +1,12 @@
use bevy_derive::Deref;
#[derive(Deref)]
struct TupleStruct(usize, String);
#[derive(Deref)]
struct Struct {
foo: usize,
bar: String,
}
fn main() {}

View file

@ -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)

View file

@ -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() {}

View file

@ -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]
| ^^^^^^^^

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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");
}

View file

@ -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() {}

View file

@ -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()]
| ^

View file

@ -0,0 +1,9 @@
use bevy_derive::DerefMut;
#[derive(DerefMut)]
struct UnitStruct;
#[derive(DerefMut)]
enum Enum {}
fn main() {}

View file

@ -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)

View file

@ -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() {}

View file

@ -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)

View file

@ -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() {}

View file

@ -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)

View file

@ -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() {}

View file

@ -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)

View file

@ -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() {}

View file

@ -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]
| ^^^^^^^^

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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) {