Skip to content

Commit

Permalink
incorporate changes made to Substrate version of MaxEncodedLen
Browse files Browse the repository at this point in the history
Note: doesn't include the MaxEncodedLen impl for H160, H256, H512.
A substrate companion will be necessary to re-add those.
  • Loading branch information
coriolinus committed Jun 22, 2021
1 parent ccfa540 commit 9f24e9d
Show file tree
Hide file tree
Showing 23 changed files with 308 additions and 16 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ and this crate adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.h

## Unreleased

## [2.2.0-rc.1] - 2021-06-21
## [2.2.0-rc.1] - 2021-06-22

### Added
- `MaxEncodedLen` trait for items that have a statically known maximum encoded size. ([#268](https://github.com/paritytech/parity-scale-codec/pull/268))
Expand Down
2 changes: 1 addition & 1 deletion derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ pub fn compact_as_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStr
}

/// Derive `MaxEncodedLen`.
#[proc_macro_derive(MaxEncodedLen)]
#[proc_macro_derive(MaxEncodedLen, attributes(max_encoded_len_mod))]
pub fn derive_max_encoded_len(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
max_encoded_len::derive_max_encoded_len(input)
}
87 changes: 83 additions & 4 deletions derive/src/max_encoded_len.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use proc_macro_crate::{crate_name, FoundCrate};
use proc_macro2::{Span, Ident};
use quote::{quote, quote_spanned};
use syn::{
Data, DeriveInput, Fields, GenericParam, Generics, TraitBound, Type, TypeParamBound,
Data, DeriveInput, Error, Fields, GenericParam, Generics, Meta, TraitBound, Type, TypeParamBound,
parse_quote, spanned::Spanned,
};

Expand All @@ -26,8 +28,10 @@ pub fn derive_max_encoded_len(input: proc_macro::TokenStream) -> proc_macro::Tok
Err(e) => return e.to_compile_error().into(),
};

let crate_import = crate::include_parity_scale_codec_crate();
let mel_trait: TraitBound = parse_quote!(_parity_scale_codec::MaxEncodedLen);
let mel_trait = match max_encoded_len_trait(&input) {
Ok(mel_trait) => mel_trait,
Err(e) => return e.to_compile_error().into(),
};

let name = &input.ident;
let generics = add_trait_bounds(input.generics, mel_trait.clone());
Expand All @@ -37,7 +41,6 @@ pub fn derive_max_encoded_len(input: proc_macro::TokenStream) -> proc_macro::Tok

