Skip to content

Commit

Permalink
Merge pull request #1671 from radixdlt/feature/sbor-type-name-override
Browse files Browse the repository at this point in the history
feature: Add type_name override to Describe proc macro
  • Loading branch information
talekhinezh authored Jan 8, 2024
2 parents 930dcae + 7379878 commit 7acc215
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 73 deletions.
12 changes: 6 additions & 6 deletions radix-engine-derive/src/scrypto_describe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ mod tests {
quote! {
impl ::sbor::Describe<radix_engine_common::data::scrypto::ScryptoCustomTypeKind > for MyStruct {
const TYPE_ID: ::sbor::RustTypeId = ::sbor::RustTypeId::novel_with_code(
stringify!(MyStruct),
"MyStruct",
&[],
&#code_hash
);
fn type_data() -> ::sbor::TypeData<radix_engine_common::data::scrypto::ScryptoCustomTypeKind, ::sbor::RustTypeId> {
::sbor::TypeData::struct_with_named_fields(
stringify!(MyStruct),
"MyStruct",
::sbor::rust::vec![],
)
}
Expand All @@ -63,13 +63,13 @@ mod tests {
>
{
const TYPE_ID: ::sbor::RustTypeId = ::sbor::RustTypeId::novel_with_code(
stringify!(Thing),
"Thing",
&[<T>::TYPE_ID,],
&#code_hash
);
fn type_data() -> ::sbor::TypeData<radix_engine_common::data::scrypto::ScryptoCustomTypeKind, ::sbor::RustTypeId> {
::sbor::TypeData::struct_with_named_fields(
stringify!(Thing),
"Thing",
::sbor::rust::vec![
("field", <T as ::sbor::Describe<radix_engine_common::data::scrypto::ScryptoCustomTypeKind >>::TYPE_ID),
],
Expand Down Expand Up @@ -99,14 +99,14 @@ mod tests {
T: ::sbor::Describe<radix_engine_common::data::scrypto::ScryptoCustomTypeKind >
{
const TYPE_ID: ::sbor::RustTypeId = ::sbor::RustTypeId::novel_with_code(
stringify!(MyEnum),
"MyEnum",
&[<T>::TYPE_ID,],
&#code_hash
);
fn type_data() -> ::sbor::TypeData<radix_engine_common::data::scrypto::ScryptoCustomTypeKind, ::sbor::RustTypeId> {
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 ,] ,) ,
Expand Down
101 changes: 44 additions & 57 deletions sbor-derive-common/src/describe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand Down Expand Up @@ -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<B>), 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 { .. }) => {
Expand All @@ -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<B>), 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),)*
],
Expand All @@ -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<B>), 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,)*
],
Expand All @@ -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)
}
}
}
Expand Down Expand Up @@ -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,)*
],
Expand Down Expand Up @@ -330,14 +317,14 @@ mod tests {
quote! {
impl <C: ::sbor::CustomTypeKind<::sbor::RustTypeId> > ::sbor::Describe<C> for Test {
const TYPE_ID: ::sbor::RustTypeId = ::sbor::RustTypeId::novel_with_code(
stringify!(Test),
"Test",
&[],
&#code_hash
);

fn type_data() -> ::sbor::TypeData <C, ::sbor::RustTypeId> {
::sbor::TypeData::struct_with_named_fields(
stringify!(Test),
"Test",
::sbor::rust::vec![
("a", <u32 as ::sbor::Describe<C>>::TYPE_ID),
("b", <Vec<u8> as ::sbor::Describe<C>>::TYPE_ID),
Expand Down Expand Up @@ -372,7 +359,7 @@ mod tests {
for Test
{
const TYPE_ID: ::sbor::RustTypeId = ::sbor::RustTypeId::novel_with_code(
stringify!(Test),
"Test",
&[],
&#code_hash
);
Expand All @@ -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",
Expand Down Expand Up @@ -428,14 +415,14 @@ mod tests {
quote! {
impl <C: ::sbor::CustomTypeKind<::sbor::RustTypeId> > ::sbor::Describe<C> for Test {
const TYPE_ID: ::sbor::RustTypeId = ::sbor::RustTypeId::novel_with_code(
stringify!(Test),
"Test",
&[],
&#code_hash
);

fn type_data() -> ::sbor::TypeData <C, ::sbor::RustTypeId> {
::sbor::TypeData::struct_with_unnamed_fields(
stringify!(Test),
"Test",
::sbor::rust::vec![
<u32 as ::sbor::Describe<C>>::TYPE_ID,
<Vec<u8> as ::sbor::Describe<C>>::TYPE_ID,
Expand Down Expand Up @@ -464,13 +451,13 @@ mod tests {
quote! {
impl <C: ::sbor::CustomTypeKind<::sbor::RustTypeId> > ::sbor::Describe<C> for Test {
const TYPE_ID: ::sbor::RustTypeId = ::sbor::RustTypeId::novel_with_code(
stringify!(Test),
"Test",
&[],
&#code_hash
);

fn type_data() -> ::sbor::TypeData <C, ::sbor::RustTypeId> {
::sbor::TypeData::struct_with_unit_fields(stringify!(Test))
::sbor::TypeData::struct_with_unit_fields("Test")
}
}
},
Expand All @@ -493,15 +480,15 @@ mod tests {
T2: ::sbor::Describe<C>
{
const TYPE_ID: ::sbor::RustTypeId = ::sbor::RustTypeId::novel_with_code(
stringify!(Test),
"Test",
&[<T>::TYPE_ID, <T2>::TYPE_ID,],
&#code_hash
);

fn type_data() -> ::sbor::TypeData <C, ::sbor::RustTypeId> {
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(
Expand Down
19 changes: 9 additions & 10 deletions sbor-derive-common/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<usize, VariantValue> = BTreeMap::new();

for (i, variant) in variants.iter().enumerate() {
Expand Down Expand Up @@ -300,19 +300,18 @@ fn parse_u8_from_literal(literal: &Lit) -> Option<u8> {
}
}

fn get_sbor_attribute_string_value(
pub fn get_sbor_attribute_string_value(
attributes: &[Attribute],
field_name: &str,
attribute_name: &str,
) -> Result<Option<String>> {
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<bool> {
extract_sbor_typed_attributes(attributes)?.get_bool_value(&field_name)
}

pub fn get_sbor_bool_value(attributes: &[Attribute], attribute_name: &str) -> Result<bool> {
extract_sbor_typed_attributes(&attributes)?.get_bool_value(attribute_name)
pub fn get_sbor_attribute_bool_value(
attributes: &[Attribute],
attribute_name: &str,
) -> Result<bool> {
extract_sbor_typed_attributes(attributes)?.get_bool_value(attribute_name)
}

pub fn is_categorize_skipped(f: &Field) -> Result<bool> {
Expand Down
17 changes: 17 additions & 0 deletions sbor-tests/tests/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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: (),
Expand Down Expand Up @@ -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::<UnitStructRenamed, NoCustomSchema>();

// 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());
}
7 changes: 7 additions & 0 deletions sbor-tests/tests/transparent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -134,6 +140,7 @@ fn decode_is_correct() {
fn describe_is_correct() {
// With inner u32
check_identical_types::<TestStructNamed, u32>("TestStructNamed");
check_identical_types::<TestStructRenamed, u32>("TestStructRenamed2");
check_identical_types::<TestStructUnnamed, u32>("TestStructUnnamed");
check_identical_types::<TestStruct<u32>, u32>("TestStruct");

Expand Down

0 comments on commit 7acc215

Please sign in to comment.