From edc8f7b409ccd17044a34e9358608cb6bc8f0323 Mon Sep 17 00:00:00 2001
From: Keith Yeung <kungfukeith11@gmail.com>
Date: Thu, 25 Aug 2022 01:47:08 +0800
Subject: [PATCH] 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 <>
---
 .../procedural/src/pallet/expand/storage.rs   | 217 ++++++++++++++++--
 .../procedural/src/pallet/parse/storage.rs    | 110 ++++++++-
 substrate/frame/support/src/lib.rs            |  81 ++++---
 .../frame/support/src/storage/types/mod.rs    |  32 ++-
 substrate/frame/support/test/tests/pallet.rs  | 139 ++++++++++-
 .../support/test/tests/pallet_instance.rs     | 129 ++++++++++-
 .../storage_result_query_missing_generics.rs  |  21 ++
 ...orage_result_query_missing_generics.stderr |  15 ++
 ...storage_result_query_multiple_type_args.rs |  23 ++
 ...age_result_query_multiple_type_args.stderr |   5 +
 ...ge_result_query_no_defined_pallet_error.rs |  16 ++
 ...esult_query_no_defined_pallet_error.stderr |   5 +
 ...age_result_query_parenthesized_generics.rs |  22 ++
 ...result_query_parenthesized_generics.stderr |   5 +
 ...storage_result_query_wrong_generic_kind.rs |  22 ++
 ...age_result_query_wrong_generic_kind.stderr |   5 +
 16 files changed, 770 insertions(+), 77 deletions(-)
 create mode 100644 substrate/frame/support/test/tests/pallet_ui/storage_result_query_missing_generics.rs
 create mode 100644 substrate/frame/support/test/tests/pallet_ui/storage_result_query_missing_generics.stderr
 create mode 100644 substrate/frame/support/test/tests/pallet_ui/storage_result_query_multiple_type_args.rs
 create mode 100644 substrate/frame/support/test/tests/pallet_ui/storage_result_query_multiple_type_args.stderr
 create mode 100644 substrate/frame/support/test/tests/pallet_ui/storage_result_query_no_defined_pallet_error.rs
 create mode 100644 substrate/frame/support/test/tests/pallet_ui/storage_result_query_no_defined_pallet_error.stderr
 create mode 100644 substrate/frame/support/test/tests/pallet_ui/storage_result_query_parenthesized_generics.rs
 create mode 100644 substrate/frame/support/test/tests/pallet_ui/storage_result_query_parenthesized_generics.stderr
 create mode 100644 substrate/frame/support/test/tests/pallet_ui/storage_result_query_wrong_generic_kind.rs
 create mode 100644 substrate/frame/support/test/tests/pallet_ui/storage_result_query_wrong_generic_kind.stderr

diff --git a/substrate/frame/support/procedural/src/pallet/expand/storage.rs b/substrate/frame/support/procedural/src/pallet/expand/storage.rs
index 657968e17a8..181f35b5454 100644
--- a/substrate/frame/support/procedural/src/pallet/expand/storage.rs
+++ b/substrate/frame/support/procedural/src/pallet/expand/storage.rs
@@ -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 )*
 	)
 }
diff --git a/substrate/frame/support/procedural/src/pallet/parse/storage.rs b/substrate/frame/support/procedural/src/pallet/parse/storage.rs
index 1f1bb5b2f26..97abdd3ad09 100644
--- a/substrate/frame/support/procedural/src/pallet/parse/storage.rs
+++ b/substrate/frame/support/procedural/src/pallet/parse/storage.rs
@@ -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))
 		}
 
