diff --git a/README.md b/README.md index bc2a0f46..2eaf0c28 100644 --- a/README.md +++ b/README.md @@ -54,8 +54,7 @@ pub enum TypeDef { The following "built-in" types have predefined `TypeInfo` definitions: -- **Primitives:** `bool`, `char`, `str`, `u8`, `u16`, `u32`, `u64`, `u128`, `i8`, `i16`, `i32`, `i64 -`, `i128`. +- **Primitives:** `bool`, `char`, `str`, `u8`, `u16`, `u32`, `u64`, `u128`, `i8`, `i16`, `i32`, `i64`, `i128`. - **Sequence:** Variable size sequence of elements of `T`, where `T` implements `TypeInfo`. e.g. `[T]`, `&[T]`, `&mut [T]`, `Vec` @@ -234,7 +233,7 @@ struct Foo { #[codec(compact)] a: S } You may experience the following error when using this generic type without the correct bounds: -``` +```sh error[E0275]: overflow evaluating the requirement `_::_parity_scale_codec::Compact<_>: Decode` ``` diff --git a/derive/src/lib.rs b/derive/src/lib.rs index 71ab562f..99af3f5b 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -251,7 +251,7 @@ fn is_c_like_enum(variants: &VariantList) -> bool { fn generate_variant_type(data_enum: &DataEnum, scale_info: &Ident) -> TokenStream2 { let variants = &data_enum.variants; - if is_c_like_enum(&variants) { + if is_c_like_enum(variants) { return generate_c_like_enum_def(variants, scale_info) } @@ -262,6 +262,7 @@ fn generate_variant_type(data_enum: &DataEnum, scale_info: &Ident) -> TokenStrea let ident = &v.ident; let v_name = quote! {::core::stringify!(#ident) }; let docs = utils::get_doc_literals(&v.attrs); + let index = utils::maybe_index(v).map(|i| quote!(.index(#i))); let fields = match v.fields { Fields::Named(ref fs) => { @@ -287,7 +288,10 @@ fn generate_variant_type(data_enum: &DataEnum, scale_info: &Ident) -> TokenStrea quote! { .variant(#v_name, |v| - v.fields(#fields).docs(&[ #( #docs ),* ]) + v + .fields(#fields) + .docs(&[ #( #docs ),* ]) + #index ) } }); diff --git a/derive/src/trait_bounds.rs b/derive/src/trait_bounds.rs index 65da4257..41e5e6cb 100644 --- a/derive/src/trait_bounds.rs +++ b/derive/src/trait_bounds.rs @@ -160,11 +160,11 @@ fn collect_types_to_bind( .iter() .filter(|field| { // Only add a bound if the type uses a generic. - type_contains_idents(&field.ty, &ty_params) + type_contains_idents(&field.ty, ty_params) && // Remove all remaining types that start/contain the input ident // to not have them in the where clause. - !type_or_sub_type_path_starts_with_ident(&field.ty, &input_ident) + !type_or_sub_type_path_starts_with_ident(&field.ty, input_ident) }) .map(|f| (f.ty.clone(), utils::is_compact(f))) .collect() diff --git a/derive/src/utils.rs b/derive/src/utils.rs index 47d3d404..32c0c2f4 100644 --- a/derive/src/utils.rs +++ b/derive/src/utils.rs @@ -58,8 +58,26 @@ pub fn get_doc_literals(attrs: &[syn::Attribute]) -> Vec { /// Look for a `#[codec(index = $int)]` attribute on a variant. If no attribute /// is found, fall back to the discriminant or just the variant index. pub fn variant_index(v: &Variant, i: usize) -> TokenStream { - // first look for an attribute - let index = find_meta_item(v.attrs.iter(), |meta| { + // first look for an `index` attribute… + let index = maybe_index(v); + // …then fallback to discriminant or just index + index.map(|i| quote! { #i }).unwrap_or_else(|| { + v.discriminant + .as_ref() + .map(|&(_, ref expr)| quote! { #expr }) + .unwrap_or_else(|| quote! { #i }) + }) +} + +/// Look for a `#[codec(index = $int)]` outer attribute on a variant. +/// If found, it is expected to be a parseable as a `u8` (panics otherwise). +pub fn maybe_index(variant: &Variant) -> Option { + let outer_attrs = variant + .attrs + .iter() + .filter(|attr| attr.style == AttrStyle::Outer); + + find_meta_item(outer_attrs, |meta| { if let NestedMeta::Meta(Meta::NameValue(ref nv)) = meta { if nv.path.is_ident("index") { if let Lit::Int(ref v) = nv.lit { @@ -72,14 +90,6 @@ pub fn variant_index(v: &Variant, i: usize) -> TokenStream { } None - }); - - // then fallback to discriminant or just index - index.map(|i| quote! { #i }).unwrap_or_else(|| { - v.discriminant - .as_ref() - .map(|&(_, ref expr)| quote! { #expr }) - .unwrap_or_else(|| quote! { #i }) }) } diff --git a/src/build.rs b/src/build.rs index 663c66d4..3aeec990 100644 --- a/src/build.rs +++ b/src/build.rs @@ -91,7 +91,7 @@ //! } //! } //! ``` -//! ## Enum without fields +//! ## Enum without fields, aka C-style enums. //! ``` //! # use scale_info::{build::{Fields, Variants}, MetaType, Path, Type, TypeInfo, Variant}; //! enum Foo { @@ -407,7 +407,7 @@ impl FieldBuilder { self.name, self.ty.expect("Type should be set by builder"), self.type_name, - &self.docs, + self.docs, ) } } @@ -453,6 +453,7 @@ impl Variants { pub struct VariantBuilder { name: &'static str, fields: Vec>, + index: Option, discriminant: Option, docs: Vec<&'static str>, } @@ -464,6 +465,7 @@ impl VariantBuilder { name, fields: Vec::new(), discriminant: None, + index: None, docs: Vec::new(), } } @@ -474,6 +476,12 @@ impl VariantBuilder { self } + /// Set the variant's codec index. + pub fn index(mut self, index: u8) -> Self { + self.index = Some(index); + self + } + /// Initialize the variant's fields. pub fn fields(mut self, fields_builder: FieldsBuilder) -> Self { self.fields = fields_builder.finalize(); @@ -488,6 +496,12 @@ impl VariantBuilder { /// Complete building and create final [`Variant`] instance. pub fn finalize(self) -> Variant { - Variant::new(self.name, self.fields, self.discriminant, self.docs) + Variant::new( + self.name, + self.fields, + self.index, + self.discriminant, + self.docs, + ) } } diff --git a/src/tests.rs b/src/tests.rs index 653d66c4..d35c63fd 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -223,3 +223,78 @@ fn basic_struct_with_phantoms() { assert_type!(SomeStruct, struct_bool_type_info); } + +#[test] +fn basic_enum_with_index() { + use scale::Encode; + + #[allow(unused)] + #[derive(Encode)] + enum IndexedRustEnum { + #[codec(index = 3)] + A(bool), + #[codec(index = 0)] + B { + b: u8, + }, + C(u16, u32), + D, + } + impl TypeInfo for IndexedRustEnum { + type Identity = Self; + + fn type_info() -> Type { + Type::builder() + .path(Path::new("IndexedRustEnum", module_path!())) + .variant( + Variants::new() + .variant("A", |v| { + v.index(3).fields( + Fields::unnamed() + .field(|f| f.ty::().type_name("bool")), + ) + }) + .variant("B", |v| { + v.index(0).fields( + Fields::named() + .field(|f| f.ty::().name("b").type_name("u8")), + ) + }) + .variant("C", |v| { + v.fields( + Fields::unnamed() + .field(|f| f.ty::().type_name("u16")) + .field(|f| f.ty::().type_name("u32")), + ) + }) + .variant_unit("D"), + ) + } + } + + let ty = Type::builder() + .path(Path::new("IndexedRustEnum", module_path!())) + .variant( + Variants::new() + .variant("A", |v| { + v.index(3).fields( + Fields::unnamed().field(|f| f.ty::().type_name("bool")), + ) + }) + .variant("B", |v| { + v.index(0).fields( + Fields::named().field(|f| f.ty::().name("b").type_name("u8")), + ) + }) + .variant("C", |v| { + v.fields( + Fields::unnamed() + .field(|f| f.ty::().type_name("u16")) + .field(|f| f.ty::().type_name("u32")), + ) + }) + .variant_unit("D"), + ); + + assert_type!(IndexedRustEnum, ty); +} diff --git a/src/ty/variant.rs b/src/ty/variant.rs index 99763eaa..1c92c2b6 100644 --- a/src/ty/variant.rs +++ b/src/ty/variant.rs @@ -157,6 +157,12 @@ pub struct Variant { serde(skip_serializing_if = "Vec::is_empty", default) )] fields: Vec>, + /// Index of the variant, used in `parity-scale-codec` + #[cfg_attr( + feature = "serde", + serde(skip_serializing_if = "Option::is_none", default) + )] + index: Option, /// The discriminant of the variant. /// /// # Note @@ -184,6 +190,7 @@ impl IntoPortable for Variant { Variant { name: self.name.into_portable(registry), fields: registry.map_into_portable(self.fields), + index: self.index, discriminant: self.discriminant, docs: registry.map_into_portable(self.docs), } @@ -195,13 +202,15 @@ impl Variant { pub(crate) fn new( name: &'static str, fields: Vec>, - index: Option, + index: Option, + discriminant: Option, docs: Vec<&'static str>, ) -> Self { Self { name, fields, - discriminant: index, + index, + discriminant, docs, } } diff --git a/test_suite/tests/derive.rs b/test_suite/tests/derive.rs index dd3c763c..81836ba0 100644 --- a/test_suite/tests/derive.rs +++ b/test_suite/tests/derive.rs @@ -252,6 +252,41 @@ fn enum_derive() { assert_type!(E, ty); } +#[test] +fn enum_derive_with_codec_index() { + #[allow(unused)] + #[derive(TypeInfo, Encode)] + enum E { + #[codec(index = 5)] + A(T), + #[codec(index = 0)] + B { b: T }, + #[codec(index = 13)] + C, + } + + let ty = Type::builder() + .path(Path::new("E", "derive")) + .type_params(tuple_meta_type!(bool)) + .variant( + Variants::new() + .variant("A", |v| { + v.index(5).fields( + Fields::unnamed().field(|f| f.ty::().type_name("T")), + ) + }) + .variant("B", |v| { + v.index(0).fields( + Fields::named() + .field(|f| f.ty::().name("b").type_name("T")), + ) + }) + .variant("C", |v| v.index(13)), + ); + + assert_type!(E, ty); +} + #[test] fn recursive_type_derive() { #[allow(unused)] diff --git a/test_suite/tests/json.rs b/test_suite/tests/json.rs index 89ef93a8..60dd8959 100644 --- a/test_suite/tests/json.rs +++ b/test_suite/tests/json.rs @@ -326,6 +326,58 @@ fn test_enum() { })); } +#[test] +fn enums_with_scale_indexed_variants() { + #[derive(TypeInfo, Encode)] + enum Animal { + #[codec(index = 123)] + Ape(u8), + #[codec(index = 12)] + Boar { a: u16, b: u32 }, + #[codec(index = 1)] + Cat, + #[codec(index = 0)] + Dog(u64, u128), + } + + assert_json_for_type::(json!({ + "path": ["json", "Animal"], + "def": { + "variant": { + "variants": [ + { + "name": "Ape", + "index": 123, + "fields": [ + { "type": 0, "typeName": "u8" } + ] + }, + { + "name": "Boar", + "index": 12, + "fields": [ + { "name": "a", "type": 1, "typeName": "u16" }, + { "name": "b", "type": 2, "typeName": "u32" } + ] + }, + { + "name": "Cat", + "index": 1, + }, + { + "name": "Dog", + "index": 0, + "fields": [ + { "type": 3, "typeName": "u64" }, + { "type": 4, "typeName": "u128" } + ] + } + ] + } + } + })); +} + #[test] fn test_recursive_type_with_box() { #[derive(TypeInfo)]