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