diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 37e4a33..236f050 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -71,6 +71,16 @@ jobs: # we run tests using BitVec which doesn't. args: --all-features --target wasm32-unknown-unknown + diff-readme-files: + name: Check that READMEs are identical + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v3 + + - name: Diff READMEs + run: diff -q README.md scale-encode/README.md + no_std: name: Check no_std build runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index 96ef6c0..ad2f0ff 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target Cargo.lock +.DS_Store \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index b298b51..3c46791 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "scale-encode-derive", "testing/no_std", ] +resolver = "2" [workspace.package] version = "0.5.0" diff --git a/README.md b/README.md index 205783c..084c051 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,17 @@ # scale-encode `parity-scale-codec` provides an `Encode` trait which allows types to SCALE encode themselves based on their shape. -This crate builds on this, and allows types to encode themselves based on `scale_info` type information. It -exposes two traits: +This crate builds on this, and allows types to encode themselves based on type information from a `TypeResolver` +implementation (one such implementation being a `scale_info::PortableRegistry`). It exposes two traits: - An `EncodeAsType` trait which when implemented on some type, describes how it can be SCALE encoded with the help of a type ID and type registry describing the expected shape of the encoded bytes. - An `EncodeAsFields` trait which when implemented on some type, describes how it can be SCALE encoded - with the help of a slice of `PortableField`'s or `PortableFieldId`'s and type registry describing the - expected shape of the encoded bytes. This is generally only implemented for tuples and structs, since we - need a set of fields to map to the provided slices. + with the help of an iterator over `Field`s and a type registry describing the expected shape of the + encoded bytes. This is generally only implemented for tuples and structs, since we need a set of fields + to map to the provided iterator. -Implementations for many built-in types are also provided for each trait, and the `macro@EncodeAsType` +Implementations for many built-in types are also provided for each trait, and the `EncodeAsType` macro makes it easy to generate implementations for new structs and enums. # Motivation @@ -24,14 +24,14 @@ use codec::Encode; use scale_encode::EncodeAsType; use scale_info::{PortableRegistry, TypeInfo}; -// We are comonly provided type information, but for our examples we construct type info from +// We are commonly provided type information, but for our examples we construct type info from // any type that implements `TypeInfo`. fn get_type_info() -> (u32, PortableRegistry) { let m = scale_info::MetaType::new::(); let mut types = scale_info::Registry::new(); let ty = types.register_type(&m); let portable_registry: PortableRegistry = types.into(); - (ty.id(), portable_registry) + (ty.id, portable_registry) } // Encode the left value via EncodeAsType into the shape of the right value. @@ -43,7 +43,7 @@ where B: TypeInfo + Encode + 'static, { let (type_id, types) = get_type_info::(); - let a_bytes = a.encode_as_type(type_id, &types).unwrap(); + let a_bytes = a.encode_as_type(&type_id, &types).unwrap(); let b_bytes = b.encode(); assert_eq!(a_bytes, b_bytes); } diff --git a/scale-encode-derive/src/lib.rs b/scale-encode-derive/src/lib.rs index 2f4d9a2..8c8eae6 100644 --- a/scale-encode-derive/src/lib.rs +++ b/scale-encode-derive/src/lib.rs @@ -67,7 +67,7 @@ fn generate_enum_impl( quote!( Self::#variant_name #matcher => { #path_to_scale_encode::Variant { name: #variant_name_str, fields: #composite } - .encode_as_type_to( + .encode_variant_as_type_to( __encode_as_type_type_id, __encode_as_type_types, __encode_as_type_out @@ -79,11 +79,11 @@ fn generate_enum_impl( quote!( impl #impl_generics #path_to_scale_encode::EncodeAsType for #path_to_type #ty_generics #where_clause { #[allow(unused_variables)] - fn encode_as_type_to( + fn encode_as_type_to( &self, // long variable names to prevent conflict with struct field names: - __encode_as_type_type_id: u32, - __encode_as_type_types: &#path_to_scale_encode::PortableRegistry, + __encode_as_type_type_id: &ScaleEncodeResolver::TypeId, + __encode_as_type_types: &ScaleEncodeResolver, __encode_as_type_out: &mut #path_to_scale_encode::Vec ) -> Result<(), #path_to_scale_encode::Error> { match self { @@ -112,15 +112,15 @@ fn generate_struct_impl( quote!( impl #impl_generics #path_to_scale_encode::EncodeAsType for #path_to_type #ty_generics #where_clause { #[allow(unused_variables)] - fn encode_as_type_to( + fn encode_as_type_to( &self, // long variable names to prevent conflict with struct field names: - __encode_as_type_type_id: u32, - __encode_as_type_types: &#path_to_scale_encode::PortableRegistry, + __encode_as_type_type_id: &ScaleEncodeResolver::TypeId, + __encode_as_type_types: &ScaleEncodeResolver, __encode_as_type_out: &mut #path_to_scale_encode::Vec ) -> Result<(), #path_to_scale_encode::Error> { let #path_to_type #matcher = self; - #composite.encode_as_type_to( + #composite.encode_composite_as_type_to( __encode_as_type_type_id, __encode_as_type_types, __encode_as_type_out @@ -129,15 +129,15 @@ fn generate_struct_impl( } impl #impl_generics #path_to_scale_encode::EncodeAsFields for #path_to_type #ty_generics #where_clause { #[allow(unused_variables)] - fn encode_as_fields_to( + fn encode_as_fields_to( &self, // long variable names to prevent conflict with struct field names: - __encode_as_type_fields: &mut dyn #path_to_scale_encode::FieldIter<'_>, - __encode_as_type_types: &#path_to_scale_encode::PortableRegistry, + __encode_as_type_fields: &mut dyn #path_to_scale_encode::FieldIter<'_, ScaleEncodeResolver::TypeId>, + __encode_as_type_types: &ScaleEncodeResolver, __encode_as_type_out: &mut #path_to_scale_encode::Vec ) -> Result<(), #path_to_scale_encode::Error> { let #path_to_type #matcher = self; - #composite.encode_as_fields_to( + #composite.encode_composite_fields_to( __encode_as_type_fields, __encode_as_type_types, __encode_as_type_out @@ -192,12 +192,12 @@ fn fields_to_matcher_and_composite( .map(|f| { let field_name_str = f.ident.as_ref().unwrap().to_string(); let field_name = &f.ident; - quote!((Some(#field_name_str), #field_name as &dyn #path_to_scale_encode::EncodeAsType)) + quote!((Some(#field_name_str), #path_to_scale_encode::CompositeField::new(#field_name))) }); ( quote!({#( #match_body ),*}), - quote!(#path_to_scale_encode::Composite([#( #tuple_body ),*].into_iter())), + quote!(#path_to_scale_encode::Composite::new([#( #tuple_body ),*].into_iter())), ) } syn::Fields::Unnamed(fields) => { @@ -210,16 +210,16 @@ fn fields_to_matcher_and_composite( let match_body = field_idents.clone().map(|(i, _)| quote!(#i)); let tuple_body = field_idents .filter(|(_, f)| !should_skip(&f.attrs)) - .map(|(i, _)| quote!((None as Option<&'static str>, #i as &dyn #path_to_scale_encode::EncodeAsType))); + .map(|(i, _)| quote!((None as Option<&'static str>, #path_to_scale_encode::CompositeField::new(#i)))); ( quote!((#( #match_body ),*)), - quote!(#path_to_scale_encode::Composite([#( #tuple_body ),*].into_iter())), + quote!(#path_to_scale_encode::Composite::new([#( #tuple_body ),*].into_iter())), ) } syn::Fields::Unit => ( quote!(), - quote!(#path_to_scale_encode::Composite(([] as [(Option<&'static str>, &dyn #path_to_scale_encode::EncodeAsType);0]).into_iter())), + quote!(#path_to_scale_encode::Composite::new(([] as [(Option<&'static str>, #path_to_scale_encode::CompositeField<_>);0]).into_iter())), ), } } diff --git a/scale-encode/Cargo.toml b/scale-encode/Cargo.toml index 6e4f0f1..2bacec6 100644 --- a/scale-encode/Cargo.toml +++ b/scale-encode/Cargo.toml @@ -17,7 +17,7 @@ include.workspace = true default = ["std", "derive", "primitive-types", "bits"] # Activates std feature. -std = ["scale-info/std"] +std = [] # Include the derive proc macro. derive = ["dep:scale-encode-derive"] @@ -29,9 +29,9 @@ primitive-types = ["dep:primitive-types"] bits = ["dep:scale-bits"] [dependencies] -scale-info = { version = "2.7.0", default-features = false, features = ["bit-vec"] } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -scale-bits = { version = "0.4.0", default-features = false, features = ["scale-info"], optional = true } +scale-type-resolver = { version = "0.1.1", default-features = false, features = ["visitor"] } +scale-bits = { version = "0.5.0", default-features = false, optional = true } scale-encode-derive = { workspace = true, optional = true } primitive-types = { version = "0.12.0", optional = true, default-features = false } smallvec = "1.10.0" @@ -39,9 +39,10 @@ derive_more = { version = "0.99.17", default-features = false, features = ["from [dev-dependencies] bitvec = { version = "1.0.1", default-features = false } -scale-info = { version = "2.3.0", features = ["bit-vec", "derive"], default-features = false } +scale-info = { version = "2.3.0", features = ["bit-vec", "derive", "std"], default-features = false } scale-encode-derive = { workspace = true } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "bit-vec"] } trybuild = "1.0.72" # enable scale-info feature for testing: primitive-types = { version = "0.12.0", default-features = false, features = ["scale-info"] } +scale-type-resolver = { version = "0.1.1", default-features = false, features = ["scale-info"] } diff --git a/scale-encode/README.md b/scale-encode/README.md index 205783c..084c051 100644 --- a/scale-encode/README.md +++ b/scale-encode/README.md @@ -1,17 +1,17 @@ # scale-encode `parity-scale-codec` provides an `Encode` trait which allows types to SCALE encode themselves based on their shape. -This crate builds on this, and allows types to encode themselves based on `scale_info` type information. It -exposes two traits: +This crate builds on this, and allows types to encode themselves based on type information from a `TypeResolver` +implementation (one such implementation being a `scale_info::PortableRegistry`). It exposes two traits: - An `EncodeAsType` trait which when implemented on some type, describes how it can be SCALE encoded with the help of a type ID and type registry describing the expected shape of the encoded bytes. - An `EncodeAsFields` trait which when implemented on some type, describes how it can be SCALE encoded - with the help of a slice of `PortableField`'s or `PortableFieldId`'s and type registry describing the - expected shape of the encoded bytes. This is generally only implemented for tuples and structs, since we - need a set of fields to map to the provided slices. + with the help of an iterator over `Field`s and a type registry describing the expected shape of the + encoded bytes. This is generally only implemented for tuples and structs, since we need a set of fields + to map to the provided iterator. -Implementations for many built-in types are also provided for each trait, and the `macro@EncodeAsType` +Implementations for many built-in types are also provided for each trait, and the `EncodeAsType` macro makes it easy to generate implementations for new structs and enums. # Motivation @@ -24,14 +24,14 @@ use codec::Encode; use scale_encode::EncodeAsType; use scale_info::{PortableRegistry, TypeInfo}; -// We are comonly provided type information, but for our examples we construct type info from +// We are commonly provided type information, but for our examples we construct type info from // any type that implements `TypeInfo`. fn get_type_info() -> (u32, PortableRegistry) { let m = scale_info::MetaType::new::(); let mut types = scale_info::Registry::new(); let ty = types.register_type(&m); let portable_registry: PortableRegistry = types.into(); - (ty.id(), portable_registry) + (ty.id, portable_registry) } // Encode the left value via EncodeAsType into the shape of the right value. @@ -43,7 +43,7 @@ where B: TypeInfo + Encode + 'static, { let (type_id, types) = get_type_info::(); - let a_bytes = a.encode_as_type(type_id, &types).unwrap(); + let a_bytes = a.encode_as_type(&type_id, &types).unwrap(); let b_bytes = b.encode(); assert_eq!(a_bytes, b_bytes); } diff --git a/scale-encode/src/error/mod.rs b/scale-encode/src/error/mod.rs index 715ff4c..19ca0e9 100644 --- a/scale-encode/src/error/mod.rs +++ b/scale-encode/src/error/mod.rs @@ -114,16 +114,19 @@ impl Display for Error { /// The underlying nature of the error. #[derive(Debug, derive_more::From, derive_more::Display)] pub enum ErrorKind { + /// There was an error resolving the type via the given [`crate::TypeResolver`]. + #[display(fmt = "Failed to resolve type: {_0}")] + TypeResolvingError(String), /// Cannot find a given type. - #[display(fmt = "Cannot find type with ID {_0}")] - TypeNotFound(u32), + #[display(fmt = "Cannot find type with identifier {_0}")] + TypeNotFound(String), /// Cannot encode the actual type given into the target type ID. - #[display(fmt = "Cannot encode {actual:?} into type with ID {expected}")] + #[display(fmt = "Cannot encode {actual:?} into type with ID {expected_id}")] WrongShape { /// The actual kind we have to encode actual: Kind, - /// ID of the expected type. - expected: u32, + /// Identifier for the expected type + expected_id: String, }, /// The types line up, but the expected length of the target type is different from the length of the input value. #[display( @@ -136,20 +139,22 @@ pub enum ErrorKind { expected_len: usize, }, /// We cannot encode the number given into the target type; it's out of range. - #[display(fmt = "Number {value} is out of range for target type {expected}")] + #[display( + fmt = "Number {value} is out of range for target type with identifier {expected_id}" + )] NumberOutOfRange { /// A string represenatation of the numeric value that was out of range. value: String, - /// Id of the expected numeric type that we tried to encode it to. - expected: u32, + /// Identifier for the expected numeric type that we tried to encode it to. + expected_id: String, }, /// Cannot find a variant with a matching name on the target type. - #[display(fmt = "Variant {name} does not exist on type with ID {expected}")] + #[display(fmt = "Variant {name} does not exist on type with identifier {expected_id}")] CannotFindVariant { /// Variant name we can't find in the expected type. name: String, - /// ID of the expected type. - expected: u32, + /// Identifier for the expected type. + expected_id: String, }, /// Cannot find a field on our source type that's needed for the target type. #[display(fmt = "Field {name} does not exist in our source struct")] diff --git a/scale-encode/src/impls/bits.rs b/scale-encode/src/impls/bits.rs index b36cb98..aa61955 100644 --- a/scale-encode/src/impls/bits.rs +++ b/scale-encode/src/impls/bits.rs @@ -17,37 +17,33 @@ use crate::{ error::{Error, ErrorKind, Kind}, EncodeAsType, }; -use alloc::vec::Vec; -use scale_info::TypeDef; +use alloc::{format, vec::Vec}; +use scale_type_resolver::{visitor, TypeResolver}; impl EncodeAsType for scale_bits::Bits { - fn encode_as_type_to( + fn encode_as_type_to( &self, - type_id: u32, - types: &scale_info::PortableRegistry, + type_id: &R::TypeId, + types: &R, out: &mut Vec, ) -> Result<(), crate::Error> { let type_id = super::find_single_entry_with_same_repr(type_id, types); - let ty = types - .resolve(type_id) - .ok_or_else(|| Error::new(ErrorKind::TypeNotFound(type_id)))?; - if let TypeDef::BitSequence(ty) = &ty.type_def { - let Ok(format) = scale_bits::Format::from_metadata(ty, types) else { - return Err(wrong_shape(type_id)) - }; + let v = visitor::new(out, |_, _| Err(wrong_shape(type_id))).visit_bit_sequence( + |out, store, order| { + let format = scale_bits::Format { store, order }; + scale_bits::encode_using_format_to(self.iter(), format, out); + Ok(()) + }, + ); - scale_bits::encode_using_format_to(self.iter(), format, out); - Ok(()) - } else { - Err(wrong_shape(type_id)) - } + super::resolve_type_and_encode(types, type_id, v) } } -fn wrong_shape(type_id: u32) -> Error { +fn wrong_shape(type_id: impl core::fmt::Debug) -> Error { Error::new(ErrorKind::WrongShape { actual: Kind::BitSequence, - expected: type_id, + expected_id: format!("{type_id:?}"), }) } diff --git a/scale-encode/src/impls/composite.rs b/scale-encode/src/impls/composite.rs index 9edc148..35a49a0 100644 --- a/scale-encode/src/impls/composite.rs +++ b/scale-encode/src/impls/composite.rs @@ -15,18 +15,80 @@ use crate::{ error::{Error, ErrorKind, Kind, Location}, - EncodeAsFields, EncodeAsType, Field, FieldIter, + EncodeAsType, Field, FieldIter, TypeResolver, }; use alloc::collections::BTreeMap; -use alloc::{string::ToString, vec::Vec}; -use scale_info::{PortableRegistry, TypeDef}; +use alloc::{format, string::ToString, vec::Vec}; +use scale_type_resolver::visitor; -/// This type represents named or unnamed composite values, and can be used -/// to help generate `EncodeAsType` impls. It's primarily used by the exported -/// macros to do just that. +/// This trait exists to get around object safety issues using [`EncodeAsType`]. +/// It's object safe and automatically implemented for any type which implements +/// [`EncodeAsType`]. We need this to construct generic [`Composite`] types. +trait EncodeAsTypeWithResolver { + fn encode_as_type_with_resolver_to( + &self, + type_id: &R::TypeId, + types: &R, + out: &mut Vec, + ) -> Result<(), Error>; +} +impl EncodeAsTypeWithResolver for T { + fn encode_as_type_with_resolver_to( + &self, + type_id: &R::TypeId, + types: &R, + out: &mut Vec, + ) -> Result<(), Error> { + self.encode_as_type_to(type_id, types, out) + } +} + +/// A struct representing a single composite field. To be used in conjunction +/// with the [`Composite`] struct to construct generic composite shaped types. +/// this basically takes a type which implements [`EncodeAsType`] and turns it +/// into something object safe. +pub struct CompositeField<'a, R> { + val: &'a dyn EncodeAsTypeWithResolver, +} + +impl<'a, R> Copy for CompositeField<'a, R> {} +impl<'a, R> Clone for CompositeField<'a, R> { + fn clone(&self) -> Self { + *self + } +} +impl<'a, R> core::fmt::Debug for CompositeField<'a, R> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str("CompositeField") + } +} + +impl<'a, R: TypeResolver> CompositeField<'a, R> { + /// Construct a new composite field given some type which implements + /// [`EncodeAsType`]. + pub fn new(val: &'a T) -> Self { + CompositeField { val } + } + + /// SCALE encode this composite field to bytes based on the underlying type. + pub fn encode_composite_field_to( + &self, + type_id: &R::TypeId, + types: &R, + out: &mut Vec, + ) -> Result<(), Error> { + self.val + .encode_as_type_with_resolver_to(type_id, types, out) + } +} + +/// This type represents named or unnamed composite values, and can be used to help generate +/// `EncodeAsType` impls. It's primarily used by the exported macros to do just that. /// /// ```rust -/// use scale_encode::{ Error, EncodeAsType, Composite, PortableRegistry }; +/// use scale_encode::{ +/// Error, EncodeAsType, Composite, CompositeField, TypeResolver +/// }; /// /// struct MyType { /// foo: bool, @@ -35,111 +97,160 @@ use scale_info::{PortableRegistry, TypeDef}; /// } /// /// impl EncodeAsType for MyType { -/// fn encode_as_type_to(&self, type_id: u32, types: &PortableRegistry, out: &mut Vec) -> Result<(), Error> { -/// Composite([ -/// (Some("foo"), &self.foo as &dyn EncodeAsType), -/// (Some("bar"), &self.bar as &dyn EncodeAsType), -/// (Some("wibble"), &self.wibble as &dyn EncodeAsType) -/// ].into_iter()).encode_as_type_to(type_id, types, out) +/// fn encode_as_type_to( +/// &self, +/// type_id: &R::TypeId, +/// types: &R, +/// out: &mut Vec +/// ) -> Result<(), Error> { +/// Composite::new([ +/// (Some("foo"), CompositeField::new(&self.foo)), +/// (Some("bar"), CompositeField::new(&self.bar)), +/// (Some("wibble"), CompositeField::new(&self.wibble)) +/// ].into_iter()).encode_composite_as_type_to(type_id, types, out) /// } /// } /// ``` -pub struct Composite(pub Vals); +/// +/// [`Composite`] cannot implement [`EncodeAsType`] itself, because it is tied to being +/// encoded with a specific `R: TypeResolver`, whereas things implementing [`EncodeAsType`] +/// need to be encodable using _any_ [`TypeResolver`]. This is ultimately because +/// [`EncodeAsType`] is not object safe, which prevents it from being used to describe +/// [`CompositeFields`][CompositeField]. +pub struct Composite { + vals: Vals, + marker: core::marker::PhantomData, +} -impl<'a, Vals> EncodeAsType for Composite +impl<'a, R, Vals> Composite where - Vals: ExactSizeIterator, &'a dyn EncodeAsType)> + Clone, + R: TypeResolver + 'a, + Vals: ExactSizeIterator, CompositeField<'a, R>)> + Clone, { - fn encode_as_type_to( + /// Construct a new [`Composite`] type by providing an iterator over + /// the fields that it contains. + /// + /// ```rust + /// use scale_encode::{ Composite, CompositeField }; + /// use scale_info::PortableRegistry; + /// + /// Composite::::new([ + /// (Some("foo"), CompositeField::new(&123)), + /// (Some("bar"), CompositeField::new(&"hello")) + /// ].into_iter()); + /// ``` + pub fn new(vals: Vals) -> Self { + Composite { + vals, + marker: core::marker::PhantomData, + } + } + + /// A shortcut for [`Self::encode_composite_as_type_to()`] which internally + /// allocates a [`Vec`] and returns it. + pub fn encode_composite_as_type( + &self, + type_id: &R::TypeId, + types: &R, + ) -> Result, Error> { + let mut out = Vec::new(); + self.encode_composite_as_type_to(type_id, types, &mut out)?; + Ok(out) + } + + /// Encode this composite value as the provided type to the output bytes. + pub fn encode_composite_as_type_to( &self, - type_id: u32, - types: &PortableRegistry, + type_id: &R::TypeId, + types: &R, out: &mut Vec, ) -> Result<(), Error> { - let mut vals_iter = self.0.clone(); + let vals_iter = self.vals.clone(); let vals_iter_len = vals_iter.len(); // Skip through any single field composites/tuples without names. If there // are names, we may want to line up input field(s) on them. let type_id = skip_through_single_unnamed_fields(type_id, types); - let ty = types - .resolve(type_id) - .ok_or_else(|| Error::new(ErrorKind::TypeNotFound(type_id)))?; - - match &ty.type_def { - // If we see a tuple type, it'll have more than one field else it'd have been skipped above. - TypeDef::Tuple(tuple) => { - // If there is exactly one val, it won't line up with the tuple then, so - // try encoding one level in instead. - if vals_iter_len == 1 { - return vals_iter - .next() - .unwrap() - .1 - .encode_as_type_to(type_id, types, out); - } - - let mut fields = tuple.fields.iter().map(|f| Field::unnamed(f.id)); - self.encode_as_fields_to(&mut fields, types, out) + let v = visitor::new((out, vals_iter), |(out, mut vals_iter), _| { + // Rather than immediately giving up, we should at least see whether + // we can skip one level in to our value and encode that. + if vals_iter_len == 1 { + return vals_iter + .next() + .expect("1 value expected") + .1 + .encode_composite_field_to(type_id, types, out); } - // If we see a composite type, it has either named fields or !=1 unnamed fields. - TypeDef::Composite(composite) => { - // If vals are named, we may need to line them up with some named composite. - // If they aren't named, we only care about lining up based on matching lengths. - let is_named_vals = vals_iter.clone().any(|(name, _)| name.is_some()); - - // If there is exactly one val that isn't named, then we know it won't line - // up with this composite then, so try encoding one level in. - if !is_named_vals && vals_iter_len == 1 { - return vals_iter - .next() - .unwrap() - .1 - .encode_as_type_to(type_id, types, out); - } - - let mut fields = composite - .fields - .iter() - .map(|f| Field::new(f.ty.id, f.name.as_deref())); - self.encode_as_fields_to(&mut fields, types, out) + + // If we get here, then it means the value we were given had more than + // one field, and the type we were given was ultimately some one-field thing + // that contained a non composite/tuple type, so it would never work out. + Err(Error::new(ErrorKind::WrongShape { + actual: Kind::Struct, + expected_id: format!("{type_id:?}"), + })) + }) + .visit_not_found(|_| Err(Error::new(ErrorKind::TypeNotFound(format!("{type_id:?}"))))) + .visit_composite(|(out, mut vals_iter), mut fields| { + // If vals are named, we may need to line them up with some named composite. + // If they aren't named, we only care about lining up based on matching lengths. + let is_named_vals = vals_iter.clone().any(|(name, _)| name.is_some()); + + // If there is exactly one val that isn't named, then we know it won't line + // up with this composite then, so try encoding one level in. + if !is_named_vals && vals_iter_len == 1 { + return vals_iter + .next() + .expect("1 value expected") + .1 + .encode_composite_field_to(type_id, types, out); } - // We may have skipped through to some primitive or other type. - _ => { - // Rather than immediately giving up, we should at least see whether - // we can skip one level in to our value and encode that. - if vals_iter_len == 1 { - return vals_iter - .next() - .unwrap() - .1 - .encode_as_type_to(type_id, types, out); - } - - // If we get here, then it means the value we were given had more than - // one field, and the type we were given was ultimately some one-field thing - // that contained a non composite/tuple type, so it would never work out. - Err(Error::new(ErrorKind::WrongShape { - actual: Kind::Tuple, - expected: type_id, - })) + + self.encode_composite_fields_to(&mut fields, types, out) + }) + .visit_tuple(|(out, mut vals_iter), type_ids| { + // If there is exactly one val, it won't line up with the tuple then, so + // try encoding one level in instead. + if vals_iter_len == 1 { + return vals_iter + .next() + .unwrap() + .1 + .encode_composite_field_to(type_id, types, out); } - } + + let mut fields = type_ids.map(Field::unnamed); + self.encode_composite_fields_to( + &mut fields as &mut dyn FieldIter<'_, R::TypeId>, + types, + out, + ) + }); + + super::resolve_type_and_encode(types, type_id, v) } -} -impl<'a, Vals> EncodeAsFields for Composite -where - Vals: ExactSizeIterator, &'a dyn EncodeAsType)> + Clone, -{ - fn encode_as_fields_to( + /// A shortcut for [`Self::encode_composite_fields_to()`] which internally + /// allocates a [`Vec`] and returns it. + pub fn encode_composite_fields( + &self, + fields: &mut dyn FieldIter<'_, R::TypeId>, + types: &R, + ) -> Result, Error> { + let mut out = Vec::new(); + self.encode_composite_fields_to(fields, types, &mut out)?; + Ok(out) + } + + /// Encode the composite fields as the provided field description to the output bytes + pub fn encode_composite_fields_to( &self, - fields: &mut dyn FieldIter<'_>, - types: &PortableRegistry, + fields: &mut dyn FieldIter<'_, R::TypeId>, + types: &R, out: &mut Vec, ) -> Result<(), Error> { - let vals_iter = self.0.clone(); + let vals_iter = self.vals.clone(); // Most of the time there aren't too many fields, so avoid allocation in most cases: let fields = smallvec::SmallVec::<[_; 16]>::from_iter(fields); @@ -147,7 +258,7 @@ where // Both the target and source type have to have named fields for us to use // names to line them up. let is_named = { - let is_target_named = fields.iter().any(|f| f.name().is_some()); + let is_target_named = fields.iter().any(|f| f.name.is_some()); let is_source_named = vals_iter.clone().any(|(name, _)| name.is_some()); is_target_named && is_source_named }; @@ -157,20 +268,22 @@ where // then encode to the target type by matching the names. If fields are // named, we don't even mind if the number of fields doesn't line up; // we just ignore any fields we provided that aren't needed. - let source_fields_by_name: BTreeMap<&str, &dyn EncodeAsType> = vals_iter + let source_fields_by_name: BTreeMap<&str, CompositeField<'a, R>> = vals_iter .map(|(name, val)| (name.unwrap_or(""), val)) .collect(); for field in fields { // Find the field in our source type: - let name = field.name().unwrap_or(""); + let name = field.name.unwrap_or(""); let Some(value) = source_fields_by_name.get(name) else { - return Err(Error::new(ErrorKind::CannotFindField { name: name.to_string() })) + return Err(Error::new(ErrorKind::CannotFindField { + name: name.to_string(), + })); }; // Encode the value to the output: value - .encode_as_type_to(field.id(), types, out) + .encode_composite_field_to(field.id, types, out) .map_err(|e| e.at_field(name.to_string()))?; } @@ -188,14 +301,15 @@ where } for (idx, (field, (name, val))) in fields.iter().zip(vals_iter).enumerate() { - val.encode_as_type_to(field.id(), types, out).map_err(|e| { - let loc = if let Some(name) = name { - Location::field(name.to_string()) - } else { - Location::idx(idx) - }; - e.at(loc) - })?; + val.encode_composite_field_to(field.id, types, out) + .map_err(|e| { + let loc = if let Some(name) = name { + Location::field(name.to_string()) + } else { + Location::idx(idx) + }; + e.at(loc) + })?; } Ok(()) } @@ -204,19 +318,31 @@ where // Single unnamed fields carry no useful information and can be skipped through. // Single named fields may still be useful to line up with named composites. -fn skip_through_single_unnamed_fields(type_id: u32, types: &PortableRegistry) -> u32 { - let Some(ty) = types.resolve(type_id) else { - return type_id - }; - match &ty.type_def { - TypeDef::Tuple(tuple) if tuple.fields.len() == 1 => { - skip_through_single_unnamed_fields(tuple.fields[0].id, types) - } - TypeDef::Composite(composite) - if composite.fields.len() == 1 && composite.fields[0].name.is_none() => - { - skip_through_single_unnamed_fields(composite.fields[0].ty.id, types) - } - _ => type_id, - } +fn skip_through_single_unnamed_fields<'a, R: TypeResolver>( + type_id: &'a R::TypeId, + types: &'a R, +) -> &'a R::TypeId { + let v = visitor::new((), |_, _| type_id) + .visit_composite(|_, fields| { + // If exactly 1 unnamed field, recurse into it, else return current type ID. + let Some(f) = fields.next() else { + return type_id; + }; + if fields.next().is_some() || f.name.is_some() { + return type_id; + }; + skip_through_single_unnamed_fields(f.id, types) + }) + .visit_tuple(|_, type_ids| { + // Else if exactly 1 tuple entry, recurse into it, else return current type ID. + let Some(new_type_id) = type_ids.next() else { + return type_id; + }; + if type_ids.next().is_some() { + return type_id; + }; + skip_through_single_unnamed_fields(new_type_id, types) + }); + + types.resolve_type(type_id, v).unwrap_or(type_id) } diff --git a/scale-encode/src/impls/mod.rs b/scale-encode/src/impls/mod.rs index a1d555e..c0c7ebe 100644 --- a/scale-encode/src/impls/mod.rs +++ b/scale-encode/src/impls/mod.rs @@ -20,19 +20,15 @@ mod composite; mod primitive_types; mod variant; -// Useful to help encode key-value types or custom variant types manually. -// Primarily used in the derive macro. -pub use composite::Composite; -pub use variant::Variant; - use crate::{ error::{Error, ErrorKind, Kind}, - EncodeAsFields, EncodeAsType, FieldIter, + EncodeAsFields, EncodeAsType, }; use alloc::{ borrow::ToOwned, boxed::Box, collections::{BTreeMap, BTreeSet, BinaryHeap, LinkedList, VecDeque}, + format, rc::Rc, string::{String, ToString}, sync::Arc, @@ -48,53 +44,87 @@ use core::{ ops::{Range, RangeInclusive}, time::Duration, }; -use scale_info::{PortableRegistry, TypeDef, TypeDefPrimitive}; +use scale_type_resolver::{visitor, FieldIter, Primitive, ResolvedTypeVisitor, TypeResolver}; + +// Useful to help encode key-value types or custom variant types manually. +// Primarily used in the derive macro. +pub use composite::{Composite, CompositeField}; +pub use variant::Variant; + +fn resolve_type_and_encode< + 'resolver, + R: TypeResolver, + V: ResolvedTypeVisitor<'resolver, TypeId = R::TypeId, Value = Result<(), Error>>, +>( + types: &'resolver R, + type_id: &R::TypeId, + visitor: V, +) -> Result<(), Error> { + match types.resolve_type(type_id, visitor) { + Ok(res) => res, + Err(e) => Err(Error::new(ErrorKind::TypeResolvingError(e.to_string()))), + } +} impl EncodeAsType for bool { - fn encode_as_type_to( + fn encode_as_type_to( &self, - type_id: u32, - types: &PortableRegistry, + type_id: &R::TypeId, + types: &R, out: &mut Vec, ) -> Result<(), Error> { let type_id = find_single_entry_with_same_repr(type_id, types); - let ty = types - .resolve(type_id) - .ok_or_else(|| Error::new(ErrorKind::TypeNotFound(type_id)))?; - if let TypeDef::Primitive(TypeDefPrimitive::Bool) = &ty.type_def { - self.encode_to(out); - Ok(()) - } else { - Err(Error::new(ErrorKind::WrongShape { + let wrong_shape_err = || { + Error::new(ErrorKind::WrongShape { actual: Kind::Bool, - expected: type_id, - })) - } + expected_id: format!("{type_id:?}"), + }) + }; + + let v = visitor::new((), |_, _| Err(wrong_shape_err())) + .visit_primitive(|_, primitive| { + if primitive == Primitive::Bool { + self.encode_to(out); + Ok(()) + } else { + Err(wrong_shape_err()) + } + }) + .visit_not_found(|_| Err(Error::new(ErrorKind::TypeNotFound(format!("{type_id:?}"))))); + + resolve_type_and_encode(types, type_id, v) } } impl EncodeAsType for str { - fn encode_as_type_to( + fn encode_as_type_to( &self, - type_id: u32, - types: &PortableRegistry, + type_id: &R::TypeId, + types: &R, out: &mut Vec, ) -> Result<(), Error> { let type_id = find_single_entry_with_same_repr(type_id, types); - let ty = types - .resolve(type_id) - .ok_or_else(|| Error::new(ErrorKind::TypeNotFound(type_id)))?; - if let TypeDef::Primitive(TypeDefPrimitive::Str) = &ty.type_def { - self.encode_to(out); - Ok(()) - } else { - Err(Error::new(ErrorKind::WrongShape { + let wrong_shape_err = || { + Error::new(ErrorKind::WrongShape { actual: Kind::Str, - expected: type_id, - })) - } + expected_id: format!("{type_id:?}"), + }) + }; + + let v = visitor::new((), |_, _| Err(wrong_shape_err())) + .visit_primitive(|_, primitive| { + if primitive == Primitive::Str { + self.encode_to(out); + Ok(()) + } else { + Err(wrong_shape_err()) + } + }) + .visit_not_found(|_| Err(Error::new(ErrorKind::TypeNotFound(format!("{type_id:?}"))))); + + resolve_type_and_encode(types, type_id, v) } } @@ -102,10 +132,10 @@ impl<'a, T> EncodeAsType for &'a T where T: EncodeAsType + ?Sized, { - fn encode_as_type_to( + fn encode_as_type_to( &self, - type_id: u32, - types: &PortableRegistry, + type_id: &R::TypeId, + types: &R, out: &mut Vec, ) -> Result<(), Error> { (*self).encode_as_type_to(type_id, types, out) @@ -116,10 +146,10 @@ impl<'a, T> EncodeAsType for alloc::borrow::Cow<'a, T> where T: 'a + EncodeAsType + ToOwned + ?Sized, { - fn encode_as_type_to( + fn encode_as_type_to( &self, - type_id: u32, - types: &PortableRegistry, + type_id: &R::TypeId, + types: &R, out: &mut Vec, ) -> Result<(), Error> { (**self).encode_as_type_to(type_id, types, out) @@ -130,10 +160,10 @@ impl EncodeAsType for [T] where T: EncodeAsType, { - fn encode_as_type_to( + fn encode_as_type_to( &self, - type_id: u32, - types: &PortableRegistry, + type_id: &R::TypeId, + types: &R, out: &mut Vec, ) -> Result<(), Error> { encode_iterable_sequence_to(self.len(), self.iter(), type_id, types, out) @@ -141,10 +171,10 @@ where } impl EncodeAsType for [T; N] { - fn encode_as_type_to( + fn encode_as_type_to( &self, - type_id: u32, - types: &PortableRegistry, + type_id: &R::TypeId, + types: &R, out: &mut Vec, ) -> Result<(), Error> { self[..].encode_as_type_to(type_id, types, out) @@ -152,10 +182,10 @@ impl EncodeAsType for [T; N] { } impl EncodeAsType for PhantomData { - fn encode_as_type_to( + fn encode_as_type_to( &self, - type_id: u32, - types: &PortableRegistry, + type_id: &R::TypeId, + types: &R, out: &mut Vec, ) -> Result<(), Error> { ().encode_as_type_to(type_id, types, out) @@ -163,45 +193,45 @@ impl EncodeAsType for PhantomData { } impl EncodeAsType for Result { - fn encode_as_type_to( + fn encode_as_type_to( &self, - type_id: u32, - types: &PortableRegistry, + type_id: &R::TypeId, + types: &R, out: &mut Vec, ) -> Result<(), Error> { match self { Ok(v) => Variant { name: "Ok", - fields: Composite([(None, v as &dyn EncodeAsType)].iter().copied()), + fields: Composite::new([(None, CompositeField::new(v))].iter().copied()), } - .encode_as_type_to(type_id, types, out), + .encode_variant_as_type_to(type_id, types, out), Err(e) => Variant { name: "Err", - fields: Composite([(None, e as &dyn EncodeAsType)].iter().copied()), + fields: Composite::new([(None, CompositeField::new(e))].iter().copied()), } - .encode_as_type_to(type_id, types, out), + .encode_variant_as_type_to(type_id, types, out), } } } impl EncodeAsType for Option { - fn encode_as_type_to( + fn encode_as_type_to( &self, - type_id: u32, - types: &PortableRegistry, + type_id: &R::TypeId, + types: &R, out: &mut Vec, ) -> Result<(), Error> { match self { Some(v) => Variant { name: "Some", - fields: Composite([(None, v as &dyn EncodeAsType)].iter().copied()), + fields: Composite::new([(None, CompositeField::new(v))].iter().copied()), } - .encode_as_type_to(type_id, types, out), + .encode_variant_as_type_to(type_id, types, out), None => Variant { name: "None", - fields: Composite([].iter().copied()), + fields: Composite::new([].iter().copied()), } - .encode_as_type_to(type_id, types, out), + .encode_variant_as_type_to(type_id, types, out), } } } @@ -210,73 +240,61 @@ impl EncodeAsType for Option { macro_rules! impl_encode_number { ($ty:ty) => { impl EncodeAsType for $ty { - fn encode_as_type_to( + fn encode_as_type_to( &self, - type_id: u32, - types: &PortableRegistry, + type_id: &R::TypeId, + types: &R, out: &mut Vec, ) -> Result<(), Error> { let type_id = find_single_entry_with_same_repr(type_id, types); - let ty = types - .resolve(type_id) - .ok_or_else(|| Error::new(ErrorKind::TypeNotFound(type_id)))?; - - fn try_num + Encode>( - num: $ty, - target_id: u32, - out: &mut Vec, - ) -> Result<(), Error> { - let n: T = num.try_into().map_err(|_| { - Error::new(ErrorKind::NumberOutOfRange { - value: num.to_string(), - expected: target_id, - }) - })?; - n.encode_to(out); - Ok(()) - } + let wrong_shape_err = || { + Error::new(ErrorKind::WrongShape { + actual: Kind::Number, + expected_id: format!("{type_id:?}"), + }) + }; - match &ty.type_def { - TypeDef::Primitive(TypeDefPrimitive::U8) => try_num::(*self, type_id, out), - TypeDef::Primitive(TypeDefPrimitive::U16) => { - try_num::(*self, type_id, out) - } - TypeDef::Primitive(TypeDefPrimitive::U32) => { - try_num::(*self, type_id, out) - } - TypeDef::Primitive(TypeDefPrimitive::U64) => { - try_num::(*self, type_id, out) - } - TypeDef::Primitive(TypeDefPrimitive::U128) => { - try_num::(*self, type_id, out) - } - TypeDef::Primitive(TypeDefPrimitive::I8) => try_num::(*self, type_id, out), - TypeDef::Primitive(TypeDefPrimitive::I16) => { - try_num::(*self, type_id, out) - } - TypeDef::Primitive(TypeDefPrimitive::I32) => { - try_num::(*self, type_id, out) - } - TypeDef::Primitive(TypeDefPrimitive::I64) => { - try_num::(*self, type_id, out) - } - TypeDef::Primitive(TypeDefPrimitive::I128) => { - try_num::(*self, type_id, out) - } - TypeDef::Compact(c) => { - let type_id = find_single_entry_with_same_repr(c.type_param.id, types); - - let ty = types - .resolve(type_id) - .ok_or_else(|| Error::new(ErrorKind::TypeNotFound(type_id)))?; + let v = visitor::new(out, |_out, _kind| Err(wrong_shape_err())) + .visit_primitive(|out, primitive| { + fn try_num + Encode>( + num: $ty, + target_id: impl core::fmt::Debug, + out: &mut Vec, + ) -> Result<(), Error> { + let n: T = num.try_into().map_err(|_| { + Error::new(ErrorKind::NumberOutOfRange { + value: num.to_string(), + expected_id: format!("{target_id:?}"), + }) + })?; + n.encode_to(out); + Ok(()) + } + + match primitive { + Primitive::U8 => try_num::(*self, type_id, out), + Primitive::U16 => try_num::(*self, type_id, out), + Primitive::U32 => try_num::(*self, type_id, out), + Primitive::U64 => try_num::(*self, type_id, out), + Primitive::U128 => try_num::(*self, type_id, out), + Primitive::I8 => try_num::(*self, type_id, out), + Primitive::I16 => try_num::(*self, type_id, out), + Primitive::I32 => try_num::(*self, type_id, out), + Primitive::I64 => try_num::(*self, type_id, out), + Primitive::I128 => try_num::(*self, type_id, out), + _ => Err(wrong_shape_err()), + } + }) + .visit_compact(|out, type_id| { + let type_id = find_single_entry_with_same_repr(type_id, types); macro_rules! try_compact_num { ($num:expr, $target_kind:expr, $out:expr, $type:ty) => {{ let n: $type = $num.try_into().map_err(|_| { Error::new(ErrorKind::NumberOutOfRange { value: $num.to_string(), - expected: type_id, + expected_id: format!("{type_id:?}"), }) })?; Compact(n).encode_to($out); @@ -284,33 +302,34 @@ macro_rules! impl_encode_number { }}; } - match ty.type_def { - TypeDef::Primitive(TypeDefPrimitive::U8) => { - try_compact_num!(*self, NumericKind::U8, out, u8) - } - TypeDef::Primitive(TypeDefPrimitive::U16) => { - try_compact_num!(*self, NumericKind::U16, out, u16) - } - TypeDef::Primitive(TypeDefPrimitive::U32) => { - try_compact_num!(*self, NumericKind::U32, out, u32) - } - TypeDef::Primitive(TypeDefPrimitive::U64) => { - try_compact_num!(*self, NumericKind::U64, out, u64) - } - TypeDef::Primitive(TypeDefPrimitive::U128) => { - try_compact_num!(*self, NumericKind::U128, out, u128) - } - _ => Err(Error::new(ErrorKind::WrongShape { - actual: Kind::Number, - expected: type_id, - })), - } - } - _ => Err(Error::new(ErrorKind::WrongShape { - actual: Kind::Number, - expected: type_id, - })), - } + let v = visitor::new(out, |_, _| Err(wrong_shape_err())).visit_primitive( + |out, primitive| match primitive { + Primitive::U8 => { + try_compact_num!(*self, NumericKind::U8, out, u8) + } + Primitive::U16 => { + try_compact_num!(*self, NumericKind::U16, out, u16) + } + Primitive::U32 => { + try_compact_num!(*self, NumericKind::U32, out, u32) + } + Primitive::U64 => { + try_compact_num!(*self, NumericKind::U64, out, u64) + } + Primitive::U128 => { + try_compact_num!(*self, NumericKind::U128, out, u128) + } + _ => Err(wrong_shape_err()), + }, + ); + + resolve_type_and_encode(types, type_id, v) + }) + .visit_not_found(|_out| { + Err(Error::new(ErrorKind::TypeNotFound(format!("{type_id:?}")))) + }); + + resolve_type_and_encode(types, type_id, v) } } }; @@ -332,13 +351,13 @@ impl_encode_number!(isize); macro_rules! impl_encode_tuple { ($($name:ident: $t:ident),*) => { impl < $($t),* > EncodeAsType for ($($t,)*) where $($t: EncodeAsType),* { - fn encode_as_type_to(&self, type_id: u32, types: &PortableRegistry, out: &mut Vec) -> Result<(), Error> { + fn encode_as_type_to(&self, type_id: &Resolver::TypeId, types: &Resolver, out: &mut Vec) -> Result<(), Error> { let ($($name,)*) = self; - Composite([ + Composite::new([ $( - (None as Option<&'static str>, $name as &dyn EncodeAsType) + (None as Option<&'static str>, CompositeField::new($name)) ,)* - ].iter().copied()).encode_as_type_to(type_id, types, out) + ].iter().copied()).encode_composite_as_type_to(type_id, types, out) } } } @@ -374,7 +393,12 @@ macro_rules! impl_encode_seq_via_iterator { impl $(< $($param),+ >)? EncodeAsType for $ty $(< $($param),+ >)? where $( $($param: EncodeAsType),+ )? { - fn encode_as_type_to(&self, type_id: u32, types: &PortableRegistry, out: &mut Vec) -> Result<(), Error> { + fn encode_as_type_to( + &self, + type_id: &R::TypeId, + types: &R, + out: &mut Vec, + ) -> Result<(), Error> { encode_iterable_sequence_to(self.len(), self.iter(), type_id, types, out) } } @@ -387,39 +411,41 @@ impl_encode_seq_via_iterator!(VecDeque[V]); impl_encode_seq_via_iterator!(Vec[V]); impl, V: EncodeAsType> EncodeAsType for BTreeMap { - fn encode_as_type_to( + fn encode_as_type_to( &self, - type_id: u32, - types: &PortableRegistry, + type_id: &R::TypeId, + types: &R, out: &mut Vec, ) -> Result<(), Error> { - let ty = types - .resolve(type_id) - .ok_or_else(|| Error::new(ErrorKind::TypeNotFound(type_id)))?; - - if matches!(ty.type_def, TypeDef::Array(_) | TypeDef::Sequence(_)) { - encode_iterable_sequence_to(self.len(), self.values(), type_id, types, out) - } else { - Composite( + let v = visitor::new(out, |out, _| { + Composite::new( self.iter() - .map(|(k, v)| (Some(k.as_ref()), v as &dyn EncodeAsType)), + .map(|(k, v)| (Some(k.as_ref()), CompositeField::new(v))), ) - .encode_as_type_to(type_id, types, out) - } + .encode_composite_as_type_to(type_id, types, out) + }) + .visit_array(|out, _, _| { + encode_iterable_sequence_to(self.len(), self.values(), type_id, types, out) + }) + .visit_sequence(|out, _| { + encode_iterable_sequence_to(self.len(), self.values(), type_id, types, out) + }); + + resolve_type_and_encode(types, type_id, v) } } impl, V: EncodeAsType> EncodeAsFields for BTreeMap { - fn encode_as_fields_to( + fn encode_as_fields_to( &self, - fields: &mut dyn FieldIter<'_>, - types: &PortableRegistry, + fields: &mut dyn FieldIter<'_, R::TypeId>, + types: &R, out: &mut Vec, ) -> Result<(), Error> { - Composite( + Composite::new( self.iter() - .map(|(k, v)| (Some(k.as_ref()), v as &dyn EncodeAsType)), + .map(|(k, v)| (Some(k.as_ref()), CompositeField::new(v))), ) - .encode_as_fields_to(fields, types, out) + .encode_composite_fields_to(fields, types, out) } } @@ -428,7 +454,12 @@ impl, V: EncodeAsType> EncodeAsFields for BTreeMap { macro_rules! impl_encode_like { ($ty:ident $(<$( $param:ident ),+>)? as $delegate_ty:ty where |$val:ident| $expr:expr) => { impl $(< $($param: EncodeAsType),+ >)? EncodeAsType for $ty $(<$( $param ),+>)? { - fn encode_as_type_to(&self, type_id: u32, types: &PortableRegistry, out: &mut Vec) -> Result<(), Error> { + fn encode_as_type_to( + &self, + type_id: &R::TypeId, + types: &R, + out: &mut Vec, + ) -> Result<(), Error> { let delegate: $delegate_ty = { let $val = self; $expr @@ -461,75 +492,93 @@ impl_encode_like!(Compact as &T where |val| &val.0); // Attempt to recurse into some type, returning the innermost type found that has an identical // SCALE encoded representation to the given type. For instance, `(T,)` encodes identically to // `T`, as does `Mytype { inner: T }` or `[T; 1]`. -fn find_single_entry_with_same_repr(type_id: u32, types: &PortableRegistry) -> u32 { - let Some(ty) = types.resolve(type_id) else { - return type_id - }; - match &ty.type_def { - TypeDef::Tuple(tuple) if tuple.fields.len() == 1 => { - find_single_entry_with_same_repr(tuple.fields[0].id, types) - } - TypeDef::Composite(composite) if composite.fields.len() == 1 => { - find_single_entry_with_same_repr(composite.fields[0].ty.id, types) - } - _ => type_id, - } +fn find_single_entry_with_same_repr<'a, R: TypeResolver>( + type_id: &'a R::TypeId, + types: &'a R, +) -> &'a R::TypeId { + let v = visitor::new((), |_, _| type_id) + .visit_tuple(|_, fields| { + let Some(new_type_id) = fields.next() else { + return type_id; + }; + if fields.next().is_some() { + return type_id; + } + find_single_entry_with_same_repr(new_type_id, types) + }) + .visit_composite(|_, fields| { + let Some(field) = fields.next() else { + return type_id; + }; + if fields.next().is_some() { + return type_id; + } + find_single_entry_with_same_repr(field.id, types) + }); + + types.resolve_type(type_id, v).unwrap_or(type_id) } // Encode some iterator of items to the type provided. -fn encode_iterable_sequence_to( +fn encode_iterable_sequence_to( len: usize, it: I, - type_id: u32, - types: &PortableRegistry, + type_id: &R::TypeId, + types: &R, out: &mut Vec, ) -> Result<(), Error> where I: Iterator, I::Item: EncodeAsType, + R: TypeResolver, { - let ty = types - .resolve(type_id) - .ok_or_else(|| Error::new(ErrorKind::TypeNotFound(type_id)))?; + let wrong_shape_err = || { + Error::new(ErrorKind::WrongShape { + actual: Kind::Array, + expected_id: format!("{type_id:?}"), + }) + }; - match &ty.type_def { - TypeDef::Array(arr) => { - if arr.len == len as u32 { + let v = visitor::new((it, out), |_, _| Err(wrong_shape_err())) + .visit_array(|(it, out), inner_ty_id, array_len| { + if array_len == len { for (idx, item) in it.enumerate() { - item.encode_as_type_to(arr.type_param.id, types, out) + item.encode_as_type_to(inner_ty_id, types, out) .map_err(|e| e.at_idx(idx))?; } Ok(()) } else { Err(Error::new(ErrorKind::WrongLength { actual_len: len, - expected_len: arr.len as usize, + expected_len: array_len, })) } - } - TypeDef::Sequence(seq) => { + }) + .visit_sequence(|(it, out), inner_ty_id| { // Sequences are prefixed with their compact encoded length: Compact(len as u32).encode_to(out); for (idx, item) in it.enumerate() { - item.encode_as_type_to(seq.type_param.id, types, out) + item.encode_as_type_to(inner_ty_id, types, out) .map_err(|e| e.at_idx(idx))?; } Ok(()) - } - // if the target type is a basic newtype wrapper, then dig into that and try encoding to - // the thing inside it. This is fairly common, and allowing this means that users don't have - // to wrap things needlessly just to make types line up. - TypeDef::Tuple(tup) if tup.fields.len() == 1 => { - encode_iterable_sequence_to(len, it, tup.fields[0].id, types, out) - } - TypeDef::Composite(com) if com.fields.len() == 1 => { - encode_iterable_sequence_to(len, it, com.fields[0].ty.id, types, out) - } - _ => Err(Error::new(ErrorKind::WrongShape { - actual: Kind::Array, - expected: type_id, - })), - } + }) + .visit_tuple(|(it, out), inner_type_ids| { + if inner_type_ids.len() == 1 { + encode_iterable_sequence_to(len, it, inner_type_ids.next().unwrap(), types, out) + } else { + Err(wrong_shape_err()) + } + }) + .visit_composite(|(it, out), fields| { + if fields.len() == 1 { + encode_iterable_sequence_to(len, it, fields.next().unwrap().id, types, out) + } else { + Err(wrong_shape_err()) + } + }); + + resolve_type_and_encode(types, type_id, v) } #[cfg(all(feature = "derive", feature = "bits", feature = "primitive-types"))] @@ -540,7 +589,7 @@ mod test { use alloc::vec; use codec::Decode; use core::fmt::Debug; - use scale_info::TypeInfo; + use scale_info::{PortableRegistry, TypeInfo}; /// Given a type definition, return type ID and registry representing it. fn make_type() -> (u32, PortableRegistry) { @@ -554,7 +603,7 @@ mod test { fn encode_type(value: V) -> Result, Error> { let (type_id, types) = make_type::(); - let bytes = value.encode_as_type(type_id, &types)?; + let bytes = value.encode_as_type(&type_id, &types)?; Ok(bytes) } @@ -603,11 +652,11 @@ mod test { let mut fields = c .fields .iter() - .map(|f| Field::new(f.ty.id, f.name.as_deref())); + .map(|f| Field::new(&f.ty.id, f.name.as_deref())); value.encode_as_fields(&mut fields, &types).unwrap() } scale_info::TypeDef::Tuple(t) => { - let mut fields = t.fields.iter().map(|f| Field::unnamed(f.id)); + let mut fields = t.fields.iter().map(|f| Field::unnamed(&f.id)); value.encode_as_fields(&mut fields, &types).unwrap() } _ => { @@ -682,7 +731,7 @@ mod test { let (type_id, types) = make_type::>(); let e = vec![1u8, 2, 3].encode(); let e2 = vec![1u8, 2, 3] - .encode_as_type(type_id, &types) + .encode_as_type(&type_id, &types) .expect("can encode 2"); assert_eq!(e, e2); } @@ -739,26 +788,16 @@ mod test { #[derive(Debug, scale_info::TypeInfo, codec::Decode, PartialEq)] struct Foo { a: u8, - b: (bool,), - c: String, + b: u16, + c: u32, } - let v = BTreeMap::from([ - ("a", &1u8 as &dyn EncodeAsType), - ("c", &"hello" as &dyn EncodeAsType), - ("b", &true as &dyn EncodeAsType), - ]); + let v = BTreeMap::from([("a", 1), ("c", 2), ("b", 3)]); // BTreeMap can go to a key-val composite, or unnamed: - assert_value_roundtrips_to( - v.clone(), - Foo { - a: 1, - b: (true,), - c: "hello".to_string(), - }, - ); - assert_value_roundtrips_to(v, (1, true, "hello".to_string())); + assert_value_roundtrips_to(v.clone(), Foo { a: 1, b: 3, c: 2 }); + // BTreeMaps are iterated in order of key: + assert_value_roundtrips_to(v, (1, 3, 2)); } #[test] @@ -841,12 +880,17 @@ mod test { } // note: fields do not need to be in order when named: - let vals = [ - (Some("hello"), &("world".to_string()) as &dyn EncodeAsType), - (Some("bar"), &12345u128 as &dyn EncodeAsType), - (Some("wibble"), &true as &dyn EncodeAsType), + let source_vals = [ + (Some("hello"), CompositeField::new(&"world")), + (Some("bar"), CompositeField::new(&12345u128)), + (Some("wibble"), CompositeField::new(&true)), ]; - let source = Composite(vals.iter().copied()); + let source = Composite::new(source_vals.iter().copied()); + + // Composite can't implement `EncodeAsType` and so need "manually" encoding: + let (type_id, types) = make_type::(); + let bytes = source.encode_composite_as_type(&type_id, &types).unwrap(); + let cursor = &mut &*bytes; let target = Foo { bar: 12345, @@ -854,33 +898,45 @@ mod test { hello: "world".to_string(), }; - assert_value_roundtrips_to(source, target); + let new_target = Foo::decode(cursor).unwrap(); + + assert_eq!(target, new_target); + assert_eq!(cursor.len(), 0); } #[test] fn tuple_composite_can_encode_to_unnamed_structs() { #[derive(Debug, scale_info::TypeInfo, codec::Decode, PartialEq, Clone)] struct Foo(u32, bool, String); + let (type_id, types) = make_type::(); // note: unnamed target so fields need to be in order (can be named or not) - let named_vals = [ - (Some("bar"), &12345u128 as &dyn EncodeAsType), - (Some("wibble"), &true as &dyn EncodeAsType), - (Some("hello"), &"world".to_string() as &dyn EncodeAsType), + let source_vals = [ + (Some("bar"), CompositeField::new(&12345u128)), + (Some("wibble"), CompositeField::new(&true)), + (Some("hello"), CompositeField::new(&"world")), ]; - let source = Composite(named_vals.iter().copied()); - - let unnamed_vals = [ - (None, &12345u128 as &dyn EncodeAsType), - (None, &true as &dyn EncodeAsType), - (None, &"world".to_string() as &dyn EncodeAsType), + let source = Composite::new(source_vals.iter().copied()); + let source_bytes = source.encode_composite_as_type(&type_id, &types).unwrap(); + let source_cursor = &mut &*source_bytes; + + let source2_vals = [ + (None, CompositeField::new(&12345u128)), + (None, CompositeField::new(&true)), + (None, CompositeField::new(&"world")), ]; - let source2 = Composite(unnamed_vals.iter().copied()); + let source2 = Composite::new(source2_vals.iter().copied()); + let source2_bytes = source2.encode_composite_as_type(&type_id, &types).unwrap(); + let source2_cursor = &mut &*source2_bytes; let target = Foo(12345, true, "world".to_string()); + let new_target = Foo::decode(source_cursor).unwrap(); + let new_target2 = Foo::decode(source2_cursor).unwrap(); - assert_value_roundtrips_to(source, target.clone()); - assert_value_roundtrips_to(source2, target); + assert_eq!(target, new_target); + assert_eq!(target, new_target2); + assert_eq!(source_cursor.len(), 0); + assert_eq!(source2_cursor.len(), 0); } #[test] @@ -893,15 +949,18 @@ mod test { } // note: fields do not need to be in order when named: - let vals = [ - (Some("hello"), &"world".to_string() as &dyn EncodeAsType), - (Some("bar"), &12345u128 as &dyn EncodeAsType), + let source_vals = [ + (Some("hello"), CompositeField::new(&"world")), + (Some("bar"), CompositeField::new(&12345u128)), // wrong name: - (Some("wibbles"), &true as &dyn EncodeAsType), + (Some("wibbles"), CompositeField::new(&true)), ]; - let source = Composite(vals.iter().copied()); + let source = Composite::new(source_vals.iter().copied()); - encode_type::<_, Foo>(source).unwrap_err(); + let (type_id, types) = make_type::(); + let _bytes = source + .encode_composite_as_type(&type_id, &types) + .unwrap_err(); } #[test] @@ -1006,19 +1065,19 @@ mod test { #[derive(TypeInfo, Encode)] struct Foo { some_field: u64, - another: bool, + another: u8, } assert_encodes_fields_like_type( BTreeMap::from([ - ("other1", &123u64 as &dyn EncodeAsType), - ("another", &true as &dyn EncodeAsType), - ("some_field", &123u64 as &dyn EncodeAsType), - ("other2", &123u64 as &dyn EncodeAsType), + ("other1", 1), + ("another", 2), + ("some_field", 3), + ("other2", 4), ]), Foo { - some_field: 123, - another: true, + some_field: 3, + another: 2, }, ) } diff --git a/scale-encode/src/impls/primitive_types.rs b/scale-encode/src/impls/primitive_types.rs index 2d68eeb..0f62b09 100644 --- a/scale-encode/src/impls/primitive_types.rs +++ b/scale-encode/src/impls/primitive_types.rs @@ -16,11 +16,17 @@ use crate::{error::Error, EncodeAsType}; use alloc::vec::Vec; use primitive_types::{H128, H160, H256, H384, H512, H768}; +use scale_type_resolver::TypeResolver; macro_rules! impl_encode { ($($ty:ty),*) => {$( impl EncodeAsType for $ty { - fn encode_as_type_to(&self, type_id: u32, types: &scale_info::PortableRegistry, out: &mut Vec) -> Result<(), Error> { + fn encode_as_type_to( + &self, + type_id: &R::TypeId, + types: &R, + out: &mut Vec, + ) -> Result<(), Error> { let type_id = super::find_single_entry_with_same_repr(type_id, types); self.0.encode_as_type_to(type_id, types, out) } diff --git a/scale-encode/src/impls/variant.rs b/scale-encode/src/impls/variant.rs index 4792142..533adcc 100644 --- a/scale-encode/src/impls/variant.rs +++ b/scale-encode/src/impls/variant.rs @@ -13,20 +13,20 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{ - error::{Error, ErrorKind, Kind}, - EncodeAsFields, EncodeAsType, Field, -}; -use alloc::{string::ToString, vec::Vec}; +use super::composite::{Composite, CompositeField}; +use crate::error::{Error, ErrorKind, Kind}; +use alloc::{format, string::ToString, vec::Vec}; use codec::Encode; -use scale_info::{PortableRegistry, TypeDef}; +use scale_type_resolver::{visitor, TypeResolver}; /// This type represents named or unnamed composite values, and can be used /// to help generate `EncodeAsType` impls. It's primarily used by the exported /// macros to do just that. /// /// ```rust -/// use scale_encode::{ Error, EncodeAsType, Composite, Variant, PortableRegistry }; +/// use scale_encode::{ +/// Error, EncodeAsType, Composite, CompositeField, Variant, TypeResolver +/// }; /// /// enum MyType { /// SomeField(bool), @@ -34,64 +34,86 @@ use scale_info::{PortableRegistry, TypeDef}; /// } /// /// impl EncodeAsType for MyType { -/// fn encode_as_type_to(&self, type_id: u32, types: &PortableRegistry, out: &mut Vec) -> Result<(), Error> { +/// fn encode_as_type_to( +/// &self, +/// type_id: &R::TypeId, +/// types: &R, +/// out: &mut Vec +/// ) -> Result<(), Error> { /// match self { /// MyType::SomeField(b) => Variant { /// name: "SomeField", -/// fields: Composite([ -/// (None, b as &dyn EncodeAsType), +/// fields: Composite::new([ +/// (None, CompositeField::new(b)), /// ].into_iter()) -/// }.encode_as_type_to(type_id, types, out), +/// }.encode_variant_as_type_to(type_id, types, out), /// MyType::OtherField { foo, bar } => Variant { /// name: "OtherField", -/// fields: Composite([ -/// (Some("foo"), foo as &dyn EncodeAsType), -/// (Some("bar"), bar as &dyn EncodeAsType) +/// fields: Composite::new([ +/// (Some("foo"), CompositeField::new(foo)), +/// (Some("bar"), CompositeField::new(bar)) /// ].into_iter()) -/// }.encode_as_type_to(type_id, types, out) +/// }.encode_variant_as_type_to(type_id, types, out) /// } /// } /// } /// ``` -pub struct Variant<'a, Vals> { +pub struct Variant<'a, R, Vals> { /// The name of the variant we'll try to encode into. pub name: &'a str, /// The fields of the variant that we wish to encode. - pub fields: super::composite::Composite, + pub fields: Composite, } -impl<'a, Vals> EncodeAsType for Variant<'a, Vals> +impl<'a, R, Vals> Variant<'a, R, Vals> where - Vals: ExactSizeIterator, &'a dyn EncodeAsType)> + Clone, + R: TypeResolver + 'a, + Vals: ExactSizeIterator, CompositeField<'a, R>)> + Clone, { - fn encode_as_type_to( + /// A shortcut for [`Self::encode_variant_as_type_to()`] which internally + /// allocates a [`Vec`] and returns it. + pub fn encode_variant_as_type(&self, type_id: &R::TypeId, types: &R) -> Result, Error> { + let mut out = Vec::new(); + self.encode_variant_as_type_to(type_id, types, &mut out)?; + Ok(out) + } + + /// Encode the variant as the provided type to the output bytes. + pub fn encode_variant_as_type_to( &self, - type_id: u32, - types: &PortableRegistry, + type_id: &R::TypeId, + types: &R, out: &mut Vec, ) -> Result<(), Error> { let type_id = super::find_single_entry_with_same_repr(type_id, types); - let ty = types - .resolve(type_id) - .ok_or_else(|| Error::new(ErrorKind::TypeNotFound(type_id)))?; - match &ty.type_def { - TypeDef::Variant(var) => { - let vars = &var.variants; - let Some(v) = vars.iter().find(|v| v.name == self.name) else { - return Err(Error::new(ErrorKind::CannotFindVariant { name: self.name.to_string(), expected: type_id })); - }; - v.index.encode_to(out); - let mut fields = v - .fields - .iter() - .map(|f| Field::new(f.ty.id, f.name.as_deref())); - self.fields.encode_as_fields_to(&mut fields, types, out) - } - _ => Err(Error::new(ErrorKind::WrongShape { + let v = visitor::new((), |_, _| { + Err(Error::new(ErrorKind::WrongShape { actual: Kind::Str, - expected: type_id, - })), - } + expected_id: format!("{type_id:?}"), + })) + }) + .visit_variant(|_, vars| { + let mut res = None; + for var in vars { + if var.name == self.name { + res = Some(var); + break; + } + } + + let Some(mut var) = res else { + return Err(Error::new(ErrorKind::CannotFindVariant { + name: self.name.to_string(), + expected_id: format!("{type_id:?}"), + })); + }; + + var.index.encode_to(out); + self.fields + .encode_composite_fields_to(&mut var.fields, types, out) + }); + + super::resolve_type_and_encode(types, type_id, v) } } diff --git a/scale-encode/src/lib.rs b/scale-encode/src/lib.rs index 86450b5..445ddd7 100644 --- a/scale-encode/src/lib.rs +++ b/scale-encode/src/lib.rs @@ -17,8 +17,8 @@ /*! `parity-scale-codec` provides an `Encode` trait which allows types to SCALE encode themselves based on their shape. -This crate builds on this, and allows types to encode themselves based on [`scale_info`] type information. It -exposes two traits: +This crate builds on this, and allows types to encode themselves based on type information from a [`TypeResolver`] +implementation (one such implementation being a `scale_info::PortableRegistry`). It exposes two traits: - An [`EncodeAsType`] trait which when implemented on some type, describes how it can be SCALE encoded with the help of a type ID and type registry describing the expected shape of the encoded bytes. @@ -47,7 +47,7 @@ fn get_type_info() -> (u32, PortableRegistry) { let mut types = scale_info::Registry::new(); let ty = types.register_type(&m); let portable_registry: PortableRegistry = types.into(); - (ty.id(), portable_registry) + (ty.id, portable_registry) } // Encode the left value via EncodeAsType into the shape of the right value. @@ -59,7 +59,7 @@ where B: TypeInfo + Encode + 'static, { let (type_id, types) = get_type_info::(); - let a_bytes = a.encode_as_type(type_id, &types).unwrap(); + let a_bytes = a.encode_as_type(&type_id, &types).unwrap(); let b_bytes = b.encode(); assert_eq!(a_bytes, b_bytes); } @@ -154,8 +154,8 @@ pub use alloc::vec::Vec; pub use error::Error; // Useful types to help implement EncodeAsType/Fields with: -pub use crate::impls::{Composite, Variant}; -pub use scale_info::PortableRegistry; +pub use crate::impls::{Composite, CompositeField, Variant}; +pub use scale_type_resolver::{Field, FieldIter, TypeResolver}; /// Re-exports of external crates. pub mod ext { @@ -164,20 +164,24 @@ pub mod ext { } /// This trait signals that some static type can possibly be SCALE encoded given some -/// `type_id` and [`PortableRegistry`] which dictates the expected encoding. +/// `type_id` and a corresponding [`TypeResolver`] which tells us about the expected encoding. pub trait EncodeAsType { /// Given some `type_id`, `types`, a `context` and some output target for the SCALE encoded bytes, /// attempt to SCALE encode the current value into the type given by `type_id`. - fn encode_as_type_to( + fn encode_as_type_to( &self, - type_id: u32, - types: &PortableRegistry, + type_id: &R::TypeId, + types: &R, out: &mut Vec, ) -> Result<(), Error>; /// This is a helper function which internally calls [`EncodeAsType::encode_as_type_to`]. Prefer to /// implement that instead. - fn encode_as_type(&self, type_id: u32, types: &PortableRegistry) -> Result, Error> { + fn encode_as_type( + &self, + type_id: &R::TypeId, + types: &R, + ) -> Result, Error> { let mut out = Vec::new(); self.encode_as_type_to(type_id, types, &mut out)?; Ok(out) @@ -189,19 +193,19 @@ pub trait EncodeAsType { /// tuple and struct types, and is automatically implemented via the [`macro@EncodeAsType`] macro. pub trait EncodeAsFields { /// Given some fields describing the shape of a type, attempt to encode to that shape. - fn encode_as_fields_to( + fn encode_as_fields_to( &self, - fields: &mut dyn FieldIter<'_>, - types: &PortableRegistry, + fields: &mut dyn FieldIter<'_, R::TypeId>, + types: &R, out: &mut Vec, ) -> Result<(), Error>; /// This is a helper function which internally calls [`EncodeAsFields::encode_as_fields_to`]. Prefer to /// implement that instead. - fn encode_as_fields( + fn encode_as_fields( &self, - fields: &mut dyn FieldIter<'_>, - types: &PortableRegistry, + fields: &mut dyn FieldIter<'_, R::TypeId>, + types: &R, ) -> Result, Error> { let mut out = Vec::new(); self.encode_as_fields_to(fields, types, &mut out)?; @@ -209,43 +213,6 @@ pub trait EncodeAsFields { } } -/// A representation of a single field to be encoded via [`EncodeAsFields::encode_as_fields_to`]. -#[derive(Debug, Clone, Copy)] -pub struct Field<'a> { - name: Option<&'a str>, - id: u32, -} - -impl<'a> Field<'a> { - /// Construct a new field with an ID and optional name. - pub fn new(id: u32, name: Option<&'a str>) -> Self { - Field { id, name } - } - /// Create a new unnamed field. - pub fn unnamed(id: u32) -> Self { - Field { name: None, id } - } - /// Create a new named field. - pub fn named(id: u32, name: &'a str) -> Self { - Field { - name: Some(name), - id, - } - } - /// The field name, if any. - pub fn name(&self) -> Option<&'a str> { - self.name - } - /// The field ID. - pub fn id(&self) -> u32 { - self.id - } -} - -/// An iterator over a set of fields. -pub trait FieldIter<'a>: Iterator> {} -impl<'a, T> FieldIter<'a> for T where T: Iterator> {} - /// The `EncodeAsType` derive macro can be used to implement `EncodeAsType` /// on structs and enums whose fields all implement `EncodeAsType`. /// @@ -315,16 +282,3 @@ impl<'a, T> FieldIter<'a> for T where T: Iterator> {} /// behaviour and provide your own trait bounds instead using this option. #[cfg(feature = "derive")] pub use scale_encode_derive::EncodeAsType; - -#[cfg(test)] -mod test { - use super::*; - use alloc::boxed::Box; - - // Confirm object safety of EncodeAsFields; we want this. - // (doesn't really need to run; compile time only.) - #[test] - fn is_object_safe() { - fn _foo(_input: Box) {} - } -}