frame_support_procedural/
pallet_error.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18use frame_support_procedural_tools::generate_access_from_frame_or_crate;
19use quote::ToTokens;
20
21// Derive `PalletError`
22pub fn derive_pallet_error(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
23	let syn::DeriveInput { ident: name, generics, data, .. } = match syn::parse(input) {
24		Ok(input) => input,
25		Err(e) => return e.to_compile_error().into(),
26	};
27
28	let frame_support = match generate_access_from_frame_or_crate("frame-support") {
29		Ok(c) => c,
30		Err(e) => return e.into_compile_error().into(),
31	};
32	let frame_support = &frame_support;
33	let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
34
35	let max_encoded_size = match data {
36		syn::Data::Struct(syn::DataStruct { fields, .. }) => match fields {
37			syn::Fields::Named(syn::FieldsNamed { named: fields, .. }) |
38			syn::Fields::Unnamed(syn::FieldsUnnamed { unnamed: fields, .. }) => {
39				let maybe_field_tys = fields
40					.iter()
41					.map(|f| generate_field_types(f, &frame_support))
42					.collect::<syn::Result<Vec<_>>>();
43				let field_tys = match maybe_field_tys {
44					Ok(tys) => tys.into_iter().flatten(),
45					Err(e) => return e.into_compile_error().into(),
46				};
47				quote::quote! {
48					0_usize
49					#(
50						.saturating_add(<
51							#field_tys as #frame_support::traits::PalletError
52						>::MAX_ENCODED_SIZE)
53					)*
54				}
55			},
56			syn::Fields::Unit => quote::quote!(0),
57		},
58		syn::Data::Enum(syn::DataEnum { variants, .. }) => {
59			let field_tys = variants
60				.iter()
61				.map(|variant| generate_variant_field_types(variant, &frame_support))
62				.collect::<Result<Vec<Option<Vec<proc_macro2::TokenStream>>>, syn::Error>>();
63
64			let field_tys = match field_tys {
65				Ok(tys) => tys.into_iter().flatten().collect::<Vec<_>>(),
66				Err(e) => return e.to_compile_error().into(),
67			};
68
69			// We start with `1`, because the discriminant of an enum is stored as u8
70			if field_tys.is_empty() {
71				quote::quote!(1)
72			} else {
73				let variant_sizes = field_tys.into_iter().map(|variant_field_tys| {
74					quote::quote! {
75						1_usize
76						#(.saturating_add(<
77							#variant_field_tys as #frame_support::traits::PalletError
78						>::MAX_ENCODED_SIZE))*
79					}
80				});
81
82				quote::quote! {{
83					let mut size = 1_usize;
84					let mut tmp = 0_usize;
85					#(
86						tmp = #variant_sizes;
87						size = if tmp > size { tmp } else { size };
88						tmp = 0_usize;
89					)*
90					size
91				}}
92			}
93		},
94		syn::Data::Union(syn::DataUnion { union_token, .. }) => {
95			let msg = "Cannot derive `PalletError` for union; please implement it directly";
96			return syn::Error::new(union_token.span, msg).into_compile_error().into()
97		},
98	};
99
100	quote::quote!(
101		const _: () = {
102			impl #impl_generics #frame_support::traits::PalletError
103				for #name #ty_generics #where_clause
104			{
105				const MAX_ENCODED_SIZE: usize = #max_encoded_size;
106			}
107		};
108	)
109	.into()
110}
111
112fn generate_field_types(
113	field: &syn::Field,
114	scrate: &syn::Path,
115) -> syn::Result<Option<proc_macro2::TokenStream>> {
116	let attrs = &field.attrs;
117
118	for attr in attrs {
119		if attr.path().is_ident("codec") {
120			let mut res = None;
121
122			attr.parse_nested_meta(|meta| {
123				if meta.path.is_ident("skip") {
124					res = Some(None);
125				} else if meta.path.is_ident("compact") {
126					let field_ty = &field.ty;
127					res = Some(Some(quote::quote!(#scrate::__private::codec::Compact<#field_ty>)));
128				} else if meta.path.is_ident("compact") {
129					res = Some(Some(meta.value()?.parse()?));
130				}
131
132				Ok(())
133			})?;
134
135			if let Some(v) = res {
136				return Ok(v)
137			}
138		}
139	}
140
141	Ok(Some(field.ty.to_token_stream()))
142}
143
144fn generate_variant_field_types(
145	variant: &syn::Variant,
146	scrate: &syn::Path,
147) -> syn::Result<Option<Vec<proc_macro2::TokenStream>>> {
148	let attrs = &variant.attrs;
149
150	for attr in attrs {
151		if attr.path().is_ident("codec") {
152			let mut skip = false;
153
154			// We ignore the error intentionally as this isn't `codec(skip)` when
155			// `parse_nested_meta` fails.
156			let _ = attr.parse_nested_meta(|meta| {
157				skip = meta.path.is_ident("skip");
158				Ok(())
159			});
160
161			if skip {
162				return Ok(None)
163			}
164		}
165	}
166
167	match &variant.fields {
168		syn::Fields::Named(syn::FieldsNamed { named: fields, .. }) |
169		syn::Fields::Unnamed(syn::FieldsUnnamed { unnamed: fields, .. }) => {
170			let field_tys = fields
171				.iter()
172				.map(|field| generate_field_types(field, scrate))
173				.collect::<syn::Result<Vec<_>>>()?;
174			Ok(Some(field_tys.into_iter().flatten().collect()))
175		},
176		syn::Fields::Unit => Ok(None),
177	}
178}