diff --git a/frame/support/procedural/src/pallet/expand/storage.rs b/frame/support/procedural/src/pallet/expand/storage.rs index c78e93e1d6391..c956425379c53 100644 --- a/frame/support/procedural/src/pallet/expand/storage.rs +++ b/frame/support/procedural/src/pallet/expand/storage.rs @@ -16,7 +16,7 @@ // limitations under the License. use crate::pallet::Def; -use crate::pallet::parse::storage::{Metadata, QueryKind}; +use crate::pallet::parse::storage::{Metadata, QueryKind, StorageGenerics}; use frame_support_procedural_tools::clean_type_string; /// Generate the prefix_ident related the the storage. @@ -25,50 +25,112 @@ fn prefix_ident(storage_ident: &syn::Ident) -> syn::Ident { syn::Ident::new(&format!("_GeneratedPrefixForStorage{}", storage_ident), storage_ident.span()) } -/// * generate StoragePrefix structs (e.g. for a storage `MyStorage` a struct with the name -/// `_GeneratedPrefixForStorage$NameOfStorage` is generated) and implements StorageInstance trait. -/// * replace the first generic `_` by the generated prefix structure -/// * generate metadatas -pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream { +/// * if generics are unnamed: replace the first generic `_` by the generated prefix structure +/// * if generics are named: reorder the generic, remove their name, and add the missing ones. +/// * Add `#[allow(type_alias_bounds)]` +pub fn process_generics(def: &mut Def) { let frame_support = &def.frame_support; - let frame_system = &def.frame_system; - let pallet_ident = &def.pallet_struct.pallet; - - // Replace first arg `_` by the generated prefix structure. - // Add `#[allow(type_alias_bounds)]` for storage_def in def.storages.iter_mut() { let item = &mut def.item.content.as_mut().expect("Checked by def").1[storage_def.index]; - let typ_item = if let syn::Item::Type(t) = item { - t - } else { - unreachable!("Checked by def"); + let typ_item = match item { + syn::Item::Type(t) => t, + _ => unreachable!("Checked by def"), }; typ_item.attrs.push(syn::parse_quote!(#[allow(type_alias_bounds)])); - let typ_path = if let syn::Type::Path(p) = &mut *typ_item.ty { - p - } else { - unreachable!("Checked by def"); + let typ_path = match &mut *typ_item.ty { + syn::Type::Path(p) => p, + _ => unreachable!("Checked by def"), }; - let args = if let syn::PathArguments::AngleBracketed(args) = - &mut typ_path.path.segments[0].arguments - { - args - } else { - unreachable!("Checked by def"); + let args = match &mut typ_path.path.segments[0].arguments { + syn::PathArguments::AngleBracketed(args) => args, + _ => unreachable!("Checked by def"), }; + let prefix_ident = prefix_ident(&storage_def.ident); let type_use_gen = if def.config.has_instance { quote::quote_spanned!(storage_def.attr_span => T, I) } else { quote::quote_spanned!(storage_def.attr_span => T) }; - let prefix_ident = prefix_ident(&storage_def.ident); - args.args[0] = syn::parse_quote!( #prefix_ident<#type_use_gen> ); + + let default_query_kind: syn::Type = + syn::parse_quote!(#frame_support::storage::types::OptionQuery); + let default_on_empty: syn::Type = + syn::parse_quote!(#frame_support::traits::GetDefault); + let default_max_values: syn::Type = + syn::parse_quote!(#frame_support::traits::GetDefault); + + if let Some(named_generics) = storage_def.named_generics.clone() { + args.args.clear(); + args.args.push(syn::parse_quote!( #prefix_ident<#type_use_gen> )); + match named_generics { + StorageGenerics::Value { value, query_kind, on_empty } => { + args.args.push(syn::GenericArgument::Type(value)); + let query_kind = query_kind.unwrap_or_else(|| default_query_kind.clone()); + args.args.push(syn::GenericArgument::Type(query_kind)); + let on_empty = on_empty.unwrap_or_else(|| default_on_empty.clone()); + args.args.push(syn::GenericArgument::Type(on_empty)); + } + StorageGenerics::Map { hasher, key, value, query_kind, on_empty, max_values } => { + args.args.push(syn::GenericArgument::Type(hasher)); + args.args.push(syn::GenericArgument::Type(key)); + args.args.push(syn::GenericArgument::Type(value)); + let query_kind = query_kind.unwrap_or_else(|| default_query_kind.clone()); + args.args.push(syn::GenericArgument::Type(query_kind)); + let on_empty = on_empty.unwrap_or_else(|| default_on_empty.clone()); + args.args.push(syn::GenericArgument::Type(on_empty)); + let max_values = max_values.unwrap_or_else(|| default_max_values.clone()); + args.args.push(syn::GenericArgument::Type(max_values)); + } + StorageGenerics::DoubleMap { + hasher1, key1, hasher2, key2, value, query_kind, on_empty, max_values, + } => { + args.args.push(syn::GenericArgument::Type(hasher1)); + args.args.push(syn::GenericArgument::Type(key1)); + args.args.push(syn::GenericArgument::Type(hasher2)); + args.args.push(syn::GenericArgument::Type(key2)); + args.args.push(syn::GenericArgument::Type(value)); + let query_kind = query_kind.unwrap_or_else(|| default_query_kind.clone()); + args.args.push(syn::GenericArgument::Type(query_kind)); + let on_empty = on_empty.unwrap_or_else(|| default_on_empty.clone()); + args.args.push(syn::GenericArgument::Type(on_empty)); + let max_values = max_values.unwrap_or_else(|| default_max_values.clone()); + args.args.push(syn::GenericArgument::Type(max_values)); + } + StorageGenerics::NMap { keygen, value, query_kind, on_empty, max_values, } => { + args.args.push(syn::GenericArgument::Type(keygen)); + args.args.push(syn::GenericArgument::Type(value)); + let query_kind = query_kind.unwrap_or_else(|| default_query_kind.clone()); + args.args.push(syn::GenericArgument::Type(query_kind)); + let on_empty = on_empty.unwrap_or_else(|| default_on_empty.clone()); + args.args.push(syn::GenericArgument::Type(on_empty)); + let max_values = max_values.unwrap_or_else(|| default_max_values.clone()); + args.args.push(syn::GenericArgument::Type(max_values)); + } + } + } else { + args.args[0] = syn::parse_quote!( #prefix_ident<#type_use_gen> ); + } } +} + +/// * generate StoragePrefix structs (e.g. for a storage `MyStorage` a struct with the name +/// `_GeneratedPrefixForStorage$NameOfStorage` is generated) and implements StorageInstance trait. +/// * if generics are unnamed: replace the first generic `_` by the generated prefix structure +/// * if generics are named: reorder the generic, remove their name, and add the missing ones. +/// * Add `#[allow(type_alias_bounds)]` on storages type alias +/// * generate metadatas +pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream { + process_generics(def); + + let frame_support = &def.frame_support; + let frame_system = &def.frame_system; + let pallet_ident = &def.pallet_struct.pallet; + let entries = def.storages.iter() .map(|storage| { diff --git a/frame/support/procedural/src/pallet/parse/storage.rs b/frame/support/procedural/src/pallet/parse/storage.rs index 80c2e10a25206..6b842ab7fa401 100644 --- a/frame/support/procedural/src/pallet/parse/storage.rs +++ b/frame/support/procedural/src/pallet/parse/storage.rs @@ -18,6 +18,7 @@ use super::helper; use syn::spanned::Spanned; use quote::ToTokens; +use std::collections::HashMap; /// List of additional token to be used for parsing. mod keyword { @@ -51,17 +52,17 @@ impl syn::parse::Parse for PalletStorageAttr { /// The value and key types used by storages. Needed to expand metadata. pub enum Metadata { - Value { value: syn::GenericArgument }, - Map { value: syn::GenericArgument, key: syn::GenericArgument }, + Value { value: syn::Type }, + Map { value: syn::Type, key: syn::Type }, DoubleMap { - value: syn::GenericArgument, - key1: syn::GenericArgument, - key2: syn::GenericArgument + value: syn::Type, + key1: syn::Type, + key2: syn::Type }, NMap { keys: Vec, - keygen: syn::GenericArgument, - value: syn::GenericArgument, + keygen: syn::Type, + value: syn::Type, }, } @@ -98,41 +99,402 @@ pub struct StorageDef { pub attr_span: proc_macro2::Span, /// The `cfg` attributes. pub cfg_attrs: Vec, + /// If generics are named (e.g. `StorageValue`) then this contains all the + /// generics of the storage. + /// If generics are not named, this is none. + pub named_generics: Option, } -/// In `Foo` retrieve the argument at given position, i.e. A is argument at position 0. -fn retrieve_arg( + +/// The parsed generic from the +#[derive(Clone)] +pub enum StorageGenerics { + DoubleMap { + hasher1: syn::Type, + key1: syn::Type, + hasher2: syn::Type, + key2: syn::Type, + value: syn::Type, + query_kind: Option, + on_empty: Option, + max_values: Option, + }, + Map { + hasher: syn::Type, + key: syn::Type, + value: syn::Type, + query_kind: Option, + on_empty: Option, + max_values: Option, + }, + Value { + value: syn::Type, + query_kind: Option, + on_empty: Option, + }, + NMap { + keygen: syn::Type, + value: syn::Type, + query_kind: Option, + on_empty: Option, + max_values: Option, + }, +} + +impl StorageGenerics { + /// Return the metadata from the defined generics + fn metadata(&self) -> syn::Result { + let res = match self.clone() { + Self::DoubleMap { value, key1, key2, .. } => Metadata::DoubleMap { value, key1, key2 }, + Self::Map { value, key, .. } => Metadata::Map { value, key }, + Self::Value { value, .. } => Metadata::Value { value }, + Self::NMap { keygen, value, .. } => Metadata::NMap { + keys: collect_keys(&keygen)?, + keygen, + value, + }, + }; + + Ok(res) + } + + /// Return the query kind from the defined generics + fn query_kind(&self) -> Option { + match &self { + Self::DoubleMap { query_kind, .. } + | Self::Map { query_kind, .. } + | Self::Value { query_kind, .. } + | Self::NMap { query_kind, .. } + => query_kind.clone(), + } + } +} + +enum StorageKind { + Value, + Map, + DoubleMap, + NMap, +} + +/// Check the generics in the `map` contains the generics in `gen` may contains generics in +/// `optional_gen`, and doesn't contains any other. +fn check_generics( + map: &HashMap, + mandatory_generics: &[&str], + optional_generics: &[&str], + storage_type_name: &str, + args_span: proc_macro2::Span, +) -> syn::Result<()> { + let mut errors = vec![]; + + let expectation = { + let mut e = format!( + "`{}` expect generics {}and optional generics {}", + storage_type_name, + mandatory_generics.iter().map(|name| format!("`{}`, ", name)).collect::(), + &optional_generics.iter().map(|name| format!("`{}`, ", name)).collect::(), + ); + e.pop(); + e.pop(); + e.push_str("."); + e + }; + + for (gen_name, gen_binding) in map { + if !mandatory_generics.contains(&gen_name.as_str()) + && !optional_generics.contains(&gen_name.as_str()) + { + let msg = format!( + "Invalid pallet::storage, Unexpected generic `{}` for `{}`. {}", + gen_name, + storage_type_name, + expectation, + ); + errors.push(syn::Error::new(gen_binding.span(), msg)); + } + } + + for mandatory_generic in mandatory_generics { + if !map.contains_key(&mandatory_generic.to_string()) { + let msg = format!( + "Invalid pallet::storage, cannot find `{}` generic, required for `{}`.", + mandatory_generic, + storage_type_name + ); + errors.push(syn::Error::new(args_span, msg)); + } + } + + let mut errors = errors.drain(..); + if let Some(mut error) = errors.next() { + for other_error in errors { + error.combine(other_error); + } + Err(error) + } else { + Ok(()) + } +} + +/// Returns `(named generics, metadata, query kind)` +fn process_named_generics( + storage: &StorageKind, + args_span: proc_macro2::Span, + args: &[syn::Binding], +) -> syn::Result<(Option, Metadata, Option)> { + let mut parsed = HashMap::::new(); + + // Ensure no duplicate. + for arg in args { + if let Some(other) = parsed.get(&arg.ident.to_string()) { + let msg = "Invalid pallet::storage, Duplicated named generic"; + let mut err = syn::Error::new(arg.ident.span(), msg); + err.combine(syn::Error::new(other.ident.span(), msg)); + return Err(err); + } + parsed.insert(arg.ident.to_string(), arg.clone()); + } + + let generics = match storage { + StorageKind::Value => { + check_generics( + &parsed, + &["Value"], + &["QueryKind", "OnEmpty"], + "StorageValue", + args_span, + )?; + + StorageGenerics::Value { + value: parsed.remove("Value") + .map(|binding| binding.ty) + .expect("checked above as mandatory generic"), + query_kind: parsed.remove("QueryKind") + .map(|binding| binding.ty), + on_empty: parsed.remove("OnEmpty") + .map(|binding| binding.ty), + } + } + StorageKind::Map => { + check_generics( + &parsed, + &["Hasher", "Key", "Value"], + &["QueryKind", "OnEmpty", "MaxValues"], + "StorageMap", + args_span, + )?; + + StorageGenerics::Map { + hasher: parsed.remove("Hasher") + .map(|binding| binding.ty) + .expect("checked above as mandatory generic"), + key: parsed.remove("Key") + .map(|binding| binding.ty) + .expect("checked above as mandatory generic"), + value: parsed.remove("Value") + .map(|binding| binding.ty) + .expect("checked above as mandatory generic"), + query_kind: parsed.remove("QueryKind").map(|binding| binding.ty), + on_empty: parsed.remove("OnEmpty").map(|binding| binding.ty), + max_values: parsed.remove("MaxValues").map(|binding| binding.ty), + } + } + StorageKind::DoubleMap => { + check_generics( + &parsed, + &["Hasher1", "Key1", "Hasher2", "Key2", "Value"], + &["QueryKind", "OnEmpty", "MaxValues"], + "StorageDoubleMap", + args_span, + )?; + + StorageGenerics::DoubleMap { + hasher1: parsed.remove("Hasher1") + .map(|binding| binding.ty) + .expect("checked above as mandatory generic"), + key1: parsed.remove("Key1") + .map(|binding| binding.ty) + .expect("checked above as mandatory generic"), + hasher2: parsed.remove("Hasher2") + .map(|binding| binding.ty) + .expect("checked above as mandatory generic"), + key2: parsed.remove("Key2") + .map(|binding| binding.ty) + .expect("checked above as mandatory generic"), + value: parsed.remove("Value") + .map(|binding| binding.ty) + .expect("checked above as mandatory generic"), + query_kind: parsed.remove("QueryKind").map(|binding| binding.ty), + on_empty: parsed.remove("OnEmpty").map(|binding| binding.ty), + max_values: parsed.remove("MaxValues").map(|binding| binding.ty), + } + } + StorageKind::NMap => { + check_generics( + &parsed, + &["Key", "Value"], + &["QueryKind", "OnEmpty", "MaxValues"], + "StorageNMap", + args_span, + )?; + + StorageGenerics::NMap { + keygen: parsed.remove("Key") + .map(|binding| binding.ty) + .expect("checked above as mandatory generic"), + value: parsed.remove("Value") + .map(|binding| binding.ty) + .expect("checked above as mandatory generic"), + query_kind: parsed.remove("QueryKind").map(|binding| binding.ty), + on_empty: parsed.remove("OnEmpty").map(|binding| binding.ty), + max_values: parsed.remove("MaxValues").map(|binding| binding.ty), + } + } + }; + + let metadata = generics.metadata()?; + let query_kind = generics.query_kind(); + + Ok((Some(generics), metadata, query_kind)) +} + +/// Returns `(named generics, metadata, query kind)` +fn process_unnamed_generics( + storage: &StorageKind, + args_span: proc_macro2::Span, + args: &[syn::Type], +) -> syn::Result<(Option, Metadata, Option)> { + let retrieve_arg = |arg_pos| { + args.get(arg_pos) + .cloned() + .ok_or_else(|| { + let msg = format!( + "Invalid pallet::storage, unexpected number of generic argument, \ + expect at least {} args, found {}.", + arg_pos + 1, + args.len(), + ); + syn::Error::new(args_span, msg) + }) + }; + + let prefix_arg = retrieve_arg(0)?; + syn::parse2::(prefix_arg.to_token_stream()) + .map_err(|e| { + let msg = "Invalid pallet::storage, for unnamed generic arguments the type \ + first generic argument must be `_`, the argument is then replaced by macro."; + let mut err = syn::Error::new(prefix_arg.span(), msg); + err.combine(e); + err + })?; + + let res = match storage { + StorageKind::Value => ( + None, + Metadata::Value { value: retrieve_arg(1)? }, + retrieve_arg(2).ok(), + ), + StorageKind::Map => ( + None, + Metadata::Map { + key: retrieve_arg(2)?, + value: retrieve_arg(3)?, + }, + retrieve_arg(4).ok(), + ), + StorageKind::DoubleMap => ( + None, + Metadata::DoubleMap { + key1: retrieve_arg(2)?, + key2: retrieve_arg(4)?, + value: retrieve_arg(5)?, + }, + retrieve_arg(6).ok(), + ), + StorageKind::NMap => { + let keygen = retrieve_arg(1)?; + let keys = collect_keys(&keygen)?; + ( + None, + Metadata::NMap { + keys, + keygen, + value: retrieve_arg(2)?, + }, + retrieve_arg(3).ok(), + ) + }, + }; + + Ok(res) +} + +/// Returns `(named generics, metadata, query kind)` +fn process_generics( segment: &syn::PathSegment, - arg_pos: usize, -) -> syn::Result { - if let syn::PathArguments::AngleBracketed(args) = &segment.arguments { - if arg_pos < args.args.len() { - Ok(args.args[arg_pos].clone()) - } else { - let msg = format!("pallet::storage unexpected number of generic argument, expected at \ - least {} args, found {}", arg_pos + 1, args.args.len()); - Err(syn::Error::new(args.span(), msg)) +) -> syn::Result<(Option, Metadata, Option)> { + let storage_kind = match &*segment.ident.to_string() { + "StorageValue" => StorageKind::Value, + "StorageMap" => StorageKind::Map, + "StorageDoubleMap" => StorageKind::DoubleMap, + "StorageNMap" => StorageKind::NMap, + found => { + let msg = format!( + "Invalid pallet::storage, expected ident: `StorageValue` or \ + `StorageMap` or `StorageDoubleMap` or `StorageNMap` in order to expand metadata, \ + found `{}`.", + found, + ); + return Err(syn::Error::new(segment.ident.span(), msg)); } + }; + + let args_span = segment.arguments.span(); + + let args = match &segment.arguments { + syn::PathArguments::AngleBracketed(args) if args.args.len() != 0 => args, + _ => { + let msg = "Invalid pallet::storage, invalid number of generic generic arguments, \ + expect more that 0 generic arguments."; + return Err(syn::Error::new(segment.span(), msg)); + } + }; + + if args.args.iter().all(|gen| matches!(gen, syn::GenericArgument::Type(_))) { + let args = args.args.iter() + .map(|gen| match gen { + syn::GenericArgument::Type(gen) => gen.clone(), + _ => unreachable!("It is asserted above that all generics are types"), + }) + .collect::>(); + process_unnamed_generics(&storage_kind, args_span, &args) + } else if args.args.iter().all(|gen| matches!(gen, syn::GenericArgument::Binding(_))) { + let args = args.args.iter() + .map(|gen| match gen { + syn::GenericArgument::Binding(gen) => gen.clone(), + _ => unreachable!("It is asserted above that all generics are bindings"), + }) + .collect::>(); + process_named_generics(&storage_kind, args_span, &args) } else { - let msg = format!("pallet::storage unexpected number of generic argument, expected at \ - least {} args, found none", arg_pos + 1); + let msg = "Invalid pallet::storage, invalid generic declaration for storage. Expect only \ + type generics or binding generics, e.g. `` or \ + ``."; Err(syn::Error::new(segment.span(), msg)) } } /// Parse the 2nd type argument to `StorageNMap` and return its keys. -fn collect_keys(keygen: &syn::GenericArgument) -> syn::Result> { - if let syn::GenericArgument::Type(syn::Type::Tuple(tup)) = keygen { +fn collect_keys(keygen: &syn::Type) -> syn::Result> { + if let syn::Type::Tuple(tup) = keygen { tup .elems .iter() .map(extract_key) .collect::>>() - } else if let syn::GenericArgument::Type(ty) = keygen { - Ok(vec![extract_key(ty)?]) } else { - let msg = format!("Invalid pallet::storage, expected tuple of Key structs or Key struct"); - Err(syn::Error::new(keygen.span(), msg)) + Ok(vec![extract_key(keygen)?]) } } @@ -187,7 +549,7 @@ impl StorageDef { let item = if let syn::Item::Type(item) = item { item } else { - return Err(syn::Error::new(item.span(), "Invalid pallet::storage, expected item type")); + return Err(syn::Error::new(item.span(), "Invalid pallet::storage, expect item type.")); }; let mut attrs: Vec = helper::take_item_pallet_attrs(&mut item.attrs)?; @@ -217,55 +579,14 @@ impl StorageDef { return Err(syn::Error::new(item.ty.span(), msg)); } - let query_kind; - let metadata = match &*typ.path.segments[0].ident.to_string() { - "StorageValue" => { - query_kind = retrieve_arg(&typ.path.segments[0], 2); - Metadata::Value { - value: retrieve_arg(&typ.path.segments[0], 1)?, - } - } - "StorageMap" => { - query_kind = retrieve_arg(&typ.path.segments[0], 4); - Metadata::Map { - key: retrieve_arg(&typ.path.segments[0], 2)?, - value: retrieve_arg(&typ.path.segments[0], 3)?, - } - } - "StorageDoubleMap" => { - query_kind = retrieve_arg(&typ.path.segments[0], 6); - Metadata::DoubleMap { - key1: retrieve_arg(&typ.path.segments[0], 2)?, - key2: retrieve_arg(&typ.path.segments[0], 4)?, - value: retrieve_arg(&typ.path.segments[0], 5)?, - } - } - "StorageNMap" => { - query_kind = retrieve_arg(&typ.path.segments[0], 3); - let keygen = retrieve_arg(&typ.path.segments[0], 1)?; - let keys = collect_keys(&keygen)?; - Metadata::NMap { - keys, - keygen, - value: retrieve_arg(&typ.path.segments[0], 2)?, - } - } - found => { - let msg = format!( - "Invalid pallet::storage, expected ident: `StorageValue` or \ - `StorageMap` or `StorageDoubleMap` or `StorageNMap` in order \ - to expand metadata, found `{}`", - found, - ); - return Err(syn::Error::new(item.ty.span(), msg)); - } - }; + let (named_generics, metadata, query_kind) = process_generics(&typ.path.segments[0])?; + let query_kind = query_kind .map(|query_kind| match query_kind { - syn::GenericArgument::Type(syn::Type::Path(path)) + syn::Type::Path(path) if path.path.segments.last().map_or(false, |s| s.ident == "OptionQuery") => Some(QueryKind::OptionQuery), - syn::GenericArgument::Type(syn::Type::Path(path)) + syn::Type::Path(path) if path.path.segments.last().map_or(false, |s| s.ident == "ValueQuery") => Some(QueryKind::ValueQuery), _ => None, @@ -279,16 +600,6 @@ impl StorageDef { return Err(syn::Error::new(getter.unwrap().span(), msg)); } - let prefix_arg = retrieve_arg(&typ.path.segments[0], 0)?; - syn::parse2::(prefix_arg.to_token_stream()) - .map_err(|e| { - let msg = "Invalid use of `#[pallet::storage]`, the type first generic argument \ - must be `_`, the final argument is automatically set by macro."; - let mut err = syn::Error::new(prefix_arg.span(), msg); - err.combine(e); - err - })?; - Ok(StorageDef { attr_span, index, @@ -301,6 +612,7 @@ impl StorageDef { query_kind, where_clause, cfg_attrs, + named_generics, }) } } diff --git a/frame/support/src/lib.rs b/frame/support/src/lib.rs index 0f96cdd023195..cc7bd2126c0c2 100644 --- a/frame/support/src/lib.rs +++ b/frame/support/src/lib.rs @@ -1586,11 +1586,28 @@ pub mod pallet_prelude { /// #[pallet::storage] /// #[pallet::getter(fn $getter_name)] // optional /// $vis type $StorageName<$some_generic> $optional_where_clause +/// = $StorageType<$generic_name = $some_generics, $other_name = $some_other, ...>; +/// ``` +/// or with unnamed generic +/// ```ignore +/// #[pallet::storage] +/// #[pallet::getter(fn $getter_name)] // optional +/// $vis type $StorageName<$some_generic> $optional_where_clause /// = $StorageType<_, $some_generics, ...>; /// ``` /// I.e. it must be a type alias, with generics: `T` or `T: Config`, aliased type must be one /// of `StorageValue`, `StorageMap` or `StorageDoubleMap` (defined in frame_support). -/// Their first generic must be `_` as it is written by the macro itself. +/// The generic arguments of the storage type can be given in two manner: named and unnamed. +/// For named generic argument: the name for each argument is the one as define on the storage +/// struct: +/// * [`pallet_prelude::StorageValue`] expect `Value` and optionally `QueryKind` and `OnEmpty`, +/// * [`pallet_prelude::StorageMap`] expect `Hasher`, `Key`, `Value` and optionally `QueryKind` and +/// `OnEmpty`, +/// * [`pallet_prelude::StorageDoubleMap`] expect `Hasher1`, `Key1`, `Hasher2`, `Key2`, `Value` and +/// optionally `QueryKind` and `OnEmpty`. +/// +/// For unnamed generic argument: Their first generic must be `_` as it is replaced by the macro +/// and other generic must declared as a normal declaration of type generic in rust. /// /// The Prefix generic written by the macro is generated using `PalletInfo::name::>()` /// and the name of the storage type. @@ -1604,6 +1621,12 @@ pub mod pallet_prelude { /// ```ignore /// #[pallet::storage] /// #[pallet::getter(fn my_storage)] +/// pub(super) type MyStorage = StorageMap; +/// ``` +/// or +/// ```ignore +/// #[pallet::storage] +/// #[pallet::getter(fn my_storage)] /// pub(super) type MyStorage = StorageMap<_, Blake2_128Concat, u32, u32>; /// ``` /// @@ -1613,7 +1636,7 @@ pub mod pallet_prelude { /// ```ignore /// #[cfg(feature = "my-feature")] /// #[pallet::storage] -/// pub(super) type MyStorage = StorageValue<_, u32>; +/// pub(super) type MyStorage = StorageValue; /// ``` /// /// All the `cfg` attributes are automatically copied to the items generated for the storage, i.e. the @@ -1630,10 +1653,11 @@ pub mod pallet_prelude { /// ### Macro expansion /// /// For each storage item the macro generates a struct named -/// `_GeneratedPrefixForStorage$NameOfStorage`, and implements [`StorageInstance`](traits::StorageInstance) -/// on it using the pallet and storage name. It then uses it as the first generic of the aliased -/// type. +/// `_GeneratedPrefixForStorage$NameOfStorage`, and implements +/// [`StorageInstance`](traits::StorageInstance) on it using the pallet and storage name. It then +/// uses it as the first generic of the aliased type. /// +/// For named generic, the macro will reorder the generics, and remove the names. /// /// The macro implements the function `storage_metadata` on `Pallet` implementing the metadata for /// all storage items based on their kind: @@ -1915,12 +1939,13 @@ pub mod pallet_prelude { /// // storage item. Thus generic hasher is supported. /// #[pallet::storage] /// pub(super) type MyStorageValue = -/// StorageValue<_, T::Balance, ValueQuery, MyDefault>; +/// StorageValue>; /// /// // Another storage declaration /// #[pallet::storage] /// #[pallet::getter(fn my_storage)] -/// pub(super) type MyStorage = StorageMap<_, Blake2_128Concat, u32, u32>; +/// pub(super) type MyStorage = +/// StorageMap; /// /// // Declare the genesis config (optional). /// // @@ -2057,12 +2082,12 @@ pub mod pallet_prelude { /// /// #[pallet::storage] /// pub(super) type MyStorageValue, I: 'static = ()> = -/// StorageValue<_, T::Balance, ValueQuery, MyDefault>; +/// StorageValue>; /// /// #[pallet::storage] /// #[pallet::getter(fn my_storage)] /// pub(super) type MyStorage = -/// StorageMap<_, Blake2_128Concat, u32, u32>; +/// StorageMap; /// /// #[pallet::genesis_config] /// #[derive(Default)] @@ -2234,7 +2259,7 @@ pub mod pallet_prelude { /// ```ignore /// #[pallet::type_value] fn MyStorageOnEmpty() -> u32 { 3u32 } /// #[pallet::storage] -/// pub(super) type MyStorage = StorageValue; +/// pub(super) type MyStorage = StorageValue<_, u32, ValueQuery, MyStorageOnEmpty>; /// ``` /// /// NOTE: `decl_storage` also generates functions `assimilate_storage` and `build_storage` diff --git a/frame/support/test/tests/pallet.rs b/frame/support/test/tests/pallet.rs index 0a768c79e779c..cc3d83f47232d 100644 --- a/frame/support/test/tests/pallet.rs +++ b/frame/support/test/tests/pallet.rs @@ -196,7 +196,7 @@ pub mod pallet { StorageValue<_, ::_2>; #[pallet::storage] - pub type Value = StorageValue<_, u32>; + pub type Value = StorageValue; #[pallet::type_value] pub fn MyDefault() -> u16 @@ -211,14 +211,19 @@ pub mod pallet { StorageMap<_, Blake2_128Concat, u8, u16, ValueQuery, MyDefault>; #[pallet::storage] - pub type Map2 = StorageMap<_, Twox64Concat, u16, u32, OptionQuery, GetDefault, ConstU32<3>>; + pub type Map2 = StorageMap< + Hasher = Twox64Concat, Key = u16, Value = u32, MaxValues = ConstU32<3> + >; #[pallet::storage] pub type DoubleMap = StorageDoubleMap<_, Blake2_128Concat, u8, Twox64Concat, u16, u32>; #[pallet::storage] pub type DoubleMap2 = StorageDoubleMap< - _, Twox64Concat, u16, Blake2_128Concat, u32, u64, OptionQuery, GetDefault, ConstU32<5>, + Hasher1 = Twox64Concat, Key1 = u16, + Hasher2 = Blake2_128Concat, Key2 = u32, + Value = u64, + MaxValues = ConstU32<5>, >; #[pallet::storage] @@ -228,15 +233,9 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn nmap2)] pub type NMap2 = StorageNMap< - _, - ( - NMapKey, - NMapKey, - ), - u64, - OptionQuery, - GetDefault, - ConstU32<11>, + Key = (NMapKey, NMapKey), + Value = u64, + MaxValues = ConstU32<11>, >; #[pallet::storage] diff --git a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.rs b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.rs new file mode 100644 index 0000000000000..30b6d651f3b89 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.rs @@ -0,0 +1,25 @@ +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::{Hooks, StorageValue}; + use frame_system::pallet_prelude::BlockNumberFor; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::call] + impl Pallet {} + + struct Bar; + + #[pallet::storage] + type Foo = StorageValue; +} + +fn main() { +} diff --git a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr new file mode 100644 index 0000000000000..e2802b5e545f7 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr @@ -0,0 +1,33 @@ +error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied + --> $DIR/storage_ensure_span_are_ok_on_wrong_gen.rs:20:12 + | +20 | #[pallet::storage] + | ^^^^^^^ the trait `WrapperTypeDecode` is not implemented for `Bar` + | + = note: required because of the requirements on the impl of `Decode` for `Bar` + = note: required because of the requirements on the impl of `FullCodec` for `Bar` + = note: required because of the requirements on the impl of `StorageValueMetadata` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` + = note: required by `frame_support::storage::types::StorageValueMetadata::NAME` + +error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied + --> $DIR/storage_ensure_span_are_ok_on_wrong_gen.rs:20:12 + | +20 | #[pallet::storage] + | ^^^^^^^ the trait `EncodeLike` is not implemented for `Bar` + | + = note: required because of the requirements on the impl of `FullEncode` for `Bar` + = note: required because of the requirements on the impl of `FullCodec` for `Bar` + = note: required because of the requirements on the impl of `StorageValueMetadata` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` + = note: required by `frame_support::storage::types::StorageValueMetadata::NAME` + +error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied + --> $DIR/storage_ensure_span_are_ok_on_wrong_gen.rs:20:12 + | +20 | #[pallet::storage] + | ^^^^^^^ the trait `WrapperTypeEncode` is not implemented for `Bar` + | + = note: required because of the requirements on the impl of `pallet::_::_parity_scale_codec::Encode` for `Bar` + = note: required because of the requirements on the impl of `FullEncode` for `Bar` + = note: required because of the requirements on the impl of `FullCodec` for `Bar` + = note: required because of the requirements on the impl of `StorageValueMetadata` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` + = note: required by `frame_support::storage::types::StorageValueMetadata::NAME` diff --git a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs new file mode 100644 index 0000000000000..ddb19121660da --- /dev/null +++ b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs @@ -0,0 +1,25 @@ +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::{Hooks, StorageValue}; + use frame_system::pallet_prelude::BlockNumberFor; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::call] + impl Pallet {} + + struct Bar; + + #[pallet::storage] + type Foo = StorageValue<_, Bar>; +} + +fn main() { +} diff --git a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr new file mode 100644 index 0000000000000..e54a8c227eea2 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr @@ -0,0 +1,33 @@ +error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied + --> $DIR/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs:20:12 + | +20 | #[pallet::storage] + | ^^^^^^^ the trait `WrapperTypeDecode` is not implemented for `Bar` + | + = note: required because of the requirements on the impl of `Decode` for `Bar` + = note: required because of the requirements on the impl of `FullCodec` for `Bar` + = note: required because of the requirements on the impl of `StorageValueMetadata` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` + = note: required by `frame_support::storage::types::StorageValueMetadata::NAME` + +error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied + --> $DIR/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs:20:12 + | +20 | #[pallet::storage] + | ^^^^^^^ the trait `EncodeLike` is not implemented for `Bar` + | + = note: required because of the requirements on the impl of `FullEncode` for `Bar` + = note: required because of the requirements on the impl of `FullCodec` for `Bar` + = note: required because of the requirements on the impl of `StorageValueMetadata` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` + = note: required by `frame_support::storage::types::StorageValueMetadata::NAME` + +error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied + --> $DIR/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs:20:12 + | +20 | #[pallet::storage] + | ^^^^^^^ the trait `WrapperTypeEncode` is not implemented for `Bar` + | + = note: required because of the requirements on the impl of `pallet::_::_parity_scale_codec::Encode` for `Bar` + = note: required because of the requirements on the impl of `FullEncode` for `Bar` + = note: required because of the requirements on the impl of `FullCodec` for `Bar` + = note: required because of the requirements on the impl of `StorageValueMetadata` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` + = note: required by `frame_support::storage::types::StorageValueMetadata::NAME` diff --git a/frame/support/test/tests/pallet_ui/storage_invalid_first_generic.stderr b/frame/support/test/tests/pallet_ui/storage_invalid_first_generic.stderr index d332e6c2d3d1b..b37f7e57f3552 100644 --- a/frame/support/test/tests/pallet_ui/storage_invalid_first_generic.stderr +++ b/frame/support/test/tests/pallet_ui/storage_invalid_first_generic.stderr @@ -1,4 +1,4 @@ -error: Invalid use of `#[pallet::storage]`, the type first generic argument must be `_`, the final argument is automatically set by macro. +error: Invalid pallet::storage, for unnamed generic arguments the type first generic argument must be `_`, the argument is then replaced by macro. --> $DIR/storage_invalid_first_generic.rs:19:29 | 19 | type Foo = StorageValue; diff --git a/frame/support/test/tests/pallet_ui/storage_not_storage_type.stderr b/frame/support/test/tests/pallet_ui/storage_not_storage_type.stderr index 73fda60942475..4fd59183282d0 100644 --- a/frame/support/test/tests/pallet_ui/storage_not_storage_type.stderr +++ b/frame/support/test/tests/pallet_ui/storage_not_storage_type.stderr @@ -1,4 +1,4 @@ -error: Invalid pallet::storage, expected ident: `StorageValue` or `StorageMap` or `StorageDoubleMap` or `StorageNMap` in order to expand metadata, found `u8` +error: Invalid pallet::storage, expected ident: `StorageValue` or `StorageMap` or `StorageDoubleMap` or `StorageNMap` in order to expand metadata, found `u8`. --> $DIR/storage_not_storage_type.rs:19:16 | 19 | type Foo = u8; diff --git a/frame/support/test/tests/pallet_ui/storage_value_duplicate_named_generic.rs b/frame/support/test/tests/pallet_ui/storage_value_duplicate_named_generic.rs new file mode 100644 index 0000000000000..1f076b1ecbfc6 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/storage_value_duplicate_named_generic.rs @@ -0,0 +1,23 @@ +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::{Hooks, StorageValue}; + use frame_system::pallet_prelude::BlockNumberFor; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::call] + impl Pallet {} + + #[pallet::storage] + type Foo = StorageValue; +} + +fn main() { +} diff --git a/frame/support/test/tests/pallet_ui/storage_value_duplicate_named_generic.stderr b/frame/support/test/tests/pallet_ui/storage_value_duplicate_named_generic.stderr new file mode 100644 index 0000000000000..3def9061fec8a --- /dev/null +++ b/frame/support/test/tests/pallet_ui/storage_value_duplicate_named_generic.stderr @@ -0,0 +1,11 @@ +error: Invalid pallet::storage, Duplicated named generic + --> $DIR/storage_value_duplicate_named_generic.rs:19:42 + | +19 | type Foo = StorageValue; + | ^^^^^ + +error: Invalid pallet::storage, Duplicated named generic + --> $DIR/storage_value_duplicate_named_generic.rs:19:29 + | +19 | type Foo = StorageValue; + | ^^^^^ diff --git a/frame/support/test/tests/pallet_ui/storage_value_generic_named_and_unnamed.rs b/frame/support/test/tests/pallet_ui/storage_value_generic_named_and_unnamed.rs new file mode 100644 index 0000000000000..fd0ea4794bc43 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/storage_value_generic_named_and_unnamed.rs @@ -0,0 +1,23 @@ +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::{Hooks, StorageValue, OptionQuery}; + use frame_system::pallet_prelude::BlockNumberFor; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::call] + impl Pallet {} + + #[pallet::storage] + type Foo = StorageValue; +} + +fn main() { +} diff --git a/frame/support/test/tests/pallet_ui/storage_value_generic_named_and_unnamed.stderr b/frame/support/test/tests/pallet_ui/storage_value_generic_named_and_unnamed.stderr new file mode 100644 index 0000000000000..61c01943cc3f5 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/storage_value_generic_named_and_unnamed.stderr @@ -0,0 +1,5 @@ +error: Invalid pallet::storage, invalid generic declaration for storage. Expect only type generics or binding generics, e.g. `` or ``. + --> $DIR/storage_value_generic_named_and_unnamed.rs:19:16 + | +19 | type Foo = StorageValue; + | ^^^^^^^^^^^^ diff --git a/frame/support/test/tests/pallet_ui/storage_value_no_generic.stderr b/frame/support/test/tests/pallet_ui/storage_value_no_generic.stderr index 894f7095b2b5a..f7449c5ffda7d 100644 --- a/frame/support/test/tests/pallet_ui/storage_value_no_generic.stderr +++ b/frame/support/test/tests/pallet_ui/storage_value_no_generic.stderr @@ -1,4 +1,4 @@ -error: pallet::storage unexpected number of generic argument, expected at least 2 args, found none +error: Invalid pallet::storage, invalid number of generic generic arguments, expect more that 0 generic arguments. --> $DIR/storage_value_no_generic.rs:19:16 | 19 | type Foo = StorageValue; diff --git a/frame/support/test/tests/pallet_ui/storage_value_unexpected_named_generic.rs b/frame/support/test/tests/pallet_ui/storage_value_unexpected_named_generic.rs new file mode 100644 index 0000000000000..a3e54448e42ad --- /dev/null +++ b/frame/support/test/tests/pallet_ui/storage_value_unexpected_named_generic.rs @@ -0,0 +1,23 @@ +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::{Hooks, StorageValue}; + use frame_system::pallet_prelude::BlockNumberFor; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::call] + impl Pallet {} + + #[pallet::storage] + type Foo = StorageValue

