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
120 changes: 109 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,99 @@ 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(), "cannot specify non-generic aliases 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) {
continue;
}

self.idents.push(ident);
}

for alias in other.aliases {
let value = alias.value();
if self
.aliases
.iter()
.any(|other_alias| value == other_alias.value())
{
continue;
}

self.aliases.push(alias);
}

for alias in other.deprecated_aliases {
let value = alias.value();
if self
.deprecated_aliases
.iter()
.any(|other_alias| value == other_alias.value())
{
continue;
}

self.deprecated_aliases.push(alias);
}
}

/// Returns true if the given reflected trait name (i.e. `ReflectDefault` for `Default`)
Expand All @@ -169,6 +266,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 +343,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
10 changes: 7 additions & 3 deletions crates/bevy_reflect/bevy_reflect_derive/src/reflect_value.rs
Original file line number Diff line number Diff line change
@@ -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).
///
Expand All @@ -28,6 +29,8 @@ impl Parse for ReflectValueDef {
fn parse(input: ParseStream) -> syn::Result<Self> {
let type_ident = input.parse::<Ident>()?;
let generics = input.parse::<Generics>()?;
let is_generic = utility::is_generic(&generics, false);

let mut lookahead = input.lookahead1();
let mut where_clause = None;
if lookahead.peek(Where) {
Expand All @@ -39,7 +42,8 @@ impl Parse for ReflectValueDef {
if lookahead.peek(Paren) {
let content;
parenthesized!(content in input);
traits = Some(content.parse::<ReflectTraits>()?);
let meta = content.parse_terminated::<NestedMeta, Comma>(NestedMeta::parse)?;
traits = Some(ReflectTraits::from_nested_metas(&meta, is_generic)?);
}

Ok(ReflectValueDef {
Expand Down
28 changes: 19 additions & 9 deletions crates/bevy_reflect/bevy_reflect_derive/src/registration.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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),*]
}
}
}
}
13 changes: 12 additions & 1 deletion crates/bevy_reflect/bevy_reflect_derive/src/utility.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -96,3 +96,14 @@ impl<T> ResultSifter<T> {
}
}
}

/// 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()
}
}
63 changes: 63 additions & 0 deletions crates/bevy_reflect/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<Foo>();
registry.register::<u32>();

let deserialize_foo = |serialized: &str| {
let mut deserializer = Deserializer::from_str(serialized).unwrap();
let reflect_deserializer = ReflectDeserializer::new(&registry);
let value = reflect_deserializer.deserialize(&mut deserializer).unwrap();
<Foo as FromReflect>::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<dyn Reflect> = 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<dyn Reflect> = 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<dyn Reflect> = Box::new(Foo { x: 123 });
assert!(expected.reflect_partial_eq(&dynamic_struct).unwrap());
}

#[test]
fn reflect_serialize() {
#[derive(Reflect)]
Expand Down
Loading