Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Allow renaming storage item prefixes #9016

Merged
9 commits merged into from
Jun 14, 2021
44 changes: 37 additions & 7 deletions frame/support/procedural/src/pallet/expand/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,48 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use crate::pallet::Def;
use crate::pallet::{Def, parse::storage::StorageDef};
use crate::pallet::parse::storage::{Metadata, QueryKind, StorageGenerics};
use frame_support_procedural_tools::clean_type_string;
use std::collections::HashSet;

/// Generate the prefix_ident related the the storage.
/// prefix_ident is used for the prefix struct to be given to storage as first generic param.
fn prefix_ident(storage_ident: &syn::Ident) -> syn::Ident {
fn prefix_ident(storage: &StorageDef) -> syn::Ident {
let storage_ident = &storage.ident;
syn::Ident::new(&format!("_GeneratedPrefixForStorage{}", storage_ident), storage_ident.span())
}

/// Check for duplicated storage prefixes. This step is necessary since users can specify an
/// alternative storage prefix using the #[pallet::storage_prefix] syntax, and we need to ensure
/// that the prefix specified by the user is not a duplicate of an existing one.
fn check_prefix_duplicates(
storage_def: &StorageDef,
set: &mut HashSet<String>,
) -> syn::Result<()> {
let prefix = storage_def.prefix();

if !set.insert(prefix.clone()) {
let err = syn::Error::new(
storage_def.prefix_span(),
format!("Duplicate storage prefixes found for `{}`", prefix),
);
return Err(err);
}

Ok(())
}

/// * 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) {
pub fn process_generics(def: &mut Def) -> syn::Result<()> {
let frame_support = &def.frame_support;
let mut prefix_set = HashSet::new();

for storage_def in def.storages.iter_mut() {
check_prefix_duplicates(storage_def, &mut prefix_set)?;

let item = &mut def.item.content.as_mut().expect("Checked by def").1[storage_def.index];

let typ_item = match item {
Expand All @@ -50,7 +76,7 @@ pub fn process_generics(def: &mut Def) {
_ => unreachable!("Checked by def"),
};

KiChjang marked this conversation as resolved.
Show resolved Hide resolved
let prefix_ident = prefix_ident(&storage_def.ident);
let prefix_ident = prefix_ident(&storage_def);
let type_use_gen = if def.config.has_instance {
quote::quote_spanned!(storage_def.attr_span => T, I)
} else {
Expand Down Expand Up @@ -116,6 +142,8 @@ pub fn process_generics(def: &mut Def) {
args.args[0] = syn::parse_quote!( #prefix_ident<#type_use_gen> );
}
}

Ok(())
}

/// * generate StoragePrefix structs (e.g. for a storage `MyStorage` a struct with the name
Expand All @@ -125,7 +153,9 @@ pub fn process_generics(def: &mut Def) {
/// * Add `#[allow(type_alias_bounds)]` on storages type alias
/// * generate metadatas
pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream {
process_generics(def);
if let Err(e) = process_generics(def) {
return e.into_compile_error().into();
}

let frame_support = &def.frame_support;
let frame_system = &def.frame_system;
Expand Down Expand Up @@ -344,9 +374,9 @@ pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream {
let prefix_structs = def.storages.iter().map(|storage_def| {
let type_impl_gen = &def.type_impl_generics(storage_def.attr_span);
let type_use_gen = &def.type_use_generics(storage_def.attr_span);
let prefix_struct_ident = prefix_ident(&storage_def.ident);
let prefix_struct_ident = prefix_ident(&storage_def);
let prefix_struct_vis = &storage_def.vis;
let prefix_struct_const = storage_def.ident.to_string();
let prefix_struct_const = storage_def.prefix();
let config_where_clause = &def.config.where_clause;

let cfg_attrs = &storage_def.cfg_attrs;
Expand Down
97 changes: 84 additions & 13 deletions frame/support/procedural/src/pallet/parse/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,28 +25,60 @@ mod keyword {
syn::custom_keyword!(Error);
syn::custom_keyword!(pallet);
syn::custom_keyword!(getter);
syn::custom_keyword!(storage_prefix);
syn::custom_keyword!(OptionQuery);
syn::custom_keyword!(ValueQuery);
}

/// Parse for `#[pallet::getter(fn dummy)]`
pub struct PalletStorageAttr {
getter: syn::Ident,
/// Parse for one of the following:
/// * `#[pallet::getter(fn dummy)]`
/// * `#[pallet::storage_prefix = "CustomName"]`
pub enum PalletStorageAttr {
Getter(syn::Ident, proc_macro2::Span),
StorageName(syn::LitStr, proc_macro2::Span),
}
KiChjang marked this conversation as resolved.
Show resolved Hide resolved

impl PalletStorageAttr {
fn attr_span(&self) -> proc_macro2::Span {
match self {
Self::Getter(_, span) | Self::StorageName(_, span) => *span,
}
}
}

impl syn::parse::Parse for PalletStorageAttr {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
input.parse::<syn::Token![#]>()?;
let attr_span = input.span();
let content;
syn::bracketed!(content in input);
content.parse::<keyword::pallet>()?;
content.parse::<syn::Token![::]>()?;
content.parse::<keyword::getter>()?;

let generate_content;
syn::parenthesized!(generate_content in content);
generate_content.parse::<syn::Token![fn]>()?;
Ok(Self { getter: generate_content.parse::<syn::Ident>()? })
let lookahead = content.lookahead1();
if lookahead.peek(keyword::getter) {
content.parse::<keyword::getter>()?;

let generate_content;
syn::parenthesized!(generate_content in content);
generate_content.parse::<syn::Token![fn]>()?;
Ok(Self::Getter(generate_content.parse::<syn::Ident>()?, attr_span))
} else if lookahead.peek(keyword::storage_prefix) {
content.parse::<keyword::storage_prefix>()?;
content.parse::<syn::Token![=]>()?;

let renamed_prefix = content.parse::<syn::LitStr>()?;
// Ensure the renamed prefix is a proper Rust identifier
syn::parse_str::<syn::Ident>(&renamed_prefix.value())
.map_err(|_| {
let msg = format!("`{}` is not a valid identifier", renamed_prefix.value());
syn::Error::new(renamed_prefix.span(), msg)
})?;

Ok(Self::StorageName(renamed_prefix, attr_span))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just a sidenote, now that we don't use renamed_prefix as ident, we don't need to force it to be a valid ident anymore AFAICT

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want that though? This would mean that it's possible to create storage prefixes that does not follow Rust identifier naming conventions. I guess that kind of makes sense, but something tells me that it's a bad idea, like there might be a footgun lying there somewhere if we're too permissive...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, we can leverage the constraint when wanted.

} else {
Err(lookahead.error())
}
}
}

Expand Down Expand Up @@ -89,6 +121,8 @@ pub struct StorageDef {
pub instances: Vec<helper::InstanceUsage>,
/// Optional getter to generate. If some then query_kind is ensured to be some as well.
pub getter: Option<syn::Ident>,
/// 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.
/// 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.
Expand All @@ -105,7 +139,6 @@ pub struct StorageDef {
pub named_generics: Option<StorageGenerics>,
}


/// The parsed generic from the
#[derive(Clone)]
pub enum StorageGenerics {
Expand Down Expand Up @@ -541,6 +574,25 @@ fn extract_key(ty: &syn::Type) -> syn::Result<syn::Type> {
}

impl StorageDef {
/// Return the storage prefix for this storage item
pub fn prefix(&self) -> String {
self
.rename_as
.as_ref()
.map(syn::LitStr::value)
.unwrap_or(self.ident.to_string())
}

/// Return either the span of the ident or the span of the literal in the
/// #[storage_prefix] attribute
pub fn prefix_span(&self) -> proc_macro2::Span {
self
.rename_as
.as_ref()
.map(syn::LitStr::span)
.unwrap_or(self.ident.span())
}

pub fn try_from(
attr_span: proc_macro2::Span,
index: usize,
Expand All @@ -552,12 +604,30 @@ impl StorageDef {
return Err(syn::Error::new(item.span(), "Invalid pallet::storage, expect item type."));
};

let mut attrs: Vec<PalletStorageAttr> = helper::take_item_pallet_attrs(&mut item.attrs)?;
if attrs.len() > 1 {
let attrs: Vec<PalletStorageAttr> = helper::take_item_pallet_attrs(&mut item.attrs)?;
let (mut getters, mut names) = attrs
.into_iter()
.partition::<Vec<_>, _>(|attr| matches!(attr, PalletStorageAttr::Getter(..)));
if getters.len() > 1 {
let msg = "Invalid pallet::storage, multiple argument pallet::getter found";
return Err(syn::Error::new(attrs[1].getter.span(), msg));
return Err(syn::Error::new(getters[1].attr_span(), msg));
}
let getter = attrs.pop().map(|attr| attr.getter);
if names.len() > 1 {
let msg = "Invalid pallet::storage, multiple argument pallet::storage_prefix found";
return Err(syn::Error::new(names[1].attr_span(), msg));
}
let getter = getters.pop().map(|attr| {
match attr {
PalletStorageAttr::Getter(ident, _) => ident,
_ => unreachable!(),
}
});
let rename_as = names.pop().map(|attr| {
match attr {
PalletStorageAttr::StorageName(lit, _) => lit,
_ => unreachable!(),
}
});

let cfg_attrs = helper::get_item_cfg_attrs(&item.attrs);

Expand Down Expand Up @@ -609,6 +679,7 @@ impl StorageDef {
metadata,
docs,
getter,
rename_as,
query_kind,
where_clause,
cfg_attrs,
Expand Down
20 changes: 20 additions & 0 deletions frame/support/test/tests/pallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,10 @@ pub mod pallet {
#[pallet::storage]
pub type Value<T> = StorageValue<Value = u32>;

#[pallet::storage]
#[pallet::storage_prefix = "Value2"]
pub type RenamedValue<T> = StorageValue<Value = u64>;

#[pallet::type_value]
pub fn MyDefault<T: Config>() -> u16
where T::AccountId: From<SomeType7> + From<SomeType1> + SomeAssociation1
Expand Down Expand Up @@ -577,6 +581,10 @@ fn storage_expand() {
let k = [twox_128(b"Example"), twox_128(b"Value")].concat();
assert_eq!(unhashed::get::<u32>(&k), Some(1u32));

pallet::RenamedValue::<Runtime>::put(2);
let k = [twox_128(b"Example"), twox_128(b"Value2")].concat();
assert_eq!(unhashed::get::<u64>(&k), Some(2));

pallet::Map::<Runtime>::insert(1, 2);
let mut k = [twox_128(b"Example"), twox_128(b"Map")].concat();
k.extend(1u8.using_encoded(blake2_128_concat));
Expand Down Expand Up @@ -697,6 +705,13 @@ fn metadata() {
default: DecodeDifferent::Decoded(vec![0]),
documentation: DecodeDifferent::Decoded(vec![]),
},
StorageEntryMetadata {
name: DecodeDifferent::Decoded("Value2".to_string()),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we might actually want to break the metadata here...

Maybe not right in this PR, but you can imagine someone will want to expose the name as "Balance" but use a custom prefix like "1".

As such, there would be a name property in the metadata, and a prefix property.

But this is something that would be much more annoying to merge in, and so probably should be an issue and we save it for when we already will break metadata.

modifier: StorageEntryModifier::Optional,
ty: StorageEntryType::Plain(DecodeDifferent::Decoded("u64".to_string())),
default: DecodeDifferent::Decoded(vec![0]),
documentation: DecodeDifferent::Decoded(vec![]),
},
StorageEntryMetadata {
name: DecodeDifferent::Decoded("Map".to_string()),
modifier: StorageEntryModifier::Default,
Expand Down Expand Up @@ -993,6 +1008,11 @@ fn test_storage_info() {
max_values: Some(1),
max_size: Some(4),
},
StorageInfo {
prefix: prefix(b"Example", b"Value2"),
max_values: Some(1),
max_size: Some(8),
},
StorageInfo {
prefix: prefix(b"Example", b"Map"),
max_values: None,
Expand Down
21 changes: 21 additions & 0 deletions frame/support/test/tests/pallet_ui/duplicate_storage_prefix.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#[frame_support::pallet]
mod pallet {
use frame_support::pallet_prelude::StorageValue;

#[pallet::config]
pub trait Config: frame_system::Config {}

#[pallet::pallet]
#[pallet::generate_store(trait Store)]
pub struct Pallet<T>(core::marker::PhantomData<T>);

#[pallet::storage]
type Foo<T> = StorageValue<_, u8>;

#[pallet::storage]
#[pallet::storage_prefix = "Foo"]
type NotFoo<T> = StorageValue<_, u16>;
}

fn main() {
}
17 changes: 17 additions & 0 deletions frame/support/test/tests/pallet_ui/duplicate_storage_prefix.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
error: Duplicate storage prefixes found for `Foo`
--> $DIR/duplicate_storage_prefix.rs:16:32
|
16 | #[pallet::storage_prefix = "Foo"]
| ^^^^^

error[E0412]: cannot find type `_GeneratedPrefixForStorageFoo` in this scope
--> $DIR/duplicate_storage_prefix.rs:13:7
|
13 | type Foo<T> = StorageValue<_, u8>;
| ^^^ not found in this scope

error[E0121]: the type placeholder `_` is not allowed within types on item signatures
--> $DIR/duplicate_storage_prefix.rs:17:35
|
17 | type NotFoo<T> = StorageValue<_, u16>;
| ^ not allowed in type signatures
21 changes: 21 additions & 0 deletions frame/support/test/tests/pallet_ui/storage_invalid_attribute.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#[frame_support::pallet]
mod pallet {
use frame_support::pallet_prelude::Hooks;
use frame_system::pallet_prelude::BlockNumberFor;

#[pallet::config]
pub trait Config: frame_system::Config {}

#[pallet::pallet]
pub struct Pallet<T>(_);

#[pallet::call]
impl<T: Config> Pallet<T> {}

#[pallet::storage]
#[pallet::generate_store(pub trait Store)]
type Foo<T> = StorageValue<u8, u8>;
}

fn main() {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
error: expected `getter` or `storage_prefix`
--> $DIR/storage_invalid_attribute.rs:16:12
|
16 | #[pallet::generate_store(pub trait Store)]
| ^^^^^^^^^^^^^^
18 changes: 18 additions & 0 deletions frame/support/test/tests/pallet_ui/storage_invalid_rename_value.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#[frame_support::pallet]
mod pallet {
use frame_support::pallet_prelude::Hooks;
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::storage]
#[pallet::storage_prefix = "pub"]
type Foo<T> = StorageValue<_, u8>;
}

fn main() {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
error: `pub` is not a valid identifier
--> $DIR/storage_invalid_rename_value.rs:13:29
|
13 | #[pallet::storage_prefix = "pub"]
| ^^^^^
Loading