mirror of
https://github.com/bevyengine/bevy
synced 2024-12-24 03:53:06 +00:00
30d84519a2
# Objective Bevy seems to want to standardize on "American English" spellings. Not sure if this is laid out anywhere in writing, but see also #15947. While perusing the docs for `typos`, I noticed that it has a `locale` config option and tried it out. ## Solution Switch to `en-us` locale in the `typos` config and run `typos -w` ## Migration Guide The following methods or fields have been renamed from `*dependants*` to `*dependents*`. - `ProcessorAssetInfo::dependants` - `ProcessorAssetInfos::add_dependant` - `ProcessorAssetInfos::non_existent_dependants` - `AssetInfo::dependants_waiting_on_load` - `AssetInfo::dependants_waiting_on_recursive_dep_load` - `AssetInfos::loader_dependants` - `AssetInfos::remove_dependants_and_labels`
1208 lines
41 KiB
Rust
1208 lines
41 KiB
Rust
use core::fmt;
|
|
use proc_macro2::Span;
|
|
|
|
use crate::{
|
|
container_attributes::{ContainerAttributes, FromReflectAttrs, TypePathAttrs},
|
|
field_attributes::FieldAttributes,
|
|
remote::RemoteType,
|
|
result_sifter::ResultSifter,
|
|
serialization::SerializationDataDef,
|
|
string_expr::StringExpr,
|
|
type_path::parse_path_no_leading_colon,
|
|
where_clause_options::WhereClauseOptions,
|
|
REFLECT_ATTRIBUTE_NAME, TYPE_NAME_ATTRIBUTE_NAME, TYPE_PATH_ATTRIBUTE_NAME,
|
|
};
|
|
use quote::{quote, ToTokens};
|
|
use syn::token::Comma;
|
|
|
|
use crate::generics::generate_generics;
|
|
use syn::{
|
|
parse_str, punctuated::Punctuated, spanned::Spanned, Data, DeriveInput, Field, Fields,
|
|
GenericParam, Generics, Ident, LitStr, Meta, Path, PathSegment, Type, TypeParam, Variant,
|
|
};
|
|
|
|
pub(crate) enum ReflectDerive<'a> {
|
|
Struct(ReflectStruct<'a>),
|
|
TupleStruct(ReflectStruct<'a>),
|
|
UnitStruct(ReflectStruct<'a>),
|
|
Enum(ReflectEnum<'a>),
|
|
Opaque(ReflectMeta<'a>),
|
|
}
|
|
|
|
/// Metadata present on all reflected types, including name, generics, and attributes.
|
|
///
|
|
/// # Example
|
|
///
|
|
/// ```ignore (bevy_reflect is not accessible from this crate)
|
|
/// #[derive(Reflect)]
|
|
/// // traits
|
|
/// // |----------------------------------------|
|
|
/// #[reflect(PartialEq, Serialize, Deserialize, Default)]
|
|
/// // type_path generics
|
|
/// // |-------------------||----------|
|
|
/// struct ThingThatImReflecting<T1, T2, T3> {/* ... */}
|
|
/// ```
|
|
pub(crate) struct ReflectMeta<'a> {
|
|
/// The registered traits for this type.
|
|
attrs: ContainerAttributes,
|
|
/// The path to this type.
|
|
type_path: ReflectTypePath<'a>,
|
|
/// The optional remote type to use instead of the actual type.
|
|
remote_ty: Option<RemoteType<'a>>,
|
|
/// A cached instance of the path to the `bevy_reflect` crate.
|
|
bevy_reflect_path: Path,
|
|
/// The documentation for this type, if any
|
|
#[cfg(feature = "documentation")]
|
|
docs: crate::documentation::Documentation,
|
|
}
|
|
|
|
/// Struct data used by derive macros for `Reflect` and `FromReflect`.
|
|
///
|
|
/// # Example
|
|
///
|
|
/// ```ignore (bevy_reflect is not accessible from this crate)
|
|
/// #[derive(Reflect)]
|
|
/// #[reflect(PartialEq, Serialize, Deserialize, Default)]
|
|
/// struct ThingThatImReflecting<T1, T2, T3> {
|
|
/// x: T1, // |
|
|
/// y: T2, // |- fields
|
|
/// z: T3 // |
|
|
/// }
|
|
/// ```
|
|
pub(crate) struct ReflectStruct<'a> {
|
|
meta: ReflectMeta<'a>,
|
|
serialization_data: Option<SerializationDataDef>,
|
|
fields: Vec<StructField<'a>>,
|
|
}
|
|
|
|
/// Enum data used by derive macros for `Reflect` and `FromReflect`.
|
|
///
|
|
/// # Example
|
|
///
|
|
/// ```ignore (bevy_reflect is not accessible from this crate)
|
|
/// #[derive(Reflect)]
|
|
/// #[reflect(PartialEq, Serialize, Deserialize, Default)]
|
|
/// enum ThingThatImReflecting<T1, T2, T3> {
|
|
/// A(T1), // |
|
|
/// B, // |- variants
|
|
/// C { foo: T2, bar: T3 } // |
|
|
/// }
|
|
/// ```
|
|
pub(crate) struct ReflectEnum<'a> {
|
|
meta: ReflectMeta<'a>,
|
|
variants: Vec<EnumVariant<'a>>,
|
|
}
|
|
|
|
/// Represents a field on a struct or tuple struct.
|
|
#[derive(Clone)]
|
|
pub(crate) struct StructField<'a> {
|
|
/// The raw field.
|
|
pub data: &'a Field,
|
|
/// The reflection-based attributes on the field.
|
|
pub attrs: FieldAttributes,
|
|
/// The index of this field within the struct.
|
|
pub declaration_index: usize,
|
|
/// The index of this field as seen by the reflection API.
|
|
///
|
|
/// This index accounts for the removal of [ignored] fields.
|
|
/// It will only be `Some(index)` when the field is not ignored.
|
|
///
|
|
/// [ignored]: crate::field_attributes::ReflectIgnoreBehavior::IgnoreAlways
|
|
pub reflection_index: Option<usize>,
|
|
/// The documentation for this field, if any
|
|
#[cfg(feature = "documentation")]
|
|
pub doc: crate::documentation::Documentation,
|
|
}
|
|
|
|
/// Represents a variant on an enum.
|
|
pub(crate) struct EnumVariant<'a> {
|
|
/// The raw variant.
|
|
pub data: &'a Variant,
|
|
/// The fields within this variant.
|
|
pub fields: EnumVariantFields<'a>,
|
|
/// The reflection-based attributes on the variant.
|
|
pub attrs: FieldAttributes,
|
|
/// The documentation for this variant, if any
|
|
#[cfg(feature = "documentation")]
|
|
pub doc: crate::documentation::Documentation,
|
|
}
|
|
|
|
pub(crate) enum EnumVariantFields<'a> {
|
|
Named(Vec<StructField<'a>>),
|
|
Unnamed(Vec<StructField<'a>>),
|
|
Unit,
|
|
}
|
|
|
|
/// How the macro was invoked.
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
pub(crate) enum ReflectImplSource {
|
|
/// Using `impl_reflect!`.
|
|
ImplRemoteType,
|
|
/// Using `#[derive(...)]`.
|
|
DeriveLocalType,
|
|
/// Using `#[reflect_remote]`.
|
|
RemoteReflect,
|
|
}
|
|
|
|
/// Which trait the macro explicitly implements.
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
pub(crate) enum ReflectTraitToImpl {
|
|
Reflect,
|
|
FromReflect,
|
|
TypePath,
|
|
}
|
|
|
|
/// The provenance of a macro invocation.
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
pub(crate) struct ReflectProvenance {
|
|
pub source: ReflectImplSource,
|
|
pub trait_: ReflectTraitToImpl,
|
|
}
|
|
|
|
impl fmt::Display for ReflectProvenance {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
use self::{ReflectImplSource as S, ReflectTraitToImpl as T};
|
|
let str = match (self.source, self.trait_) {
|
|
(S::ImplRemoteType, T::Reflect) => "`impl_reflect`",
|
|
(S::DeriveLocalType, T::Reflect) => "`#[derive(Reflect)]`",
|
|
(S::DeriveLocalType, T::FromReflect) => "`#[derive(FromReflect)]`",
|
|
(S::DeriveLocalType, T::TypePath) => "`#[derive(TypePath)]`",
|
|
(S::RemoteReflect, T::Reflect) => "`#[reflect_remote]`",
|
|
(S::RemoteReflect, T::FromReflect | T::TypePath)
|
|
| (S::ImplRemoteType, T::FromReflect | T::TypePath) => unreachable!(),
|
|
};
|
|
f.write_str(str)
|
|
}
|
|
}
|
|
|
|
impl<'a> ReflectDerive<'a> {
|
|
pub fn from_input(
|
|
input: &'a DeriveInput,
|
|
provenance: ReflectProvenance,
|
|
) -> Result<Self, syn::Error> {
|
|
let mut container_attributes = ContainerAttributes::default();
|
|
// Should indicate whether `#[type_path = "..."]` was used.
|
|
let mut custom_path: Option<Path> = None;
|
|
// Should indicate whether `#[type_name = "..."]` was used.
|
|
let mut custom_type_name: Option<Ident> = None;
|
|
|
|
#[cfg(feature = "documentation")]
|
|
let mut doc = crate::documentation::Documentation::default();
|
|
|
|
for attribute in &input.attrs {
|
|
match &attribute.meta {
|
|
Meta::List(meta_list) if meta_list.path.is_ident(REFLECT_ATTRIBUTE_NAME) => {
|
|
container_attributes.parse_meta_list(meta_list, provenance.trait_)?;
|
|
}
|
|
Meta::NameValue(pair) if pair.path.is_ident(TYPE_PATH_ATTRIBUTE_NAME) => {
|
|
let syn::Expr::Lit(syn::ExprLit {
|
|
lit: syn::Lit::Str(lit),
|
|
..
|
|
}) = &pair.value
|
|
else {
|
|
return Err(syn::Error::new(
|
|
pair.span(),
|
|
format_args!("`#[{TYPE_PATH_ATTRIBUTE_NAME} = \"...\"]` must be a string literal"),
|
|
));
|
|
};
|
|
|
|
custom_path = Some(syn::parse::Parser::parse_str(
|
|
parse_path_no_leading_colon,
|
|
&lit.value(),
|
|
)?);
|
|
}
|
|
Meta::NameValue(pair) if pair.path.is_ident(TYPE_NAME_ATTRIBUTE_NAME) => {
|
|
let syn::Expr::Lit(syn::ExprLit {
|
|
lit: syn::Lit::Str(lit),
|
|
..
|
|
}) = &pair.value
|
|
else {
|
|
return Err(syn::Error::new(
|
|
pair.span(),
|
|
format_args!("`#[{TYPE_NAME_ATTRIBUTE_NAME} = \"...\"]` must be a string literal"),
|
|
));
|
|
};
|
|
|
|
custom_type_name = Some(parse_str(&lit.value())?);
|
|
}
|
|
#[cfg(feature = "documentation")]
|
|
Meta::NameValue(pair) if pair.path.is_ident("doc") => {
|
|
if let syn::Expr::Lit(syn::ExprLit {
|
|
lit: syn::Lit::Str(lit),
|
|
..
|
|
}) = &pair.value
|
|
{
|
|
doc.push(lit.value());
|
|
}
|
|
}
|
|
_ => continue,
|
|
}
|
|
}
|
|
match (&mut custom_path, custom_type_name) {
|
|
(Some(path), custom_type_name) => {
|
|
let ident = custom_type_name.unwrap_or_else(|| input.ident.clone());
|
|
path.segments.push(PathSegment::from(ident));
|
|
}
|
|
(None, Some(name)) => {
|
|
return Err(syn::Error::new(
|
|
name.span(),
|
|
format!("cannot use `#[{TYPE_NAME_ATTRIBUTE_NAME} = \"...\"]` without a `#[{TYPE_PATH_ATTRIBUTE_NAME} = \"...\"]` attribute."),
|
|
));
|
|
}
|
|
_ => (),
|
|
}
|
|
|
|
let type_path = ReflectTypePath::Internal {
|
|
ident: &input.ident,
|
|
custom_path,
|
|
generics: &input.generics,
|
|
};
|
|
|
|
let meta = ReflectMeta::new(type_path, container_attributes);
|
|
|
|
if provenance.source == ReflectImplSource::ImplRemoteType
|
|
&& meta.type_path_attrs().should_auto_derive()
|
|
&& !meta.type_path().has_custom_path()
|
|
{
|
|
return Err(syn::Error::new(
|
|
meta.type_path().span(),
|
|
format!("a #[{TYPE_PATH_ATTRIBUTE_NAME} = \"...\"] attribute must be specified when using {provenance}")
|
|
));
|
|
}
|
|
|
|
#[cfg(feature = "documentation")]
|
|
let meta = meta.with_docs(doc);
|
|
|
|
if meta.attrs().is_opaque() {
|
|
return Ok(Self::Opaque(meta));
|
|
}
|
|
|
|
match &input.data {
|
|
Data::Struct(data) => {
|
|
let fields = Self::collect_struct_fields(&data.fields)?;
|
|
let reflect_struct = ReflectStruct {
|
|
meta,
|
|
serialization_data: SerializationDataDef::new(&fields)?,
|
|
fields,
|
|
};
|
|
|
|
match data.fields {
|
|
Fields::Named(..) => Ok(Self::Struct(reflect_struct)),
|
|
Fields::Unnamed(..) => Ok(Self::TupleStruct(reflect_struct)),
|
|
Fields::Unit => Ok(Self::UnitStruct(reflect_struct)),
|
|
}
|
|
}
|
|
Data::Enum(data) => {
|
|
let variants = Self::collect_enum_variants(&data.variants)?;
|
|
|
|
let reflect_enum = ReflectEnum { meta, variants };
|
|
Ok(Self::Enum(reflect_enum))
|
|
}
|
|
Data::Union(..) => Err(syn::Error::new(
|
|
input.span(),
|
|
"reflection not supported for unions",
|
|
)),
|
|
}
|
|
}
|
|
|
|
/// Set the remote type for this derived type.
|
|
///
|
|
/// # Panics
|
|
///
|
|
/// Panics when called on [`ReflectDerive::Opaque`].
|
|
pub fn set_remote(&mut self, remote_ty: Option<RemoteType<'a>>) {
|
|
match self {
|
|
Self::Struct(data) | Self::TupleStruct(data) | Self::UnitStruct(data) => {
|
|
data.meta.remote_ty = remote_ty;
|
|
}
|
|
Self::Enum(data) => {
|
|
data.meta.remote_ty = remote_ty;
|
|
}
|
|
Self::Opaque(meta) => {
|
|
meta.remote_ty = remote_ty;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Get the remote type path, if any.
|
|
pub fn remote_ty(&self) -> Option<RemoteType> {
|
|
match self {
|
|
Self::Struct(data) | Self::TupleStruct(data) | Self::UnitStruct(data) => {
|
|
data.meta.remote_ty()
|
|
}
|
|
Self::Enum(data) => data.meta.remote_ty(),
|
|
Self::Opaque(meta) => meta.remote_ty(),
|
|
}
|
|
}
|
|
|
|
/// Get the [`ReflectMeta`] for this derived type.
|
|
pub fn meta(&self) -> &ReflectMeta {
|
|
match self {
|
|
Self::Struct(data) | Self::TupleStruct(data) | Self::UnitStruct(data) => data.meta(),
|
|
Self::Enum(data) => data.meta(),
|
|
Self::Opaque(meta) => meta,
|
|
}
|
|
}
|
|
|
|
pub fn where_clause_options(&self) -> WhereClauseOptions {
|
|
match self {
|
|
Self::Struct(data) | Self::TupleStruct(data) | Self::UnitStruct(data) => {
|
|
data.where_clause_options()
|
|
}
|
|
Self::Enum(data) => data.where_clause_options(),
|
|
Self::Opaque(meta) => WhereClauseOptions::new(meta),
|
|
}
|
|
}
|
|
|
|
fn collect_struct_fields(fields: &'a Fields) -> Result<Vec<StructField<'a>>, syn::Error> {
|
|
let mut active_index = 0;
|
|
let sifter: ResultSifter<StructField<'a>> = fields
|
|
.iter()
|
|
.enumerate()
|
|
.map(
|
|
|(declaration_index, field)| -> Result<StructField, syn::Error> {
|
|
let attrs = FieldAttributes::parse_attributes(&field.attrs)?;
|
|
|
|
let reflection_index = if attrs.ignore.is_ignored() {
|
|
None
|
|
} else {
|
|
active_index += 1;
|
|
Some(active_index - 1)
|
|
};
|
|
|
|
Ok(StructField {
|
|
declaration_index,
|
|
reflection_index,
|
|
attrs,
|
|
data: field,
|
|
#[cfg(feature = "documentation")]
|
|
doc: crate::documentation::Documentation::from_attributes(&field.attrs),
|
|
})
|
|
},
|
|
)
|
|
.fold(ResultSifter::default(), ResultSifter::fold);
|
|
|
|
sifter.finish()
|
|
}
|
|
|
|
fn collect_enum_variants(
|
|
variants: &'a Punctuated<Variant, Comma>,
|
|
) -> Result<Vec<EnumVariant<'a>>, syn::Error> {
|
|
let sifter: ResultSifter<EnumVariant<'a>> = variants
|
|
.iter()
|
|
.map(|variant| -> Result<EnumVariant, syn::Error> {
|
|
let fields = Self::collect_struct_fields(&variant.fields)?;
|
|
|
|
let fields = match variant.fields {
|
|
Fields::Named(..) => EnumVariantFields::Named(fields),
|
|
Fields::Unnamed(..) => EnumVariantFields::Unnamed(fields),
|
|
Fields::Unit => EnumVariantFields::Unit,
|
|
};
|
|
Ok(EnumVariant {
|
|
fields,
|
|
attrs: FieldAttributes::parse_attributes(&variant.attrs)?,
|
|
data: variant,
|
|
#[cfg(feature = "documentation")]
|
|
doc: crate::documentation::Documentation::from_attributes(&variant.attrs),
|
|
})
|
|
})
|
|
.fold(ResultSifter::default(), ResultSifter::fold);
|
|
|
|
sifter.finish()
|
|
}
|
|
}
|
|
|
|
impl<'a> ReflectMeta<'a> {
|
|
pub fn new(type_path: ReflectTypePath<'a>, attrs: ContainerAttributes) -> Self {
|
|
Self {
|
|
attrs,
|
|
type_path,
|
|
remote_ty: None,
|
|
bevy_reflect_path: crate::meta::get_bevy_reflect_path(),
|
|
#[cfg(feature = "documentation")]
|
|
docs: Default::default(),
|
|
}
|
|
}
|
|
|
|
/// Sets the documentation for this type.
|
|
#[cfg(feature = "documentation")]
|
|
pub fn with_docs(self, docs: crate::documentation::Documentation) -> Self {
|
|
Self { docs, ..self }
|
|
}
|
|
|
|
/// The registered reflect attributes on this struct.
|
|
pub fn attrs(&self) -> &ContainerAttributes {
|
|
&self.attrs
|
|
}
|
|
|
|
/// The `FromReflect` attributes on this type.
|
|
#[expect(
|
|
clippy::wrong_self_convention,
|
|
reason = "Method returns `FromReflectAttrs`, does not actually convert data."
|
|
)]
|
|
pub fn from_reflect(&self) -> &FromReflectAttrs {
|
|
self.attrs.from_reflect_attrs()
|
|
}
|
|
|
|
/// The `TypePath` attributes on this type.
|
|
pub fn type_path_attrs(&self) -> &TypePathAttrs {
|
|
self.attrs.type_path_attrs()
|
|
}
|
|
|
|
/// The path to this type.
|
|
pub fn type_path(&self) -> &ReflectTypePath<'a> {
|
|
&self.type_path
|
|
}
|
|
|
|
/// Get the remote type path, if any.
|
|
pub fn remote_ty(&self) -> Option<RemoteType> {
|
|
self.remote_ty
|
|
}
|
|
|
|
/// Whether this reflected type represents a remote type or not.
|
|
pub fn is_remote_wrapper(&self) -> bool {
|
|
self.remote_ty.is_some()
|
|
}
|
|
|
|
/// The cached `bevy_reflect` path.
|
|
pub fn bevy_reflect_path(&self) -> &Path {
|
|
&self.bevy_reflect_path
|
|
}
|
|
|
|
/// Returns the `GetTypeRegistration` impl as a `TokenStream`.
|
|
pub fn get_type_registration(
|
|
&self,
|
|
where_clause_options: &WhereClauseOptions,
|
|
) -> proc_macro2::TokenStream {
|
|
crate::registration::impl_get_type_registration(
|
|
self,
|
|
where_clause_options,
|
|
None,
|
|
Option::<core::iter::Empty<&Type>>::None,
|
|
)
|
|
}
|
|
|
|
/// The collection of docstrings for this type, if any.
|
|
#[cfg(feature = "documentation")]
|
|
pub fn doc(&self) -> &crate::documentation::Documentation {
|
|
&self.docs
|
|
}
|
|
}
|
|
|
|
impl<'a> StructField<'a> {
|
|
/// Generates a `TokenStream` for `NamedField` or `UnnamedField` construction.
|
|
pub fn to_info_tokens(&self, bevy_reflect_path: &Path) -> proc_macro2::TokenStream {
|
|
let name = match &self.data.ident {
|
|
Some(ident) => ident.to_string().to_token_stream(),
|
|
None => self.reflection_index.to_token_stream(),
|
|
};
|
|
|
|
let field_info = if self.data.ident.is_some() {
|
|
quote! {
|
|
#bevy_reflect_path::NamedField
|
|
}
|
|
} else {
|
|
quote! {
|
|
#bevy_reflect_path::UnnamedField
|
|
}
|
|
};
|
|
|
|
let ty = self.reflected_type();
|
|
let custom_attributes = self.attrs.custom_attributes.to_tokens(bevy_reflect_path);
|
|
|
|
#[cfg_attr(
|
|
not(feature = "documentation"),
|
|
expect(
|
|
unused_mut,
|
|
reason = "Needs to be mutable if `documentation` feature is enabled.",
|
|
)
|
|
)]
|
|
let mut info = quote! {
|
|
#field_info::new::<#ty>(#name).with_custom_attributes(#custom_attributes)
|
|
};
|
|
|
|
#[cfg(feature = "documentation")]
|
|
{
|
|
let docs = &self.doc;
|
|
info.extend(quote! {
|
|
.with_docs(#docs)
|
|
});
|
|
}
|
|
|
|
info
|
|
}
|
|
|
|
/// Returns the reflected type of this field.
|
|
///
|
|
/// Normally this is just the field's defined type.
|
|
/// However, this can be adjusted to use a different type, like for representing remote types.
|
|
/// In those cases, the returned value is the remote wrapper type.
|
|
pub fn reflected_type(&self) -> &Type {
|
|
self.attrs.remote.as_ref().unwrap_or(&self.data.ty)
|
|
}
|
|
|
|
pub fn attrs(&self) -> &FieldAttributes {
|
|
&self.attrs
|
|
}
|
|
}
|
|
|
|
impl<'a> ReflectStruct<'a> {
|
|
/// Access the metadata associated with this struct definition.
|
|
pub fn meta(&self) -> &ReflectMeta<'a> {
|
|
&self.meta
|
|
}
|
|
|
|
/// Returns the [`SerializationDataDef`] for this struct.
|
|
pub fn serialization_data(&self) -> Option<&SerializationDataDef> {
|
|
self.serialization_data.as_ref()
|
|
}
|
|
|
|
/// Returns the `GetTypeRegistration` impl as a `TokenStream`.
|
|
///
|
|
/// Returns a specific implementation for structs and this method should be preferred over the generic [`get_type_registration`](ReflectMeta) method
|
|
pub fn get_type_registration(
|
|
&self,
|
|
where_clause_options: &WhereClauseOptions,
|
|
) -> proc_macro2::TokenStream {
|
|
crate::registration::impl_get_type_registration(
|
|
self.meta(),
|
|
where_clause_options,
|
|
self.serialization_data(),
|
|
Some(self.active_types().iter()),
|
|
)
|
|
}
|
|
|
|
/// Get a collection of types which are exposed to the reflection API
|
|
pub fn active_types(&self) -> Vec<Type> {
|
|
self.active_fields()
|
|
.map(|field| field.reflected_type().clone())
|
|
.collect()
|
|
}
|
|
|
|
/// Get an iterator of fields which are exposed to the reflection API.
|
|
pub fn active_fields(&self) -> impl Iterator<Item = &StructField<'a>> {
|
|
self.fields()
|
|
.iter()
|
|
.filter(|field| field.attrs.ignore.is_active())
|
|
}
|
|
|
|
/// Get an iterator of fields which are ignored by the reflection API
|
|
pub fn ignored_fields(&self) -> impl Iterator<Item = &StructField<'a>> {
|
|
self.fields()
|
|
.iter()
|
|
.filter(|field| field.attrs.ignore.is_ignored())
|
|
}
|
|
|
|
/// The complete set of fields in this struct.
|
|
pub fn fields(&self) -> &[StructField<'a>] {
|
|
&self.fields
|
|
}
|
|
|
|
pub fn where_clause_options(&self) -> WhereClauseOptions {
|
|
WhereClauseOptions::new_with_fields(self.meta(), self.active_types().into_boxed_slice())
|
|
}
|
|
|
|
/// Generates a `TokenStream` for `TypeInfo::Struct` or `TypeInfo::TupleStruct` construction.
|
|
pub fn to_info_tokens(&self, is_tuple: bool) -> proc_macro2::TokenStream {
|
|
let bevy_reflect_path = self.meta().bevy_reflect_path();
|
|
|
|
let (info_variant, info_struct) = if is_tuple {
|
|
(
|
|
Ident::new("TupleStruct", Span::call_site()),
|
|
Ident::new("TupleStructInfo", Span::call_site()),
|
|
)
|
|
} else {
|
|
(
|
|
Ident::new("Struct", Span::call_site()),
|
|
Ident::new("StructInfo", Span::call_site()),
|
|
)
|
|
};
|
|
|
|
let field_infos = self
|
|
.active_fields()
|
|
.map(|field| field.to_info_tokens(bevy_reflect_path));
|
|
|
|
let custom_attributes = self
|
|
.meta
|
|
.attrs
|
|
.custom_attributes()
|
|
.to_tokens(bevy_reflect_path);
|
|
|
|
let mut info = quote! {
|
|
#bevy_reflect_path::#info_struct::new::<Self>(&[
|
|
#(#field_infos),*
|
|
])
|
|
.with_custom_attributes(#custom_attributes)
|
|
};
|
|
|
|
if let Some(generics) = generate_generics(self.meta()) {
|
|
info.extend(quote! {
|
|
.with_generics(#generics)
|
|
});
|
|
}
|
|
|
|
#[cfg(feature = "documentation")]
|
|
{
|
|
let docs = self.meta().doc();
|
|
info.extend(quote! {
|
|
.with_docs(#docs)
|
|
});
|
|
}
|
|
|
|
quote! {
|
|
#bevy_reflect_path::TypeInfo::#info_variant(#info)
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> ReflectEnum<'a> {
|
|
/// Access the metadata associated with this enum definition.
|
|
pub fn meta(&self) -> &ReflectMeta<'a> {
|
|
&self.meta
|
|
}
|
|
|
|
/// Returns the given ident as a qualified unit variant of this enum.
|
|
///
|
|
/// This takes into account the remote type, if any.
|
|
pub fn get_unit(&self, variant: &Ident) -> proc_macro2::TokenStream {
|
|
let name = self
|
|
.meta
|
|
.remote_ty
|
|
.map(|path| match path.as_expr_path() {
|
|
Ok(path) => path.to_token_stream(),
|
|
Err(err) => err.into_compile_error(),
|
|
})
|
|
.unwrap_or_else(|| self.meta.type_path().to_token_stream());
|
|
|
|
quote! {
|
|
#name::#variant
|
|
}
|
|
}
|
|
|
|
/// The complete set of variants in this enum.
|
|
pub fn variants(&self) -> &[EnumVariant<'a>] {
|
|
&self.variants
|
|
}
|
|
|
|
/// Get a collection of types which are exposed to the reflection API
|
|
pub fn active_types(&self) -> Vec<Type> {
|
|
self.active_fields()
|
|
.map(|field| field.reflected_type().clone())
|
|
.collect()
|
|
}
|
|
|
|
/// 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(EnumVariant::active_fields)
|
|
}
|
|
|
|
pub fn where_clause_options(&self) -> WhereClauseOptions {
|
|
WhereClauseOptions::new_with_fields(self.meta(), self.active_types().into_boxed_slice())
|
|
}
|
|
|
|
/// Returns the `GetTypeRegistration` impl as a `TokenStream`.
|
|
///
|
|
/// Returns a specific implementation for enums and this method should be preferred over the generic [`get_type_registration`](crate::ReflectMeta) method
|
|
pub fn get_type_registration(
|
|
&self,
|
|
where_clause_options: &WhereClauseOptions,
|
|
) -> proc_macro2::TokenStream {
|
|
crate::registration::impl_get_type_registration(
|
|
self.meta(),
|
|
where_clause_options,
|
|
None,
|
|
Some(self.active_fields().map(StructField::reflected_type)),
|
|
)
|
|
}
|
|
|
|
/// Generates a `TokenStream` for `TypeInfo::Enum` construction.
|
|
pub fn to_info_tokens(&self) -> proc_macro2::TokenStream {
|
|
let bevy_reflect_path = self.meta().bevy_reflect_path();
|
|
|
|
let variants = self
|
|
.variants
|
|
.iter()
|
|
.map(|variant| variant.to_info_tokens(bevy_reflect_path));
|
|
|
|
let custom_attributes = self
|
|
.meta
|
|
.attrs
|
|
.custom_attributes()
|
|
.to_tokens(bevy_reflect_path);
|
|
|
|
let mut info = quote! {
|
|
#bevy_reflect_path::EnumInfo::new::<Self>(&[
|
|
#(#variants),*
|
|
])
|
|
.with_custom_attributes(#custom_attributes)
|
|
};
|
|
|
|
if let Some(generics) = generate_generics(self.meta()) {
|
|
info.extend(quote! {
|
|
.with_generics(#generics)
|
|
});
|
|
}
|
|
|
|
#[cfg(feature = "documentation")]
|
|
{
|
|
let docs = self.meta().doc();
|
|
info.extend(quote! {
|
|
.with_docs(#docs)
|
|
});
|
|
}
|
|
|
|
quote! {
|
|
#bevy_reflect_path::TypeInfo::Enum(#info)
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> EnumVariant<'a> {
|
|
/// Get an iterator of fields which are exposed to the reflection API
|
|
pub fn active_fields(&self) -> impl Iterator<Item = &StructField<'a>> {
|
|
self.fields()
|
|
.iter()
|
|
.filter(|field| field.attrs.ignore.is_active())
|
|
}
|
|
|
|
/// The complete set of fields in this variant.
|
|
pub fn fields(&self) -> &[StructField<'a>] {
|
|
match &self.fields {
|
|
EnumVariantFields::Named(fields) | EnumVariantFields::Unnamed(fields) => fields,
|
|
EnumVariantFields::Unit => &[],
|
|
}
|
|
}
|
|
|
|
/// Generates a `TokenStream` for `VariantInfo` construction.
|
|
pub fn to_info_tokens(&self, bevy_reflect_path: &Path) -> proc_macro2::TokenStream {
|
|
let variant_name = &self.data.ident.to_string();
|
|
|
|
let (info_variant, info_struct) = match &self.fields {
|
|
EnumVariantFields::Unit => (
|
|
Ident::new("Unit", Span::call_site()),
|
|
Ident::new("UnitVariantInfo", Span::call_site()),
|
|
),
|
|
EnumVariantFields::Unnamed(..) => (
|
|
Ident::new("Tuple", Span::call_site()),
|
|
Ident::new("TupleVariantInfo", Span::call_site()),
|
|
),
|
|
EnumVariantFields::Named(..) => (
|
|
Ident::new("Struct", Span::call_site()),
|
|
Ident::new("StructVariantInfo", Span::call_site()),
|
|
),
|
|
};
|
|
|
|
let fields = self
|
|
.active_fields()
|
|
.map(|field| field.to_info_tokens(bevy_reflect_path));
|
|
|
|
let args = match &self.fields {
|
|
EnumVariantFields::Unit => quote!(#variant_name),
|
|
_ => {
|
|
quote!( #variant_name , &[#(#fields),*] )
|
|
}
|
|
};
|
|
|
|
let custom_attributes = self.attrs.custom_attributes.to_tokens(bevy_reflect_path);
|
|
|
|
#[cfg_attr(
|
|
not(feature = "documentation"),
|
|
expect(
|
|
unused_mut,
|
|
reason = "Needs to be mutable if `documentation` feature is enabled.",
|
|
)
|
|
)]
|
|
let mut info = quote! {
|
|
#bevy_reflect_path::#info_struct::new(#args)
|
|
.with_custom_attributes(#custom_attributes)
|
|
};
|
|
|
|
#[cfg(feature = "documentation")]
|
|
{
|
|
let docs = &self.doc;
|
|
info.extend(quote! {
|
|
.with_docs(#docs)
|
|
});
|
|
}
|
|
|
|
quote! {
|
|
#bevy_reflect_path::VariantInfo::#info_variant(#info)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Represents a path to a type.
|
|
///
|
|
/// This is used over [`struct@Ident`] or [`Path`]
|
|
/// to have the correct semantics for [deriving `TypePath`].
|
|
///
|
|
/// The type can always be reached with its [`ToTokens`] implementation.
|
|
///
|
|
/// The [`short_type_path`], [`type_ident`], [`crate_name`], and [`module_path`] methods
|
|
/// have corresponding methods on the `TypePath` trait.
|
|
/// [`long_type_path`] corresponds to the `type_path` method on `TypePath`.
|
|
///
|
|
/// [deriving `TypePath`]: crate::derive_type_path
|
|
/// [`long_type_path`]: ReflectTypePath::long_type_path
|
|
/// [`short_type_path`]: ReflectTypePath::short_type_path
|
|
/// [`type_ident`]: ReflectTypePath::type_ident
|
|
/// [`crate_name`]: ReflectTypePath::crate_name
|
|
/// [`module_path`]: ReflectTypePath::module_path
|
|
///
|
|
/// # Example
|
|
///
|
|
/// ```ignore (bevy_reflect is not accessible from this crate)
|
|
/// # use syn::parse_quote;
|
|
/// # use bevy_reflect_derive::ReflectTypePath;
|
|
/// let path: syn::Path = parse_quote!(::std::marker::PhantomData)?;
|
|
///
|
|
/// let type_path = ReflectTypePath::External {
|
|
/// path,
|
|
/// custom_path: None,
|
|
/// };
|
|
///
|
|
/// // Equivalent to "std::marker".
|
|
/// let module_path = type_path.module_path();
|
|
/// # Ok::<(), syn::Error>(())
|
|
/// ```
|
|
pub(crate) enum ReflectTypePath<'a> {
|
|
/// Types without a crate/module that can be named from any scope (e.g. `bool`).
|
|
Primitive(&'a Ident),
|
|
/// Using `::my_crate::foo::Bar` syntax.
|
|
///
|
|
/// May have a separate custom path used for the `TypePath` implementation.
|
|
External {
|
|
path: &'a Path,
|
|
custom_path: Option<Path>,
|
|
generics: &'a Generics,
|
|
},
|
|
/// The name of a type relative to its scope.
|
|
///
|
|
/// The type must be able to be reached with just its name.
|
|
///
|
|
/// May have a separate alias path used for the `TypePath` implementation.
|
|
///
|
|
/// Module and crate are found with [`module_path!()`](module_path),
|
|
/// if there is no custom path specified.
|
|
Internal {
|
|
ident: &'a Ident,
|
|
custom_path: Option<Path>,
|
|
generics: &'a Generics,
|
|
},
|
|
/// Any [`Type`] with only a defined `type_path` and `short_type_path`.
|
|
#[expect(
|
|
dead_code,
|
|
reason = "Not currently used but may be useful in the future due to its generality."
|
|
)]
|
|
Anonymous {
|
|
qualified_type: Type,
|
|
long_type_path: StringExpr,
|
|
short_type_path: StringExpr,
|
|
},
|
|
}
|
|
|
|
impl<'a> ReflectTypePath<'a> {
|
|
/// Returns the path interpreted as an [`struct@Ident`].
|
|
///
|
|
/// Returns [`None`] if [anonymous].
|
|
///
|
|
/// [anonymous]: ReflectTypePath::Anonymous
|
|
pub fn get_ident(&self) -> Option<&Ident> {
|
|
match self {
|
|
Self::Internal {
|
|
ident, custom_path, ..
|
|
} => Some(
|
|
custom_path
|
|
.as_ref()
|
|
.map(|path| &path.segments.last().unwrap().ident)
|
|
.unwrap_or(ident),
|
|
),
|
|
Self::External {
|
|
path, custom_path, ..
|
|
} => Some(
|
|
&custom_path
|
|
.as_ref()
|
|
.unwrap_or(path)
|
|
.segments
|
|
.last()
|
|
.unwrap()
|
|
.ident,
|
|
),
|
|
Self::Primitive(ident) => Some(ident),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
/// The generics associated with the type.
|
|
///
|
|
/// Empty if [anonymous] or [primitive].
|
|
///
|
|
/// [primitive]: ReflectTypePath::Primitive
|
|
/// [anonymous]: ReflectTypePath::Anonymous
|
|
pub fn generics(&self) -> &'a Generics {
|
|
// Use a constant because we need to return a reference of at least 'a.
|
|
const EMPTY_GENERICS: &Generics = &Generics {
|
|
gt_token: None,
|
|
lt_token: None,
|
|
where_clause: None,
|
|
params: Punctuated::new(),
|
|
};
|
|
|
|
match self {
|
|
Self::Internal { generics, .. } | Self::External { generics, .. } => generics,
|
|
_ => EMPTY_GENERICS,
|
|
}
|
|
}
|
|
|
|
/// Whether an implementation of `Typed` or `TypePath` should be generic.
|
|
///
|
|
/// Returning true that it should use a `GenericTypeCell` in its implementation.
|
|
pub fn impl_is_generic(&self) -> bool {
|
|
// Whether to use `GenericTypeCell` is not dependent on lifetimes
|
|
// (which all have to be 'static anyway).
|
|
!self
|
|
.generics()
|
|
.params
|
|
.iter()
|
|
.all(|param| matches!(param, GenericParam::Lifetime(_)))
|
|
}
|
|
|
|
/// Returns the path interpreted as a [`Path`].
|
|
///
|
|
/// Returns [`None`] if [anonymous], [primitive],
|
|
/// or a [`ReflectTypePath::Internal`] without a custom path.
|
|
///
|
|
/// [primitive]: ReflectTypePath::Primitive
|
|
/// [anonymous]: ReflectTypePath::Anonymous
|
|
pub fn get_path(&self) -> Option<&Path> {
|
|
match self {
|
|
Self::Internal { custom_path, .. } => custom_path.as_ref(),
|
|
Self::External {
|
|
path, custom_path, ..
|
|
} => Some(custom_path.as_ref().unwrap_or(path)),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
/// Returns whether this [internal] or [external] path has a custom path.
|
|
///
|
|
/// [internal]: ReflectTypePath::Internal
|
|
/// [external]: ReflectTypePath::External
|
|
pub fn has_custom_path(&self) -> bool {
|
|
match self {
|
|
Self::Internal { custom_path, .. } | Self::External { custom_path, .. } => {
|
|
custom_path.is_some()
|
|
}
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
/// Returns a [`StringExpr`] representing the name of the type's crate.
|
|
///
|
|
/// Returns [`None`] if the type is [primitive] or [anonymous].
|
|
///
|
|
/// For non-customized [internal] paths this is created from [`module_path`].
|
|
///
|
|
/// For `Option<PhantomData>`, this is `"core"`.
|
|
///
|
|
/// [primitive]: ReflectTypePath::Primitive
|
|
/// [anonymous]: ReflectTypePath::Anonymous
|
|
/// [internal]: ReflectTypePath::Internal
|
|
pub fn crate_name(&self) -> Option<StringExpr> {
|
|
if let Some(path) = self.get_path() {
|
|
let crate_name = &path.segments.first().unwrap().ident;
|
|
return Some(StringExpr::from(crate_name));
|
|
}
|
|
|
|
match self {
|
|
Self::Internal { .. } => Some(StringExpr::Borrowed(quote! {
|
|
::core::module_path!()
|
|
.split(':')
|
|
.next()
|
|
.unwrap()
|
|
})),
|
|
Self::External { .. } => unreachable!(),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
/// Combines type generics and const generics into one [`StringExpr`].
|
|
///
|
|
/// This string can be used with a `GenericTypePathCell` in a `TypePath` implementation.
|
|
///
|
|
/// The `ty_generic_fn` param maps [`TypeParam`]s to [`StringExpr`]s.
|
|
fn reduce_generics(
|
|
generics: &Generics,
|
|
mut ty_generic_fn: impl FnMut(&TypeParam) -> StringExpr,
|
|
) -> StringExpr {
|
|
let mut params = generics.params.iter().filter_map(|param| match param {
|
|
GenericParam::Type(type_param) => Some(ty_generic_fn(type_param)),
|
|
GenericParam::Const(const_param) => {
|
|
let ident = &const_param.ident;
|
|
let ty = &const_param.ty;
|
|
|
|
Some(StringExpr::Owned(quote! {
|
|
<#ty as ::std::string::ToString>::to_string(&#ident)
|
|
}))
|
|
}
|
|
GenericParam::Lifetime(_) => None,
|
|
});
|
|
|
|
params
|
|
.next()
|
|
.into_iter()
|
|
.chain(params.flat_map(|x| [StringExpr::from_str(", "), x]))
|
|
.collect()
|
|
}
|
|
|
|
/// Returns a [`StringExpr`] representing the "type path" of the type.
|
|
///
|
|
/// For `Option<PhantomData>`, this is `"std::option::Option<std::marker::PhantomData>"`.
|
|
pub fn long_type_path(&self, bevy_reflect_path: &Path) -> StringExpr {
|
|
match self {
|
|
Self::Primitive(ident) => StringExpr::from(ident),
|
|
Self::Anonymous { long_type_path, .. } => long_type_path.clone(),
|
|
Self::Internal { generics, .. } | Self::External { generics, .. } => {
|
|
let ident = self.type_ident().unwrap();
|
|
let module_path = self.module_path().unwrap();
|
|
|
|
if self.impl_is_generic() {
|
|
let generics = ReflectTypePath::reduce_generics(
|
|
generics,
|
|
|TypeParam { ident, .. }| {
|
|
StringExpr::Borrowed(quote! {
|
|
<#ident as #bevy_reflect_path::TypePath>::type_path()
|
|
})
|
|
},
|
|
);
|
|
|
|
StringExpr::from_iter([
|
|
module_path,
|
|
StringExpr::from_str("::"),
|
|
ident,
|
|
StringExpr::from_str("<"),
|
|
generics,
|
|
StringExpr::from_str(">"),
|
|
])
|
|
} else {
|
|
StringExpr::from_iter([module_path, StringExpr::from_str("::"), ident])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Returns a [`StringExpr`] representing the "short path" of the type.
|
|
///
|
|
/// For `Option<PhantomData>`, this is `"Option<PhantomData>"`.
|
|
pub fn short_type_path(&self, bevy_reflect_path: &Path) -> StringExpr {
|
|
match self {
|
|
Self::Anonymous {
|
|
short_type_path, ..
|
|
} => short_type_path.clone(),
|
|
Self::Primitive(ident) => StringExpr::from(ident),
|
|
Self::External { generics, .. } | Self::Internal { generics, .. } => {
|
|
let ident = self.type_ident().unwrap();
|
|
|
|
if self.impl_is_generic() {
|
|
let generics = ReflectTypePath::reduce_generics(
|
|
generics,
|
|
|TypeParam { ident, .. }| {
|
|
StringExpr::Borrowed(quote! {
|
|
<#ident as #bevy_reflect_path::TypePath>::short_type_path()
|
|
})
|
|
},
|
|
);
|
|
|
|
StringExpr::from_iter([
|
|
ident,
|
|
StringExpr::from_str("<"),
|
|
generics,
|
|
StringExpr::from_str(">"),
|
|
])
|
|
} else {
|
|
ident
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Returns a [`StringExpr`] representing the path to the module
|
|
/// that the type is in.
|
|
///
|
|
/// Returns [`None`] if the type is [primitive] or [anonymous].
|
|
///
|
|
/// For non-customized [internal] paths this is created from [`module_path`].
|
|
///
|
|
/// For `Option<PhantomData>`, this is `"std::option"`.
|
|
///
|
|
/// [primitive]: ReflectTypePath::Primitive
|
|
/// [anonymous]: ReflectTypePath::Anonymous
|
|
/// [internal]: ReflectTypePath::Internal
|
|
pub fn module_path(&self) -> Option<StringExpr> {
|
|
if let Some(path) = self.get_path() {
|
|
let path_string = path
|
|
.segments
|
|
.pairs()
|
|
.take(path.segments.len() - 1)
|
|
.map(|pair| pair.value().ident.to_string())
|
|
.reduce(|path, ident| path + "::" + &ident)
|
|
.unwrap();
|
|
|
|
let path_lit = LitStr::new(&path_string, path.span());
|
|
return Some(StringExpr::from_lit(&path_lit));
|
|
}
|
|
|
|
match self {
|
|
Self::Internal { .. } => Some(StringExpr::Const(quote! {
|
|
::core::module_path!()
|
|
})),
|
|
Self::External { .. } => unreachable!(),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
/// Returns a [`StringExpr`] representing the type's final ident.
|
|
///
|
|
/// Returns [`None`] if the type is [anonymous].
|
|
///
|
|
/// This is not necessarily a valid qualified path to the type.
|
|
///
|
|
/// For `Option<PhantomData>`, this is `"Option"`.
|
|
///
|
|
/// [anonymous]: ReflectTypePath::Anonymous
|
|
pub fn type_ident(&self) -> Option<StringExpr> {
|
|
self.get_ident().map(StringExpr::from)
|
|
}
|
|
|
|
/// Returns the true type regardless of whether a custom path is specified.
|
|
///
|
|
/// To get the custom path if there is one, use [`Self::get_path`].
|
|
///
|
|
/// For example, the type `Foo<T: Debug>` would return `Foo<T>`.
|
|
pub fn true_type(&self) -> proc_macro2::TokenStream {
|
|
match self {
|
|
Self::Primitive(ident) => quote!(#ident),
|
|
Self::Internal {
|
|
ident, generics, ..
|
|
} => {
|
|
let (_, ty_generics, _) = generics.split_for_impl();
|
|
quote!(#ident #ty_generics)
|
|
}
|
|
Self::External { path, generics, .. } => {
|
|
let (_, ty_generics, _) = generics.split_for_impl();
|
|
quote!(#path #ty_generics)
|
|
}
|
|
Self::Anonymous { qualified_type, .. } => qualified_type.to_token_stream(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> ToTokens for ReflectTypePath<'a> {
|
|
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
|
match self {
|
|
Self::Internal { ident, .. } | Self::Primitive(ident) => ident.to_tokens(tokens),
|
|
Self::External { path, .. } => path.to_tokens(tokens),
|
|
Self::Anonymous { qualified_type, .. } => qualified_type.to_tokens(tokens),
|
|
}
|
|
}
|
|
}
|