Skip to content

Commit

Permalink
Rewrite fn max_encoded_len_trait for clarity
Browse files Browse the repository at this point in the history
  • Loading branch information
coriolinus committed Jun 22, 2021
1 parent 67474db commit 395dbaa
Show file tree
Hide file tree
Showing 19 changed files with 164 additions and 283 deletions.
78 changes: 3 additions & 75 deletions derive/src/max_encoded_len.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,10 @@
// 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 crate::utils::codec_crate_path;
use quote::{quote, quote_spanned};
use syn::{
Data, DeriveInput, Error, Fields, GenericParam, Generics, Meta, TraitBound, Type, TypeParamBound,
Data, DeriveInput, Fields, GenericParam, Generics, TraitBound, Type, TypeParamBound,
parse_quote, spanned::Spanned,
};

Expand Down Expand Up @@ -52,81 +51,10 @@ pub fn derive_max_encoded_len(input: proc_macro::TokenStream) -> proc_macro::Tok
}

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()))?;
let mel = codec_crate_path(&input.attrs)?;
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
87 changes: 69 additions & 18 deletions derive/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@

use std::str::FromStr;

use proc_macro2::TokenStream;
use proc_macro_crate::{crate_name, FoundCrate};
use proc_macro2::{Span, Ident, TokenStream};
use quote::quote;
use syn::{
spanned::Spanned,
Meta, NestedMeta, Lit, Attribute, Variant, Field, DeriveInput, Fields, Data, FieldsUnnamed,
FieldsNamed, MetaNameValue, punctuated::Punctuated, token, parse::Parse,
Attribute, Data, DeriveInput, Error, Field, Fields, FieldsNamed, FieldsUnnamed, Lit, Meta,
MetaNameValue, NestedMeta, parse_quote, parse::Parse, Path, punctuated::Punctuated,
spanned::Spanned, token, Variant,
};

fn find_meta_item<'a, F, R, I, M>(mut itr: I, mut pred: F) -> Option<R> where
Expand Down Expand Up @@ -119,6 +121,49 @@ pub fn has_dumb_trait_bound(attrs: &[Attribute]) -> bool {
}).is_some()
}

/// Generate the crate access for the crate using 2018 syntax.
fn crate_access() -> syn::Result<Ident> {
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)),
}
}

/// Match `#[codec(crate = ...)]` and return the `...`
fn codec_crate_path_lit(attr: &Attribute) -> Option<Lit> {
// match `#[codec ...]`
if attr.path != parse_quote!(codec) {
return None;
};
// match `#[codec(crate = ...)]` and return the `...`
match attr.parse_meta() {
Ok(Meta::NameValue(MetaNameValue { path, lit, .. })) if path == parse_quote!(crate) => {
Some(lit)
}
_ => None,
}
}

/// Match `#[codec(crate = "...")]` and return the contents as a `Path`

pub fn codec_crate_path(attrs: &[Attribute]) -> syn::Result<Path> {
match attrs.iter().find_map(codec_crate_path_lit) {
Some(Lit::Str(lit_str)) => lit_str.parse::<Path>(),
Some(lit) => {
Err(Error::new(
lit.span(),
"Expected format: #[codec(crate = \"path::to::codec\")]",
))
}
None => crate_access().map(|ident| ident.into()),
}
}

/// Trait bounds.
pub type TraitBounds = Punctuated<syn::WherePredicate, token::Comma>;