quote::quote!(
const _: () = {
#crate_import
impl #impl_generics #mel_trait for #name #ty_generics #where_clause {
fn max_encoded_len() -> usize {
#data_expr
Expand All @@ -48,6 +51,82 @@ pub fn derive_max_encoded_len(input: proc_macro::TokenStream) -> proc_macro::Tok
.into()
}

fn max_encoded_len_trait(input: &DeriveInput) -> syn::Result<TraitBound> {
let mel = {
const EXPECT_LIST: &str = "expect: #[max_encoded_len_mod(path::to::crate)]";
const EXPECT_PATH: &str = "expect: path::to::crate";

macro_rules! return_err {
($wrong_style:expr, $err:expr) => {
return Err(Error::new($wrong_style.span(), $err))
};
}

let mut mel_crates = Vec::with_capacity(2);
mel_crates.extend(input
.attrs
.iter()
.filter(|attr| attr.path == parse_quote!(max_encoded_len_mod))
.take(2)
.map(|attr| {
let meta_list = match attr.parse_meta()? {
Meta::List(meta_list) => meta_list,
Meta::Path(wrong_style) => return_err!(wrong_style, EXPECT_LIST),
Meta::NameValue(wrong_style) => return_err!(wrong_style, EXPECT_LIST),
};
if meta_list.nested.len() != 1 {
return_err!(meta_list, "expected exactly 1 item");
}
let first_nested =
meta_list.nested.into_iter().next().expect("length checked above");
let meta = match first_nested {
syn::NestedMeta::Lit(l) => {
return_err!(l, "expected a path item, not a literal")
}
syn::NestedMeta::Meta(meta) => meta,
};
let path = match meta {
Meta::Path(path) => path,
Meta::List(ref wrong_style) => return_err!(wrong_style, EXPECT_PATH),
Meta::NameValue(ref wrong_style) => return_err!(wrong_style, EXPECT_PATH),
};
Ok(path)
})
.collect::<Result<Vec<_>, _>>()?);

// we have to return `Result<Ident, Error>` here in order to satisfy the trait
// bounds for `.or_else` for `generate_crate_access_2018`, even though `Option<Ident>`
// would be more natural in this circumstance.
match mel_crates.len() {
0 => Err(Error::new(
input.span(),
"this error is spurious and swallowed by the or_else below",
)),
1 => Ok(mel_crates.into_iter().next().expect("length is checked")),
_ => return_err!(mel_crates[1], "duplicate max_encoded_len_mod definition"),
}
}
.or_else(|_| crate_access().map(|ident| ident.into()))?;
Ok(parse_quote!(#mel::MaxEncodedLen))
}

/// Generate the crate access for the crate using 2018 syntax.
fn crate_access() -> Result<syn::Ident, Error> {
const DEF_CRATE: &str = "parity-scale-codec";
match crate_name(DEF_CRATE) {
Ok(FoundCrate::Itself) => {
let name = DEF_CRATE.to_string().replace("-", "_");
Ok(syn::Ident::new(&name, Span::call_site()))
},
Ok(FoundCrate::Name(name)) => {
Ok(Ident::new(&name, Span::call_site()))
},
Err(e) => {
Err(Error::new(Span::call_site(), e))
}
}
}

// Add a bound `T: MaxEncodedLen` to every type parameter T.
fn add_trait_bounds(mut generics: Generics, mel_trait: TraitBound) -> Generics {
for param in &mut generics.params {
Expand Down
22 changes: 21 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -291,13 +291,19 @@ pub use self::depth_limit::DecodeLimit;
pub use self::encode_append::EncodeAppend;
pub use self::encode_like::{EncodeLike, Ref};
pub use max_encoded_len::MaxEncodedLen;
/// Derive [`MaxEncodedLen`][max_encoded_len::MaxEncodedLen].
/// Derive macro for [`MaxEncodedLen`][max_encoded_len::MaxEncodedLen].
///
/// # Examples
///
/// ```
/// # use parity_scale_codec::{Encode, MaxEncodedLen};
/// #[derive(Encode, MaxEncodedLen)]
/// struct Example;
/// ```
///
/// ```
/// # use parity_scale_codec::{Encode, MaxEncodedLen};
/// #[derive(Encode, MaxEncodedLen)]
/// struct TupleStruct(u8, u32);
///
/// assert_eq!(TupleStruct::max_encoded_len(), u8::max_encoded_len() + u32::max_encoded_len());
Expand All @@ -314,5 +320,19 @@ pub use max_encoded_len::MaxEncodedLen;
/// assert_eq!(GenericEnum::<u8>::max_encoded_len(), u8::max_encoded_len() + u8::max_encoded_len());
/// assert_eq!(GenericEnum::<u128>::max_encoded_len(), u8::max_encoded_len() + u128::max_encoded_len());
/// ```
///
/// # Within other macros
///
/// Sometimes the `MaxEncodedLen` trait and macro are used within another macro, and it can't be
/// guaranteed that the `max_encoded_len` module is available at the call site. In that case, the
/// macro should reexport the `max_encoded_len` module and specify the path to the reexport:
///
/// ```ignore
/// pub use parity_scale_codec::max_encoded_len;
///
/// #[derive(Encode, MaxEncodedLen)]
/// #[max_encoded_len_mod($crate::max_encoded_len)]
/// struct Example;
/// ```
#[cfg(feature = "derive")]
pub use parity_scale_codec_derive::MaxEncodedLen;
14 changes: 9 additions & 5 deletions src/max_encoded_len.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.

//! `trait MaxEncodedLen` bounds the maximum encoded length of items.

#![cfg_attr(not(feature = "std"), no_std)]

use crate::{Compact, Encode};
use impl_trait_for_tuples::impl_for_tuples;
use core::{mem, marker::PhantomData};
Expand Down Expand Up @@ -55,15 +59,15 @@ macro_rules! impl_compact {
}

impl_compact!(
// https://github.com/paritytech/parity-scale-codec/blob/f0341dabb01aa9ff0548558abb6dcc5c31c669a1/src/compact.rs#L261
// github.com/paritytech/parity-scale-codec/blob/f0341dabb01aa9ff0548558abb6dcc5c31c669a1/src/compact.rs#L261
u8 => 2;
// https://github.com/paritytech/parity-scale-codec/blob/f0341dabb01aa9ff0548558abb6dcc5c31c669a1/src/compact.rs#L291
// github.com/paritytech/parity-scale-codec/blob/f0341dabb01aa9ff0548558abb6dcc5c31c669a1/src/compact.rs#L291
u16 => 4;
// https://github.com/paritytech/parity-scale-codec/blob/f0341dabb01aa9ff0548558abb6dcc5c31c669a1/src/compact.rs#L326
// github.com/paritytech/parity-scale-codec/blob/f0341dabb01aa9ff0548558abb6dcc5c31c669a1/src/compact.rs#L326
u32 => 5;
// https://github.com/paritytech/parity-scale-codec/blob/f0341dabb01aa9ff0548558abb6dcc5c31c669a1/src/compact.rs#L369
// github.com/paritytech/parity-scale-codec/blob/f0341dabb01aa9ff0548558abb6dcc5c31c669a1/src/compact.rs#L369
u64 => 9;
// https://github.com/paritytech/parity-scale-codec/blob/f0341dabb01aa9ff0548558abb6dcc5c31c669a1/src/compact.rs#L413
// github.com/paritytech/parity-scale-codec/blob/f0341dabb01aa9ff0548558abb6dcc5c31c669a1/src/compact.rs#L413
u128 => 17;
);

Expand Down
9 changes: 9 additions & 0 deletions tests/max_encoded_len_ui/list_list_item.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use parity_scale_codec::{Encode, MaxEncodedLen};

#[derive(Encode, MaxEncodedLen)]
#[max_encoded_len_mod(foo())]
struct Example;

fn main() {
let _ = Example::max_encoded_len();
}
18 changes: 18 additions & 0 deletions tests/max_encoded_len_ui/list_list_item.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
error: expect: path::to::crate
--> $DIR/list_list_item.rs:4:23
|
4 | #[max_encoded_len_mod(foo())]
| ^^^

error[E0599]: no function or associated item named `max_encoded_len` found for struct `Example` in the current scope
--> $DIR/list_list_item.rs:8:19
|
5 | struct Example;
| --------------- function or associated item `max_encoded_len` not found for this
...
8 | let _ = Example::max_encoded_len();
| ^^^^^^^^^^^^^^^ function or associated item not found in `Example`
|
= help: items from traits can only be used if the trait is implemented and in scope
= note: the following trait defines an item `max_encoded_len`, perhaps you need to implement it:
candidate #1: `MaxEncodedLen`
9 changes: 9 additions & 0 deletions tests/max_encoded_len_ui/literal_list_item.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use parity_scale_codec::{Encode, MaxEncodedLen};

#[derive(Encode, MaxEncodedLen)]
#[max_encoded_len_mod("frame_support::max_encoded_len")]
struct Example;

fn main() {
let _ = Example::max_encoded_len();
}
18 changes: 18 additions & 0 deletions tests/max_encoded_len_ui/literal_list_item.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
error: expected a path item, not a literal
--> $DIR/literal_list_item.rs:4:23
|
4 | #[max_encoded_len_mod("frame_support::max_encoded_len")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0599]: no function or associated item named `max_encoded_len` found for struct `Example` in the current scope
--> $DIR/literal_list_item.rs:8:19
|
5 | struct Example;
| --------------- function or associated item `max_encoded_len` not found for this
...
8 | let _ = Example::max_encoded_len();
| ^^^^^^^^^^^^^^^ function or associated item not found in `Example`
|
= help: items from traits can only be used if the trait is implemented and in scope
= note: the following trait defines an item `max_encoded_len`, perhaps you need to implement it:
candidate #1: `MaxEncodedLen`
9 changes: 9 additions & 0 deletions tests/max_encoded_len_ui/name_value_attr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use parity_scale_codec::{Encode, MaxEncodedLen};

#[derive(Encode, MaxEncodedLen)]
#[max_encoded_len_mod = "frame_support::max_encoded_len"]
struct Example;

fn main() {
let _ = Example::max_encoded_len();
}
18 changes: 18 additions & 0 deletions tests/max_encoded_len_ui/name_value_attr.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
error: expect: #[max_encoded_len_mod(path::to::crate)]
--> $DIR/name_value_attr.rs:4:3
|
4 | #[max_encoded_len_mod = "frame_support::max_encoded_len"]
| ^^^^^^^^^^^^^^^^^^^

error[E0599]: no function or associated item named `max_encoded_len` found for struct `Example` in the current scope
--> $DIR/name_value_attr.rs:8:19
|
5 | struct Example;
| --------------- function or associated item `max_encoded_len` not found for this
...
8 | let _ = Example::max_encoded_len();
| ^^^^^^^^^^^^^^^ function or associated item not found in `Example`
|
= help: items from traits can only be used if the trait is implemented and in scope
= note: the following trait defines an item `max_encoded_len`, perhaps you need to implement it:
candidate #1: `MaxEncodedLen`
9 changes: 9 additions & 0 deletions tests/max_encoded_len_ui/name_value_list_item.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use parity_scale_codec::{Encode, MaxEncodedLen};

#[derive(Encode, MaxEncodedLen)]
#[max_encoded_len_mod(path = "frame_support::max_encoded_len")]
struct Example;

fn main() {
let _ = Example::max_encoded_len();
}
18 changes: 18 additions & 0 deletions tests/max_encoded_len_ui/name_value_list_item.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
error: expect: path::to::crate
--> $DIR/name_value_list_item.rs:4:23
|
4 | #[max_encoded_len_mod(path = "frame_support::max_encoded_len")]
| ^^^^

error[E0599]: no function or associated item named `max_encoded_len` found for struct `Example` in the current scope
--> $DIR/name_value_list_item.rs:8:19
|
5 | struct Example;
| --------------- function or associated item `max_encoded_len` not found for this
...
8 | let _ = Example::max_encoded_len();
| ^^^^^^^^^^^^^^^ function or associated item not found in `Example`
|
= help: items from traits can only be used if the trait is implemented and in scope
= note: the following trait defines an item `max_encoded_len`, perhaps you need to implement it:
candidate #1: `MaxEncodedLen`
9 changes: 9 additions & 0 deletions tests/max_encoded_len_ui/no_path_list_items.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use parity_scale_codec::{Encode, MaxEncodedLen};

#[derive(Encode, MaxEncodedLen)]
#[max_encoded_len_mod]
struct Example;

fn main() {
let _ = Example::max_encoded_len();
}
18 changes: 18 additions & 0 deletions tests/max_encoded_len_ui/no_path_list_items.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
error: expect: #[max_encoded_len_mod(path::to::crate)]
--> $DIR/no_path_list_items.rs:4:3
|
4 | #[max_encoded_len_mod]
| ^^^^^^^^^^^^^^^^^^^

error[E0599]: no function or associated item named `max_encoded_len` found for struct `Example` in the current scope
--> $DIR/no_path_list_items.rs:8:19
|
5 | struct Example;
| --------------- function or associated item `max_encoded_len` not found for this
...
8 | let _ = Example::max_encoded_len();
| ^^^^^^^^^^^^^^^ function or associated item not found in `Example`
|
= help: items from traits can only be used if the trait is implemented and in scope
= note: the following trait defines an item `max_encoded_len`, perhaps you need to implement it:
candidate #1: `MaxEncodedLen`
2 changes: 1 addition & 1 deletion tests/max_encoded_len_ui/not_encode.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use parity_scale_codec::MaxEncodedLen;
use parity_scale_codec::{MaxEncodedLen};

#[derive(MaxEncodedLen)]
struct NotEncode;
Expand Down
2 changes: 1 addition & 1 deletion tests/max_encoded_len_ui/not_mel.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use parity_scale_codec::{MaxEncodedLen, Encode};
use parity_scale_codec::{Encode, MaxEncodedLen};

#[derive(Encode)]
struct NotMel;
Expand Down
9 changes: 9 additions & 0 deletions tests/max_encoded_len_ui/path_attr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use parity_scale_codec::{Encode, MaxEncodedLen};

#[derive(Encode, MaxEncodedLen)]
#[max_encoded_len_mod]
struct Example;

fn main() {
let _ = Example::max_encoded_len();
}
18 changes: 18 additions & 0 deletions tests/max_encoded_len_ui/path_attr.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
error: expect: #[max_encoded_len_mod(path::to::crate)]
--> $DIR/path_attr.rs:4:3
|
4 | #[max_encoded_len_mod]
| ^^^^^^^^^^^^^^^^^^^

error[E0599]: no function or associated item named `max_encoded_len` found for struct `Example` in the current scope
--> $DIR/path_attr.rs:8:19
|
5 | struct Example;
| --------------- function or associated item `max_encoded_len` not found for this
...
8 | let _ = Example::max_encoded_len();
| ^^^^^^^^^^^^^^^ function or associated item not found in `Example`
|
= help: items from traits can only be used if the trait is implemented and in scope
= note: the following trait defines an item `max_encoded_len`, perhaps you need to implement it:
candidate #1: `MaxEncodedLen`
Loading

0 comments on commit 9f24e9d

Please sign in to comment.