// This file is part of Substrate. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! Implementation of the `storage_alias` attribute macro. use crate::{counter_prefix, pallet::parse::helper}; use frame_support_procedural_tools::generate_access_from_frame_or_crate; use proc_macro2::{Span, TokenStream}; use quote::{quote, ToTokens}; use syn::{ parenthesized, parse::{Parse, ParseStream}, punctuated::Punctuated, spanned::Spanned, token, visit::Visit, Attribute, Error, Ident, Result, Token, Type, TypeParam, Visibility, WhereClause, }; /// Extension trait for [`Type`]. trait TypeExt { fn get_ident(&self) -> Option<&Ident>; fn contains_ident(&self, ident: &Ident) -> bool; } impl TypeExt for Type { fn get_ident(&self) -> Option<&Ident> { match self { Type::Path(p) => match &p.qself { Some(qself) => qself.ty.get_ident(), None => p.path.get_ident(), }, _ => None, } } fn contains_ident(&self, ident: &Ident) -> bool { struct ContainsIdent<'a> { ident: &'a Ident, found: bool, } impl<'a, 'ast> Visit<'ast> for ContainsIdent<'a> { fn visit_ident(&mut self, i: &'ast Ident) { if i == self.ident { self.found = true; } } } let mut visitor = ContainsIdent { ident, found: false }; syn::visit::visit_type(&mut visitor, self); visitor.found } } /// Represents generics which only support [`TypeParam`] separated by commas. struct SimpleGenerics { lt_token: Token![<], params: Punctuated, gt_token: Token![>], } impl SimpleGenerics { /// Returns the generics for types declarations etc. fn type_generics(&self) -> impl Iterator { self.params.iter().map(|p| &p.ident) } /// Returns the generics for the `impl` block. fn impl_generics(&self) -> impl Iterator { self.params.iter() } } impl Parse for SimpleGenerics { fn parse(input: ParseStream<'_>) -> Result { Ok(Self { lt_token: input.parse()?, params: Punctuated::parse_separated_nonempty(input)?, gt_token: input.parse()?, }) } } impl ToTokens for SimpleGenerics { fn to_tokens(&self, tokens: &mut TokenStream) { self.lt_token.to_tokens(tokens); self.params.to_tokens(tokens); self.gt_token.to_tokens(tokens); } } mod storage_types { syn::custom_keyword!(StorageValue); syn::custom_keyword!(StorageMap); syn::custom_keyword!(CountedStorageMap); syn::custom_keyword!(StorageDoubleMap); syn::custom_keyword!(StorageNMap); } /// The types of prefixes the storage alias macro supports. mod prefix_types { // Use the verbatim/unmodified input name as the prefix. syn::custom_keyword!(verbatim); // The input type is a pallet and its pallet name should be used as the prefix. syn::custom_keyword!(pallet_name); // The input type implements `Get<'static str>` and this `str` should be used as the prefix. syn::custom_keyword!(dynamic); } /// The supported storage types enum StorageType { Value { _kw: storage_types::StorageValue, _lt_token: Token![<], prefix: Type, _value_comma: Token![,], value_ty: Type, query_type: Option<(Token![,], Type)>, _trailing_comma: Option, _gt_token: Token![>], }, Map { _kw: storage_types::StorageMap, _lt_token: Token![<], prefix: Type, _hasher_comma: Token![,], hasher_ty: Type, _key_comma: Token![,], key_ty: Type, _value_comma: Token![,], value_ty: Type, query_type: Option<(Token![,], Type)>, _trailing_comma: Option, _gt_token: Token![>], }, CountedMap { _kw: storage_types::CountedStorageMap, _lt_token: Token![<], prefix: Type, _hasher_comma: Token![,], hasher_ty: Type, _key_comma: Token![,], key_ty: Type, _value_comma: Token![,], value_ty: Type, query_type: Option<(Token![,], Type)>, _trailing_comma: Option, _gt_token: Token![>], }, DoubleMap { _kw: storage_types::StorageDoubleMap, _lt_token: Token![<], prefix: Type, _hasher1_comma: Token![,], hasher1_ty: Type, _key1_comma: Token![,], key1_ty: Type, _hasher2_comma: Token![,], hasher2_ty: Type, _key2_comma: Token![,], key2_ty: Type, _value_comma: Token![,], value_ty: Type, query_type: Option<(Token![,], Type)>, _trailing_comma: Option, _gt_token: Token![>], }, NMap { _kw: storage_types::StorageNMap, _lt_token: Token![<], prefix: Type, _paren_comma: Token![,], _paren_token: token::Paren, key_types: Punctuated, _value_comma: Token![,], value_ty: Type, query_type: Option<(Token![,], Type)>, _trailing_comma: Option, _gt_token: Token![>], }, } impl StorageType { /// Generate the actual type declaration. fn generate_type_declaration( &self, crate_: &syn::Path, storage_instance: &StorageInstance, storage_name: &Ident, storage_generics: Option<&SimpleGenerics>, visibility: &Visibility, attributes: &[Attribute], ) -> TokenStream { let storage_instance_generics = &storage_instance.generics; let storage_instance = &storage_instance.name; let attributes = attributes.iter(); let storage_generics = storage_generics.map(|g| { let generics = g.type_generics(); quote!( < #( #generics ),* > ) }); match self { Self::Value { value_ty, query_type, .. } => { let query_type = query_type.as_ref().map(|(c, t)| quote!(#c #t)); quote! { #( #attributes )* #visibility type #storage_name #storage_generics = #crate_::storage::types::StorageValue< #storage_instance #storage_instance_generics, #value_ty #query_type >; } }, Self::CountedMap { value_ty, query_type, hasher_ty, key_ty, .. } | Self::Map { value_ty, query_type, hasher_ty, key_ty, .. } => { let query_type = query_type.as_ref().map(|(c, t)| quote!(#c #t)); let map_type = Ident::new( match self { Self::Map { .. } => "StorageMap", _ => "CountedStorageMap", }, Span::call_site(), ); quote! { #( #attributes )* #visibility type #storage_name #storage_generics = #crate_::storage::types::#map_type< #storage_instance #storage_instance_generics, #hasher_ty, #key_ty, #value_ty #query_type >; } }, Self::DoubleMap { value_ty, query_type, hasher1_ty, key1_ty, hasher2_ty, key2_ty, .. } => { let query_type = query_type.as_ref().map(|(c, t)| quote!(#c #t)); quote! { #( #attributes )* #visibility type #storage_name #storage_generics = #crate_::storage::types::StorageDoubleMap< #storage_instance #storage_instance_generics, #hasher1_ty, #key1_ty, #hasher2_ty, #key2_ty, #value_ty #query_type >; } }, Self::NMap { value_ty, query_type, key_types, .. } => { let query_type = query_type.as_ref().map(|(c, t)| quote!(#c #t)); let key_types = key_types.iter(); quote! { #( #attributes )* #visibility type #storage_name #storage_generics = #crate_::storage::types::StorageNMap< #storage_instance #storage_instance_generics, ( #( #key_types ),* ), #value_ty #query_type >; } }, } } /// The prefix for this storage type. fn prefix(&self) -> &Type { match self { Self::Value { prefix, .. } | Self::Map { prefix, .. } | Self::CountedMap { prefix, .. } | Self::NMap { prefix, .. } | Self::DoubleMap { prefix, .. } => prefix, } } } impl Parse for StorageType { fn parse(input: ParseStream<'_>) -> Result { let lookahead = input.lookahead1(); let parse_query_type = |input: ParseStream<'_>| -> Result> { if input.peek(Token![,]) && !input.peek2(Token![>]) { Ok(Some((input.parse()?, input.parse()?))) } else { Ok(None) } }; if lookahead.peek(storage_types::StorageValue) { Ok(Self::Value { _kw: input.parse()?, _lt_token: input.parse()?, prefix: input.parse()?, _value_comma: input.parse()?, value_ty: input.parse()?, query_type: parse_query_type(input)?, _trailing_comma: input.peek(Token![,]).then(|| input.parse()).transpose()?, _gt_token: input.parse()?, }) } else if lookahead.peek(storage_types::StorageMap) { Ok(Self::Map { _kw: input.parse()?, _lt_token: input.parse()?, prefix: input.parse()?, _hasher_comma: input.parse()?, hasher_ty: input.parse()?, _key_comma: input.parse()?, key_ty: input.parse()?, _value_comma: input.parse()?, value_ty: input.parse()?, query_type: parse_query_type(input)?, _trailing_comma: input.peek(Token![,]).then(|| input.parse()).transpose()?, _gt_token: input.parse()?, }) } else if lookahead.peek(storage_types::CountedStorageMap) { Ok(Self::CountedMap { _kw: input.parse()?, _lt_token: input.parse()?, prefix: input.parse()?, _hasher_comma: input.parse()?, hasher_ty: input.parse()?, _key_comma: input.parse()?, key_ty: input.parse()?, _value_comma: input.parse()?, value_ty: input.parse()?, query_type: parse_query_type(input)?, _trailing_comma: input.peek(Token![,]).then(|| input.parse()).transpose()?, _gt_token: input.parse()?, }) } else if lookahead.peek(storage_types::StorageDoubleMap) { Ok(Self::DoubleMap { _kw: input.parse()?, _lt_token: input.parse()?, prefix: input.parse()?, _hasher1_comma: input.parse()?, hasher1_ty: input.parse()?, _key1_comma: input.parse()?, key1_ty: input.parse()?, _hasher2_comma: input.parse()?, hasher2_ty: input.parse()?, _key2_comma: input.parse()?, key2_ty: input.parse()?, _value_comma: input.parse()?, value_ty: input.parse()?, query_type: parse_query_type(input)?, _trailing_comma: input.peek(Token![,]).then(|| input.parse()).transpose()?, _gt_token: input.parse()?, }) } else if lookahead.peek(storage_types::StorageNMap) { let content; Ok(Self::NMap { _kw: input.parse()?, _lt_token: input.parse()?, prefix: input.parse()?, _paren_comma: input.parse()?, _paren_token: parenthesized!(content in input), key_types: Punctuated::parse_terminated(&content)?, _value_comma: input.parse()?, value_ty: input.parse()?, query_type: parse_query_type(input)?, _trailing_comma: input.peek(Token![,]).then(|| input.parse()).transpose()?, _gt_token: input.parse()?, }) } else { Err(lookahead.error()) } } } /// The input expected by this macro. struct Input { attributes: Vec, visibility: Visibility, _type: Token![type], storage_name: Ident, storage_generics: Option, where_clause: Option, _equal: Token![=], storage_type: StorageType, _semicolon: Token![;], } impl Parse for Input { fn parse(input: ParseStream<'_>) -> Result { let attributes = input.call(Attribute::parse_outer)?; let visibility = input.parse()?; let _type = input.parse()?; let storage_name = input.parse()?; let lookahead = input.lookahead1(); let storage_generics = if lookahead.peek(Token![<]) { Some(input.parse()?) } else if lookahead.peek(Token![=]) { None } else { return Err(lookahead.error()) }; let lookahead = input.lookahead1(); let where_clause = if lookahead.peek(Token![where]) { Some(input.parse()?) } else if lookahead.peek(Token![=]) { None } else { return Err(lookahead.error()) }; let _equal = input.parse()?; let storage_type = input.parse()?; let _semicolon = input.parse()?; Ok(Self { attributes, visibility, _type, storage_name, storage_generics, _equal, storage_type, where_clause, _semicolon, }) } } /// Defines which type of prefix the storage alias is using. #[derive(Clone, Copy)] enum PrefixType { /// An appropriate prefix will be determined automatically. /// /// If generics are passed, this is assumed to be a pallet and the pallet name should be used. /// Otherwise use the verbatim passed name as prefix. Compatibility, /// The provided ident/name will be used as the prefix. Verbatim, /// The provided type will be used to determine the prefix. This type must /// implement `PalletInfoAccess` which specifies the proper name. This /// name is then used as the prefix. PalletName, /// Uses the provided type implementing `Get<'static str>` to determine the prefix. Dynamic, } /// Implementation of the `storage_alias` attribute macro. pub fn storage_alias(attributes: TokenStream, input: TokenStream) -> Result { let input = syn::parse2::(input)?; let crate_ = generate_access_from_frame_or_crate("frame-support")?; let prefix_type = if attributes.is_empty() { PrefixType::Compatibility } else if syn::parse2::(attributes.clone()).is_ok() { PrefixType::Verbatim } else if syn::parse2::(attributes.clone()).is_ok() { PrefixType::PalletName } else if syn::parse2::(attributes.clone()).is_ok() { PrefixType::Dynamic } else { return Err(Error::new(attributes.span(), "Unknown attributes")) }; let storage_instance = generate_storage_instance( &crate_, &input.storage_name, input.storage_generics.as_ref(), input.where_clause.as_ref(), input.storage_type.prefix(), &input.visibility, matches!(input.storage_type, StorageType::CountedMap { .. }), prefix_type, )?; let definition = input.storage_type.generate_type_declaration( &crate_, &storage_instance, &input.storage_name, input.storage_generics.as_ref(), &input.visibility, &input.attributes, ); let storage_instance_code = storage_instance.code; Ok(quote! { #storage_instance_code #definition }) } /// The storage instance to use for the storage alias. struct StorageInstance { name: Ident, generics: TokenStream, code: TokenStream, } /// Generate the [`StorageInstance`] for the storage alias. fn generate_storage_instance( crate_: &syn::Path, storage_name: &Ident, storage_generics: Option<&SimpleGenerics>, storage_where_clause: Option<&WhereClause>, prefix: &Type, visibility: &Visibility, is_counted_map: bool, prefix_type: PrefixType, ) -> Result { if let Type::Infer(_) = prefix { return Err(Error::new(prefix.span(), "`_` is not allowed as prefix by `storage_alias`.")) } let impl_generics_used_by_prefix = storage_generics .as_ref() .map(|g| { g.impl_generics() .filter(|g| prefix.contains_ident(&g.ident)) .collect::>() }) .unwrap_or_default(); let (pallet_prefix, impl_generics, type_generics) = match prefix_type { PrefixType::Compatibility => if !impl_generics_used_by_prefix.is_empty() { let type_generics = impl_generics_used_by_prefix.iter().map(|g| &g.ident); let impl_generics = impl_generics_used_by_prefix.iter(); ( quote! { < #prefix as #crate_::traits::PalletInfoAccess>::name() }, quote!( #( #impl_generics ),* ), quote!( #( #type_generics ),* ), ) } else if let Some(prefix) = prefix.get_ident() { let prefix_str = prefix.to_string(); (quote!(#prefix_str), quote!(), quote!()) } else { return Err(Error::new_spanned( prefix, "If there are no generics, the prefix is only allowed to be an identifier.", )) }, PrefixType::Verbatim => { let prefix_str = match prefix.get_ident() { Some(p) => p.to_string(), None => return Err(Error::new_spanned( prefix, "Prefix type `verbatim` requires that the prefix is an ident.", )), }; (quote!(#prefix_str), quote!(), quote!()) }, PrefixType::PalletName => { let type_generics = impl_generics_used_by_prefix.iter().map(|g| &g.ident); let impl_generics = impl_generics_used_by_prefix.iter(); ( quote! { <#prefix as #crate_::traits::PalletInfoAccess>::name() }, quote!( #( #impl_generics ),* ), quote!( #( #type_generics ),* ), ) }, PrefixType::Dynamic => { let type_generics = impl_generics_used_by_prefix.iter().map(|g| &g.ident); let impl_generics = impl_generics_used_by_prefix.iter(); ( quote! { <#prefix as #crate_::traits::Get<_>>::get() }, quote!( #( #impl_generics ),* ), quote!( #( #type_generics ),* ), ) }, }; let where_clause = storage_where_clause.map(|w| quote!(#w)).unwrap_or_default(); let name_str = format!("{}_Storage_Instance", storage_name); let name = Ident::new(&name_str, Span::call_site()); let storage_name_str = storage_name.to_string(); let counter_code = is_counted_map.then(|| { let counter_name = Ident::new(&counter_prefix(&name_str), Span::call_site()); let counter_storage_name_str = counter_prefix(&storage_name_str); let storage_prefix_hash = helper::two128_str(&counter_storage_name_str); quote! { #visibility struct #counter_name< #impl_generics >( ::core::marker::PhantomData<(#type_generics)> ) #where_clause; impl<#impl_generics> #crate_::traits::StorageInstance for #counter_name< #type_generics > #where_clause { fn pallet_prefix() -> &'static str { #pallet_prefix } const STORAGE_PREFIX: &'static str = #counter_storage_name_str; fn storage_prefix_hash() -> [u8; 16] { #storage_prefix_hash } } impl<#impl_generics> #crate_::storage::types::CountedStorageMapInstance for #name< #type_generics > #where_clause { type CounterPrefix = #counter_name < #type_generics >; } } }); let storage_prefix_hash = helper::two128_str(&storage_name_str); // Implement `StorageInstance` trait. let code = quote! { #[allow(non_camel_case_types)] #visibility struct #name< #impl_generics >( ::core::marker::PhantomData<(#type_generics)> ) #where_clause; impl<#impl_generics> #crate_::traits::StorageInstance for #name< #type_generics > #where_clause { fn pallet_prefix() -> &'static str { #pallet_prefix } const STORAGE_PREFIX: &'static str = #storage_name_str; fn storage_prefix_hash() -> [u8; 16] { #storage_prefix_hash } } #counter_code }; Ok(StorageInstance { name, code, generics: quote!( < #type_generics > ) }) }