Improve storage_alias and make UnlockAndUnreserveAllFunds independent of the pallet (#14773)

* Make `storage_alias` more generic over the `prefix`

* Make `UnlockAndUnreserveAllFunds` indepenend from the pallet

* FMT

* Fix error reporting

* Rename prefix type

* Add test

* Apply suggestions from code review

Co-authored-by: Sam Johnson <sam@durosoft.com>

* ".git/.scripts/commands/fmt/fmt.sh"

---------

Co-authored-by: Sam Johnson <sam@durosoft.com>
Co-authored-by: command-bot <>
This commit is contained in:
Bastian Köcher
2023-08-16 10:15:32 +02:00
committed by GitHub
parent 67964d25a3
commit 04b1e4fbd8
8 changed files with 1103 additions and 916 deletions
@@ -547,8 +547,8 @@ pub fn __create_tt_macro(input: TokenStream) -> TokenStream {
}
#[proc_macro_attribute]
pub fn storage_alias(_: TokenStream, input: TokenStream) -> TokenStream {
storage_alias::storage_alias(input.into())
pub fn storage_alias(attributes: TokenStream, input: TokenStream) -> TokenStream {
storage_alias::storage_alias(attributes.into(), input.into())
.unwrap_or_else(|r| r.into_compile_error())
.into()
}
@@ -22,78 +22,48 @@ use frame_support_procedural_tools::generate_crate_access_2018;
use proc_macro2::{Span, TokenStream};
use quote::{quote, ToTokens};
use syn::{
ext::IdentExt,
parenthesized,
parse::{Parse, ParseStream},
punctuated::Punctuated,
token, Attribute, Error, Ident, Result, Token, Type, TypeParam, Visibility, WhereClause,
spanned::Spanned,
token,
visit::Visit,
Attribute, Error, Ident, Result, Token, Type, TypeParam, Visibility, WhereClause,
};
/// Represents a path that only consists of [`Ident`] separated by `::`.
struct SimplePath {
leading_colon: Option<Token![::]>,
segments: Punctuated<Ident, Token![::]>,
/// Extension trait for [`Type`].
trait TypeExt {
fn get_ident(&self) -> Option<&Ident>;
fn contains_ident(&self, ident: &Ident) -> bool;
}
impl SimplePath {
/// Returns the [`Ident`] of this path.
///
/// It only returns `Some(_)` if there is exactly one element and no leading colon.
impl TypeExt for Type {
fn get_ident(&self) -> Option<&Ident> {
if self.segments.len() != 1 || self.leading_colon.is_some() {
None
} else {
self.segments.first()
match self {
Type::Path(p) => match &p.qself {
Some(qself) => qself.ty.get_ident(),
None => p.path.get_ident(),
},
_ => None,
}
}
}
impl Parse for SimplePath {
fn parse(input: ParseStream<'_>) -> Result<Self> {
Ok(Self {
leading_colon: if input.peek(Token![::]) { Some(input.parse()?) } else { None },
segments: Punctuated::parse_separated_nonempty_with(input, |p| Ident::parse_any(p))?,
})
}
}
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;
}
}
}
impl ToTokens for SimplePath {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.leading_colon.to_tokens(tokens);
self.segments.to_tokens(tokens);
}
}
/// Represents generics which only support [`Ident`] separated by commas as you would pass it to a
/// type.
struct TypeGenerics {
lt_token: Token![<],
params: Punctuated<Ident, token::Comma>,
gt_token: Token![>],
}
impl TypeGenerics {
/// Returns the generics for types declarations etc.
fn iter(&self) -> impl Iterator<Item = &Ident> {
self.params.iter()
}
}
impl Parse for TypeGenerics {
fn parse(input: ParseStream<'_>) -> Result<Self> {
Ok(Self {
lt_token: input.parse()?,
params: Punctuated::parse_separated_nonempty(input)?,
gt_token: input.parse()?,
})
}
}
impl ToTokens for TypeGenerics {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.lt_token.to_tokens(tokens);
self.params.to_tokens(tokens);
self.gt_token.to_tokens(tokens);
let mut visitor = ContainsIdent { ident, found: false };
syn::visit::visit_type(&mut visitor, self);
visitor.found
}
}
@@ -142,13 +112,22 @@ mod storage_types {
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: SimplePath,
prefix_generics: Option<TypeGenerics>,
prefix: Type,
_value_comma: Token![,],
value_ty: Type,
query_type: Option<(Token![,], Type)>,
@@ -158,8 +137,7 @@ enum StorageType {
Map {
_kw: storage_types::StorageMap,
_lt_token: Token![<],
prefix: SimplePath,
prefix_generics: Option<TypeGenerics>,
prefix: Type,
_hasher_comma: Token![,],
hasher_ty: Type,
_key_comma: Token![,],
@@ -173,8 +151,7 @@ enum StorageType {
CountedMap {
_kw: storage_types::CountedStorageMap,
_lt_token: Token![<],
prefix: SimplePath,
prefix_generics: Option<TypeGenerics>,
prefix: Type,
_hasher_comma: Token![,],
hasher_ty: Type,
_key_comma: Token![,],
@@ -188,8 +165,7 @@ enum StorageType {
DoubleMap {
_kw: storage_types::StorageDoubleMap,
_lt_token: Token![<],
prefix: SimplePath,
prefix_generics: Option<TypeGenerics>,
prefix: Type,
_hasher1_comma: Token![,],
hasher1_ty: Type,
_key1_comma: Token![,],
@@ -207,8 +183,7 @@ enum StorageType {
NMap {
_kw: storage_types::StorageNMap,
_lt_token: Token![<],
prefix: SimplePath,
prefix_generics: Option<TypeGenerics>,
prefix: Type,
_paren_comma: Token![,],
_paren_token: token::Paren,
key_types: Punctuated<Type, Token![,]>,
@@ -231,6 +206,7 @@ impl StorageType {
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| {
@@ -240,22 +216,20 @@ impl StorageType {
});
match self {
Self::Value { value_ty, query_type, prefix_generics, .. } => {
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 #prefix_generics,
#storage_instance #storage_instance_generics,
#value_ty
#query_type
>;
}
},
Self::CountedMap {
value_ty, query_type, hasher_ty, key_ty, prefix_generics, ..
} |
Self::Map { value_ty, query_type, hasher_ty, key_ty, prefix_generics, .. } => {
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 {
@@ -268,7 +242,7 @@ impl StorageType {
quote! {
#( #attributes )*
#visibility type #storage_name #storage_generics = #crate_::storage::types::#map_type<
#storage_instance #prefix_generics,
#storage_instance #storage_instance_generics,
#hasher_ty,
#key_ty,
#value_ty
@@ -283,7 +257,6 @@ impl StorageType {
key1_ty,
hasher2_ty,
key2_ty,
prefix_generics,
..
} => {
let query_type = query_type.as_ref().map(|(c, t)| quote!(#c #t));
@@ -291,7 +264,7 @@ impl StorageType {
quote! {
#( #attributes )*
#visibility type #storage_name #storage_generics = #crate_::storage::types::StorageDoubleMap<
#storage_instance #prefix_generics,
#storage_instance #storage_instance_generics,
#hasher1_ty,
#key1_ty,
#hasher2_ty,
@@ -301,14 +274,14 @@ impl StorageType {
>;
}
},
Self::NMap { value_ty, query_type, key_types, prefix_generics, .. } => {
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 #prefix_generics,
#storage_instance #storage_instance_generics,
( #( #key_types ),* ),
#value_ty
#query_type
@@ -319,7 +292,7 @@ impl StorageType {
}
/// The prefix for this storage type.
fn prefix(&self) -> &SimplePath {
fn prefix(&self) -> &Type {
match self {
Self::Value { prefix, .. } |
Self::Map { prefix, .. } |
@@ -328,17 +301,6 @@ impl StorageType {
Self::DoubleMap { prefix, .. } => prefix,
}
}
/// The prefix generics for this storage type.
fn prefix_generics(&self) -> Option<&TypeGenerics> {
match self {
Self::Value { prefix_generics, .. } |
Self::Map { prefix_generics, .. } |
Self::CountedMap { prefix_generics, .. } |
Self::NMap { prefix_generics, .. } |
Self::DoubleMap { prefix_generics, .. } => prefix_generics.as_ref(),
}
}
}
impl Parse for StorageType {
@@ -353,23 +315,11 @@ impl Parse for StorageType {
}
};
let parse_pallet_generics = |input: ParseStream<'_>| -> Result<Option<TypeGenerics>> {
let lookahead = input.lookahead1();
if lookahead.peek(Token![<]) {
Ok(Some(input.parse()?))
} else if lookahead.peek(Token![,]) {
Ok(None)
} else {
Err(lookahead.error())
}
};
if lookahead.peek(storage_types::StorageValue) {
Ok(Self::Value {
_kw: input.parse()?,
_lt_token: input.parse()?,
prefix: input.parse()?,
prefix_generics: parse_pallet_generics(input)?,
_value_comma: input.parse()?,
value_ty: input.parse()?,
query_type: parse_query_type(input)?,
@@ -381,7 +331,6 @@ impl Parse for StorageType {
_kw: input.parse()?,
_lt_token: input.parse()?,
prefix: input.parse()?,
prefix_generics: parse_pallet_generics(input)?,
_hasher_comma: input.parse()?,
hasher_ty: input.parse()?,
_key_comma: input.parse()?,
@@ -397,7 +346,6 @@ impl Parse for StorageType {
_kw: input.parse()?,
_lt_token: input.parse()?,
prefix: input.parse()?,
prefix_generics: parse_pallet_generics(input)?,
_hasher_comma: input.parse()?,
hasher_ty: input.parse()?,
_key_comma: input.parse()?,
@@ -413,7 +361,6 @@ impl Parse for StorageType {
_kw: input.parse()?,
_lt_token: input.parse()?,
prefix: input.parse()?,
prefix_generics: parse_pallet_generics(input)?,
_hasher1_comma: input.parse()?,
hasher1_ty: input.parse()?,
_key1_comma: input.parse()?,
@@ -434,7 +381,6 @@ impl Parse for StorageType {
_kw: input.parse()?,
_lt_token: input.parse()?,
prefix: input.parse()?,
prefix_generics: parse_pallet_generics(input)?,
_paren_comma: input.parse()?,
_paren_token: parenthesized!(content in input),
key_types: Punctuated::parse_terminated(&content)?,
@@ -508,20 +454,50 @@ impl Parse for Input {
}
}
/// 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(input: TokenStream) -> Result<TokenStream> {
pub fn storage_alias(attributes: TokenStream, input: TokenStream) -> Result<TokenStream> {
let input = syn::parse2::<Input>(input)?;
let crate_ = generate_crate_access_2018("frame-support")?;
let prefix_type = if attributes.is_empty() {
PrefixType::Compatibility
} else if syn::parse2::<prefix_types::verbatim>(attributes.clone()).is_ok() {
PrefixType::Verbatim
} else if syn::parse2::<prefix_types::pallet_name>(attributes.clone()).is_ok() {
PrefixType::PalletName
} else if syn::parse2::<prefix_types::dynamic>(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.storage_type.prefix_generics(),
&input.visibility,
matches!(input.storage_type, StorageType::CountedMap { .. }),
prefix_type,
)?;
let definition = input.storage_type.generate_type_declaration(
@@ -545,6 +521,7 @@ pub fn storage_alias(input: TokenStream) -> Result<TokenStream> {
/// The storage instance to use for the storage alias.
struct StorageInstance {
name: Ident,
generics: TokenStream,
code: TokenStream,
}
@@ -554,42 +531,84 @@ fn generate_storage_instance(
storage_name: &Ident,
storage_generics: Option<&SimpleGenerics>,
storage_where_clause: Option<&WhereClause>,
prefix: &SimplePath,
prefix_generics: Option<&TypeGenerics>,
prefix: &Type,
visibility: &Visibility,
is_counted_map: bool,
prefix_type: PrefixType,
) -> Result<StorageInstance> {
if let Some(ident) = prefix.get_ident().filter(|i| *i == "_") {
return Err(Error::new(ident.span(), "`_` is not allowed as prefix by `storage_alias`."))
if let Type::Infer(_) = prefix {
return Err(Error::new(prefix.span(), "`_` is not allowed as prefix by `storage_alias`."))
}
let (pallet_prefix, impl_generics, type_generics) =
if let Some((prefix_generics, storage_generics)) =
prefix_generics.and_then(|p| storage_generics.map(|s| (p, s)))
{
let type_generics = prefix_generics.iter();
let type_generics2 = prefix_generics.iter();
let impl_generics = storage_generics
.impl_generics()
.filter(|g| prefix_generics.params.iter().any(|pg| *pg == g.ident));
let impl_generics_used_by_prefix = storage_generics
.as_ref()
.map(|g| {
g.impl_generics()
.filter(|g| prefix.contains_ident(&g.ident))
.collect::<Vec<_>>()
})
.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 < #( #type_generics2 ),* > as #crate_::traits::PalletInfoAccess>::name()
<#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();
},
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_str), quote!(), quote!())
} else {
return Err(Error::new_spanned(
prefix,
"If there are no generics, the prefix is only allowed to be an identifier.",
))
};
(
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();
@@ -644,5 +663,5 @@ fn generate_storage_instance(
#counter_code
};
Ok(StorageInstance { name, code })
Ok(StorageInstance { name, code, generics: quote!( < #type_generics > ) })
}