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 parameter bounds #9046

Merged
merged 9 commits into from
Jan 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/bevy_asset/src/handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ impl std::fmt::Debug for StrongHandle {
/// [`Handle::Strong`] also provides access to useful [`Asset`] metadata, such as the [`AssetPath`] (if it exists).
#[derive(Component, Reflect)]
#[reflect(Component)]
#[reflect(where A: Asset)]
pub enum Handle<A: Asset> {
/// A "strong" reference to a live (or loading) [`Asset`]. If a [`Handle`] is [`Handle::Strong`], the [`Asset`] will be kept
/// alive until the [`Handle`] is dropped. Strong handles also provide access to additional asset metadata.
Expand Down
1 change: 1 addition & 0 deletions crates/bevy_asset/src/id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use thiserror::Error;
///
/// For an "untyped" / "generic-less" id, see [`UntypedAssetId`].
#[derive(Reflect)]
#[reflect(where A: Asset)]
pub enum AssetId<A: Asset> {
/// A small / efficient runtime identifier that can be used to efficiently look up an asset stored in [`Assets`]. This is
/// the "default" identifier used for assets. The alternative(s) (ex: [`AssetId::Uuid`]) will only be used if assets are
Expand Down
105 changes: 70 additions & 35 deletions crates/bevy_reflect/bevy_reflect_derive/src/container_attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@

use crate::utility;
use bevy_macro_utils::fq_std::{FQAny, FQOption};
use proc_macro2::{Ident, Span};
use proc_macro2::{Ident, Span, TokenTree};
use quote::quote_spanned;
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::token::Comma;
use syn::{Expr, LitBool, Meta, Path};
use syn::{Expr, LitBool, Meta, MetaList, Path, WhereClause};

// The "special" trait idents that are used internally for reflection.
// Received via attributes like `#[reflect(PartialEq, Hash, ...)]`
Expand Down Expand Up @@ -211,24 +211,40 @@ pub(crate) struct ReflectTraits {
partial_eq: TraitImpl,
from_reflect_attrs: FromReflectAttrs,
type_path_attrs: TypePathAttrs,
custom_where: Option<WhereClause>,
idents: Vec<Ident>,
}

impl ReflectTraits {
pub fn from_metas(
pub fn from_meta_list(
meta: &MetaList,
is_from_reflect_derive: bool,
) -> Result<Self, syn::Error> {
match meta.tokens.clone().into_iter().next() {
// Handles `#[reflect(where T: Trait, U::Assoc: Trait)]`
Some(TokenTree::Ident(ident)) if ident == "where" => Ok(Self {
custom_where: Some(meta.parse_args::<WhereClause>()?),
..Self::default()
}),
_ => Self::from_metas(
meta.parse_args_with(Punctuated::<Meta, Comma>::parse_terminated)?,
is_from_reflect_derive,
),
}
}

fn from_metas(
metas: Punctuated<Meta, Comma>,
is_from_reflect_derive: bool,
) -> Result<Self, syn::Error> {
let mut traits = ReflectTraits::default();
for meta in &metas {
match meta {
// Handles `#[reflect( Hash, Default, ... )]`
// Handles `#[reflect( Debug, PartialEq, Hash, SomeTrait )]`
Meta::Path(path) => {
// Get the first ident in the path (hopefully the path only contains one and not `std::hash::Hash`)
let Some(segment) = path.segments.iter().next() else {
let Some(ident) = path.get_ident() else {
continue;
};
let ident = &segment.ident;
let ident_name = ident.to_string();

// Track the span where the trait is implemented for future errors
Expand All @@ -255,38 +271,38 @@ impl ReflectTraits {
}
}
}
// Handles `#[reflect( Debug(custom_debug_fn) )]`
Meta::List(list) if list.path.is_ident(DEBUG_ATTR) => {
let ident = list.path.get_ident().unwrap();
list.parse_nested_meta(|meta| {
let trait_func_ident = TraitImpl::Custom(meta.path, ident.span());
traits.debug.merge(trait_func_ident)
})?;
}
// Handles `#[reflect( PartialEq(custom_partial_eq_fn) )]`
Meta::List(list) if list.path.is_ident(PARTIAL_EQ_ATTR) => {
let ident = list.path.get_ident().unwrap();
list.parse_nested_meta(|meta| {
let trait_func_ident = TraitImpl::Custom(meta.path, ident.span());
traits.partial_eq.merge(trait_func_ident)
})?;
}
// Handles `#[reflect( Hash(custom_hash_fn) )]`
Meta::List(list) => {
// Get the first ident in the path (hopefully the path only contains one and not `std::hash::Hash`)
let Some(segment) = list.path.segments.iter().next() else {
continue;
};

let ident = segment.ident.to_string();

// Track the span where the trait is implemented for future errors
let span = ident.span();

Meta::List(list) if list.path.is_ident(HASH_ATTR) => {
let ident = list.path.get_ident().unwrap();
list.parse_nested_meta(|meta| {
// This should be the path of the custom function
let trait_func_ident = TraitImpl::Custom(meta.path, span);
match ident.as_str() {
DEBUG_ATTR => {
traits.debug.merge(trait_func_ident)?;
}
PARTIAL_EQ_ATTR => {
traits.partial_eq.merge(trait_func_ident)?;
}
HASH_ATTR => {
traits.hash.merge(trait_func_ident)?;
}
_ => {
return Err(syn::Error::new(span, "Can only use custom functions for special traits (i.e. `Hash`, `PartialEq`, `Debug`)"));
}
}
Ok(())
let trait_func_ident = TraitImpl::Custom(meta.path, ident.span());
traits.hash.merge(trait_func_ident)
})?;
}
Meta::List(list) => {
return Err(syn::Error::new_spanned(
list,
format!(
"expected one of [{DEBUG_ATTR:?}, {PARTIAL_EQ_ATTR:?}, {HASH_ATTR:?}]"
),
));
}
Meta::NameValue(pair) => {
if pair.path.is_ident(FROM_REFLECT_ATTR) {
traits.from_reflect_attrs.auto_derive =
Expand Down Expand Up @@ -402,6 +418,10 @@ impl ReflectTraits {
}
}

pub fn custom_where(&self) -> Option<&WhereClause> {
self.custom_where.as_ref()
}

/// Merges the trait implementations of this [`ReflectTraits`] with another one.
///
/// An error is returned if the two [`ReflectTraits`] have conflicting implementations.
Expand All @@ -411,11 +431,26 @@ impl ReflectTraits {
self.partial_eq.merge(other.partial_eq)?;
self.from_reflect_attrs.merge(other.from_reflect_attrs)?;
self.type_path_attrs.merge(other.type_path_attrs)?;

self.merge_custom_where(other.custom_where);

for ident in other.idents {
add_unique_ident(&mut self.idents, ident)?;
}
Ok(())
}

fn merge_custom_where(&mut self, other: Option<WhereClause>) {
match (&mut self.custom_where, other) {
(Some(this), Some(other)) => {
this.predicates.extend(other.predicates);
}
(None, Some(other)) => {
self.custom_where = Some(other);
}
_ => {}
}
}
}

impl Parse for ReflectTraits {
Expand Down
31 changes: 7 additions & 24 deletions crates/bevy_reflect/bevy_reflect_derive/src/derive_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,10 +167,8 @@ impl<'a> ReflectDerive<'a> {
}

reflect_mode = Some(ReflectMode::Normal);
let new_traits = ReflectTraits::from_metas(
meta_list.parse_args_with(Punctuated::<Meta, Comma>::parse_terminated)?,
is_from_reflect_derive,
)?;
let new_traits =
ReflectTraits::from_meta_list(meta_list, is_from_reflect_derive)?;
traits.merge(new_traits)?;
}
Meta::List(meta_list) if meta_list.path.is_ident(REFLECT_VALUE_ATTRIBUTE_NAME) => {
Expand All @@ -182,10 +180,8 @@ impl<'a> ReflectDerive<'a> {
}

reflect_mode = Some(ReflectMode::Value);
let new_traits = ReflectTraits::from_metas(
meta_list.parse_args_with(Punctuated::<Meta, Comma>::parse_terminated)?,
is_from_reflect_derive,
)?;
let new_traits =
ReflectTraits::from_meta_list(meta_list, is_from_reflect_derive)?;
traits.merge(new_traits)?;
}
Meta::Path(path) if path.is_ident(REFLECT_VALUE_ATTRIBUTE_NAME) => {
Expand Down Expand Up @@ -484,7 +480,7 @@ impl<'a> ReflectStruct<'a> {
}

pub fn where_clause_options(&self) -> WhereClauseOptions {
WhereClauseOptions::new(self.meta(), self.active_fields(), self.ignored_fields())
WhereClauseOptions::new(self.meta())
}
}

Expand All @@ -507,22 +503,8 @@ impl<'a> ReflectEnum<'a> {
&self.variants
}

/// Get an iterator of fields which are exposed to the reflection API
pub fn active_fields(&self) -> impl Iterator<Item = &StructField<'a>> {
self.variants()
.iter()
.flat_map(|variant| variant.active_fields())
}

/// Get an iterator of fields which are ignored by the reflection API
pub fn ignored_fields(&self) -> impl Iterator<Item = &StructField<'a>> {
self.variants()
.iter()
.flat_map(|variant| variant.ignored_fields())
}

pub fn where_clause_options(&self) -> WhereClauseOptions {
WhereClauseOptions::new(self.meta(), self.active_fields(), self.ignored_fields())
WhereClauseOptions::new(self.meta())
}
}

Expand Down Expand Up @@ -668,6 +650,7 @@ impl<'a> ReflectTypePath<'a> {
where_clause: None,
params: Punctuated::new(),
};

match self {
Self::Internal { generics, .. } | Self::External { generics, .. } => generics,
_ => EMPTY_GENERICS,
Expand Down
46 changes: 6 additions & 40 deletions crates/bevy_reflect/bevy_reflect_derive/src/from_reflect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::container_attributes::REFLECT_DEFAULT;
use crate::derive_data::ReflectEnum;
use crate::enum_utility::{get_variant_constructors, EnumVariantConstructors};
use crate::field_attributes::DefaultBehavior;
use crate::utility::{extend_where_clause, ident_or_index, WhereClauseOptions};
use crate::utility::{ident_or_index, WhereClauseOptions};
use crate::{ReflectMeta, ReflectStruct};
use bevy_macro_utils::fq_std::{FQAny, FQClone, FQDefault, FQOption};
use proc_macro2::Span;
Expand All @@ -24,7 +24,7 @@ pub(crate) fn impl_value(meta: &ReflectMeta) -> proc_macro2::TokenStream {
let bevy_reflect_path = meta.bevy_reflect_path();
let (impl_generics, ty_generics, where_clause) = type_path.generics().split_for_impl();
let where_from_reflect_clause =
extend_where_clause(where_clause, &WhereClauseOptions::new_value(meta));
WhereClauseOptions::new_type_path(meta).extend_where_clause(where_clause);
quote! {
impl #impl_generics #bevy_reflect_path::FromReflect for #type_path #ty_generics #where_from_reflect_clause {
fn from_reflect(reflect: &dyn #bevy_reflect_path::Reflect) -> #FQOption<Self> {
Expand All @@ -50,22 +50,8 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> proc_macro2::TokenStream
let (impl_generics, ty_generics, where_clause) = enum_path.generics().split_for_impl();

// Add FromReflect bound for each active field
let where_from_reflect_clause = extend_where_clause(
where_clause,
&WhereClauseOptions::new_with_bounds(
reflect_enum.meta(),
reflect_enum.active_fields(),
reflect_enum.ignored_fields(),
|field| match &field.attrs.default {
DefaultBehavior::Default => Some(quote!(#FQDefault)),
_ => None,
},
|field| match &field.attrs.default {
DefaultBehavior::Func(_) => None,
_ => Some(quote!(#FQDefault)),
},
),
);
let where_from_reflect_clause =
WhereClauseOptions::new(reflect_enum.meta()).extend_where_clause(where_clause);

quote! {
impl #impl_generics #bevy_reflect_path::FromReflect for #enum_path #ty_generics #where_from_reflect_clause {
Expand Down Expand Up @@ -144,28 +130,8 @@ fn impl_struct_internal(
.split_for_impl();

// Add FromReflect bound for each active field
let where_from_reflect_clause = extend_where_clause(
where_clause,
&WhereClauseOptions::new_with_bounds(
reflect_struct.meta(),
reflect_struct.active_fields(),
reflect_struct.ignored_fields(),
|field| match &field.attrs.default {
DefaultBehavior::Default => Some(quote!(#FQDefault)),
_ => None,
},
|field| {
if is_defaultable {
None
} else {
match &field.attrs.default {
DefaultBehavior::Func(_) => None,
_ => Some(quote!(#FQDefault)),
}
}
},
),
);
let where_from_reflect_clause =
WhereClauseOptions::new(reflect_struct.meta()).extend_where_clause(where_clause);

quote! {
impl #impl_generics #bevy_reflect_path::FromReflect for #struct_path #ty_generics #where_from_reflect_clause {
Expand Down
3 changes: 1 addition & 2 deletions crates/bevy_reflect/bevy_reflect_derive/src/impls/enums.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use crate::derive_data::{EnumVariant, EnumVariantFields, ReflectEnum, StructField};
use crate::enum_utility::{get_variant_constructors, EnumVariantConstructors};
use crate::impls::{impl_type_path, impl_typed};
use crate::utility::extend_where_clause;
use bevy_macro_utils::fq_std::{FQAny, FQBox, FQOption, FQResult};
use proc_macro2::{Ident, Span};
use quote::quote;
Expand Down Expand Up @@ -92,7 +91,7 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> proc_macro2::TokenStream
let (impl_generics, ty_generics, where_clause) =
reflect_enum.meta().type_path().generics().split_for_impl();

let where_reflect_clause = extend_where_clause(where_clause, &where_clause_options);
let where_reflect_clause = where_clause_options.extend_where_clause(where_clause);

quote! {
#get_type_registration_impl
Expand Down
4 changes: 2 additions & 2 deletions crates/bevy_reflect/bevy_reflect_derive/src/impls/structs.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::impls::{impl_type_path, impl_typed};
use crate::utility::{extend_where_clause, ident_or_index};
use crate::utility::ident_or_index;
use crate::ReflectStruct;
use bevy_macro_utils::fq_std::{FQAny, FQBox, FQDefault, FQOption, FQResult};
use quote::{quote, ToTokens};
Expand Down Expand Up @@ -99,7 +99,7 @@ pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> proc_macro2::TokenS
.generics()
.split_for_impl();

let where_reflect_clause = extend_where_clause(where_clause, &where_clause_options);
let where_reflect_clause = where_clause_options.extend_where_clause(where_clause);

quote! {
#get_type_registration_impl
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use crate::impls::{impl_type_path, impl_typed};
use crate::utility::extend_where_clause;
use crate::ReflectStruct;
use bevy_macro_utils::fq_std::{FQAny, FQBox, FQDefault, FQOption, FQResult};
use quote::{quote, ToTokens};
Expand Down Expand Up @@ -90,7 +89,7 @@ pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> proc_macro2::
.generics()
.split_for_impl();

let where_reflect_clause = extend_where_clause(where_clause, &where_clause_options);
let where_reflect_clause = where_clause_options.extend_where_clause(where_clause);

quote! {
#get_type_registration_impl
Expand Down
Loading
Loading