Unverified Commit 4510676a authored by Keith Yeung's avatar Keith Yeung Committed by GitHub
Browse files

Support custom trait bounds on MaxEncodedLen derive macro (#279)

* Support custom trait bounds on MaxEncodedLen derive macro

* Bump version to 2.2.0-rc.3

* Reorder error messaging
parent ddd7960d
Pipeline #144387 passed with stages
in 21 minutes and 59 seconds
......@@ -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
......
......@@ -452,7 +452,7 @@ checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
[[package]]
name = "parity-scale-codec"
version = "2.2.0-rc.2"
version = "2.2.0-rc.3"
dependencies = [
"arbitrary",
"arrayvec",
......@@ -470,7 +470,7 @@ dependencies = [
[[package]]
name = "parity-scale-codec-derive"
version = "2.2.0-rc.2"
version = "2.2.0-rc.3"
dependencies = [
"parity-scale-codec",
"proc-macro-crate",
......
[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 <admin@parity.io>"]
license = "Apache-2.0"
repository = "https://github.com/paritytech/parity-scale-codec"
......@@ -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 }
......
[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 <admin@parity.io>"]
license = "Apache-2.0"
edition = "2018"
......
......@@ -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,
......@@ -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);
......@@ -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 {
......@@ -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.")
.to_compile_error()
}
}
......
......@@ -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> {
......@@ -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> {
......@@ -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 {
......@@ -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:
......@@ -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()? {
......
......@@ -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 {
......
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")]
......
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)]
......
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)]
......
......@@ -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 {
| ^^^^^
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment