Skip to content

Commit

Permalink
Enable generic type encoding via TypeResolver and remove dependency…
Browse files Browse the repository at this point in the history
… on `scale-info` (#19)

* WIP use TypeResolver

* Finish updating to use TypeResolver (minus dep patch)

* update to crates.io scale-type-resolver

* fix no-std and clippy

* remove scale-info references and ensure READMEs remain in sync

* remove []s in READMEs

* remove scale-info from dependency tree entirely

* Uncomment some tests, tweak a comment, expect over unwrap
  • Loading branch information
jsdw authored Feb 16, 2024
1 parent 0fa7594 commit 82ab8cd
Show file tree
Hide file tree
Showing 14 changed files with 744 additions and 563 deletions.
10 changes: 10 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,16 @@ jobs:
# we run tests using BitVec<u64,_> 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
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/target
Cargo.lock
.DS_Store
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ members = [
"scale-encode-derive",
"testing/no_std",
]
resolver = "2"

[workspace.package]
version = "0.5.0"
Expand Down
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
# scale-encode

`parity-scale-codec` provides an `Encode` trait which allows types to SCALE encode themselves based on their shape.
This crate builds on this, and allows types to encode themselves based on `scale_info` type information. It
exposes two traits:
This crate builds on this, and allows types to encode themselves based on type information from a `TypeResolver`
implementation (one such implementation being a `scale_info::PortableRegistry`). It exposes two traits:

- An `EncodeAsType` trait which when implemented on some type, describes how it can be SCALE encoded
with the help of a type ID and type registry describing the expected shape of the encoded bytes.
- An `EncodeAsFields` trait which when implemented on some type, describes how it can be SCALE encoded
with the help of a slice of `PortableField`'s or `PortableFieldId`'s and type registry describing the
expected shape of the encoded bytes. This is generally only implemented for tuples and structs, since we
need a set of fields to map to the provided slices.
with the help of an iterator over `Field`s and a type registry describing the expected shape of the
encoded bytes. This is generally only implemented for tuples and structs, since we need a set of fields
to map to the provided iterator.

Implementations for many built-in types are also provided for each trait, and the `macro@EncodeAsType`
Implementations for many built-in types are also provided for each trait, and the `EncodeAsType`
macro makes it easy to generate implementations for new structs and enums.

# Motivation
Expand All @@ -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<T: TypeInfo + 'static>() -> (u32, PortableRegistry) {
let m = scale_info::MetaType::new::<T>();
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.
Expand All @@ -43,7 +43,7 @@ where
B: TypeInfo + Encode + 'static,
{
let (type_id, types) = get_type_info::<B>();
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);
}
Expand Down
34 changes: 17 additions & 17 deletions scale-encode-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<ScaleEncodeResolver: #path_to_scale_encode::TypeResolver>(
&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<u8>
) -> Result<(), #path_to_scale_encode::Error> {
match self {
Expand Down Expand Up @@ -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<ScaleEncodeResolver: #path_to_scale_encode::TypeResolver>(
&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<u8>
) -> 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
Expand All @@ -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<ScaleEncodeResolver: #path_to_scale_encode::TypeResolver>(
&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<u8>
) -> Result<(), #path_to_scale_encode::Error> {
let #path_to_type #matcher = self;
#composite.encode_as_fields_to(
#composite.encode_composite_fields_to(
__encode_as_type_fields,
__encode_as_type_types,
__encode_as_type_out
Expand Down Expand Up @@ -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) => {
Expand All @@ -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())),
),
}
}
Expand Down
9 changes: 5 additions & 4 deletions scale-encode/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand All @@ -29,19 +29,20 @@ primitive-types = ["dep:primitive-types"]
bits = ["dep:scale-bits"]

[dependencies]
scale-info = { version = "2.7.0", default-features = false, features = ["bit-vec"] }
codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] }
scale-bits = { version = "0.4.0", default-features = false, features = ["scale-info"], optional = true }
scale-type-resolver = { version = "0.1.1", default-features = false, features = ["visitor"] }
scale-bits = { version = "0.5.0", default-features = false, optional = true }
scale-encode-derive = { workspace = true, optional = true }
primitive-types = { version = "0.12.0", optional = true, default-features = false }
smallvec = "1.10.0"
derive_more = { version = "0.99.17", default-features = false, features = ["from", "display"] }

