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
	 Jakob Hellermann
						Jakob Hellermann