re-enable #[derive(TypeUuid)] for generics (#4118)
Support for deriving `TypeUuid` for types with generics was initially added in https://github.com/bevyengine/bevy/pull/2044 but later reverted https://github.com/bevyengine/bevy/pull/2204 because it lead to `MyStruct<A>` and `MyStruct<B>` having the same type uuid. This PR fixes this by generating code like ```rust #[derive(TypeUuid)] #[uuid = "69b09733-a21a-4dab-a444-d472986bd672"] struct Type<T>(T); impl<T: TypeUuid> TypeUuid for Type<T> { const TYPE_UUID: TypeUuid = generate_compound_uuid(Uuid::from_bytes([/* 69b0 uuid */]), T::TYPE_UUID); } ``` where `generate_compound_uuid` will XOR the non-metadata bits of the two UUIDs. Co-authored-by: XBagon <xbagon@outlook.de> Co-authored-by: Jakob Hellermann <hellermann@sipgate.de>
This commit is contained in:
parent
d5e770dfcb
commit
3d36ec41dc
@ -1,24 +1,26 @@
|
||||
extern crate proc_macro;
|
||||
|
||||
use bevy_macro_utils::BevyManifest;
|
||||
use quote::{quote, ToTokens};
|
||||
use quote::quote;
|
||||
use syn::*;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub 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 ast: DeriveInput = syn::parse(input).unwrap();
|
||||
let mut ast: DeriveInput = syn::parse(input).unwrap();
|
||||
let bevy_reflect_path: Path = BevyManifest::default().get_path("bevy_reflect");
|
||||
|
||||
// Build the trait implementation
|
||||
let name = &ast.ident;
|
||||
|
||||
let (impl_generics, type_generics, _) = &ast.generics.split_for_impl();
|
||||
assert!(
|
||||
impl_generics.to_token_stream().is_empty() && type_generics.to_token_stream().is_empty(),
|
||||
"#[derive(TypeUuid)] is not supported for generics.",
|
||||
);
|
||||
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 mut uuid = None;
|
||||
for attribute in ast.attrs.iter().filter_map(|attr| attr.parse_meta().ok()) {
|
||||
@ -56,11 +58,17 @@ pub fn type_uuid_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStre
|
||||
.map(|byte| format!("{:#X}", byte))
|
||||
.map(|byte_str| syn::parse_str::<LitInt>(&byte_str).unwrap());
|
||||
|
||||
let base = quote! { #bevy_reflect_path::Uuid::from_bytes([#( #bytes ),*]) };
|
||||
let type_uuid = ast.generics.type_params().fold(base, |acc, param| {
|
||||
let ident = ¶m.ident;
|
||||
quote! {
|
||||
#bevy_reflect_path::__macro_exports::generate_composite_uuid(#acc, <#ident as #bevy_reflect_path::TypeUuid>::TYPE_UUID)
|
||||
}
|
||||
});
|
||||
|
||||
let gen = quote! {
|
||||
impl #bevy_reflect_path::TypeUuid for #name {
|
||||
const TYPE_UUID: #bevy_reflect_path::Uuid = #bevy_reflect_path::Uuid::from_bytes([
|
||||
#( #bytes ),*
|
||||
]);
|
||||
impl #impl_generics #bevy_reflect_path::TypeUuid for #name #type_generics #where_clause {
|
||||
const TYPE_UUID: #bevy_reflect_path::Uuid = #type_uuid;
|
||||
}
|
||||
};
|
||||
gen.into()
|
||||
|
||||
@ -46,6 +46,36 @@ pub use type_uuid::*;
|
||||
pub use bevy_reflect_derive::*;
|
||||
pub use erased_serde;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub mod __macro_exports {
|
||||
use crate::Uuid;
|
||||
|
||||
/// Generates a new UUID from the given UUIDs `a` and `b`,
|
||||
/// where the bytes are generated by a bitwise `a ^ b.rotate_right(1)`.
|
||||
/// The generated UUID will be a `UUIDv4` (meaning that the bytes should be random, not e.g. derived from the system time).
|
||||
#[allow(clippy::unusual_byte_groupings)] // unusual byte grouping is meant to signal the relevant bits
|
||||
pub const fn generate_composite_uuid(a: Uuid, b: Uuid) -> Uuid {
|
||||
let mut new = [0; 16];
|
||||
let mut i = 0;
|
||||
while i < new.len() {
|
||||
// rotating ensures different uuids for A<B<C>> and B<A<C>> because: A ^ (B ^ C) = B ^ (A ^ C)
|
||||
// notice that you have to rotate the second parameter: A.rr ^ (B.rr ^ C) = B.rr ^ (A.rr ^ C)
|
||||
// Solution: A ^ (B ^ C.rr).rr != B ^ (A ^ C.rr).rr
|
||||
new[i] = a.as_bytes()[i] ^ b.as_bytes()[i].rotate_right(1);
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
// Version: the most significant 4 bits in the 6th byte: 11110000
|
||||
new[6] = new[6] & 0b0000_1111 | 0b0100_0000; // set version to v4
|
||||
|
||||
// Variant: the most significant 3 bits in the 8th byte: 11100000
|
||||
new[8] = new[8] & 0b000_11111 | 0b100_00000; // set variant to rfc4122
|
||||
|
||||
Uuid::from_bytes(new)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::blacklisted_name, clippy::approx_constant)]
|
||||
mod tests {
|
||||
|
||||
@ -28,3 +28,98 @@ where
|
||||
std::any::type_name::<Self>()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate as bevy_reflect;
|
||||
use bevy_reflect_derive::TypeUuid;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
#[derive(TypeUuid)]
|
||||
#[uuid = "af6466c2-a9f4-11eb-bcbc-0242ac130002"]
|
||||
struct TestDeriveStruct<T>
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
_value: T,
|
||||
}
|
||||
|
||||
fn test_impl_type_uuid(_: &impl TypeUuid) {}
|
||||
|
||||
#[test]
|
||||
fn test_generic_type_uuid_derive() {
|
||||
#[derive(TypeUuid, Clone)]
|
||||
#[uuid = "ebb16cc9-4d5a-453c-aa8c-c72bd8ec83a2"]
|
||||
struct T;
|
||||
|
||||
let test_struct = TestDeriveStruct { _value: T };
|
||||
test_impl_type_uuid(&test_struct);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generic_type_unique_uuid() {
|
||||
#[derive(TypeUuid, Clone)]
|
||||
#[uuid = "49951b1c-4811-45e7-acc6-3119249fbd8f"]
|
||||
struct A;
|
||||
|
||||
#[derive(TypeUuid, Clone)]
|
||||
#[uuid = "4882b8f5-5556-4cee-bea6-a2e5991997b7"]
|
||||
struct B;
|
||||
|
||||
let uuid_a = TestDeriveStruct::<A>::TYPE_UUID;
|
||||
let uuid_b = TestDeriveStruct::<B>::TYPE_UUID;
|
||||
|
||||
assert_ne!(uuid_a, uuid_b);
|
||||
assert_ne!(uuid_a, A::TYPE_UUID);
|
||||
assert_ne!(uuid_b, B::TYPE_UUID);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_inverted_generic_type_unique_uuid() {
|
||||
#[derive(TypeUuid, Clone)]
|
||||
#[uuid = "49951b1c-4811-45e7-acc6-3119249fbd8f"]
|
||||
struct Inner;
|
||||
|
||||
#[derive(TypeUuid, Clone)]
|
||||
#[uuid = "23ebc0c3-ef69-4ea0-8c2a-dca1b4e27c0d"]
|
||||
struct TestDeriveStructA<T>
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
#[derive(TypeUuid, Clone)]
|
||||
#[uuid = "a82f9936-70cb-482a-bd3d-cb99d87de55f"]
|
||||
struct TestDeriveStructB<T>
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
let uuid_ab = TestDeriveStructA::<TestDeriveStructB<Inner>>::TYPE_UUID;
|
||||
let uuid_ba = TestDeriveStructB::<TestDeriveStructA<Inner>>::TYPE_UUID;
|
||||
|
||||
assert_ne!(uuid_ab, uuid_ba);
|
||||
assert_ne!(uuid_ab, TestDeriveStructA::<Inner>::TYPE_UUID);
|
||||
assert_ne!(uuid_ba, TestDeriveStructB::<Inner>::TYPE_UUID);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generic_type_uuid_same_for_eq_param() {
|
||||
#[derive(TypeUuid, Clone)]
|
||||
#[uuid = "49951b1c-4811-45e7-acc6-3119249fbd8f"]
|
||||
struct A;
|
||||
|
||||
#[derive(TypeUuid, Clone)]
|
||||
#[uuid = "49951b1c-4811-45e7-acc6-3119249fbd8f"]
|
||||
struct BButSameAsA;
|
||||
|
||||
let uuid_a = TestDeriveStruct::<A>::TYPE_UUID;
|
||||
let uuid_b = TestDeriveStruct::<BButSameAsA>::TYPE_UUID;
|
||||
|
||||
assert_eq!(uuid_a, uuid_b);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user