diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index a9c89d7..37e4a33 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -71,6 +71,32 @@ jobs: # we run tests using BitVec which doesn't. args: --all-features --target wasm32-unknown-unknown + no_std: + name: Check no_std build + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v2 + + - name: Install Rust stable toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + target: aarch64-unknown-none + override: true + + - name: Rust Cache + uses: Swatinem/rust-cache@v1.3.0 + + - name: Check no_std build + uses: actions-rs/cargo@v1.0.3 + with: + command: check + # The aarch64-unknown-none doesn't support `std`, so this + # will fail if the crate is not no_std compatible. + args: --no-default-features --target aarch64-unknown-none --features bits,primitive-types,derive + fmt: name: Cargo fmt runs-on: ubuntu-latest @@ -138,6 +164,12 @@ jobs: command: test args: --all-targets --workspace + - name: Cargo test no_std + uses: actions-rs/cargo@v1.0.3 + with: + command: test + args: --all-targets --workspace --no-default-features --features bits,primitive-types + - name: Cargo test docs uses: actions-rs/cargo@v1.0.3 with: diff --git a/Cargo.toml b/Cargo.toml index 6314e13..5694499 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,8 @@ [workspace] members = [ "scale-encode", - "scale-encode-derive" + "scale-encode-derive", + "testing/no_std", ] [workspace.package] diff --git a/scale-encode-derive/src/lib.rs b/scale-encode-derive/src/lib.rs index fbaef43..8ca9c3f 100644 --- a/scale-encode-derive/src/lib.rs +++ b/scale-encode-derive/src/lib.rs @@ -81,7 +81,7 @@ fn generate_enum_impl( // 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_out: &mut Vec + __encode_as_type_out: &mut #path_to_scale_encode::Vec ) -> Result<(), #path_to_scale_encode::Error> { match self { #( #match_arms, )* @@ -113,7 +113,7 @@ fn generate_struct_impl( // 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_out: &mut Vec + __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( @@ -129,7 +129,7 @@ fn generate_struct_impl( // 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_out: &mut Vec + __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( @@ -193,15 +193,13 @@ fn fields_to_matcher_and_composite( ) } syn::Fields::Unnamed(fields) => { - let field_idents: Vec = fields + let field_idents = fields .unnamed .iter() .enumerate() - .map(|(idx, _)| format_ident!("_{idx}")) - .collect(); - let match_body = field_idents.iter().map(|i| quote!(#i)); + .map(|(idx, _)| format_ident!("_{idx}")); + let match_body = field_idents.clone().map(|i| quote!(#i)); let tuple_body = field_idents - .iter() .map(|i| quote!((None as Option<&'static str>, #i as &dyn #path_to_scale_encode::EncodeAsType))); ( diff --git a/scale-encode/Cargo.toml b/scale-encode/Cargo.toml index 81e16cf..b1f6cd3 100644 --- a/scale-encode/Cargo.toml +++ b/scale-encode/Cargo.toml @@ -14,31 +14,33 @@ keywords.workspace = true include.workspace = true [features] -default = ["derive", "primitive-types", "bits"] +default = ["std", "derive", "primitive-types", "bits"] -# Include the derive proc macro +# Activates std feature. +std = ["scale-info/std"] + +# Include the derive proc macro. derive = ["dep:scale-encode-derive"] -# impls for key primitive-types +# impls for key primitive-types. primitive-types = ["dep:primitive-types"] # impls for Bits. bits = ["dep:scale-bits"] [dependencies] -scale-info = { version = "2.7.0", features = ["bit-vec"] } -thiserror = "1.0.37" -codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "full"] } -scale-bits = { version = "0.3.0", default-features = false, features = ["scale-info"], optional = true } +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-encode-derive = { workspace = true, optional = true } primitive-types = { version = "0.12.0", optional = true, default-features = false } smallvec = "1.10.0" [dev-dependencies] -bitvec = "1.0.1" -scale-info = { version = "2.3.0", features = ["bit-vec", "derive"] } +bitvec = { version = "1.0.1", default-features = false } +scale-info = { version = "2.3.0", features = ["bit-vec", "derive"], default-features = false } scale-encode-derive = { workspace = true } -codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "full", "bit-vec"] } +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"] } diff --git a/scale-encode/src/error/context.rs b/scale-encode/src/error/context.rs index a209801..f51d881 100644 --- a/scale-encode/src/error/context.rs +++ b/scale-encode/src/error/context.rs @@ -16,7 +16,7 @@ //! This module provides a [`Context`] type, which tracks the path //! that we're attempting to encode to aid in error reporting. -use std::borrow::Cow; +use alloc::{borrow::Cow, vec::Vec}; /// A cheaply clonable opaque context which allows us to track the current /// location into a type that we're trying to encode, to aid in @@ -55,8 +55,8 @@ impl<'a> Path<'a> { } } -impl<'a> std::fmt::Display for Path<'a> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl<'a> core::fmt::Display for Path<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { for (idx, loc) in self.0.iter().enumerate() { if idx != 0 { f.write_str(".")?; diff --git a/scale-encode/src/error/mod.rs b/scale-encode/src/error/mod.rs index 84a05c0..43cf0d8 100644 --- a/scale-encode/src/error/mod.rs +++ b/scale-encode/src/error/mod.rs @@ -16,13 +16,13 @@ //! An error that is emitted whenever some encoding fails. mod context; -use std::borrow::Cow; -use std::fmt::Display; +use alloc::{borrow::Cow, boxed::Box, string::String}; +use core::fmt::Display; pub use context::{Context, Location}; /// An error produced while attempting to encode some type. -#[derive(Debug, thiserror::Error)] +#[derive(Debug)] pub struct Error { context: Context, kind: ErrorKind, @@ -83,7 +83,7 @@ impl Error { } impl Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let path = self.context.path(); let kind = &self.kind; write!(f, "Error at {path}: {kind}") @@ -91,13 +91,11 @@ impl Display for Error { } /// The underlying nature of the error. -#[derive(Debug, thiserror::Error)] +#[derive(Debug)] pub enum ErrorKind { /// Cannot find a given type. - #[error("Cannot find type with ID {0}")] TypeNotFound(u32), /// Cannot encode the actual type given into the target type ID. - #[error("Cannot encode {actual:?} into type with ID {expected}")] WrongShape { /// The actual kind we have to encode actual: Kind, @@ -105,7 +103,6 @@ pub enum ErrorKind { expected: u32, }, /// The types line up, but the expected length of the target type is different from the length of the input value. - #[error("Cannot encode to type; expected length {expected_len} but got length {actual_len}")] WrongLength { /// Length we have actual_len: usize, @@ -113,7 +110,6 @@ pub enum ErrorKind { expected_len: usize, }, /// We cannot encode the number given into the target type; it's out of range. - #[error("Number {value} is out of range for target type {expected}")] NumberOutOfRange { /// A string represenatation of the numeric value that was out of range. value: String, @@ -121,7 +117,6 @@ pub enum ErrorKind { expected: u32, }, /// Cannot find a variant with a matching name on the target type. - #[error("Variant {name} does not exist on type with ID {expected}")] CannotFindVariant { /// Variant name we can't find in the expected type. name: String, @@ -129,18 +124,61 @@ pub enum ErrorKind { expected: u32, }, /// Cannot find a field on our source type that's needed for the target type. - #[error("Field {name} does not exist in our source struct")] CannotFindField { /// Name of the field which was not provided. name: String, }, /// A custom error. - #[error("Custom error: {0}")] Custom(CustomError), } +impl From for ErrorKind { + fn from(err: CustomError) -> ErrorKind { + ErrorKind::Custom(err) + } +} + +impl Display for ErrorKind { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + ErrorKind::TypeNotFound(id) => write!(f, "Cannot find type with ID {id}"), + ErrorKind::WrongShape { actual, expected } => { + write!(f, "Cannot encode {actual:?} into type with ID {expected}") + } + ErrorKind::WrongLength { + actual_len, + expected_len, + } => { + write!(f, "Cannot encode to type; expected length {expected_len} but got length {actual_len}") + } + ErrorKind::NumberOutOfRange { value, expected } => { + write!( + f, + "Number {value} is out of range for target type {expected}" + ) + } + ErrorKind::CannotFindVariant { name, expected } => { + write!( + f, + "Variant {name} does not exist on type with ID {expected}" + ) + } + ErrorKind::CannotFindField { name } => { + write!(f, "Field {name} does not exist in our source struct") + } + ErrorKind::Custom(custom_error) => { + write!(f, "Custom error: {custom_error:?}") + } + } + } +} + +#[cfg(feature = "std")] type CustomError = Box; +#[cfg(not(feature = "std"))] +type CustomError = Box; + /// The kind of type that we're trying to encode. #[allow(missing_docs)] #[derive(Copy, Clone, PartialEq, Eq, Debug)] @@ -160,12 +198,27 @@ pub enum Kind { mod test { use super::*; - #[derive(thiserror::Error, Debug)] + #[derive(Debug)] enum MyError { - #[error("Foo!")] Foo, } + impl Display for MyError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{self:?}") + } + } + + #[cfg(feature = "std")] + impl std::error::Error for MyError {} + + #[cfg(not(feature = "std"))] + impl Into for MyError { + fn into(self) -> CustomError { + Box::new(self) + } + } + #[test] fn custom_error() { // Just a compile-time check that we can ergonomically provide an arbitrary custom error: diff --git a/scale-encode/src/impls/bits.rs b/scale-encode/src/impls/bits.rs index 83ee940..b36cb98 100644 --- a/scale-encode/src/impls/bits.rs +++ b/scale-encode/src/impls/bits.rs @@ -17,6 +17,7 @@ use crate::{ error::{Error, ErrorKind, Kind}, EncodeAsType, }; +use alloc::vec::Vec; use scale_info::TypeDef; impl EncodeAsType for scale_bits::Bits { diff --git a/scale-encode/src/impls/composite.rs b/scale-encode/src/impls/composite.rs index e8ee818..9edc148 100644 --- a/scale-encode/src/impls/composite.rs +++ b/scale-encode/src/impls/composite.rs @@ -17,8 +17,9 @@ use crate::{ error::{Error, ErrorKind, Kind, Location}, EncodeAsFields, EncodeAsType, Field, FieldIter, }; +use alloc::collections::BTreeMap; +use alloc::{string::ToString, vec::Vec}; use scale_info::{PortableRegistry, TypeDef}; -use std::collections::HashMap; /// This type represents named or unnamed composite values, and can be used /// to help generate `EncodeAsType` impls. It's primarily used by the exported @@ -156,7 +157,7 @@ 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: HashMap<&str, &dyn EncodeAsType> = vals_iter + let source_fields_by_name: BTreeMap<&str, &dyn EncodeAsType> = vals_iter .map(|(name, val)| (name.unwrap_or(""), val)) .collect(); diff --git a/scale-encode/src/impls/mod.rs b/scale-encode/src/impls/mod.rs index 96624b4..8c66d50 100644 --- a/scale-encode/src/impls/mod.rs +++ b/scale-encode/src/impls/mod.rs @@ -25,20 +25,30 @@ mod variant; pub use composite::Composite; pub use variant::Variant; -use crate::error::{Error, ErrorKind, Kind}; -use crate::{EncodeAsFields, EncodeAsType, FieldIter}; +use crate::{ + error::{Error, ErrorKind, Kind}, + EncodeAsFields, EncodeAsType, FieldIter, +}; +use alloc::{ + borrow::ToOwned, + boxed::Box, + collections::{BTreeMap, BTreeSet, BinaryHeap, LinkedList, VecDeque}, + rc::Rc, + string::{String, ToString}, + sync::Arc, + vec::Vec, +}; use codec::{Compact, Encode}; -use core::num::{ - NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroU128, NonZeroU16, - NonZeroU32, NonZeroU64, NonZeroU8, +use core::{ + marker::PhantomData, + num::{ + NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroU128, NonZeroU16, + NonZeroU32, NonZeroU64, NonZeroU8, + }, + ops::{Range, RangeInclusive}, + time::Duration, }; -use core::ops::{Range, RangeInclusive}; use scale_info::{PortableRegistry, TypeDef, TypeDefPrimitive}; -use std::collections::{BTreeMap, BTreeSet, BinaryHeap, LinkedList, VecDeque}; -use std::marker::PhantomData; -use std::rc::Rc; -use std::sync::Arc; -use std::time::Duration; impl EncodeAsType for bool { fn encode_as_type_to( @@ -102,7 +112,7 @@ where } } -impl<'a, T> EncodeAsType for std::borrow::Cow<'a, T> +impl<'a, T> EncodeAsType for alloc::borrow::Cow<'a, T> where T: 'a + EncodeAsType + ToOwned + ?Sized, { @@ -527,9 +537,10 @@ where mod test { use super::*; use crate::{EncodeAsFields, Field}; + use alloc::vec; use codec::Decode; + use core::fmt::Debug; use scale_info::TypeInfo; - use std::fmt::Debug; /// Given a type definition, return type ID and registry representing it. fn make_type() -> (u32, PortableRegistry) { @@ -686,7 +697,7 @@ mod test { assert_encodes_like_codec(-1234); assert_encodes_like_codec(100_000_000_000_000u128); assert_encodes_like_codec(()); - assert_encodes_like_codec(std::marker::PhantomData::<()>); + assert_encodes_like_codec(core::marker::PhantomData::<()>); assert_encodes_like_codec([1, 2, 3, 4, 5]); assert_encodes_like_codec([1u8, 2, 3, 4, 5]); assert_encodes_like_codec(vec![1, 2, 3, 4, 5]); @@ -701,7 +712,7 @@ mod test { // These don't impl TypeInfo so we have to provide the target type to encode to & compare with: assert_value_roundtrips_to(Arc::new("hi"), "hi".to_string()); assert_value_roundtrips_to(Rc::new("hi"), "hi".to_string()); - // encodes_like_codec(std::time::Duration::from_millis(123456)); + // encodes_like_codec(core::time::Duration::from_millis(123456)); } #[test] diff --git a/scale-encode/src/impls/primitive_types.rs b/scale-encode/src/impls/primitive_types.rs index 737bc39..2d68eeb 100644 --- a/scale-encode/src/impls/primitive_types.rs +++ b/scale-encode/src/impls/primitive_types.rs @@ -14,6 +14,7 @@ // limitations under the License. use crate::{error::Error, EncodeAsType}; +use alloc::vec::Vec; use primitive_types::{H128, H160, H256, H384, H512, H768}; macro_rules! impl_encode { diff --git a/scale-encode/src/impls/variant.rs b/scale-encode/src/impls/variant.rs index c29e671..4792142 100644 --- a/scale-encode/src/impls/variant.rs +++ b/scale-encode/src/impls/variant.rs @@ -17,6 +17,7 @@ use crate::{ error::{Error, ErrorKind, Kind}, EncodeAsFields, EncodeAsType, Field, }; +use alloc::{string::ToString, vec::Vec}; use codec::Encode; use scale_info::{PortableRegistry, TypeDef}; diff --git a/scale-encode/src/lib.rs b/scale-encode/src/lib.rs index 5a7597d..86450b5 100644 --- a/scale-encode/src/lib.rs +++ b/scale-encode/src/lib.rs @@ -13,6 +13,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![cfg_attr(not(feature = "std"), no_std)] + /*! `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 @@ -139,16 +141,28 @@ assert_encodes_to( */ #![deny(missing_docs)] +extern crate alloc; + mod impls; pub mod error; +// This is exported for generated derive code to use, to be compatible with std or no-std as needed. +#[doc(hidden)] +pub use alloc::vec::Vec; + pub use error::Error; // Useful types to help implement EncodeAsType/Fields with: pub use crate::impls::{Composite, Variant}; pub use scale_info::PortableRegistry; +/// Re-exports of external crates. +pub mod ext { + #[cfg(feature = "primitive-types")] + pub use primitive_types; +} + /// This trait signals that some static type can possibly be SCALE encoded given some /// `type_id` and [`PortableRegistry`] which dictates the expected encoding. pub trait EncodeAsType { @@ -232,18 +246,6 @@ impl<'a> Field<'a> { pub trait FieldIter<'a>: Iterator> {} impl<'a, T> FieldIter<'a> for T where T: Iterator> {} -#[cfg(test)] -mod test { - use super::*; - - // 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) {} - } -} - /// The `EncodeAsType` derive macro can be used to implement `EncodeAsType` /// on structs and enums whose fields all implement `EncodeAsType`. /// @@ -313,3 +315,16 @@ mod test { /// 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) {} + } +} diff --git a/scale-encode/tests/macros/pass_derive_generics.rs b/scale-encode/tests/macros/pass_derive_generics.rs index 164cc5b..6bb8e5e 100644 --- a/scale-encode/tests/macros/pass_derive_generics.rs +++ b/scale-encode/tests/macros/pass_derive_generics.rs @@ -21,20 +21,20 @@ struct NotEncodeAsType; #[derive(EncodeAsType)] enum Bar { Wibble(bool, T, U, V), - Wobble + Wobble, } // This impls EncodeAsType ok; we set no default trait bounds. #[derive(EncodeAsType)] #[encode_as_type(trait_bounds = "")] enum NoTraitBounds { - Wibble(std::marker::PhantomData), + Wibble(core::marker::PhantomData), } // Structs (and const bounds) impl EncodeAsType OK. #[derive(EncodeAsType)] struct MyStruct { - array: [Bar; V] + array: [Bar; V], } fn can_encode_as_type() {} @@ -44,4 +44,4 @@ fn main() { can_encode_as_type::>(); can_encode_as_type::>(); can_encode_as_type::>(); -} \ No newline at end of file +} diff --git a/testing/no_std/Cargo.toml b/testing/no_std/Cargo.toml new file mode 100644 index 0000000..00979c0 --- /dev/null +++ b/testing/no_std/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "test-no-std" +description = "Testing no_std build of scale-encode" +readme = "README.md" + +version.workspace = true +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true +keywords.workspace = true +include.workspace = true + +[dependencies] +scale-encode = { path = "../../scale-encode", default-features = false, features = ["primitive-types", "derive", "bits"] } diff --git a/testing/no_std/README.md b/testing/no_std/README.md new file mode 100644 index 0000000..7ed0fe1 --- /dev/null +++ b/testing/no_std/README.md @@ -0,0 +1 @@ +test no_std build of scale-encode and scale-encode-derive diff --git a/testing/no_std/src/lib.rs b/testing/no_std/src/lib.rs new file mode 100644 index 0000000..9eba032 --- /dev/null +++ b/testing/no_std/src/lib.rs @@ -0,0 +1,28 @@ +#![no_std] +extern crate alloc; + +use scale_encode::EncodeAsType; + +pub struct NotEncodeAsType; + +// Enums with generic params can impl EncodeAsType. +#[derive(EncodeAsType)] +pub enum Bar { + Wibble(bool, T, U, V), + Wobble, +} + +// This impls EncodeAsType ok; we set no default trait bounds. +#[derive(EncodeAsType)] +#[encode_as_type(trait_bounds = "")] +pub enum NoTraitBounds { + Wibble(core::marker::PhantomData), +} + +// Structs (and const bounds) impl EncodeAsType OK. +#[derive(EncodeAsType)] +pub struct MyStruct { + array: [Bar; V], +} + +pub fn can_encode_as_type() {}