frame_support_procedural/pallet/parse/
config.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 super::helper;
19use frame_support_procedural_tools::{get_doc_literals, is_using_frame_crate};
20use quote::ToTokens;
21use syn::{spanned::Spanned, token, Token};
22
23/// List of additional token to be used for parsing.
24mod keyword {
25	syn::custom_keyword!(Config);
26	syn::custom_keyword!(From);
27	syn::custom_keyword!(T);
28	syn::custom_keyword!(I);
29	syn::custom_keyword!(config);
30	syn::custom_keyword!(pallet);
31	syn::custom_keyword!(IsType);
32	syn::custom_keyword!(RuntimeEvent);
33	syn::custom_keyword!(Event);
34	syn::custom_keyword!(frame_system);
35	syn::custom_keyword!(disable_frame_system_supertrait_check);
36	syn::custom_keyword!(no_default);
37	syn::custom_keyword!(no_default_bounds);
38	syn::custom_keyword!(constant);
39}
40
41#[derive(Default)]
42pub struct DefaultTrait {
43	/// A bool for each sub-trait item indicates whether the item has
44	/// `#[pallet::no_default_bounds]` attached to it. If true, the item will not have any bounds
45	/// in the generated default sub-trait.
46	pub items: Vec<(syn::TraitItem, bool)>,
47	pub has_system: bool,
48}
49
50/// Input definition for the pallet config.
51pub struct ConfigDef {
52	/// The index of item in pallet module.
53	pub index: usize,
54	/// Whether the trait has instance (i.e. define with `Config<I = ()>`)
55	pub has_instance: bool,
56	/// Const associated type.
57	pub consts_metadata: Vec<ConstMetadataDef>,
58	/// Whether the trait has the associated type `Event`, note that those bounds are
59	/// checked:
60	/// * `IsType<Self as frame_system::Config>::RuntimeEvent`
61	/// * `From<Event>` or `From<Event<T>>` or `From<Event<T, I>>`
62	pub has_event_type: bool,
63	/// The where clause on trait definition but modified so `Self` is `T`.
64	pub where_clause: Option<syn::WhereClause>,
65	/// Whether a default sub-trait should be generated.
66	///
67	/// Contains default sub-trait items (instantiated by `#[pallet::config(with_default)]`).
68	/// Vec will be empty if `#[pallet::config(with_default)]` is not specified or if there are
69	/// no trait items.
70	pub default_sub_trait: Option<DefaultTrait>,
71}
72
73/// Input definition for a constant in pallet config.
74pub struct ConstMetadataDef {
75	/// Name of the associated type.
76	pub ident: syn::Ident,
77	/// The type in Get, e.g. `u32` in `type Foo: Get<u32>;`, but `Self` is replaced by `T`
78	pub type_: syn::Type,
79	/// The doc associated
80	pub doc: Vec<syn::Expr>,
81	/// attributes
82	pub attrs: Vec<syn::Attribute>,
83}
84
85impl TryFrom<&syn::TraitItemType> for ConstMetadataDef {
86	type Error = syn::Error;
87
88	fn try_from(trait_ty: &syn::TraitItemType) -> Result<Self, Self::Error> {
89		let err = |span, msg| {
90			syn::Error::new(span, format!("Invalid usage of `#[pallet::constant]`: {}", msg))
91		};
92		let doc = get_doc_literals(&trait_ty.attrs);
93		let ident = trait_ty.ident.clone();
94		let bound = trait_ty
95			.bounds
96			.iter()
97			.find_map(|param_bound| {
98				let syn::TypeParamBound::Trait(trait_bound) = param_bound else { return None };
99
100				trait_bound.path.segments.last().and_then(|s| (s.ident == "Get").then(|| s))
101			})
102			.ok_or_else(|| err(trait_ty.span(), "`Get<T>` trait bound not found"))?;
103
104		let syn::PathArguments::AngleBracketed(ref ab) = bound.arguments else {
105			return Err(err(bound.span(), "Expected trait generic args"));
106		};
107
108		// Only one type argument is expected.
109		if ab.args.len() != 1 {
110			return Err(err(bound.span(), "Expected a single type argument"));
111		}
112
113		let syn::GenericArgument::Type(ref type_arg) = ab.args[0] else {
114			return Err(err(ab.args[0].span(), "Expected a type argument"));
115		};
116
117		let type_ = syn::parse2::<syn::Type>(replace_self_by_t(type_arg.to_token_stream()))
118			.expect("Internal error: replacing `Self` by `T` should result in valid type");
119
120		Ok(Self { ident, type_, doc, attrs: trait_ty.attrs.clone() })
121	}
122}
123
124/// Parse for `#[pallet::disable_frame_system_supertrait_check]`
125pub struct DisableFrameSystemSupertraitCheck;
126
127impl syn::parse::Parse for DisableFrameSystemSupertraitCheck {
128	fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
129		input.parse::<syn::Token![#]>()?;
130		let content;
131		syn::bracketed!(content in input);
132		content.parse::<syn::Ident>()?;
133		content.parse::<syn::Token![::]>()?;
134
135		content.parse::<keyword::disable_frame_system_supertrait_check>()?;
136		Ok(Self)
137	}
138}
139
140/// Parsing for the `typ` portion of `PalletAttr`
141#[derive(derive_syn_parse::Parse, PartialEq, Eq)]
142pub enum PalletAttrType {
143	#[peek(keyword::no_default, name = "no_default")]
144	NoDefault(keyword::no_default),
145	#[peek(keyword::no_default_bounds, name = "no_default_bounds")]
146	NoBounds(keyword::no_default_bounds),
147	#[peek(keyword::constant, name = "constant")]
148	Constant(keyword::constant),
149}
150
151/// Parsing for `#[pallet::X]`
152#[derive(derive_syn_parse::Parse)]
153pub struct PalletAttr {
154	_pound: Token![#],
155	#[bracket]
156	_bracket: token::Bracket,
157	#[inside(_bracket)]
158	_pallet: keyword::pallet,
159	#[prefix(Token![::] in _bracket)]
160	#[inside(_bracket)]
161	typ: PalletAttrType,
162}
163
164/// Parse for `IsType<<Self as $path>::RuntimeEvent>` and retrieve `$path`
165pub struct IsTypeBoundEventParse(syn::Path);
166
167impl syn::parse::Parse for IsTypeBoundEventParse {
168	fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
169		input.parse::<keyword::IsType>()?;
170		input.parse::<syn::Token![<]>()?;
171		input.parse::<syn::Token![<]>()?;
172		input.parse::<syn::Token![Self]>()?;
173		input.parse::<syn::Token![as]>()?;
174		let config_path = input.parse::<syn::Path>()?;
175		input.parse::<syn::Token![>]>()?;
176		input.parse::<syn::Token![::]>()?;
177		input.parse::<keyword::RuntimeEvent>()?;
178		input.parse::<syn::Token![>]>()?;
179
180		Ok(Self(config_path))
181	}
182}
183
184/// Parse for `From<Event>` or `From<Event<Self>>` or `From<Event<Self, I>>`
185pub struct FromEventParse {
186	is_generic: bool,
187	has_instance: bool,
188}
189
190impl syn::parse::Parse for FromEventParse {
191	fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
192		let mut is_generic = false;
193		let mut has_instance = false;
194
195		input.parse::<keyword::From>()?;
196		input.parse::<syn::Token![<]>()?;
197		input.parse::<keyword::Event>()?;
198		if input.peek(syn::Token![<]) {
199			is_generic = true;
200			input.parse::<syn::Token![<]>()?;
201			input.parse::<syn::Token![Self]>()?;
202			if input.peek(syn::Token![,]) {
203				input.parse::<syn::Token![,]>()?;
204				input.parse::<keyword::I>()?;
205				has_instance = true;
206			}
207			input.parse::<syn::Token![>]>()?;
208		}
209		input.parse::<syn::Token![>]>()?;
210
211		Ok(Self { is_generic, has_instance })
212	}
213}
214
215/// Check if trait_item is `type RuntimeEvent`, if so checks its bounds are those expected.
216/// (Event type is reserved type)
217fn check_event_type(
218	frame_system: &syn::Path,
219	trait_item: &syn::TraitItem,
220	trait_has_instance: bool,
221) -> syn::Result<bool> {
222	let syn::TraitItem::Type(type_) = trait_item else { return Ok(false) };
223
224	if type_.ident != "RuntimeEvent" {
225		return Ok(false);
226	}
227
228	// Check event has no generics
229	if !type_.generics.params.is_empty() || type_.generics.where_clause.is_some() {
230		let msg =
231			"Invalid `type RuntimeEvent`, associated type `RuntimeEvent` is reserved and must have\
232					no generics nor where_clause";
233		return Err(syn::Error::new(trait_item.span(), msg));
234	}
235
236	// Check bound contains IsType and From
237	let has_is_type_bound = type_.bounds.iter().any(|s| {
238		syn::parse2::<IsTypeBoundEventParse>(s.to_token_stream())
239			.map_or(false, |b| has_expected_system_config(b.0, frame_system))
240	});
241
242	if !has_is_type_bound {
243		let msg =
244			"Invalid `type RuntimeEvent`, associated type `RuntimeEvent` is reserved and must \
245					bound: `IsType<<Self as frame_system::Config>::RuntimeEvent>`"
246				.to_string();
247		return Err(syn::Error::new(type_.span(), msg));
248	}
249
250	let from_event_bound = type_
251		.bounds
252		.iter()
253		.find_map(|s| syn::parse2::<FromEventParse>(s.to_token_stream()).ok());
254
255	let Some(from_event_bound) = from_event_bound else {
256		let msg =
257			"Invalid `type RuntimeEvent`, associated type `RuntimeEvent` is reserved and must \
258				bound: `From<Event>` or `From<Event<Self>>` or `From<Event<Self, I>>`";
259		return Err(syn::Error::new(type_.span(), msg));
260	};
261
262	if from_event_bound.is_generic && (from_event_bound.has_instance != trait_has_instance) {
263		let msg =
264			"Invalid `type RuntimeEvent`, associated type `RuntimeEvent` bounds inconsistent \
265					`From<Event..>`. Config and generic Event must be both with instance or \
266					without instance";
267		return Err(syn::Error::new(type_.span(), msg));
268	}
269
270	Ok(true)
271}
272
273/// Check that the path to `frame_system::Config` is valid, this is that the path is just
274/// `frame_system::Config` or when using the `frame` crate it is
275/// `polkadot_sdk_frame::xyz::frame_system::Config`.
276fn has_expected_system_config(path: syn::Path, frame_system: &syn::Path) -> bool {
277	// Check if `frame_system` is actually 'frame_system'.
278	if path.segments.iter().all(|s| s.ident != "frame_system") {
279		return false;
280	}
281
282	let mut expected_system_config =
283		match (is_using_frame_crate(&path), is_using_frame_crate(&frame_system)) {
284			(true, false) =>
285			// We can't use the path to `frame_system` from `frame` if `frame_system` is not being
286			// in scope through `frame`.
287				return false,
288			(false, true) =>
289			// We know that the only valid frame_system path is one that is `frame_system`, as
290			// `frame` re-exports it as such.
291				syn::parse2::<syn::Path>(quote::quote!(frame_system)).expect("is a valid path; qed"),
292			(_, _) =>
293			// They are either both `frame_system` or both `polkadot_sdk_frame::xyz::frame_system`.
294				frame_system.clone(),
295		};
296
297	expected_system_config
298		.segments
299		.push(syn::PathSegment::from(syn::Ident::new("Config", path.span())));
300
301	// the parse path might be something like `frame_system::Config<...>`, so we
302	// only compare the idents along the path.
303	expected_system_config
304		.segments
305		.into_iter()
306		.map(|ps| ps.ident)
307		.collect::<Vec<_>>() ==
308		path.segments.into_iter().map(|ps| ps.ident).collect::<Vec<_>>()
309}
310
311/// Replace ident `Self` by `T`
312pub fn replace_self_by_t(input: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
313	input
314		.into_iter()
315		.map(|token_tree| match token_tree {
316			proc_macro2::TokenTree::Group(group) =>
317				proc_macro2::Group::new(group.delimiter(), replace_self_by_t(group.stream())).into(),
318			proc_macro2::TokenTree::Ident(ident) if ident == "Self" =>
319				proc_macro2::Ident::new("T", ident.span()).into(),
320			other => other,
321		})
322		.collect()
323}
324
325impl ConfigDef {
326	pub fn try_from(
327		frame_system: &syn::Path,
328		index: usize,
329		item: &mut syn::Item,
330		enable_default: bool,
331	) -> syn::Result<Self> {
332		let syn::Item::Trait(item) = item else {
333			let msg = "Invalid pallet::config, expected trait definition";
334			return Err(syn::Error::new(item.span(), msg));
335		};
336
337		if !matches!(item.vis, syn::Visibility::Public(_)) {
338			let msg = "Invalid pallet::config, trait must be public";
339			return Err(syn::Error::new(item.span(), msg));
340		}
341
342		syn::parse2::<keyword::Config>(item.ident.to_token_stream())?;
343
344		let where_clause = {
345			let stream = replace_self_by_t(item.generics.where_clause.to_token_stream());
346			syn::parse2::<Option<syn::WhereClause>>(stream).expect(
347				"Internal error: replacing `Self` by `T` should result in valid where
348					clause",
349			)
350		};
351
352		if item.generics.params.len() > 1 {
353			let msg = "Invalid pallet::config, expected no more than one generic";
354			return Err(syn::Error::new(item.generics.params[2].span(), msg));
355		}
356
357		let has_instance = if item.generics.params.first().is_some() {
358			helper::check_config_def_gen(&item.generics, item.ident.span())?;
359			true
360		} else {
361			false
362		};
363
364		let has_frame_system_supertrait = item.supertraits.iter().any(|s| {
365			syn::parse2::<syn::Path>(s.to_token_stream())
366				.map_or(false, |b| has_expected_system_config(b, frame_system))
367		});
368
369		let mut has_event_type = false;
370		let mut consts_metadata = vec![];
371		let mut default_sub_trait = if enable_default {
372			Some(DefaultTrait {
373				items: Default::default(),
374				has_system: has_frame_system_supertrait,
375			})
376		} else {
377			None
378		};
379		for trait_item in &mut item.items {
380			let is_event = check_event_type(frame_system, trait_item, has_instance)?;
381			has_event_type = has_event_type || is_event;
382
383			let mut already_no_default = false;
384			let mut already_constant = false;
385			let mut already_no_default_bounds = false;
386
387			while let Ok(Some(pallet_attr)) =
388				helper::take_first_item_pallet_attr::<PalletAttr>(trait_item)
389			{
390				match (pallet_attr.typ, &trait_item) {
391					(PalletAttrType::Constant(_), syn::TraitItem::Type(ref typ)) => {
392						if already_constant {
393							return Err(syn::Error::new(
394								pallet_attr._bracket.span.join(),
395								"Duplicate #[pallet::constant] attribute not allowed.",
396							));
397						}
398						already_constant = true;
399						consts_metadata.push(ConstMetadataDef::try_from(typ)?);
400					},
401					(PalletAttrType::Constant(_), _) =>
402						return Err(syn::Error::new(
403							trait_item.span(),
404							"Invalid #[pallet::constant] in #[pallet::config], expected type item",
405						)),
406					(PalletAttrType::NoDefault(_), _) => {
407						if !enable_default {
408							return Err(syn::Error::new(
409								pallet_attr._bracket.span.join(),
410								"`#[pallet:no_default]` can only be used if `#[pallet::config(with_default)]` \
411								has been specified"
412							));
413						}
414						if already_no_default {
415							return Err(syn::Error::new(
416								pallet_attr._bracket.span.join(),
417								"Duplicate #[pallet::no_default] attribute not allowed.",
418							));
419						}
420
421						already_no_default = true;
422					},
423					(PalletAttrType::NoBounds(_), _) => {
424						if !enable_default {
425							return Err(syn::Error::new(
426								pallet_attr._bracket.span.join(),
427								"`#[pallet:no_default_bounds]` can only be used if `#[pallet::config(with_default)]` \
428								has been specified"
429							));
430						}
431						if already_no_default_bounds {
432							return Err(syn::Error::new(
433								pallet_attr._bracket.span.join(),
434								"Duplicate #[pallet::no_default_bounds] attribute not allowed.",
435							));
436						}
437						already_no_default_bounds = true;
438					},
439				}
440			}
441
442			if !already_no_default && enable_default {
443				default_sub_trait
444					.as_mut()
445					.expect("is 'Some(_)' if 'enable_default'; qed")
446					.items
447					.push((trait_item.clone(), already_no_default_bounds));
448			}
449		}
450
451		let attr: Option<DisableFrameSystemSupertraitCheck> =
452			helper::take_first_item_pallet_attr(&mut item.attrs)?;
453		let disable_system_supertrait_check = attr.is_some();
454
455		if !has_frame_system_supertrait && !disable_system_supertrait_check {
456			let found = if item.supertraits.is_empty() {
457				"none".to_string()
458			} else {
459				let mut found = item
460					.supertraits
461					.iter()
462					.fold(String::new(), |acc, s| format!("{}`{}`, ", acc, quote::quote!(#s)));
463				found.pop();
464				found.pop();
465				found
466			};
467
468			let msg = format!(
469				"Invalid pallet::trait, expected explicit `{}::Config` as supertrait, \
470				found {}. \
471				(try `pub trait Config: frame_system::Config {{ ...` or \
472				`pub trait Config<I: 'static>: frame_system::Config {{ ...`). \
473				To disable this check, use `#[pallet::disable_frame_system_supertrait_check]`",
474				frame_system.to_token_stream(),
475				found,
476			);
477			return Err(syn::Error::new(item.span(), msg));
478		}
479
480		Ok(Self {
481			index,
482			has_instance,
483			consts_metadata,
484			has_event_type,
485			where_clause,
486			default_sub_trait,
487		})
488	}
489}
490
491#[cfg(test)]
492mod tests {
493	use super::*;
494	#[test]
495	fn has_expected_system_config_works() {
496		let frame_system = syn::parse2::<syn::Path>(quote::quote!(frame_system)).unwrap();
497		let path = syn::parse2::<syn::Path>(quote::quote!(frame_system::Config)).unwrap();
498		assert!(has_expected_system_config(path, &frame_system));
499	}
500
501	#[test]
502	fn has_expected_system_config_works_with_assoc_type() {
503		let frame_system = syn::parse2::<syn::Path>(quote::quote!(frame_system)).unwrap();
504		let path =
505			syn::parse2::<syn::Path>(quote::quote!(frame_system::Config<RuntimeCall = Call>))
506				.unwrap();
507		assert!(has_expected_system_config(path, &frame_system));
508	}
509
510	#[test]
511	fn has_expected_system_config_works_with_frame() {
512		let path = syn::parse2::<syn::Path>(quote::quote!(frame_system::Config)).unwrap();
513
514		let frame_system =
515			syn::parse2::<syn::Path>(quote::quote!(polkadot_sdk_frame::deps::frame_system))
516				.unwrap();
517		assert!(has_expected_system_config(path.clone(), &frame_system));
518
519		let frame_system =
520			syn::parse2::<syn::Path>(quote::quote!(frame::deps::frame_system)).unwrap();
521		assert!(has_expected_system_config(path, &frame_system));
522	}
523
524	#[test]
525	fn has_expected_system_config_works_with_frame_full_path() {
526		let frame_system =
527			syn::parse2::<syn::Path>(quote::quote!(polkadot_sdk_frame::deps::frame_system))
528				.unwrap();
529		let path =
530			syn::parse2::<syn::Path>(quote::quote!(polkadot_sdk_frame::deps::frame_system::Config))
531				.unwrap();
532		assert!(has_expected_system_config(path, &frame_system));
533
534		let frame_system =
535			syn::parse2::<syn::Path>(quote::quote!(frame::deps::frame_system)).unwrap();
536		let path =
537			syn::parse2::<syn::Path>(quote::quote!(frame::deps::frame_system::Config)).unwrap();
538		assert!(has_expected_system_config(path, &frame_system));
539	}
540
541	#[test]
542	fn has_expected_system_config_works_with_other_frame_full_path() {
543		let frame_system =
544			syn::parse2::<syn::Path>(quote::quote!(polkadot_sdk_frame::xyz::frame_system)).unwrap();
545		let path =
546			syn::parse2::<syn::Path>(quote::quote!(polkadot_sdk_frame::xyz::frame_system::Config))
547				.unwrap();
548		assert!(has_expected_system_config(path, &frame_system));
549
550		let frame_system =
551			syn::parse2::<syn::Path>(quote::quote!(frame::xyz::frame_system)).unwrap();
552		let path =
553			syn::parse2::<syn::Path>(quote::quote!(frame::xyz::frame_system::Config)).unwrap();
554		assert!(has_expected_system_config(path, &frame_system));
555	}
556
557	#[test]
558	fn has_expected_system_config_does_not_works_with_mixed_frame_full_path() {
559		let frame_system =
560			syn::parse2::<syn::Path>(quote::quote!(polkadot_sdk_frame::xyz::frame_system)).unwrap();
561		let path =
562			syn::parse2::<syn::Path>(quote::quote!(polkadot_sdk_frame::deps::frame_system::Config))
563				.unwrap();
564		assert!(!has_expected_system_config(path, &frame_system));
565	}
566
567	#[test]
568	fn has_expected_system_config_does_not_works_with_other_mixed_frame_full_path() {
569		let frame_system =
570			syn::parse2::<syn::Path>(quote::quote!(polkadot_sdk_frame::deps::frame_system))
571				.unwrap();
572		let path =
573			syn::parse2::<syn::Path>(quote::quote!(polkadot_sdk_frame::xyz::frame_system::Config))
574				.unwrap();
575		assert!(!has_expected_system_config(path, &frame_system));
576	}
577
578	#[test]
579	fn has_expected_system_config_does_not_work_with_frame_full_path_if_not_frame_crate() {
580		let frame_system = syn::parse2::<syn::Path>(quote::quote!(frame_system)).unwrap();
581		let path =
582			syn::parse2::<syn::Path>(quote::quote!(polkadot_sdk_frame::deps::frame_system::Config))
583				.unwrap();
584		assert!(!has_expected_system_config(path, &frame_system));
585	}
586
587	#[test]
588	fn has_expected_system_config_unexpected_frame_system() {
589		let frame_system =
590			syn::parse2::<syn::Path>(quote::quote!(framez::deps::frame_system)).unwrap();
591		let path = syn::parse2::<syn::Path>(quote::quote!(frame_system::Config)).unwrap();
592		assert!(!has_expected_system_config(path, &frame_system));
593	}
594
595	#[test]
596	fn has_expected_system_config_unexpected_path() {
597		let frame_system = syn::parse2::<syn::Path>(quote::quote!(frame_system)).unwrap();
598		let path = syn::parse2::<syn::Path>(quote::quote!(frame_system::ConfigSystem)).unwrap();
599		assert!(!has_expected_system_config(path, &frame_system));
600	}
601
602	#[test]
603	fn has_expected_system_config_not_frame_system() {
604		let frame_system = syn::parse2::<syn::Path>(quote::quote!(something)).unwrap();
605		let path = syn::parse2::<syn::Path>(quote::quote!(something::Config)).unwrap();
606		assert!(!has_expected_system_config(path, &frame_system));
607	}
608}