; +} + +fn main() { +} diff --git a/frame/support/test/tests/pallet_ui/storage_value_unexpected_named_generic.stderr b/frame/support/test/tests/pallet_ui/storage_value_unexpected_named_generic.stderr new file mode 100644 index 0000000000000..f03b71ff5eb6e --- /dev/null +++ b/frame/support/test/tests/pallet_ui/storage_value_unexpected_named_generic.stderr @@ -0,0 +1,11 @@ +error: Invalid pallet::storage, Unexpected generic `P` for `StorageValue`. `StorageValue` expect generics `Value`, and optional generics `QueryKind`, `OnEmpty`. + --> $DIR/storage_value_unexpected_named_generic.rs:19:29 + | +19 | type Foo = StorageValue

; + | ^ + +error: Invalid pallet::storage, cannot find `Value` generic, required for `StorageValue`. + --> $DIR/storage_value_unexpected_named_generic.rs:19:28 + | +19 | type Foo = StorageValue

; + | ^ diff --git a/frame/support/test/tests/pallet_ui/storage_wrong_item.stderr b/frame/support/test/tests/pallet_ui/storage_wrong_item.stderr index 8cc180b5bfe49..d875d8acec66f 100644 --- a/frame/support/test/tests/pallet_ui/storage_wrong_item.stderr +++ b/frame/support/test/tests/pallet_ui/storage_wrong_item.stderr @@ -1,4 +1,4 @@ -error: Invalid pallet::storage, expected item type +error: Invalid pallet::storage, expect item type. --> $DIR/storage_wrong_item.rs:19:2 | 19 | impl Foo {}