Implement ResultQuery (#11257)

* Implement ResultQuery

* Fix test expectations

* Add more tests

* Fix test expectations

* Clean up some names

* Silence warnings

* Specify error type when supplying error type to ResultQuery

* cargo fmt

* Add support for type parameters in parameter_types macro

* Reduce deeply indented code

* Fixes

* Update test expectation

* Rewrite and document formula for calculating max storage size

* More docs

* cargo fmt

* formatting

Co-authored-by: parity-processbot <>
This commit is contained in:
Keith Yeung
2022-08-25 01:47:08 +08:00
committed by GitHub
parent 6aba2fdc99
commit edc8f7b409
16 changed files with 770 additions and 77 deletions
@@ -19,7 +19,9 @@ use crate::pallet::{
parse::storage::{Metadata, QueryKind, StorageDef, StorageGenerics},
Def,
};
use std::collections::HashMap;
use quote::ToTokens;
use std::{collections::HashMap, ops::IndexMut};
use syn::spanned::Spanned;
/// Generate the prefix_ident related to the storage.
/// prefix_ident is used for the prefix struct to be given to storage as first generic param.
@@ -84,12 +86,28 @@ fn check_prefix_duplicates(
Ok(())
}
pub struct ResultOnEmptyStructMetadata {
/// The Rust ident that is going to be used as the name of the OnEmpty struct.
pub name: syn::Ident,
/// The path to the error type being returned by the ResultQuery.
pub error_path: syn::Path,
/// The visibility of the OnEmpty struct.
pub visibility: syn::Visibility,
/// The type of the storage item.
pub value_ty: syn::Type,
/// The name of the pallet error enum variant that is going to be returned.
pub variant_name: syn::Ident,
/// The span used to report compilation errors about the OnEmpty struct.
pub span: proc_macro2::Span,
}
///
/// * 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) -> syn::Result<()> {
pub fn process_generics(def: &mut Def) -> syn::Result<Vec<ResultOnEmptyStructMetadata>> {
let frame_support = &def.frame_support;
let mut on_empty_struct_metadata = Vec::new();
for storage_def in def.storages.iter_mut() {
let item = &mut def.item.content.as_mut().expect("Checked by def").1[storage_def.index];
@@ -120,27 +138,72 @@ pub fn process_generics(def: &mut Def) -> syn::Result<()> {
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 mut default_on_empty = |value_ty: syn::Type| -> syn::Type {
if let Some(QueryKind::ResultQuery(error_path, variant_name)) =
storage_def.query_kind.as_ref()
{
let on_empty_ident =
quote::format_ident!("__Frame_Internal_Get{}Result", storage_def.ident);
on_empty_struct_metadata.push(ResultOnEmptyStructMetadata {
name: on_empty_ident.clone(),
visibility: storage_def.vis.clone(),
value_ty,
error_path: error_path.clone(),
variant_name: variant_name.clone(),
span: storage_def.attr_span,
});
return syn::parse_quote!(#on_empty_ident)
}
syn::parse_quote!(#frame_support::traits::GetDefault)
};
let default_max_values: syn::Type = syn::parse_quote!(#frame_support::traits::GetDefault);
let set_result_query_type_parameter = |query_type: &mut syn::Type| -> syn::Result<()> {
if let Some(QueryKind::ResultQuery(error_path, _)) = storage_def.query_kind.as_ref() {
if let syn::Type::Path(syn::TypePath { path: syn::Path { segments, .. }, .. }) =
query_type
{
if let Some(seg) = segments.last_mut() {
if let syn::PathArguments::AngleBracketed(
syn::AngleBracketedGenericArguments { args, .. },
) = &mut seg.arguments
{
args.clear();
args.push(syn::GenericArgument::Type(syn::parse_quote!(#error_path)));
}
}
} else {
let msg = format!(
"Invalid pallet::storage, unexpected type for query, expected ResultQuery \
with 1 type parameter, found `{}`",
query_type.to_token_stream().to_string()
);
return Err(syn::Error::new(query_type.span(), msg))
}
}
Ok(())
};
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(value.clone()));
let mut query_kind = query_kind.unwrap_or_else(|| default_query_kind.clone());
set_result_query_type_parameter(&mut query_kind)?;
args.args.push(syn::GenericArgument::Type(query_kind));
let on_empty = on_empty.unwrap_or_else(|| default_on_empty.clone());
let on_empty = on_empty.unwrap_or_else(|| default_on_empty(value));
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(value.clone()));
let mut query_kind = query_kind.unwrap_or_else(|| default_query_kind.clone());
set_result_query_type_parameter(&mut query_kind)?;
args.args.push(syn::GenericArgument::Type(query_kind));
let on_empty = on_empty.unwrap_or_else(|| default_on_empty.clone());
let on_empty = on_empty.unwrap_or_else(|| default_on_empty(value));
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));
@@ -155,10 +218,11 @@ pub fn process_generics(def: &mut Def) -> syn::Result<()> {
} => {
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(value.clone()));
let mut query_kind = query_kind.unwrap_or_else(|| default_query_kind.clone());
set_result_query_type_parameter(&mut query_kind)?;
args.args.push(syn::GenericArgument::Type(query_kind));
let on_empty = on_empty.unwrap_or_else(|| default_on_empty.clone());
let on_empty = on_empty.unwrap_or_else(|| default_on_empty(value));
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));
@@ -177,20 +241,22 @@ pub fn process_generics(def: &mut Def) -> syn::Result<()> {
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(value.clone()));
let mut query_kind = query_kind.unwrap_or_else(|| default_query_kind.clone());
set_result_query_type_parameter(&mut query_kind)?;
args.args.push(syn::GenericArgument::Type(query_kind));
let on_empty = on_empty.unwrap_or_else(|| default_on_empty.clone());
let on_empty = on_empty.unwrap_or_else(|| default_on_empty(value));
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(value.clone()));
let mut query_kind = query_kind.unwrap_or_else(|| default_query_kind.clone());
set_result_query_type_parameter(&mut query_kind)?;
args.args.push(syn::GenericArgument::Type(query_kind));
let on_empty = on_empty.unwrap_or_else(|| default_on_empty.clone());
let on_empty = on_empty.unwrap_or_else(|| default_on_empty(value));
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));
@@ -198,10 +264,40 @@ pub fn process_generics(def: &mut Def) -> syn::Result<()> {
}
} else {
args.args[0] = syn::parse_quote!( #prefix_ident<#type_use_gen> );
let (value_idx, query_idx, on_empty_idx) = match storage_def.metadata {
Metadata::Value { .. } => (1, 2, 3),
Metadata::NMap { .. } => (2, 3, 4),
Metadata::Map { .. } | Metadata::CountedMap { .. } => (3, 4, 5),
Metadata::DoubleMap { .. } => (5, 6, 7),
};
if query_idx < args.args.len() {
if let syn::GenericArgument::Type(query_kind) = args.args.index_mut(query_idx) {
set_result_query_type_parameter(query_kind)?;
}
} else if let Some(QueryKind::ResultQuery(error_path, _)) =
storage_def.query_kind.as_ref()
{
args.args.push(syn::GenericArgument::Type(syn::parse_quote!(#error_path)))
}
// Here, we only need to check if OnEmpty is *not* specified, and if so, then we have to
// generate a default OnEmpty struct for it.
if on_empty_idx >= args.args.len() &&
matches!(storage_def.query_kind.as_ref(), Some(QueryKind::ResultQuery(_, _)))
{
let value_ty = match args.args[value_idx].clone() {
syn::GenericArgument::Type(ty) => ty,
_ => unreachable!(),
};
let on_empty = default_on_empty(value_ty);
args.args.push(syn::GenericArgument::Type(on_empty));
}
}
}
Ok(())
Ok(on_empty_struct_metadata)
}
///
@@ -212,9 +308,10 @@ pub fn process_generics(def: &mut Def) -> syn::Result<()> {
/// * Add `#[allow(type_alias_bounds)]` on storages type alias
/// * generate metadatas
pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream {
if let Err(e) = process_generics(def) {
return e.into_compile_error()
}
let on_empty_struct_metadata = match process_generics(def) {
Ok(idents) => idents,
Err(e) => return e.into_compile_error(),
};
// Check for duplicate prefixes
let mut prefix_set = HashMap::new();
@@ -277,6 +374,10 @@ pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream {
QueryKind::OptionQuery => quote::quote_spanned!(storage.attr_span =>
Option<#value>
),
QueryKind::ResultQuery(error_path, _) =>
quote::quote_spanned!(storage.attr_span =>
Result<#value, #error_path>
),
QueryKind::ValueQuery => quote::quote!(#value),
};
quote::quote_spanned!(storage.attr_span =>
@@ -296,6 +397,10 @@ pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream {
QueryKind::OptionQuery => quote::quote_spanned!(storage.attr_span =>
Option<#value>
),
QueryKind::ResultQuery(error_path, _) =>
quote::quote_spanned!(storage.attr_span =>
Result<#value, #error_path>
),
QueryKind::ValueQuery => quote::quote!(#value),
};
quote::quote_spanned!(storage.attr_span =>
@@ -317,6 +422,10 @@ pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream {
QueryKind::OptionQuery => quote::quote_spanned!(storage.attr_span =>
Option<#value>
),
QueryKind::ResultQuery(error_path, _) =>
quote::quote_spanned!(storage.attr_span =>
Result<#value, #error_path>
),
QueryKind::ValueQuery => quote::quote!(#value),
};
quote::quote_spanned!(storage.attr_span =>
@@ -338,6 +447,10 @@ pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream {
QueryKind::OptionQuery => quote::quote_spanned!(storage.attr_span =>
Option<#value>
),
QueryKind::ResultQuery(error_path, _) =>
quote::quote_spanned!(storage.attr_span =>
Result<#value, #error_path>
),
QueryKind::ValueQuery => quote::quote!(#value),
};
quote::quote_spanned!(storage.attr_span =>
@@ -361,6 +474,10 @@ pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream {
QueryKind::OptionQuery => quote::quote_spanned!(storage.attr_span =>
Option<#value>
),
QueryKind::ResultQuery(error_path, _) =>
quote::quote_spanned!(storage.attr_span =>
Result<#value, #error_path>
),
QueryKind::ValueQuery => quote::quote!(#value),
};
quote::quote_spanned!(storage.attr_span =>
@@ -459,6 +576,61 @@ pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream {
)
});
let on_empty_structs = on_empty_struct_metadata.into_iter().map(|metadata| {
use crate::pallet::parse::GenericKind;
use syn::{GenericArgument, Path, PathArguments, PathSegment, Type, TypePath};
let ResultOnEmptyStructMetadata {
name,
visibility,
value_ty,
error_path,
variant_name,
span,
} = metadata;
let generic_kind = match error_path.segments.last() {
Some(PathSegment { arguments: PathArguments::AngleBracketed(args), .. }) => {
let (has_config, has_instance) =
args.args.iter().fold((false, false), |(has_config, has_instance), arg| {
match arg {
GenericArgument::Type(Type::Path(TypePath {
path: Path { segments, .. },
..
})) => {
let maybe_config =
segments.first().map_or(false, |seg| seg.ident == "T");
let maybe_instance =
segments.first().map_or(false, |seg| seg.ident == "I");
(has_config || maybe_config, has_instance || maybe_instance)
},
_ => (has_config, has_instance),
}
});
GenericKind::from_gens(has_config, has_instance).unwrap_or(GenericKind::None)
},
_ => GenericKind::None,
};
let type_impl_gen = generic_kind.type_impl_gen(proc_macro2::Span::call_site());
let config_where_clause = &def.config.where_clause;
quote::quote_spanned!(span =>
#[doc(hidden)]
#[allow(non_camel_case_types)]
#visibility struct #name;
impl<#type_impl_gen> #frame_support::traits::Get<Result<#value_ty, #error_path>>
for #name
#config_where_clause
{
fn get() -> Result<#value_ty, #error_path> {
Err(<#error_path>::#variant_name)
}
}
)
});
let mut where_clauses = vec![&def.config.where_clause];
where_clauses.extend(def.storages.iter().map(|storage| &storage.where_clause));
let completed_where_clause = super::merge_where_clauses(&where_clauses);
@@ -489,5 +661,6 @@ pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream {
#( #getters )*
#( #prefix_structs )*
#( #on_empty_structs )*
)
}
@@ -29,6 +29,7 @@ mod keyword {
syn::custom_keyword!(storage_prefix);
syn::custom_keyword!(unbounded);
syn::custom_keyword!(OptionQuery);
syn::custom_keyword!(ResultQuery);
syn::custom_keyword!(ValueQuery);
}
@@ -129,6 +130,7 @@ pub enum Metadata {
pub enum QueryKind {
OptionQuery,
ResultQuery(syn::Path, syn::Ident),
ValueQuery,
}
@@ -153,7 +155,7 @@ pub struct StorageDef {
/// Optional expression that evaluates to a type that can be used as StoragePrefix instead of
/// ident.
pub rename_as: Option<syn::LitStr>,
/// Whereas the querytype of the storage is OptionQuery or ValueQuery.
/// Whereas the querytype of the storage is OptionQuery, ResultQuery or ValueQuery.
/// Note that this is best effort as it can't be determined when QueryKind is generic, and
/// result can be false if user do some unexpected type alias.
pub query_kind: Option<QueryKind>,
@@ -695,21 +697,105 @@ impl StorageDef {
let (named_generics, metadata, query_kind) = process_generics(&typ.path.segments[0])?;
let query_kind = query_kind
.map(|query_kind| match query_kind {
syn::Type::Path(path)
if path.path.segments.last().map_or(false, |s| s.ident == "OptionQuery") =>
Some(QueryKind::OptionQuery),
syn::Type::Path(path)
if path.path.segments.last().map_or(false, |s| s.ident == "ValueQuery") =>
Some(QueryKind::ValueQuery),
_ => None,
.map(|query_kind| {
use syn::{
AngleBracketedGenericArguments, GenericArgument, Path, PathArguments, Type,
TypePath,
};
let result_query = match query_kind {
Type::Path(path)
if path
.path
.segments
.last()
.map_or(false, |s| s.ident == "OptionQuery") =>
return Ok(Some(QueryKind::OptionQuery)),
Type::Path(TypePath { path: Path { segments, .. }, .. })
if segments.last().map_or(false, |s| s.ident == "ResultQuery") =>
segments
.last()
.expect("segments is checked to have the last value; qed")
.clone(),
Type::Path(path)
if path.path.segments.last().map_or(false, |s| s.ident == "ValueQuery") =>
return Ok(Some(QueryKind::ValueQuery)),
_ => return Ok(None),
};
let error_type = match result_query.arguments {
PathArguments::AngleBracketed(AngleBracketedGenericArguments {
args, ..
}) => {
if args.len() != 1 {
let msg = format!(
"Invalid pallet::storage, unexpected number of generic arguments \
for ResultQuery, expected 1 type argument, found {}",
args.len(),
);
return Err(syn::Error::new(args.span(), msg))
}
args[0].clone()
},
args => {
let msg = format!(
"Invalid pallet::storage, unexpected generic args for ResultQuery, \
expected angle-bracketed arguments, found `{}`",
args.to_token_stream().to_string()
);
return Err(syn::Error::new(args.span(), msg))
},
};
match error_type {
GenericArgument::Type(Type::Path(TypePath {
path: Path { segments: err_variant, leading_colon },
..
})) => {
if err_variant.len() < 2 {
let msg = format!(
"Invalid pallet::storage, unexpected number of path segments for \
the generics in ResultQuery, expected a path with at least 2 \
segments, found {}",
err_variant.len(),
);
return Err(syn::Error::new(err_variant.span(), msg))
}
let mut error = err_variant.clone();
let err_variant = error
.pop()
.expect("Checked to have at least 2; qed")
.into_value()
.ident;
// Necessary here to eliminate the last double colon
let last =
error.pop().expect("Checked to have at least 2; qed").into_value();
error.push_value(last);
Ok(Some(QueryKind::ResultQuery(
syn::Path { leading_colon: leading_colon.clone(), segments: error },
err_variant,
)))
},
gen_arg => {
let msg = format!(
"Invalid pallet::storage, unexpected generic argument kind, expected a \
type path to a `PalletError` enum variant, found `{}`",
gen_arg.to_token_stream().to_string(),
);
Err(syn::Error::new(gen_arg.span(), msg))
},
}
})
.unwrap_or(Some(QueryKind::OptionQuery)); // This value must match the default generic.
.transpose()?
.unwrap_or(Some(QueryKind::OptionQuery));
if let (None, Some(getter)) = (query_kind.as_ref(), getter.as_ref()) {
let msg = "Invalid pallet::storage, cannot generate getter because QueryKind is not \
identifiable. QueryKind must be `OptionQuery`, `ValueQuery`, or default one to be \
identifiable.";
identifiable. QueryKind must be `OptionQuery`, `ResultQuery`, `ValueQuery`, or default \
one to be identifiable.";
return Err(syn::Error::new(getter.span(), msg))
}