From 3d36ec41dcbfe7d5ead2a9dc5777c933f7f37e09 Mon Sep 17 00:00:00 2001 From: Jakob Hellermann Date: Tue, 26 Apr 2022 19:41:25 +0000 Subject: [PATCH] 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` and `MyStruct` 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); impl TypeUuid for Type { 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 Co-authored-by: Jakob Hellermann --- .../bevy_reflect_derive/src/type_uuid.rs | 30 +++--- crates/bevy_reflect/src/lib.rs | 30 ++++++ crates/bevy_reflect/src/type_uuid.rs | 95 +++++++++++++++++++ 3 files changed, 144 insertions(+), 11 deletions(-) diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/type_uuid.rs b/crates/bevy_reflect/bevy_reflect_derive/src/type_uuid.rs index 77c95b4a3debd..382036a9f3713 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/type_uuid.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/type_uuid.rs @@ -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::(&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() diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index e935c042a53e1..52f231cd27b47 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -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> and B> 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 { diff --git a/crates/bevy_reflect/src/type_uuid.rs b/crates/bevy_reflect/src/type_uuid.rs index 545ab97bb594b..5cfd46ab6a771 100644 --- a/crates/bevy_reflect/src/type_uuid.rs +++ b/crates/bevy_reflect/src/type_uuid.rs @@ -28,3 +28,98 @@ where std::any::type_name::() } } + +#[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 + 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::::TYPE_UUID; + let uuid_b = TestDeriveStruct::::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 + where + T: Clone, + { + _phantom: PhantomData, + } + + #[derive(TypeUuid, Clone)] + #[uuid = "a82f9936-70cb-482a-bd3d-cb99d87de55f"] + struct TestDeriveStructB + where + T: Clone, + { + _phantom: PhantomData, + } + + let uuid_ab = TestDeriveStructA::>::TYPE_UUID; + let uuid_ba = TestDeriveStructB::>::TYPE_UUID; + + assert_ne!(uuid_ab, uuid_ba); + assert_ne!(uuid_ab, TestDeriveStructA::::TYPE_UUID); + assert_ne!(uuid_ba, TestDeriveStructB::::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::::TYPE_UUID; + let uuid_b = TestDeriveStruct::::TYPE_UUID; + + assert_eq!(uuid_a, uuid_b); + } +}