frame_support_procedural/construct_runtime/expand/
outer_enums.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 crate::construct_runtime::Pallet;
19use proc_macro2::{Span, TokenStream};
20use quote::{quote, ToTokens};
21use std::str::FromStr;
22use syn::{Generics, Ident};
23
24/// Represents the types supported for creating an outer enum.
25#[derive(Clone, Copy, PartialEq)]
26pub enum OuterEnumType {
27	/// Collects the Event enums from all pallets.
28	Event,
29	/// Collects the Error enums from all pallets.
30	Error,
31}
32
33impl OuterEnumType {
34	/// The name of the structure this enum represents.
35	fn struct_name(&self) -> &str {
36		match self {
37			OuterEnumType::Event => "RuntimeEvent",
38			OuterEnumType::Error => "RuntimeError",
39		}
40	}
41
42	/// The name of the variant (ie `Event` or `Error`).
43	fn variant_name(&self) -> &str {
44		match self {
45			OuterEnumType::Event => "Event",
46			OuterEnumType::Error => "Error",
47		}
48	}
49}
50
51impl ToTokens for OuterEnumType {
52	fn to_tokens(&self, tokens: &mut TokenStream) {
53		match self {
54			OuterEnumType::Event => quote!(Event).to_tokens(tokens),
55			OuterEnumType::Error => quote!(Error).to_tokens(tokens),
56		}
57	}
58}
59
60/// Create an outer enum that encapsulates all pallets as variants.
61///
62/// Each variant represents a pallet and contains the corresponding type declared with either:
63/// - #[pallet::event] for the [`OuterEnumType::Event`] variant
64/// - #[pallet::error] for the [`OuterEnumType::Error`] variant
65///
66/// The name of the outer enum is prefixed with Runtime, resulting in names like RuntimeEvent
67/// or RuntimeError.
68///
69/// This structure facilitates the decoding process by leveraging the metadata.
70///
71/// # Example
72///
73/// The code generate looks like the following for [`OuterEnumType::Event`].
74///
75/// ```ignore
76/// enum RuntimeEvent {
77///     #[codec(index = 0)]
78///     System(pallet_system::Event),
79///
80///     #[codec(index = 5)]
81///     Balances(pallet_system::Event),
82/// }
83/// ```
84///
85/// Notice that the pallet index is preserved using the `#[codec(index = ..)]` attribute.
86pub fn expand_outer_enum(
87	runtime: &Ident,
88	pallet_decls: &[Pallet],
89	scrate: &TokenStream,
90	enum_ty: OuterEnumType,
91) -> syn::Result<TokenStream> {
92	// Stores all pallet variants.
93	let mut enum_variants = TokenStream::new();
94	// Generates the enum conversion between the `Runtime` outer enum and the pallet's enum.
95	let mut enum_conversions = TokenStream::new();
96	// Specific for events to query via `is_event_part_defined!`.
97	let mut query_enum_part_macros = Vec::new();
98
99	let enum_name_str = enum_ty.variant_name();
100	let enum_name_ident = Ident::new(enum_ty.struct_name(), Span::call_site());
101
102	for pallet_decl in pallet_decls {
103		let Some(pallet_entry) = pallet_decl.find_part(enum_name_str) else { continue };
104
105		let path = &pallet_decl.path;
106		let pallet_name = &pallet_decl.name;
107		let index = pallet_decl.index;
108		let instance = pallet_decl.instance.as_ref();
109		let generics = &pallet_entry.generics;
110
111		if instance.is_some() && generics.params.is_empty() {
112			let msg = format!(
113				"Instantiable pallet with no generic `{}` cannot \
114					be constructed: pallet `{}` must have generic `{}`",
115				enum_name_str, pallet_name, enum_name_str,
116			);
117			return Err(syn::Error::new(pallet_name.span(), msg))
118		}
119
120		let part_is_generic = !generics.params.is_empty();
121		let pallet_enum = match (instance, part_is_generic) {
122			(Some(inst), true) => quote!(#path::#enum_ty::<#runtime, #path::#inst>),
123			(Some(inst), false) => quote!(#path::#enum_ty::<#path::#inst>),
124			(None, true) => quote!(#path::#enum_ty::<#runtime>),
125			(None, false) => quote!(#path::#enum_ty),
126		};
127
128		enum_variants.extend(expand_enum_variant(
129			runtime,
130			pallet_decl,
131			index,
132			instance,
133			generics,
134			enum_ty,
135		));
136		enum_conversions.extend(expand_enum_conversion(
137			pallet_decl,
138			&pallet_enum,
139			&enum_name_ident,
140		));
141
142		if enum_ty == OuterEnumType::Event {
143			query_enum_part_macros.push(quote! {
144				#path::__substrate_event_check::is_event_part_defined!(#pallet_name);
145			});
146		}
147	}
148
149	// Derives specific for the event.
150	let event_custom_derives =
151		if enum_ty == OuterEnumType::Event { quote!(Clone, PartialEq, Eq,) } else { quote!() };
152
153	// Implementation specific for errors.
154	let error_custom_impl = generate_error_impl(scrate, enum_ty);
155
156	Ok(quote! {
157		#( #query_enum_part_macros )*
158
159		#[derive(
160			#event_custom_derives
161			#scrate::__private::codec::Encode,
162			#scrate::__private::codec::Decode,
163			#scrate::__private::scale_info::TypeInfo,
164			#scrate::__private::RuntimeDebug,
165		)]
166		#[allow(non_camel_case_types)]
167		pub enum #enum_name_ident {
168			#enum_variants
169		}
170
171		#enum_conversions
172
173		#error_custom_impl
174	})
175}
176
177fn expand_enum_variant(
178	runtime: &Ident,
179	pallet: &Pallet,
180	index: u8,
181	instance: Option<&Ident>,
182	generics: &Generics,
183	enum_ty: OuterEnumType,
184) -> TokenStream {
185	let path = &pallet.path;
186	let variant_name = &pallet.name;
187	let part_is_generic = !generics.params.is_empty();
188	let attr = pallet.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| {
189		let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original()))
190			.expect("was successfully parsed before; qed");
191		quote! {
192			#acc
193			#attr
194		}
195	});
196
197	match instance {
198		Some(inst) if part_is_generic => quote! {
199			#attr
200			#[codec(index = #index)]
201			#variant_name(#path::#enum_ty<#runtime, #path::#inst>),
202		},
203		Some(inst) => quote! {
204			#attr
205			#[codec(index = #index)]
206			#variant_name(#path::#enum_ty<#path::#inst>),
207		},
208		None if part_is_generic => quote! {
209			#attr
210			#[codec(index = #index)]
211			#variant_name(#path::#enum_ty<#runtime>),
212		},
213		None => quote! {
214			#attr
215			#[codec(index = #index)]
216			#variant_name(#path::#enum_ty),
217		},
218	}
219}
220
221fn expand_enum_conversion(
222	pallet: &Pallet,
223	pallet_enum: &TokenStream,
224	enum_name_ident: &Ident,
225) -> TokenStream {
226	let variant_name = &pallet.name;
227	let attr = pallet.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| {
228		let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original()))
229			.expect("was successfully parsed before; qed");
230		quote! {
231			#acc
232			#attr
233		}
234	});
235
236	quote! {
237		#attr
238		impl From<#pallet_enum> for #enum_name_ident {
239			fn from(x: #pallet_enum) -> Self {
240				#enum_name_ident
241				::#variant_name(x)
242			}
243		}
244		#attr
245		impl TryInto<#pallet_enum> for #enum_name_ident {
246			type Error = ();
247
248			fn try_into(self) -> ::core::result::Result<#pallet_enum, Self::Error> {
249				match self {
250					Self::#variant_name(evt) => Ok(evt),
251					_ => Err(()),
252				}
253			}
254		}
255	}
256}
257
258fn generate_error_impl(scrate: &TokenStream, enum_ty: OuterEnumType) -> TokenStream {
259	// Implementation is specific to `Error`s.
260	if enum_ty == OuterEnumType::Event {
261		return quote! {}
262	}
263
264	let enum_name_ident = Ident::new(enum_ty.struct_name(), Span::call_site());
265
266	quote! {
267		impl #enum_name_ident {
268			/// Optionally convert the `DispatchError` into the `RuntimeError`.
269			///
270			/// Returns `Some` if the error matches the `DispatchError::Module` variant, otherwise `None`.
271			pub fn from_dispatch_error(err: #scrate::sp_runtime::DispatchError) -> Option<Self> {
272				let #scrate::sp_runtime::DispatchError::Module(module_error) = err else { return None };
273
274				let bytes = #scrate::__private::codec::Encode::encode(&module_error);
275				#scrate::__private::codec::Decode::decode(&mut &bytes[..]).ok()
276			}
277		}
278	}
279}