Skip to content
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

bevy_reflect: Type aliases #5830

Closed
wants to merge 14 commits into from
150 changes: 150 additions & 0 deletions crates/bevy_app/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use bevy_ecs::{
world::World,
};
use bevy_utils::{tracing::debug, HashMap};
use std::borrow::Cow;
use std::fmt::Debug;

#[cfg(feature = "trace")]
Expand Down Expand Up @@ -923,6 +924,155 @@ impl App {
self
}

/// Register an alias for the given type, `T`, in the [`TypeRegistry`] resource.
///
/// This will implicitly overwrite existing usages of the given alias and print a warning to the console if it does so.
///
/// To register the alias only if it isn't already in use, try using [`try_register_type_alias`].
/// Otherwise, to explicitly overwrite existing aliases without the warning, try using [`overwrite_type_alias`].
///
/// If an alias was overwritten, then the [`TypeId`] of the previous type is returned.
///
/// [`TypeRegistry`]: bevy_reflect::TypeRegistry
/// [`try_register_type_alias`]: Self::try_register_type_alias
/// [`overwrite_type_alias`]: Self::overwrite_type_alias
/// [`TypeId`]: std::any::TypeId
#[cfg(feature = "bevy_reflect")]
pub fn register_type_alias<T: bevy_reflect::Reflect>(
&mut self,
alias: impl Into<Cow<'static, str>>,
) -> &mut Self {
{
let registry = self.world.resource_mut::<AppTypeRegistry>();
registry.write().register_alias::<T>(alias);
}
self
}

/// Register a _deprecated_ alias for the given type, `T`, in the [`TypeRegistry`] resource.
///
/// To register an alias that isn't marked as deprecated, use [`register_type_alias`].
///
/// This will implicitly overwrite existing usages of the given alias and print a warning to the console if it does so.
///
/// To register the alias only if it isn't already in use, try using [`try_register_type_alias`].
/// Otherwise, to explicitly overwrite existing aliases without the warning, try using [`overwrite_type_alias`].
///
/// If an alias was overwritten, then the [`TypeId`] of the previous type is returned.
///
/// [`TypeRegistry`]: bevy_reflect::TypeRegistry
/// [`register_type_alias`]: Self::register_type_alias
/// [`try_register_type_alias`]: Self::try_register_type_alias
/// [`overwrite_type_alias`]: Self::overwrite_type_alias
/// [`TypeId`]: std::any::TypeId
#[cfg(feature = "bevy_reflect")]
pub fn register_deprecated_type_alias<T: bevy_reflect::Reflect>(
&mut self,
alias: impl Into<Cow<'static, str>>,
) -> &mut Self {
{
let registry = self.world.resource_mut::<AppTypeRegistry>();
registry.write().register_deprecated_alias::<T>(alias);
}
self
}

/// Attempts to register an alias for the given type, `T`, in the [`TypeRegistry`] resource if it isn't already in use.
///
/// To register the alias whether or not it exists, try using either [`register_type_alias`] or [`overwrite_type_alias`].
///
/// If the given alias is already in use, then the [`TypeId`] of that type is returned.
///
/// [`TypeRegistry`]: bevy_reflect::TypeRegistry
/// [`register_type_alias`]: Self::register_type_alias
/// [`overwrite_type_alias`]: Self::overwrite_type_alias
/// [`TypeId`]: std::any::TypeId
#[cfg(feature = "bevy_reflect")]
pub fn try_register_type_alias<T: bevy_reflect::Reflect>(
&mut self,
alias: impl Into<Cow<'static, str>>,
) -> &mut Self {
{
let registry = self.world.resource_mut::<AppTypeRegistry>();
registry.write().try_register_alias::<T>(alias);
}
self
}

/// Attempts to register a _deprecated_ alias for the given type, `T`, in the [`TypeRegistry`] resource if it isn't already in use.
///
/// To try and register an alias that isn't marked as deprecated, use [`try_register_type_alias`].
///
/// To register the alias whether or not it exists, try using either [`register_deprecated_type_alias`] or [`overwrite_deprecated_type_alias`].
///
/// If the given alias is already in use, then the [`TypeId`] of that type is returned.
///
/// [`TypeRegistry`]: bevy_reflect::TypeRegistry
/// [`try_register_type_alias`]: Self::try_register_type_alias
/// [`register_deprecated_type_alias`]: Self::register_deprecated_type_alias
/// [`overwrite_deprecated_type_alias`]: Self::overwrite_deprecated_type_alias
/// [`TypeId`]: std::any::TypeId
#[cfg(feature = "bevy_reflect")]
pub fn try_register_deprecated_type_alias<T: bevy_reflect::Reflect>(
&mut self,
alias: impl Into<Cow<'static, str>>,
) -> &mut Self {
{
let registry = self.world.resource_mut::<AppTypeRegistry>();
registry.write().try_register_deprecated_alias::<T>(alias);
}
self
}

