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
@ -13,3 +13,4 @@ toml_edit = "0.19"
|
|||||||
syn = "2.0"
|
syn = "2.0"
|
||||||
quote = "1.0"
|
quote = "1.0"
|
||||||
rustc-hash = "1.0"
|
rustc-hash = "1.0"
|
||||||
|
proc-macro2 = "1.0"
|
||||||
|
@ -2,6 +2,7 @@ use syn::{Expr, ExprLit, Lit};
|
|||||||
|
|
||||||
use crate::symbol::Symbol;
|
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> {
|
pub fn get_lit_str(attr_name: Symbol, value: &Expr) -> syn::Result<&syn::LitStr> {
|
||||||
if let Expr::Lit(ExprLit {
|
if let Expr::Lit(ExprLit {
|
||||||
lit: Lit::Str(lit), ..
|
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> {
|
pub fn get_lit_bool(attr_name: Symbol, value: &Expr) -> syn::Result<bool> {
|
||||||
if let Expr::Lit(ExprLit {
|
if let Expr::Lit(ExprLit {
|
||||||
lit: Lit::Bool(lit),
|
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`.
|
//! 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
|
||||||
//! 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.
|
//! 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
|
//! # Example
|
||||||
//! Instead of writing this:
|
//! Instead of writing this:
|
||||||
@ -33,14 +36,22 @@
|
|||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::{quote, ToTokens};
|
use quote::{quote, ToTokens};
|
||||||
|
|
||||||
pub(crate) struct FQAny;
|
/// Fully Qualified (FQ) short name for [`::core::any::Any`]
|
||||||
pub(crate) struct FQBox;
|
pub struct FQAny;
|
||||||
pub(crate) struct FQClone;
|
/// Fully Qualified (FQ) short name for [`::std::boxed::Box`]
|
||||||
pub(crate) struct FQDefault;
|
pub struct FQBox;
|
||||||
pub(crate) struct FQOption;
|
/// Fully Qualified (FQ) short name for [`::core::clone::Clone`]
|
||||||
pub(crate) struct FQResult;
|
pub struct FQClone;
|
||||||
pub(crate) struct FQSend;
|
/// Fully Qualified (FQ) short name for [`::core::default::Default`]
|
||||||
pub(crate) struct FQSync;
|
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 {
|
impl ToTokens for FQAny {
|
||||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
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)]
|
#![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;
|
extern crate proc_macro;
|
||||||
|
|
||||||
mod attrs;
|
mod attrs;
|
||||||
|
mod bevy_manifest;
|
||||||
|
pub mod fq_std;
|
||||||
|
mod label;
|
||||||
mod shape;
|
mod shape;
|
||||||
mod symbol;
|
mod symbol;
|
||||||
|
|
||||||
pub use attrs::*;
|
pub use attrs::*;
|
||||||
|
pub use bevy_manifest::*;
|
||||||
|
pub use label::*;
|
||||||
pub use shape::*;
|
pub use shape::*;
|
||||||
pub use symbol::*;
|
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 std::fmt::{self, Display};
|
||||||
use syn::{Ident, Path};
|
use syn::{Ident, Path};
|
||||||
|
|
||||||
|
/// A single named value, representable as a [string](str).
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct Symbol(pub &'static str);
|
pub struct Symbol(pub &'static str);
|
||||||
|
|
||||||
|
@ -5,8 +5,8 @@
|
|||||||
//! the derive helper attribute for `Reflect`, which looks like:
|
//! the derive helper attribute for `Reflect`, which looks like:
|
||||||
//! `#[reflect(PartialEq, Default, ...)]` and `#[reflect_value(PartialEq, Default, ...)]`.
|
//! `#[reflect(PartialEq, Default, ...)]` and `#[reflect_value(PartialEq, Default, ...)]`.
|
||||||
|
|
||||||
use crate::fq_std::{FQAny, FQOption};
|
|
||||||
use crate::utility;
|
use crate::utility;
|
||||||
|
use bevy_macro_utils::fq_std::{FQAny, FQOption};
|
||||||
use proc_macro2::{Ident, Span};
|
use proc_macro2::{Ident, Span};
|
||||||
use quote::quote_spanned;
|
use quote::quote_spanned;
|
||||||
use syn::parse::{Parse, ParseStream};
|
use syn::parse::{Parse, ParseStream};
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
//! Contains code related to documentation reflection (requires the `documentation` feature).
|
//! 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 proc_macro2::TokenStream;
|
||||||
use quote::{quote, ToTokens};
|
use quote::{quote, ToTokens};
|
||||||
use syn::{Attribute, Expr, ExprLit, Lit, Meta};
|
use syn::{Attribute, Expr, ExprLit, Lit, Meta};
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
use crate::derive_data::StructField;
|
use crate::derive_data::StructField;
|
||||||
use crate::field_attributes::DefaultBehavior;
|
use crate::field_attributes::DefaultBehavior;
|
||||||
use crate::fq_std::{FQDefault, FQOption};
|
|
||||||
use crate::{
|
use crate::{
|
||||||
derive_data::{EnumVariantFields, ReflectEnum},
|
derive_data::{EnumVariantFields, ReflectEnum},
|
||||||
utility::ident_or_index,
|
utility::ident_or_index,
|
||||||
};
|
};
|
||||||
|
use bevy_macro_utils::fq_std::{FQDefault, FQOption};
|
||||||
use proc_macro2::Ident;
|
use proc_macro2::Ident;
|
||||||
use quote::{quote, ToTokens};
|
use quote::{quote, ToTokens};
|
||||||
use syn::Member;
|
use syn::Member;
|
||||||
|
@ -2,9 +2,9 @@ use crate::container_attributes::REFLECT_DEFAULT;
|
|||||||
use crate::derive_data::ReflectEnum;
|
use crate::derive_data::ReflectEnum;
|
||||||
use crate::enum_utility::{get_variant_constructors, EnumVariantConstructors};
|
use crate::enum_utility::{get_variant_constructors, EnumVariantConstructors};
|
||||||
use crate::field_attributes::DefaultBehavior;
|
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::utility::{extend_where_clause, ident_or_index, WhereClauseOptions};
|
||||||
use crate::{ReflectMeta, ReflectStruct};
|
use crate::{ReflectMeta, ReflectStruct};
|
||||||
|
use bevy_macro_utils::fq_std::{FQAny, FQClone, FQDefault, FQOption};
|
||||||
use proc_macro2::Span;
|
use proc_macro2::Span;
|
||||||
use quote::{quote, ToTokens};
|
use quote::{quote, ToTokens};
|
||||||
use syn::{Field, Ident, Lit, LitInt, LitStr, Member};
|
use syn::{Field, Ident, Lit, LitInt, LitStr, Member};
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
use crate::derive_data::{EnumVariant, EnumVariantFields, ReflectEnum, StructField};
|
use crate::derive_data::{EnumVariant, EnumVariantFields, ReflectEnum, StructField};
|
||||||
use crate::enum_utility::{get_variant_constructors, EnumVariantConstructors};
|
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::impls::{impl_type_path, impl_typed};
|
||||||
use crate::utility::extend_where_clause;
|
use crate::utility::extend_where_clause;
|
||||||
|
use bevy_macro_utils::fq_std::{FQAny, FQBox, FQOption, FQResult};
|
||||||
use proc_macro2::{Ident, Span};
|
use proc_macro2::{Ident, Span};
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::Fields;
|
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::impls::{impl_type_path, impl_typed};
|
||||||
use crate::utility::{extend_where_clause, ident_or_index};
|
use crate::utility::{extend_where_clause, ident_or_index};
|
||||||
use crate::ReflectStruct;
|
use crate::ReflectStruct;
|
||||||
|
use bevy_macro_utils::fq_std::{FQAny, FQBox, FQDefault, FQOption, FQResult};
|
||||||
use quote::{quote, ToTokens};
|
use quote::{quote, ToTokens};
|
||||||
|
|
||||||
/// Implements `Struct`, `GetTypeRegistration`, and `Reflect` for the given derive data.
|
/// 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::impls::{impl_type_path, impl_typed};
|
||||||
use crate::utility::extend_where_clause;
|
use crate::utility::extend_where_clause;
|
||||||
use crate::ReflectStruct;
|
use crate::ReflectStruct;
|
||||||
|
use bevy_macro_utils::fq_std::{FQAny, FQBox, FQDefault, FQOption, FQResult};
|
||||||
use quote::{quote, ToTokens};
|
use quote::{quote, ToTokens};
|
||||||
use syn::{Index, Member};
|
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::impls::{impl_type_path, impl_typed};
|
||||||
use crate::utility::{extend_where_clause, WhereClauseOptions};
|
use crate::utility::{extend_where_clause, WhereClauseOptions};
|
||||||
use crate::ReflectMeta;
|
use crate::ReflectMeta;
|
||||||
|
use bevy_macro_utils::fq_std::{FQAny, FQBox, FQClone, FQOption, FQResult};
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
|
|
||||||
/// Implements `GetTypeRegistration` and `Reflect` for the given type data.
|
/// Implements `GetTypeRegistration` and `Reflect` for the given type data.
|
||||||
|
@ -20,7 +20,6 @@ mod derive_data;
|
|||||||
mod documentation;
|
mod documentation;
|
||||||
mod enum_utility;
|
mod enum_utility;
|
||||||
mod field_attributes;
|
mod field_attributes;
|
||||||
mod fq_std;
|
|
||||||
mod from_reflect;
|
mod from_reflect;
|
||||||
mod impls;
|
mod impls;
|
||||||
mod reflect_value;
|
mod reflect_value;
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
use crate::fq_std::{FQBox, FQClone, FQOption, FQResult};
|
use bevy_macro_utils::{
|
||||||
use bevy_macro_utils::BevyManifest;
|
fq_std::{FQBox, FQClone, FQOption, FQResult},
|
||||||
|
BevyManifest,
|
||||||
|
};
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::{parse::Parse, parse_macro_input, Attribute, ItemTrait, Token};
|
use syn::{parse::Parse, parse_macro_input, Attribute, ItemTrait, Token};
|
||||||
|
@ -2,8 +2,10 @@
|
|||||||
|
|
||||||
use crate::derive_data::{ReflectMeta, StructField};
|
use crate::derive_data::{ReflectMeta, StructField};
|
||||||
use crate::field_attributes::ReflectIgnoreBehavior;
|
use crate::field_attributes::ReflectIgnoreBehavior;
|
||||||
use crate::fq_std::{FQAny, FQOption, FQSend, FQSync};
|
use bevy_macro_utils::{
|
||||||
use bevy_macro_utils::BevyManifest;
|
fq_std::{FQAny, FQOption, FQSend, FQSync},
|
||||||
|
BevyManifest,
|
||||||
|
};
|
||||||
use bit_set::BitSet;
|
use bit_set::BitSet;
|
||||||
use proc_macro2::{Ident, Span};
|
use proc_macro2::{Ident, Span};
|
||||||
use quote::{quote, ToTokens};
|
use quote::{quote, ToTokens};
|
||||||
|
Loading…
Reference in New Issue
Block a user