implement TypeUuid for primitives and fix multiple-parameter generics having the same TypeUuid (#6633)

# Objective

- Fixes #5432 
- Fixes #6680

## Solution

- move code responsible for generating the `impl TypeUuid` from `type_uuid_derive` into a new function, `gen_impl_type_uuid`.
- this allows the new proc macro, `impl_type_uuid`, to call the code for generation.
- added struct `TypeUuidDef` and implemented `syn::Parse` to allow parsing of the input for the new macro.
- finally, used the new macro `impl_type_uuid` to implement `TypeUuid` for the standard library (in `crates/bevy_reflect/src/type_uuid_impl.rs`).
- fixes #6680 by doing a wrapping add of the param's index to its `TYPE_UUID`

Co-authored-by: dis-da-moe <84386186+dis-da-moe@users.noreply.github.com>
This commit is contained in:
dis-da-moe 2023-02-16 17:09:44 +00:00
parent 81307290e2
commit 8853bef6df
20 changed files with 281 additions and 98 deletions

View file

@ -11,79 +11,11 @@ use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::{format_ident, quote};
use syn::{
parse::{Parse, ParseStream},
parse_macro_input, parse_quote,
punctuated::Punctuated,
spanned::Spanned,
token::Comma,
ConstParam, DeriveInput, Field, GenericParam, Ident, Index, LitInt, Meta, MetaList, NestedMeta,
Result, Token, TypeParam,
parse::ParseStream, parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned,
ConstParam, DeriveInput, Field, GenericParam, Ident, Index, Meta, MetaList, NestedMeta, Token,
TypeParam,
};
struct AllTuples {
macro_ident: Ident,
start: usize,
end: usize,
idents: Vec<Ident>,
}
impl Parse for AllTuples {
fn parse(input: ParseStream) -> Result<Self> {
let macro_ident = input.parse::<Ident>()?;
input.parse::<Comma>()?;
let start = input.parse::<LitInt>()?.base10_parse()?;
input.parse::<Comma>()?;
let end = input.parse::<LitInt>()?.base10_parse()?;
input.parse::<Comma>()?;
let mut idents = vec![input.parse::<Ident>()?];
while input.parse::<Comma>().is_ok() {
idents.push(input.parse::<Ident>()?);
}
Ok(AllTuples {
macro_ident,
start,
end,
idents,
})
}
}
#[proc_macro]
pub fn all_tuples(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as AllTuples);
let len = input.end - input.start;
let mut ident_tuples = Vec::with_capacity(len);
for i in input.start..=input.end {
let idents = input
.idents
.iter()
.map(|ident| format_ident!("{}{}", ident, i));
if input.idents.len() < 2 {
ident_tuples.push(quote! {
#(#idents)*
});
} else {
ident_tuples.push(quote! {
(#(#idents),*)
});
}
}
let macro_ident = &input.macro_ident;
let invocations = (input.start..=input.end).map(|i| {
let ident_tuples = &ident_tuples[..i];
quote! {
#macro_ident!(#(#ident_tuples),*);
}
});
TokenStream::from(quote! {
#(
#invocations
)*
})
}
enum BundleFieldKind {
Component,
Ignore,

View file

@ -15,8 +15,8 @@ use crate::{
storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow},
TypeIdMap,
};
use bevy_ecs_macros::all_tuples;
use bevy_ptr::OwningPtr;
use bevy_utils::all_tuples;
use std::any::TypeId;
/// The `Bundle` trait enables insertion and removal of [`Component`]s from an entity.

View file

@ -52,7 +52,7 @@ pub mod prelude {
};
}
pub use bevy_ecs_macros::all_tuples;
pub use bevy_utils::all_tuples;
/// A specialized hashmap type with Key of `TypeId`
type TypeIdMap<V> = rustc_hash::FxHashMap<TypeId, V>;

View file

@ -7,9 +7,9 @@ use crate::{
storage::{ComponentSparseSet, Table, TableRow},
world::{Mut, Ref, World},
};
use bevy_ecs_macros::all_tuples;
pub use bevy_ecs_macros::WorldQuery;
use bevy_ptr::{ThinSlicePtr, UnsafeCellDeref};
use bevy_utils::all_tuples;
use std::{cell::UnsafeCell, marker::PhantomData};
/// Types that can be fetched from a [`World`] using a [`Query`].

View file