/// Register an alias for the given type, `T`, in the [`TypeRegistry`] resource, explicitly overwriting existing aliases.
///
/// Unlike, [`register_type_alias`], this does not print a warning when overwriting existing aliases.
///
/// To register the alias only if it isn't already in use, try using [`try_register_type_alias`].
///
/// If an alias was overwritten, then the [`TypeId`] of the previous type is returned.
/// [`TypeRegistry`]: bevy_reflect::TypeRegistry
/// [`register_type_alias`]: Self::register_type_alias
/// [`try_register_type_alias`]: Self::try_register_type_alias
/// [`TypeId`]: std::any::TypeId
#[cfg(feature = "bevy_reflect")]
pub fn overwrite_type_alias<T: bevy_reflect::Reflect>(
&mut self,
alias: impl Into<Cow<'static, str>>,
) -> &mut Self {
{
let registry = self.world.resource_mut::<AppTypeRegistry>();
registry.write().overwrite_alias::<T>(alias);
}
self
}

/// Register a _deprecated_ alias for the given type, `T`, in the [`TypeRegistry`] resource, explicitly overwriting existing aliases.
///
/// To register an alias that isn't marked as deprecated, use [`overwrite_type_alias`].
///
/// Unlike, [`register_type_alias`], this does not print a warning when overwriting existing aliases.
///
/// To register the alias only if it isn't already in use, try using [`try_register_type_alias`].
///
/// If an alias was overwritten, then the [`TypeId`] of the previous type is returned.
/// [`TypeRegistry`]: bevy_reflect::TypeRegistry
/// [`overwrite_type_alias`]: Self::overwrite_type_alias
/// [`register_type_alias`]: Self::register_type_alias
/// [`try_register_type_alias`]: Self::try_register_type_alias
/// [`TypeId`]: std::any::TypeId
#[cfg(feature = "bevy_reflect")]
pub fn overwrite_deprecated_type_alias<T: bevy_reflect::Reflect>(
&mut self,
alias: impl Into<Cow<'static, str>>,
) -> &mut Self {
{
let registry = self.world.resource_mut::<AppTypeRegistry>();
registry.write().overwrite_deprecated_alias::<T>(alias);
}
self
}

/// Adds an [`App`] as a child of the current one.
///
/// The provided function `f` is called by the [`update`](Self::update) method. The [`World`]
Expand Down
114 changes: 103 additions & 11 deletions crates/bevy_reflect/bevy_reflect_derive/src/container_attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
use crate::utility;
use proc_macro2::Ident;
use quote::quote;
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::token::Comma;
use syn::{Meta, NestedMeta, Path};
use syn::{Lit, LitStr, Meta, NestedMeta, Path};

// The "special" trait idents that are used internally for reflection.
// Received via attributes like `#[reflect(PartialEq, Hash, ...)]`
Expand All @@ -23,6 +23,10 @@ const HASH_ATTR: &str = "Hash";
// but useful to know exist nonetheless
pub(crate) const REFLECT_DEFAULT: &str = "ReflectDefault";

// Other container attribute idents
const ALIAS_ATTR: &str = "alias";
const DEPRECATED_ALIAS_ATTR: &str = "deprecated_alias";

