mirror of
https://github.com/bevyengine/bevy
synced 2025-01-08 03:08:55 +00:00
cbb4c26cad
# Objective I recently had an issue, where I have a struct: ``` struct Property { inner: T } ``` that I use as a wrapper for internal purposes. I don't want to update my struct definition to ``` struct Property<T: Reflect>{ inner: T } ``` because I still want to be able to build `Property<T>` for types `T` that are not `Reflect`. (and also because I don't want to update my whole code base with `<T: Reflect>` bounds) I still wanted to have reflection on it (for `bevy_inspector_egui`), but adding `derive(Reflect)` fails with the error: `T cannot be sent between threads safely. T needs to implement Sync.` I believe that `bevy_reflect` should adopt the model of other derives in the case of generics, which is to add the `Reflect` implementation only if the generics also implement `Reflect`. (That is the behaviour of other macros such as `derive(Clone)` or `derive(Debug)`. It's also the current behavior of `derive(FromReflect)`. Basically doing something like: ``` impl<T> Reflect for Foo<T> where T: Reflect ``` ## Solution - I updated the derive macros for `Structs` and `TupleStructs` to add extra `where` bounds. - Every type that is reflected will need a `T: Reflect` bound - Ignored types will need a `T: 'static + Send + Sync` bound. Here's the reason. For cases like this: ``` #[derive(Reflect)] struct Foo<T, U>{ a: T #[reflect(ignore)] b: U } ``` I had to add the bound `'static + Send + Sync` to ignored generics like `U`. The reason is that we want `Foo<T, U>` to be `Reflect: 'static + Send + Sync`, so `Foo<T, U>` must be able to implement those auto-traits. `Foo<T, U>` will only implement those auto-traits if every generic type implements them, including ignored types. This means that the previously compile-fail case now compiles: ``` #[derive(Reflect)] struct Foo<'a> { #[reflect(ignore)] value: &'a str, } ``` But `Foo<'a>` will only be useable in the cases where `'a: 'static` and panic if we don't have `'a: 'static`, which is what we want (nice bonus from this PR ;) ) --- ## Changelog > This section is optional. If this was a trivial fix, or has no externally-visible impact, you can delete this section. ### Added Possibility to add `derive(Reflect)` to structs and enums that contain generic types, like so: ``` #[derive(Reflect)] struct Foo<T>{ a: T } ``` Reflection will only be available if the generic type T also implements `Reflect`. (previously, this would just return a compiler error)
215 lines
6.8 KiB
Rust
215 lines
6.8 KiB
Rust
//! General-purpose utility functions for internal usage within this crate.
|
|
|
|
use crate::field_attributes::ReflectIgnoreBehavior;
|
|
use bevy_macro_utils::BevyManifest;
|
|
use bit_set::BitSet;
|
|
use proc_macro2::{Ident, Span};
|
|
use quote::quote;
|
|
use syn::{Member, Path, Type, WhereClause};
|
|
|
|
/// Returns the correct path for `bevy_reflect`.
|
|
pub(crate) fn get_bevy_reflect_path() -> Path {
|
|
BevyManifest::get_path_direct("bevy_reflect")
|
|
}
|
|
|
|
/// Returns the "reflected" ident for a given string.
|
|
///
|
|
/// # Example
|
|
///
|
|
/// ```ignore
|
|
/// let reflected: Ident = get_reflect_ident("Hash");
|
|
/// assert_eq!("ReflectHash", reflected.to_string());
|
|
/// ```
|
|
pub(crate) fn get_reflect_ident(name: &str) -> Ident {
|
|
let reflected = format!("Reflect{name}");
|
|
Ident::new(&reflected, Span::call_site())
|
|
}
|
|
|
|
/// Helper struct used to process an iterator of `Result<Vec<T>, syn::Error>`,
|
|
/// combining errors into one along the way.
|
|
pub(crate) struct ResultSifter<T> {
|
|
items: Vec<T>,
|
|
errors: Option<syn::Error>,
|
|
}
|
|
|
|
/// Returns a `Member` made of `ident` or `index` if `ident` is None.
|
|
///
|
|
/// Rust struct syntax allows for `Struct { foo: "string" }` with explicitly
|
|
/// named fields. It allows the `Struct { 0: "string" }` syntax when the struct
|
|
/// is declared as a tuple struct.
|
|
///
|
|
/// ```
|
|
/// # fn main() {
|
|
/// struct Foo { field: &'static str }
|
|
/// struct Bar(&'static str);
|
|
/// let Foo { field } = Foo { field: "hi" };
|
|
/// let Bar { 0: field } = Bar { 0: "hello" };
|
|
/// let Bar(field) = Bar("hello"); // more common syntax
|
|
/// # }
|
|
/// ```
|
|
///
|
|
/// This function helps field access in context where you are declaring either
|
|
/// a tuple struct or a struct with named fields. If you don't have a field name,
|
|
/// it means you need to access the struct through an index.
|
|
pub(crate) fn ident_or_index(ident: Option<&Ident>, index: usize) -> Member {
|
|
// TODO(Quality) when #4761 is merged, code that does this should be replaced
|
|
// by a call to `ident_or_index`.
|
|
ident.map_or_else(
|
|
|| Member::Unnamed(index.into()),
|
|
|ident| Member::Named(ident.clone()),
|
|
)
|
|
}
|
|
|
|
/// Options defining how to extend the `where` clause in reflection with any additional bounds needed.
|
|
pub(crate) struct WhereClauseOptions {
|
|
/// Any types that will be reflected and need an extra trait bound
|
|
pub(crate) active_types: Box<[Type]>,
|
|
/// Trait bounds to add to the active types
|
|
pub(crate) active_trait_bounds: proc_macro2::TokenStream,
|
|
/// Any types that won't be reflected and need an extra trait bound
|
|
pub(crate) ignored_types: Box<[Type]>,
|
|
/// Trait bounds to add to the ignored types
|
|
pub(crate) ignored_trait_bounds: proc_macro2::TokenStream,
|
|
}
|
|
|
|
impl Default for WhereClauseOptions {
|
|
/// By default, don't add any additional bounds to the `where` clause
|
|
fn default() -> Self {
|
|
Self {
|
|
active_types: Box::new([]),
|
|
ignored_types: Box::new([]),
|
|
active_trait_bounds: quote! {},
|
|
ignored_trait_bounds: quote! {},
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Extends the `where` clause in reflection with any additional bounds needed.
|
|
///
|
|
/// This is mostly used to add additional bounds to reflected objects with generic types.
|
|
/// For reflection purposes, we usually have:
|
|
/// * `active_trait_bounds: Reflect`
|
|
/// * `ignored_trait_bounds: Any + Send + Sync`
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `where_clause`: existing `where` clause present on the object to be derived
|
|
/// * `where_clause_options`: additional paramters defining which trait bounds to add to the `where` clause
|
|
///
|
|
/// # Example
|
|
///
|
|
/// The struct:
|
|
/// ```ignore
|
|
/// #[derive(Reflect)]
|
|
/// struct Foo<T, U> {
|
|
/// a: T,
|
|
/// #[reflect(ignore)]
|
|
/// b: U
|
|
/// }
|
|
/// ```
|
|
/// will have active types: `[T]` and ignored types: `[U]`
|
|
///
|
|
/// The `extend_where_clause` function will yield the following `where` clause:
|
|
/// ```ignore
|
|
/// where
|
|
/// T: Reflect, // active_trait_bounds
|
|
/// U: Any + Send + Sync, // ignored_trait_bounds
|
|
/// ```
|
|
pub(crate) fn extend_where_clause(
|
|
where_clause: Option<&WhereClause>,
|
|
where_clause_options: &WhereClauseOptions,
|
|
) -> proc_macro2::TokenStream {
|
|
let active_types = &where_clause_options.active_types;
|
|
let ignored_types = &where_clause_options.ignored_types;
|
|
let active_trait_bounds = &where_clause_options.active_trait_bounds;
|
|
let ignored_trait_bounds = &where_clause_options.ignored_trait_bounds;
|
|
|
|
let mut generic_where_clause = if where_clause.is_some() {
|
|
quote! {#where_clause}
|
|
} else if !(active_types.is_empty() && ignored_types.is_empty()) {
|
|
quote! {where}
|
|
} else {
|
|
quote! {}
|
|
};
|
|
generic_where_clause.extend(quote! {
|
|
#(#active_types: #active_trait_bounds,)*
|
|
#(#ignored_types: #ignored_trait_bounds,)*
|
|
});
|
|
generic_where_clause
|
|
}
|
|
|
|
impl<T> Default for ResultSifter<T> {
|
|
fn default() -> Self {
|
|
Self {
|
|
items: Vec::new(),
|
|
errors: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T> ResultSifter<T> {
|
|
/// Sift the given result, combining errors if necessary.
|
|
pub fn sift(&mut self, result: Result<T, syn::Error>) {
|
|
match result {
|
|
Ok(data) => self.items.push(data),
|
|
Err(err) => {
|
|
if let Some(ref mut errors) = self.errors {
|
|
errors.combine(err);
|
|
} else {
|
|
self.errors = Some(err);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Associated method that provides a convenient implementation for [`Iterator::fold`].
|
|
pub fn fold(mut sifter: Self, result: Result<T, syn::Error>) -> Self {
|
|
sifter.sift(result);
|
|
sifter
|
|
}
|
|
|
|
/// Complete the sifting process and return the final result.
|
|
pub fn finish(self) -> Result<Vec<T>, syn::Error> {
|
|
if let Some(errors) = self.errors {
|
|
Err(errors)
|
|
} else {
|
|
Ok(self.items)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Converts an iterator over ignore behaviour of members to a bitset of ignored members.
|
|
///
|
|
/// Takes into account the fact that always ignored (non-reflected) members are skipped.
|
|
///
|
|
/// # Example
|
|
/// ```rust,ignore
|
|
/// pub struct HelloWorld {
|
|
/// reflected_field: u32 // index: 0
|
|
///
|
|
/// #[reflect(ignore)]
|
|
/// non_reflected_field: u32 // index: N/A (not 1!)
|
|
///
|
|
/// #[reflect(skip_serializing)]
|
|
/// non_serialized_field: u32 // index: 1
|
|
/// }
|
|
/// ```
|
|
/// Would convert to the `0b01` bitset (i.e second field is NOT serialized)
|
|
///
|
|
pub(crate) fn members_to_serialization_denylist<T>(member_iter: T) -> BitSet<u32>
|
|
where
|
|
T: Iterator<Item = ReflectIgnoreBehavior>,
|
|
{
|
|
let mut bitset = BitSet::default();
|
|
|
|
member_iter.fold(0, |next_idx, member| match member {
|
|
ReflectIgnoreBehavior::IgnoreAlways => next_idx,
|
|
ReflectIgnoreBehavior::IgnoreSerialization => {
|
|
bitset.insert(next_idx);
|
|
next_idx + 1
|
|
}
|
|
ReflectIgnoreBehavior::None => next_idx + 1,
|
|
});
|
|
|
|
bitset
|
|
}
|