parity_scale_codec_derive/
max_encoded_len.rs

1// Copyright (C) 2021 Parity Technologies (UK) Ltd.
2// SPDX-License-Identifier: Apache-2.0
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// 	http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16#![cfg(feature = "max-encoded-len")]
17
18use crate::{
19	trait_bounds,
20	utils::{codec_crate_path, custom_mel_trait_bound, has_dumb_trait_bound, should_skip},
21};
22use quote::{quote, quote_spanned};
23use syn::{parse_quote, spanned::Spanned, Data, DeriveInput, Fields, Type};
24
25/// impl for `#[derive(MaxEncodedLen)]`
26pub fn derive_max_encoded_len(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
27	let mut input: DeriveInput = match syn::parse(input) {
28		Ok(input) => input,
29		Err(e) => return e.to_compile_error().into(),
30	};
31
32	let crate_path = match codec_crate_path(&input.attrs) {
33		Ok(crate_path) => crate_path,
34		Err(error) => return error.into_compile_error().into(),
35	};
36
37	let name = &input.ident;
38	if let Err(e) = trait_bounds::add(
39		&input.ident,
40		&mut input.generics,
41		&input.data,
42		custom_mel_trait_bound(&input.attrs),
43		parse_quote!(#crate_path::MaxEncodedLen),
44		None,
45		has_dumb_trait_bound(&input.attrs),
46		&crate_path
47	) {
48		return e.to_compile_error().into()
49	}
50	let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
51
52	let data_expr = data_length_expr(&input.data);
53
54	quote::quote!(
55		const _: () = {
56			impl #impl_generics #crate_path::MaxEncodedLen for #name #ty_generics #where_clause {
57				fn max_encoded_len() -> ::core::primitive::usize {
58					#data_expr
59				}
60			}
61		};
62	)
63	.into()
64}
65
66/// generate an expression to sum up the max encoded length from several fields
67fn fields_length_expr(fields: &Fields) -> proc_macro2::TokenStream {
68	let type_iter: Box<dyn Iterator<Item = &Type>> = match fields {
69		Fields::Named(ref fields) => Box::new(
70			fields.named.iter().filter_map(|field| if should_skip(&field.attrs) {
71				None
72			} else {
73				Some(&field.ty)
74			})
75		),
76		Fields::Unnamed(ref fields) => Box::new(
77			fields.unnamed.iter().filter_map(|field| if should_skip(&field.attrs) {
78				None
79			} else {
80				Some(&field.ty)
81			})
82		),
83		Fields::Unit => Box::new(std::iter::empty()),
84	};
85	// expands to an expression like
86	//
87	//   0
88	//     .saturating_add(<type of first field>::max_encoded_len())
89	//     .saturating_add(<type of second field>::max_encoded_len())
90	//
91	// We match the span of each field to the span of the corresponding
92	// `max_encoded_len` call. This way, if one field's type doesn't implement
93	// `MaxEncodedLen`, the compiler's error message will underline which field
94	// caused the issue.
95	let expansion = type_iter.map(|ty| {
96		quote_spanned! {
97			ty.span() => .saturating_add(<#ty>::max_encoded_len())
98		}
99	});
100	quote! {
101		0_usize #( #expansion )*
102	}
103}
104
105// generate an expression to sum up the max encoded length of each field
106fn data_length_expr(data: &Data) -> proc_macro2::TokenStream {
107	match *data {
108		Data::Struct(ref data) => fields_length_expr(&data.fields),
109		Data::Enum(ref data) => {
110			// We need an expression expanded for each variant like
111			//
112			//   0
113			//     .max(<variant expression>)
114			//     .max(<variant expression>)
115			//     .saturating_add(1)
116			//
117			// The 1 derives from the discriminant; see
118			// https://github.com/paritytech/parity-scale-codec/
119			//   blob/f0341dabb01aa9ff0548558abb6dcc5c31c669a1/derive/src/encode.rs#L211-L216
120			//
121			// Each variant expression's sum is computed the way an equivalent struct's would be.
122
123			let expansion = data.variants.iter().map(|variant| {
124				let variant_expression = fields_length_expr(&variant.fields);
125				quote! {
126					.max(#variant_expression)
127				}
128			});
129
130			quote! {
131				0_usize #( #expansion )* .saturating_add(1)
132			}
133		},
134		Data::Union(ref data) => {
135			// https://github.com/paritytech/parity-scale-codec/
136			//   blob/f0341dabb01aa9ff0548558abb6dcc5c31c669a1/derive/src/encode.rs#L290-L293
137			syn::Error::new(data.union_token.span(), "Union types are not supported.")
138				.to_compile_error()
139		},
140	}
141}