diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 7d6c027da0cdf..fa48b6ef86e2c 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -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")] @@ -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( + &mut self, + alias: impl Into>, + ) -> &mut Self { + { + let registry = self.world.resource_mut::(); + registry.write().register_alias::(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( + &mut self, + alias: impl Into>, + ) -> &mut Self { + { + let registry = self.world.resource_mut::(); + registry.write().register_deprecated_alias::(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( + &mut self, + alias: impl Into>, + ) -> &mut Self { + { + let registry = self.world.resource_mut::(); + registry.write().try_register_alias::(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( + &mut self, + alias: impl Into>, + ) -> &mut Self { + { + let registry = self.world.resource_mut::(); + registry.write().try_register_deprecated_alias::(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( + &mut self, + alias: impl Into>, + ) -> &mut Self { + { + let registry = self.world.resource_mut::(); + registry.write().overwrite_alias::(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( + &mut self, + alias: impl Into>, + ) -> &mut Self { + { + let registry = self.world.resource_mut::(); + registry.write().overwrite_deprecated_alias::(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`] diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/container_attributes.rs b/crates/bevy_reflect/bevy_reflect_derive/src/container_attributes.rs index 99513ed3acbd4..d111095a133c0 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/container_attributes.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/container_attributes.rs @@ -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, ...)]` @@ -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 { @@ -103,11 +107,16 @@ pub(crate) struct ReflectTraits { hash: TraitImpl, partial_eq: TraitImpl, idents: Vec, + aliases: Vec, + deprecated_aliases: Vec, } impl ReflectTraits { /// Create a new [`ReflectTraits`] instance from a set of nested metas. - pub fn from_nested_metas(nested_metas: &Punctuated) -> Self { + pub fn from_nested_metas( + nested_metas: &Punctuated, + is_generic: bool, + ) -> syn::Result { let mut traits = ReflectTraits::default(); for nested_meta in nested_metas.iter() { match nested_meta { @@ -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`) @@ -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`. @@ -238,10 +337,3 @@ impl ReflectTraits { } } } - -impl Parse for ReflectTraits { - fn parse(input: ParseStream) -> syn::Result { - let result = Punctuated::::parse_terminated(input)?; - Ok(ReflectTraits::from_nested_metas(&result)) - } -} diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/derive_data.rs b/crates/bevy_reflect/bevy_reflect_derive/src/derive_data.rs index 422809d6db804..ca48cf0c8cfa3 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/derive_data.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/derive_data.rs @@ -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 @@ -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, + )?); + } } } } @@ -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) } } diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/reflect_value.rs b/crates/bevy_reflect/bevy_reflect_derive/src/reflect_value.rs index ec54b99a6f404..23a93823b3127 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/reflect_value.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/reflect_value.rs @@ -1,8 +1,9 @@ use crate::container_attributes::ReflectTraits; +use crate::utility; use proc_macro2::Ident; use syn::parse::{Parse, ParseStream}; -use syn::token::{Paren, Where}; -use syn::{parenthesized, Generics}; +use syn::token::{Comma, Paren, Where}; +use syn::{parenthesized, Generics, NestedMeta}; /// A struct used to define a simple reflected value type (such as primitives). /// @@ -28,6 +29,8 @@ impl Parse for ReflectValueDef { fn parse(input: ParseStream) -> syn::Result { let type_ident = input.parse::()?; let generics = input.parse::()?; + let is_generic = utility::is_generic(&generics, false); + let mut lookahead = input.lookahead1(); let mut where_clause = None; if lookahead.peek(Where) { @@ -39,7 +42,8 @@ impl Parse for ReflectValueDef { if lookahead.peek(Paren) { let content; parenthesized!(content in input); - traits = Some(content.parse::()?); + let meta = content.parse_terminated::(NestedMeta::parse)?; + traits = Some(ReflectTraits::from_nested_metas(&meta, is_generic)?); } Ok(ReflectValueDef { diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/registration.rs b/crates/bevy_reflect/bevy_reflect_derive/src/registration.rs index 7b13f7d352ef8..1cf8fb9aef5b0 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/registration.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/registration.rs @@ -1,17 +1,19 @@ //! Contains code related specifically to Bevy's type registration. -use proc_macro2::Ident; +use crate::ReflectMeta; use quote::quote; -use syn::{Generics, Path}; /// Creates the `GetTypeRegistration` impl for the given type data. -pub(crate) fn impl_get_type_registration( - type_name: &Ident, - bevy_reflect_path: &Path, - registration_data: &[Ident], - generics: &Generics, -) -> proc_macro2::TokenStream { - let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); +pub(crate) fn impl_get_type_registration(meta: &ReflectMeta) -> proc_macro2::TokenStream { + let type_name = meta.type_name(); + let bevy_reflect_path = meta.bevy_reflect_path(); + + let registration_data = meta.traits().idents(); + + let aliases = meta.traits().aliases(); + let deprecated_aliases = meta.traits().deprecated_aliases(); + + let (impl_generics, ty_generics, where_clause) = meta.generics().split_for_impl(); quote! { #[allow(unused_mut)] impl #impl_generics #bevy_reflect_path::GetTypeRegistration for #type_name #ty_generics #where_clause { @@ -21,6 +23,14 @@ pub(crate) fn impl_get_type_registration( #(registration.insert::<#registration_data>(#bevy_reflect_path::FromType::<#type_name #ty_generics>::from_type());)* registration } + + fn aliases() -> &'static [&'static str] { + &[#(#aliases),*] + } + + fn deprecated_aliases() -> &'static [&'static str] { + &[#(#deprecated_aliases),*] + } } } } diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/utility.rs b/crates/bevy_reflect/bevy_reflect_derive/src/utility.rs index f8894689a3858..ff494641fdcd4 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/utility.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/utility.rs @@ -2,7 +2,7 @@ use bevy_macro_utils::BevyManifest; use proc_macro2::{Ident, Span}; -use syn::{Member, Path}; +use syn::{Generics, Member, Path}; /// Returns the correct path for `bevy_reflect`. pub(crate) fn get_bevy_reflect_path() -> Path { @@ -96,3 +96,14 @@ impl ResultSifter { } } } + +/// Returns true if the given [`Generics`] contains generic information. +/// +/// If `include_lifetimes` is false, this does not count lifetime arguments. +pub(crate) fn is_generic(generics: &Generics, include_lifetimes: bool) -> bool { + if include_lifetimes { + generics.params.len() - generics.lifetimes().count() > 0 + } else { + !generics.params.is_empty() + } +} diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index ef9e05ccd3cff..c6f0e67e9e6ca 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -436,6 +436,69 @@ mod tests { assert_eq!(new_foo, expected_new_foo); } + // FIXME: This test doesn't actually test anything since the current deserializer doesn't rely on + // the type name being correct and registered. + #[test] + fn reflect_alias() { + #[derive(Reflect, FromReflect)] + #[reflect(alias = "SomeAlias")] + #[reflect(alias = "SomeOtherAlias")] + #[reflect(deprecated_alias = "SomeOldAlias")] + struct Foo { + x: u32, + } + + let mut registry = TypeRegistry::default(); + registry.register::(); + registry.register::(); + + let deserialize_foo = |serialized: &str| { + let mut deserializer = Deserializer::from_str(serialized).unwrap(); + let reflect_deserializer = ReflectDeserializer::new(®istry); + let value = reflect_deserializer.deserialize(&mut deserializer).unwrap(); + ::from_reflect(value.as_ref()).unwrap() + }; + + let serialized = r#"{ + "type": "SomeAlias", + "struct": { + "x": { + "type": "u32", + "value": 123, + } + } + }"#; + let dynamic_struct = deserialize_foo(serialized); + let expected: Box = Box::new(Foo { x: 123 }); + assert!(expected.reflect_partial_eq(&dynamic_struct).unwrap()); + + let serialized = r#"{ + "type": "SomeOtherAlias", + "struct": { + "x": { + "type": "u32", + "value": 123, + } + } + }"#; + let dynamic_struct = deserialize_foo(serialized); + let expected: Box = Box::new(Foo { x: 123 }); + assert!(expected.reflect_partial_eq(&dynamic_struct).unwrap()); + + let serialized = r#"{ + "type": "SomeOldAlias", + "struct": { + "x": { + "type": "u32", + "value": 123, + } + } + }"#; + let dynamic_struct = deserialize_foo(serialized); + let expected: Box = Box::new(Foo { x: 123 }); + assert!(expected.reflect_partial_eq(&dynamic_struct).unwrap()); + } + #[test] fn reflect_serialize() { #[derive(Reflect)] diff --git a/crates/bevy_reflect/src/serde/de.rs b/crates/bevy_reflect/src/serde/de.rs index ad24185de15f3..953461859452a 100644 --- a/crates/bevy_reflect/src/serde/de.rs +++ b/crates/bevy_reflect/src/serde/de.rs @@ -145,7 +145,11 @@ impl<'a, 'de> Visitor<'de> for ReflectVisitor<'a> { while let Some(key) = map.next_key::()? { match key.as_str() { type_fields::TYPE => { - type_name = Some(map.next_value()?); + let key = map.next_value::()?; + + self.registry.warn_on_alias_deprecation(&key); + + type_name = Some(key); } type_fields::MAP => { let _type_name = type_name @@ -217,8 +221,11 @@ impl<'a, 'de> Visitor<'de> for ReflectVisitor<'a> { let type_name = type_name .take() .ok_or_else(|| de::Error::missing_field(type_fields::TYPE))?; - let registration = - self.registry.get_with_name(&type_name).ok_or_else(|| { + let registration = self + .registry + .get_with_name(&type_name) + .or_else(|| self.registry.get_with_alias(&type_name)) + .ok_or_else(|| { de::Error::custom(format_args!( "No registration found for {}", type_name diff --git a/crates/bevy_reflect/src/type_registry.rs b/crates/bevy_reflect/src/type_registry.rs index 74865abeb0c91..8e7f0351d2384 100644 --- a/crates/bevy_reflect/src/type_registry.rs +++ b/crates/bevy_reflect/src/type_registry.rs @@ -1,9 +1,11 @@ use crate::{serde::Serializable, Reflect, TypeInfo, Typed}; use bevy_ptr::{Ptr, PtrMut}; +use bevy_utils::tracing::warn; use bevy_utils::{HashMap, HashSet}; use downcast_rs::{impl_downcast, Downcast}; use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use serde::Deserialize; +use std::borrow::Cow; use std::{any::TypeId, fmt::Debug, sync::Arc}; /// A registry of reflected types. @@ -11,6 +13,7 @@ pub struct TypeRegistry { registrations: HashMap, short_name_to_id: HashMap, full_name_to_id: HashMap, + alias_to_id: HashMap, AliasData>, ambiguous_names: HashSet, } @@ -33,6 +36,35 @@ impl Debug for TypeRegistryArc { /// This trait is automatically implemented for types which derive [`Reflect`]. pub trait GetTypeRegistration { fn get_type_registration() -> TypeRegistration; + + /// Returns the static set of aliases that can be used to refer to this type. + /// + /// Note that these are the _default_ aliases used specifically for type registration. + /// For a given [registry], the actual set of aliases for a registered type may differ from the + /// ones listed here. + /// + /// If you need the list of aliases for this type, please use [`TypeRegistration::aliases`]. + /// + /// [registry]: TypeRegistry + fn aliases() -> &'static [&'static str] { + &[] + } + + /// Returns the static set of _deprecated_ aliases that can be used to refer to this type. + /// + /// For the list of _current_ aliases, try using [`aliases`] instead. + /// + /// Note that, like [`aliases`], this is the _default_ set used specifically for type registration. + /// For a given [registry], the actual set of deprecated aliases for a registered type may differ from the + /// ones listed here. + /// + /// If you need the list of aliases for this type, please use [`TypeRegistration::deprecated_aliases`]. + /// + /// [`aliases`]: GetTypeRegistration::aliases + /// [registry]: TypeRegistry + fn deprecated_aliases() -> &'static [&'static str] { + &[] + } } impl Default for TypeRegistry { @@ -48,6 +80,7 @@ impl TypeRegistry { registrations: Default::default(), short_name_to_id: Default::default(), full_name_to_id: Default::default(), + alias_to_id: Default::default(), ambiguous_names: Default::default(), } } @@ -87,6 +120,8 @@ impl TypeRegistry { /// Registers the type described by `registration`. pub fn add_registration(&mut self, registration: TypeRegistration) { + let type_id = registration.type_id(); + let type_name = registration.type_name(); let short_name = registration.short_name.to_string(); if self.short_name_to_id.contains_key(&short_name) || self.ambiguous_names.contains(&short_name) @@ -95,13 +130,18 @@ impl TypeRegistry { self.short_name_to_id.remove(&short_name); self.ambiguous_names.insert(short_name); } else { - self.short_name_to_id - .insert(short_name, registration.type_id()); + self.short_name_to_id.insert(short_name, type_id); } - self.full_name_to_id - .insert(registration.type_name().to_string(), registration.type_id()); - self.registrations - .insert(registration.type_id(), registration); + + for alias in ®istration.aliases { + self.register_alias_internal(alias.clone(), type_name, type_id, false, true); + } + for alias in ®istration.deprecated_aliases { + self.register_alias_internal(alias.clone(), type_name, type_id, true, true); + } + + self.full_name_to_id.insert(type_name.to_string(), type_id); + self.registrations.insert(type_id, registration); } /// Registers the type data `D` for type `T`. @@ -172,6 +212,36 @@ impl TypeRegistry { .and_then(move |id| self.get_mut(id)) } + /// Returns a reference to the [`TypeRegistration`] of the type with the + /// given alias. + /// + /// If no type with the given alias has been registered, returns `None`. + pub fn get_with_alias(&self, alias: &str) -> Option<&TypeRegistration> { + let alias_data = self.alias_to_id.get(alias)?; + let registration = self.get(alias_data.type_id)?; + + if alias_data.is_deprecated { + Self::warn_alias_deprecation_internal(alias, registration); + } + + Some(registration) + } + + /// Returns a mutable reference to the [`TypeRegistration`] of the type with + /// the given alias. + /// + /// If no type with the given alias has been registered, returns `None`. + pub fn get_with_alias_mut(&mut self, alias: &str) -> Option<&mut TypeRegistration> { + let alias_data = *self.alias_to_id.get(alias)?; + let registration = self.get_mut(alias_data.type_id)?; + + if alias_data.is_deprecated { + Self::warn_alias_deprecation_internal(alias, registration); + } + + Some(registration) + } + /// Returns a reference to the [`TypeRegistration`] of the type with /// the given short name. /// @@ -239,6 +309,172 @@ impl TypeRegistry { pub fn iter_mut(&mut self) -> impl Iterator { self.registrations.values_mut() } + + /// Register an alias for the given type, `T`. + /// + /// 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_alias`](Self::try_register_alias). + /// Otherwise, to explicitly overwrite existing aliases without the warning, + /// try using [`overwrite_alias`](Self::overwrite_alias). + /// + /// If an alias was overwritten, then the [`TypeId`] of the previous type is returned. + pub fn register_alias( + &mut self, + alias: impl Into>, + ) -> Option { + let registerer = AliasRegisterer::implicit_overwrite(self, "TypeRegistry::register_alias"); + registerer.register::(alias, false) + } + + /// Register a _deprecated_ alias for the given type, `T`. + /// + /// To register an alias that isn't marked as deprecated, use [`register_alias`](Self::register_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_deprecated_alias`](Self::try_register_deprecated_alias). + /// Otherwise, to explicitly overwrite existing aliases without the warning, + /// try using [`overwrite_deprecated_alias`](Self::overwrite_deprecated_alias). + /// + /// If an alias was overwritten, then the [`TypeId`] of the previous type is returned. + pub fn register_deprecated_alias( + &mut self, + alias: impl Into>, + ) -> Option { + let registerer = + AliasRegisterer::implicit_overwrite(self, "TypeRegistry::register_deprecated_alias"); + registerer.register::(alias, true) + } + + /// Attempts to register an alias for the given type, `T`, if it isn't already in use. + /// + /// To register the alias whether or not it exists, + /// try using either [`register_alias`](Self::register_alias) or [`overwrite_alias`](Self::overwrite_alias). + /// + /// If the given alias is already in use, then the [`TypeId`] of that type is returned. + pub fn try_register_alias( + &mut self, + alias: impl Into>, + ) -> Option { + let registerer = AliasRegisterer::no_overwrite(self, "TypeRegistry::try_register_alias"); + registerer.register::(alias, false) + } + + /// Attempts to register a _deprecated_ alias for the given type, `T`, if it isn't already in use. + /// + /// To try and register an alias that isn't marked as deprecated, use [`try_register_alias`](Self::try_register_alias). + /// + /// To register the alias whether or not it exists, + /// try using either [`register_deprecated_alias`](Self::register_deprecated_alias) + /// or [`overwrite_deprecated_alias`](Self::overwrite_deprecated_alias). + /// + /// If the given alias is already in use, then the [`TypeId`] of that type is returned. + pub fn try_register_deprecated_alias( + &mut self, + alias: impl Into>, + ) -> Option { + let registerer = + AliasRegisterer::no_overwrite(self, "TypeRegistry::try_register_deprecated_alias"); + registerer.register::(alias, true) + } + + /// Register an alias for the given type, `T`, explicitly overwriting existing aliases. + /// + /// Unlike, [`register_alias`](Self::register_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_alias`](Self::try_register_alias). + /// + /// If an alias was overwritten, then the [`TypeId`] of the previous type is returned. + pub fn overwrite_alias( + &mut self, + alias: impl Into>, + ) -> Option { + let registerer = AliasRegisterer::explicit_overwrite(self, "TypeRegistry::overwrite_alias"); + registerer.register::(alias, false) + } + + /// Register a _deprecated_ alias for the given type, `T`, explicitly overwriting existing aliases. + /// + /// To register an alias that isn't marked as deprecated, use [`overwrite_alias`](Self::overwrite_alias). + /// + /// Unlike, [`register_deprecated_alias`](Self::register_deprecated_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_deprecated_alias`](Self::try_register_deprecated_alias). + /// + /// If an alias was overwritten, then the [`TypeId`] of the previous type is returned. + pub fn overwrite_deprecated_alias( + &mut self, + alias: impl Into>, + ) -> Option { + let registerer = + AliasRegisterer::explicit_overwrite(self, "TypeRegistry::overwrite_deprecated_alias"); + registerer.register::(alias, true) + } + + /// Registers an alias for the given type. + fn register_alias_internal( + &mut self, + alias: Cow<'static, str>, + type_name: &'static str, + type_id: TypeId, + is_deprecated: bool, + should_warn: bool, + ) -> Option { + let existing = self.alias_to_id.insert( + alias.clone(), + AliasData { + type_id, + is_deprecated, + }, + ); + + if let Some(existing) = existing.and_then(|existing| self.get_mut(existing.type_id)) { + existing.aliases.remove(&alias); + existing.deprecated_aliases.remove(&alias); + + if should_warn { + warn!( + "overwrote alias `{alias}` — was assigned to type `{}` ({:?}), but is now assigned to type `{}` ({:?})", + existing.type_name(), + existing.type_id(), + type_name, + type_id + ); + } + + Some(existing.type_id()) + } else { + None + } + } + + /// If the given alias exists in the registry, prints a warning if it's deprecated. + pub(crate) fn warn_on_alias_deprecation(&self, alias: &str) { + if let Some(data) = self.alias_to_id.get(alias) { + if data.is_deprecated { + if let Some(registration) = self.get(data.type_id) { + Self::warn_alias_deprecation_internal(alias, registration); + } + } + } + } + + /// Prints a warning stating that the given alias has been deprecated for the given registration. + fn warn_alias_deprecation_internal(alias: &str, registration: &TypeRegistration) { + warn!( + "the alias `{}` has been deprecated for the type `{}` ({:?}) and may be removed in the future. \ + Consider using the full type name or one of the current aliases: {:?}", + alias, + registration.type_name(), + registration.type_id(), + registration.aliases(), + ); + } } impl TypeRegistryArc { @@ -270,6 +506,8 @@ pub struct TypeRegistration { short_name: String, data: HashMap>, type_info: &'static TypeInfo, + aliases: HashSet>, + deprecated_aliases: HashSet>, } impl TypeRegistration { @@ -314,12 +552,18 @@ impl TypeRegistration { } /// Creates type registration information for `T`. - pub fn of() -> Self { + pub fn of() -> Self { let type_name = std::any::type_name::(); Self { data: HashMap::default(), short_name: bevy_utils::get_short_name(type_name), type_info: T::type_info(), + aliases: HashSet::from_iter(T::aliases().iter().map(|&alias| Cow::Borrowed(alias))), + deprecated_aliases: HashSet::from_iter( + T::deprecated_aliases() + .iter() + .map(|&alias| Cow::Borrowed(alias)), + ), } } @@ -336,6 +580,20 @@ impl TypeRegistration { pub fn type_name(&self) -> &'static str { self.type_info.type_name() } + + /// Returns the default set of aliases for the type. + /// + /// For the set of _deprecated_ aliases, try [`deprecated_aliases`](Self::deprecated_aliases). + pub fn aliases(&self) -> &HashSet> { + &self.aliases + } + + /// Returns the default set of _deprecated_ aliases for the type. + /// + /// For the set of _current_ aliases, try [`aliases`](Self::aliases). + pub fn deprecated_aliases(&self) -> &HashSet> { + &self.deprecated_aliases + } } impl Clone for TypeRegistration { @@ -349,10 +607,98 @@ impl Clone for TypeRegistration { data, short_name: self.short_name.clone(), type_info: self.type_info, + aliases: self.aliases.clone(), + deprecated_aliases: self.deprecated_aliases.clone(), } } } +#[derive(Copy, Clone)] +struct AliasData { + pub type_id: TypeId, + pub is_deprecated: bool, +} + +/// A simple helper struct for registering type aliases. +struct AliasRegisterer<'a> { + registry: &'a mut TypeRegistry, + func_name: &'static str, + allow_overwrite: bool, + should_warn: bool, +} + +impl<'a> AliasRegisterer<'a> { + /// Configure the registerer to register aliases with an implicit overwrite (produces a warning). + fn implicit_overwrite(registry: &'a mut TypeRegistry, func_name: &'static str) -> Self { + Self { + registry, + func_name, + allow_overwrite: true, + should_warn: true, + } + } + + /// Configure the registerer to register aliases with an explicit overwrite (does not produce a warning). + fn explicit_overwrite(registry: &'a mut TypeRegistry, func_name: &'static str) -> Self { + Self { + registry, + func_name, + allow_overwrite: true, + should_warn: false, + } + } + + /// Configure the registerer to register aliases as long as they are not already in use. + fn no_overwrite(registry: &'a mut TypeRegistry, func_name: &'static str) -> Self { + Self { + registry, + func_name, + allow_overwrite: false, + should_warn: false, + } + } + + /// Register the given alias for type, `T`. + fn register( + self, + alias: impl Into>, + is_deprecated: bool, + ) -> Option { + let Self { + registry, + func_name, + allow_overwrite, + should_warn, + } = self; + + let alias = alias.into(); + + if !allow_overwrite { + if let Some(data) = registry.alias_to_id.get(&alias) { + return Some(data.type_id); + } + } + + let registration = registry.get_mut(TypeId::of::()).unwrap_or_else(|| { + panic!( + "attempted to call `{func_name}` for type `{T}` with alias `{alias}` without registering `{T}` first", + T = std::any::type_name::(), + ) + }); + + if is_deprecated { + registration.deprecated_aliases.insert(alias.clone()); + } else { + registration.aliases.insert(alias.clone()); + } + + let type_name = registration.type_name(); + let type_id = registration.type_id(); + + registry.register_alias_internal(alias, type_name, type_id, is_deprecated, should_warn) + } +} + /// A trait for types generated by the [`#[reflect_trait]`][0] attribute macro. /// /// [0]: crate::reflect_trait @@ -521,10 +867,11 @@ impl FromType for ReflectFromPtr { } #[cfg(test)] -mod test { +mod tests { + use std::any::TypeId; use std::ptr::NonNull; - use crate::{GetTypeRegistration, ReflectFromPtr, TypeRegistration}; + use crate::{GetTypeRegistration, ReflectFromPtr, TypeRegistration, TypeRegistry}; use bevy_ptr::{Ptr, PtrMut}; use bevy_utils::HashMap; @@ -575,6 +922,143 @@ mod test { } } + #[test] + fn should_register_deprecated_alias() { + #[derive(Reflect)] + struct Foo; + #[derive(Reflect)] + struct Bar; + + let mut registry = TypeRegistry::empty(); + registry.register::(); + registry.register::(); + + let previous = registry.register_deprecated_alias::("my_deprecated_alias"); + assert_eq!(None, previous); + + let registration = registry.get_with_alias("my_deprecated_alias").unwrap(); + assert_eq!(TypeId::of::(), registration.type_id()); + assert!(registration + .deprecated_aliases() + .contains("my_deprecated_alias")); + + let previous = registry.register_deprecated_alias::("my_deprecated_alias"); + assert_eq!(Some(TypeId::of::()), previous); + + let registration = registry.get_with_alias("my_deprecated_alias").unwrap(); + assert_eq!(TypeId::of::(), registration.type_id()); + assert!(registration + .deprecated_aliases() + .contains("my_deprecated_alias")); + + // Confirm that the registrations' aliases have been updated + let foo_registration = registry.get(TypeId::of::()).unwrap(); + let bar_registration = registry.get(TypeId::of::()).unwrap(); + assert!(!foo_registration + .deprecated_aliases() + .contains("my_deprecated_alias")); + assert!(bar_registration + .deprecated_aliases() + .contains("my_deprecated_alias")); + } + + #[test] + fn should_register_alias_for_generic() { + #[derive(Reflect)] + struct Foo(T); + + let mut registry = TypeRegistry::empty(); + registry.register::>(); + registry.register::>(); + + let previous = registry.register_alias::>("my_alias"); + assert_eq!(None, previous); + + let registration = registry.get_with_alias("my_alias").unwrap(); + assert_eq!(TypeId::of::>(), registration.type_id()); + assert!(registration.aliases().contains("my_alias")); + + let previous = registry.register_alias::>("my_alias"); + assert_eq!(Some(TypeId::of::>()), previous); + + let registration = registry.get_with_alias("my_alias").unwrap(); + assert_eq!(TypeId::of::>(), registration.type_id()); + assert!(registration.aliases().contains("my_alias")); + + // Confirm that the registrations' aliases have been updated + let foo_registration = registry.get(TypeId::of::>()).unwrap(); + let bar_registration = registry.get(TypeId::of::>()).unwrap(); + assert!(!foo_registration.aliases().contains("my_alias")); + assert!(bar_registration.aliases().contains("my_alias")); + } + + #[test] + fn should_register_new_alias() { + #[derive(Reflect)] + struct Foo; + #[derive(Reflect)] + struct Bar; + + let mut registry = TypeRegistry::empty(); + registry.register::(); + registry.register::(); + + let previous = registry.register_alias::("my_alias"); + assert_eq!(None, previous); + + let registration = registry.get_with_alias("my_alias").unwrap(); + assert_eq!(TypeId::of::(), registration.type_id()); + assert!(registration.aliases().contains("my_alias")); + + let previous = registry.register_alias::("my_alias"); + assert_eq!(Some(TypeId::of::()), previous); + + let registration = registry.get_with_alias("my_alias").unwrap(); + assert_eq!(TypeId::of::(), registration.type_id()); + assert!(registration.aliases().contains("my_alias")); + + // Confirm that the registrations' aliases have been updated + let foo_registration = registry.get(TypeId::of::()).unwrap(); + let bar_registration = registry.get(TypeId::of::()).unwrap(); + assert!(!foo_registration.aliases().contains("my_alias")); + assert!(bar_registration.aliases().contains("my_alias")); + } + + #[test] + fn should_not_register_existing_alias() { + #[derive(Reflect)] + struct Foo; + #[derive(Reflect)] + struct Bar; + + let mut registry = TypeRegistry::empty(); + registry.register::(); + registry.register::(); + + registry.register_alias::("my_alias"); + let current = registry.try_register_alias::("my_alias"); + assert_eq!(Some(TypeId::of::()), current); + + let registration = registry.get_with_alias("my_alias").unwrap(); + assert_eq!(TypeId::of::(), registration.type_id()); + + // Confirm that the registrations' aliases have been updated + let foo_registration = registry.get(TypeId::of::()).unwrap(); + let bar_registration = registry.get(TypeId::of::()).unwrap(); + assert!(foo_registration.aliases().contains("my_alias")); + assert!(!bar_registration.aliases().contains("my_alias")); + } + + #[test] + #[should_panic(expected = "attempted to call `TypeRegistry::register_alias` for type")] + fn register_alias_should_panic_if_no_registration() { + #[derive(Reflect)] + struct Foo; + + let mut registry = TypeRegistry::empty(); + registry.register_alias::("my_alias"); + } + #[test] fn test_property_type_registration() { assert_eq!(