/// A marker for trait implementations registered via the `Reflect` derive macro.
#[derive(Clone, Default)]
pub(crate) enum TraitImpl {
Expand Down Expand Up @@ -103,11 +107,16 @@ pub(crate) struct ReflectTraits {
hash: TraitImpl,
partial_eq: TraitImpl,
idents: Vec<Ident>,
aliases: Vec<LitStr>,
deprecated_aliases: Vec<LitStr>,
}

impl ReflectTraits {
/// Create a new [`ReflectTraits`] instance from a set of nested metas.
pub fn from_nested_metas(nested_metas: &Punctuated<NestedMeta, Comma>) -> Self {
pub fn from_nested_metas(
nested_metas: &Punctuated<NestedMeta, Comma>,
is_generic: bool,
) -> syn::Result<Self> {
let mut traits = ReflectTraits::default();
for nested_meta in nested_metas.iter() {
match nested_meta {
Expand Down Expand Up @@ -151,11 +160,93 @@ impl ReflectTraits {
}
}
}
// Handles `#[reflect( alias = "Foo" )]`
NestedMeta::Meta(Meta::NameValue(pair)) => {
let ident = pair
.path
.get_ident()
.ok_or_else(|| syn::Error::new(pair.span(), "not a valid path"))?;

// Closure that handles defining an alias on a generic type
let try_handle_generic_alias = || -> syn::Result<()> {
if is_generic {
Err(syn::Error::new(ident.span(), "alias attributes cannot be used on generic types. Consider using `TypeRegistry::register_alias` instead"))
} else {
Ok(())
}
};

let attr_name = ident.to_string();
match (attr_name.as_str(), &pair.lit) {
(ALIAS_ATTR, Lit::Str(alias)) => {
try_handle_generic_alias()?;
traits.aliases.push(alias.clone());
}
(DEPRECATED_ALIAS_ATTR, Lit::Str(alias)) => {
try_handle_generic_alias()?;
traits.deprecated_aliases.push(alias.clone());
}
(DEPRECATED_ALIAS_ATTR | ALIAS_ATTR, lit) => {
try_handle_generic_alias()?;
return Err(syn::Error::new(lit.span(), "expected a string literal"));
}
(_, _) => {
return Err(syn::Error::new(
ident.span(),
format_args!(
"unknown attribute `{}`, expected one of {:?}",
attr_name,
[ALIAS_ATTR, DEPRECATED_ALIAS_ATTR]
),
));
}
}
}
_ => {}
}
}

traits
Ok(traits)
}

pub fn combine(&mut self, other: Self) {
if matches!(self.debug, TraitImpl::NotImplemented) {
self.debug = other.debug;
}
if matches!(self.hash, TraitImpl::NotImplemented) {
self.hash = other.hash;
}
if matches!(self.partial_eq, TraitImpl::NotImplemented) {
self.partial_eq = other.partial_eq;
}

for ident in other.idents {
if !self.idents.contains(&ident) {
self.idents.push(ident);
}
}

for alias in other.aliases {
let value = alias.value();
if self
.aliases
.iter()
.all(|other_alias| value != other_alias.value())
{
self.aliases.push(alias);
}
}

for alias in other.deprecated_aliases {
let value = alias.value();
if self
.deprecated_aliases
.iter()
.all(|other_alias| value != other_alias.value())
{
self.deprecated_aliases.push(alias);
}
}
}

/// Returns true if the given reflected trait name (i.e. `ReflectDefault` for `Default`)
Expand All @@ -169,6 +260,14 @@ impl ReflectTraits {
&self.idents
}

pub fn aliases(&self) -> &[LitStr] {
&self.aliases
}

pub fn deprecated_aliases(&self) -> &[LitStr] {
&self.deprecated_aliases
}

/// Returns the implementation of `Reflect::reflect_hash` as a `TokenStream`.
///
/// If `Hash` was not registered, returns `None`.
Expand Down Expand Up @@ -238,10 +337,3 @@ impl ReflectTraits {
}
}
}

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))
}
}
30 changes: 21 additions & 9 deletions crates/bevy_reflect/bevy_reflect_derive/src/derive_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ impl<'a> ReflectDerive<'a> {
// Should indicate whether `#[reflect_value]` was used
let mut force_reflect_value = false;

let is_generic = utility::is_generic(&input.generics, false);

for attribute in input.attrs.iter().filter_map(|attr| attr.parse_meta().ok()) {
let meta_list = if let Meta::List(meta_list) = attribute {
meta_list
Expand All @@ -119,10 +121,25 @@ impl<'a> ReflectDerive<'a> {

if let Some(ident) = meta_list.path.get_ident() {
if ident == REFLECT_ATTRIBUTE_NAME {
traits = ReflectTraits::from_nested_metas(&meta_list.nested);
if force_reflect_value {
force_reflect_value = false;
traits = ReflectTraits::from_nested_metas(&meta_list.nested, is_generic)?;
} else {
traits.combine(ReflectTraits::from_nested_metas(
&meta_list.nested,
is_generic,
)?);
}
} else if ident == REFLECT_VALUE_ATTRIBUTE_NAME {
force_reflect_value = true;
traits = ReflectTraits::from_nested_metas(&meta_list.nested);
if !force_reflect_value {
force_reflect_value = true;
traits = ReflectTraits::from_nested_metas(&meta_list.nested, is_generic)?;
} else {
traits.combine(ReflectTraits::from_nested_metas(
&meta_list.nested,
is_generic,
)?);
}
}
}
}
Expand Down Expand Up @@ -242,12 +259,7 @@ impl<'a> ReflectMeta<'a> {

/// Returns the `GetTypeRegistration` impl as a `TokenStream`.
pub fn get_type_registration(&self) -> proc_macro2::TokenStream {
crate::registration::impl_get_type_registration(
self.type_name,
&self.bevy_reflect_path,
self.traits.idents(),
self.generics,
)
crate::registration::impl_get_type_registration(self)
}
}

Expand Down
Loading