From 19b53b11d80c2ed6a4e3d05d7c5695515a8e7e82 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Tue, 13 Feb 2024 18:23:43 +0000 Subject: [PATCH 1/8] WIP use TypeResolver --- .gitignore | 1 + Cargo.toml | 4 + scale-encode-derive/src/lib.rs | 34 +- scale-encode/Cargo.toml | 3 +- scale-encode/src/error/mod.rs | 25 +- scale-encode/src/impls/bits.rs | 31 +- scale-encode/src/impls/composite.rs | 225 +++++++--- scale-encode/src/impls/mod.rs | 519 ++++++++++++---------- scale-encode/src/impls/primitive_types.rs | 8 +- scale-encode/src/impls/variant.rs | 71 +-- scale-encode/src/lib.rs | 77 +--- 11 files changed, 553 insertions(+), 445 deletions(-) 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..2bd9456 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" @@ -18,3 +19,6 @@ include = ["Cargo.toml", "src/**/*.rs", "README.md", "LICENSE"] [workspace.dependencies] scale-encode = { version = "0.5.0", path = "scale-encode" } scale-encode-derive = { version = "0.5.0", path = "scale-encode-derive" } + +[patch.crates-io] +scale-type-resolver = { path = "../scale-type-resolver" } \ No newline at end of file diff --git a/scale-encode-derive/src/lib.rs b/scale-encode-derive/src/lib.rs index 2f4d9a2..b1f3445 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_as_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..e9ea651 100644 --- a/scale-encode/Cargo.toml +++ b/scale-encode/Cargo.toml @@ -31,7 +31,8 @@ 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 = "0.1.0" +scale-bits = { version = "0.5.0", default-features = false, features = ["scale-info"], optional = true } scale-encode-derive = { workspace = true, optional = true } primitive-types = { version = "0.12.0", optional = true, default-features = false } smallvec = "1.10.0" diff --git a/scale-encode/src/error/mod.rs b/scale-encode/src/error/mod.rs index 715ff4c..8c41e0d 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,20 @@ 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..67089c1 100644 --- a/scale-encode/src/impls/bits.rs +++ b/scale-encode/src/impls/bits.rs @@ -17,37 +17,32 @@ use crate::{ error::{Error, ErrorKind, Kind}, EncodeAsType, }; +use scale_type_resolver::{TypeResolver, visitor}; use alloc::vec::Vec; -use scale_info::TypeDef; 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..0d78f6c 100644 --- a/scale-encode/src/impls/composite.rs +++ b/scale-encode/src/impls/composite.rs @@ -15,11 +15,67 @@ 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 scale_type_resolver::visitor; + +/// 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: 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 @@ -44,47 +100,69 @@ use scale_info::{PortableRegistry, TypeDef}; /// } /// } /// ``` -pub struct Composite(pub Vals); +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 }; + /// + /// 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 } + } + + /// 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. + 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() .unwrap() .1 - .encode_as_type_to(type_id, types, out); + .encode_composite_field_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) - } - // If we see a composite type, it has either named fields or !=1 unnamed fields. - TypeDef::Composite(composite) => { + // 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_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()); @@ -96,50 +174,37 @@ where .next() .unwrap() .1 - .encode_as_type_to(type_id, types, out); + .encode_composite_field_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) - } - // 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. + 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_as_type_to(type_id, types, out); + .encode_composite_field_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, - })) - } - } + let mut fields = type_ids.map(|id| Field::unnamed(id)); + 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( + /// 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 +212,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 +222,20 @@ 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() })) }; // 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,7 +253,7 @@ 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| { + 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 { @@ -204,19 +269,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 + }; + let None = fields.next() else { + return type_id + }; + if 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(type_id) = type_ids.next() else { + return type_id + }; + let None = type_ids.next() else { + return type_id + }; + skip_through_single_unnamed_fields(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..bdbb4fa 100644 --- a/scale-encode/src/impls/mod.rs +++ b/scale-encode/src/impls/mod.rs @@ -22,13 +22,14 @@ 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 composite::{ Composite, CompositeField }; pub use variant::Variant; use crate::{ error::{Error, ErrorKind, Kind}, - EncodeAsFields, EncodeAsType, FieldIter, + EncodeAsFields, EncodeAsType, }; +use scale_type_resolver::{ visitor, FieldIter, Primitive, ResolvedTypeVisitor, TypeResolver }; use alloc::{ borrow::ToOwned, boxed::Box, @@ -48,53 +49,77 @@ use core::{ ops::{Range, RangeInclusive}, time::Duration, }; -use scale_info::{PortableRegistry, TypeDef, TypeDefPrimitive}; + +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 +127,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 +141,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 +155,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 +166,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 +177,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 +188,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 +235,77 @@ 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 +313,35 @@ 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 +363,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 +405,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 +423,42 @@ 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( - self.iter() - .map(|(k, v)| (Some(k.as_ref()), v as &dyn EncodeAsType)), - ) - .encode_as_type_to(type_id, types, out) - } + let v = visitor + ::new(out,|out,_| { + Composite::new( + self.iter() + .map(|(k, v)| (Some(k.as_ref()), CompositeField::new(v))), + ) + .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 +467,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 +505,90 @@ 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<'resolver, R: TypeResolver>(type_id: &'resolver R::TypeId, types: &'resolver R) -> &'resolver R::TypeId { + let v = visitor::new((),|_,_| type_id) + .visit_tuple(|_,fields| { + let Some(field_ty_id) = fields.next() else { + return type_id + }; + if fields.next().is_some() { + return type_id + } + field_ty_id + }) + .visit_composite(|_,fields| { + let Some(field) = fields.next() else { + return type_id + }; + if fields.next().is_some() { + return type_id + } + field.id + }); + + 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 +599,7 @@ mod test { use alloc::vec; use codec::Decode; use core::fmt::Debug; - use scale_info::TypeInfo; + use scale_info::{ TypeInfo, PortableRegistry }; /// Given a type definition, return type ID and registry representing it. fn make_type() -> (u32, PortableRegistry) { @@ -554,7 +613,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 +662,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 +741,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); } @@ -846,7 +905,7 @@ mod test { (Some("bar"), &12345u128 as &dyn EncodeAsType), (Some("wibble"), &true as &dyn EncodeAsType), ]; - let source = Composite(vals.iter().copied()); + let source = Composite::new(vals.iter().copied()); let target = Foo { bar: 12345, @@ -864,18 +923,18 @@ mod test { // 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), + (Some("bar"), CompositeField::new(&12345u128)), + (Some("wibble"), CompositeField::new(&true)), + (Some("hello"), CompositeField::new(&"world".to_string())), ]; - let source = Composite(named_vals.iter().copied()); + let source = Composite::new(named_vals.iter().copied()); let unnamed_vals = [ - (None, &12345u128 as &dyn EncodeAsType), - (None, &true as &dyn EncodeAsType), - (None, &"world".to_string() as &dyn EncodeAsType), + (None, CompositeField::new(&12345u128)), + (None, CompositeField::new(&true)), + (None, CompositeField::new(&"world".to_string())), ]; - let source2 = Composite(unnamed_vals.iter().copied()); + let source2 = Composite::new(unnamed_vals.iter().copied()); let target = Foo(12345, true, "world".to_string()); @@ -894,12 +953,12 @@ 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("hello"), CompositeField::new(&"world".to_string())), + (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(vals.iter().copied()); encode_type::<_, Foo>(source).unwrap_err(); } 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..266fd63 100644 --- a/scale-encode/src/impls/variant.rs +++ b/scale-encode/src/impls/variant.rs @@ -13,13 +13,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{ - error::{Error, ErrorKind, Kind}, - EncodeAsFields, EncodeAsType, Field, -}; +use crate::error::{Error, ErrorKind, Kind}; use alloc::{string::ToString, vec::Vec}; use codec::Encode; -use scale_info::{PortableRegistry, TypeDef}; +use scale_type_resolver::{TypeResolver,visitor}; +use super::composite::{ Composite, CompositeField }; /// This type represents named or unnamed composite values, and can be used /// to help generate `EncodeAsType` impls. It's primarily used by the exported @@ -53,45 +51,54 @@ use scale_info::{PortableRegistry, TypeDef}; /// } /// } /// ``` -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( + /// 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 })); + let v = visitor + ::new((),|_,_| { + Err(Error::new(ErrorKind::WrongShape { + actual: Kind::Str, + 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:?}") + })); }; - 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 { - actual: Kind::Str, - expected: 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..3826750 100644 --- a/scale-encode/src/lib.rs +++ b/scale-encode/src/lib.rs @@ -152,9 +152,10 @@ pub mod error; pub use alloc::vec::Vec; pub use error::Error; +pub use scale_type_resolver::{ TypeResolver, FieldIter, Field }; // Useful types to help implement EncodeAsType/Fields with: -pub use crate::impls::{Composite, Variant}; +pub use crate::impls::{Composite, CompositeField, Variant}; pub use scale_info::PortableRegistry; /// Re-exports of external crates. @@ -168,16 +169,20 @@ pub mod ext { 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 +194,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 +214,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 +283,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) {} - } -} From 6c4b4eeb3d414ed2f731bec43d78ff92fdc30ec1 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Wed, 14 Feb 2024 16:52:08 +0000 Subject: [PATCH 2/8] Finish updating to use TypeResolver (minus dep patch) --- scale-encode-derive/src/lib.rs | 2 +- scale-encode/Cargo.toml | 1 - scale-encode/src/error/mod.rs | 4 +- scale-encode/src/impls/bits.rs | 9 +- scale-encode/src/impls/composite.rs | 239 ++++++++++++++--------- scale-encode/src/impls/mod.rs | 289 ++++++++++++++-------------- scale-encode/src/impls/variant.rs | 83 ++++---- scale-encode/src/lib.rs | 9 +- 8 files changed, 350 insertions(+), 286 deletions(-) diff --git a/scale-encode-derive/src/lib.rs b/scale-encode-derive/src/lib.rs index b1f3445..8c8eae6 100644 --- a/scale-encode-derive/src/lib.rs +++ b/scale-encode-derive/src/lib.rs @@ -137,7 +137,7 @@ fn generate_struct_impl( __encode_as_type_out: &mut #path_to_scale_encode::Vec ) -> Result<(), #path_to_scale_encode::Error> { let #path_to_type #matcher = self; - #composite.encode_composite_as_fields_to( + #composite.encode_composite_fields_to( __encode_as_type_fields, __encode_as_type_types, __encode_as_type_out diff --git a/scale-encode/Cargo.toml b/scale-encode/Cargo.toml index e9ea651..6569948 100644 --- a/scale-encode/Cargo.toml +++ b/scale-encode/Cargo.toml @@ -29,7 +29,6 @@ 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-type-resolver = "0.1.0" scale-bits = { version = "0.5.0", default-features = false, features = ["scale-info"], optional = true } diff --git a/scale-encode/src/error/mod.rs b/scale-encode/src/error/mod.rs index 8c41e0d..19ca0e9 100644 --- a/scale-encode/src/error/mod.rs +++ b/scale-encode/src/error/mod.rs @@ -139,7 +139,9 @@ 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 with identifier {expected_id}")] + #[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, diff --git a/scale-encode/src/impls/bits.rs b/scale-encode/src/impls/bits.rs index 67089c1..f2f1343 100644 --- a/scale-encode/src/impls/bits.rs +++ b/scale-encode/src/impls/bits.rs @@ -17,8 +17,8 @@ use crate::{ error::{Error, ErrorKind, Kind}, EncodeAsType, }; -use scale_type_resolver::{TypeResolver, visitor}; use alloc::vec::Vec; +use scale_type_resolver::{visitor, TypeResolver}; impl EncodeAsType for scale_bits::Bits { fn encode_as_type_to( @@ -29,12 +29,13 @@ impl EncodeAsType for scale_bits::Bits { ) -> Result<(), crate::Error> { let type_id = super::find_single_entry_with_same_repr(type_id, types); - let v = visitor::new(out, |_,_| Err(wrong_shape(type_id))) - .visit_bit_sequence(|out, store, order| { + 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(()) - }); + }, + ); super::resolve_type_and_encode(types, type_id, v) } diff --git a/scale-encode/src/impls/composite.rs b/scale-encode/src/impls/composite.rs index 0d78f6c..1d3ff13 100644 --- a/scale-encode/src/impls/composite.rs +++ b/scale-encode/src/impls/composite.rs @@ -15,8 +15,7 @@ use crate::{ error::{Error, ErrorKind, Kind, Location}, - EncodeAsType, Field, FieldIter, - TypeResolver + EncodeAsType, Field, FieldIter, TypeResolver, }; use alloc::collections::BTreeMap; use alloc::{string::ToString, vec::Vec}; @@ -33,7 +32,7 @@ trait EncodeAsTypeWithResolver { out: &mut Vec, ) -> Result<(), Error>; } -impl EncodeAsTypeWithResolver for T { +impl EncodeAsTypeWithResolver for T { fn encode_as_type_with_resolver_to( &self, type_id: &R::TypeId, @@ -49,17 +48,22 @@ impl EncodeAsTypeWithResolver for T { /// this basically takes a type which implements [`EncodeAsType`] and turns it /// into something object safe. pub struct CompositeField<'a, R> { - val: &'a dyn EncodeAsTypeWithResolver + val: &'a dyn EncodeAsTypeWithResolver, } -impl <'a, R> Copy for CompositeField<'a, R> {} -impl <'a, R> Clone for CompositeField<'a, R> { +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> { +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 { @@ -73,16 +77,18 @@ impl <'a, R: TypeResolver> CompositeField<'a, R> { types: &R, out: &mut Vec, ) -> Result<(), Error> { - self.val.encode_as_type_with_resolver_to(type_id, types, out) + 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. +/// 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, @@ -91,18 +97,29 @@ impl <'a, R: TypeResolver> CompositeField<'a, R> { /// } /// /// 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) /// } /// } /// ``` +/// +/// [`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 + marker: core::marker::PhantomData, } impl<'a, R, Vals> Composite @@ -115,14 +132,30 @@ where /// /// ```rust /// use scale_encode::{ Composite, CompositeField }; + /// use scale_info::PortableRegistry; /// - /// Composite::new([ + /// 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 } + Composite { + vals, + marker: core::marker::PhantomData, + } + } + + /// A shortcut for [`Self::encode_composite_as_type_to()`] for when you just + /// want it to allocate and return the encoded bytes. + 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. @@ -139,64 +172,77 @@ where // 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 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() - .unwrap() - .1 - .encode_composite_field_to(type_id, 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() + .unwrap() + .1 + .encode_composite_field_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_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 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() - .unwrap() - .1 - .encode_composite_field_to(type_id, types, out); - } + // 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_composite_field_to(type_id, types, out); + } - 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); - } + 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(|id| Field::unnamed(id)); - self.encode_composite_fields_to(&mut fields as &mut dyn FieldIter<'_, R::TypeId>, types, out) - }); + let mut fields = type_ids.map(|id| Field::unnamed(id)); + self.encode_composite_fields_to( + &mut fields as &mut dyn FieldIter<'_, R::TypeId>, + types, + out, + ) + }); super::resolve_type_and_encode(types, type_id, v) } + /// A shortcut for [`Self::encode_composite_fields_to()`] for when you just + /// want it to allocate and return the encoded bytes. + 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, @@ -230,7 +276,9 @@ where // Find the field in our source type: 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: @@ -253,14 +301,15 @@ where } for (idx, (field, (name, val))) in fields.iter().zip(vals_iter).enumerate() { - 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) - })?; + 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(()) } @@ -269,30 +318,30 @@ 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<'a, R: TypeResolver>(type_id: &'a R::TypeId, types: &'a R) -> &'a R::TypeId { - let v = visitor::new((), |_,_| type_id) - .visit_composite(|_,fields| { +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 + return type_id; }; - let None = fields.next() else { - return type_id + if fields.next().is_some() || f.name.is_some() { + return type_id; }; - if f.name.is_some() { - return type_id - } skip_through_single_unnamed_fields(f.id, types) }) - .visit_tuple(|_,type_ids| { + .visit_tuple(|_, type_ids| { // Else if exactly 1 tuple entry, recurse into it, else return current type ID. - let Some(type_id) = type_ids.next() else { - return type_id + let Some(new_type_id) = type_ids.next() else { + return type_id; }; - let None = type_ids.next() else { - return type_id + if type_ids.next().is_some() { + return type_id; }; - skip_through_single_unnamed_fields(type_id, types) + 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 bdbb4fa..3395057 100644 --- a/scale-encode/src/impls/mod.rs +++ b/scale-encode/src/impls/mod.rs @@ -22,14 +22,13 @@ mod variant; // 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 composite::{Composite, CompositeField}; pub use variant::Variant; use crate::{ error::{Error, ErrorKind, Kind}, EncodeAsFields, EncodeAsType, }; -use scale_type_resolver::{ visitor, FieldIter, Primitive, ResolvedTypeVisitor, TypeResolver }; use alloc::{ borrow::ToOwned, boxed::Box, @@ -49,11 +48,20 @@ use core::{ ops::{Range, RangeInclusive}, time::Duration, }; - -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> { +use scale_type_resolver::{visitor, FieldIter, Primitive, ResolvedTypeVisitor, TypeResolver}; + +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()))) + Err(e) => Err(Error::new(ErrorKind::TypeResolvingError(e.to_string()))), } } @@ -73,8 +81,8 @@ impl EncodeAsType for bool { }) }; - let v = visitor::new((),|_,_| Err(wrong_shape_err())) - .visit_primitive(|_,primitive| { + let v = visitor::new((), |_, _| Err(wrong_shape_err())) + .visit_primitive(|_, primitive| { if primitive == Primitive::Bool { self.encode_to(out); Ok(()) @@ -82,9 +90,7 @@ impl EncodeAsType for bool { Err(wrong_shape_err()) } }) - .visit_not_found(|_| { - Err(Error::new(ErrorKind::TypeNotFound(format!("{type_id:?}")))) - }); + .visit_not_found(|_| Err(Error::new(ErrorKind::TypeNotFound(format!("{type_id:?}"))))); resolve_type_and_encode(types, type_id, v) } @@ -106,8 +112,8 @@ impl EncodeAsType for str { }) }; - let v = visitor::new((),|_,_| Err(wrong_shape_err())) - .visit_primitive(|_,primitive| { + let v = visitor::new((), |_, _| Err(wrong_shape_err())) + .visit_primitive(|_, primitive| { if primitive == Primitive::Str { self.encode_to(out); Ok(()) @@ -115,9 +121,7 @@ impl EncodeAsType for str { Err(wrong_shape_err()) } }) - .visit_not_found(|_| { - Err(Error::new(ErrorKind::TypeNotFound(format!("{type_id:?}")))) - }); + .visit_not_found(|_| Err(Error::new(ErrorKind::TypeNotFound(format!("{type_id:?}"))))); resolve_type_and_encode(types, type_id, v) } @@ -250,8 +254,8 @@ macro_rules! impl_encode_number { }) }; - let v = visitor::new(out, |_out,_kind| Err(wrong_shape_err())) - .visit_primitive(|out,primitive| { + 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, @@ -269,35 +273,19 @@ macro_rules! impl_encode_number { 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::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) - }, + 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| { + .visit_compact(|out, type_id| { let type_id = find_single_entry_with_same_repr(type_id, types); macro_rules! try_compact_num { @@ -313,27 +301,26 @@ macro_rules! impl_encode_number { }}; } - 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()), + 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) }) @@ -429,20 +416,19 @@ impl, V: EncodeAsType> EncodeAsType for BTreeMap { types: &R, out: &mut Vec, ) -> Result<(), Error> { - let v = visitor - ::new(out,|out,_| { - Composite::new( - self.iter() - .map(|(k, v)| (Some(k.as_ref()), CompositeField::new(v))), - ) - .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) - }); + let v = visitor::new(out, |out, _| { + Composite::new( + self.iter() + .map(|(k, v)| (Some(k.as_ref()), CompositeField::new(v))), + ) + .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) } @@ -505,25 +491,28 @@ 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<'resolver, R: TypeResolver>(type_id: &'resolver R::TypeId, types: &'resolver R) -> &'resolver R::TypeId { - let v = visitor::new((),|_,_| type_id) - .visit_tuple(|_,fields| { - let Some(field_ty_id) = fields.next() else { - return 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 + return type_id; } - field_ty_id + find_single_entry_with_same_repr(new_type_id, types) }) - .visit_composite(|_,fields| { + .visit_composite(|_, fields| { let Some(field) = fields.next() else { - return type_id + return type_id; }; if fields.next().is_some() { - return type_id + return type_id; } - field.id + find_single_entry_with_same_repr(field.id, types) }); types.resolve_type(type_id, v).unwrap_or(type_id) @@ -540,7 +529,7 @@ fn encode_iterable_sequence_to( where I: Iterator, I::Item: EncodeAsType, - R: TypeResolver + R: TypeResolver, { let wrong_shape_err = || { Error::new(ErrorKind::WrongShape { @@ -549,8 +538,8 @@ where }) }; - let v = visitor::new((it,out), |_,_| Err(wrong_shape_err())) - .visit_array(|(it,out), inner_ty_id, array_len| { + 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(inner_ty_id, types, out) @@ -564,7 +553,7 @@ where })) } }) - .visit_sequence(|(it,out), inner_ty_id| { + .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() { @@ -573,14 +562,14 @@ where } Ok(()) }) - .visit_tuple(|(it,out), inner_type_ids| { + .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| { + .visit_composite(|(it, out), fields| { if fields.len() == 1 { encode_iterable_sequence_to(len, it, fields.next().unwrap().id, types, out) } else { @@ -599,7 +588,7 @@ mod test { use alloc::vec; use codec::Decode; use core::fmt::Debug; - use scale_info::{ TypeInfo, PortableRegistry }; + use scale_info::{PortableRegistry, TypeInfo}; /// Given a type definition, return type ID and registry representing it. fn make_type() -> (u32, PortableRegistry) { @@ -798,26 +787,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] @@ -825,16 +804,16 @@ mod test { assert_encodes_like_codec(()); assert_encodes_like_codec((12345,)); assert_encodes_like_codec((123u8, true)); - assert_encodes_like_codec((123u8, true, "hello")); - // Encode isn't implemented for `char` (but we treat it as a u32): - assert_encodes_like_codec((123u8, true, "hello".to_string(), 'a' as u32)); - assert_encodes_like_codec(( - 123u8, - true, - "hello".to_string(), - 'a' as u32, - 123_000_000_000u128, - )); + // assert_encodes_like_codec((123u8, true, "hello")); + // // Encode isn't implemented for `char` (but we treat it as a u32): + // assert_encodes_like_codec((123u8, true, "hello".to_string(), 'a' as u32)); + // assert_encodes_like_codec(( + // 123u8, + // true, + // "hello".to_string(), + // 'a' as u32, + // 123_000_000_000u128, + // )); } #[test] @@ -900,12 +879,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::new(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, @@ -913,33 +897,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 = [ + let source_vals = [ (Some("bar"), CompositeField::new(&12345u128)), (Some("wibble"), CompositeField::new(&true)), - (Some("hello"), CompositeField::new(&"world".to_string())), + (Some("hello"), CompositeField::new(&"world")), ]; - let source = Composite::new(named_vals.iter().copied()); + 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 unnamed_vals = [ + let source2_vals = [ (None, CompositeField::new(&12345u128)), (None, CompositeField::new(&true)), - (None, CompositeField::new(&"world".to_string())), + (None, CompositeField::new(&"world")), ]; - let source2 = Composite::new(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] @@ -952,15 +948,18 @@ mod test { } // note: fields do not need to be in order when named: - let vals = [ - (Some("hello"), CompositeField::new(&"world".to_string())), + let source_vals = [ + (Some("hello"), CompositeField::new(&"world")), (Some("bar"), CompositeField::new(&12345u128)), // wrong name: (Some("wibbles"), CompositeField::new(&true)), ]; - let source = Composite::new(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] @@ -1065,19 +1064,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/variant.rs b/scale-encode/src/impls/variant.rs index 266fd63..4c3e34a 100644 --- a/scale-encode/src/impls/variant.rs +++ b/scale-encode/src/impls/variant.rs @@ -13,18 +13,20 @@ // See the License for the specific language governing permissions and // limitations under the License. +use super::composite::{Composite, CompositeField}; use crate::error::{Error, ErrorKind, Kind}; use alloc::{string::ToString, vec::Vec}; use codec::Encode; -use scale_type_resolver::{TypeResolver,visitor}; -use super::composite::{ Composite, CompositeField }; +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), @@ -32,21 +34,26 @@ use super::composite::{ Composite, CompositeField }; /// } /// /// 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) /// } /// } /// } @@ -63,6 +70,14 @@ where R: TypeResolver + 'a, Vals: ExactSizeIterator, CompositeField<'a, R>)> + Clone, { + /// A shortcut for [`Self::encode_variant_as_type_to()`] for when you just + /// want it to allocate and return the encoded bytes. + 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, @@ -72,32 +87,32 @@ where ) -> Result<(), Error> { let type_id = super::find_single_entry_with_same_repr(type_id, types); - let v = visitor - ::new((),|_,_| { - Err(Error::new(ErrorKind::WrongShape { - actual: Kind::Str, - 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 v = visitor::new((), |_, _| { + Err(Error::new(ErrorKind::WrongShape { + actual: Kind::Str, + 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:?}") - })); - }; + 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) - }); + 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 3826750..5881d3c 100644 --- a/scale-encode/src/lib.rs +++ b/scale-encode/src/lib.rs @@ -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); } @@ -152,11 +152,10 @@ pub mod error; pub use alloc::vec::Vec; pub use error::Error; -pub use scale_type_resolver::{ TypeResolver, FieldIter, Field }; // Useful types to help implement EncodeAsType/Fields with: pub use crate::impls::{Composite, CompositeField, Variant}; -pub use scale_info::PortableRegistry; +pub use scale_type_resolver::{Field, FieldIter, TypeResolver}; /// Re-exports of external crates. pub mod ext { @@ -181,7 +180,7 @@ pub trait EncodeAsType { fn encode_as_type( &self, type_id: &R::TypeId, - types: &R + types: &R, ) -> Result, Error> { let mut out = Vec::new(); self.encode_as_type_to(type_id, types, &mut out)?; From d1dfcf16147ce038919540e973db7ba7826b71e7 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Thu, 15 Feb 2024 16:57:40 +0000 Subject: [PATCH 3/8] update to crates.io scale-type-resolver --- Cargo.toml | 3 --- scale-encode/Cargo.toml | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2bd9456..3c46791 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,3 @@ include = ["Cargo.toml", "src/**/*.rs", "README.md", "LICENSE"] [workspace.dependencies] scale-encode = { version = "0.5.0", path = "scale-encode" } scale-encode-derive = { version = "0.5.0", path = "scale-encode-derive" } - -[patch.crates-io] -scale-type-resolver = { path = "../scale-type-resolver" } \ No newline at end of file diff --git a/scale-encode/Cargo.toml b/scale-encode/Cargo.toml index 6569948..7b3217b 100644 --- a/scale-encode/Cargo.toml +++ b/scale-encode/Cargo.toml @@ -30,7 +30,7 @@ bits = ["dep:scale-bits"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -scale-type-resolver = "0.1.0" +scale-type-resolver = "0.1.1" scale-bits = { version = "0.5.0", default-features = false, features = ["scale-info"], optional = true } scale-encode-derive = { workspace = true, optional = true } primitive-types = { version = "0.12.0", optional = true, default-features = false } From 2a8369d7ad51842159f7c2c753ab334d69849884 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Thu, 15 Feb 2024 17:08:26 +0000 Subject: [PATCH 4/8] fix no-std and clippy --- scale-encode/src/impls/bits.rs | 2 +- scale-encode/src/impls/composite.rs | 4 ++-- scale-encode/src/impls/mod.rs | 11 ++++++----- scale-encode/src/impls/variant.rs | 2 +- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/scale-encode/src/impls/bits.rs b/scale-encode/src/impls/bits.rs index f2f1343..aa61955 100644 --- a/scale-encode/src/impls/bits.rs +++ b/scale-encode/src/impls/bits.rs @@ -17,7 +17,7 @@ use crate::{ error::{Error, ErrorKind, Kind}, EncodeAsType, }; -use alloc::vec::Vec; +use alloc::{format, vec::Vec}; use scale_type_resolver::{visitor, TypeResolver}; impl EncodeAsType for scale_bits::Bits { diff --git a/scale-encode/src/impls/composite.rs b/scale-encode/src/impls/composite.rs index 1d3ff13..3d09375 100644 --- a/scale-encode/src/impls/composite.rs +++ b/scale-encode/src/impls/composite.rs @@ -18,7 +18,7 @@ use crate::{ EncodeAsType, Field, FieldIter, TypeResolver, }; use alloc::collections::BTreeMap; -use alloc::{string::ToString, vec::Vec}; +use alloc::{format, string::ToString, vec::Vec}; use scale_type_resolver::visitor; /// This trait exists to get around object safety issues using [`EncodeAsType`]. @@ -220,7 +220,7 @@ where .encode_composite_field_to(type_id, types, out); } - let mut fields = type_ids.map(|id| Field::unnamed(id)); + let mut fields = type_ids.map(Field::unnamed); self.encode_composite_fields_to( &mut fields as &mut dyn FieldIter<'_, R::TypeId>, types, diff --git a/scale-encode/src/impls/mod.rs b/scale-encode/src/impls/mod.rs index 3395057..5e91674 100644 --- a/scale-encode/src/impls/mod.rs +++ b/scale-encode/src/impls/mod.rs @@ -20,11 +20,6 @@ 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, CompositeField}; -pub use variant::Variant; - use crate::{ error::{Error, ErrorKind, Kind}, EncodeAsFields, EncodeAsType, @@ -33,6 +28,7 @@ use alloc::{ borrow::ToOwned, boxed::Box, collections::{BTreeMap, BTreeSet, BinaryHeap, LinkedList, VecDeque}, + format, rc::Rc, string::{String, ToString}, sync::Arc, @@ -50,6 +46,11 @@ use core::{ }; 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, diff --git a/scale-encode/src/impls/variant.rs b/scale-encode/src/impls/variant.rs index 4c3e34a..133dd1f 100644 --- a/scale-encode/src/impls/variant.rs +++ b/scale-encode/src/impls/variant.rs @@ -15,7 +15,7 @@ use super::composite::{Composite, CompositeField}; use crate::error::{Error, ErrorKind, Kind}; -use alloc::{string::ToString, vec::Vec}; +use alloc::{format, string::ToString, vec::Vec}; use codec::Encode; use scale_type_resolver::{visitor, TypeResolver}; From a7274777cb004773a5fd429ac4f0eafaca93a224 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Thu, 15 Feb 2024 17:25:33 +0000 Subject: [PATCH 5/8] remove scale-info references and ensure READMEs remain in sync --- .github/workflows/rust.yml | 10 ++++++++++ README.md | 22 +++++++++++----------- scale-encode/Cargo.toml | 4 ++-- scale-encode/README.md | 22 +++++++++++----------- scale-encode/src/lib.rs | 6 +++--- 5 files changed, 37 insertions(+), 27 deletions(-) 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/README.md b/README.md index 205783c..23179c8 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 +- 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. +- An [`EncodeAsFields`] trait which when implemented on some type, describes how it can be SCALE encoded + 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 [`macro@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/Cargo.toml b/scale-encode/Cargo.toml index 7b3217b..cd9d998 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"] @@ -39,7 +39,7 @@ 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" diff --git a/scale-encode/README.md b/scale-encode/README.md index 205783c..23179c8 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 +- 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. +- An [`EncodeAsFields`] trait which when implemented on some type, describes how it can be SCALE encoded + 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 [`macro@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/lib.rs b/scale-encode/src/lib.rs index 5881d3c..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. @@ -164,7 +164,7 @@ 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`. From cdf624cb7892164e3cd96d2015d9f42ad860228f Mon Sep 17 00:00:00 2001 From: James Wilson Date: Fri, 16 Feb 2024 09:29:12 +0000 Subject: [PATCH 6/8] remove []s in READMEs --- README.md | 10 +++++----- scale-encode/README.md | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 23179c8..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 type information from a [`TypeResolver`] +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 +- 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 an iterator over [`Field`]s and a type registry describing the expected shape of the +- An `EncodeAsFields` trait which when implemented on some type, describes how it can be SCALE encoded + 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 diff --git a/scale-encode/README.md b/scale-encode/README.md index 23179c8..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 type information from a [`TypeResolver`] +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 +- 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 an iterator over [`Field`]s and a type registry describing the expected shape of the +- An `EncodeAsFields` trait which when implemented on some type, describes how it can be SCALE encoded + 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 From c4854a7fecfc94bb291a80a30b9bbe27d42472b5 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Fri, 16 Feb 2024 09:54:38 +0000 Subject: [PATCH 7/8] remove scale-info from dependency tree entirely --- scale-encode/Cargo.toml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scale-encode/Cargo.toml b/scale-encode/Cargo.toml index cd9d998..2bacec6 100644 --- a/scale-encode/Cargo.toml +++ b/scale-encode/Cargo.toml @@ -30,8 +30,8 @@ bits = ["dep:scale-bits"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -scale-type-resolver = "0.1.1" -scale-bits = { version = "0.5.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" @@ -45,3 +45,4 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = 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"] } From 4126071ae261572d731b2db3f82a5f2158ff8c3c Mon Sep 17 00:00:00 2001 From: James Wilson Date: Fri, 16 Feb 2024 11:06:43 +0000 Subject: [PATCH 8/8] Uncomment some tests, tweak a comment, expect over unwrap --- scale-encode/src/impls/composite.rs | 12 ++++++------ scale-encode/src/impls/mod.rs | 20 ++++++++++---------- scale-encode/src/impls/variant.rs | 4 ++-- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/scale-encode/src/impls/composite.rs b/scale-encode/src/impls/composite.rs index 3d09375..35a49a0 100644 --- a/scale-encode/src/impls/composite.rs +++ b/scale-encode/src/impls/composite.rs @@ -146,8 +146,8 @@ where } } - /// A shortcut for [`Self::encode_composite_as_type_to()`] for when you just - /// want it to allocate and return the encoded bytes. + /// 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, @@ -178,7 +178,7 @@ where if vals_iter_len == 1 { return vals_iter .next() - .unwrap() + .expect("1 value expected") .1 .encode_composite_field_to(type_id, types, out); } @@ -202,7 +202,7 @@ where if !is_named_vals && vals_iter_len == 1 { return vals_iter .next() - .unwrap() + .expect("1 value expected") .1 .encode_composite_field_to(type_id, types, out); } @@ -231,8 +231,8 @@ where super::resolve_type_and_encode(types, type_id, v) } - /// A shortcut for [`Self::encode_composite_fields_to()`] for when you just - /// want it to allocate and return the encoded bytes. + /// 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>, diff --git a/scale-encode/src/impls/mod.rs b/scale-encode/src/impls/mod.rs index 5e91674..c0c7ebe 100644 --- a/scale-encode/src/impls/mod.rs +++ b/scale-encode/src/impls/mod.rs @@ -805,16 +805,16 @@ mod test { assert_encodes_like_codec(()); assert_encodes_like_codec((12345,)); assert_encodes_like_codec((123u8, true)); - // assert_encodes_like_codec((123u8, true, "hello")); - // // Encode isn't implemented for `char` (but we treat it as a u32): - // assert_encodes_like_codec((123u8, true, "hello".to_string(), 'a' as u32)); - // assert_encodes_like_codec(( - // 123u8, - // true, - // "hello".to_string(), - // 'a' as u32, - // 123_000_000_000u128, - // )); + assert_encodes_like_codec((123u8, true, "hello")); + // Encode isn't implemented for `char` (but we treat it as a u32): + assert_encodes_like_codec((123u8, true, "hello".to_string(), 'a' as u32)); + assert_encodes_like_codec(( + 123u8, + true, + "hello".to_string(), + 'a' as u32, + 123_000_000_000u128, + )); } #[test] diff --git a/scale-encode/src/impls/variant.rs b/scale-encode/src/impls/variant.rs index 133dd1f..533adcc 100644 --- a/scale-encode/src/impls/variant.rs +++ b/scale-encode/src/impls/variant.rs @@ -70,8 +70,8 @@ where R: TypeResolver + 'a, Vals: ExactSizeIterator, CompositeField<'a, R>)> + Clone, { - /// A shortcut for [`Self::encode_variant_as_type_to()`] for when you just - /// want it to allocate and return the encoded bytes. + /// 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)?;