Expand Down Expand Up @@ -179,12 +224,20 @@ pub fn filter_skip_unnamed<'a>(fields: &'a syn::FieldsUnnamed) -> impl Iterator<
/// Ensure attributes are correctly applied. This *must* be called before using
/// any of the attribute finder methods or the macro may panic if it encounters
/// misapplied attributes.
/// `#[codec(dumb_trait_bound)]` is the only accepted top attribute.
///
/// The top level can have the following attributes:
///
/// * `#[codec(dumb_trait_bound)]`
/// * `#[codec(crate = "path::to::crate")]
///
/// Fields can have the following attributes:
///
/// * `#[codec(skip)]`
/// * `#[codec(compact)]`
/// * `#[codec(encoded_as = "$EncodeAs")]` with $EncodedAs a valid TokenStream
///
/// Variants can have the following attributes:
///
/// * `#[codec(skip)]`
/// * `#[codec(index = $int)]`
pub fn check_attributes(input: &DeriveInput) -> syn::Result<()> {
Expand Down Expand Up @@ -293,26 +346,24 @@ 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))]` or \
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";
if attr.path.is_ident("codec") {
if attr.parse_args::<CustomTraitBound<encode_bound>>().is_ok() {
return Ok(())
} else if attr.parse_args::<CustomTraitBound<decode_bound>>().is_ok() {
return Ok(())
} else {
match attr.parse_meta()? {
Meta::List(ref meta_list) if meta_list.nested.len() == 1 => {
match meta_list.nested.first().expect("Just checked that there is one item; qed") {
if attr.path.is_ident("codec")
&& !(attr.parse_args::<CustomTraitBound<encode_bound>>().is_ok()
|| attr.parse_args::<CustomTraitBound<decode_bound>>().is_ok()
|| codec_crate_path_lit(attr).is_some())
{
match attr.parse_meta()? {
Meta::List(ref meta_list) if meta_list.nested.len() == 1 => {
match meta_list.nested.first().expect("Just checked that there is one item; qed") {
NestedMeta::Meta(Meta::Path(path))
if path.get_ident().map_or(false, |i| i == "dumb_trait_bound") => Ok(()),

elt @ _ => Err(syn::Error::new(elt.span(), top_error)),
}
},
_ => Err(syn::Error::new(attr.span(), top_error)),
}
_ => Err(syn::Error::new(attr.span(), top_error)),
}
} else {
Ok(())
Expand Down
61 changes: 34 additions & 27 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
//! suitable for resource-constrained execution environments like blockchain runtimes and low-power,
//! low-memory devices.
//!
//! It is important to note that the encoding context (knowledge of how the types and data structures look)
//! needs to be known separately at both encoding and decoding ends.
//! It is important to note that the encoding context (knowledge of how the types and data
//! structures look) needs to be known separately at both encoding and decoding ends.
//! The encoded data does not include this contextual information.
//!
//! To get a better understanding of how the encoding is done for different types,
Expand All @@ -35,46 +35,55 @@
//!
//! ### Encode
//!
//! The `Encode` trait is used for encoding of data into the SCALE format. The `Encode` trait contains the following functions:
//! The `Encode` trait is used for encoding of data into the SCALE format. The `Encode` trait
//! contains the following functions:

//!
//! * `size_hint(&self) -> usize`: Gets the capacity (in bytes) required for the encoded data.
//! This is to avoid double-allocation of memory needed for the encoding.
//! It can be an estimate and does not need to be an exact number.
//! If the size is not known, even no good maximum, then we can skip this function from the trait implementation.
//! This is required to be a cheap operation, so should not involve iterations etc.
//! * `encode_to<T: Output>(&self, dest: &mut T)`: Encodes the value and appends it to a destination buffer.
//! If the size is not known, even no good maximum, then we can skip this function from the trait
//! implementation. This is required to be a cheap operation, so should not involve iterations etc.
//! * `encode_to<T: Output>(&self, dest: &mut T)`: Encodes the value and appends it to a destination
//! buffer.
//! * `encode(&self) -> Vec<u8>`: Encodes the type data and returns a slice.
//! * `using_encoded<R, F: FnOnce(&[u8]) -> R>(&self, f: F) -> R`: Encodes the type data and executes a closure on the encoded value.
//! * `using_encoded<R, F: FnOnce(&[u8]) -> R>(&self, f: F) -> R`: Encodes the type data and
//! executes a closure on the encoded value.
//! Returns the result from the executed closure.
//!
//! **Note:** Implementations should override `using_encoded` for value types and `encode_to` for allocating types.
//! `size_hint` should be implemented for all types, wherever possible. Wrapper types should override all methods.
//! **Note:** Implementations should override `using_encoded` for value types and `encode_to` for
//! allocating types. `size_hint` should be implemented for all types, wherever possible. Wrapper
//! types should override all methods.
//!
//! ### Decode
//!
//! The `Decode` trait is used for deserialization/decoding of encoded data into the respective types.
//! The `Decode` trait is used for deserialization/decoding of encoded data into the respective
//! types.
//!
//! * `fn decode<I: Input>(value: &mut I) -> Result<Self, Error>`: Tries to decode the value from SCALE format to the type it is called on.
//! * `fn decode<I: Input>(value: &mut I) -> Result<Self, Error>`: Tries to decode the value from
//! SCALE format to the type it is called on.
//! Returns an `Err` if the decoding fails.
//!
//! ### CompactAs
//!
//! The `CompactAs` trait is used for wrapping custom types/structs as compact types, which makes them even more space/memory efficient.
//! The compact encoding is described [here](https://substrate.dev/docs/en/knowledgebase/advanced/codec#compactgeneral-integers).
//! The `CompactAs` trait is used for wrapping custom types/structs as compact types, which makes
//! them even more space/memory efficient. The compact encoding is described [here](https://substrate.dev/docs/en/knowledgebase/advanced/codec#compactgeneral-integers).
//!
//! * `encode_as(&self) -> &Self::As`: Encodes the type (self) as a compact type.
//! The type `As` is defined in the same trait and its implementation should be compact encode-able.
//! * `decode_from(_: Self::As) -> Result<Self, Error>`: Decodes the type (self) from a compact encode-able type.
//! * `decode_from(_: Self::As) -> Result<Self, Error>`: Decodes the type (self) from a compact
//! encode-able type.
//!
//! ### HasCompact
//!
//! The `HasCompact` trait, if implemented, tells that the corresponding type is a compact encode-able type.
//! The `HasCompact` trait, if implemented, tells that the corresponding type is a compact
//! encode-able type.
//!
//! ### EncodeLike
//!
//! The `EncodeLike` trait needs to be implemented for each type manually. When using derive, it is
//! done automatically for you. Basically the trait gives you the opportunity to accept multiple types
//! to a function that all encode to the same representation.
//! done automatically for you. Basically the trait gives you the opportunity to accept multiple
//! types to a function that all encode to the same representation.
//!
//! ## Usage Examples
//!
Expand Down Expand Up @@ -212,24 +221,22 @@
//! ## Derive attributes
//!
//! The derive implementation supports the following attributes:
//! - `codec(dumb_trait_bound)`: This attribute needs to be placed above the type that one of the trait
//! should be implemented for. It will make the algorithm that determines the to-add trait bounds
//! fall back to just use the type parameters of the type. This can be useful for situation where
//! the algorithm includes private types in the public interface. By using this attribute, you should
//! not get this error/warning again.
//! - `codec(dumb_trait_bound)`: This attribute needs to be placed above the type that one of the
//! trait should be implemented for. It will make the algorithm that determines the to-add trait
//! bounds fall back to just use the type parameters of the type. This can be useful for situation
//! where the algorithm includes private types in the public interface. By using this attribute,
//! you should not get this error/warning again.
//! - `codec(skip)`: Needs to be placed above a field or variant and makes it to be skipped while
//! encoding/decoding.
//! - `codec(compact)`: Needs to be placed above a field and makes the field use compact encoding.
//! (The type needs to support compact encoding.)
//! - `codec(encoded_as = "OtherType")`: Needs to be placed above a field and makes the field being encoded
//! by using `OtherType`.
//! - `codec(encoded_as = "OtherType")`: Needs to be placed above a field and makes the field being
//! encoded by using `OtherType`.
//! - `codec(index = 0)`: Needs to be placed above an enum variant to make the variant use the given
//! index when encoded. By default the index is determined by counting from `0` beginning wth the
//! first variant.
//!

#![warn(missing_docs)]

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

#[cfg(not(feature = "std"))]
Expand Down Expand Up @@ -331,7 +338,7 @@ pub use max_encoded_len::MaxEncodedLen;
/// pub use parity_scale_codec::max_encoded_len;
///
/// #[derive(Encode, MaxEncodedLen)]
/// #[max_encoded_len_mod($crate::max_encoded_len)]
/// #[codec(crate = "$crate::max_encoded_len")]
/// struct Example;
/// ```
#[cfg(feature = "derive")]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use parity_scale_codec::{Encode, MaxEncodedLen};

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

fn main() {
Expand Down
19 changes: 19 additions & 0 deletions tests/max_encoded_len_ui/incomplete_attr.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
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
--> $DIR/incomplete_attr.rs:4:9
|
4 | #[codec(crate)]
| ^^^^^

error[E0277]: the trait bound `Example: WrapperTypeEncode` is not satisfied
--> $DIR/incomplete_attr.rs:3:18
|
3 | #[derive(Encode, MaxEncodedLen)]
| ^^^^^^^^^^^^^ the trait `WrapperTypeEncode` is not implemented for `Example`
|
::: $WORKSPACE/src/max_encoded_len.rs
|
| pub trait MaxEncodedLen: Encode {
| ------ required by this bound in `MaxEncodedLen`
|
= note: required because of the requirements on the impl of `Encode` for `Example`
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
2 changes: 1 addition & 1 deletion tests/max_encoded_len_ui/list_list_item.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use parity_scale_codec::{Encode, MaxEncodedLen};

#[derive(Encode, MaxEncodedLen)]
#[max_encoded_len_mod(foo())]
#[codec(crate = "foo()")]
struct Example;

fn main() {
Expand Down
Loading

0 comments on commit 395dbaa

Please sign in to comment.