diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d9958ab..36f44459 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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)) diff --git a/derive/src/lib.rs b/derive/src/lib.rs index cbc2a1fc..c95ce27d 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -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) } diff --git a/derive/src/max_encoded_len.rs b/derive/src/max_encoded_len.rs index b1f177f8..b26a8991 100644 --- a/derive/src/max_encoded_len.rs +++ b/derive/src/max_encoded_len.rs @@ -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, }; @@ -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()); @@ -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 @@ -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 { + 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::, _>>()?); + + // we have to return `Result` here in order to satisfy the trait + // bounds for `.or_else` for `generate_crate_access_2018`, even though `Option` + // 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 { + 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 { diff --git a/src/lib.rs b/src/lib.rs index 84b158c3..fa6d5774 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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()); @@ -314,5 +320,19 @@ pub use max_encoded_len::MaxEncodedLen; /// assert_eq!(GenericEnum::::max_encoded_len(), u8::max_encoded_len() + u8::max_encoded_len()); /// assert_eq!(GenericEnum::::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; diff --git a/src/max_encoded_len.rs b/src/max_encoded_len.rs index 62f017bc..aac7f6ef 100644 --- a/src/max_encoded_len.rs +++ b/src/max_encoded_len.rs @@ -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}; @@ -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; ); diff --git a/tests/max_encoded_len_ui/list_list_item.rs b/tests/max_encoded_len_ui/list_list_item.rs new file mode 100644 index 00000000..f1041121 --- /dev/null +++ b/tests/max_encoded_len_ui/list_list_item.rs @@ -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(); +} diff --git a/tests/max_encoded_len_ui/list_list_item.stderr b/tests/max_encoded_len_ui/list_list_item.stderr new file mode 100644 index 00000000..69926e3f --- /dev/null +++ b/tests/max_encoded_len_ui/list_list_item.stderr @@ -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` diff --git a/tests/max_encoded_len_ui/literal_list_item.rs b/tests/max_encoded_len_ui/literal_list_item.rs new file mode 100644 index 00000000..6eaef10e --- /dev/null +++ b/tests/max_encoded_len_ui/literal_list_item.rs @@ -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(); +} diff --git a/tests/max_encoded_len_ui/literal_list_item.stderr b/tests/max_encoded_len_ui/literal_list_item.stderr new file mode 100644 index 00000000..3533b9ea --- /dev/null +++ b/tests/max_encoded_len_ui/literal_list_item.stderr @@ -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` diff --git a/tests/max_encoded_len_ui/name_value_attr.rs b/tests/max_encoded_len_ui/name_value_attr.rs new file mode 100644 index 00000000..f881c024 --- /dev/null +++ b/tests/max_encoded_len_ui/name_value_attr.rs @@ -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(); +} diff --git a/tests/max_encoded_len_ui/name_value_attr.stderr b/tests/max_encoded_len_ui/name_value_attr.stderr new file mode 100644 index 00000000..170b4e4b --- /dev/null +++ b/tests/max_encoded_len_ui/name_value_attr.stderr @@ -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` diff --git a/tests/max_encoded_len_ui/name_value_list_item.rs b/tests/max_encoded_len_ui/name_value_list_item.rs new file mode 100644 index 00000000..2be034a7 --- /dev/null +++ b/tests/max_encoded_len_ui/name_value_list_item.rs @@ -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(); +} diff --git a/tests/max_encoded_len_ui/name_value_list_item.stderr b/tests/max_encoded_len_ui/name_value_list_item.stderr new file mode 100644 index 00000000..5de16471 --- /dev/null +++ b/tests/max_encoded_len_ui/name_value_list_item.stderr @@ -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` diff --git a/tests/max_encoded_len_ui/no_path_list_items.rs b/tests/max_encoded_len_ui/no_path_list_items.rs new file mode 100644 index 00000000..22f188f9 --- /dev/null +++ b/tests/max_encoded_len_ui/no_path_list_items.rs @@ -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(); +} diff --git a/tests/max_encoded_len_ui/no_path_list_items.stderr b/tests/max_encoded_len_ui/no_path_list_items.stderr new file mode 100644 index 00000000..4c0a830f --- /dev/null +++ b/tests/max_encoded_len_ui/no_path_list_items.stderr @@ -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` diff --git a/tests/max_encoded_len_ui/not_encode.rs b/tests/max_encoded_len_ui/not_encode.rs index 073cd653..15c9d71d 100644 --- a/tests/max_encoded_len_ui/not_encode.rs +++ b/tests/max_encoded_len_ui/not_encode.rs @@ -1,4 +1,4 @@ -use parity_scale_codec::MaxEncodedLen; +use parity_scale_codec::{MaxEncodedLen}; #[derive(MaxEncodedLen)] struct NotEncode; diff --git a/tests/max_encoded_len_ui/not_mel.rs b/tests/max_encoded_len_ui/not_mel.rs index 398660e7..0f4a5f44 100644 --- a/tests/max_encoded_len_ui/not_mel.rs +++ b/tests/max_encoded_len_ui/not_mel.rs @@ -1,4 +1,4 @@ -use parity_scale_codec::{MaxEncodedLen, Encode}; +use parity_scale_codec::{Encode, MaxEncodedLen}; #[derive(Encode)] struct NotMel; diff --git a/tests/max_encoded_len_ui/path_attr.rs b/tests/max_encoded_len_ui/path_attr.rs new file mode 100644 index 00000000..22f188f9 --- /dev/null +++ b/tests/max_encoded_len_ui/path_attr.rs @@ -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(); +} diff --git a/tests/max_encoded_len_ui/path_attr.stderr b/tests/max_encoded_len_ui/path_attr.stderr new file mode 100644 index 00000000..9a9f2b30 --- /dev/null +++ b/tests/max_encoded_len_ui/path_attr.stderr @@ -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` diff --git a/tests/max_encoded_len_ui/two_path_list_items.rs b/tests/max_encoded_len_ui/two_path_list_items.rs new file mode 100644 index 00000000..9820dab5 --- /dev/null +++ b/tests/max_encoded_len_ui/two_path_list_items.rs @@ -0,0 +1,9 @@ +use parity_scale_codec::{Encode, MaxEncodedLen}; + +#[derive(Encode, MaxEncodedLen)] +#[max_encoded_len_mod(max_encoded_len, parity_scale_codec::max_encoded_len)] +struct Example; + +fn main() { + let _ = Example::max_encoded_len(); +} diff --git a/tests/max_encoded_len_ui/two_path_list_items.stderr b/tests/max_encoded_len_ui/two_path_list_items.stderr new file mode 100644 index 00000000..edb77e01 --- /dev/null +++ b/tests/max_encoded_len_ui/two_path_list_items.stderr @@ -0,0 +1,18 @@ +error: expected exactly 1 item + --> $DIR/two_path_list_items.rs:4:3 + | +4 | #[max_encoded_len_mod(max_encoded_len, parity_scale_codec::max_encoded_len)] + | ^^^^^^^^^^^^^^^^^^^ + +error[E0599]: no function or associated item named `max_encoded_len` found for struct `Example` in the current scope + --> $DIR/two_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` diff --git a/tests/max_encoded_len_ui/union.rs b/tests/max_encoded_len_ui/union.rs index 85f8e547..2fb91e39 100644 --- a/tests/max_encoded_len_ui/union.rs +++ b/tests/max_encoded_len_ui/union.rs @@ -1,4 +1,4 @@ -use parity_scale_codec::{MaxEncodedLen, Encode}; +use parity_scale_codec::{Encode, MaxEncodedLen}; #[derive(Encode, MaxEncodedLen)] union Union { diff --git a/tests/max_encoded_len_ui/unsupported_variant.rs b/tests/max_encoded_len_ui/unsupported_variant.rs index 0ea093ab..7358107f 100644 --- a/tests/max_encoded_len_ui/unsupported_variant.rs +++ b/tests/max_encoded_len_ui/unsupported_variant.rs @@ -1,4 +1,4 @@ -use parity_scale_codec::{MaxEncodedLen, Encode}; +use parity_scale_codec::{Encode, MaxEncodedLen}; #[derive(Encode)] struct NotMel;