Skip to content
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ and this crate adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.h

## Unreleased

## [2.2.0-rc.3] - 2021-06-25
- Add support for custom where bounds `codec(mel_bound(T: MaxEncodedLen))` when deriving the traits. PR #279

## [2.2.0-rc.2] - 2021-06-24

- Updated syntax of `#[codec(crate = <path>)]` attribute macro: no longer expects the crate path to
Expand Down
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "parity-scale-codec"
description = "SCALE - Simple Concatenating Aggregated Little Endians"
version = "2.2.0-rc.2"
version = "2.2.0-rc.3"
authors = ["Parity Technologies <[email protected]>"]
license = "Apache-2.0"
repository = "https://github.com/paritytech/parity-scale-codec"
Expand All @@ -11,7 +11,7 @@ edition = "2018"
[dependencies]
arrayvec = { version = "0.7", default-features = false }
serde = { version = "1.0.102", optional = true }
parity-scale-codec-derive = { path = "derive", version = "2.2.0-rc.2", default-features = false, optional = true }
parity-scale-codec-derive = { path = "derive", version = "2.2.0-rc.3", default-features = false, optional = true }
bitvec = { version = "0.20.1", default-features = false, features = ["alloc"], optional = true }
byte-slice-cast = { version = "1.0.0", default-features = false }
generic-array = { version = "0.14.4", optional = true }
Expand Down
2 changes: 1 addition & 1 deletion derive/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "parity-scale-codec-derive"
description = "Serialization and deserialization derive macro for Parity SCALE Codec"
version = "2.2.0-rc.2"
version = "2.2.0-rc.3"
authors = ["Parity Technologies <[email protected]>"]
license = "Apache-2.0"
edition = "2018"
Expand Down
16 changes: 13 additions & 3 deletions derive/src/max_encoded_len.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use crate::utils::codec_crate_path;
use crate::utils::{self, codec_crate_path, custom_mel_trait_bound};
use quote::{quote, quote_spanned};
use syn::{
Data, DeriveInput, Fields, GenericParam, Generics, TraitBound, Type, TypeParamBound,
Expand All @@ -33,7 +33,11 @@ pub fn derive_max_encoded_len(input: proc_macro::TokenStream) -> proc_macro::Tok
};

let name = &input.ident;
let generics = add_trait_bounds(input.generics, mel_trait.clone());
let generics = if let Some(custom_bound) = custom_mel_trait_bound(&input.attrs) {
add_custom_trait_bounds(input.generics, custom_bound)
} else {
add_trait_bounds(input.generics, mel_trait.clone())
};
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();

let data_expr = data_length_expr(&input.data);
Expand Down Expand Up @@ -65,6 +69,12 @@ fn add_trait_bounds(mut generics: Generics, mel_trait: TraitBound) -> Generics {
generics
}

// Add custom trait bounds to the type parameters as specified by the user.
fn add_custom_trait_bounds(mut generics: Generics, custom_bound: utils::TraitBounds) -> Generics {
generics.make_where_clause().predicates.extend(custom_bound);
generics
}

