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; | extern crate proc_macro; | ||||||
| 
 | 
 | ||||||
| use bevy_macro_utils::BevyManifest; | use bevy_macro_utils::BevyManifest; | ||||||
| use quote::{quote, ToTokens}; | use quote::quote; | ||||||
| use syn::*; | use syn::*; | ||||||
| use uuid::Uuid; | use uuid::Uuid; | ||||||
| 
 | 
 | ||||||
| pub fn type_uuid_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { | pub fn type_uuid_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { | ||||||
|     // Construct a representation of Rust code as a syntax tree
 |     // Construct a representation of Rust code as a syntax tree
 | ||||||
|     // that we can manipulate
 |     // 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"); |     let bevy_reflect_path: Path = BevyManifest::default().get_path("bevy_reflect"); | ||||||
| 
 | 
 | ||||||
|     // Build the trait implementation
 |     // Build the trait implementation
 | ||||||
|     let name = &ast.ident; |     let name = &ast.ident; | ||||||
| 
 | 
 | ||||||
|     let (impl_generics, type_generics, _) = &ast.generics.split_for_impl(); |     ast.generics.type_params_mut().for_each(|param| { | ||||||
|     assert!( |         param | ||||||
|         impl_generics.to_token_stream().is_empty() && type_generics.to_token_stream().is_empty(), |             .bounds | ||||||
|         "#[derive(TypeUuid)] is not supported for generics.", |             .push(syn::parse_quote!(#bevy_reflect_path::TypeUuid)) | ||||||
|     ); |     }); | ||||||
|  | 
 | ||||||
|  |     let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); | ||||||
| 
 | 
 | ||||||
|     let mut uuid = None; |     let mut uuid = None; | ||||||
|     for attribute in ast.attrs.iter().filter_map(|attr| attr.parse_meta().ok()) { |     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| format!("{:#X}", byte)) | ||||||
|         .map(|byte_str| syn::parse_str::<LitInt>(&byte_str).unwrap()); |         .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! { |     let gen = quote! { | ||||||
|         impl #bevy_reflect_path::TypeUuid for #name { |         impl #impl_generics #bevy_reflect_path::TypeUuid for #name #type_generics #where_clause { | ||||||
|             const TYPE_UUID: #bevy_reflect_path::Uuid = #bevy_reflect_path::Uuid::from_bytes([ |             const TYPE_UUID: #bevy_reflect_path::Uuid = #type_uuid; | ||||||
|                 #( #bytes ),* |  | ||||||
|             ]); |  | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
|     gen.into() |     gen.into() | ||||||
|  | |||||||
| @ -46,6 +46,36 @@ pub use type_uuid::*; | |||||||
| pub use bevy_reflect_derive::*; | pub use bevy_reflect_derive::*; | ||||||
| pub use erased_serde; | 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)] | #[cfg(test)] | ||||||
| #[allow(clippy::blacklisted_name, clippy::approx_constant)] | #[allow(clippy::blacklisted_name, clippy::approx_constant)] | ||||||
| mod tests { | mod tests { | ||||||
|  | |||||||
| @ -28,3 +28,98 @@ where | |||||||
|         std::any::type_name::<Self>() |         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
	 Jakob Hellermann
						Jakob Hellermann