[dev-dependencies]
bitvec = { version = "1.0.1", default-features = false }
scale-info = { version = "2.3.0", features = ["bit-vec", "derive"], default-features = false }
scale-info = { version = "2.3.0", features = ["bit-vec", "derive", "std"], default-features = false }
scale-encode-derive = { workspace = true }
codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "bit-vec"] }
trybuild = "1.0.72"
# enable scale-info feature for testing:
primitive-types = { version = "0.12.0", default-features = false, features = ["scale-info"] }
scale-type-resolver = { version = "0.1.1", default-features = false, features = ["scale-info"] }
18 changes: 9 additions & 9 deletions scale-encode/README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
# scale-encode

`parity-scale-codec` provides an `Encode` trait which allows types to SCALE encode themselves based on their shape.
This crate builds on this, and allows types to encode themselves based on `scale_info` type information. It
exposes two traits:
This crate builds on this, and allows types to encode themselves based on type information from a `TypeResolver`
implementation (one such implementation being a `scale_info::PortableRegistry`). It exposes two traits:

- An `EncodeAsType` trait which when implemented on some type, describes how it can be SCALE encoded
with the help of a type ID and type registry describing the expected shape of the encoded bytes.
- An `EncodeAsFields` trait which when implemented on some type, describes how it can be SCALE encoded
with the help of a slice of `PortableField`'s or `PortableFieldId`'s and type registry describing the
expected shape of the encoded bytes. This is generally only implemented for tuples and structs, since we
need a set of fields to map to the provided slices.
with the help of an iterator over `Field`s and a type registry describing the expected shape of the
encoded bytes. This is generally only implemented for tuples and structs, since we need a set of fields
to map to the provided iterator.

Implementations for many built-in types are also provided for each trait, and the `macro@EncodeAsType`
Implementations for many built-in types are also provided for each trait, and the `EncodeAsType`
macro makes it easy to generate implementations for new structs and enums.

# Motivation
Expand All @@ -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<T: TypeInfo + 'static>() -> (u32, PortableRegistry) {
let m = scale_info::MetaType::new::<T>();
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.
Expand All @@ -43,7 +43,7 @@ where
B: TypeInfo + Encode + 'static,
{
let (type_id, types) = get_type_info::<B>();
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);
}
Expand Down
27 changes: 16 additions & 11 deletions scale-encode/src/error/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -136,20 +139,22 @@ pub enum ErrorKind {
expected_len: usize,
},
/// We cannot encode the number given into the target type; it's out of range.
#[display(fmt = "Number {value} is out of range for target type {expected}")]
#[display(
fmt = "Number {value} is out of range for target type with identifier {expected_id}"
)]
NumberOutOfRange {
/// A string represenatation of the numeric value that was out of range.
value: String,
/// Id of the expected numeric type that we tried to encode it to.
expected: u32,
/// Identifier for the expected numeric type that we tried to encode it to.
expected_id: String,
},
/// Cannot find a variant with a matching name on the target type.
#[display(fmt = "Variant {name} does not exist on type with ID {expected}")]
#[display(fmt = "Variant {name} does not exist on type with identifier {expected_id}")]
CannotFindVariant {
/// Variant name we can't find in the expected type.
name: String,
/// ID of the expected type.
expected: u32,
/// Identifier for the expected type.
expected_id: String,
},
/// Cannot find a field on our source type that's needed for the target type.
#[display(fmt = "Field {name} does not exist in our source struct")]
Expand Down
34 changes: 15 additions & 19 deletions scale-encode/src/impls/bits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,37 +17,33 @@ use crate::{
error::{Error, ErrorKind, Kind},
EncodeAsType,
};
use alloc::vec::Vec;
use scale_info::TypeDef;
use alloc::{format, vec::Vec};
use scale_type_resolver::{visitor, TypeResolver};

impl EncodeAsType for scale_bits::Bits {
fn encode_as_type_to(
fn encode_as_type_to<R: TypeResolver>(
&self,
type_id: u32,
types: &scale_info::PortableRegistry,
type_id: &R::TypeId,
types: &R,
out: &mut Vec<u8>,
) -> 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:?}"),
})
}
Loading

0 comments on commit 82ab8cd

Please sign in to comment.