-
Notifications
You must be signed in to change notification settings - Fork 262
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Storage: Support iterating over NMaps with partial keys #1079
Changes from 19 commits
ddd2c2d
52a01d2
dae5bf9
e8ad7c1
198be19
a2099bb
e5af576
5e95250
86af7ad
57c5eb5
2e8f684
8da983c
0386a13
a0be58b
b2cd7ab
2309e3a
3313077
fc7bb8f
2c68b43
d63002b
3d1d877
cab5e1e
6ecd755
66b529c
95e9aea
03857ca
ee21534
95293dc
830ffa3
57df713
4d5abe6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,9 +2,10 @@ | |
// This file is dual-licensed as Apache-2.0 or GPL-3.0. | ||
// see LICENSE for license details. | ||
|
||
use crate::types::TypePath; | ||
use crate::{types::TypeGenerator, CratePath}; | ||
use heck::ToSnakeCase as _; | ||
use proc_macro2::TokenStream as TokenStream2; | ||
use proc_macro2::{Ident, TokenStream as TokenStream2}; | ||
use quote::{format_ident, quote}; | ||
use scale_info::TypeDef; | ||
use subxt_metadata::{ | ||
|
@@ -61,147 +62,105 @@ fn generate_storage_entry_fns( | |
crate_path: &CratePath, | ||
should_gen_docs: bool, | ||
) -> Result<TokenStream2, CodegenError> { | ||
let (fields, key_impl) = match storage_entry.entry_type() { | ||
StorageEntryType::Plain(_) => (vec![], quote!(vec![])), | ||
let keys: Vec<(Ident, TypePath)> = match storage_entry.entry_type() { | ||
StorageEntryType::Plain(_) => vec![], | ||
StorageEntryType::Map { key_ty, .. } => { | ||
match &type_gen.resolve_type(*key_ty).type_def { | ||
// An N-map; return each of the keys separately. | ||
TypeDef::Tuple(tuple) => { | ||
let fields = tuple | ||
.fields | ||
.iter() | ||
.enumerate() | ||
.map(|(i, f)| { | ||
let field_name = format_ident!("_{}", syn::Index::from(i)); | ||
let field_type = type_gen.resolve_type_path(f.id); | ||
(field_name, field_type) | ||
}) | ||
.collect::<Vec<_>>(); | ||
|
||
let keys = fields | ||
.iter() | ||
.map(|(field_name, _)| { | ||
quote!( #crate_path::storage::address::make_static_storage_map_key(#field_name.borrow()) ) | ||
}); | ||
let key_impl = quote! { | ||
vec![ #( #keys ),* ] | ||
}; | ||
|
||
(fields, key_impl) | ||
} | ||
TypeDef::Tuple(tuple) => tuple | ||
.fields | ||
.iter() | ||
.enumerate() | ||
.map(|(i, f)| { | ||
let ident: Ident = format_ident!("_{}", syn::Index::from(i)); | ||
jsdw marked this conversation as resolved.
Show resolved
Hide resolved
|
||
let ty_path = type_gen.resolve_type_path(f.id); | ||
(ident, ty_path) | ||
}) | ||
.collect::<Vec<_>>(), | ||
// A map with a single key; return the single key. | ||
_ => { | ||
let ident = format_ident!("_0"); | ||
let ty_path = type_gen.resolve_type_path(*key_ty); | ||
let fields = vec![(format_ident!("_0"), ty_path)]; | ||
let key_impl = quote! { | ||
vec![ #crate_path::storage::address::make_static_storage_map_key(_0.borrow()) ] | ||
}; | ||
(fields, key_impl) | ||
vec![(ident, ty_path)] | ||
} | ||
} | ||
} | ||
}; | ||
|
||
let pallet_name = pallet.name(); | ||
let storage_name = storage_entry.name(); | ||
let Some(storage_hash) = pallet.storage_hash(storage_name) else { | ||
return Err(CodegenError::MissingStorageMetadata(pallet_name.into(), storage_name.into())); | ||
}; | ||
|
||
let fn_name = format_ident!("{}", storage_entry.name().to_snake_case()); | ||
let snake_case_name = storage_entry.name().to_snake_case(); | ||
let storage_entry_ty = match storage_entry.entry_type() { | ||
StorageEntryType::Plain(ty) => *ty, | ||
StorageEntryType::Map { value_ty, .. } => *value_ty, | ||
}; | ||
let storage_entry_value_ty = type_gen.resolve_type_path(storage_entry_ty); | ||
|
||
let docs = storage_entry.docs(); | ||
let docs = should_gen_docs | ||
.then_some(quote! { #( #[doc = #docs ] )* }) | ||
.unwrap_or_default(); | ||
|
||
let key_args = fields.iter().map(|(field_name, field_type)| { | ||
// The field type is translated from `std::vec::Vec<T>` to `[T]`. We apply | ||
// Borrow to all types, so this just makes it a little more ergonomic. | ||
// | ||
// TODO [jsdw]: Support mappings like `String -> str` too for better borrow | ||
// ergonomics. | ||
let field_ty = match field_type.vec_type_param() { | ||
Some(ty) => quote!([#ty]), | ||
_ => quote!(#field_type), | ||
}; | ||
quote!( #field_name: impl ::std::borrow::Borrow<#field_ty> ) | ||
}); | ||
|
||
let is_map_type = matches!(storage_entry.entry_type(), StorageEntryType::Map { .. }); | ||
|
||
// Is the entry iterable? | ||
let is_iterable_type = if is_map_type { | ||
quote!(#crate_path::storage::address::Yes) | ||
} else { | ||
quote!(()) | ||
}; | ||
|
||
let has_default_value = match storage_entry.modifier() { | ||
StorageEntryModifier::Default => true, | ||
StorageEntryModifier::Optional => false, | ||
let is_defaultable_type = match storage_entry.modifier() { | ||
StorageEntryModifier::Default => quote!(#crate_path::storage::address::Yes), | ||
StorageEntryModifier::Optional => quote!(()), | ||
}; | ||
|
||
// Does the entry have a default value? | ||
let is_defaultable_type = if has_default_value { | ||
quote!(#crate_path::storage::address::Yes) | ||
} else { | ||
quote!(()) | ||
}; | ||
let all_fns = (0..=keys.len()).map(|n_keys| { | ||
let keys_slice = &keys[..n_keys]; | ||
let (fn_name, is_fetchable, is_iterable) = if n_keys == keys.len() { | ||
let fn_name = format_ident!("{snake_case_name}"); | ||
(fn_name, true, false) | ||
} else { | ||
let fn_name = if n_keys == 0 { | ||
format_ident!("{snake_case_name}_iter") | ||
} else { | ||
format_ident!("{snake_case_name}_iter{}", n_keys) | ||
}; | ||
(fn_name, false, true) | ||
}; | ||
let is_fetchable_type = is_fetchable.then_some(quote!(#crate_path::storage::address::Yes)).unwrap_or(quote!(())); | ||
let is_iterable_type = is_iterable.then_some(quote!(#crate_path::storage::address::Yes)).unwrap_or(quote!(())); | ||
Comment on lines
+113
to
+125
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: I found this a bit hard to follow, maybe a match would state the intent a bit better. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I had a go and came up with something like this let is_iterator = n_keys != keys.len();
let fn_name = match (is_iterator, keys.len()) {
(false, _) => format_ident!("{snake_case_name}"),
(true, 0) => format_ident!("{snake_case_name}_iter"),
(true, n) => format_ident!("{snake_case_name}_iter{}", n)
};
let yes_ty = || quote!(#crate_path::storage::address::Yes);
let no_ty = || quote!(());
let is_fetchable_type = is_iterator.then(no_ty).unwrap_or_else(yes_ty);
let is_iterable_type = is_iterator.then(yes_ty).unwrap_or_else(no_ty); |
||
let key_impls = keys_slice.iter().map(|(field_name, _)| quote!( #crate_path::storage::address::make_static_storage_map_key(#field_name.borrow()) )); | ||
let key_args = keys_slice.iter().map(|(field_name, field_type)| { | ||
// The field type is translated from `std::vec::Vec<T>` to `[T]`. We apply | ||
// Borrow to all types, so this just makes it a little more ergonomic. | ||
// TODO [jsdw]: Support mappings like `String -> str` too for better borrow | ||
// ergonomics. | ||
let field_ty = match field_type.vec_type_param() { | ||
Some(ty) => quote!([#ty]), | ||
_ => quote!(#field_type), | ||
}; | ||
quote!( #field_name: impl ::std::borrow::Borrow<#field_ty> ) | ||
}); | ||
|
||
// If the item is a map, we want a way to access the root entry to do things like iterate over it, | ||
// so expose a function to create this entry, too: | ||
let root_entry_fn = if is_map_type { | ||
let fn_name_root = format_ident!("{}_root", fn_name); | ||
quote!( | ||
#docs | ||
pub fn #fn_name_root( | ||
pub fn #fn_name( | ||
&self, | ||
#(#key_args,)* | ||
) -> #crate_path::storage::address::Address::< | ||
#crate_path::storage::address::StaticStorageMapKey, | ||
#storage_entry_value_ty, | ||
(), | ||
#is_fetchable_type, | ||
#is_defaultable_type, | ||
#is_iterable_type | ||
> { | ||
#crate_path::storage::address::Address::new_static( | ||
#pallet_name, | ||
#storage_name, | ||
Vec::new(), | ||
vec![#(#key_impls,)*], | ||
[#(#storage_hash,)*] | ||
) | ||
} | ||
) | ||
} else { | ||
quote!() | ||
}; | ||
}); | ||
|
||
Ok(quote! { | ||
// Access a specific value from a storage entry | ||
#docs | ||
pub fn #fn_name( | ||
&self, | ||
#( #key_args, )* | ||
) -> #crate_path::storage::address::Address::< | ||
#crate_path::storage::address::StaticStorageMapKey, | ||
#storage_entry_value_ty, | ||
#crate_path::storage::address::Yes, | ||
#is_defaultable_type, | ||
#is_iterable_type | ||
> { | ||
#crate_path::storage::address::Address::new_static( | ||
#pallet_name, | ||
#storage_name, | ||
#key_impl, | ||
[#(#storage_hash,)*] | ||
) | ||
} | ||
#( #all_fns | ||
|
||
#root_entry_fn | ||
)* | ||
}) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -50,7 +50,7 @@ pub trait StorageAddress { | |
pub struct Yes; | ||
|
||
/// A concrete storage address. This can be created from static values (ie those generated | ||
/// via the `subxt` macro) or dynamic values via [`dynamic`] and [`dynamic_root`]. | ||
/// via the `subxt` macro) or dynamic values via [`dynamic`] and [`dynamic_iter`]. | ||
pub struct Address<StorageKey, ReturnTy, Fetchable, Defaultable, Iterable> { | ||
pallet_name: Cow<'static, str>, | ||
entry_name: Cow<'static, str>, | ||
|
@@ -229,7 +229,7 @@ pub fn make_static_storage_map_key<T: codec::Encode>(t: T) -> StaticStorageMapKe | |
} | ||
|
||
/// Construct a new dynamic storage lookup to the root of some entry. | ||
pub fn dynamic_root( | ||
pub fn dynamic_iter( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, but now the Maybe we should just remove this function entirely, since people can just use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (and if we remove this, remember to update the guide :) |
||
pallet_name: impl Into<String>, | ||
entry_name: impl Into<String>, | ||
) -> DynamicAddress<Value> { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are all of these extra spaces an unintentional side effect of some search and replace? They look wrong to me :)