diff --git a/radix-engine-derive/src/scrypto_describe.rs b/radix-engine-derive/src/scrypto_describe.rs index 5a2a697ef0d..cfa953770cf 100644 --- a/radix-engine-derive/src/scrypto_describe.rs +++ b/radix-engine-derive/src/scrypto_describe.rs @@ -31,13 +31,13 @@ mod tests { quote! { impl ::sbor::Describe for MyStruct { const TYPE_ID: ::sbor::RustTypeId = ::sbor::RustTypeId::novel_with_code( - stringify!(MyStruct), + "MyStruct", &[], &#code_hash ); fn type_data() -> ::sbor::TypeData { ::sbor::TypeData::struct_with_named_fields( - stringify!(MyStruct), + "MyStruct", ::sbor::rust::vec![], ) } @@ -63,13 +63,13 @@ mod tests { > { const TYPE_ID: ::sbor::RustTypeId = ::sbor::RustTypeId::novel_with_code( - stringify!(Thing), + "Thing", &[::TYPE_ID,], &#code_hash ); fn type_data() -> ::sbor::TypeData { ::sbor::TypeData::struct_with_named_fields( - stringify!(Thing), + "Thing", ::sbor::rust::vec![ ("field", >::TYPE_ID), ], @@ -99,14 +99,14 @@ mod tests { T: ::sbor::Describe { const TYPE_ID: ::sbor::RustTypeId = ::sbor::RustTypeId::novel_with_code( - stringify!(MyEnum), + "MyEnum", &[::TYPE_ID,], &#code_hash ); fn type_data() -> ::sbor::TypeData { use ::sbor::rust::borrow::ToOwned; ::sbor::TypeData::enum_variants( - stringify!(MyEnum), + "MyEnum", :: sbor :: rust :: prelude :: indexmap ! [ 0u8 => :: sbor :: TypeData :: struct_with_named_fields ("A", :: sbor :: rust :: vec ! [("named", < T as :: sbor :: Describe < radix_engine_common::data::scrypto::ScryptoCustomTypeKind >> :: TYPE_ID) ,] ,) , 1u8 => :: sbor :: TypeData :: struct_with_unnamed_fields ("B", :: sbor :: rust :: vec ! [< String as :: sbor :: Describe < radix_engine_common::data::scrypto::ScryptoCustomTypeKind >> :: TYPE_ID ,] ,) , diff --git a/sbor-derive-common/src/describe.rs b/sbor-derive-common/src/describe.rs index e55f3a4f3ea..cbdf3aa8aa0 100644 --- a/sbor-derive-common/src/describe.rs +++ b/sbor-derive-common/src/describe.rs @@ -71,15 +71,17 @@ fn handle_transparent_describe( }; // Replace the type name, unless opted out using the "transparent_name" tag - if !get_sbor_bool_value(&attrs, "transparent_name")? { + if !get_sbor_attribute_bool_value(&attrs, "transparent_name")? { + let type_name = get_sbor_attribute_string_value(&attrs, "type_name")? + .unwrap_or(ident.to_string()); type_data_content = quote! { use ::sbor::rust::prelude::*; #type_data_content - .with_name(Some(Cow::Borrowed(stringify!(#ident)))) + .with_name(Some(Cow::Borrowed(#type_name))) }; type_id = quote! { ::sbor::RustTypeId::novel_with_code( - stringify!(#ident), + #type_name, &[#type_id], &#code_hash ) @@ -126,6 +128,27 @@ fn handle_normal_describe( let (impl_generics, ty_generics, where_clause, child_types, custom_type_kind_generic) = build_describe_generics(&generics, &attrs, context_custom_type_kind)?; + let type_name = + get_sbor_attribute_string_value(&attrs, "type_name")?.unwrap_or(ident.to_string()); + + let type_id = quote! { + ::sbor::RustTypeId::novel_with_code( + #type_name, + // Here we really want to cause distinct types to have distinct hashes, whilst still supporting (most) recursive types. + // The code hash itself is pretty good for this, but if you allow generic types, it's not enough, as the same code can create + // different types depending on the generic types providing. Adding in the generic types' TYPE_IDs solves that issue. + // + // It's still technically possible to get a collision (by abusing type namespacing to have two types with identical code + // reference other types) but it's good enough - you're only shooting yourself in the food at that point. + // + // Note that it might seem possible to still hit issues with infinite recursion, if you pass a type as its own generic type parameter. + // EG (via a type alias B = A), but these types won't come up in practice because they require an infinite generic depth + // which the compiler will throw out for other reasons. + &[#(<#child_types>::TYPE_ID,)*], + &#code_hash + ) + }; + let output = match data { Data::Struct(s) => match &s.fields { syn::Fields::Named(FieldsNamed { .. }) => { @@ -137,25 +160,11 @@ fn handle_normal_describe( let unique_field_types: Vec<_> = get_unique_types(&unskipped_field_types); quote! { impl #impl_generics ::sbor::Describe <#custom_type_kind_generic> for #ident #ty_generics #where_clause { - const TYPE_ID: ::sbor::RustTypeId = ::sbor::RustTypeId::novel_with_code( - stringify!(#ident), - // Here we really want to cause distinct types to have distinct hashes, whilst still supporting (most) recursive types. - // The code hash itself is pretty good for this, but if you allow generic types, it's not enough, as the same code can create - // different types depending on the generic types providing. Adding in the generic types' TYPE_IDs solves that issue. - // - // It's still technically possible to get a collision (by abusing type namespacing to have two types with identical code - // reference other types) but it's good enough - you're only shooting yourself in the food at that point. - // - // Note that it might seem possible to still hit issues with infinite recursion, if you pass a type as its own generic type parameter. - // EG (via a type alias B = A), but these types won't come up in practice because they require an infinite generic depth - // which the compiler will throw out for other reasons. - &[#(<#child_types>::TYPE_ID,)*], - &#code_hash - ); + const TYPE_ID: ::sbor::RustTypeId = #type_id; fn type_data() -> ::sbor::TypeData<#custom_type_kind_generic, ::sbor::RustTypeId> { ::sbor::TypeData::struct_with_named_fields( - stringify!(#ident), + #type_name, ::sbor::rust::vec![ #((#unskipped_field_name_strings, <#unskipped_field_types as ::sbor::Describe<#custom_type_kind_generic>>::TYPE_ID),)* ], @@ -177,25 +186,11 @@ fn handle_normal_describe( quote! { impl #impl_generics ::sbor::Describe <#custom_type_kind_generic> for #ident #ty_generics #where_clause { - const TYPE_ID: ::sbor::RustTypeId = ::sbor::RustTypeId::novel_with_code( - stringify!(#ident), - // Here we really want to cause distinct types to have distinct hashes, whilst still supporting (most) recursive types. - // The code hash itself is pretty good for this, but if you allow generic types, it's not enough, as the same code can create - // different types depending on the generic types providing. Adding in the generic types' TYPE_IDs solves that issue. - // - // It's still technically possible to get a collision (by abusing type namespacing to have two types with identical code - // reference other types) but it's good enough - you're only shooting yourself in the food at that point. - // - // Note that it might seem possible to still hit issues with infinite recursion, if you pass a type as its own generic type parameter. - // EG (via a type alias B = A), but these types won't come up in practice because they require an infinite generic depth - // which the compiler will throw out for other reasons. - &[#(<#child_types>::TYPE_ID,)*], - &#code_hash - ); + const TYPE_ID: ::sbor::RustTypeId = #type_id; fn type_data() -> ::sbor::TypeData<#custom_type_kind_generic, ::sbor::RustTypeId> { ::sbor::TypeData::struct_with_unnamed_fields( - stringify!(#ident), + #type_name, ::sbor::rust::vec![ #(<#unskipped_field_types as ::sbor::Describe<#custom_type_kind_generic>>::TYPE_ID,)* ], @@ -211,14 +206,10 @@ fn handle_normal_describe( syn::Fields::Unit => { quote! { impl #impl_generics ::sbor::Describe <#custom_type_kind_generic> for #ident #ty_generics #where_clause { - const TYPE_ID: ::sbor::RustTypeId = ::sbor::RustTypeId::novel_with_code( - stringify!(#ident), - &[#(<#child_types>::TYPE_ID,)*], - &#code_hash - ); + const TYPE_ID: ::sbor::RustTypeId = #type_id; fn type_data() -> ::sbor::TypeData<#custom_type_kind_generic, ::sbor::RustTypeId> { - ::sbor::TypeData::struct_with_unit_fields(stringify!(#ident)) + ::sbor::TypeData::struct_with_unit_fields(#type_name) } } } @@ -278,16 +269,12 @@ fn handle_normal_describe( quote! { impl #impl_generics ::sbor::Describe <#custom_type_kind_generic> for #ident #ty_generics #where_clause { - const TYPE_ID: ::sbor::RustTypeId = ::sbor::RustTypeId::novel_with_code( - stringify!(#ident), - &[#(<#child_types>::TYPE_ID,)*], - &#code_hash - ); + const TYPE_ID: ::sbor::RustTypeId = #type_id; fn type_data() -> ::sbor::TypeData<#custom_type_kind_generic, ::sbor::RustTypeId> { use ::sbor::rust::borrow::ToOwned; ::sbor::TypeData::enum_variants( - stringify!(#ident), + #type_name, ::sbor::rust::prelude::indexmap![ #(#variant_discriminators => #variant_type_data,)* ], @@ -330,14 +317,14 @@ mod tests { quote! { impl > ::sbor::Describe for Test { const TYPE_ID: ::sbor::RustTypeId = ::sbor::RustTypeId::novel_with_code( - stringify!(Test), + "Test", &[], &#code_hash ); fn type_data() -> ::sbor::TypeData { ::sbor::TypeData::struct_with_named_fields( - stringify!(Test), + "Test", ::sbor::rust::vec![ ("a", >::TYPE_ID), ("b", as ::sbor::Describe>::TYPE_ID), @@ -372,7 +359,7 @@ mod tests { for Test { const TYPE_ID: ::sbor::RustTypeId = ::sbor::RustTypeId::novel_with_code( - stringify!(Test), + "Test", &[], &#code_hash ); @@ -381,7 +368,7 @@ mod tests { radix_engine_interface::data::ScryptoCustomTypeKind<::sbor::RustTypeId>, ::sbor::RustTypeId> { ::sbor::TypeData::struct_with_named_fields( - stringify!(Test), + "Test", ::sbor::rust::vec![ ( "a", @@ -428,14 +415,14 @@ mod tests { quote! { impl > ::sbor::Describe for Test { const TYPE_ID: ::sbor::RustTypeId = ::sbor::RustTypeId::novel_with_code( - stringify!(Test), + "Test", &[], &#code_hash ); fn type_data() -> ::sbor::TypeData { ::sbor::TypeData::struct_with_unnamed_fields( - stringify!(Test), + "Test", ::sbor::rust::vec![ >::TYPE_ID, as ::sbor::Describe>::TYPE_ID, @@ -464,13 +451,13 @@ mod tests { quote! { impl > ::sbor::Describe for Test { const TYPE_ID: ::sbor::RustTypeId = ::sbor::RustTypeId::novel_with_code( - stringify!(Test), + "Test", &[], &#code_hash ); fn type_data() -> ::sbor::TypeData { - ::sbor::TypeData::struct_with_unit_fields(stringify!(Test)) + ::sbor::TypeData::struct_with_unit_fields("Test") } } }, @@ -493,7 +480,7 @@ mod tests { T2: ::sbor::Describe { const TYPE_ID: ::sbor::RustTypeId = ::sbor::RustTypeId::novel_with_code( - stringify!(Test), + "Test", &[::TYPE_ID, ::TYPE_ID,], &#code_hash ); @@ -501,7 +488,7 @@ mod tests { fn type_data() -> ::sbor::TypeData { use ::sbor::rust::borrow::ToOwned; ::sbor::TypeData::enum_variants( - stringify!(Test), + "Test", ::sbor::rust::prelude::indexmap![ 0u8 => ::sbor::TypeData::struct_with_unit_fields("A"), 1u8 => ::sbor::TypeData::struct_with_unnamed_fields( diff --git a/sbor-derive-common/src/utils.rs b/sbor-derive-common/src/utils.rs index b4eccf2a3ca..4247d888e59 100644 --- a/sbor-derive-common/src/utils.rs +++ b/sbor-derive-common/src/utils.rs @@ -212,7 +212,7 @@ pub fn get_variant_discriminator_mapping( } let use_repr_discriminators = - get_sbor_attribute_boolean_value(enum_attributes, "use_repr_discriminators")?; + get_sbor_attribute_bool_value(enum_attributes, "use_repr_discriminators")?; let mut variant_ids: BTreeMap = BTreeMap::new(); for (i, variant) in variants.iter().enumerate() { @@ -300,19 +300,18 @@ fn parse_u8_from_literal(literal: &Lit) -> Option { } } -fn get_sbor_attribute_string_value( +pub fn get_sbor_attribute_string_value( attributes: &[Attribute], - field_name: &str, + attribute_name: &str, ) -> Result> { - extract_sbor_typed_attributes(attributes)?.get_string_value(&field_name) + extract_sbor_typed_attributes(attributes)?.get_string_value(attribute_name) } -fn get_sbor_attribute_boolean_value(attributes: &[Attribute], field_name: &str) -> Result { - extract_sbor_typed_attributes(attributes)?.get_bool_value(&field_name) -} - -pub fn get_sbor_bool_value(attributes: &[Attribute], attribute_name: &str) -> Result { - extract_sbor_typed_attributes(&attributes)?.get_bool_value(attribute_name) +pub fn get_sbor_attribute_bool_value( + attributes: &[Attribute], + attribute_name: &str, +) -> Result { + extract_sbor_typed_attributes(attributes)?.get_bool_value(attribute_name) } pub fn is_categorize_skipped(f: &Field) -> Result { diff --git a/sbor-tests/tests/schema.rs b/sbor-tests/tests/schema.rs index ebe1325db06..ce232062aba 100644 --- a/sbor-tests/tests/schema.rs +++ b/sbor-tests/tests/schema.rs @@ -10,6 +10,10 @@ use sbor::*; #[derive(Sbor)] pub struct UnitStruct; +#[derive(Sbor)] +#[sbor(type_name = "UnitStructRenamed2")] +pub struct UnitStructRenamed; + #[derive(Sbor)] pub struct BasicSample { pub a: (), @@ -229,3 +233,16 @@ fn create_recursive_schema_works_correctly() { assert_eq!(metadata.get_name().unwrap(), "IndirectRecursive1"); assert!(schema.v1().validate().is_ok()); } + +#[test] +fn test_type_name_works_correctly() { + // Most of this test is checking that such recursive schemas can: (A) happily compile and (B) don't panic when a schema is generated + let (type_id, schema) = + generate_full_schema_from_single_type::(); + + // The original type should be the first type in the schema + assert!(matches!(type_id, LocalTypeId::SchemaLocalIndex(0))); + let metadata = schema.v1().resolve_type_metadata(type_id).unwrap(); + assert_eq!(metadata.get_name().unwrap(), "UnitStructRenamed2"); + assert!(schema.v1().validate().is_ok()); +} diff --git a/sbor-tests/tests/transparent.rs b/sbor-tests/tests/transparent.rs index 39cc5dfd6fc..d1544161532 100644 --- a/sbor-tests/tests/transparent.rs +++ b/sbor-tests/tests/transparent.rs @@ -9,6 +9,12 @@ pub struct TestStructNamed { pub state: u32, } +#[derive(Sbor, PartialEq, Eq, Debug)] +#[sbor(transparent, type_name = "TestStructRenamed2")] +pub struct TestStructRenamed { + pub state: u32, +} + #[derive(Sbor, PartialEq, Eq, Debug)] #[sbor(transparent)] pub struct TestStructUnnamed(u32); @@ -134,6 +140,7 @@ fn decode_is_correct() { fn describe_is_correct() { // With inner u32 check_identical_types::("TestStructNamed"); + check_identical_types::("TestStructRenamed2"); check_identical_types::("TestStructUnnamed"); check_identical_types::, u32>("TestStruct");