Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable generic type encoding via TypeResolver and remove dependency on scale-info #19

Merged
merged 8 commits into from
Feb 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dq: why do we need two readme's?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The one in /scale-encode is the oen that is shown on crates.io for the crate, and the one at root is shown on github! This was the laziest way I could think of to make sure that they were kept in sync :)

Copy link
Member

@niklasad1 niklasad1 Feb 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you could just link to root/github README in the Cargo.toml of the crate but fine :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, probably a symlink would work here to avoid some extra CI minutes :D

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That might not work because if the readme is outside of the crate it won't be uploaded with it, but I'd have to try it!

Copy link
Collaborator Author

@jsdw jsdw Feb 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Git does handle symlinks ok (as long as the FS supports it) though by the sounds of it (eg https://stackoverflow.com/questions/954560/how-does-git-handle-symbolic-links) (I'd assumed that it wouldn't!), so we could just make one readme be a symlink to the other, but I'm not too bothered either way given the CI check :)


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 = []
jsdw marked this conversation as resolved.
Show resolved Hide resolved

# 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
Loading