-
-
Notifications
You must be signed in to change notification settings - Fork 4.3k
[Merged by Bors] - bevy_reflect_derive: Tidying up the code #4712
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
0c65b87
4c3f1d6
bd57543
c513157
e7604cf
226405f
9708d36
a15af0e
f5c0e10
f5d651c
768750b
0c70b78
1538bf9
a4d88ad
86d0b42
7b32e97
7e2fcbe
c72b56f
e77f20f
ca2b137
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,234 @@ | ||
| //! Contains code related to container attributes for reflected types. | ||
| //! | ||
| //! A container attribute is an attribute which applies to an entire struct or enum | ||
| //! as opposed to a particular field or variant. An example of such an attribute is | ||
| //! the derive helper attribute for `Reflect`, which looks like: | ||
| //! `#[reflect(PartialEq, Default, ...)]` and `#[reflect_value(PartialEq, Default, ...)]`. | ||
|
|
||
| use crate::utility; | ||
| use proc_macro2::Ident; | ||
| use quote::quote; | ||
| use syn::parse::{Parse, ParseStream}; | ||
| use syn::punctuated::Punctuated; | ||
| use syn::token::Comma; | ||
| use syn::{Meta, NestedMeta, Path}; | ||
|
|
||
| // The "special" trait idents that are used internally for reflection. | ||
| // Received via attributes like `#[reflect(PartialEq, Hash, ...)]` | ||
| const PARTIAL_EQ_ATTR: &str = "PartialEq"; | ||
| const HASH_ATTR: &str = "Hash"; | ||
| const SERIALIZE_ATTR: &str = "Serialize"; | ||
|
|
||
| /// A marker for trait implementations registered via the `Reflect` derive macro. | ||
| #[derive(Clone)] | ||
| pub(crate) enum TraitImpl { | ||
| /// The trait is not registered as implemented. | ||
| NotImplemented, | ||
| /// The trait is registered as implemented. | ||
| Implemented, | ||
|
|
||
| // TODO: This can be made to use `ExprPath` instead of `Ident`, allowing for fully qualified paths to be used | ||
| /// The trait is registered with a custom function rather than an actual implementation. | ||
| Custom(Ident), | ||
| } | ||
|
|
||
| impl Default for TraitImpl { | ||
| fn default() -> Self { | ||
| Self::NotImplemented | ||
| } | ||
| } | ||
|
|
||
| /// A collection of traits that have been registered for a reflected type. | ||
| /// | ||
| /// This keeps track of a few traits that are utilized internally for reflection | ||
| /// (we'll call these traits _special traits_ within this context), but it | ||
| /// will also keep track of all registered traits. Traits are registered as part of the | ||
| /// `Reflect` derive macro using the helper attribute: `#[reflect(...)]`. | ||
| /// | ||
| /// The list of special traits are as follows: | ||
| /// * `Hash` | ||
| /// * `PartialEq` | ||
| /// * `Serialize` | ||
| /// | ||
| /// When registering a trait, there are a few things to keep in mind: | ||
| /// * Traits must have a valid `Reflect{}` struct in scope. For example, `Default` | ||
| /// needs `bevy_reflect::prelude::ReflectDefault` in scope. | ||
| /// * Traits must be single path identifiers. This means you _must_ use `Default` | ||
| /// instead of `std::default::Default` (otherwise it will try to register `Reflectstd`!) | ||
| /// * A custom function may be supplied in place of an actual implementation | ||
| /// for the special traits (but still follows the same single-path identifier | ||
| /// rules as normal). | ||
| /// | ||
| /// # Example | ||
| /// | ||
| /// Registering the `Default` implementation: | ||
| /// | ||
| /// ```ignore | ||
| /// // Import ReflectDefault so it's accessible by the derive macro | ||
| /// use bevy_reflect::prelude::ReflectDefault. | ||
| /// | ||
| /// #[derive(Reflect, Default)] | ||
| /// #[reflect(Default)] | ||
| /// struct Foo; | ||
| /// ``` | ||
| /// | ||
| /// Registering the `Hash` implementation: | ||
| /// | ||
| /// ```ignore | ||
| /// // `Hash` is a "special trait" and does not need (nor have) a ReflectHash struct | ||
| /// | ||
| /// #[derive(Reflect, Hash)] | ||
| /// #[reflect(Hash)] | ||
| /// struct Foo; | ||
| /// ``` | ||
| /// | ||
| /// Registering the `Hash` implementation using a custom function: | ||
| /// | ||
| /// ```ignore | ||
| /// // This function acts as our `Hash` implementation and | ||
| /// // corresponds to the `Reflect::reflect_hash` method. | ||
| /// fn get_hash(foo: &Foo) -> Option<u64> { | ||
| /// Some(123) | ||
| /// } | ||
| /// | ||
| /// #[derive(Reflect)] | ||
| /// // Register the custom `Hash` function | ||
| /// #[reflect(Hash(get_hash))] | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm wondering if it would make sense to add Default as a special trait- it definitely has special behavior, and allowing a special custom function for it like this would be nice.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That makes sense to me. It would involve adding a But yeah that's probably best for a follow up PR (maybe even in #4140).
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like this in #4140 at first glance. |
||
| /// struct Foo; | ||
| /// ``` | ||
| /// | ||
| /// > __Note:__ Registering a custom function only works for special traits. | ||
| /// | ||
| #[derive(Default)] | ||
| pub(crate) struct ReflectTraits { | ||
| hash: TraitImpl, | ||
| partial_eq: TraitImpl, | ||
| serialize: TraitImpl, | ||
| idents: Vec<Ident>, | ||
| } | ||
|
|
||
| impl ReflectTraits { | ||
| /// Create a new [`ReflectTraits`] instance from a set of nested metas. | ||
| pub fn from_nested_metas(nested_metas: &Punctuated<NestedMeta, Comma>) -> Self { | ||
| let mut traits = ReflectTraits::default(); | ||
| for nested_meta in nested_metas.iter() { | ||
| match nested_meta { | ||
| // Handles `#[reflect( Hash, Default, ... )]` | ||
| NestedMeta::Meta(Meta::Path(path)) => { | ||
| // Get the first ident in the path (hopefully the path only contains one and not `std::hash::Hash`) | ||
| let ident = if let Some(segment) = path.segments.iter().next() { | ||
| segment.ident.to_string() | ||
| } else { | ||
| continue; | ||
| }; | ||
|
|
||
| match ident.as_str() { | ||
| PARTIAL_EQ_ATTR => traits.partial_eq = TraitImpl::Implemented, | ||
| HASH_ATTR => traits.hash = TraitImpl::Implemented, | ||
| SERIALIZE_ATTR => traits.serialize = TraitImpl::Implemented, | ||
| // We only track reflected idents for traits not considered special | ||
| _ => traits.idents.push(utility::get_reflect_ident(&ident)), | ||
| } | ||
| } | ||
| // Handles `#[reflect( Hash(custom_hash_fn) )]` | ||
| NestedMeta::Meta(Meta::List(list)) => { | ||
| // Get the first ident in the path (hopefully the path only contains one and not `std::hash::Hash`) | ||
| let ident = if let Some(segment) = list.path.segments.iter().next() { | ||
| segment.ident.to_string() | ||
| } else { | ||
| continue; | ||
| }; | ||
|
|
||
| let list_meta = list.nested.iter().next(); | ||
| if let Some(NestedMeta::Meta(Meta::Path(path))) = list_meta { | ||
| if let Some(segment) = path.segments.iter().next() { | ||
| // This should be the ident of the custom function | ||
| let trait_func_ident = TraitImpl::Custom(segment.ident.clone()); | ||
| match ident.as_str() { | ||
| PARTIAL_EQ_ATTR => traits.partial_eq = trait_func_ident, | ||
| HASH_ATTR => traits.hash = trait_func_ident, | ||
| SERIALIZE_ATTR => traits.serialize = trait_func_ident, | ||
| _ => {} | ||
| } | ||
| } | ||
| } | ||
| } | ||
| _ => {} | ||
| } | ||
| } | ||
|
|
||
| traits | ||
| } | ||
|
|
||
| /// Returns true if the given reflected trait name (i.e. `ReflectDefault` for `Default`) | ||
| /// is registered for this type. | ||
| pub fn contains(&self, name: &str) -> bool { | ||
| self.idents.iter().any(|ident| ident == name) | ||
| } | ||
|
|
||
| /// The list of reflected traits by their reflected ident (i.e. `ReflectDefault` for `Default`). | ||
| pub fn idents(&self) -> &[Ident] { | ||
| &self.idents | ||
| } | ||
|
|
||
| /// Returns the logic for `Reflect::reflect_hash` as a `TokenStream`. | ||
| /// | ||
| /// If `Hash` was not registered, returns `None`. | ||
| pub fn get_hash_impl(&self, path: &Path) -> Option<proc_macro2::TokenStream> { | ||
| match &self.hash { | ||
| TraitImpl::Implemented => Some(quote! { | ||
| use std::hash::{Hash, Hasher}; | ||
| let mut hasher = #path::ReflectHasher::default(); | ||
| Hash::hash(&std::any::Any::type_id(self), &mut hasher); | ||
| Hash::hash(self, &mut hasher); | ||
| Some(hasher.finish()) | ||
| }), | ||
| TraitImpl::Custom(impl_fn) => Some(quote! { | ||
| Some(#impl_fn(self)) | ||
| }), | ||
| TraitImpl::NotImplemented => None, | ||
| } | ||
| } | ||
|
|
||
| /// Returns the logic for `Reflect::reflect_partial_eq` as a `TokenStream`. | ||
| /// | ||
| /// If `PartialEq` was not registered, returns `None`. | ||
| pub fn get_partial_eq_impl(&self) -> Option<proc_macro2::TokenStream> { | ||
| match &self.partial_eq { | ||
| TraitImpl::Implemented => Some(quote! { | ||
| let value = value.any(); | ||
| if let Some(value) = value.downcast_ref::<Self>() { | ||
| Some(std::cmp::PartialEq::eq(self, value)) | ||
| } else { | ||
| Some(false) | ||
| } | ||
| }), | ||
| TraitImpl::Custom(impl_fn) => Some(quote! { | ||
| Some(#impl_fn(self, value)) | ||
| }), | ||
| TraitImpl::NotImplemented => None, | ||
| } | ||
| } | ||
|
|
||
| /// Returns the logic for `Reflect::serializable` as a `TokenStream`. | ||
| /// | ||
| /// If `Serialize` was not registered, returns `None`. | ||
| pub fn get_serialize_impl(&self, path: &Path) -> Option<proc_macro2::TokenStream> { | ||
| match &self.serialize { | ||
| TraitImpl::Implemented => Some(quote! { | ||
| Some(#path::serde::Serializable::Borrowed(self)) | ||
| }), | ||
| TraitImpl::Custom(impl_fn) => Some(quote! { | ||
| Some(#impl_fn(self)) | ||
| }), | ||
| TraitImpl::NotImplemented => None, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| impl Parse for ReflectTraits { | ||
| fn parse(input: ParseStream) -> syn::Result<Self> { | ||
| let result = Punctuated::<NestedMeta, Comma>::parse_terminated(input)?; | ||
| Ok(ReflectTraits::from_nested_metas(&result)) | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.