diff --git a/crates/bevy_macro_utils/Cargo.toml b/crates/bevy_macro_utils/Cargo.toml index 261e8778806522..286ae0135e042b 100644 --- a/crates/bevy_macro_utils/Cargo.toml +++ b/crates/bevy_macro_utils/Cargo.toml @@ -13,3 +13,4 @@ toml_edit = "0.19" syn = "2.0" quote = "1.0" rustc-hash = "1.0" +proc-macro2 = "1.0" diff --git a/crates/bevy_macro_utils/src/attrs.rs b/crates/bevy_macro_utils/src/attrs.rs index 33b96e5b998979..9e000f217bbe33 100644 --- a/crates/bevy_macro_utils/src/attrs.rs +++ b/crates/bevy_macro_utils/src/attrs.rs @@ -2,6 +2,7 @@ use syn::{Expr, ExprLit, Lit}; use crate::symbol::Symbol; +/// Get a [literal string](struct@syn::LitStr) from the provided [expression](Expr). pub fn get_lit_str(attr_name: Symbol, value: &Expr) -> syn::Result<&syn::LitStr> { if let Expr::Lit(ExprLit { lit: Lit::Str(lit), .. @@ -16,6 +17,7 @@ pub fn get_lit_str(attr_name: Symbol, value: &Expr) -> syn::Result<&syn::LitStr> } } +/// Get a [literal boolean](struct@syn::LitBool) from the provided [expression](Expr) as a [`bool`]. pub fn get_lit_bool(attr_name: Symbol, value: &Expr) -> syn::Result { if let Expr::Lit(ExprLit { lit: Lit::Bool(lit), diff --git a/crates/bevy_macro_utils/src/bevy_manifest.rs b/crates/bevy_macro_utils/src/bevy_manifest.rs new file mode 100644 index 00000000000000..039f49f8bddd5e --- /dev/null +++ b/crates/bevy_macro_utils/src/bevy_manifest.rs @@ -0,0 +1,126 @@ +extern crate proc_macro; + +use proc_macro::TokenStream; +use std::{env, path::PathBuf}; +use toml_edit::{Document, Item}; + +/// The path to the `Cargo.toml` file for the Bevy project. +pub struct BevyManifest { + manifest: Document, +} + +impl Default for BevyManifest { + fn default() -> Self { + Self { + manifest: env::var_os("CARGO_MANIFEST_DIR") + .map(PathBuf::from) + .map(|mut path| { + path.push("Cargo.toml"); + if !path.exists() { + panic!( + "No Cargo manifest found for crate. Expected: {}", + path.display() + ); + } + let manifest = std::fs::read_to_string(path.clone()).unwrap_or_else(|_| { + panic!("Unable to read cargo manifest: {}", path.display()) + }); + manifest.parse::().unwrap_or_else(|_| { + panic!("Failed to parse cargo manifest: {}", path.display()) + }) + }) + .expect("CARGO_MANIFEST_DIR is not defined."), + } + } +} +const BEVY: &str = "bevy"; +const BEVY_INTERNAL: &str = "bevy_internal"; + +impl BevyManifest { + /// Attempt to retrieve the [path](syn::Path) of a particular package in + /// the [manifest](BevyManifest) by [name](str). + pub fn maybe_get_path(&self, name: &str) -> Option { + fn dep_package(dep: &Item) -> Option<&str> { + if dep.as_str().is_some() { + None + } else { + dep.get("package").map(|name| name.as_str().unwrap()) + } + } + + let find_in_deps = |deps: &Item| -> Option { + let package = if let Some(dep) = deps.get(name) { + return Some(Self::parse_str(dep_package(dep).unwrap_or(name))); + } else if let Some(dep) = deps.get(BEVY) { + dep_package(dep).unwrap_or(BEVY) + } else if let Some(dep) = deps.get(BEVY_INTERNAL) { + dep_package(dep).unwrap_or(BEVY_INTERNAL) + } else { + return None; + }; + + let mut path = Self::parse_str::(package); + if let Some(module) = name.strip_prefix("bevy_") { + path.segments.push(Self::parse_str(module)); + } + Some(path) + }; + + let deps = self.manifest.get("dependencies"); + let deps_dev = self.manifest.get("dev-dependencies"); + + deps.and_then(find_in_deps) + .or_else(|| deps_dev.and_then(find_in_deps)) + } + + /// Returns the path for the crate with the given name. + /// + /// This is a convenience method for constructing a [manifest] and + /// calling the [`get_path`] method. + /// + /// This method should only be used where you just need the path and can't + /// cache the [manifest]. If caching is possible, it's recommended to create + /// the [manifest] yourself and use the [`get_path`] method. + /// + /// [`get_path`]: Self::get_path + /// [manifest]: Self + pub fn get_path_direct(name: &str) -> syn::Path { + Self::default().get_path(name) + } + + /// Returns the path for the crate with the given name. + pub fn get_path(&self, name: &str) -> syn::Path { + self.maybe_get_path(name) + .unwrap_or_else(|| Self::parse_str(name)) + } + + /// Attempt to parse the provided [path](str) as a [syntax tree node](syn::parse::Parse) + pub fn try_parse_str(path: &str) -> Option { + syn::parse(path.parse::().ok()?).ok() + } + + /// Attempt to parse provided [path](str) as a [syntax tree node](syn::parse::Parse). + /// + /// # Panics + /// + /// Will panic if the path is not able to be parsed. For a non-panicing option, see [`try_parse_str`] + /// + /// [`try_parse_str`]: Self::try_parse_str + pub fn parse_str(path: &str) -> T { + Self::try_parse_str(path).unwrap() + } + + /// Attempt to get a subcrate [path](syn::Path) under Bevy by [name](str) + pub fn get_subcrate(&self, subcrate: &str) -> Option { + self.maybe_get_path(BEVY) + .map(|bevy_path| { + let mut segments = bevy_path.segments; + segments.push(BevyManifest::parse_str(subcrate)); + syn::Path { + leading_colon: None, + segments, + } + }) + .or_else(|| self.maybe_get_path(&format!("bevy_{subcrate}"))) + } +} diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/fq_std.rs b/crates/bevy_macro_utils/src/fq_std.rs similarity index 66% rename from crates/bevy_reflect/bevy_reflect_derive/src/fq_std.rs rename to crates/bevy_macro_utils/src/fq_std.rs index 788aa675efd381..d8ed917a1500a4 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/fq_std.rs +++ b/crates/bevy_macro_utils/src/fq_std.rs @@ -1,6 +1,9 @@ -//! This module contains unit structs that should be used inside `quote!` and `spanned_quote!` using the variable interpolation syntax in place of their equivalent structs and traits present in `std`. -// -//! To create hygienic proc macros, all the names must be its fully qualified form. These unit structs help us to not specify the fully qualified name every single time. +//! This module contains unit structs that should be used inside `quote!` and `spanned_quote!` +//! using the variable interpolation syntax in place of their equivalent structs and traits +//! present in `std`. +//! +//! To create hygienic proc macros, all the names must be its fully qualified form. These +//! unit structs help us to not specify the fully qualified name every single time. //! //! # Example //! Instead of writing this: @@ -33,14 +36,22 @@ use proc_macro2::TokenStream; use quote::{quote, ToTokens}; -pub(crate) struct FQAny; -pub(crate) struct FQBox; -pub(crate) struct FQClone; -pub(crate) struct FQDefault; -pub(crate) struct FQOption; -pub(crate) struct FQResult; -pub(crate) struct FQSend; -pub(crate) struct FQSync; +/// Fully Qualified (FQ) short name for [`::core::any::Any`] +pub struct FQAny; +/// Fully Qualified (FQ) short name for [`::std::boxed::Box`] +pub struct FQBox; +/// Fully Qualified (FQ) short name for [`::core::clone::Clone`] +pub struct FQClone; +/// Fully Qualified (FQ) short name for [`::core::default::Default`] +pub struct FQDefault; +/// Fully Qualified (FQ) short name for [`::core::option::Option`] +pub struct FQOption; +/// Fully Qualified (FQ) short name for [`::core::result::Result`] +pub struct FQResult; +/// Fully Qualified (FQ) short name for [`::core::marker::Send`] +pub struct FQSend; +/// Fully Qualified (FQ) short name for [`::core::marker::Sync`] +pub struct FQSync; impl ToTokens for FQAny { fn to_tokens(&self, tokens: &mut TokenStream) { diff --git a/crates/bevy_macro_utils/src/label.rs b/crates/bevy_macro_utils/src/label.rs new file mode 100644 index 00000000000000..e127b5ddc905ac --- /dev/null +++ b/crates/bevy_macro_utils/src/label.rs @@ -0,0 +1,208 @@ +use proc_macro::{TokenStream, TokenTree}; +use quote::{quote, quote_spanned}; +use rustc_hash::FxHashSet; +use syn::{spanned::Spanned, Ident}; + +use crate::BevyManifest; + +/// Finds an identifier that will not conflict with the specified set of tokens. +/// If the identifier is present in `haystack`, extra characters will be added +/// to it until it no longer conflicts with anything. +/// +/// Note that the returned identifier can still conflict in niche cases, +/// such as if an identifier in `haystack` is hidden behind an un-expanded macro. +pub fn ensure_no_collision(value: Ident, haystack: TokenStream) -> Ident { + // Collect all the identifiers in `haystack` into a set. + let idents = { + // List of token streams that will be visited in future loop iterations. + let mut unvisited = vec![haystack]; + // Identifiers we have found while searching tokens. + let mut found = FxHashSet::default(); + while let Some(tokens) = unvisited.pop() { + for t in tokens { + match t { + // Collect any identifiers we encounter. + TokenTree::Ident(ident) => { + found.insert(ident.to_string()); + } + // Queue up nested token streams to be visited in a future loop iteration. + TokenTree::Group(g) => unvisited.push(g.stream()), + TokenTree::Punct(_) | TokenTree::Literal(_) => {} + } + } + } + + found + }; + + let span = value.span(); + + // If there's a collision, add more characters to the identifier + // until it doesn't collide with anything anymore. + let mut value = value.to_string(); + while idents.contains(&value) { + value.push('X'); + } + + Ident::new(&value, span) +} + +/// Derive a label trait +/// +/// # Args +/// +/// - `input`: The [`syn::DeriveInput`] for struct that is deriving the label trait +/// - `trait_path`: The path [`syn::Path`] to the label trait +pub fn derive_boxed_label(input: syn::DeriveInput, trait_path: &syn::Path) -> TokenStream { + let bevy_utils_path = BevyManifest::default().get_path("bevy_utils"); + + let ident = input.ident; + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + let mut where_clause = where_clause.cloned().unwrap_or_else(|| syn::WhereClause { + where_token: Default::default(), + predicates: Default::default(), + }); + where_clause.predicates.push( + syn::parse2(quote! { + Self: 'static + Send + Sync + Clone + Eq + ::std::fmt::Debug + ::std::hash::Hash + }) + .unwrap(), + ); + + (quote! { + impl #impl_generics #trait_path for #ident #ty_generics #where_clause { + fn dyn_clone(&self) -> std::boxed::Box { + std::boxed::Box::new(std::clone::Clone::clone(self)) + } + + fn as_dyn_eq(&self) -> &dyn #bevy_utils_path::label::DynEq { + self + } + + fn dyn_hash(&self, mut state: &mut dyn ::std::hash::Hasher) { + let ty_id = #trait_path::inner_type_id(self); + ::std::hash::Hash::hash(&ty_id, &mut state); + ::std::hash::Hash::hash(self, &mut state); + } + } + + impl #impl_generics ::std::convert::AsRef for #ident #ty_generics #where_clause { + fn as_ref(&self) -> &dyn #trait_path { + self + } + } + }) + .into() +} + +/// Derive a label trait +/// +/// # Args +/// +/// - `input`: The [`syn::DeriveInput`] for struct that is deriving the label trait +/// - `trait_path`: The path [`syn::Path`] to the label trait +pub fn derive_label( + input: syn::DeriveInput, + trait_path: &syn::Path, + attr_name: &str, +) -> TokenStream { + // return true if the variant specified is an `ignore_fields` attribute + fn is_ignore(attr: &syn::Attribute, attr_name: &str) -> bool { + if attr.path().get_ident().as_ref().unwrap() != &attr_name { + return false; + } + + syn::custom_keyword!(ignore_fields); + attr.parse_args_with(|input: syn::parse::ParseStream| { + let ignore = input.parse::>()?.is_some(); + Ok(ignore) + }) + .unwrap() + } + + let ident = input.ident.clone(); + + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + let mut where_clause = where_clause.cloned().unwrap_or_else(|| syn::WhereClause { + where_token: Default::default(), + predicates: Default::default(), + }); + where_clause + .predicates + .push(syn::parse2(quote! { Self: 'static }).unwrap()); + + let as_str = match input.data { + syn::Data::Struct(d) => { + // see if the user tried to ignore fields incorrectly + if let Some(attr) = d + .fields + .iter() + .flat_map(|f| &f.attrs) + .find(|a| is_ignore(a, attr_name)) + { + let err_msg = format!("`#[{attr_name}(ignore_fields)]` cannot be applied to fields individually: add it to the struct declaration"); + return quote_spanned! { + attr.span() => compile_error!(#err_msg); + } + .into(); + } + // Structs must either be fieldless, or explicitly ignore the fields. + let ignore_fields = input.attrs.iter().any(|a| is_ignore(a, attr_name)); + if matches!(d.fields, syn::Fields::Unit) || ignore_fields { + let lit = ident.to_string(); + quote! { #lit } + } else { + let err_msg = format!("Labels cannot contain data, unless explicitly ignored with `#[{attr_name}(ignore_fields)]`"); + return quote_spanned! { + d.fields.span() => compile_error!(#err_msg); + } + .into(); + } + } + syn::Data::Enum(d) => { + // check if the user put #[label(ignore_fields)] in the wrong place + if let Some(attr) = input.attrs.iter().find(|a| is_ignore(a, attr_name)) { + let err_msg = format!("`#[{attr_name}(ignore_fields)]` can only be applied to enum variants or struct declarations"); + return quote_spanned! { + attr.span() => compile_error!(#err_msg); + } + .into(); + } + let arms = d.variants.iter().map(|v| { + // Variants must either be fieldless, or explicitly ignore the fields. + let ignore_fields = v.attrs.iter().any(|a| is_ignore(a, attr_name)); + if matches!(v.fields, syn::Fields::Unit) | ignore_fields { + let mut path = syn::Path::from(ident.clone()); + path.segments.push(v.ident.clone().into()); + let lit = format!("{ident}::{}", v.ident.clone()); + quote! { #path { .. } => #lit } + } else { + let err_msg = format!("Label variants cannot contain data, unless explicitly ignored with `#[{attr_name}(ignore_fields)]`"); + quote_spanned! { + v.fields.span() => _ => { compile_error!(#err_msg); } + } + } + }); + quote! { + match self { + #(#arms),* + } + } + } + syn::Data::Union(_) => { + return quote_spanned! { + input.span() => compile_error!("Unions cannot be used as labels."); + } + .into(); + } + }; + + (quote! { + impl #impl_generics #trait_path for #ident #ty_generics #where_clause { + fn as_str(&self) -> &'static str { + #as_str + } + } + }) + .into() +} diff --git a/crates/bevy_macro_utils/src/lib.rs b/crates/bevy_macro_utils/src/lib.rs index 59e224d51194df..2363d61cf0062e 100644 --- a/crates/bevy_macro_utils/src/lib.rs +++ b/crates/bevy_macro_utils/src/lib.rs @@ -1,324 +1,19 @@ #![allow(clippy::type_complexity)] +#![warn(missing_docs)] +#![deny(unsafe_code)] +//! A collection of helper types and functions for working on macros within the Bevy ecosystem. extern crate proc_macro; mod attrs; +mod bevy_manifest; +pub mod fq_std; +mod label; mod shape; mod symbol; pub use attrs::*; +pub use bevy_manifest::*; +pub use label::*; pub use shape::*; pub use symbol::*; - -use proc_macro::{TokenStream, TokenTree}; -use quote::{quote, quote_spanned}; -use rustc_hash::FxHashSet; -use std::{env, path::PathBuf}; -use syn::{spanned::Spanned, Ident}; -use toml_edit::{Document, Item}; - -pub struct BevyManifest { - manifest: Document, -} - -impl Default for BevyManifest { - fn default() -> Self { - Self { - manifest: env::var_os("CARGO_MANIFEST_DIR") - .map(PathBuf::from) - .map(|mut path| { - path.push("Cargo.toml"); - if !path.exists() { - panic!( - "No Cargo manifest found for crate. Expected: {}", - path.display() - ); - } - let manifest = std::fs::read_to_string(path.clone()).unwrap_or_else(|_| { - panic!("Unable to read cargo manifest: {}", path.display()) - }); - manifest.parse::().unwrap_or_else(|_| { - panic!("Failed to parse cargo manifest: {}", path.display()) - }) - }) - .expect("CARGO_MANIFEST_DIR is not defined."), - } - } -} -const BEVY: &str = "bevy"; -const BEVY_INTERNAL: &str = "bevy_internal"; - -impl BevyManifest { - pub fn maybe_get_path(&self, name: &str) -> Option { - fn dep_package(dep: &Item) -> Option<&str> { - if dep.as_str().is_some() { - None - } else { - dep.get("package").map(|name| name.as_str().unwrap()) - } - } - - let find_in_deps = |deps: &Item| -> Option { - let package = if let Some(dep) = deps.get(name) { - return Some(Self::parse_str(dep_package(dep).unwrap_or(name))); - } else if let Some(dep) = deps.get(BEVY) { - dep_package(dep).unwrap_or(BEVY) - } else if let Some(dep) = deps.get(BEVY_INTERNAL) { - dep_package(dep).unwrap_or(BEVY_INTERNAL) - } else { - return None; - }; - - let mut path = Self::parse_str::(package); - if let Some(module) = name.strip_prefix("bevy_") { - path.segments.push(Self::parse_str(module)); - } - Some(path) - }; - - let deps = self.manifest.get("dependencies"); - let deps_dev = self.manifest.get("dev-dependencies"); - - deps.and_then(find_in_deps) - .or_else(|| deps_dev.and_then(find_in_deps)) - } - - /// Returns the path for the crate with the given name. - /// - /// This is a convenience method for constructing a [manifest] and - /// calling the [`get_path`] method. - /// - /// This method should only be used where you just need the path and can't - /// cache the [manifest]. If caching is possible, it's recommended to create - /// the [manifest] yourself and use the [`get_path`] method. - /// - /// [`get_path`]: Self::get_path - /// [manifest]: Self - pub fn get_path_direct(name: &str) -> syn::Path { - Self::default().get_path(name) - } - - pub fn get_path(&self, name: &str) -> syn::Path { - self.maybe_get_path(name) - .unwrap_or_else(|| Self::parse_str(name)) - } - - pub fn parse_str(path: &str) -> T { - syn::parse(path.parse::().unwrap()).unwrap() - } - - pub fn get_subcrate(&self, subcrate: &str) -> Option { - self.maybe_get_path(BEVY) - .map(|bevy_path| { - let mut segments = bevy_path.segments; - segments.push(BevyManifest::parse_str(subcrate)); - syn::Path { - leading_colon: None, - segments, - } - }) - .or_else(|| self.maybe_get_path(&format!("bevy_{subcrate}"))) - } -} - -/// Finds an identifier that will not conflict with the specified set of tokens. -/// If the identifier is present in `haystack`, extra characters will be added -/// to it until it no longer conflicts with anything. -/// -/// Note that the returned identifier can still conflict in niche cases, -/// such as if an identifier in `haystack` is hidden behind an un-expanded macro. -pub fn ensure_no_collision(value: Ident, haystack: TokenStream) -> Ident { - // Collect all the identifiers in `haystack` into a set. - let idents = { - // List of token streams that will be visited in future loop iterations. - let mut unvisited = vec![haystack]; - // Identifiers we have found while searching tokens. - let mut found = FxHashSet::default(); - while let Some(tokens) = unvisited.pop() { - for t in tokens { - match t { - // Collect any identifiers we encounter. - TokenTree::Ident(ident) => { - found.insert(ident.to_string()); - } - // Queue up nested token streams to be visited in a future loop iteration. - TokenTree::Group(g) => unvisited.push(g.stream()), - TokenTree::Punct(_) | TokenTree::Literal(_) => {} - } - } - } - - found - }; - - let span = value.span(); - - // If there's a collision, add more characters to the identifier - // until it doesn't collide with anything anymore. - let mut value = value.to_string(); - while idents.contains(&value) { - value.push('X'); - } - - Ident::new(&value, span) -} - -/// Derive a label trait -/// -/// # Args -/// -/// - `input`: The [`syn::DeriveInput`] for struct that is deriving the label trait -/// - `trait_path`: The path [`syn::Path`] to the label trait -pub fn derive_boxed_label(input: syn::DeriveInput, trait_path: &syn::Path) -> TokenStream { - let bevy_utils_path = BevyManifest::default().get_path("bevy_utils"); - - let ident = input.ident; - let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); - let mut where_clause = where_clause.cloned().unwrap_or_else(|| syn::WhereClause { - where_token: Default::default(), - predicates: Default::default(), - }); - where_clause.predicates.push( - syn::parse2(quote! { - Self: 'static + Send + Sync + Clone + Eq + ::std::fmt::Debug + ::std::hash::Hash - }) - .unwrap(), - ); - - (quote! { - impl #impl_generics #trait_path for #ident #ty_generics #where_clause { - fn dyn_clone(&self) -> std::boxed::Box { - std::boxed::Box::new(std::clone::Clone::clone(self)) - } - - fn as_dyn_eq(&self) -> &dyn #bevy_utils_path::label::DynEq { - self - } - - fn dyn_hash(&self, mut state: &mut dyn ::std::hash::Hasher) { - let ty_id = #trait_path::inner_type_id(self); - ::std::hash::Hash::hash(&ty_id, &mut state); - ::std::hash::Hash::hash(self, &mut state); - } - } - - impl #impl_generics ::std::convert::AsRef for #ident #ty_generics #where_clause { - fn as_ref(&self) -> &dyn #trait_path { - self - } - } - }) - .into() -} - -/// Derive a label trait -/// -/// # Args -/// -/// - `input`: The [`syn::DeriveInput`] for struct that is deriving the label trait -/// - `trait_path`: The path [`syn::Path`] to the label trait -pub fn derive_label( - input: syn::DeriveInput, - trait_path: &syn::Path, - attr_name: &str, -) -> TokenStream { - // return true if the variant specified is an `ignore_fields` attribute - fn is_ignore(attr: &syn::Attribute, attr_name: &str) -> bool { - if attr.path().get_ident().as_ref().unwrap() != &attr_name { - return false; - } - - syn::custom_keyword!(ignore_fields); - attr.parse_args_with(|input: syn::parse::ParseStream| { - let ignore = input.parse::>()?.is_some(); - Ok(ignore) - }) - .unwrap() - } - - let ident = input.ident.clone(); - - let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); - let mut where_clause = where_clause.cloned().unwrap_or_else(|| syn::WhereClause { - where_token: Default::default(), - predicates: Default::default(), - }); - where_clause - .predicates - .push(syn::parse2(quote! { Self: 'static }).unwrap()); - - let as_str = match input.data { - syn::Data::Struct(d) => { - // see if the user tried to ignore fields incorrectly - if let Some(attr) = d - .fields - .iter() - .flat_map(|f| &f.attrs) - .find(|a| is_ignore(a, attr_name)) - { - let err_msg = format!("`#[{attr_name}(ignore_fields)]` cannot be applied to fields individually: add it to the struct declaration"); - return quote_spanned! { - attr.span() => compile_error!(#err_msg); - } - .into(); - } - // Structs must either be fieldless, or explicitly ignore the fields. - let ignore_fields = input.attrs.iter().any(|a| is_ignore(a, attr_name)); - if matches!(d.fields, syn::Fields::Unit) || ignore_fields { - let lit = ident.to_string(); - quote! { #lit } - } else { - let err_msg = format!("Labels cannot contain data, unless explicitly ignored with `#[{attr_name}(ignore_fields)]`"); - return quote_spanned! { - d.fields.span() => compile_error!(#err_msg); - } - .into(); - } - } - syn::Data::Enum(d) => { - // check if the user put #[label(ignore_fields)] in the wrong place - if let Some(attr) = input.attrs.iter().find(|a| is_ignore(a, attr_name)) { - let err_msg = format!("`#[{attr_name}(ignore_fields)]` can only be applied to enum variants or struct declarations"); - return quote_spanned! { - attr.span() => compile_error!(#err_msg); - } - .into(); - } - let arms = d.variants.iter().map(|v| { - // Variants must either be fieldless, or explicitly ignore the fields. - let ignore_fields = v.attrs.iter().any(|a| is_ignore(a, attr_name)); - if matches!(v.fields, syn::Fields::Unit) | ignore_fields { - let mut path = syn::Path::from(ident.clone()); - path.segments.push(v.ident.clone().into()); - let lit = format!("{ident}::{}", v.ident.clone()); - quote! { #path { .. } => #lit } - } else { - let err_msg = format!("Label variants cannot contain data, unless explicitly ignored with `#[{attr_name}(ignore_fields)]`"); - quote_spanned! { - v.fields.span() => _ => { compile_error!(#err_msg); } - } - } - }); - quote! { - match self { - #(#arms),* - } - } - } - syn::Data::Union(_) => { - return quote_spanned! { - input.span() => compile_error!("Unions cannot be used as labels."); - } - .into(); - } - }; - - (quote! { - impl #impl_generics #trait_path for #ident #ty_generics #where_clause { - fn as_str(&self) -> &'static str { - #as_str - } - } - }) - .into() -} diff --git a/crates/bevy_macro_utils/src/symbol.rs b/crates/bevy_macro_utils/src/symbol.rs index dc639f4d831339..f99b8dfc5cbac2 100644 --- a/crates/bevy_macro_utils/src/symbol.rs +++ b/crates/bevy_macro_utils/src/symbol.rs @@ -1,6 +1,7 @@ use std::fmt::{self, Display}; use syn::{Ident, Path}; +/// A single named value, representable as a [string](str). #[derive(Copy, Clone)] pub struct Symbol(pub &'static str); diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/container_attributes.rs b/crates/bevy_reflect/bevy_reflect_derive/src/container_attributes.rs index ad2067e22239ef..a251b716a5d76b 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/container_attributes.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/container_attributes.rs @@ -5,8 +5,8 @@ //! the derive helper attribute for `Reflect`, which looks like: //! `#[reflect(PartialEq, Default, ...)]` and `#[reflect_value(PartialEq, Default, ...)]`. -use crate::fq_std::{FQAny, FQOption}; use crate::utility; +use bevy_macro_utils::fq_std::{FQAny, FQOption}; use proc_macro2::{Ident, Span}; use quote::quote_spanned; use syn::parse::{Parse, ParseStream}; diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/documentation.rs b/crates/bevy_reflect/bevy_reflect_derive/src/documentation.rs index a19245d85486e3..33aec4c4f32217 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/documentation.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/documentation.rs @@ -1,6 +1,6 @@ //! Contains code related to documentation reflection (requires the `documentation` feature). -use crate::fq_std::FQOption; +use bevy_macro_utils::fq_std::FQOption; use proc_macro2::TokenStream; use quote::{quote, ToTokens}; use syn::{Attribute, Expr, ExprLit, Lit, Meta}; diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/enum_utility.rs b/crates/bevy_reflect/bevy_reflect_derive/src/enum_utility.rs index 2a09055e209d59..36109bbd30c3f8 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/enum_utility.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/enum_utility.rs @@ -1,10 +1,10 @@ use crate::derive_data::StructField; use crate::field_attributes::DefaultBehavior; -use crate::fq_std::{FQDefault, FQOption}; use crate::{ derive_data::{EnumVariantFields, ReflectEnum}, utility::ident_or_index, }; +use bevy_macro_utils::fq_std::{FQDefault, FQOption}; use proc_macro2::Ident; use quote::{quote, ToTokens}; use syn::Member; diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/from_reflect.rs b/crates/bevy_reflect/bevy_reflect_derive/src/from_reflect.rs index 00ae55339f805a..dc84fc6ab61c45 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/from_reflect.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/from_reflect.rs @@ -2,9 +2,9 @@ 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::fq_std::{FQAny, FQClone, FQDefault, FQOption}; use crate::utility::{extend_where_clause, ident_or_index, WhereClauseOptions}; use crate::{ReflectMeta, ReflectStruct}; +use bevy_macro_utils::fq_std::{FQAny, FQClone, FQDefault, FQOption}; use proc_macro2::Span; use quote::{quote, ToTokens}; use syn::{Field, Ident, Lit, LitInt, LitStr, Member}; diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/impls/enums.rs b/crates/bevy_reflect/bevy_reflect_derive/src/impls/enums.rs index 19b6bebbd78ecd..aad84808ccc3ad 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/impls/enums.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/impls/enums.rs @@ -1,8 +1,8 @@ use crate::derive_data::{EnumVariant, EnumVariantFields, ReflectEnum, StructField}; use crate::enum_utility::{get_variant_constructors, EnumVariantConstructors}; -use crate::fq_std::{FQAny, FQBox, FQOption, FQResult}; 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; use syn::Fields; diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/impls/structs.rs b/crates/bevy_reflect/bevy_reflect_derive/src/impls/structs.rs index 40ca5e3bd503b3..a4d6e92007012d 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/impls/structs.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/impls/structs.rs @@ -1,7 +1,7 @@ -use crate::fq_std::{FQAny, FQBox, FQDefault, FQOption, FQResult}; use crate::impls::{impl_type_path, impl_typed}; use crate::utility::{extend_where_clause, ident_or_index}; use crate::ReflectStruct; +use bevy_macro_utils::fq_std::{FQAny, FQBox, FQDefault, FQOption, FQResult}; use quote::{quote, ToTokens}; /// Implements `Struct`, `GetTypeRegistration`, and `Reflect` for the given derive data. diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/impls/tuple_structs.rs b/crates/bevy_reflect/bevy_reflect_derive/src/impls/tuple_structs.rs index 37984fc1f08d73..4e39a034e13471 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/impls/tuple_structs.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/impls/tuple_structs.rs @@ -1,7 +1,7 @@ -use crate::fq_std::{FQAny, FQBox, FQDefault, FQOption, FQResult}; 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}; use syn::{Index, Member}; diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/impls/values.rs b/crates/bevy_reflect/bevy_reflect_derive/src/impls/values.rs index e92383a7463437..88b8479737cecb 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/impls/values.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/impls/values.rs @@ -1,7 +1,7 @@ -use crate::fq_std::{FQAny, FQBox, FQClone, FQOption, FQResult}; use crate::impls::{impl_type_path, impl_typed}; use crate::utility::{extend_where_clause, WhereClauseOptions}; use crate::ReflectMeta; +use bevy_macro_utils::fq_std::{FQAny, FQBox, FQClone, FQOption, FQResult}; use quote::quote; /// Implements `GetTypeRegistration` and `Reflect` for the given type data. diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/lib.rs b/crates/bevy_reflect/bevy_reflect_derive/src/lib.rs index 11c1b888013859..f2251d3f8d0741 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/lib.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/lib.rs @@ -20,7 +20,6 @@ mod derive_data; mod documentation; mod enum_utility; mod field_attributes; -mod fq_std; mod from_reflect; mod impls; mod reflect_value; diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/trait_reflection.rs b/crates/bevy_reflect/bevy_reflect_derive/src/trait_reflection.rs index 14bd9d6d406746..b8ea651b884925 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/trait_reflection.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/trait_reflection.rs @@ -1,5 +1,7 @@ -use crate::fq_std::{FQBox, FQClone, FQOption, FQResult}; -use bevy_macro_utils::BevyManifest; +use bevy_macro_utils::{ + fq_std::{FQBox, FQClone, FQOption, FQResult}, + BevyManifest, +}; use proc_macro::TokenStream; use quote::quote; use syn::{parse::Parse, parse_macro_input, Attribute, ItemTrait, Token}; diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/utility.rs b/crates/bevy_reflect/bevy_reflect_derive/src/utility.rs index 925e678f2956dc..9d25e35a37533e 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/utility.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/utility.rs @@ -2,8 +2,10 @@ use crate::derive_data::{ReflectMeta, StructField}; use crate::field_attributes::ReflectIgnoreBehavior; -use crate::fq_std::{FQAny, FQOption, FQSend, FQSync}; -use bevy_macro_utils::BevyManifest; +use bevy_macro_utils::{ + fq_std::{FQAny, FQOption, FQSend, FQSync}, + BevyManifest, +}; use bit_set::BitSet; use proc_macro2::{Ident, Span}; use quote::{quote, ToTokens};