mirror of
https://github.com/bevyengine/bevy
synced 2024-09-20 06:22:01 +00:00
Moved fq_std
from bevy_reflect_derive
to bevy_macro_utils
(#9956)
# Objective - Fixes #9363 ## Solution Moved `fq_std` from `bevy_reflect_derive` to `bevy_macro_utils`. This does make the `FQ*` types public where they were previously private, which is a change to the public-facing API, but I don't believe a breaking one. Additionally, I've done a basic QA pass over the `bevy_macro_utils` crate, adding `deny(unsafe)`, `warn(missing_docs)`, and documentation where required.
This commit is contained in:
parent
0f20cfaa57
commit
e5dbde86fb
18 changed files with 384 additions and 337 deletions
|
@ -13,3 +13,4 @@ toml_edit = "0.19"
|
|||
syn = "2.0"
|
||||
quote = "1.0"
|
||||
rustc-hash = "1.0"
|
||||
proc-macro2 = "1.0"
|
||||
|
|
|
@ -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<bool> {
|
||||
if let Expr::Lit(ExprLit {
|
||||
lit: Lit::Bool(lit),
|
||||
|
|
126
crates/bevy_macro_utils/src/bevy_manifest.rs
Normal file
126
crates/bevy_macro_utils/src/bevy_manifest.rs
Normal file
|
@ -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::<Document>().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<syn::Path> {
|
||||
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<syn::Path> {
|
||||
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::<syn::Path>(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<T: syn::parse::Parse>(path: &str) -> Option<T> {
|
||||
syn::parse(path.parse::<TokenStream>().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<T: syn::parse::Parse>(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<syn::Path> {
|
||||
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}")))
|
||||
}
|
||||
}
|
|
@ -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) {
|
208
crates/bevy_macro_utils/src/label.rs
Normal file
208
crates/bevy_macro_utils/src/label.rs
Normal file
|
@ -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<dyn #trait_path> {
|
||||
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<dyn #trait_path> 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::<Option<ignore_fields>>()?.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()
|
||||
}
|
|
@ -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::<Document>().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<syn::Path> {
|
||||
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<syn::Path> {
|
||||
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::<syn::Path>(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<T: syn::parse::Parse>(path: &str) -> T {
|
||||
syn::parse(path.parse::<TokenStream>().unwrap()).unwrap()
|
||||
}
|
||||
|
||||
pub fn get_subcrate(&self, subcrate: &str) -> Option<syn::Path> {
|
||||
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<dyn #trait_path> {
|
||||
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<dyn #trait_path> 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::<Option<ignore_fields>>()?.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()
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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};
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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};
|
||||
|
|
Loading…
Reference in a new issue