diff --git a/crates/bevy_derive/src/derefs.rs b/crates/bevy_derive/src/derefs.rs new file mode 100644 index 00000000000000..559b22be19c4cb --- /dev/null +++ b/crates/bevy_derive/src/derefs.rs @@ -0,0 +1,69 @@ +use proc_macro::{Span, TokenStream}; +use quote::quote; +use syn::{parse_macro_input, Data, DeriveInput, Index, Member, Type}; + +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) { + Ok(items) => items, + Err(err) => { + return err.into_compile_error().into(); + } + }; + let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); + + TokenStream::from(quote! { + impl #impl_generics ::std::ops::Deref for #ident #ty_generics #where_clause { + type Target = #field_type; + + fn deref(&self) -> &Self::Target { + &self.#field_member + } + } + }) +} + +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) { + Ok(items) => items, + Err(err) => { + return err.into_compile_error().into(); + } + }; + let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); + + TokenStream::from(quote! { + impl #impl_generics ::std::ops::DerefMut for #ident #ty_generics #where_clause { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.#field_member + } + } + }) +} + +fn get_inner_field(ast: &DeriveInput, is_mut: bool) -> syn::Result<(Member, &Type)> { + match &ast.data { + 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))); + Ok((member, &field.ty)) + } + _ => { + let msg = if is_mut { + "DerefMut can only be derived for structs with a single field" + } else { + "Deref can only be derived for structs with a single field" + }; + Err(syn::Error::new(Span::call_site().into(), msg)) + } + } +} diff --git a/crates/bevy_derive/src/lib.rs b/crates/bevy_derive/src/lib.rs index cd0da4668e792e..929eaf67b7bb15 100644 --- a/crates/bevy_derive/src/lib.rs +++ b/crates/bevy_derive/src/lib.rs @@ -2,6 +2,7 @@ extern crate proc_macro; mod app_plugin; mod bevy_main; +mod derefs; mod enum_variant_meta; mod modules; @@ -15,6 +16,61 @@ 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. +/// +/// If you need [`DerefMut`] as well, consider using the other [derive] macro alongside +/// this one. +/// +/// # Example +/// +/// ``` +/// use bevy_derive::Deref; +/// +/// #[derive(Deref)] +/// struct MyNewtype(String); +/// +/// let foo = MyNewtype(String::from("Hello")); +/// assert_eq!(5, foo.len()); +/// ``` +/// +/// [`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)] +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. +/// +/// [`DerefMut`] requires a [`Deref`] implementation. You can implement it manually or use +/// Bevy's [derive] macro for convenience. +/// +/// # Example +/// +/// ``` +/// use bevy_derive::{Deref, DerefMut}; +/// +/// #[derive(Deref, DerefMut)] +/// struct MyNewtype(String); +/// +/// let mut foo = MyNewtype(String::from("Hello")); +/// 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)] +pub fn derive_deref_mut(input: TokenStream) -> TokenStream { + derefs::derive_deref_mut(input) +} + #[proc_macro_attribute] pub fn bevy_main(attr: TokenStream, item: TokenStream) -> TokenStream { bevy_main::bevy_main(attr, item) diff --git a/crates/bevy_internal/src/prelude.rs b/crates/bevy_internal/src/prelude.rs index 148d245beed45a..27a11eb2662b1b 100644 --- a/crates/bevy_internal/src/prelude.rs +++ b/crates/bevy_internal/src/prelude.rs @@ -5,7 +5,7 @@ pub use crate::{ transform::prelude::*, utils::prelude::*, window::prelude::*, DefaultPlugins, MinimalPlugins, }; -pub use bevy_derive::bevy_main; +pub use bevy_derive::{bevy_main, Deref, DerefMut}; #[doc(hidden)] #[cfg(feature = "bevy_audio")] diff --git a/examples/2d/contributors.rs b/examples/2d/contributors.rs index e4132d299dd3c4..3491a11482aeb1 100644 --- a/examples/2d/contributors.rs +++ b/examples/2d/contributors.rs @@ -27,6 +27,7 @@ struct ContributorSelection { idx: usize, } +#[derive(Deref, DerefMut)] struct SelectTimer(Timer); #[derive(Component)] @@ -161,7 +162,7 @@ fn select_system( mut query: Query<(&Contributor, &mut Sprite, &mut Transform)>, time: Res