referrerpolicy=no-referrer-when-downgrade

frame_support_procedural/
storage_alias.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
18//! Implementation of the `storage_alias` attribute macro.
19
20use crate::{counter_prefix, pallet::parse::helper};
21use frame_support_procedural_tools::generate_access_from_frame_or_crate;
22use proc_macro2::{Span, TokenStream};
23use quote::{quote, ToTokens};
24use syn::{
25	parenthesized,
26	parse::{Parse, ParseStream},
27	punctuated::Punctuated,
28	spanned::Spanned,
29	token,
30	visit::Visit,
31	Attribute, Error, Ident, Result, Token, Type, TypeParam, Visibility, WhereClause,
32};
33
34/// Extension trait for [`Type`].
35trait TypeExt {
36	fn get_ident(&self) -> Option<&Ident>;
37	fn contains_ident(&self, ident: &Ident) -> bool;
38}
39
40impl TypeExt for Type {
41	fn get_ident(&self) -> Option<&Ident> {
42		match self {
43			Type::Path(p) => match &p.qself {
44				Some(qself) => qself.ty.get_ident(),
45				None => p.path.get_ident(),
46			},
47			_ => None,
48		}
49	}
50
51	fn contains_ident(&self, ident: &Ident) -> bool {
52		struct ContainsIdent<'a> {
53			ident: &'a Ident,
54			found: bool,
55		}
56		impl<'a, 'ast> Visit<'ast> for ContainsIdent<'a> {
57			fn visit_ident(&mut self, i: &'ast Ident) {
58				if i == self.ident {
59					self.found = true;
60				}
61			}
62		}
63
64		let mut visitor = ContainsIdent { ident, found: false };
65		syn::visit::visit_type(&mut visitor, self);
66		visitor.found
67	}
68}
69
70/// Represents generics which only support [`TypeParam`] separated by commas.
71struct SimpleGenerics {
72	lt_token: Token![<],
73	params: Punctuated<TypeParam, token::Comma>,
74	gt_token: Token![>],
75}
76
77impl SimpleGenerics {
78	/// Returns the generics for types declarations etc.
79	fn type_generics(&self) -> impl Iterator<Item = &Ident> {
80		self.params.iter().map(|p| &p.ident)
81	}
82
83	/// Returns the generics for the `impl` block.
84	fn impl_generics(&self) -> impl Iterator<Item = &TypeParam> {
85		self.params.iter()
86	}
87}
88
89impl Parse for SimpleGenerics {
90	fn parse(input: ParseStream<'_>) -> Result<Self> {
91		Ok(Self {
92			lt_token: input.parse()?,
93			params: Punctuated::parse_separated_nonempty(input)?,
94			gt_token: input.parse()?,
95		})
96	}
97}
98
99impl ToTokens for SimpleGenerics {
100	fn to_tokens(&self, tokens: &mut TokenStream) {
101		self.lt_token.to_tokens(tokens);
102		self.params.to_tokens(tokens);
103		self.gt_token.to_tokens(tokens);
104	}
105}
106
107mod storage_types {
108	syn::custom_keyword!(StorageValue);
109	syn::custom_keyword!(StorageMap);
110	syn::custom_keyword!(CountedStorageMap);
111	syn::custom_keyword!(StorageDoubleMap);
112	syn::custom_keyword!(StorageNMap);
113}
114
115/// The types of prefixes the storage alias macro supports.
116mod prefix_types {
117	// Use the verbatim/unmodified input name as the prefix.
118	syn::custom_keyword!(verbatim);
119	// The input type is a pallet and its pallet name should be used as the prefix.
120	syn::custom_keyword!(pallet_name);
121	// The input type implements `Get<'static str>` and this `str` should be used as the prefix.
122	syn::custom_keyword!(dynamic);
123}
124
125/// The supported storage types
126enum StorageType {
127	Value {
128		_kw: storage_types::StorageValue,
129		_lt_token: Token![<],
130		prefix: Type,
131		_value_comma: Token![,],
132		value_ty: Type,
133		query_type: Option<(Token![,], Type)>,
134		_trailing_comma: Option<Token![,]>,
135		_gt_token: Token![>],
136	},
137	Map {
138		_kw: storage_types::StorageMap,
139		_lt_token: Token![<],
140		prefix: Type,
141		_hasher_comma: Token![,],
142		hasher_ty: Type,
143		_key_comma: Token![,],
144		key_ty: Type,
145		_value_comma: Token![,],
146		value_ty: Type,
147		query_type: Option<(Token![,], Type)>,
148		_trailing_comma: Option<Token![,]>,
149		_gt_token: Token![>],
150	},
151	CountedMap {
152		_kw: storage_types::CountedStorageMap,
153		_lt_token: Token![<],
154		prefix: Type,
155		_hasher_comma: Token![,],
156		hasher_ty: Type,
157		_key_comma: Token![,],
158		key_ty: Type,
159		_value_comma: Token![,],
160		value_ty: Type,
161		query_type: Option<(Token![,], Type)>,
162		_trailing_comma: Option<Token![,]>,
163		_gt_token: Token![>],
164	},
165	DoubleMap {
166		_kw: storage_types::StorageDoubleMap,
167		_lt_token: Token![<],
168		prefix: Type,
169		_hasher1_comma: Token![,],
170		hasher1_ty: Type,
171		_key1_comma: Token![,],
172		key1_ty: Type,
173		_hasher2_comma: Token![,],
174		hasher2_ty: Type,
175		_key2_comma: Token![,],
176		key2_ty: Type,
177		_value_comma: Token![,],
178		value_ty: Type,
179		query_type: Option<(Token![,], Type)>,
180		_trailing_comma: Option<Token![,]>,
181		_gt_token: Token![>],
182	},
183	NMap {
184		_kw: storage_types::StorageNMap,
185		_lt_token: Token![<],
186		prefix: Type,
187		_paren_comma: Token![,],
188		_paren_token: token::Paren,
189		key_types: Punctuated<Type, Token![,]>,
190		_value_comma: Token![,],
191		value_ty: Type,
192		query_type: Option<(Token![,], Type)>,
193		_trailing_comma: Option<Token![,]>,
194		_gt_token: Token![>],
195	},
196}
197
198impl StorageType {
199	/// Generate the actual type declaration.
200	fn generate_type_declaration(
201		&self,
202		crate_: &syn::Path,
203		storage_instance: &StorageInstance,
204		storage_name: &Ident,
205		storage_generics: Option<&SimpleGenerics>,
206		visibility: &Visibility,
207		attributes: &[Attribute],
208	) -> TokenStream {
209		let storage_instance_generics = &storage_instance.generics;
210		let storage_instance = &storage_instance.name;
211		let attributes = attributes.iter();
212		let storage_generics = storage_generics.map(|g| {
213			let generics = g.type_generics();
214
215			quote!( < #( #generics ),* > )
216		});
217
218		match self {
219			Self::Value { value_ty, query_type, .. } => {
220				let query_type = query_type.as_ref().map(|(c, t)| quote!(#c #t));
221
222				quote! {
223					#( #attributes )*
224					#visibility type #storage_name #storage_generics = #crate_::storage::types::StorageValue<
225						#storage_instance #storage_instance_generics,
226						#value_ty
227						#query_type
228					>;
229				}
230			},
231			Self::CountedMap { value_ty, query_type, hasher_ty, key_ty, .. } |
232			Self::Map { value_ty, query_type, hasher_ty, key_ty, .. } => {
233				let query_type = query_type.as_ref().map(|(c, t)| quote!(#c #t));
234				let map_type = Ident::new(
235					match self {
236						Self::Map { .. } => "StorageMap",
237						_ => "CountedStorageMap",
238					},
239					Span::call_site(),
240				);
241
242				quote! {
243					#( #attributes )*
244					#visibility type #storage_name #storage_generics = #crate_::storage::types::#map_type<
245						#storage_instance #storage_instance_generics,
246						#hasher_ty,
247						#key_ty,
248						#value_ty
249						#query_type
250					>;
251				}
252			},
253			Self::DoubleMap {
254				value_ty,
255				query_type,
256				hasher1_ty,
257				key1_ty,
258				hasher2_ty,
259				key2_ty,
260				..
261			} => {
262				let query_type = query_type.as_ref().map(|(c, t)| quote!(#c #t));
263
264				quote! {
265					#( #attributes )*
266					#visibility type #storage_name #storage_generics = #crate_::storage::types::StorageDoubleMap<
267						#storage_instance #storage_instance_generics,
268						#hasher1_ty,
269						#key1_ty,
270						#hasher2_ty,
271						#key2_ty,
272						#value_ty
273						#query_type
274					>;
275				}
276			},
277			Self::NMap { value_ty, query_type, key_types, .. } => {
278				let query_type = query_type.as_ref().map(|(c, t)| quote!(#c #t));
279				let key_types = key_types.iter();
280
281				quote! {
282					#( #attributes )*
283					#visibility type #storage_name #storage_generics = #crate_::storage::types::StorageNMap<
284						#storage_instance #storage_instance_generics,
285						( #( #key_types ),* ),
286						#value_ty
287						#query_type
288					>;
289				}
290			},
291		}
292	}
293
294	/// The prefix for this storage type.
295	fn prefix(&self) -> &Type {
296		match self {
297			Self::Value { prefix, .. } |
298			Self::Map { prefix, .. } |
299			Self::CountedMap { prefix, .. } |
300			Self::NMap { prefix, .. } |
301			Self::DoubleMap { prefix, .. } => prefix,
302		}
303	}
304}
305
306impl Parse for StorageType {
307	fn parse(input: ParseStream<'_>) -> Result<Self> {
308		let lookahead = input.lookahead1();
309
310		let parse_query_type = |input: ParseStream<'_>| -> Result<Option<(Token![,], Type)>> {
311			if input.peek(Token![,]) && !input.peek2(Token![>]) {
312				Ok(Some((input.parse()?, input.parse()?)))
313			} else {
314				Ok(None)
315			}
316		};
317
318		if lookahead.peek(storage_types::StorageValue) {
319			Ok(Self::Value {
320				_kw: input.parse()?,
321				_lt_token: input.parse()?,
322				prefix: input.parse()?,
323				_value_comma: input.parse()?,
324				value_ty: input.parse()?,
325				query_type: parse_query_type(input)?,
326				_trailing_comma: input.peek(Token![,]).then(|| input.parse()).transpose()?,
327				_gt_token: input.parse()?,
328			})
329		} else if lookahead.peek(storage_types::StorageMap) {
330			Ok(Self::Map {
331				_kw: input.parse()?,
332				_lt_token: input.parse()?,
333				prefix: input.parse()?,
334				_hasher_comma: input.parse()?,
335				hasher_ty: input.parse()?,
336				_key_comma: input.parse()?,
337				key_ty: input.parse()?,
338				_value_comma: input.parse()?,
339				value_ty: input.parse()?,
340				query_type: parse_query_type(input)?,
341				_trailing_comma: input.peek(Token![,]).then(|| input.parse()).transpose()?,
342				_gt_token: input.parse()?,
343			})
344		} else if lookahead.peek(storage_types::CountedStorageMap) {
345			Ok(Self::CountedMap {
346				_kw: input.parse()?,
347				_lt_token: input.parse()?,
348				prefix: input.parse()?,
349				_hasher_comma: input.parse()?,
350				hasher_ty: input.parse()?,
351				_key_comma: input.parse()?,
352				key_ty: input.parse()?,
353				_value_comma: input.parse()?,
354				value_ty: input.parse()?,
355				query_type: parse_query_type(input)?,
356				_trailing_comma: input.peek(Token![,]).then(|| input.parse()).transpose()?,
357				_gt_token: input.parse()?,
358			})
359		} else if lookahead.peek(storage_types::StorageDoubleMap) {
360			Ok(Self::DoubleMap {
361				_kw: input.parse()?,
362				_lt_token: input.parse()?,
363				prefix: input.parse()?,
364				_hasher1_comma: input.parse()?,
365				hasher1_ty: input.parse()?,
366				_key1_comma: input.parse()?,
367				key1_ty: input.parse()?,
368				_hasher2_comma: input.parse()?,
369				hasher2_ty: input.parse()?,
370				_key2_comma: input.parse()?,
371				key2_ty: input.parse()?,
372				_value_comma: input.parse()?,
373				value_ty: input.parse()?,
374				query_type: parse_query_type(input)?,
375				_trailing_comma: input.peek(Token![,]).then(|| input.parse()).transpose()?,
376				_gt_token: input.parse()?,
377			})
378		} else if lookahead.peek(storage_types::StorageNMap) {
379			let content;
380			Ok(Self::NMap {
381				_kw: input.parse()?,
382				_lt_token: input.parse()?,
383				prefix: input.parse()?,
384				_paren_comma: input.parse()?,
385				_paren_token: parenthesized!(content in input),
386				key_types: Punctuated::parse_terminated(&content)?,
387				_value_comma: input.parse()?,
388				value_ty: input.parse()?,
389				query_type: parse_query_type(input)?,
390				_trailing_comma: input.peek(Token![,]).then(|| input.parse()).transpose()?,
391				_gt_token: input.parse()?,
392			})
393		} else {
394			Err(lookahead.error())
395		}
396	}
397}
398
399/// The input expected by this macro.
400struct Input {
401	attributes: Vec<Attribute>,
402	visibility: Visibility,
403	_type: Token![type],
404	storage_name: Ident,
405	storage_generics: Option<SimpleGenerics>,
406	where_clause: Option<WhereClause>,
407	_equal: Token![=],
408	storage_type: StorageType,
409	_semicolon: Token![;],
410}
411
412impl Parse for Input {
413	fn parse(input: ParseStream<'_>) -> Result<Self> {
414		let attributes = input.call(Attribute::parse_outer)?;
415		let visibility = input.parse()?;
416		let _type = input.parse()?;
417		let storage_name = input.parse()?;
418
419		let lookahead = input.lookahead1();
420		let storage_generics = if lookahead.peek(Token![<]) {
421			Some(input.parse()?)
422		} else if lookahead.peek(Token![=]) {
423			None
424		} else {
425			return Err(lookahead.error())
426		};
427
428		let lookahead = input.lookahead1();
429		let where_clause = if lookahead.peek(Token![where]) {
430			Some(input.parse()?)
431		} else if lookahead.peek(Token![=]) {
432			None
433		} else {
434			return Err(lookahead.error())
435		};
436
437		let _equal = input.parse()?;
438
439		let storage_type = input.parse()?;
440
441		let _semicolon = input.parse()?;
442
443		Ok(Self {
444			attributes,
445			visibility,
446			_type,
447			storage_name,
448			storage_generics,
449			_equal,
450			storage_type,
451			where_clause,
452			_semicolon,
453		})
454	}
455}
456
457/// Defines which type of prefix the storage alias is using.
458#[derive(Clone, Copy)]
459enum PrefixType {
460	/// An appropriate prefix will be determined automatically.
461	///
462	/// If generics are passed, this is assumed to be a pallet and the pallet name should be used.
463	/// Otherwise use the verbatim passed name as prefix.
464	Compatibility,
465	/// The provided ident/name will be used as the prefix.
466	Verbatim,
467	/// The provided type will be used to determine the prefix. This type must
468	/// implement `PalletInfoAccess` which specifies the proper name. This
469	/// name is then used as the prefix.
470	PalletName,
471	/// Uses the provided type implementing `Get<'static str>` to determine the prefix.
472	Dynamic,
473}
474
475/// Implementation of the `storage_alias` attribute macro.
476pub fn storage_alias(attributes: TokenStream, input: TokenStream) -> Result<TokenStream> {
477	let input = syn::parse2::<Input>(input)?;
478	let crate_ = generate_access_from_frame_or_crate("frame-support")?;
479
480	let prefix_type = if attributes.is_empty() {
481		PrefixType::Compatibility
482	} else if syn::parse2::<prefix_types::verbatim>(attributes.clone()).is_ok() {
483		PrefixType::Verbatim
484	} else if syn::parse2::<prefix_types::pallet_name>(attributes.clone()).is_ok() {
485		PrefixType::PalletName
486	} else if syn::parse2::<prefix_types::dynamic>(attributes.clone()).is_ok() {
487		PrefixType::Dynamic
488	} else {
489		return Err(Error::new(attributes.span(), "Unknown attributes"))
490	};
491
492	let storage_instance = generate_storage_instance(
493		&crate_,
494		&input.storage_name,
495		input.storage_generics.as_ref(),
496		input.where_clause.as_ref(),
497		input.storage_type.prefix(),
498		&input.visibility,
499		matches!(input.storage_type, StorageType::CountedMap { .. }),
500		prefix_type,
501	)?;
502
503	let definition = input.storage_type.generate_type_declaration(
504		&crate_,
505		&storage_instance,
506		&input.storage_name,
507		input.storage_generics.as_ref(),
508		&input.visibility,
509		&input.attributes,
510	);
511
512	let storage_instance_code = storage_instance.code;
513
514	Ok(quote! {
515		#storage_instance_code
516
517		#definition
518	})
519}
520
521/// The storage instance to use for the storage alias.
522struct StorageInstance {
523	name: Ident,
524	generics: TokenStream,
525	code: TokenStream,
526}
527
528/// Generate the [`StorageInstance`] for the storage alias.
529fn generate_storage_instance(
530	crate_: &syn::Path,
531	storage_name: &Ident,
532	storage_generics: Option<&SimpleGenerics>,
533	storage_where_clause: Option<&WhereClause>,
534	prefix: &Type,
535	visibility: &Visibility,
536	is_counted_map: bool,
537	prefix_type: PrefixType,
538) -> Result<StorageInstance> {
539	if let Type::Infer(_) = prefix {
540		return Err(Error::new(prefix.span(), "`_` is not allowed as prefix by `storage_alias`."))
541	}
542
543	let impl_generics_used_by_prefix = storage_generics
544		.as_ref()
545		.map(|g| {
546			g.impl_generics()
547				.filter(|g| prefix.contains_ident(&g.ident))
548				.collect::<Vec<_>>()
549		})
550		.unwrap_or_default();
551
552	let (pallet_prefix, impl_generics, type_generics) = match prefix_type {
553		PrefixType::Compatibility =>
554			if !impl_generics_used_by_prefix.is_empty() {
555				let type_generics = impl_generics_used_by_prefix.iter().map(|g| &g.ident);
556				let impl_generics = impl_generics_used_by_prefix.iter();
557
558				(
559					quote! {
560						< #prefix as #crate_::traits::PalletInfoAccess>::name()
561					},
562					quote!( #( #impl_generics ),* ),
563					quote!( #( #type_generics ),* ),
564				)
565			} else if let Some(prefix) = prefix.get_ident() {
566				let prefix_str = prefix.to_string();
567
568				(quote!(#prefix_str), quote!(), quote!())
569			} else {
570				return Err(Error::new_spanned(
571					prefix,
572					"If there are no generics, the prefix is only allowed to be an identifier.",
573				))
574			},
575		PrefixType::Verbatim => {
576			let prefix_str = match prefix.get_ident() {
577				Some(p) => p.to_string(),
578				None =>
579					return Err(Error::new_spanned(
580						prefix,
581						"Prefix type `verbatim` requires that the prefix is an ident.",
582					)),
583			};
584
585			(quote!(#prefix_str), quote!(), quote!())
586		},
587		PrefixType::PalletName => {
588			let type_generics = impl_generics_used_by_prefix.iter().map(|g| &g.ident);
589			let impl_generics = impl_generics_used_by_prefix.iter();
590
591			(
592				quote! {
593					<#prefix as #crate_::traits::PalletInfoAccess>::name()
594				},
595				quote!( #( #impl_generics ),* ),
596				quote!( #( #type_generics ),* ),
597			)
598		},
599		PrefixType::Dynamic => {
600			let type_generics = impl_generics_used_by_prefix.iter().map(|g| &g.ident);
601			let impl_generics = impl_generics_used_by_prefix.iter();
602
603			(
604				quote! {
605					<#prefix as #crate_::traits::Get<_>>::get()
606				},
607				quote!( #( #impl_generics ),* ),
608				quote!( #( #type_generics ),* ),
609			)
610		},
611	};
612
613	let where_clause = storage_where_clause.map(|w| quote!(#w)).unwrap_or_default();
614
615	let name_str = format!("{}_Storage_Instance", storage_name);
616	let name = Ident::new(&name_str, Span::call_site());
617	let storage_name_str = storage_name.to_string();
618
619	let counter_code = is_counted_map.then(|| {
620		let counter_name = Ident::new(&counter_prefix(&name_str), Span::call_site());
621		let counter_storage_name_str = counter_prefix(&storage_name_str);
622		let storage_prefix_hash = helper::two128_str(&counter_storage_name_str);
623
624		quote! {
625			#visibility struct #counter_name< #impl_generics >(
626				::core::marker::PhantomData<(#type_generics)>
627			) #where_clause;
628
629			impl<#impl_generics> #crate_::traits::StorageInstance
630				for #counter_name< #type_generics > #where_clause
631			{
632				fn pallet_prefix() -> &'static str {
633					#pallet_prefix
634				}
635
636				const STORAGE_PREFIX: &'static str = #counter_storage_name_str;
637				fn storage_prefix_hash() -> [u8; 16] {
638					#storage_prefix_hash
639				}
640			}
641
642			impl<#impl_generics> #crate_::storage::types::CountedStorageMapInstance
643				for #name< #type_generics > #where_clause
644			{
645				type CounterPrefix = #counter_name < #type_generics >;
646			}
647		}
648	});
649
650	let storage_prefix_hash = helper::two128_str(&storage_name_str);
651
652	// Implement `StorageInstance` trait.
653	let code = quote! {
654		#[allow(non_camel_case_types)]
655		#visibility struct #name< #impl_generics >(
656			::core::marker::PhantomData<(#type_generics)>
657		) #where_clause;
658
659		impl<#impl_generics> #crate_::traits::StorageInstance
660			for #name< #type_generics > #where_clause
661		{
662			fn pallet_prefix() -> &'static str {
663				#pallet_prefix
664			}
665
666			const STORAGE_PREFIX: &'static str = #storage_name_str;
667			fn storage_prefix_hash() -> [u8; 16] {
668				#storage_prefix_hash
669			}
670		}
671
672		#counter_code
673	};
674
675	Ok(StorageInstance { name, code, generics: quote!( < #type_generics > ) })
676}