diff --git a/crates/bevy_reflect/derive/Cargo.toml b/crates/bevy_reflect/derive/Cargo.toml index 0df5d9af23..468e89e8fb 100644 --- a/crates/bevy_reflect/derive/Cargo.toml +++ b/crates/bevy_reflect/derive/Cargo.toml @@ -20,10 +20,9 @@ functions = [] [dependencies] bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.15.0-dev" } - -syn = { version = "2.0", features = ["full"] } proc-macro2 = "1.0" quote = "1.0" +syn = { version = "2.0", features = ["full"] } uuid = { version = "1.1", features = ["v4"] } [lints] diff --git a/crates/bevy_reflect/derive/src/attribute_parser.rs b/crates/bevy_reflect/derive/src/attribute_parser.rs new file mode 100644 index 0000000000..8696fee265 --- /dev/null +++ b/crates/bevy_reflect/derive/src/attribute_parser.rs @@ -0,0 +1,36 @@ +use syn::parse::{Parse, ParseStream, Peek}; +use syn::punctuated::Punctuated; + +/// Returns a [`syn::parse::Parser`] which parses a stream of zero or more occurrences of `T` +/// separated by punctuation of type `P`, with optional trailing punctuation. +/// +/// This is functionally the same as [`Punctuated::parse_terminated`], +/// but accepts a closure rather than a function pointer. +pub(crate) fn terminated_parser syn::Result>( + terminator: P, + mut parser: F, +) -> impl FnOnce(ParseStream) -> syn::Result> +where + P: Peek, + P::Token: Parse, +{ + let _ = terminator; + move |stream: ParseStream| { + let mut punctuated = Punctuated::new(); + + loop { + if stream.is_empty() { + break; + } + let value = parser(stream)?; + punctuated.push_value(value); + if stream.is_empty() { + break; + } + let punct = stream.parse()?; + punctuated.push_punct(punct); + } + + Ok(punctuated) + } +} diff --git a/crates/bevy_reflect/derive/src/container_attributes.rs b/crates/bevy_reflect/derive/src/container_attributes.rs index be1e5d52b4..cef874e927 100644 --- a/crates/bevy_reflect/derive/src/container_attributes.rs +++ b/crates/bevy_reflect/derive/src/container_attributes.rs @@ -5,10 +5,9 @@ //! the derive helper attribute for `Reflect`, which looks like: //! `#[reflect(PartialEq, Default, ...)]` and `#[reflect_value(PartialEq, Default, ...)]`. +use crate::attribute_parser::terminated_parser; use crate::custom_attributes::CustomAttributes; use crate::derive_data::ReflectTraitToImpl; -use crate::utility; -use crate::utility::terminated_parser; use bevy_macro_utils::fq_std::{FQAny, FQOption}; use proc_macro2::{Ident, Span}; use quote::quote_spanned; @@ -268,7 +267,7 @@ impl ContainerAttributes { let ident_name = ident.to_string(); // Create the reflect ident - let mut reflect_ident = utility::get_reflect_ident(&ident_name); + let mut reflect_ident = crate::ident::get_reflect_ident(&ident_name); // We set the span to the old ident so any compile errors point to that ident instead reflect_ident.set_span(ident.span()); diff --git a/crates/bevy_reflect/derive/src/derive_data.rs b/crates/bevy_reflect/derive/src/derive_data.rs index 7ae1d4f473..f68af17537 100644 --- a/crates/bevy_reflect/derive/src/derive_data.rs +++ b/crates/bevy_reflect/derive/src/derive_data.rs @@ -3,15 +3,17 @@ use proc_macro2::Span; use crate::container_attributes::{ContainerAttributes, FromReflectAttrs, TypePathAttrs}; use crate::field_attributes::FieldAttributes; +use crate::result_sifter::ResultSifter; +use crate::string_expr::StringExpr; use crate::type_path::parse_path_no_leading_colon; -use crate::utility::{StringExpr, WhereClauseOptions}; +use crate::where_clause_options::WhereClauseOptions; use quote::{quote, ToTokens}; use syn::token::Comma; use crate::remote::RemoteType; use crate::serialization::SerializationDataDef; use crate::{ - utility, REFLECT_ATTRIBUTE_NAME, REFLECT_VALUE_ATTRIBUTE_NAME, TYPE_NAME_ATTRIBUTE_NAME, + REFLECT_ATTRIBUTE_NAME, REFLECT_VALUE_ATTRIBUTE_NAME, TYPE_NAME_ATTRIBUTE_NAME, TYPE_PATH_ATTRIBUTE_NAME, }; use syn::punctuated::Punctuated; @@ -399,7 +401,7 @@ impl<'a> ReflectDerive<'a> { fn collect_struct_fields(fields: &'a Fields) -> Result>, syn::Error> { let mut active_index = 0; - let sifter: utility::ResultSifter> = fields + let sifter: ResultSifter> = fields .iter() .enumerate() .map( @@ -423,10 +425,7 @@ impl<'a> ReflectDerive<'a> { }) }, ) - .fold( - utility::ResultSifter::default(), - utility::ResultSifter::fold, - ); + .fold(ResultSifter::default(), ResultSifter::fold); sifter.finish() } @@ -434,7 +433,7 @@ impl<'a> ReflectDerive<'a> { fn collect_enum_variants( variants: &'a Punctuated, ) -> Result>, syn::Error> { - let sifter: utility::ResultSifter> = variants + let sifter: ResultSifter> = variants .iter() .map(|variant| -> Result { let fields = Self::collect_struct_fields(&variant.fields)?; @@ -452,10 +451,7 @@ impl<'a> ReflectDerive<'a> { doc: crate::documentation::Documentation::from_attributes(&variant.attrs), }) }) - .fold( - utility::ResultSifter::default(), - utility::ResultSifter::fold, - ); + .fold(ResultSifter::default(), ResultSifter::fold); sifter.finish() } @@ -467,7 +463,7 @@ impl<'a> ReflectMeta<'a> { attrs, type_path, remote_ty: None, - bevy_reflect_path: utility::get_bevy_reflect_path(), + bevy_reflect_path: crate::meta::get_bevy_reflect_path(), #[cfg(feature = "documentation")] docs: Default::default(), } diff --git a/crates/bevy_reflect/derive/src/enum_utility.rs b/crates/bevy_reflect/derive/src/enum_utility.rs index a279a82237..766e94b859 100644 --- a/crates/bevy_reflect/derive/src/enum_utility.rs +++ b/crates/bevy_reflect/derive/src/enum_utility.rs @@ -1,6 +1,6 @@ use crate::derive_data::StructField; use crate::field_attributes::DefaultBehavior; -use crate::{derive_data::ReflectEnum, utility::ident_or_index}; +use crate::{derive_data::ReflectEnum, ident::ident_or_index}; use bevy_macro_utils::fq_std::{FQDefault, FQOption}; use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote}; diff --git a/crates/bevy_reflect/derive/src/field_attributes.rs b/crates/bevy_reflect/derive/src/field_attributes.rs index 5228f80a67..a0ef12ef8f 100644 --- a/crates/bevy_reflect/derive/src/field_attributes.rs +++ b/crates/bevy_reflect/derive/src/field_attributes.rs @@ -4,8 +4,8 @@ //! as opposed to an entire struct or enum. An example of such an attribute is //! the derive helper attribute for `Reflect`, which looks like: `#[reflect(ignore)]`. +use crate::attribute_parser::terminated_parser; use crate::custom_attributes::CustomAttributes; -use crate::utility::terminated_parser; use crate::REFLECT_ATTRIBUTE_NAME; use quote::ToTokens; use syn::parse::ParseStream; diff --git a/crates/bevy_reflect/derive/src/from_reflect.rs b/crates/bevy_reflect/derive/src/from_reflect.rs index 9ed0d1fcaa..5bc62197b6 100644 --- a/crates/bevy_reflect/derive/src/from_reflect.rs +++ b/crates/bevy_reflect/derive/src/from_reflect.rs @@ -2,7 +2,8 @@ use crate::container_attributes::REFLECT_DEFAULT; use crate::derive_data::ReflectEnum; use crate::enum_utility::{EnumVariantOutputData, FromReflectVariantBuilder, VariantBuilder}; use crate::field_attributes::DefaultBehavior; -use crate::utility::{ident_or_index, WhereClauseOptions}; +use crate::ident::ident_or_index; +use crate::where_clause_options::WhereClauseOptions; use crate::{ReflectMeta, ReflectStruct}; use bevy_macro_utils::fq_std::{FQClone, FQDefault, FQOption}; use proc_macro2::Span; diff --git a/crates/bevy_reflect/derive/src/ident.rs b/crates/bevy_reflect/derive/src/ident.rs new file mode 100644 index 0000000000..f14c7ff98f --- /dev/null +++ b/crates/bevy_reflect/derive/src/ident.rs @@ -0,0 +1,45 @@ +use proc_macro2::{Ident, Span}; +use syn::Member; + +/// Returns the "reflected" ident for a given string. +/// +/// # Example +/// +/// ``` +/// # use proc_macro2::Ident; +/// # // We can't import this method because of its visibility. +/// # fn get_reflect_ident(name: &str) -> Ident { +/// # let reflected = format!("Reflect{name}"); +/// # Ident::new(&reflected, proc_macro2::Span::call_site()) +/// # } +/// 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()) +} + +/// 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. +/// +/// ``` +/// 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 contexts where you are declaring either +/// a tuple struct or a struct with named fields. If you don't have a field name, +/// it means that you must access the field through an index. +pub(crate) fn ident_or_index(ident: Option<&Ident>, index: usize) -> Member { + ident.map_or_else( + || Member::Unnamed(index.into()), + |ident| Member::Named(ident.clone()), + ) +} diff --git a/crates/bevy_reflect/derive/src/impls/common.rs b/crates/bevy_reflect/derive/src/impls/common.rs index f01fd96e04..12f324c330 100644 --- a/crates/bevy_reflect/derive/src/impls/common.rs +++ b/crates/bevy_reflect/derive/src/impls/common.rs @@ -2,7 +2,7 @@ use bevy_macro_utils::fq_std::{FQAny, FQBox, FQOption, FQResult}; use quote::quote; -use crate::{derive_data::ReflectMeta, utility::WhereClauseOptions}; +use crate::{derive_data::ReflectMeta, where_clause_options::WhereClauseOptions}; pub fn impl_full_reflect( meta: &ReflectMeta, diff --git a/crates/bevy_reflect/derive/src/impls/func/from_arg.rs b/crates/bevy_reflect/derive/src/impls/func/from_arg.rs index 9757dd415c..ec663a103e 100644 --- a/crates/bevy_reflect/derive/src/impls/func/from_arg.rs +++ b/crates/bevy_reflect/derive/src/impls/func/from_arg.rs @@ -1,5 +1,5 @@ use crate::derive_data::ReflectMeta; -use crate::utility::WhereClauseOptions; +use crate::where_clause_options::WhereClauseOptions; use bevy_macro_utils::fq_std::FQResult; use quote::quote; diff --git a/crates/bevy_reflect/derive/src/impls/func/function_impls.rs b/crates/bevy_reflect/derive/src/impls/func/function_impls.rs index 3b42203d41..1696f906f6 100644 --- a/crates/bevy_reflect/derive/src/impls/func/function_impls.rs +++ b/crates/bevy_reflect/derive/src/impls/func/function_impls.rs @@ -2,7 +2,7 @@ use crate::derive_data::ReflectMeta; use crate::impls::func::from_arg::impl_from_arg; use crate::impls::func::get_ownership::impl_get_ownership; use crate::impls::func::into_return::impl_into_return; -use crate::utility::WhereClauseOptions; +use crate::where_clause_options::WhereClauseOptions; use quote::quote; pub(crate) fn impl_function_traits( diff --git a/crates/bevy_reflect/derive/src/impls/func/get_ownership.rs b/crates/bevy_reflect/derive/src/impls/func/get_ownership.rs index 65f75c7a64..f04a9cad39 100644 --- a/crates/bevy_reflect/derive/src/impls/func/get_ownership.rs +++ b/crates/bevy_reflect/derive/src/impls/func/get_ownership.rs @@ -1,5 +1,5 @@ use crate::derive_data::ReflectMeta; -use crate::utility::WhereClauseOptions; +use crate::where_clause_options::WhereClauseOptions; use quote::quote; pub(crate) fn impl_get_ownership( diff --git a/crates/bevy_reflect/derive/src/impls/func/into_return.rs b/crates/bevy_reflect/derive/src/impls/func/into_return.rs index db964b3cbb..7359184e62 100644 --- a/crates/bevy_reflect/derive/src/impls/func/into_return.rs +++ b/crates/bevy_reflect/derive/src/impls/func/into_return.rs @@ -1,5 +1,5 @@ use crate::derive_data::ReflectMeta; -use crate::utility::WhereClauseOptions; +use crate::where_clause_options::WhereClauseOptions; use quote::quote; pub(crate) fn impl_into_return( diff --git a/crates/bevy_reflect/derive/src/impls/typed.rs b/crates/bevy_reflect/derive/src/impls/typed.rs index d407c1c88e..17ea342107 100644 --- a/crates/bevy_reflect/derive/src/impls/typed.rs +++ b/crates/bevy_reflect/derive/src/impls/typed.rs @@ -1,17 +1,16 @@ -use crate::utility::{StringExpr, WhereClauseOptions}; +use crate::derive_data::{ReflectMeta, ReflectTypePath}; +use crate::string_expr::StringExpr; +use crate::where_clause_options::WhereClauseOptions; +use bevy_macro_utils::fq_std::FQOption; +use proc_macro2::TokenStream; use quote::{quote, ToTokens}; -use crate::{ - derive_data::{ReflectMeta, ReflectTypePath}, - utility::wrap_in_option, -}; - /// Returns an expression for a `NonGenericTypeCell` or `GenericTypeCell` to generate `'static` references. fn static_type_cell( meta: &ReflectMeta, property: TypedProperty, - generator: proc_macro2::TokenStream, -) -> proc_macro2::TokenStream { + generator: TokenStream, +) -> TokenStream { let bevy_reflect_path = meta.bevy_reflect_path(); if meta.type_path().impl_is_generic() { let cell_type = match property { @@ -48,11 +47,11 @@ pub(crate) enum TypedProperty { TypePath, } -pub(crate) fn impl_type_path(meta: &ReflectMeta) -> proc_macro2::TokenStream { +pub(crate) fn impl_type_path(meta: &ReflectMeta) -> TokenStream { let where_clause_options = WhereClauseOptions::new(meta); if !meta.attrs().type_path_attrs().should_auto_derive() { - return proc_macro2::TokenStream::new(); + return TokenStream::new(); } let type_path = meta.type_path(); @@ -132,8 +131,8 @@ pub(crate) fn impl_type_path(meta: &ReflectMeta) -> proc_macro2::TokenStream { pub(crate) fn impl_typed( meta: &ReflectMeta, where_clause_options: &WhereClauseOptions, - type_info_generator: proc_macro2::TokenStream, -) -> proc_macro2::TokenStream { + type_info_generator: TokenStream, +) -> TokenStream { let type_path = meta.type_path(); let bevy_reflect_path = meta.bevy_reflect_path(); @@ -151,3 +150,15 @@ pub(crate) fn impl_typed( } } } + +/// Turns an `Option` into a `TokenStream` for an `Option`. +fn wrap_in_option(tokens: Option) -> TokenStream { + match tokens { + Some(tokens) => quote! { + #FQOption::Some(#tokens) + }, + None => quote! { + #FQOption::None + }, + } +} diff --git a/crates/bevy_reflect/derive/src/impls/values.rs b/crates/bevy_reflect/derive/src/impls/values.rs index 3a804f3bed..f2ff2a82d2 100644 --- a/crates/bevy_reflect/derive/src/impls/values.rs +++ b/crates/bevy_reflect/derive/src/impls/values.rs @@ -1,5 +1,5 @@ use crate::impls::{common_partial_reflect_methods, impl_full_reflect, impl_type_path, impl_typed}; -use crate::utility::WhereClauseOptions; +use crate::where_clause_options::WhereClauseOptions; use crate::ReflectMeta; use bevy_macro_utils::fq_std::{FQBox, FQClone, FQOption, FQResult}; use quote::quote; diff --git a/crates/bevy_reflect/derive/src/lib.rs b/crates/bevy_reflect/derive/src/lib.rs index 2fdb055b60..38f4488e6d 100644 --- a/crates/bevy_reflect/derive/src/lib.rs +++ b/crates/bevy_reflect/derive/src/lib.rs @@ -16,6 +16,7 @@ extern crate proc_macro; +mod attribute_parser; mod container_attributes; mod custom_attributes; mod derive_data; @@ -24,15 +25,19 @@ mod documentation; mod enum_utility; mod field_attributes; mod from_reflect; +mod ident; mod impls; +mod meta; mod reflect_value; mod registration; mod remote; +mod result_sifter; mod serialization; +mod string_expr; mod struct_utility; mod trait_reflection; mod type_path; -mod utility; +mod where_clause_options; use crate::derive_data::{ReflectDerive, ReflectMeta, ReflectStruct}; use container_attributes::ContainerAttributes; diff --git a/crates/bevy_reflect/derive/src/meta.rs b/crates/bevy_reflect/derive/src/meta.rs new file mode 100644 index 0000000000..c759634f3b --- /dev/null +++ b/crates/bevy_reflect/derive/src/meta.rs @@ -0,0 +1,7 @@ +use bevy_macro_utils::BevyManifest; +use syn::Path; + +/// Returns the correct path for `bevy_reflect`. +pub(crate) fn get_bevy_reflect_path() -> Path { + BevyManifest::get_path_direct("bevy_reflect") +} diff --git a/crates/bevy_reflect/derive/src/registration.rs b/crates/bevy_reflect/derive/src/registration.rs index 5d17e8954e..6210b4358b 100644 --- a/crates/bevy_reflect/derive/src/registration.rs +++ b/crates/bevy_reflect/derive/src/registration.rs @@ -2,7 +2,7 @@ use crate::derive_data::ReflectMeta; use crate::serialization::SerializationDataDef; -use crate::utility::WhereClauseOptions; +use crate::where_clause_options::WhereClauseOptions; use quote::quote; use syn::Type; diff --git a/crates/bevy_reflect/derive/src/remote.rs b/crates/bevy_reflect/derive/src/remote.rs index 9a60f930cf..c8194caeca 100644 --- a/crates/bevy_reflect/derive/src/remote.rs +++ b/crates/bevy_reflect/derive/src/remote.rs @@ -1,6 +1,6 @@ use crate::derive_data::{ReflectImplSource, ReflectProvenance, ReflectTraitToImpl}; +use crate::ident::ident_or_index; use crate::impls::impl_assertions; -use crate::utility::ident_or_index; use crate::{ from_reflect, impls, ReflectDerive, REFLECT_ATTRIBUTE_NAME, REFLECT_VALUE_ATTRIBUTE_NAME, }; diff --git a/crates/bevy_reflect/derive/src/result_sifter.rs b/crates/bevy_reflect/derive/src/result_sifter.rs new file mode 100644 index 0000000000..0e184738a6 --- /dev/null +++ b/crates/bevy_reflect/derive/src/result_sifter.rs @@ -0,0 +1,46 @@ +/// Helper struct used to process an iterator of `Result, syn::Error>`, +/// combining errors into one along the way. +pub(crate) struct ResultSifter { + items: Vec, + errors: Option, +} + +impl Default for ResultSifter { + fn default() -> Self { + Self { + items: Vec::new(), + errors: None, + } + } +} + +impl ResultSifter { + /// Sift the given result, combining errors if necessary. + pub fn sift(&mut self, result: Result) { + 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) -> Self { + sifter.sift(result); + sifter + } + + /// Complete the sifting process and return the final result. + pub fn finish(self) -> Result, syn::Error> { + if let Some(errors) = self.errors { + Err(errors) + } else { + Ok(self.items) + } + } +} diff --git a/crates/bevy_reflect/derive/src/string_expr.rs b/crates/bevy_reflect/derive/src/string_expr.rs new file mode 100644 index 0000000000..1284736fc9 --- /dev/null +++ b/crates/bevy_reflect/derive/src/string_expr.rs @@ -0,0 +1,106 @@ +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; +use syn::{spanned::Spanned, LitStr}; + +/// Contains tokens representing different kinds of string. +#[derive(Clone)] +pub(crate) enum StringExpr { + /// A string that is valid at compile time. + /// + /// This is either a string literal like `"mystring"`, + /// or a string created by a macro like [`module_path`] + /// or [`concat`]. + Const(TokenStream), + /// A [string slice](str) that is borrowed for a `'static` lifetime. + Borrowed(TokenStream), + /// An [owned string](String). + Owned(TokenStream), +} + +impl From for StringExpr { + fn from(value: T) -> Self { + Self::from_lit(&LitStr::new(&value.to_string(), value.span())) + } +} + +impl StringExpr { + /// Creates a [constant] [`StringExpr`] from a [`struct@LitStr`]. + /// + /// [constant]: StringExpr::Const + pub fn from_lit(lit: &LitStr) -> Self { + Self::Const(lit.to_token_stream()) + } + + /// Creates a [constant] [`StringExpr`] by interpreting a [string slice][str] as a [`struct@LitStr`]. + /// + /// [constant]: StringExpr::Const + pub fn from_str(string: &str) -> Self { + Self::Const(string.into_token_stream()) + } + + /// Returns tokens for an [owned string](String). + /// + /// The returned expression will allocate unless the [`StringExpr`] is [already owned]. + /// + /// [already owned]: StringExpr::Owned + pub fn into_owned(self) -> TokenStream { + match self { + Self::Const(tokens) | Self::Borrowed(tokens) => quote! { + ::std::string::ToString::to_string(#tokens) + }, + Self::Owned(owned) => owned, + } + } + + /// Returns tokens for a statically borrowed [string slice](str). + pub fn into_borrowed(self) -> TokenStream { + match self { + Self::Const(tokens) | Self::Borrowed(tokens) => tokens, + Self::Owned(owned) => quote! { + &#owned + }, + } + } + + /// Appends a [`StringExpr`] to another. + /// + /// If both expressions are [`StringExpr::Const`] this will use [`concat`] to merge them. + pub fn appended_by(mut self, other: StringExpr) -> Self { + if let Self::Const(tokens) = self { + if let Self::Const(more) = other { + return Self::Const(quote! { + ::core::concat!(#tokens, #more) + }); + } + self = Self::Const(tokens); + } + + let owned = self.into_owned(); + let borrowed = other.into_borrowed(); + Self::Owned(quote! { + #owned + #borrowed + }) + } +} + +impl Default for StringExpr { + fn default() -> Self { + StringExpr::from_str("") + } +} + +impl FromIterator for StringExpr { + fn from_iter>(iter: T) -> Self { + let mut iter = iter.into_iter(); + match iter.next() { + Some(mut expr) => { + for next in iter { + expr = expr.appended_by(next); + } + + expr + } + None => Default::default(), + } + } +} diff --git a/crates/bevy_reflect/derive/src/struct_utility.rs b/crates/bevy_reflect/derive/src/struct_utility.rs index 2320e8b86f..df9279c874 100644 --- a/crates/bevy_reflect/derive/src/struct_utility.rs +++ b/crates/bevy_reflect/derive/src/struct_utility.rs @@ -1,5 +1,5 @@ use crate::derive_data::StructField; -use crate::{utility, ReflectStruct}; +use crate::ReflectStruct; use quote::quote; /// A helper struct for creating remote-aware field accessors. @@ -65,8 +65,10 @@ impl FieldAccessors { reflect_struct .active_fields() .map(|field| { - let member = - utility::ident_or_index(field.data.ident.as_ref(), field.declaration_index); + let member = crate::ident::ident_or_index( + field.data.ident.as_ref(), + field.declaration_index, + ); let accessor = if is_remote { quote!(self.0.#member) } else { diff --git a/crates/bevy_reflect/derive/src/trait_reflection.rs b/crates/bevy_reflect/derive/src/trait_reflection.rs index b8ea651b88..6516c5ab7e 100644 --- a/crates/bevy_reflect/derive/src/trait_reflection.rs +++ b/crates/bevy_reflect/derive/src/trait_reflection.rs @@ -33,7 +33,7 @@ pub(crate) fn reflect_trait(_args: &TokenStream, input: TokenStream) -> TokenStr let item_trait = &trait_info.item_trait; let trait_ident = &item_trait.ident; let trait_vis = &item_trait.vis; - let reflect_trait_ident = crate::utility::get_reflect_ident(&item_trait.ident.to_string()); + let reflect_trait_ident = crate::ident::get_reflect_ident(&item_trait.ident.to_string()); let bevy_reflect_path = BevyManifest::default().get_path("bevy_reflect"); let struct_doc = format!( diff --git a/crates/bevy_reflect/derive/src/utility.rs b/crates/bevy_reflect/derive/src/utility.rs deleted file mode 100644 index 15194d0fa7..0000000000 --- a/crates/bevy_reflect/derive/src/utility.rs +++ /dev/null @@ -1,451 +0,0 @@ -//! General-purpose utility functions for internal usage within this crate. - -use crate::derive_data::ReflectMeta; -use bevy_macro_utils::{ - fq_std::{FQAny, FQOption, FQSend, FQSync}, - BevyManifest, -}; -use proc_macro2::{Ident, Span, TokenStream}; -use quote::{quote, ToTokens}; -use syn::parse::{Parse, ParseStream, Peek}; -use syn::punctuated::Punctuated; -use syn::{spanned::Spanned, LitStr, Member, Path, Token, 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 -/// -/// ``` -/// # use proc_macro2::Ident; -/// # // We can't import this method because of its visibility. -/// # fn get_reflect_ident(name: &str) -> Ident { -/// # let reflected = format!("Reflect{name}"); -/// # Ident::new(&reflected, proc_macro2::Span::call_site()) -/// # } -/// 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, syn::Error>`, -/// combining errors into one along the way. -pub(crate) struct ResultSifter { - items: Vec, - errors: Option, -} - -/// 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 { - ident.map_or_else( - || Member::Unnamed(index.into()), - |ident| Member::Named(ident.clone()), - ) -} - -/// Options defining how to extend the `where` clause for reflection. -pub(crate) struct WhereClauseOptions<'a, 'b> { - meta: &'a ReflectMeta<'b>, - active_fields: Box<[Type]>, -} - -impl<'a, 'b> WhereClauseOptions<'a, 'b> { - pub fn new(meta: &'a ReflectMeta<'b>) -> Self { - Self { - meta, - active_fields: Box::new([]), - } - } - - pub fn new_with_fields(meta: &'a ReflectMeta<'b>, active_fields: Box<[Type]>) -> Self { - Self { - meta, - active_fields, - } - } - - /// Extends the `where` clause for a type with additional bounds needed for the reflection impls. - /// - /// The default bounds added are as follows: - /// - `Self` has the bounds `Any + Send + Sync` - /// - Type parameters have the bound `TypePath` unless `#[reflect(type_path = false)]` is present - /// - Active fields have the bounds `TypePath` and either `PartialReflect` if `#[reflect(from_reflect = false)]` is present - /// or `FromReflect` otherwise (or no bounds at all if `#[reflect(no_field_bounds)]` is present) - /// - /// When the derive is used with `#[reflect(where)]`, the bounds specified in the attribute are added as well. - /// - /// # Example - /// - /// ```ignore (bevy_reflect is not accessible from this crate) - /// #[derive(Reflect)] - /// struct Foo { - /// a: T, - /// #[reflect(ignore)] - /// b: U - /// } - /// ``` - /// - /// Generates the following where clause: - /// - /// ```ignore (bevy_reflect is not accessible from this crate) - /// where - /// // `Self` bounds: - /// Self: Any + Send + Sync, - /// // Type parameter bounds: - /// T: TypePath, - /// U: TypePath, - /// // Field bounds - /// T: FromReflect + TypePath, - /// ``` - /// - /// If we had added `#[reflect(where T: MyTrait)]` to the type, it would instead generate: - /// - /// ```ignore (bevy_reflect is not accessible from this crate) - /// where - /// // `Self` bounds: - /// Self: Any + Send + Sync, - /// // Type parameter bounds: - /// T: TypePath, - /// U: TypePath, - /// // Field bounds - /// T: FromReflect + TypePath, - /// // Custom bounds - /// T: MyTrait, - /// ``` - /// - /// And if we also added `#[reflect(no_field_bounds)]` to the type, it would instead generate: - /// - /// ```ignore (bevy_reflect is not accessible from this crate) - /// where - /// // `Self` bounds: - /// Self: Any + Send + Sync, - /// // Type parameter bounds: - /// T: TypePath, - /// U: TypePath, - /// // Custom bounds - /// T: MyTrait, - /// ``` - pub fn extend_where_clause(&self, where_clause: Option<&WhereClause>) -> TokenStream { - // We would normally just use `Self`, but that won't work for generating things like assertion functions - // and trait impls for a type's reference (e.g. `impl FromArg for &MyType`) - let this = self.meta.type_path().true_type(); - - let required_bounds = self.required_bounds(); - - // Maintain existing where clause, if any. - let mut generic_where_clause = if let Some(where_clause) = where_clause { - let predicates = where_clause.predicates.iter(); - quote! {where #this: #required_bounds, #(#predicates,)*} - } else { - quote!(where #this: #required_bounds,) - }; - - // Add additional reflection trait bounds - let predicates = self.predicates(); - generic_where_clause.extend(quote! { - #predicates - }); - - generic_where_clause - } - - /// Returns an iterator the where clause predicates to extended the where clause with. - fn predicates(&self) -> Punctuated { - let mut predicates = Punctuated::new(); - - if let Some(type_param_predicates) = self.type_param_predicates() { - predicates.extend(type_param_predicates); - } - - if let Some(field_predicates) = self.active_field_predicates() { - predicates.extend(field_predicates); - } - - if let Some(custom_where) = self.meta.attrs().custom_where() { - predicates.push(custom_where.predicates.to_token_stream()); - } - - predicates - } - - /// Returns an iterator over the where clause predicates for the type parameters - /// if they require one. - fn type_param_predicates(&self) -> Option + '_> { - self.type_path_bound().map(|type_path_bound| { - self.meta - .type_path() - .generics() - .type_params() - .map(move |param| { - let ident = ¶m.ident; - - quote!(#ident : #type_path_bound) - }) - }) - } - - /// Returns an iterator over the where clause predicates for the active fields. - fn active_field_predicates(&self) -> Option + '_> { - if self.meta.attrs().no_field_bounds() { - None - } else { - let bevy_reflect_path = self.meta.bevy_reflect_path(); - let reflect_bound = self.reflect_bound(); - - // `TypePath` is always required for active fields since they are used to - // construct `NamedField` and `UnnamedField` instances for the `Typed` impl. - // Likewise, `GetTypeRegistration` is always required for active fields since - // they are used to register the type's dependencies. - Some(self.active_fields.iter().map(move |ty| { - quote!( - #ty : #reflect_bound - + #bevy_reflect_path::TypePath - // Needed for `Typed` impls - + #bevy_reflect_path::MaybeTyped - // Needed for `GetTypeRegistration` impls - + #bevy_reflect_path::__macro_exports::RegisterForReflection - ) - })) - } - } - - /// The `PartialReflect` or `FromReflect` bound to use based on `#[reflect(from_reflect = false)]`. - fn reflect_bound(&self) -> TokenStream { - let bevy_reflect_path = self.meta.bevy_reflect_path(); - - if self.meta.from_reflect().should_auto_derive() { - quote!(#bevy_reflect_path::FromReflect) - } else { - quote!(#bevy_reflect_path::PartialReflect) - } - } - - /// The `TypePath` bounds to use based on `#[reflect(type_path = false)]`. - fn type_path_bound(&self) -> Option { - if self.meta.type_path_attrs().should_auto_derive() { - let bevy_reflect_path = self.meta.bevy_reflect_path(); - Some(quote!(#bevy_reflect_path::TypePath)) - } else { - None - } - } - - /// The minimum required bounds for a type to be reflected. - fn required_bounds(&self) -> TokenStream { - quote!(#FQAny + #FQSend + #FQSync) - } -} - -impl Default for ResultSifter { - fn default() -> Self { - Self { - items: Vec::new(), - errors: None, - } - } -} - -impl ResultSifter { - /// Sift the given result, combining errors if necessary. - pub fn sift(&mut self, result: Result) { - 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) -> Self { - sifter.sift(result); - sifter - } - - /// Complete the sifting process and return the final result. - pub fn finish(self) -> Result, syn::Error> { - if let Some(errors) = self.errors { - Err(errors) - } else { - Ok(self.items) - } - } -} - -/// Turns an `Option` into a `TokenStream` for an `Option`. -pub(crate) fn wrap_in_option(tokens: Option) -> TokenStream { - match tokens { - Some(tokens) => quote! { - #FQOption::Some(#tokens) - }, - None => quote! { - #FQOption::None - }, - } -} - -/// Contains tokens representing different kinds of string. -#[derive(Clone)] -pub(crate) enum StringExpr { - /// A string that is valid at compile time. - /// - /// This is either a string literal like `"mystring"`, - /// or a string created by a macro like [`module_path`] - /// or [`concat`]. - Const(TokenStream), - /// A [string slice](str) that is borrowed for a `'static` lifetime. - Borrowed(TokenStream), - /// An [owned string](String). - Owned(TokenStream), -} - -impl From for StringExpr { - fn from(value: T) -> Self { - Self::from_lit(&LitStr::new(&value.to_string(), value.span())) - } -} - -impl StringExpr { - /// Creates a [constant] [`StringExpr`] from a [`struct@LitStr`]. - /// - /// [constant]: StringExpr::Const - pub fn from_lit(lit: &LitStr) -> Self { - Self::Const(lit.to_token_stream()) - } - - /// Creates a [constant] [`StringExpr`] by interpreting a [string slice][str] as a [`struct@LitStr`]. - /// - /// [constant]: StringExpr::Const - pub fn from_str(string: &str) -> Self { - Self::Const(string.into_token_stream()) - } - - /// Returns tokens for an [owned string](String). - /// - /// The returned expression will allocate unless the [`StringExpr`] is [already owned]. - /// - /// [already owned]: StringExpr::Owned - pub fn into_owned(self) -> TokenStream { - match self { - Self::Const(tokens) | Self::Borrowed(tokens) => quote! { - ::std::string::ToString::to_string(#tokens) - }, - Self::Owned(owned) => owned, - } - } - - /// Returns tokens for a statically borrowed [string slice](str). - pub fn into_borrowed(self) -> TokenStream { - match self { - Self::Const(tokens) | Self::Borrowed(tokens) => tokens, - Self::Owned(owned) => quote! { - &#owned - }, - } - } - - /// Appends a [`StringExpr`] to another. - /// - /// If both expressions are [`StringExpr::Const`] this will use [`concat`] to merge them. - pub fn appended_by(mut self, other: StringExpr) -> Self { - if let Self::Const(tokens) = self { - if let Self::Const(more) = other { - return Self::Const(quote! { - ::core::concat!(#tokens, #more) - }); - } - self = Self::Const(tokens); - } - - let owned = self.into_owned(); - let borrowed = other.into_borrowed(); - Self::Owned(quote! { - #owned + #borrowed - }) - } -} - -impl Default for StringExpr { - fn default() -> Self { - StringExpr::from_str("") - } -} - -impl FromIterator for StringExpr { - fn from_iter>(iter: T) -> Self { - let mut iter = iter.into_iter(); - match iter.next() { - Some(mut expr) => { - for next in iter { - expr = expr.appended_by(next); - } - - expr - } - None => Default::default(), - } - } -} - -/// Returns a [`syn::parse::Parser`] which parses a stream of zero or more occurrences of `T` -/// separated by punctuation of type `P`, with optional trailing punctuation. -/// -/// This is functionally the same as [`Punctuated::parse_terminated`], -/// but accepts a closure rather than a function pointer. -pub(crate) fn terminated_parser syn::Result>( - terminator: P, - mut parser: F, -) -> impl FnOnce(ParseStream) -> syn::Result> -where - P: Peek, - P::Token: Parse, -{ - let _ = terminator; - move |stream: ParseStream| { - let mut punctuated = Punctuated::new(); - - loop { - if stream.is_empty() { - break; - } - let value = parser(stream)?; - punctuated.push_value(value); - if stream.is_empty() { - break; - } - let punct = stream.parse()?; - punctuated.push_punct(punct); - } - - Ok(punctuated) - } -} diff --git a/crates/bevy_reflect/derive/src/where_clause_options.rs b/crates/bevy_reflect/derive/src/where_clause_options.rs new file mode 100644 index 0000000000..1ada82c534 --- /dev/null +++ b/crates/bevy_reflect/derive/src/where_clause_options.rs @@ -0,0 +1,199 @@ +use crate::derive_data::ReflectMeta; +use bevy_macro_utils::fq_std::{FQAny, FQSend, FQSync}; +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; +use syn::punctuated::Punctuated; +use syn::{Token, Type, WhereClause}; + +/// Options defining how to extend the `where` clause for reflection. +pub(crate) struct WhereClauseOptions<'a, 'b> { + meta: &'a ReflectMeta<'b>, + active_fields: Box<[Type]>, +} + +impl<'a, 'b> WhereClauseOptions<'a, 'b> { + pub fn new(meta: &'a ReflectMeta<'b>) -> Self { + Self { + meta, + active_fields: Box::new([]), + } + } + + pub fn new_with_fields(meta: &'a ReflectMeta<'b>, active_fields: Box<[Type]>) -> Self { + Self { + meta, + active_fields, + } + } + + /// Extends the `where` clause for a type with additional bounds needed for the reflection impls. + /// + /// The default bounds added are as follows: + /// - `Self` has the bounds `Any + Send + Sync` + /// - Type parameters have the bound `TypePath` unless `#[reflect(type_path = false)]` is present + /// - Active fields have the bounds `TypePath` and either `PartialReflect` if `#[reflect(from_reflect = false)]` is present + /// or `FromReflect` otherwise (or no bounds at all if `#[reflect(no_field_bounds)]` is present) + /// + /// When the derive is used with `#[reflect(where)]`, the bounds specified in the attribute are added as well. + /// + /// # Example + /// + /// ```ignore (bevy_reflect is not accessible from this crate) + /// #[derive(Reflect)] + /// struct Foo { + /// a: T, + /// #[reflect(ignore)] + /// b: U + /// } + /// ``` + /// + /// Generates the following where clause: + /// + /// ```ignore (bevy_reflect is not accessible from this crate) + /// where + /// // `Self` bounds: + /// Self: Any + Send + Sync, + /// // Type parameter bounds: + /// T: TypePath, + /// U: TypePath, + /// // Field bounds + /// T: FromReflect + TypePath, + /// ``` + /// + /// If we had added `#[reflect(where T: MyTrait)]` to the type, it would instead generate: + /// + /// ```ignore (bevy_reflect is not accessible from this crate) + /// where + /// // `Self` bounds: + /// Self: Any + Send + Sync, + /// // Type parameter bounds: + /// T: TypePath, + /// U: TypePath, + /// // Field bounds + /// T: FromReflect + TypePath, + /// // Custom bounds + /// T: MyTrait, + /// ``` + /// + /// And if we also added `#[reflect(no_field_bounds)]` to the type, it would instead generate: + /// + /// ```ignore (bevy_reflect is not accessible from this crate) + /// where + /// // `Self` bounds: + /// Self: Any + Send + Sync, + /// // Type parameter bounds: + /// T: TypePath, + /// U: TypePath, + /// // Custom bounds + /// T: MyTrait, + /// ``` + pub fn extend_where_clause(&self, where_clause: Option<&WhereClause>) -> TokenStream { + // We would normally just use `Self`, but that won't work for generating things like assertion functions + // and trait impls for a type's reference (e.g. `impl FromArg for &MyType`) + let this = self.meta.type_path().true_type(); + + let required_bounds = self.required_bounds(); + + // Maintain existing where clause, if any. + let mut generic_where_clause = if let Some(where_clause) = where_clause { + let predicates = where_clause.predicates.iter(); + quote! {where #this: #required_bounds, #(#predicates,)*} + } else { + quote!(where #this: #required_bounds,) + }; + + // Add additional reflection trait bounds + let predicates = self.predicates(); + generic_where_clause.extend(quote! { + #predicates + }); + + generic_where_clause + } + + /// Returns an iterator the where clause predicates to extended the where clause with. + fn predicates(&self) -> Punctuated { + let mut predicates = Punctuated::new(); + + if let Some(type_param_predicates) = self.type_param_predicates() { + predicates.extend(type_param_predicates); + } + + if let Some(field_predicates) = self.active_field_predicates() { + predicates.extend(field_predicates); + } + + if let Some(custom_where) = self.meta.attrs().custom_where() { + predicates.push(custom_where.predicates.to_token_stream()); + } + + predicates + } + + /// Returns an iterator over the where clause predicates for the type parameters + /// if they require one. + fn type_param_predicates(&self) -> Option + '_> { + self.type_path_bound().map(|type_path_bound| { + self.meta + .type_path() + .generics() + .type_params() + .map(move |param| { + let ident = ¶m.ident; + + quote!(#ident : #type_path_bound) + }) + }) + } + + /// Returns an iterator over the where clause predicates for the active fields. + fn active_field_predicates(&self) -> Option + '_> { + if self.meta.attrs().no_field_bounds() { + None + } else { + let bevy_reflect_path = self.meta.bevy_reflect_path(); + let reflect_bound = self.reflect_bound(); + + // `TypePath` is always required for active fields since they are used to + // construct `NamedField` and `UnnamedField` instances for the `Typed` impl. + // Likewise, `GetTypeRegistration` is always required for active fields since + // they are used to register the type's dependencies. + Some(self.active_fields.iter().map(move |ty| { + quote!( + #ty : #reflect_bound + + #bevy_reflect_path::TypePath + // Needed for `Typed` impls + + #bevy_reflect_path::MaybeTyped + // Needed for `GetTypeRegistration` impls + + #bevy_reflect_path::__macro_exports::RegisterForReflection + ) + })) + } + } + + /// The `PartialReflect` or `FromReflect` bound to use based on `#[reflect(from_reflect = false)]`. + fn reflect_bound(&self) -> TokenStream { + let bevy_reflect_path = self.meta.bevy_reflect_path(); + + if self.meta.from_reflect().should_auto_derive() { + quote!(#bevy_reflect_path::FromReflect) + } else { + quote!(#bevy_reflect_path::PartialReflect) + } + } + + /// The `TypePath` bounds to use based on `#[reflect(type_path = false)]`. + fn type_path_bound(&self) -> Option { + if self.meta.type_path_attrs().should_auto_derive() { + let bevy_reflect_path = self.meta.bevy_reflect_path(); + Some(quote!(#bevy_reflect_path::TypePath)) + } else { + None + } + } + + /// The minimum required bounds for a type to be reflected. + fn required_bounds(&self) -> TokenStream { + quote!(#FQAny + #FQSend + #FQSync) + } +}