From 17a1997d1864756bcaa22856265340fa4bdaab5b Mon Sep 17 00:00:00 2001
From: Peter Goodspeed-Niklaus <coriolinus@users.noreply.github.com>
Date: Fri, 7 May 2021 10:18:09 +0200
Subject: [PATCH] `#[derive(MaxEncodedLen)]` (#8737)

* impl #[derive(MaxEncodedLen)] for structs

* impl #[derive(MaxEncodedLen)] for enums, unions

* break long comments onto multiple lines

* add doc for public item

* add examples to macro documentation

* move MaxEncodedLen macro docs, un-ignore doc-tests
---
 substrate/frame/support/procedural/src/lib.rs |   7 +
 .../support/procedural/src/max_encoded_len.rs | 133 ++++++++++++++++
 substrate/frame/support/src/traits.rs         |  28 ++++
 .../support/test/tests/max_encoded_len.rs     | 149 ++++++++++++++++++
 .../support/test/tests/max_encoded_len_ui.rs  |  26 +++
 .../tests/max_encoded_len_ui/not_encode.rs    |   6 +
 .../max_encoded_len_ui/not_encode.stderr      |  13 ++
 .../test/tests/max_encoded_len_ui/not_mel.rs  |  14 ++
 .../tests/max_encoded_len_ui/not_mel.stderr   |  21 +++
 .../test/tests/max_encoded_len_ui/union.rs    |  10 ++
 .../tests/max_encoded_len_ui/union.stderr     |  11 ++
 .../max_encoded_len_ui/unsupported_variant.rs |  12 ++
 .../unsupported_variant.stderr                |  12 ++
 13 files changed, 442 insertions(+)
 create mode 100644 substrate/frame/support/procedural/src/max_encoded_len.rs
 create mode 100644 substrate/frame/support/test/tests/max_encoded_len.rs
 create mode 100644 substrate/frame/support/test/tests/max_encoded_len_ui.rs
 create mode 100644 substrate/frame/support/test/tests/max_encoded_len_ui/not_encode.rs
 create mode 100644 substrate/frame/support/test/tests/max_encoded_len_ui/not_encode.stderr
 create mode 100644 substrate/frame/support/test/tests/max_encoded_len_ui/not_mel.rs
 create mode 100644 substrate/frame/support/test/tests/max_encoded_len_ui/not_mel.stderr
 create mode 100644 substrate/frame/support/test/tests/max_encoded_len_ui/union.rs
 create mode 100644 substrate/frame/support/test/tests/max_encoded_len_ui/union.stderr
 create mode 100644 substrate/frame/support/test/tests/max_encoded_len_ui/unsupported_variant.rs
 create mode 100644 substrate/frame/support/test/tests/max_encoded_len_ui/unsupported_variant.stderr

diff --git a/substrate/frame/support/procedural/src/lib.rs b/substrate/frame/support/procedural/src/lib.rs
index 4cedf798821..069339a9794 100644
--- a/substrate/frame/support/procedural/src/lib.rs
+++ b/substrate/frame/support/procedural/src/lib.rs
@@ -28,6 +28,7 @@ mod debug_no_bound;
 mod clone_no_bound;
 mod partial_eq_no_bound;
 mod default_no_bound;
+mod max_encoded_len;
 
 pub(crate) use storage::INHERENT_INSTANCE_NAME;
 use proc_macro::TokenStream;
@@ -432,3 +433,9 @@ pub fn crate_to_pallet_version(input: TokenStream) -> TokenStream {
 /// The number of module instances supported by the runtime, starting at index 1,
 /// and up to `NUMBER_OF_INSTANCE`.
 pub(crate) const NUMBER_OF_INSTANCE: u8 = 16;
+
+/// Derive `MaxEncodedLen`.
+#[proc_macro_derive(MaxEncodedLen)]
+pub fn derive_max_encoded_len(input: TokenStream) -> TokenStream {
+	max_encoded_len::derive_max_encoded_len(input)
+}
diff --git a/substrate/frame/support/procedural/src/max_encoded_len.rs b/substrate/frame/support/procedural/src/max_encoded_len.rs
new file mode 100644
index 00000000000..72efa446b3f
--- /dev/null
+++ b/substrate/frame/support/procedural/src/max_encoded_len.rs
@@ -0,0 +1,133 @@
+// This file is part of Substrate.
+
+// Copyright (C) 2021 Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use frame_support_procedural_tools::generate_crate_access_2018;
+use quote::{quote, quote_spanned};
+use syn::{
+	Data, DeriveInput, Fields, GenericParam, Generics, TraitBound, Type, TypeParamBound,
+	parse_quote, spanned::Spanned,
+};
+
+/// impl for `#[derive(MaxEncodedLen)]`
+pub fn derive_max_encoded_len(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+	let input: DeriveInput = match syn::parse(input) {
+		Ok(input) => input,
+		Err(e) => return e.to_compile_error().into(),
+	};
+
+	let mel_trait = match max_encoded_len_trait() {
+		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());
+	let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
+
+	let data_expr = data_length_expr(&input.data);
+
+	quote::quote!(
+		const _: () = {
+			impl #impl_generics #mel_trait for #name #ty_generics #where_clause {
+				fn max_encoded_len() -> usize {
+					#data_expr
+				}
+			}
+		};
+	)
+	.into()
+}
+
+fn max_encoded_len_trait() -> syn::Result<TraitBound> {
+	let frame_support = generate_crate_access_2018("frame-support")?;
+	Ok(parse_quote!(#frame_support::traits::MaxEncodedLen))
+}
+
+// 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 {
+		if let GenericParam::Type(ref mut type_param) = *param {
+			type_param.bounds.push(TypeParamBound::Trait(mel_trait.clone()));
+		}
+	}
+	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 {
+		Fields::Named(ref fields) => Box::new(fields.named.iter().map(|field| &field.ty)),
+		Fields::Unnamed(ref fields) => Box::new(fields.unnamed.iter().map(|field| &field.ty)),
+		Fields::Unit => Box::new(std::iter::empty()),
+	};
+	// expands to an expression like
+	//
+	//   0
+	//     .saturating_add(<type of first field>::max_encoded_len())
+	//     .saturating_add(<type of second field>::max_encoded_len())
+	//
+	// We match the span of each field to the span of the corresponding
+	// `max_encoded_len` call. This way, if one field's type doesn't implement
+	// `MaxEncodedLen`, the compiler's error message will underline which field
+	// caused the issue.
+	let expansion = type_iter.map(|ty| {
+		quote_spanned! {
+			ty.span() => .saturating_add(<#ty>::max_encoded_len())
+		}
+	});
+	quote! {
+		0_usize #( #expansion )*
+	}
+}
+
+// generate an expression to sum up the max encoded length of each field
+fn data_length_expr(data: &Data) -> proc_macro2::TokenStream {
+	match *data {
+		Data::Struct(ref data) => fields_length_expr(&data.fields),
+		Data::Enum(ref data) => {
+			// We need an expression expanded for each variant like
+			//
+			//   0
+			//     .max(<variant expression>)
+			//     .max(<variant expression>)
+			//     .saturating_add(1)
+			//
+			// The 1 derives from the discriminant; see
+			// https://github.com/paritytech/parity-scale-codec/
+			//   blob/f0341dabb01aa9ff0548558abb6dcc5c31c669a1/derive/src/encode.rs#L211-L216
+			//
+			// Each variant expression's sum is computed the way an equivalent struct's would be.
+
+			let expansion = data.variants.iter().map(|variant| {
+				let variant_expression = fields_length_expr(&variant.fields);
+				quote! {
+					.max(#variant_expression)
+				}
+			});
+
+			quote! {
+				0_usize #( #expansion )* .saturating_add(1)
+			}
+		}
+		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")
+				.to_compile_error()
+		}
+	}
+}
diff --git a/substrate/frame/support/src/traits.rs b/substrate/frame/support/src/traits.rs
index d15356c1e1b..2d7fb3db736 100644
--- a/substrate/frame/support/src/traits.rs
+++ b/substrate/frame/support/src/traits.rs
@@ -82,4 +82,32 @@ mod voting;
 pub use voting::{CurrencyToVote, SaturatingCurrencyToVote, U128CurrencyToVote};
 
 mod max_encoded_len;
+// This looks like an overlapping import/export, but it isn't:
+// macros and traits live in distinct namespaces.
 pub use max_encoded_len::MaxEncodedLen;
+/// Derive [`MaxEncodedLen`][max_encoded_len::MaxEncodedLen].
+///
+/// # Examples
+///
+/// ```
+/// # use codec::Encode;
+/// # use frame_support::traits::MaxEncodedLen;
+/// #[derive(Encode, MaxEncodedLen)]
+/// struct TupleStruct(u8, u32);
+///
+/// assert_eq!(TupleStruct::max_encoded_len(), u8::max_encoded_len() + u32::max_encoded_len());
+/// ```
+///
+/// ```
+/// # use codec::Encode;
+/// # use frame_support::traits::MaxEncodedLen;
+/// #[derive(Encode, MaxEncodedLen)]
+/// enum GenericEnum<T> {
+/// 	A,
+/// 	B(T),
+/// }
+///
+/// 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());
+/// ```
+pub use frame_support_procedural::MaxEncodedLen;
diff --git a/substrate/frame/support/test/tests/max_encoded_len.rs b/substrate/frame/support/test/tests/max_encoded_len.rs
new file mode 100644
index 00000000000..e9e74929108
--- /dev/null
+++ b/substrate/frame/support/test/tests/max_encoded_len.rs
@@ -0,0 +1,149 @@
+// This file is part of Substrate.
+
+// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Tests for MaxEncodedLen derive macro
+
+use frame_support::traits::MaxEncodedLen;
+use codec::{Compact, Encode};
+
+// These structs won't even compile if the macro isn't working right.
+
+#[derive(Encode, MaxEncodedLen)]
+struct Primitives {
+	bool: bool,
+	eight: u8,
+}
+
+#[test]
+fn primitives_max_length() {
+	assert_eq!(Primitives::max_encoded_len(), 2);
+}
+
+#[derive(Encode, MaxEncodedLen)]
+struct Composites {
+	fixed_size_array: [u8; 128],
+	tuple: (u128, u128),
+}
+
+#[test]
+fn composites_max_length() {
+	assert_eq!(Composites::max_encoded_len(), 128 + 16 + 16);
+}
+
+#[derive(Encode, MaxEncodedLen)]
+struct Generic<T> {
+	one: T,
+	two: T,
+}
+
+#[test]
+fn generic_max_length() {
+	assert_eq!(Generic::<u8>::max_encoded_len(), u8::max_encoded_len() * 2);
+	assert_eq!(Generic::<u32>::max_encoded_len(), u32::max_encoded_len() * 2);
+}
+
+#[derive(Encode, MaxEncodedLen)]
+struct TwoGenerics<T, U> {
+	t: T,
+	u: U,
+}
+
+#[test]
+fn two_generics_max_length() {
+	assert_eq!(
+		TwoGenerics::<u8, u16>::max_encoded_len(),
+		u8::max_encoded_len() + u16::max_encoded_len()
+	);
+	assert_eq!(
+		TwoGenerics::<Compact<u64>, [u16; 8]>::max_encoded_len(),
+		Compact::<u64>::max_encoded_len() + <[u16; 8]>::max_encoded_len()
+	);
+}
+
+#[derive(Encode, MaxEncodedLen)]
+struct UnitStruct;
+
+#[test]
+fn unit_struct_max_length() {
+	assert_eq!(UnitStruct::max_encoded_len(), 0);
+}
+
+#[derive(Encode, MaxEncodedLen)]
+struct TupleStruct(u8, u32);
+
+#[test]
+fn tuple_struct_max_length() {
+	assert_eq!(TupleStruct::max_encoded_len(), u8::max_encoded_len() + u32::max_encoded_len());
+}
+
+#[derive(Encode, MaxEncodedLen)]
+struct TupleGeneric<T>(T, T);
+
+#[test]
+fn tuple_generic_max_length() {
+	assert_eq!(TupleGeneric::<u8>::max_encoded_len(), u8::max_encoded_len() * 2);
+	assert_eq!(TupleGeneric::<u32>::max_encoded_len(), u32::max_encoded_len() * 2);
+}
+
+#[derive(Encode, MaxEncodedLen)]
+#[allow(unused)]
+enum UnitEnum {
+	A,
+	B,
+}
+
+#[test]
+fn unit_enum_max_length() {
+	assert_eq!(UnitEnum::max_encoded_len(), 1);
+}
+
+#[derive(Encode, MaxEncodedLen)]
+#[allow(unused)]
+enum TupleEnum {
+	A(u32),
+	B,
+}
+
+#[test]
+fn tuple_enum_max_length() {
+	assert_eq!(TupleEnum::max_encoded_len(), 1 + u32::max_encoded_len());
+}
+
+#[derive(Encode, MaxEncodedLen)]
+#[allow(unused)]
+enum StructEnum {
+	A { sixty_four: u64, one_twenty_eight: u128 },
+	B,
+}
+
+#[test]
+fn struct_enum_max_length() {
+	assert_eq!(StructEnum::max_encoded_len(), 1 + u64::max_encoded_len() + u128::max_encoded_len());
+}
+
+// ensure that enums take the max of variant length, not the sum
+#[derive(Encode, MaxEncodedLen)]
+#[allow(unused)]
+enum EnumMaxNotSum {
+	A(u32),
+	B(u32),
+}
+
+#[test]
+fn enum_max_not_sum_max_length() {
+	assert_eq!(EnumMaxNotSum::max_encoded_len(), 1 + u32::max_encoded_len());
+}
diff --git a/substrate/frame/support/test/tests/max_encoded_len_ui.rs b/substrate/frame/support/test/tests/max_encoded_len_ui.rs
new file mode 100644
index 00000000000..c5c0489da92
--- /dev/null
+++ b/substrate/frame/support/test/tests/max_encoded_len_ui.rs
@@ -0,0 +1,26 @@
+// This file is part of Substrate.
+
+// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#[rustversion::attr(not(stable), ignore)]
+#[test]
+fn derive_no_bound_ui() {
+	// As trybuild is using `cargo check`, we don't need the real WASM binaries.
+	std::env::set_var("SKIP_WASM_BUILD", "1");
+
+	let t = trybuild::TestCases::new();
+	t.compile_fail("tests/max_encoded_len_ui/*.rs");
+}
diff --git a/substrate/frame/support/test/tests/max_encoded_len_ui/not_encode.rs b/substrate/frame/support/test/tests/max_encoded_len_ui/not_encode.rs
new file mode 100644
index 00000000000..ed6fe94471e
--- /dev/null
+++ b/substrate/frame/support/test/tests/max_encoded_len_ui/not_encode.rs
@@ -0,0 +1,6 @@
+use frame_support::traits::MaxEncodedLen;
+
+#[derive(MaxEncodedLen)]
+struct NotEncode;
+
+fn main() {}
diff --git a/substrate/frame/support/test/tests/max_encoded_len_ui/not_encode.stderr b/substrate/frame/support/test/tests/max_encoded_len_ui/not_encode.stderr
new file mode 100644
index 00000000000..f4dbeac0408
--- /dev/null
+++ b/substrate/frame/support/test/tests/max_encoded_len_ui/not_encode.stderr
@@ -0,0 +1,13 @@
+error[E0277]: the trait bound `NotEncode: WrapperTypeEncode` is not satisfied
+  --> $DIR/not_encode.rs:3:10
+   |
+3  | #[derive(MaxEncodedLen)]
+   |          ^^^^^^^^^^^^^ the trait `WrapperTypeEncode` is not implemented for `NotEncode`
+   |
+  ::: $WORKSPACE/frame/support/src/traits/max_encoded_len.rs
+   |
+   | pub trait MaxEncodedLen: Encode {
+   |                          ------ required by this bound in `MaxEncodedLen`
+   |
+   = note: required because of the requirements on the impl of `frame_support::dispatch::Encode` for `NotEncode`
+   = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/substrate/frame/support/test/tests/max_encoded_len_ui/not_mel.rs b/substrate/frame/support/test/tests/max_encoded_len_ui/not_mel.rs
new file mode 100644
index 00000000000..6116f30e527
--- /dev/null
+++ b/substrate/frame/support/test/tests/max_encoded_len_ui/not_mel.rs
@@ -0,0 +1,14 @@
+use codec::Encode;
+use frame_support::traits::MaxEncodedLen;
+
+#[derive(Encode)]
+struct NotMel;
+
+#[derive(Encode, MaxEncodedLen)]
+struct Generic<T> {
+	t: T,
+}
+
+fn main() {
+	let _ = Generic::<NotMel>::max_encoded_len();
+}
diff --git a/substrate/frame/support/test/tests/max_encoded_len_ui/not_mel.stderr b/substrate/frame/support/test/tests/max_encoded_len_ui/not_mel.stderr
new file mode 100644
index 00000000000..0aabd4b2a39
--- /dev/null
+++ b/substrate/frame/support/test/tests/max_encoded_len_ui/not_mel.stderr
@@ -0,0 +1,21 @@
+error[E0599]: the function or associated item `max_encoded_len` exists for struct `Generic<NotMel>`, but its trait bounds were not satisfied
+  --> $DIR/not_mel.rs:13:29
+   |
+5  | struct NotMel;
+   | -------------- doesn't satisfy `NotMel: MaxEncodedLen`
+...
+8  | struct Generic<T> {
+   | -----------------
+   | |
+   | function or associated item `max_encoded_len` not found for this
+   | doesn't satisfy `Generic<NotMel>: MaxEncodedLen`
+...
+13 |     let _ = Generic::<NotMel>::max_encoded_len();
+   |                                ^^^^^^^^^^^^^^^ function or associated item cannot be called on `Generic<NotMel>` due to unsatisfied trait bounds
+   |
+   = note: the following trait bounds were not satisfied:
+           `NotMel: MaxEncodedLen`
+           which is required by `Generic<NotMel>: MaxEncodedLen`
+   = 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/substrate/frame/support/test/tests/max_encoded_len_ui/union.rs b/substrate/frame/support/test/tests/max_encoded_len_ui/union.rs
new file mode 100644
index 00000000000..c685b6939e9
--- /dev/null
+++ b/substrate/frame/support/test/tests/max_encoded_len_ui/union.rs
@@ -0,0 +1,10 @@
+use codec::Encode;
+use frame_support::traits::MaxEncodedLen;
+
+#[derive(Encode, MaxEncodedLen)]
+union Union {
+	a: u8,
+	b: u16,
+}
+
+fn main() {}
diff --git a/substrate/frame/support/test/tests/max_encoded_len_ui/union.stderr b/substrate/frame/support/test/tests/max_encoded_len_ui/union.stderr
new file mode 100644
index 00000000000..bc5519d674d
--- /dev/null
+++ b/substrate/frame/support/test/tests/max_encoded_len_ui/union.stderr
@@ -0,0 +1,11 @@
+error: Union types are not supported
+ --> $DIR/union.rs:5:1
+  |
+5 | union Union {
+  | ^^^^^
+
+error: Union types are not supported.
+ --> $DIR/union.rs:5:1
+  |
+5 | union Union {
+  | ^^^^^
diff --git a/substrate/frame/support/test/tests/max_encoded_len_ui/unsupported_variant.rs b/substrate/frame/support/test/tests/max_encoded_len_ui/unsupported_variant.rs
new file mode 100644
index 00000000000..675f62c168a
--- /dev/null
+++ b/substrate/frame/support/test/tests/max_encoded_len_ui/unsupported_variant.rs
@@ -0,0 +1,12 @@
+use codec::Encode;
+use frame_support::traits::MaxEncodedLen;
+
+#[derive(Encode)]
+struct NotMel;
+
+#[derive(Encode, MaxEncodedLen)]
+enum UnsupportedVariant {
+	NotMel(NotMel),
+}
+
+fn main() {}
diff --git a/substrate/frame/support/test/tests/max_encoded_len_ui/unsupported_variant.stderr b/substrate/frame/support/test/tests/max_encoded_len_ui/unsupported_variant.stderr
new file mode 100644
index 00000000000..aa10b5e4cc1
--- /dev/null
+++ b/substrate/frame/support/test/tests/max_encoded_len_ui/unsupported_variant.stderr
@@ -0,0 +1,12 @@
+error[E0599]: no function or associated item named `max_encoded_len` found for struct `NotMel` in the current scope
+ --> $DIR/unsupported_variant.rs:9:9
+  |
+5 | struct NotMel;
+  | -------------- function or associated item `max_encoded_len` not found for this
+...
+9 |     NotMel(NotMel),
+  |            ^^^^^^ function or associated item not found in `NotMel`
+  |
+  = 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`
-- 
GitLab