Files
pezkuwi-subxt/substrate/srml/support/procedural/src/storage/impls.rs
T
Bastian Köcher 62b7c05def Implement a proper generic resolution in decl_storage! (#2913)
* Add failing test case

* move storage maps to blake2_128 (#2268)

* remove default hash, introduce twox_128 and blake2

* use blake2_128 & create ext_blake2_128

* refactor code

* add benchmark

* factorize generator

* fix

* parameterizable hasher

* some fix

* fix

* fix

* fix

* metadata

* fix

* remove debug print

* map -> blake2_256

* fix test

* fix test

* Apply suggestions from code review

Co-Authored-By: thiolliere <gui.thiolliere@gmail.com>

* impl twox 128 concat (#2353)

* impl twox_128_concat

* comment addressed

* fix

* impl twox_128->64_concat

* fix test

* Fix compilation and cleanup some docs

* Lol

* Remove traits from storage types that are not generic

* Get instance test almost working as wanted

* Make `srml-support-test` compile again :)

* Fixes test of srml-support

* Fix compilation

* Break some lines

* Remove incorrect macro match arm

* Integrates review feedback

* Update documentation

* Fix compilation
2019-06-27 13:40:22 +02:00

765 lines
23 KiB
Rust

// Copyright 2019 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
use crate::storage::transformation::{DeclStorageTypeInfos, InstanceOpts};
use srml_support_procedural_tools::syn_ext as ext;
use proc_macro2::TokenStream as TokenStream2;
use syn::Ident;
use quote::quote;
pub fn option_unwrap(is_option: bool) -> TokenStream2 {
if !is_option {
// raw type case
quote!( unwrap_or_else )
} else {
// Option<> type case
quote!( or_else )
}
}
// prefix for consts in trait Instance
pub(crate) const PREFIX_FOR: &str = "PREFIX_FOR_";
pub(crate) const HEAD_KEY_FOR: &str = "HEAD_KEY_FOR_";
pub(crate) struct Impls<'a, I: Iterator<Item=syn::Meta>> {
pub scrate: &'a TokenStream2,
pub visibility: &'a syn::Visibility,
pub traitinstance: &'a syn::Ident,
pub traittype: &'a syn::TypeParamBound,
pub instance_opts: &'a InstanceOpts,
pub type_infos: DeclStorageTypeInfos<'a>,
pub fielddefault: TokenStream2,
pub prefix: String,
pub cratename: &'a syn::Ident,
pub name: &'a syn::Ident,
pub attrs: I,
}
impl<'a, I: Iterator<Item=syn::Meta>> Impls<'a, I> {
pub fn simple_value(self) -> TokenStream2 {
let Self {
scrate,
visibility,
traitinstance,
traittype,
instance_opts,
type_infos,
fielddefault,
prefix,
name,
attrs,
..
} = self;
let DeclStorageTypeInfos { typ, value_type, is_option, .. } = type_infos;
let option_simple_1 = option_unwrap(is_option);
let mutate_impl = if !is_option {
quote!{
<Self as #scrate::storage::hashed::generator::StorageValue<#typ>>::put(&val, storage)
}
} else {
quote!{
match val {
Some(ref val) => <Self as #scrate::storage::hashed::generator::StorageValue<#typ>>::put(&val, storage),
None => <Self as #scrate::storage::hashed::generator::StorageValue<#typ>>::kill(storage),
}
}
};
let InstanceOpts {
equal_default_instance,
bound_instantiable,
instance,
..
} = instance_opts;
let final_prefix = if let Some(instance) = instance {
let const_name = Ident::new(&format!("{}{}", PREFIX_FOR, name.to_string()), proc_macro2::Span::call_site());
quote!{ #instance::#const_name.as_bytes() }
} else {
quote!{ #prefix.as_bytes() }
};
let (struct_trait, impl_trait, trait_and_instance) = if ext::type_contains_ident(
value_type, traitinstance
) {
(
quote!(#traitinstance: #traittype, #instance #bound_instantiable #equal_default_instance),
quote!(#traitinstance: #traittype, #instance #bound_instantiable),
quote!(#traitinstance, #instance),
)
} else {
(
quote!(#instance #bound_instantiable #equal_default_instance),
quote!(#instance #bound_instantiable),
quote!(#instance)
)
};
// generator for value
quote! {
#( #[ #attrs ] )*
#visibility struct #name<#struct_trait>(
#scrate::rstd::marker::PhantomData<(#trait_and_instance)>
);
impl<#impl_trait> #scrate::storage::hashed::generator::StorageValue<#typ>
for #name<#trait_and_instance>
{
type Query = #value_type;
/// Get the storage key.
fn key() -> &'static [u8] {
#final_prefix
}
/// Load the value from the provided storage instance.
fn get<S: #scrate::HashedStorage<#scrate::Twox128>>(storage: &S) -> Self::Query {
storage.get(<Self as #scrate::storage::hashed::generator::StorageValue<#typ>>::key())
.#option_simple_1(|| #fielddefault)
}
/// Take a value from storage, removing it afterwards.
fn take<S: #scrate::HashedStorage<#scrate::Twox128>>(storage: &mut S) -> Self::Query {
storage.take(<Self as #scrate::storage::hashed::generator::StorageValue<#typ>>::key())
.#option_simple_1(|| #fielddefault)
}
/// Mutate the value under a key.
fn mutate<R, F, S>(f: F, storage: &mut S) -> R
where
F: FnOnce(&mut Self::Query) -> R,
S: #scrate::HashedStorage<#scrate::Twox128>,
{
let mut val = <Self as #scrate::storage::hashed::generator::StorageValue<#typ>>::get(storage);
let ret = f(&mut val);
#mutate_impl ;
ret
}
}
}
}
pub fn map(self, hasher: TokenStream2, kty: &syn::Type) -> TokenStream2 {
let Self {
scrate,
visibility,
traitinstance,
traittype,
instance_opts,
type_infos,
fielddefault,
prefix,
name,
attrs,
..
} = self;
let DeclStorageTypeInfos { typ, value_type, is_option, .. } = type_infos;
let option_simple_1 = option_unwrap(is_option);
let as_map = quote!{ <Self as #scrate::storage::hashed::generator::StorageMap<#kty, #typ>> };
let mutate_impl = if !is_option {
quote!{
#as_map::insert(key, &val, storage)
}
} else {
quote!{
match val {
Some(ref val) => #as_map::insert(key, &val, storage),
None => #as_map::remove(key, storage),
}
}
};
let InstanceOpts {
equal_default_instance,
bound_instantiable,
instance,
..
} = instance_opts;
let final_prefix = if let Some(instance) = instance {
let const_name = syn::Ident::new(&format!("{}{}", PREFIX_FOR, name.to_string()), proc_macro2::Span::call_site());
quote!{ #instance::#const_name.as_bytes() }
} else {
quote!{ #prefix.as_bytes() }
};
let (struct_trait, impl_trait, trait_and_instance) = if ext::type_contains_ident(value_type, traitinstance)
|| ext::type_contains_ident(kty, traitinstance)
{
(
quote!(#traitinstance: #traittype, #instance #bound_instantiable #equal_default_instance),
quote!(#traitinstance: #traittype, #instance #bound_instantiable),
quote!(#traitinstance, #instance),
)
} else {
(
quote!(#instance #bound_instantiable #equal_default_instance),
quote!(#instance #bound_instantiable),
quote!(#instance)
)
};
// generator for map
quote!{
#( #[ #attrs ] )*
#visibility struct #name<#struct_trait>(
#scrate::rstd::marker::PhantomData<(#trait_and_instance)>
);
impl<#impl_trait>
#scrate::storage::hashed::generator::StorageMap<#kty, #typ> for #name<#trait_and_instance>
{
type Query = #value_type;
type Hasher = #scrate::#hasher;
/// Get the prefix key in storage.
fn prefix() -> &'static [u8] {
#final_prefix
}
/// Get the storage key used to fetch a value corresponding to a specific key.
fn key_for(x: &#kty) -> #scrate::rstd::vec::Vec<u8> {
let mut key = #as_map::prefix().to_vec();
#scrate::codec::Encode::encode_to(x, &mut key);
key
}
/// Load the value associated with the given key from the map.
fn get<S: #scrate::HashedStorage<#scrate::#hasher>>(key: &#kty, storage: &S) -> Self::Query {
let key = #as_map::key_for(key);
storage.get(&key[..]).#option_simple_1(|| #fielddefault)
}
/// Take the value, reading and removing it.
fn take<S: #scrate::HashedStorage<#scrate::#hasher>>(key: &#kty, storage: &mut S) -> Self::Query {
let key = #as_map::key_for(key);
storage.take(&key[..]).#option_simple_1(|| #fielddefault)
}
/// Mutate the value under a key
fn mutate<R, F, S>(key: &#kty, f: F, storage: &mut S) -> R
where
F: FnOnce(&mut Self::Query) -> R,
S: #scrate::HashedStorage<#scrate::#hasher>,
{
let mut val = #as_map::get(key, storage);
let ret = f(&mut val);
#mutate_impl ;
ret
}
}
impl<#impl_trait> #scrate::storage::hashed::generator::AppendableStorageMap<#kty, #typ>
for #name<#trait_and_instance>
{}
}
}
pub fn linked_map(self, hasher: TokenStream2, kty: &syn::Type) -> TokenStream2 {
let Self {
scrate,
visibility,
traitinstance,
traittype,
instance_opts,
type_infos,
fielddefault,
prefix,
name,
attrs,
..
} = self;
let InstanceOpts {
equal_default_instance,
bound_instantiable,
instance,
..
} = instance_opts;
let final_prefix = if let Some(instance) = instance {
let const_name = syn::Ident::new(&format!("{}{}", PREFIX_FOR, name.to_string()), proc_macro2::Span::call_site());
quote!{ #instance::#const_name.as_bytes() }
} else {
quote!{ #prefix.as_bytes() }
};
// make sure to use different prefix for head and elements.
let final_head_key = if let Some(instance) = instance {
let const_name = syn::Ident::new(&format!("{}{}", HEAD_KEY_FOR, name.to_string()), proc_macro2::Span::call_site());
quote!{ #instance::#const_name.as_bytes() }
} else {
let final_head_key = format!("head of {}", prefix);
quote!{ #final_head_key.as_bytes() }
};
let DeclStorageTypeInfos { typ, value_type, is_option, .. } = type_infos;
let option_simple_1 = option_unwrap(is_option);
let name_lowercase = name.to_string().to_lowercase();
let inner_module = syn::Ident::new(&format!("__linked_map_details_for_{}_do_not_use", name_lowercase), name.span());
let linkage = syn::Ident::new(&format!("__LinkageFor{}DoNotUse", name), name.span());
let phantom_data = quote! { #scrate::rstd::marker::PhantomData };
let as_map = quote!{ <Self as #scrate::storage::hashed::generator::StorageMap<#kty, #typ>> };
let put_or_insert = quote! {
match linkage {
Some(linkage) => storage.put(key_for, &(val, linkage)),
None => #as_map::insert(key, &val, storage),
}
};
let mutate_impl = if !type_infos.is_option {
put_or_insert
} else {
quote! {
match val {
Some(ref val) => #put_or_insert,
None => #as_map::remove(key, storage),
}
}
};
let (struct_trait, impl_trait, trait_and_instance, trait_lifetime) = if ext::type_contains_ident(
value_type,
traitinstance
) || ext::type_contains_ident(kty, traitinstance)
{
(
quote!(#traitinstance: #traittype, #instance #bound_instantiable #equal_default_instance),
quote!(#traitinstance: #traittype, #instance #bound_instantiable),
quote!(#traitinstance, #instance),
quote!(#traitinstance: 'static),
)
} else {
(
quote!(#instance #bound_instantiable #equal_default_instance),
quote!(#instance #bound_instantiable),
quote!(#instance),
quote!()
)
};
// generator for linked map
let helpers = quote! {
/// Linkage data of an element (it's successor and predecessor)
#[derive(#scrate::codec::Encode, #scrate::codec::Decode)]
pub(crate) struct #linkage<Key> {
/// Previous element key in storage (None for the first element)
pub previous: Option<Key>,
/// Next element key in storage (None for the last element)
pub next: Option<Key>,
}
mod #inner_module {
use super::*;
/// Re-exported version of linkage to overcome proc-macro derivation issue.
pub(crate) use super::#linkage as Linkage;
impl<Key> Default for Linkage<Key> {
fn default() -> Self {
Self {
previous: None,
next: None,
}
}
}
/// A key-value pair iterator for enumerable map.
pub(crate) struct Enumerator<'a, S, K, V> {
pub storage: &'a S,
pub next: Option<K>,
pub _data: #phantom_data<V>,
}
impl<'a, S: #scrate::HashedStorage<#scrate::#hasher>, #impl_trait>
Iterator for Enumerator<'a, S, #kty, (#typ, #trait_and_instance)>
{
type Item = (#kty, #typ);
fn next(&mut self) -> Option<Self::Item> {
let next = self.next.take()?;
let key_for = <super::#name<#trait_and_instance>
as #scrate::storage::hashed::generator::StorageMap<#kty, #typ>>::key_for(&next);
let (val, linkage): (#typ, Linkage<#kty>) = self.storage.get(&*key_for)
.expect("previous/next only contain existing entires; we enumerate using next; entry exists; qed");
self.next = linkage.next;
Some((next, val))
}
}
pub(crate) trait Utils<#struct_trait> {
/// Update linkage when this element is removed.
///
/// Takes care of updating previous and next elements points
/// as well as updates head if the element is first or last.
fn remove_linkage<S: #scrate::HashedStorage<#scrate::#hasher>>(linkage: Linkage<#kty>, storage: &mut S);
/// Read the contained data and it's linkage.
fn read_with_linkage<S>(storage: &S, key: &[u8]) -> Option<(#value_type, Linkage<#kty>)>
where
S: #scrate::HashedStorage<#scrate::#hasher>;
/// Generate linkage for newly inserted element.
///
/// Takes care of updating head and previous head's pointer.
fn new_head_linkage<S: #scrate::HashedStorage<#scrate::#hasher>>(
storage: &mut S,
key: &#kty,
) -> Linkage<#kty>;
/// Read current head pointer.
fn read_head<S: #scrate::HashedStorage<#scrate::#hasher>>(storage: &S) -> Option<#kty>;
/// Overwrite current head pointer.
///
/// If `None` is given head is removed from storage.
fn write_head<S: #scrate::HashedStorage<#scrate::#hasher>>(storage: &mut S, head: Option<&#kty>);
}
}
};
let structure = quote! {
#( #[ #attrs ] )*
#visibility struct #name<#struct_trait>(#phantom_data<(#trait_and_instance)>);
impl<#impl_trait> self::#inner_module::Utils<#trait_and_instance> for #name<#trait_and_instance> {
fn remove_linkage<S: #scrate::HashedStorage<#scrate::#hasher>>(
linkage: self::#inner_module::Linkage<#kty>,
storage: &mut S,
) {
use self::#inner_module::Utils;
let next_key = linkage.next.as_ref().map(|x| #as_map::key_for(x));
let prev_key = linkage.previous.as_ref().map(|x| #as_map::key_for(x));
if let Some(prev_key) = prev_key {
// Retrieve previous element and update `next`
let mut res = Self::read_with_linkage(storage, &*prev_key)
.expect("Linkage is updated in case entry is removed; it always points to existing keys; qed");
res.1.next = linkage.next;
storage.put(&*prev_key, &res);
} else {
// we were first so let's update the head
Self::write_head(storage, linkage.next.as_ref());
}
if let Some(next_key) = next_key {
// Update previous of next element
let mut res = Self::read_with_linkage(storage, &*next_key)
.expect("Linkage is updated in case entry is removed; it always points to existing keys; qed");
res.1.previous = linkage.previous;
storage.put(&*next_key, &res);
}
}
fn read_with_linkage<S: #scrate::HashedStorage<#scrate::#hasher>>(
storage: &S,
key: &[u8],
) -> Option<(#value_type, self::#inner_module::Linkage<#kty>)> {
storage.get(key)
}
fn new_head_linkage<S: #scrate::HashedStorage<#scrate::#hasher>>(
storage: &mut S,
key: &#kty,
) -> self::#inner_module::Linkage<#kty> {
use self::#inner_module::Utils;
if let Some(head) = Self::read_head(storage) {
// update previous head predecessor
{
let head_key = #as_map::key_for(&head);
let (data, linkage) = Self::read_with_linkage(storage, &*head_key).expect(r#"
head is set when first element is inserted and unset when last element is removed;
if head is Some then it points to existing key; qed
"#);
storage.put(&*head_key, &(data, self::#inner_module::Linkage {
next: linkage.next.as_ref(),
previous: Some(key),
}));
}
// update to current head
Self::write_head(storage, Some(key));
// return linkage with pointer to previous head
let mut linkage = self::#inner_module::Linkage::default();
linkage.next = Some(head);
linkage
} else {
// we are first - update the head and produce empty linkage
Self::write_head(storage, Some(key));
self::#inner_module::Linkage::default()
}
}
fn read_head<S: #scrate::HashedStorage<#scrate::#hasher>>(storage: &S) -> Option<#kty> {
storage.get(#final_head_key)
}
fn write_head<S: #scrate::HashedStorage<#scrate::#hasher>>(storage: &mut S, head: Option<&#kty>) {
match head {
Some(head) => storage.put(#final_head_key, head),
None => storage.kill(#final_head_key),
}
}
}
};
quote! {
#helpers
#structure
impl<#impl_trait>
#scrate::storage::hashed::generator::StorageMap<#kty, #typ> for #name<#trait_and_instance>
{
type Query = #value_type;
type Hasher = #scrate::#hasher;
/// Get the prefix key in storage.
fn prefix() -> &'static [u8] {
#final_prefix
}
/// Get the storage key used to fetch a value corresponding to a specific key.
fn key_for(key: &#kty) -> #scrate::rstd::vec::Vec<u8> {
let mut key_for = #as_map::prefix().to_vec();
#scrate::codec::Encode::encode_to(&key, &mut key_for);
key_for
}
/// Load the value associated with the given key from the map.
fn get<S: #scrate::HashedStorage<#scrate::#hasher>>(key: &#kty, storage: &S) -> Self::Query {
storage.get(&*#as_map::key_for(key)).#option_simple_1(|| #fielddefault)
}
/// Take the value, reading and removing it.
fn take<S: #scrate::HashedStorage<#scrate::#hasher>>(key: &#kty, storage: &mut S) -> Self::Query {
use self::#inner_module::Utils;
let res: Option<(#value_type, self::#inner_module::Linkage<#kty>)> = storage.take(&*#as_map::key_for(key));
match res {
Some((data, linkage)) => {
Self::remove_linkage(linkage, storage);
data
},
None => #fielddefault,
}
}
/// Remove the value under a key.
fn remove<S: #scrate::HashedStorage<#scrate::#hasher>>(key: &#kty, storage: &mut S) {
#as_map::take(key, storage);
}
/// Store a value to be associated with the given key from the map.
fn insert<S: #scrate::HashedStorage<#scrate::#hasher>>(key: &#kty, val: &#typ, storage: &mut S) {
use self::#inner_module::Utils;
let key_for = &*#as_map::key_for(key);
let linkage = match Self::read_with_linkage(storage, key_for) {
// overwrite but reuse existing linkage
Some((_data, linkage)) => linkage,
// create new linkage
None => Self::new_head_linkage(storage, key),
};
storage.put(key_for, &(val, linkage))
}
/// Mutate the value under a key
fn mutate<R, F, S>(key: &#kty, f: F, storage: &mut S) -> R
where
F: FnOnce(&mut Self::Query) -> R,
S: #scrate::HashedStorage<#scrate::#hasher>,
{
use self::#inner_module::Utils;
let key_for = &*#as_map::key_for(key);
let (mut val, linkage) = Self::read_with_linkage(storage, key_for)
.map(|(data, linkage)| (data, Some(linkage)))
.unwrap_or_else(|| (#fielddefault, None));
let ret = f(&mut val);
#mutate_impl ;
ret
}
}
impl<#impl_trait>
#scrate::storage::hashed::generator::EnumerableStorageMap<#kty, #typ> for #name<#trait_and_instance>
where
#trait_lifetime
{
fn head<S: #scrate::HashedStorage<#scrate::#hasher>>(storage: &S) -> Option<#kty> {
use self::#inner_module::Utils;
Self::read_head(storage)
}
fn enumerate<'a, S>(
storage: &'a S
) -> #scrate::rstd::boxed::Box<dyn Iterator<Item = (#kty, #typ)> + 'a>
where
S: #scrate::HashedStorage<#scrate::#hasher>,
#kty: 'a,
#typ: 'a,
{
use self::#inner_module::{Utils, Enumerator};
#scrate::rstd::boxed::Box::new(Enumerator {
next: Self::read_head(storage),
storage,
_data: #phantom_data::<(#typ, #trait_and_instance)>::default(),
})
}
}
}
}
pub fn double_map(
self,
hasher: TokenStream2,
k1ty: &syn::Type,
k2ty: &syn::Type,
k2_hasher: TokenStream2,
) -> TokenStream2 {
let Self {
scrate,
visibility,
traitinstance,
traittype,
type_infos,
fielddefault,
prefix,
name,
attrs,
instance_opts,
..
} = self;
let DeclStorageTypeInfos { typ, value_type, is_option, .. } = type_infos;
let option_simple_1 = option_unwrap(is_option);
let as_double_map = quote!{ <Self as #scrate::storage::unhashed::generator::StorageDoubleMap<#k1ty, #k2ty, #typ>> };
let mutate_impl = if !is_option {
quote!{
#as_double_map::insert(key1, key2, &val, storage)
}
} else {
quote!{
match val {
Some(ref val) => #as_double_map::insert(key1, key2, &val, storage),
None => #as_double_map::remove(key1, key2, storage),
}
}
};
let InstanceOpts {
equal_default_instance,
bound_instantiable,
instance,
..
} = instance_opts;
let final_prefix = if let Some(instance) = instance {
let const_name = syn::Ident::new(&format!("{}{}", PREFIX_FOR, name.to_string()), proc_macro2::Span::call_site());
quote!{ #instance::#const_name.as_bytes() }
} else {
quote!{ #prefix.as_bytes() }
};
let (struct_trait, impl_trait, trait_and_instance) = if ext::type_contains_ident(value_type, traitinstance)
|| ext::type_contains_ident(k1ty, traitinstance)
|| ext::type_contains_ident(k2ty, traitinstance)
{
(
quote!(#traitinstance: #traittype, #instance #bound_instantiable #equal_default_instance),
quote!(#traitinstance: #traittype, #instance #bound_instantiable),
quote!(#traitinstance, #instance),
)
} else {
(
quote!(#instance #bound_instantiable #equal_default_instance),
quote!(#instance #bound_instantiable),
quote!(#instance)
)
};
// generator for double map
quote!{
#( #[ #attrs ] )*
#visibility struct #name<#struct_trait>
(#scrate::rstd::marker::PhantomData<(#trait_and_instance)>);
impl<#impl_trait>
#scrate::storage::unhashed::generator::StorageDoubleMap<#k1ty, #k2ty, #typ> for #name<#trait_and_instance>
{
type Query = #value_type;
fn prefix_for(k1: &#k1ty) -> Vec<u8> {
use #scrate::storage::hashed::generator::StorageHasher;
let mut key = #as_double_map::prefix().to_vec();
#scrate::codec::Encode::encode_to(k1, &mut key);
#scrate::#hasher::hash(&key[..]).to_vec()
}
fn prefix() -> &'static [u8] {
#final_prefix
}
fn key_for(k1: &#k1ty, k2: &#k2ty) -> Vec<u8> {
use #scrate::storage::hashed::generator::StorageHasher;
let mut key = #as_double_map::prefix_for(k1);
#scrate::codec::Encode::using_encoded(k2, |e| key.extend(&#scrate::#k2_hasher::hash(e)));
key
}
fn get<S: #scrate::UnhashedStorage>(key1: &#k1ty, key2: &#k2ty, storage: &S) -> Self::Query {
let key = #as_double_map::key_for(key1, key2);
storage.get(&key).#option_simple_1(|| #fielddefault)
}
fn take<S: #scrate::UnhashedStorage>(key1: &#k1ty, key2: &#k2ty, storage: &mut S) -> Self::Query {
let key = #as_double_map::key_for(key1, key2);
storage.take(&key).#option_simple_1(|| #fielddefault)
}
fn mutate<R, F, S>(key1: &#k1ty, key2: &#k2ty, f: F, storage: &mut S) -> R
where
F: FnOnce(&mut Self::Query) -> R,
S: #scrate::UnhashedStorage,
{
let mut val = #as_double_map::get(key1, key2, storage);
let ret = f(&mut val);
#mutate_impl ;
ret
}
}
}
}
}