diff --git a/substrate/frame/support/src/lib.rs b/substrate/frame/support/src/lib.rs
index 7e4c944330f..72551980bc7 100644
--- a/substrate/frame/support/src/lib.rs
+++ b/substrate/frame/support/src/lib.rs
@@ -281,79 +281,85 @@ pub use frame_support_procedural::storage_alias;
 macro_rules! parameter_types {
 	(
 		$( #[ $attr:meta ] )*
-		$vis:vis const $name:ident: $type:ty = $value:expr;
+		$vis:vis const $name:ident $(< $($ty_params:ident),* >)?: $type:ty = $value:expr;
 		$( $rest:tt )*
 	) => (
 		$( #[ $attr ] )*
-		$vis struct $name;
-		$crate::parameter_types!(IMPL_CONST $name , $type , $value);
+		$vis struct $name $(
+			< $($ty_params),* >( $($crate::sp_std::marker::PhantomData<$ty_params>),* )
+		)?;
+		$crate::parameter_types!(IMPL_CONST $name , $type , $value $( $(, $ty_params)* )?);
 		$crate::parameter_types!( $( $rest )* );
 	);
 	(
 		$( #[ $attr:meta ] )*
-		$vis:vis $name:ident: $type:ty = $value:expr;
+		$vis:vis $name:ident $(< $($ty_params:ident),* >)?: $type:ty = $value:expr;
 		$( $rest:tt )*
 	) => (
 		$( #[ $attr ] )*
-		$vis struct $name;
-		$crate::parameter_types!(IMPL $name, $type, $value);
+		$vis struct $name $(
+			< $($ty_params),* >( $($crate::sp_std::marker::PhantomData<$ty_params>),* )
+		)?;
+		$crate::parameter_types!(IMPL $name, $type, $value $( $(, $ty_params)* )?);
 		$crate::parameter_types!( $( $rest )* );
 	);
 	(
 		$( #[ $attr:meta ] )*
-		$vis:vis storage $name:ident: $type:ty = $value:expr;
+		$vis:vis storage $name:ident $(< $($ty_params:ident),* >)?: $type:ty = $value:expr;
 		$( $rest:tt )*
 	) => (
 		$( #[ $attr ] )*
-		$vis struct $name;
-		$crate::parameter_types!(IMPL_STORAGE $name, $type, $value);
+		$vis struct $name $(
+			< $($ty_params),* >( $($crate::sp_std::marker::PhantomData<$ty_params>),* )
+		)?;
+		$crate::parameter_types!(IMPL_STORAGE $name, $type, $value $( $(, $ty_params)* )?);
 		$crate::parameter_types!( $( $rest )* );
 	);
 	() => ();
-	(IMPL_CONST $name:ident, $type:ty, $value:expr) => {
-		impl $name {
+	(IMPL_CONST $name:ident, $type:ty, $value:expr $(, $ty_params:ident)*) => {
+		impl< $($ty_params),* > $name< $($ty_params),* > {
 			/// Returns the value of this parameter type.
 			pub const fn get() -> $type {
 				$value
 			}
 		}
 
-		impl<I: From<$type>> $crate::traits::Get<I> for $name {
-			fn get() -> I {
-				I::from(Self::get())
+		impl<_I: From<$type> $(, $ty_params)*> $crate::traits::Get<_I> for $name< $($ty_params),* > {
+			fn get() -> _I {
+				_I::from(Self::get())
 			}
 		}
 
-		impl $crate::traits::TypedGet for $name {
+		impl< $($ty_params),* > $crate::traits::TypedGet for $name< $($ty_params),* > {
 			type Type = $type;
 			fn get() -> $type {
 				Self::get()
 			}
 		}
 	};
-	(IMPL $name:ident, $type:ty, $value:expr) => {
-		impl $name {
+	(IMPL $name:ident, $type:ty, $value:expr $(, $ty_params:ident)*) => {
+		impl< $($ty_params),* > $name< $($ty_params),* > {
 			/// Returns the value of this parameter type.
 			pub fn get() -> $type {
 				$value
 			}
 		}
 
-		impl<I: From<$type>> $crate::traits::Get<I> for $name {
-			fn get() -> I {
-				I::from(Self::get())
+		impl<_I: From<$type>, $(, $ty_params)*> $crate::traits::Get<_I> for $name< $($ty_params),* > {
+			fn get() -> _I {
+				_I::from(Self::get())
 			}
 		}
 
-		impl $crate::traits::TypedGet for $name {
+		impl< $($ty_params),* > $crate::traits::TypedGet for $name< $($ty_params),* > {
 			type Type = $type;
 			fn get() -> $type {
 				Self::get()
 			}
 		}
 	};
-	(IMPL_STORAGE $name:ident, $type:ty, $value:expr) => {
-		impl $name {
+	(IMPL_STORAGE $name:ident, $type:ty, $value:expr $(, $ty_params:ident)*) => {
+		impl< $($ty_params),* > $name< $($ty_params),* > {
 			/// Returns the key for this parameter type.
 			#[allow(unused)]
 			pub fn key() -> [u8; 16] {
@@ -379,13 +385,13 @@ macro_rules! parameter_types {
 			}
 		}
 
-		impl<I: From<$type>> $crate::traits::Get<I> for $name {
-			fn get() -> I {
-				I::from(Self::get())
+		impl<_I: From<$type> $(, $ty_params)*> $crate::traits::Get<_I> for $name< $($ty_params),* > {
+			fn get() -> _I {
+				_I::from(Self::get())
 			}
 		}
 
-		impl $crate::traits::TypedGet for $name {
+		impl< $($ty_params),* > $crate::traits::TypedGet for $name< $($ty_params),* > {
 			type Type = $type;
 			fn get() -> $type {
 				Self::get()
@@ -1360,8 +1366,8 @@ pub mod pallet_prelude {
 		storage::{
 			bounded_vec::BoundedVec,
 			types::{
-				CountedStorageMap, Key as NMapKey, OptionQuery, StorageDoubleMap, StorageMap,
-				StorageNMap, StorageValue, ValueQuery,
+				CountedStorageMap, Key as NMapKey, OptionQuery, ResultQuery, StorageDoubleMap,
+				StorageMap, StorageNMap, StorageValue, ValueQuery,
 			},
 		},
 		traits::{
@@ -1835,6 +1841,23 @@ pub mod pallet_prelude {
 /// All the `cfg` attributes are automatically copied to the items generated for the storage,
 /// i.e. the getter, storage prefix, and the metadata element etc.
 ///
+/// Any type placed as the `QueryKind` parameter must implement
+/// [`frame_support::storage::types::QueryKindTrait`]. There are 3 implementations of this
+/// trait by default:
+/// 1. [`frame_support::storage::types::OptionQuery`], the default `QueryKind` used when this
+///    type parameter is omitted. Specifying this as the `QueryKind` would cause storage map
+///    APIs that return a `QueryKind` to instead return an `Option`, returning `Some` when a
+///    value does exist under a specified storage key, and `None` otherwise.
+/// 2. [`frame_support::storage::types::ValueQuery`] causes storage map APIs that return a
+///    `QueryKind` to instead return the value type. In cases where a value does not exist
+///    under a specified storage key, the `OnEmpty` type parameter on `QueryKindTrait` is used
+///    to return an appropriate value.
+/// 3. [`frame_support::storage::types::ResultQuery`] causes storage map APIs that return a
+///    `QueryKind` to instead return a `Result<T, E>`, with `T` being the value type and `E`
+///    being the pallet error type specified by the `#[pallet::error]` attribute. In cases
+///    where a value does not exist under a specified storage key, an `Err` with the specified
+///    pallet error variant is returned.
+///
 /// NOTE: If the `QueryKind` generic parameter is still generic at this stage or is using some
 /// type alias then the generation of the getter might fail. In this case the getter can be
 /// implemented manually.
diff --git a/substrate/frame/support/src/storage/types/mod.rs b/substrate/frame/support/src/storage/types/mod.rs
index 0706e9fb377..f87da5de122 100644
--- a/substrate/frame/support/src/storage/types/mod.rs
+++ b/substrate/frame/support/src/storage/types/mod.rs
@@ -42,9 +42,11 @@ pub use value::StorageValue;
 /// Trait implementing how the storage optional value is converted into the queried type.
 ///
 /// It is implemented by:
-/// * `OptionQuery` which convert an optional value to an optional value, user when querying storage
-///   will get an optional value.
-/// * `ValueQuery` which convert an optional value to a value, user when querying storage will get a
+/// * `OptionQuery` which converts an optional value to an optional value, used when querying
+///   storage returns an optional value.
+/// * `ResultQuery` which converts an optional value to a result value, used when querying storage
+///   returns a result value.
+/// * `ValueQuery` which converts an optional value to a value, used when querying storage returns a
 ///   value.
 pub trait QueryKindTrait<Value, OnEmpty> {
 	/// Metadata for the storage kind.
@@ -85,6 +87,30 @@ where
 	}
 }
 
+/// Implement QueryKindTrait with query being `Result<Value, PalletError>`
+pub struct ResultQuery<Error>(sp_std::marker::PhantomData<Error>);
+impl<Value, Error, OnEmpty> QueryKindTrait<Value, OnEmpty> for ResultQuery<Error>
+where
+	Value: FullCodec + 'static,
+	Error: FullCodec + 'static,
+	OnEmpty: crate::traits::Get<Result<Value, Error>>,
+{
+	const METADATA: StorageEntryModifier = StorageEntryModifier::Optional;
+
+	type Query = Result<Value, Error>;
+
+	fn from_optional_value_to_query(v: Option<Value>) -> Self::Query {
+		match v {
+			Some(v) => Ok(v),
+			None => OnEmpty::get(),
+		}
+	}
+
+	fn from_query_to_optional_value(v: Self::Query) -> Option<Value> {
+		v.ok()
+	}
+}
+
 /// Implement QueryKindTrait with query being `Value`
 pub struct ValueQuery;
 impl<Value, OnEmpty> QueryKindTrait<Value, OnEmpty> for ValueQuery
diff --git a/substrate/frame/support/test/tests/pallet.rs b/substrate/frame/support/test/tests/pallet.rs
index c4bbc59c703..e96e52d3d2c 100644
--- a/substrate/frame/support/test/tests/pallet.rs
+++ b/substrate/frame/support/test/tests/pallet.rs
@@ -229,6 +229,7 @@ pub mod pallet {
 	pub enum Error<T> {
 		/// doc comment put into metadata
 		InsufficientProposersBalance,
+		NonExistentStorageValue,
 		Code(u8),
 		#[codec(skip)]
 		Skipped(u128),
@@ -282,6 +283,10 @@ pub mod pallet {
 	pub type Map2<T> =
 		StorageMap<Hasher = Twox64Concat, Key = u16, Value = u32, MaxValues = ConstU32<3>>;
 
+	#[pallet::storage]
+	pub type Map3<T> =
+		StorageMap<_, Blake2_128Concat, u32, u64, ResultQuery<Error<T>::NonExistentStorageValue>>;
+
 	#[pallet::storage]
 	pub type DoubleMap<T> = StorageDoubleMap<_, Blake2_128Concat, u8, Twox64Concat, u16, u32>;
 
@@ -295,6 +300,17 @@ pub mod pallet {
 		MaxValues = ConstU32<5>,
 	>;
 
+	#[pallet::storage]
+	pub type DoubleMap3<T> = StorageDoubleMap<
+		_,
+		Blake2_128Concat,
+		u32,
+		Twox64Concat,
+		u64,
+		u128,
+		ResultQuery<Error<T>::NonExistentStorageValue>,
+	>;
+
 	#[pallet::storage]
 	#[pallet::getter(fn nmap)]
 	pub type NMap<T> = StorageNMap<_, storage::Key<Blake2_128Concat, u8>, u32>;
@@ -307,6 +323,15 @@ pub mod pallet {
 		MaxValues = ConstU32<11>,
 	>;
 
+	#[pallet::storage]
+	#[pallet::getter(fn nmap3)]
+	pub type NMap3<T> = StorageNMap<
+		_,
+		(NMapKey<Blake2_128Concat, u8>, NMapKey<Twox64Concat, u16>),
+		u128,
+		ResultQuery<Error<T>::NonExistentStorageValue>,
+	>;
+
 	#[pallet::storage]
 	#[pallet::getter(fn conditional_value)]
 	#[cfg(feature = "conditional-storage")]
@@ -934,6 +959,16 @@ fn storage_expand() {
 		assert_eq!(unhashed::get::<u32>(&k), Some(2u32));
 		assert_eq!(&k[..32], &<pallet::Map2<Runtime>>::final_prefix());
 
+		pallet::Map3::<Runtime>::insert(1, 2);
+		let mut k = [twox_128(b"Example"), twox_128(b"Map3")].concat();
+		k.extend(1u32.using_encoded(blake2_128_concat));
+		assert_eq!(unhashed::get::<u64>(&k), Some(2u64));
+		assert_eq!(&k[..32], &<pallet::Map3<Runtime>>::final_prefix());
+		assert_eq!(
+			pallet::Map3::<Runtime>::get(2),
+			Err(pallet::Error::<Runtime>::NonExistentStorageValue),
+		);
+
 		pallet::DoubleMap::<Runtime>::insert(&1, &2, &3);
 		let mut k = [twox_128(b"Example"), twox_128(b"DoubleMap")].concat();
 		k.extend(1u8.using_encoded(blake2_128_concat));
@@ -948,6 +983,17 @@ fn storage_expand() {
 		assert_eq!(unhashed::get::<u64>(&k), Some(3u64));
 		assert_eq!(&k[..32], &<pallet::DoubleMap2<Runtime>>::final_prefix());
 
+		pallet::DoubleMap3::<Runtime>::insert(&1, &2, &3);
+		let mut k = [twox_128(b"Example"), twox_128(b"DoubleMap3")].concat();
+		k.extend(1u32.using_encoded(blake2_128_concat));
+		k.extend(2u64.using_encoded(twox_64_concat));
+		assert_eq!(unhashed::get::<u128>(&k), Some(3u128));
+		assert_eq!(&k[..32], &<pallet::DoubleMap3<Runtime>>::final_prefix());
+		assert_eq!(
+			pallet::DoubleMap3::<Runtime>::get(2, 3),
+			Err(pallet::Error::<Runtime>::NonExistentStorageValue),
+		);
+
 		pallet::NMap::<Runtime>::insert((&1,), &3);
 		let mut k = [twox_128(b"Example"), twox_128(b"NMap")].concat();
 		k.extend(1u8.using_encoded(blake2_128_concat));
@@ -961,6 +1007,17 @@ fn storage_expand() {
 		assert_eq!(unhashed::get::<u64>(&k), Some(3u64));
 		assert_eq!(&k[..32], &<pallet::NMap2<Runtime>>::final_prefix());
 
+		pallet::NMap3::<Runtime>::insert((&1, &2), &3);
+		let mut k = [twox_128(b"Example"), twox_128(b"NMap3")].concat();
+		k.extend(1u8.using_encoded(blake2_128_concat));
+		k.extend(2u16.using_encoded(twox_64_concat));
+		assert_eq!(unhashed::get::<u128>(&k), Some(3u128));
+		assert_eq!(&k[..32], &<pallet::NMap3<Runtime>>::final_prefix());
+		assert_eq!(
+			pallet::NMap3::<Runtime>::get((2, 3)),
+			Err(pallet::Error::<Runtime>::NonExistentStorageValue),
+		);
+
 		#[cfg(feature = "conditional-storage")]
 		{
 			pallet::ConditionalValue::<Runtime>::put(1);
@@ -1171,6 +1228,17 @@ fn metadata() {
 						default: vec![0],
 						docs: vec![],
 					},
+					StorageEntryMetadata {
+						name: "Map3",
+						modifier: StorageEntryModifier::Optional,
+						ty: StorageEntryType::Map {
+							key: meta_type::<u32>(),
+							value: meta_type::<u64>(),
+							hashers: vec![StorageHasher::Blake2_128Concat],
+						},
+						default: vec![1, 1],
+						docs: vec![],
+					},
 					StorageEntryMetadata {
 						name: "DoubleMap",
 						modifier: StorageEntryModifier::Optional,
@@ -1199,6 +1267,20 @@ fn metadata() {
 						default: vec![0],
 						docs: vec![],
 					},
+					StorageEntryMetadata {
+						name: "DoubleMap3",
+						modifier: StorageEntryModifier::Optional,
+						ty: StorageEntryType::Map {
+							value: meta_type::<u128>(),
+							key: meta_type::<(u32, u64)>(),
+							hashers: vec![
+								StorageHasher::Blake2_128Concat,
+								StorageHasher::Twox64Concat,
+							],
+						},
+						default: vec![1, 1],
+						docs: vec![],
+					},
 					StorageEntryMetadata {
 						name: "NMap",
 						modifier: StorageEntryModifier::Optional,
@@ -1224,6 +1306,20 @@ fn metadata() {
 						default: vec![0],
 						docs: vec![],
 					},
+					StorageEntryMetadata {
+						name: "NMap3",
+						modifier: StorageEntryModifier::Optional,
+						ty: StorageEntryType::Map {
+							key: meta_type::<(u8, u16)>(),
+							hashers: vec![
+								StorageHasher::Blake2_128Concat,
+								StorageHasher::Twox64Concat,
+							],
+							value: meta_type::<u128>(),
+						},
+						default: vec![1, 1],
+						docs: vec![],
+					},
 					#[cfg(feature = "conditional-storage")]
 					StorageEntryMetadata {
 						name: "ConditionalValue",
@@ -1436,6 +1532,8 @@ fn test_storage_info() {
 		traits::{StorageInfo, StorageInfoTrait},
 	};
 
+	// Storage max size is calculated by adding up all the hasher size, the key type size and the
+	// value type size
 	assert_eq!(
 		Example::storage_info(),
 		vec![
@@ -1465,42 +1563,63 @@ fn test_storage_info() {
 				storage_name: b"Map".to_vec(),
 				prefix: prefix(b"Example", b"Map").to_vec(),
 				max_values: None,
-				max_size: Some(3 + 16),
+				max_size: Some(16 + 1 + 2),
 			},
 			StorageInfo {
 				pallet_name: b"Example".to_vec(),
 				storage_name: b"Map2".to_vec(),
 				prefix: prefix(b"Example", b"Map2").to_vec(),
 				max_values: Some(3),
-				max_size: Some(6 + 8),
+				max_size: Some(8 + 2 + 4),
+			},
+			StorageInfo {
+				pallet_name: b"Example".to_vec(),
+				storage_name: b"Map3".to_vec(),
+				prefix: prefix(b"Example", b"Map3").to_vec(),
+				max_values: None,
+				max_size: Some(16 + 4 + 8),
 			},
 			StorageInfo {
 				pallet_name: b"Example".to_vec(),
 				storage_name: b"DoubleMap".to_vec(),
 				prefix: prefix(b"Example", b"DoubleMap").to_vec(),
 				max_values: None,
-				max_size: Some(7 + 16 + 8),
+				max_size: Some(16 + 1 + 8 + 2 + 4),
 			},
 			StorageInfo {
 				pallet_name: b"Example".to_vec(),
 				storage_name: b"DoubleMap2".to_vec(),
 				prefix: prefix(b"Example", b"DoubleMap2").to_vec(),
 				max_values: Some(5),
-				max_size: Some(14 + 8 + 16),
+				max_size: Some(8 + 2 + 16 + 4 + 8),
+			},
+			StorageInfo {
+				pallet_name: b"Example".to_vec(),
+				storage_name: b"DoubleMap3".to_vec(),
+				prefix: prefix(b"Example", b"DoubleMap3").to_vec(),
+				max_values: None,
+				max_size: Some(16 + 4 + 8 + 8 + 16),
 			},
 			StorageInfo {
 				pallet_name: b"Example".to_vec(),
 				storage_name: b"NMap".to_vec(),
 				prefix: prefix(b"Example", b"NMap").to_vec(),
 				max_values: None,
-				max_size: Some(5 + 16),
+				max_size: Some(16 + 1 + 4),
 			},
 			StorageInfo {
 				pallet_name: b"Example".to_vec(),
 				storage_name: b"NMap2".to_vec(),
 				prefix: prefix(b"Example", b"NMap2").to_vec(),
 				max_values: Some(11),
-				max_size: Some(14 + 8 + 16),
+				max_size: Some(8 + 2 + 16 + 4 + 8),
+			},
+			StorageInfo {
+				pallet_name: b"Example".to_vec(),
+				storage_name: b"NMap3".to_vec(),
+				prefix: prefix(b"Example", b"NMap3").to_vec(),
+				max_values: None,
+				max_size: Some(16 + 1 + 8 + 2 + 16),
 			},
 			#[cfg(feature = "conditional-storage")]
 			{
@@ -1519,7 +1638,7 @@ fn test_storage_info() {
 					storage_name: b"ConditionalMap".to_vec(),
 					prefix: prefix(b"Example", b"ConditionalMap").to_vec(),
 					max_values: Some(12),
-					max_size: Some(6 + 8),
+					max_size: Some(8 + 2 + 4),
 				}
 			},
 			#[cfg(feature = "conditional-storage")]
@@ -1529,7 +1648,7 @@ fn test_storage_info() {
 					storage_name: b"ConditionalDoubleMap".to_vec(),
 					prefix: prefix(b"Example", b"ConditionalDoubleMap").to_vec(),
 					max_values: None,
-					max_size: Some(7 + 16 + 8),
+					max_size: Some(16 + 1 + 8 + 2 + 4),
 				}
 			},
 			#[cfg(feature = "conditional-storage")]
@@ -1539,7 +1658,7 @@ fn test_storage_info() {
 					storage_name: b"ConditionalNMap".to_vec(),
 					prefix: prefix(b"Example", b"ConditionalNMap").to_vec(),
 					max_values: None,
-					max_size: Some(7 + 16 + 8),
+					max_size: Some(16 + 1 + 8 + 2 + 4),
 				}
 			},
 			StorageInfo {
@@ -1547,7 +1666,7 @@ fn test_storage_info() {
 				storage_name: b"RenamedCountedMap".to_vec(),
 				prefix: prefix(b"Example", b"RenamedCountedMap").to_vec(),
 				max_values: None,
-				max_size: Some(1 + 4 + 8),
+				max_size: Some(8 + 1 + 4),
 			},
 			StorageInfo {
 				pallet_name: b"Example".to_vec(),
diff --git a/substrate/frame/support/test/tests/pallet_instance.rs b/substrate/frame/support/test/tests/pallet_instance.rs
index 360a73e5ea2..f4ef5f802c4 100644
--- a/substrate/frame/support/test/tests/pallet_instance.rs
+++ b/substrate/frame/support/test/tests/pallet_instance.rs
@@ -31,7 +31,7 @@ use sp_runtime::{DispatchError, ModuleError};
 #[frame_support::pallet]
 pub mod pallet {
 	use codec::MaxEncodedLen;
-	use frame_support::{pallet_prelude::*, scale_info};
+	use frame_support::{pallet_prelude::*, parameter_types, scale_info};
 	use frame_system::pallet_prelude::*;
 	use sp_std::any::TypeId;
 
@@ -104,9 +104,11 @@ pub mod pallet {
 	}
 
 	#[pallet::error]
+	#[derive(PartialEq, Eq)]
 	pub enum Error<T, I = ()> {
 		/// doc comment put into metadata
 		InsufficientProposersBalance,
+		NonExistentStorageValue,
 	}
 
 	#[pallet::event]
@@ -128,6 +130,20 @@ pub mod pallet {
 	#[pallet::storage]
 	pub type Map2<T, I = ()> = StorageMap<_, Twox64Concat, u16, u32>;
 
+	parameter_types! {
+		pub const Map3Default<T, I>: Result<u64, Error<T, I>> = Ok(1337);
+	}
+
+	#[pallet::storage]
+	pub type Map3<T, I = ()> = StorageMap<
+		_,
+		Blake2_128Concat,
+		u32,
+		u64,
+		ResultQuery<Error<T, I>::NonExistentStorageValue>,
+		Map3Default<T, I>,
+	>;
+
 	#[pallet::storage]
 	pub type DoubleMap<T, I = ()> =
 		StorageDoubleMap<_, Blake2_128Concat, u8, Twox64Concat, u16, u32>;
@@ -136,6 +152,17 @@ pub mod pallet {
 	pub type DoubleMap2<T, I = ()> =
 		StorageDoubleMap<_, Twox64Concat, u16, Blake2_128Concat, u32, u64>;
 
+	#[pallet::storage]
+	pub type DoubleMap3<T, I = ()> = StorageDoubleMap<
+		_,
+		Blake2_128Concat,
+		u32,
+		Twox64Concat,
+		u64,
+		u128,
+		ResultQuery<Error<T, I>::NonExistentStorageValue>,
+	>;
+
 	#[pallet::storage]
 	#[pallet::getter(fn nmap)]
 	pub type NMap<T, I = ()> = StorageNMap<_, storage::Key<Blake2_128Concat, u8>, u32>;
@@ -145,6 +172,15 @@ pub mod pallet {
 	pub type NMap2<T, I = ()> =
 		StorageNMap<_, (storage::Key<Twox64Concat, u16>, storage::Key<Blake2_128Concat, u32>), u64>;
 
+	#[pallet::storage]
+	#[pallet::getter(fn nmap3)]
+	pub type NMap3<T, I = ()> = StorageNMap<
+		_,
+		(NMapKey<Blake2_128Concat, u8>, NMapKey<Twox64Concat, u16>),
+		u128,
+		ResultQuery<Error<T, I>::NonExistentStorageValue>,
+	>;
+
 	#[pallet::genesis_config]
 	#[derive(Default)]
 	pub struct GenesisConfig {
@@ -436,6 +472,13 @@ fn storage_expand() {
 		assert_eq!(unhashed::get::<u32>(&k), Some(2u32));
 		assert_eq!(&k[..32], &<pallet::Map2<Runtime>>::final_prefix());
 
+		<pallet::Map3<Runtime>>::insert(1, 2);
+		let mut k = [twox_128(b"Example"), twox_128(b"Map3")].concat();
+		k.extend(1u32.using_encoded(blake2_128_concat));
+		assert_eq!(unhashed::get::<u64>(&k), Some(2u64));
+		assert_eq!(&k[..32], &<pallet::Map3<Runtime>>::final_prefix());
+		assert_eq!(<pallet::Map3<Runtime>>::get(2), Ok(1337));
+
 		<pallet::DoubleMap<Runtime>>::insert(&1, &2, &3);
 		let mut k = [twox_128(b"Example"), twox_128(b"DoubleMap")].concat();
 		k.extend(1u8.using_encoded(blake2_128_concat));
@@ -450,6 +493,17 @@ fn storage_expand() {
 		assert_eq!(unhashed::get::<u64>(&k), Some(3u64));
 		assert_eq!(&k[..32], &<pallet::DoubleMap2<Runtime>>::final_prefix());
 
+		<pallet::DoubleMap3<Runtime>>::insert(&1, &2, &3);
+		let mut k = [twox_128(b"Example"), twox_128(b"DoubleMap3")].concat();
+		k.extend(1u32.using_encoded(blake2_128_concat));
+		k.extend(2u64.using_encoded(twox_64_concat));
+		assert_eq!(unhashed::get::<u128>(&k), Some(3u128));
+		assert_eq!(&k[..32], &<pallet::DoubleMap3<Runtime>>::final_prefix());
+		assert_eq!(
+			<pallet::DoubleMap3<Runtime>>::get(2, 3),
+			Err(pallet::Error::<Runtime>::NonExistentStorageValue),
+		);
+
 		<pallet::NMap<Runtime>>::insert((&1,), &3);
 		let mut k = [twox_128(b"Example"), twox_128(b"NMap")].concat();
 		k.extend(1u8.using_encoded(blake2_128_concat));
@@ -462,6 +516,17 @@ fn storage_expand() {
 		k.extend(2u32.using_encoded(blake2_128_concat));
 		assert_eq!(unhashed::get::<u64>(&k), Some(3u64));
 		assert_eq!(&k[..32], &<pallet::NMap2<Runtime>>::final_prefix());
+
+		<pallet::NMap3<Runtime>>::insert((&1, &2), &3);
+		let mut k = [twox_128(b"Example"), twox_128(b"NMap3")].concat();
+		k.extend(1u8.using_encoded(blake2_128_concat));
+		k.extend(2u16.using_encoded(twox_64_concat));
+		assert_eq!(unhashed::get::<u128>(&k), Some(3u128));
+		assert_eq!(&k[..32], &<pallet::NMap3<Runtime>>::final_prefix());
+		assert_eq!(
+			<pallet::NMap3<Runtime>>::get((2, 3)),
+			Err(pallet::Error::<Runtime>::NonExistentStorageValue),
+		);
 	});
 
 	TestExternalities::default().execute_with(|| {
@@ -481,6 +546,13 @@ fn storage_expand() {
 		assert_eq!(unhashed::get::<u32>(&k), Some(2u32));
 		assert_eq!(&k[..32], &<pallet::Map2<Runtime, pallet::Instance1>>::final_prefix());
 
+		<pallet::Map3<Runtime, pallet::Instance1>>::insert(1, 2);
+		let mut k = [twox_128(b"Instance1Example"), twox_128(b"Map3")].concat();
+		k.extend(1u32.using_encoded(blake2_128_concat));
+		assert_eq!(unhashed::get::<u64>(&k), Some(2u64));
+		assert_eq!(&k[..32], &<pallet::Map3<Runtime, pallet::Instance1>>::final_prefix());
+		assert_eq!(<pallet::Map3<Runtime, pallet::Instance1>>::get(2), Ok(1337));
+
 		<pallet::DoubleMap<Runtime, pallet::Instance1>>::insert(&1, &2, &3);
 		let mut k = [twox_128(b"Instance1Example"), twox_128(b"DoubleMap")].concat();
 		k.extend(1u8.using_encoded(blake2_128_concat));
@@ -495,6 +567,17 @@ fn storage_expand() {
 		assert_eq!(unhashed::get::<u64>(&k), Some(3u64));
 		assert_eq!(&k[..32], &<pallet::DoubleMap2<Runtime, pallet::Instance1>>::final_prefix());
 
+		<pallet::DoubleMap3<Runtime, pallet::Instance1>>::insert(&1, &2, &3);
+		let mut k = [twox_128(b"Instance1Example"), twox_128(b"DoubleMap3")].concat();
+		k.extend(1u32.using_encoded(blake2_128_concat));
+		k.extend(2u64.using_encoded(twox_64_concat));
+		assert_eq!(unhashed::get::<u128>(&k), Some(3u128));
+		assert_eq!(&k[..32], &<pallet::DoubleMap3<Runtime, pallet::Instance1>>::final_prefix());
+		assert_eq!(
+			<pallet::DoubleMap3<Runtime, pallet::Instance1>>::get(2, 3),
+			Err(pallet::Error::<Runtime, pallet::Instance1>::NonExistentStorageValue),
+		);
+
 		<pallet::NMap<Runtime, pallet::Instance1>>::insert((&1,), &3);
 		let mut k = [twox_128(b"Instance1Example"), twox_128(b"NMap")].concat();
 		k.extend(1u8.using_encoded(blake2_128_concat));
@@ -507,6 +590,17 @@ fn storage_expand() {
 		k.extend(2u32.using_encoded(blake2_128_concat));
 		assert_eq!(unhashed::get::<u64>(&k), Some(3u64));
 		assert_eq!(&k[..32], &<pallet::NMap2<Runtime, pallet::Instance1>>::final_prefix());
+
+		<pallet::NMap3<Runtime, pallet::Instance1>>::insert((&1, &2), &3);
+		let mut k = [twox_128(b"Instance1Example"), twox_128(b"NMap3")].concat();
+		k.extend(1u8.using_encoded(blake2_128_concat));
+		k.extend(2u16.using_encoded(twox_64_concat));
+		assert_eq!(unhashed::get::<u128>(&k), Some(3u128));
+		assert_eq!(&k[..32], &<pallet::NMap3<Runtime, pallet::Instance1>>::final_prefix());
+		assert_eq!(
+			<pallet::NMap3<Runtime, pallet::Instance1>>::get((2, 3)),
+			Err(pallet::Error::<Runtime, pallet::Instance1>::NonExistentStorageValue),
+		);
 	});
 }
 
@@ -688,6 +782,17 @@ fn metadata() {
 					default: vec![0],
 					docs: vec![],
 				},
+				StorageEntryMetadata {
+					name: "Map3",
+					modifier: StorageEntryModifier::Optional,
+					ty: StorageEntryType::Map {
+						key: scale_info::meta_type::<u32>(),
+						value: scale_info::meta_type::<u64>(),
+						hashers: vec![StorageHasher::Blake2_128Concat],
+					},
+					default: vec![0, 57, 5, 0, 0, 0, 0, 0, 0],
+					docs: vec![],
+				},
 				StorageEntryMetadata {
 					name: "DoubleMap",
 					modifier: StorageEntryModifier::Optional,
@@ -710,6 +815,17 @@ fn metadata() {
 					default: vec![0],
 					docs: vec![],
 				},
+				StorageEntryMetadata {
+					name: "DoubleMap3",
+					modifier: StorageEntryModifier::Optional,
+					ty: StorageEntryType::Map {
+						value: scale_info::meta_type::<u128>(),
+						key: scale_info::meta_type::<(u32, u64)>(),
+						hashers: vec![StorageHasher::Blake2_128Concat, StorageHasher::Twox64Concat],
+					},
+					default: vec![1, 1],
+					docs: vec![],
+				},
 				StorageEntryMetadata {
 					name: "NMap",
 					modifier: StorageEntryModifier::Optional,
@@ -732,6 +848,17 @@ fn metadata() {
 					default: vec![0],
 					docs: vec![],
 				},
+				StorageEntryMetadata {
+					name: "NMap3",
+					modifier: StorageEntryModifier::Optional,
+					ty: StorageEntryType::Map {
+						key: scale_info::meta_type::<(u8, u16)>(),
+						hashers: vec![StorageHasher::Blake2_128Concat, StorageHasher::Twox64Concat],
+						value: scale_info::meta_type::<u128>(),
+					},
+					default: vec![1, 1],
+					docs: vec![],
+				},
 			],
 		}),
 		calls: Some(scale_info::meta_type::<pallet::Call<Runtime>>().into()),
diff --git a/substrate/frame/support/test/tests/pallet_ui/storage_result_query_missing_generics.rs b/substrate/frame/support/test/tests/pallet_ui/storage_result_query_missing_generics.rs
new file mode 100644
index 00000000000..a051cc087db
--- /dev/null
+++ b/substrate/frame/support/test/tests/pallet_ui/storage_result_query_missing_generics.rs
@@ -0,0 +1,21 @@
+#[frame_support::pallet]
+mod pallet {
+	use frame_support::pallet_prelude::*;
+
+	#[pallet::config]
+	pub trait Config: frame_system::Config {}
+
+	#[pallet::pallet]
+	pub struct Pallet<T>(core::marker::PhantomData<T>);
+
+	#[pallet::error]
+	pub enum Error<T> {
+		NonExistentValue,
+	}
+
+	#[pallet::storage]
+	type Foo<T: Config> = StorageValue<_, u8, ResultQuery<Error::NonExistentValue>>;
+}
+
+fn main() {
+}
diff --git a/substrate/frame/support/test/tests/pallet_ui/storage_result_query_missing_generics.stderr b/substrate/frame/support/test/tests/pallet_ui/storage_result_query_missing_generics.stderr
new file mode 100644
index 00000000000..98265462bbd
--- /dev/null
+++ b/substrate/frame/support/test/tests/pallet_ui/storage_result_query_missing_generics.stderr
@@ -0,0 +1,15 @@
+error[E0107]: missing generics for enum `pallet::Error`
+  --> tests/pallet_ui/storage_result_query_missing_generics.rs:17:56
+   |
+17 |     type Foo<T: Config> = StorageValue<_, u8, ResultQuery<Error::NonExistentValue>>;
+   |                                                           ^^^^^ expected 1 generic argument
+   |
+note: enum defined here, with 1 generic parameter: `T`
+  --> tests/pallet_ui/storage_result_query_missing_generics.rs:12:11
+   |
+12 |     pub enum Error<T> {
+   |              ^^^^^ -
+help: add missing generic argument
+   |
+17 |     type Foo<T: Config> = StorageValue<_, u8, ResultQuery<Error<T>::NonExistentValue>>;
+   |                                                           ~~~~~~~~
diff --git a/substrate/frame/support/test/tests/pallet_ui/storage_result_query_multiple_type_args.rs b/substrate/frame/support/test/tests/pallet_ui/storage_result_query_multiple_type_args.rs
new file mode 100644
index 00000000000..9e0da4b6212
--- /dev/null
+++ b/substrate/frame/support/test/tests/pallet_ui/storage_result_query_multiple_type_args.rs
@@ -0,0 +1,23 @@
+#[frame_support::pallet]
+mod pallet {
+	use frame_support::pallet_prelude::*;
+	use frame_system::pallet_prelude::BlockNumberFor;
+
+	#[pallet::config]
+	pub trait Config: frame_system::Config {}
+
+	#[pallet::pallet]
+	pub struct Pallet<T>(core::marker::PhantomData<T>);
+
+	#[pallet::error]
+	pub enum Error<T> {
+		NonExistentValue,
+		SomeOtherError,
+	}
+
+	#[pallet::storage]
+	type Foo<T: Config> = StorageValue<_, u8, ResultQuery<Error<T>::NonExistentValue, SomeOtherError>>;
+}
+
+fn main() {
+}
diff --git a/substrate/frame/support/test/tests/pallet_ui/storage_result_query_multiple_type_args.stderr b/substrate/frame/support/test/tests/pallet_ui/storage_result_query_multiple_type_args.stderr
new file mode 100644
index 00000000000..4be2a36eb89
--- /dev/null
+++ b/substrate/frame/support/test/tests/pallet_ui/storage_result_query_multiple_type_args.stderr
@@ -0,0 +1,5 @@
+error: Invalid pallet::storage, unexpected number of generic arguments for ResultQuery, expected 1 type argument, found 2
+  --> tests/pallet_ui/storage_result_query_multiple_type_args.rs:19:56
+   |
+19 |     type Foo<T: Config> = StorageValue<_, u8, ResultQuery<Error<T>::NonExistentValue, SomeOtherError>>;
+   |                                                           ^^^^^
diff --git a/substrate/frame/support/test/tests/pallet_ui/storage_result_query_no_defined_pallet_error.rs b/substrate/frame/support/test/tests/pallet_ui/storage_result_query_no_defined_pallet_error.rs
new file mode 100644
index 00000000000..102a2261f83
--- /dev/null
+++ b/substrate/frame/support/test/tests/pallet_ui/storage_result_query_no_defined_pallet_error.rs
@@ -0,0 +1,16 @@
+#[frame_support::pallet]
+mod pallet {
+	use frame_support::pallet_prelude::*;
+
+	#[pallet::config]
+	pub trait Config: frame_system::Config {}
+
+	#[pallet::pallet]
+	pub struct Pallet<T>(core::marker::PhantomData<T>);
+
+	#[pallet::storage]
+	type Foo<T: Config> = StorageValue<_, u8, ResultQuery<NonExistentValue>>;
+}
+
+fn main() {
+}
diff --git a/substrate/frame/support/test/tests/pallet_ui/storage_result_query_no_defined_pallet_error.stderr b/substrate/frame/support/test/tests/pallet_ui/storage_result_query_no_defined_pallet_error.stderr
new file mode 100644
index 00000000000..77a7972a5b5
--- /dev/null
+++ b/substrate/frame/support/test/tests/pallet_ui/storage_result_query_no_defined_pallet_error.stderr
@@ -0,0 +1,5 @@
+error: Invalid pallet::storage, unexpected number of path segments for the generics in ResultQuery, expected a path with at least 2 segments, found 1
+  --> tests/pallet_ui/storage_result_query_no_defined_pallet_error.rs:12:56
+   |
+12 |     type Foo<T: Config> = StorageValue<_, u8, ResultQuery<NonExistentValue>>;
+   |                                                           ^^^^^^^^^^^^^^^^
diff --git a/substrate/frame/support/test/tests/pallet_ui/storage_result_query_parenthesized_generics.rs b/substrate/frame/support/test/tests/pallet_ui/storage_result_query_parenthesized_generics.rs
new file mode 100644
index 00000000000..f30dc3b6a3c
--- /dev/null
+++ b/substrate/frame/support/test/tests/pallet_ui/storage_result_query_parenthesized_generics.rs
@@ -0,0 +1,22 @@
+#[frame_support::pallet]
+mod pallet {
+	use frame_support::pallet_prelude::*;
+	use frame_system::pallet_prelude::BlockNumberFor;
+
+	#[pallet::config]
+	pub trait Config: frame_system::Config {}
+
+	#[pallet::pallet]
+	pub struct Pallet<T>(core::marker::PhantomData<T>);
+
+	#[pallet::error]
+	pub enum Error<T> {
+		NonExistentValue,
+	}
+
+	#[pallet::storage]
+	type Foo<T: Config> = StorageValue<_, u8, ResultQuery(NonExistentValue)>;
+}
+
+fn main() {
+}
diff --git a/substrate/frame/support/test/tests/pallet_ui/storage_result_query_parenthesized_generics.stderr b/substrate/frame/support/test/tests/pallet_ui/storage_result_query_parenthesized_generics.stderr
new file mode 100644
index 00000000000..caffd846f27
--- /dev/null
+++ b/substrate/frame/support/test/tests/pallet_ui/storage_result_query_parenthesized_generics.stderr
@@ -0,0 +1,5 @@
+error: Invalid pallet::storage, unexpected generic args for ResultQuery, expected angle-bracketed arguments, found `(NonExistentValue)`
+  --> tests/pallet_ui/storage_result_query_parenthesized_generics.rs:18:55
+   |
+18 |     type Foo<T: Config> = StorageValue<_, u8, ResultQuery(NonExistentValue)>;
+   |                                                          ^^^^^^^^^^^^^^^^^^
diff --git a/substrate/frame/support/test/tests/pallet_ui/storage_result_query_wrong_generic_kind.rs b/substrate/frame/support/test/tests/pallet_ui/storage_result_query_wrong_generic_kind.rs
new file mode 100644
index 00000000000..a5065398b39
--- /dev/null
+++ b/substrate/frame/support/test/tests/pallet_ui/storage_result_query_wrong_generic_kind.rs
@@ -0,0 +1,22 @@
+#[frame_support::pallet]
+mod pallet {
+	use frame_support::pallet_prelude::*;
+	use frame_system::pallet_prelude::BlockNumberFor;
+
+	#[pallet::config]
+	pub trait Config: frame_system::Config {}
+
+	#[pallet::pallet]
+	pub struct Pallet<T>(core::marker::PhantomData<T>);
+
+	#[pallet::error]
+	pub enum Error<T> {
+		NonExistentValue,
+	}
+
+	#[pallet::storage]
+	type Foo<T: Config> = StorageValue<_, u8, ResultQuery<'static>>;
+}
+
+fn main() {
+}
diff --git a/substrate/frame/support/test/tests/pallet_ui/storage_result_query_wrong_generic_kind.stderr b/substrate/frame/support/test/tests/pallet_ui/storage_result_query_wrong_generic_kind.stderr
new file mode 100644
index 00000000000..9f333ae28e6
--- /dev/null
+++ b/substrate/frame/support/test/tests/pallet_ui/storage_result_query_wrong_generic_kind.stderr
@@ -0,0 +1,5 @@
+error: Invalid pallet::storage, unexpected generic argument kind, expected a type path to a `PalletError` enum variant, found `'static`
+  --> tests/pallet_ui/storage_result_query_wrong_generic_kind.rs:18:56
+   |
+18 |     type Foo<T: Config> = StorageValue<_, u8, ResultQuery<'static>>;
+   |                                                           ^^^^^^^
-- 
GitLab