Skip to content

Commit

Permalink
Add #[deref] attribute to Deref/DerefMut derives
Browse files Browse the repository at this point in the history
  • Loading branch information
MrGVSV committed May 5, 2023
1 parent a616fa8 commit b086cb5
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 21 deletions.
75 changes: 60 additions & 15 deletions crates/bevy_derive/src/derefs.rs
Original file line number Diff line number Diff line change
@@ -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();
Expand All @@ -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();
Expand All @@ -46,24 +50,65 @@ 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.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.path.is_ident(DEREF_ATTR) {
continue;
}

if !attr.tokens.is_empty() {
return Err(syn::Error::new_spanned(
attr,
format!("{deref_attr_str} attribute does not take any arguments"),
));
}

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)))
}
47 changes: 41 additions & 6 deletions crates/bevy_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,18 @@ 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
///
/// Using a single-field struct:
///
/// ```
/// use bevy_derive::Deref;
///
Expand All @@ -35,23 +39,40 @@ pub fn derive_dynamic_plugin(input: TokenStream) -> TokenStream {
/// assert_eq!(5, foo.len());
/// ```
///
/// 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!(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)]
#[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
///
/// Using a single-field struct:
///
/// ```
/// use bevy_derive::{Deref, DerefMut};
///
Expand All @@ -63,11 +84,25 @@ 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);
/// ```
///
/// [`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)
}
Expand Down

0 comments on commit b086cb5

Please sign in to comment.