@ -6,8 +6,8 @@ use crate::{
storage::{Column, ComponentSparseSet, Table, TableRow},
world::World,
};
use bevy_ecs_macros::all_tuples;
use bevy_ptr::{ThinSlicePtr, UnsafeCellDeref};
use bevy_utils::all_tuples;
use std::{cell::UnsafeCell, marker::PhantomData};
use super::ReadOnlyWorldQuery;

View file

@ -1,4 +1,4 @@
use bevy_ecs_macros::all_tuples;
use bevy_utils::all_tuples;
use crate::{
schedule::{

View file

@ -9,7 +9,8 @@ use crate::{
},
world::{World, WorldId},
};
use bevy_ecs_macros::all_tuples;
use bevy_utils::all_tuples;
use std::{any::TypeId, borrow::Cow, marker::PhantomData};
/// A function system that runs with exclusive [`World`] access.

View file

@ -4,7 +4,7 @@ use crate::{
system::{Local, SystemMeta, SystemParam, SystemState},
world::World,
};
use bevy_ecs_macros::all_tuples;
use bevy_utils::all_tuples;
use bevy_utils::synccell::SyncCell;
pub trait ExclusiveSystemParam: Sized {

View file

@ -7,7 +7,8 @@ use crate::{
system::{check_system_change_tick, ReadOnlySystemParam, System, SystemParam, SystemParamItem},
world::{World, WorldId},
};
use bevy_ecs_macros::all_tuples;
use bevy_utils::all_tuples;
use std::{any::TypeId, borrow::Cow, marker::PhantomData};
use super::ReadOnlySystem;

View file

@ -11,11 +11,11 @@ use crate::{
system::{Query, SystemMeta},
world::{FromWorld, World},
};
use bevy_ecs_macros::impl_param_set;
pub use bevy_ecs_macros::Resource;
pub use bevy_ecs_macros::SystemParam;
use bevy_ecs_macros::{all_tuples, impl_param_set};
use bevy_ptr::UnsafeCellDeref;
use bevy_utils::synccell::SyncCell;
use bevy_utils::{all_tuples, synccell::SyncCell};
use std::{
borrow::Cow,
fmt::Debug,

View file

@ -30,11 +30,13 @@ mod type_uuid;
mod utility;
use crate::derive_data::{ReflectDerive, ReflectMeta, ReflectStruct};
use crate::type_uuid::gen_impl_type_uuid;
use proc_macro::TokenStream;
use quote::quote;
use reflect_value::ReflectValueDef;
use syn::spanned::Spanned;
use syn::{parse_macro_input, DeriveInput};
use type_uuid::TypeUuidDef;
pub(crate) static REFLECT_ATTRIBUTE_NAME: &str = "reflect";
pub(crate) static REFLECT_VALUE_ATTRIBUTE_NAME: &str = "reflect_value";
@ -185,3 +187,10 @@ pub fn impl_from_reflect_value(input: TokenStream) -> TokenStream {
def.traits.unwrap_or_default(),
))
}
/// Derives `TypeUuid` for the given type. This is used internally to implement `TypeUuid` on foreign types, such as those in the std. This macro should be used in the format of `<[Generic Params]> [Type (Path)], [Uuid (String Literal)]`.
#[proc_macro]
pub fn impl_type_uuid(input: TokenStream) -> TokenStream {
let def = parse_macro_input!(input as TypeUuidDef);
gen_impl_type_uuid(def)
}

View file

@ -2,25 +2,17 @@ extern crate proc_macro;
use bevy_macro_utils::BevyManifest;
use quote::quote;
use syn::parse::{Parse, ParseStream};
use syn::*;
use uuid::Uuid;
/// Parses input from a derive of `TypeUuid`.
pub(crate) fn type_uuid_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
// Construct a representation of Rust code as a syntax tree
// that we can manipulate
let mut ast: DeriveInput = syn::parse(input).unwrap();
let bevy_reflect_path: Path = BevyManifest::default().get_path("bevy_reflect");
let ast: DeriveInput = syn::parse(input).unwrap();
// Build the trait implementation
let name = &ast.ident;
ast.generics.type_params_mut().for_each(|param| {
param
.bounds
.push(syn::parse_quote!(#bevy_reflect_path::TypeUuid));
});
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
let type_ident = ast.ident;
let mut uuid = None;
for attribute in ast.attrs.iter().filter_map(|attr| attr.parse_meta().ok()) {
@ -50,24 +42,73 @@ pub(crate) fn type_uuid_derive(input: proc_macro::TokenStream) -> proc_macro::To
let uuid =
uuid.expect("No `#[uuid = \"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"` attribute found.");
gen_impl_type_uuid(TypeUuidDef {
type_ident,
generics: ast.generics,
uuid,
})
}
/// Generates an implementation of `TypeUuid`. If there any generics, the `TYPE_UUID` will be a composite of the generic types' `TYPE_UUID`.
pub(crate) fn gen_impl_type_uuid(def: TypeUuidDef) -> proc_macro::TokenStream {
let uuid = def.uuid;
let mut generics = def.generics;
let ty = def.type_ident;
let bevy_reflect_path: Path = BevyManifest::default().get_path("bevy_reflect");
generics.type_params_mut().for_each(|param| {
param
.bounds
.push(syn::parse_quote!(#bevy_reflect_path::TypeUuid));
});
let bytes = uuid
.as_bytes()
.iter()
.map(|byte| format!("{byte:#X}"))
.map(|byte_str| syn::parse_str::<LitInt>(&byte_str).unwrap());
let (impl_generics, type_generics, where_clause) = generics.split_for_impl();
let base = quote! { #bevy_reflect_path::Uuid::from_bytes([#( #bytes ),*]) };
let type_uuid = ast.generics.type_params().fold(base, |acc, param| {
let type_uuid = generics.type_params().enumerate().fold(base, |acc, (index, param)| {
let ident = &param.ident;
let param_uuid = quote!(
#bevy_reflect_path::Uuid::from_u128(<#ident as #bevy_reflect_path::TypeUuid>::TYPE_UUID.as_u128().wrapping_add(#index as u128))
);
quote! {
#bevy_reflect_path::__macro_exports::generate_composite_uuid(#acc, <#ident as #bevy_reflect_path::TypeUuid>::TYPE_UUID)
#bevy_reflect_path::__macro_exports::generate_composite_uuid(#acc, #param_uuid)
}
});
let gen = quote! {
impl #impl_generics #bevy_reflect_path::TypeUuid for #name #type_generics #where_clause {
impl #impl_generics #bevy_reflect_path::TypeUuid for #ty #type_generics #where_clause {
const TYPE_UUID: #bevy_reflect_path::Uuid = #type_uuid;
}
};
gen.into()
}
/// A struct containing the data required to generate an implementation of `TypeUuid`. This can be generated by either [`impl_type_uuid!`][crate::impl_type_uuid!] or [`type_uuid_derive`].
pub(crate) struct TypeUuidDef {
pub type_ident: Ident,
pub generics: Generics,
pub uuid: Uuid,
}
impl Parse for TypeUuidDef {
fn parse(input: ParseStream) -> Result<Self> {
let type_ident = input.parse::<Ident>()?;
let generics = input.parse::<Generics>()?;
input.parse::<Token![,]>()?;
let uuid = input.parse::<LitStr>()?.value();
let uuid = Uuid::parse_str(&uuid).map_err(|err| input.error(format!("{}", err)))?;
Ok(Self {
type_ident,
generics,
uuid,
})
}
}

View file

@ -13,6 +13,7 @@ mod tuple_struct;
mod type_info;
mod type_registry;
mod type_uuid;
mod type_uuid_impl;
mod impls {
#[cfg(feature = "glam")]
mod glam;

View file

@ -122,4 +122,36 @@ mod test {
assert_eq!(uuid_a, uuid_b);
}
#[test]
fn test_multiple_generic_uuid() {
#[derive(TypeUuid)]
#[uuid = "35c8a7d3-d4b3-4bd7-b847-1118dc78092f"]
struct TestGeneric<A, B> {
_value_a: A,
_value_b: B,
}
assert_ne!(
TestGeneric::<f32, bool>::TYPE_UUID,
TestGeneric::<bool, f32>::TYPE_UUID
);
}
#[test]
fn test_primitive_generic_uuid() {
test_impl_type_uuid(&true);
test_impl_type_uuid(&Some(true));
test_impl_type_uuid(&TestDeriveStruct::<bool> { _value: true });
assert_ne!(Option::<bool>::TYPE_UUID, Option::<f32>::TYPE_UUID);
assert_ne!(<[bool; 0]>::TYPE_UUID, <[bool; 1]>::TYPE_UUID);
assert_ne!(<[bool; 0]>::TYPE_UUID, <[f32; 0]>::TYPE_UUID);
assert_ne!(
<(bool, bool)>::TYPE_UUID,
<(bool, bool, bool, bool)>::TYPE_UUID
);
assert_ne!(<(bool, f32)>::TYPE_UUID, <(f32, bool)>::TYPE_UUID);
}
}

View file

@ -0,0 +1,80 @@
use crate::TypeUuid;
use crate::{self as bevy_reflect, __macro_exports::generate_composite_uuid};
use bevy_reflect_derive::impl_type_uuid;
use bevy_utils::{all_tuples, Duration, HashMap, HashSet, Instant, Uuid};
#[cfg(feature = "smallvec")]
use smallvec::SmallVec;
#[cfg(any(unix, windows))]
use std::ffi::OsString;
use std::{
num::{
NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroIsize, NonZeroU128,
NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize,
},
ops::{RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive},
path::PathBuf,
};
impl<T: TypeUuid, const N: usize> TypeUuid for [T; N] {
const TYPE_UUID: Uuid = generate_composite_uuid(
Uuid::from_u128(0x18d33c78e63c47b9bbf8f095008ab693),
generate_composite_uuid(Uuid::from_u128(N as u128), T::TYPE_UUID),
);
}
impl_type_uuid!(bool, "eb1ad0ee2dff473285bc54ebbdef682c");
impl_type_uuid!(char, "45a4710278ba48f8b31f0d72ff7f9d46");
impl_type_uuid!(u8, "fdf1a88a3e0543ca9f51ad5978ca519f");
impl_type_uuid!(u16, "ddeb93f791074860aaac1540de254edc");
impl_type_uuid!(u32, "fc565ea2367f405591e1c55f91cb60bd");
impl_type_uuid!(u64, "6c74b6a983eb44b096a9169baa6af0a1");
impl_type_uuid!(u128, "f837371a4f534b7381ed776d5056d0c1");
impl_type_uuid!(usize, "0129e1d8cff041f9b23aa99c6e1006b8");
impl_type_uuid!(i8, "af7a5411661e43b0b1631ea43a825fd2");
impl_type_uuid!(i16, "68592d5de5be4a608603c6988edfdf9c");
impl_type_uuid!(i32, "439ff07f96c94aa5a86352ded71e4730");
impl_type_uuid!(i64, "7f9534793ad24ab2b9f05d8254f4204a");
impl_type_uuid!(i128, "6e5009be5845460daf814e052cc9fcf0");
impl_type_uuid!(isize, "d3d52630da45497faf86859051c79e7d");
impl_type_uuid!(f32, "006607124a8148e1910c86f0c18c9015");
impl_type_uuid!(f64, "a5bc32f5632b478c92a0939b821fff80");
impl_type_uuid!(Result<T, E>, "d5960af2e8a743dfb7427dd59b70df95");
impl_type_uuid!(String, "c9f90d31b52d4bcd8b5c1d8b6fc1bcba");
impl_type_uuid!(PathBuf, "aa79933abd1743698583a3acad3b8989");
impl_type_uuid!(Vec<T>, "ab98f5408b974475b643662247fb3886");
impl_type_uuid!(HashMap<K, V>,"f37bfad9ca8c4f6ea7448f1c39e05f98");
impl_type_uuid!(Option<T>, "8d5ba9a9031347078955fba01ff439f0");
#[cfg(feature = "smallvec")]
impl_type_uuid!(
SmallVec<T: smallvec::Array>,
"26fd5c1bed7144fbb8d1546c02ba255a"
);
impl_type_uuid!(HashSet<K>, "5ebd2379ece44ef2b1478262962617a3");
impl_type_uuid!(RangeInclusive<T>, "79613b729ca9490881c7f47b24b22b60");
impl_type_uuid!(RangeFrom<T>, "1bd8c975f122486c9ed443e277964642");
impl_type_uuid!(RangeTo<T>, "7d938903749a4d198f496cb354929b9b");
impl_type_uuid!(RangeToInclusive<T>, "2fec56936206462fa5f35c99a62c5ed1");
impl_type_uuid!(RangeFull, "227af17f65db448782a2f6980ceae25d");
impl_type_uuid!(Duration, "cee5978c60f74a53b6848cb9c46a6e1c");
impl_type_uuid!(Instant, "9b0194a1d31c44c1afd2f6fd80ab8dfb");
impl_type_uuid!(NonZeroI128, "915a1e7fcaeb433982cebf58c2ac20e7");
impl_type_uuid!(NonZeroU128, "286de521146042cda31dfbef8f3f6cdc");
impl_type_uuid!(NonZeroIsize, "9318740a9fd14603b709b8fbc6fd2812");
impl_type_uuid!(NonZeroUsize, "a26533ed16324189878263d5e7a294ce");
impl_type_uuid!(NonZeroI64, "1aa38623127a42419cca4992e6fc3152");
impl_type_uuid!(NonZeroU64, "46be65e669a2477d942e2ec39d0d2af7");
impl_type_uuid!(NonZeroU32, "cf53a46d9efe4022967160cb61762c91");
impl_type_uuid!(NonZeroI32, "a69fbd659bef4322b88b15ff3263f530");
impl_type_uuid!(NonZeroI16, "8744c2ec8a10491fae40f8bafa58b30d");
impl_type_uuid!(NonZeroU16, "c7b8b60780a6495bab4fda2bdfedabcc");
impl_type_uuid!(NonZeroU8, "635ee104ef7947fb9d7f79dad47255a3");
impl_type_uuid!(NonZeroI8, "2d3f1570b7f64779826d44da5c7ba069");
#[cfg(any(unix, windows))]
impl_type_uuid!(OsString, "809e7b3c1ea240979ecd832f91eb842a");
macro_rules! impl_tuple {
( $($name: ident),* ) => {
const _: () = {
type Tuple< $($name),* > = ( $($name,)* );
impl_type_uuid!(Tuple< $($name),* > , "35c8a7d3d4b34bd7b8471118dc78092f");
};
};
}
all_tuples!(impl_tuple, 0, 12, A);

View file

@ -1,13 +1,12 @@
use crate::render_phase::{PhaseItem, TrackedRenderPass};
use bevy_app::App;
use bevy_ecs::{
all_tuples,
entity::Entity,
query::{QueryState, ROQueryItem, ReadOnlyWorldQuery},
system::{ReadOnlySystemParam, Resource, SystemParam, SystemParamItem, SystemState},
world::World,
};
use bevy_utils::HashMap;
use bevy_utils::{all_tuples, HashMap};
use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use std::{any::TypeId, fmt::Debug, hash::Hash};

View file

@ -17,6 +17,7 @@ tracing = { version = "0.1", default-features = false, features = ["std"] }
instant = { version = "0.1", features = ["wasm-bindgen"] }
uuid = { version = "1.1", features = ["v4", "serde"] }
hashbrown = { version = "0.12", features = ["serde"] }
bevy_utils_proc_macros = {version = "0.9.0", path = "macros"}
petgraph = "0.6"
thiserror = "1.0"

View file

@ -0,0 +1,14 @@
[package]
name = "bevy_utils_proc_macros"
version = "0.9.0"
description = "Bevy Utils Proc Macros"
edition = "2021"
license = "MIT OR Apache-2.0"
[lib]
proc-macro = true
[dependencies]
syn = "1.0"
quote = "1.0"
proc-macro2 = "1.0"

View file

@ -0,0 +1,71 @@
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{
parse::{Parse, ParseStream},
parse_macro_input,
token::Comma,
Ident, LitInt, Result,
};
struct AllTuples {
macro_ident: Ident,
start: usize,
end: usize,
idents: Vec<Ident>,
}
impl Parse for AllTuples {
fn parse(input: ParseStream) -> Result<Self> {
let macro_ident = input.parse::<Ident>()?;
input.parse::<Comma>()?;
let start = input.parse::<LitInt>()?.base10_parse()?;
input.parse::<Comma>()?;
let end = input.parse::<LitInt>()?.base10_parse()?;
input.parse::<Comma>()?;
let mut idents = vec![input.parse::<Ident>()?];
while input.parse::<Comma>().is_ok() {
idents.push(input.parse::<Ident>()?);
}
Ok(AllTuples {
macro_ident,
start,
end,
idents,
})
}
}
#[proc_macro]
pub fn all_tuples(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as AllTuples);
let len = input.end - input.start;
let mut ident_tuples = Vec::with_capacity(len);
for i in input.start..=input.end {
let idents = input
.idents
.iter()
.map(|ident| format_ident!("{}{}", ident, i));
if input.idents.len() < 2 {
ident_tuples.push(quote! {
#(#idents)*
});
} else {
ident_tuples.push(quote! {
(#(#idents),*)
});
}
}
let macro_ident = &input.macro_ident;
let invocations = (input.start..=input.end).map(|i| {
let ident_tuples = &ident_tuples[..i];
quote! {
#macro_ident!(#(#ident_tuples),*);
}
});
TokenStream::from(quote! {
#(
#invocations
)*
})
}

View file

@ -21,6 +21,7 @@ mod default;
mod float_ord;
pub use ahash::AHasher;
pub use bevy_utils_proc_macros::*;
pub use default::default;
pub use float_ord::*;
pub use hashbrown;