/// generate an expression to sum up the max encoded length from several fields
fn fields_length_expr(fields: &Fields) -> proc_macro2::TokenStream {
let type_iter: Box<dyn Iterator<Item = &Type>> = match fields {
Expand Down Expand Up @@ -124,7 +134,7 @@ fn data_length_expr(data: &Data) -> proc_macro2::TokenStream {
Data::Union(ref data) => {
// https://github.com/paritytech/parity-scale-codec/
// blob/f0341dabb01aa9ff0548558abb6dcc5c31c669a1/derive/src/encode.rs#L290-L293
syn::Error::new(data.union_token.span(), "Union types are not supported")
syn::Error::new(data.union_token.span(), "Union types are not supported.")
Copy link
Contributor

Choose a reason for hiding this comment

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

This change is going to require downstream changes to a lot of UI tests. It may well be worth doing anyway, but be prepared to follow up.

.to_compile_error()
}
}
Expand Down
23 changes: 19 additions & 4 deletions derive/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,8 +201,9 @@ impl<N: Parse> Parse for CustomTraitBound<N> {

syn::custom_keyword!(encode_bound);
syn::custom_keyword!(decode_bound);
syn::custom_keyword!(mel_bound);

/// Look for a `#[codec(decode_bound(T: Decode))]`in the given attributes.
/// Look for a `#[codec(decode_bound(T: Decode))]` in the given attributes.
///
/// If found, it should be used as trait bounds when deriving the `Decode` trait.
pub fn custom_decode_trait_bound(attrs: &[Attribute]) -> Option<TraitBounds> {
Expand All @@ -211,7 +212,7 @@ pub fn custom_decode_trait_bound(attrs: &[Attribute]) -> Option<TraitBounds> {
})
}

/// Look for a `#[codec(encode_bound(T: Encode))]`in the given attributes.
/// Look for a `#[codec(encode_bound(T: Encode))]` in the given attributes.
///
/// If found, it should be used as trait bounds when deriving the `Encode` trait.
pub fn custom_encode_trait_bound(attrs: &[Attribute]) -> Option<TraitBounds> {
Expand All @@ -220,6 +221,15 @@ pub fn custom_encode_trait_bound(attrs: &[Attribute]) -> Option<TraitBounds> {
})
}

/// Look for a `#[codec(mel_bound(T: MaxEncodedLen))]` in the given attributes.
///
/// If found, it should be used as the trait bounds when deriving the `MaxEncodedLen` trait.
pub fn custom_mel_trait_bound(attrs: &[Attribute]) -> Option<TraitBounds> {
find_meta_item(attrs.iter(), |meta: CustomTraitBound<mel_bound>| {
Some(meta.bounds)
})
}

/// Given a set of named fields, return an iterator of `Field` where all fields
/// marked `#[codec(skip)]` are filtered out.
pub fn filter_skip_named<'a>(fields: &'a syn::FieldsNamed) -> impl Iterator<Item=&Field> + 'a {
Expand All @@ -242,6 +252,9 @@ pub fn filter_skip_unnamed<'a>(fields: &'a syn::FieldsUnnamed) -> impl Iterator<
/// The top level can have the following attributes:
///
/// * `#[codec(dumb_trait_bound)]`
/// * `#[codec(encode_bound(T: Encode))]`
/// * `#[codec(decode_bound(T: Decode))]`
/// * `#[codec(mel_bound(T: MaxEncodedLen))]`
/// * `#[codec(crate = path::to::crate)]
///
/// Fields can have the following attributes:
Expand Down Expand Up @@ -361,11 +374,13 @@ fn check_variant_attribute(attr: &Attribute) -> syn::Result<()> {
// Only `#[codec(dumb_trait_bound)]` is accepted as top attribute
fn check_top_attribute(attr: &Attribute) -> syn::Result<()> {
let top_error = "Invalid attribute: only `#[codec(dumb_trait_bound)]`, \
`#[codec(encode_bound(T: Encode))]`, `#[codec(crate = path::to::crate)]`, or \
`#[codec(decode_bound(T: Decode))]` are accepted as top attribute";
`#[codec(crate = path::to::crate)]`, `#[codec(encode_bound(T: Encode))]`, \
`#[codec(decode_bound(T: Decode))]`, or `#[codec(mel_bound(T: MaxEncodedLen))]` \
are accepted as top attribute";
if attr.path.is_ident("codec")
&& attr.parse_args::<CustomTraitBound<encode_bound>>().is_err()
&& attr.parse_args::<CustomTraitBound<decode_bound>>().is_err()
&& attr.parse_args::<CustomTraitBound<mel_bound>>().is_err()
&& codec_crate_path_inner(attr).is_none()
{
match attr.parse_meta()? {
Expand Down
37 changes: 37 additions & 0 deletions tests/max_encoded_len.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,43 @@ fn tuple_generic_max_length() {
assert_eq!(TupleGeneric::<u32>::max_encoded_len(), u32::max_encoded_len() * 2);
}

#[derive(Encode)]
struct ConstU32<const N: u32>;

trait Get<T>: Encode {
fn get() -> T;
}

impl<const N: u32> Get<u32> for ConstU32<N> {
fn get() -> u32 {
N
}
}

#[derive(Encode)]
struct SomeVec<T, N> {
element: T,
size: N,
}

impl<T: MaxEncodedLen, N: Get<u32>> MaxEncodedLen for SomeVec<T, N> {
fn max_encoded_len() -> usize {
T::max_encoded_len() * N::get() as usize
}
}

#[derive(Encode, MaxEncodedLen)]
#[codec(mel_bound(N: Get<u32>))]
struct SizeGeneric<N> {
vec: SomeVec<u64, N>,
}

#[test]
fn some_vec_max_length() {
assert_eq!(SomeVec::<u64, ConstU32<3>>::max_encoded_len(), u64::max_encoded_len() * 3);
assert_eq!(SizeGeneric::<ConstU32<5>>::max_encoded_len(), u64::max_encoded_len() * 5);
}

#[derive(Encode, MaxEncodedLen)]
#[allow(unused)]
enum UnitEnum {
Expand Down
2 changes: 1 addition & 1 deletion tests/max_encoded_len_ui/crate_str.stderr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error: Invalid attribute: only `#[codec(dumb_trait_bound)]`, `#[codec(encode_bound(T: Encode))]`, `#[codec(crate = path::to::crate)]`, or `#[codec(decode_bound(T: Decode))]` are accepted as top attribute
error: Invalid attribute: only `#[codec(dumb_trait_bound)]`, `#[codec(crate = path::to::crate)]`, `#[codec(encode_bound(T: Encode))]`, `#[codec(decode_bound(T: Decode))]`, or `#[codec(mel_bound(T: MaxEncodedLen))]` are accepted as top attribute
--> $DIR/crate_str.rs:4:9
|
4 | #[codec(crate = "parity_scale_codec")]
Expand Down
2 changes: 1 addition & 1 deletion tests/max_encoded_len_ui/incomplete_attr.stderr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error: Invalid attribute: only `#[codec(dumb_trait_bound)]`, `#[codec(encode_bound(T: Encode))]`, `#[codec(crate = path::to::crate)]`, or `#[codec(decode_bound(T: Decode))]` are accepted as top attribute
error: Invalid attribute: only `#[codec(dumb_trait_bound)]`, `#[codec(crate = path::to::crate)]`, `#[codec(encode_bound(T: Encode))]`, `#[codec(decode_bound(T: Decode))]`, or `#[codec(mel_bound(T: MaxEncodedLen))]` are accepted as top attribute
--> $DIR/incomplete_attr.rs:4:9
|
4 | #[codec(crate)]
Expand Down
2 changes: 1 addition & 1 deletion tests/max_encoded_len_ui/missing_crate_specifier.stderr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error: Invalid attribute: only `#[codec(dumb_trait_bound)]`, `#[codec(encode_bound(T: Encode))]`, `#[codec(crate = path::to::crate)]`, or `#[codec(decode_bound(T: Decode))]` are accepted as top attribute
error: Invalid attribute: only `#[codec(dumb_trait_bound)]`, `#[codec(crate = path::to::crate)]`, `#[codec(encode_bound(T: Encode))]`, `#[codec(decode_bound(T: Decode))]`, or `#[codec(mel_bound(T: MaxEncodedLen))]` are accepted as top attribute
--> $DIR/missing_crate_specifier.rs:4:9
|
4 | #[codec(parity_scale_codec)]
Expand Down
6 changes: 0 additions & 6 deletions tests/max_encoded_len_ui/union.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,3 @@ error: Union types are not supported.
|
4 | union Union {
| ^^^^^

error: Union types are not supported
--> $DIR/union.rs:4:1
|
4 | union Union {
| ^^^^^