decl_storage as a proc_macro (#1176)

* `decl_storage` parsing of the macro (TODO change tool crate structure)

* Start formatting, for now use inner macro.
Still missing optional formating last part (genesis ...).

* Calling extra genesis macro

* decl_storage lines parsing.

* genesis macro as quote (need some cleaning reorg)

* dirty $crate substitute

* proc crate reorg.

* PR impl : skip usage of phantom data, it only applies in test and
council (others required it).

* Remaining macro of decl_storage, warning stringify behave sometime
oddly.

* Formatting code and some cleaning.

* Include line parsing to main struct (cannot use existing macro anymore).

* Remove genesis phantom data when there is already a field with type
parameter.

* Revert wasm files

* Remove old version of `decl_storage`.

* Fix false positive for phantom trait (additional type check on config
build).

* slight changes:
- return token errors instead of panic
- do not use useless intermediate vec

* Update srml/support/procedural/tools/derive/src/lib.rs

remove indent

Co-Authored-By: cheme <emericchevalier.pro@gmail.com>

* Switch iterations to fold, remove unused import.
This commit is contained in:
cheme
2018-12-09 21:09:39 +01:00
committed by Bastian Köcher
parent 3ffb45e3c8
commit ed5b78eaf0
34 changed files with 1681 additions and 1427 deletions
+1
View File
@@ -12,6 +12,7 @@ srml-metadata = { path = "../metadata", default-features = false }
sr-std = { path = "../../core/sr-std", default-features = false }
sr-io = { path = "../../core/sr-io", default-features = false }
sr-primitives = { path = "../../core/sr-primitives", default-features = false }
srml-support-procedural = { path = "./procedural" }
mashup = "0.1.7"
[dev-dependencies]
@@ -0,0 +1,15 @@
[package]
name = "srml-support-procedural"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
[lib]
proc-macro = true
[dependencies]
srml-support-procedural-tools = { path = "./tools" }
sr-api-macros = { path = "../../../core/sr-api-macros" }
proc-macro2 = "0.4"
quote = { version = "0.6" }
syn = { version = "0.15", features = ["full"] }
@@ -0,0 +1,59 @@
// Copyright 2017-2018 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/>.
// tag::description[]
//! Proc macro of Support code for the runtime.
// end::description[]
#![recursion_limit="256"]
extern crate proc_macro;
extern crate proc_macro2;
#[macro_use]
extern crate syn;
#[macro_use]
extern crate quote;
#[macro_use]
extern crate srml_support_procedural_tools;
mod storage;
use proc_macro::TokenStream;
/// Declares strongly-typed wrappers around codec-compatible types in storage.
///
/// ## Example
///
/// ```compile_fail
/// decl_storage! {
/// trait Store for Module<T: Trait> as Example {
/// Dummy get(dummy) config(): Option<T::Balance>;
/// Foo get(foo) config(): T::Balance;
/// }
/// }
/// ```
///
/// For now we implement a convenience trait with pre-specialised associated types, one for each
/// storage item. This allows you to gain access to publicly visible storage items from a
/// module type. Currently you must disambiguate by using `<Module as Store>::Item` rather than
/// the simpler `Module::Item`. Hopefully the rust guys with fix this soon.
#[proc_macro]
pub fn decl_storage(input: TokenStream) -> TokenStream {
storage::transformation::decl_storage_impl(input)
}
@@ -0,0 +1,144 @@
// Copyright 2017-2018 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/>.
// tag::description[]
//! `decl_storage` macro
// end::description[]
use srml_support_procedural_tools::syn_ext as ext;
use syn::Ident;
use syn::token::CustomKeyword;
pub mod transformation;
/// Parsing usage only
#[derive(ParseStruct, ToTokensStruct, Debug)]
struct StorageDefinition {
pub hidden_crate: Option<SpecificHiddenCrate>,
pub visibility: syn::Visibility,
pub trait_token: Token![trait],
pub ident: Ident,
pub for_token: Token![for],
pub module_ident: Ident,
pub mod_lt_token: Token![<],
pub mod_param: syn::GenericParam,
pub mod_gt_token: Token![>],
pub as_token: Token![as],
pub crate_ident: Ident,
pub content: ext::Braces<ext::Punctuated<DeclStorageLine, Token![;]>>,
pub extra_genesis: Option<AddExtraGenesis>,
}
#[derive(ParseStruct, ToTokensStruct, Debug)]
struct SpecificHiddenCrate {
pub keyword: ext::CustomToken<SpecificHiddenCrate>,
pub ident: ext::Parens<Ident>,
}
#[derive(ParseStruct, ToTokensStruct, Debug)]
struct AddExtraGenesis {
pub extragenesis_keyword: ext::CustomToken<AddExtraGenesis>,
pub content: ext::Braces<AddExtraGenesisContent>,
}
#[derive(ParseStruct, ToTokensStruct, Debug)]
struct AddExtraGenesisContent {
pub lines: ext::Punctuated<AddExtraGenesisLineEnum, Token![;]>,
}
#[derive(ParseEnum, ToTokensEnum, Debug)]
enum AddExtraGenesisLineEnum {
AddExtraGenesisLine(AddExtraGenesisLine),
AddExtraGenesisBuild(DeclStorageBuild),
}
#[derive(ParseStruct, ToTokensStruct, Debug)]
struct AddExtraGenesisLine {
pub attrs: ext::OuterAttributes,
pub config_keyword: ext::CustomToken<ConfigKeyword>,
pub extra_field: ext::Parens<Ident>,
pub coldot_token: Token![:],
pub extra_type: syn::Type,
// TODO use a custom ext::Option instead (syn option on '=' fails)
pub default_value: ext::Seq<DeclStorageDefault>,
}
#[derive(ParseStruct, ToTokensStruct, Debug)]
struct DeclStorageLine {
// attrs (main use case is doc)
pub attrs: ext::OuterAttributes,
// visibility (no need to make optional
pub visibility: syn::Visibility,
// name
pub name: Ident,
pub getter: Option<DeclStorageGetter>,
pub config: Option<DeclStorageConfig>,
pub build: Option<DeclStorageBuild>,
pub coldot_token: Token![:],
pub storage_type: DeclStorageType,
// TODO use a custom ext::Option instead (syn option on '=' fails)
pub default_value: ext::Seq<DeclStorageDefault>,
}
#[derive(ParseStruct, ToTokensStruct, Debug)]
struct DeclStorageGetter {
pub getter_keyword: ext::CustomToken<DeclStorageGetter>,
pub getfn: ext::Parens<Ident>,
}
#[derive(ParseStruct, ToTokensStruct, Debug)]
struct DeclStorageConfig {
pub config_keyword: ext::CustomToken<DeclStorageConfig>,
pub expr: ext::Parens<Option<syn::Ident>>,
}
#[derive(ParseStruct, ToTokensStruct, Debug)]
struct DeclStorageBuild {
pub build_keyword: ext::CustomToken<DeclStorageBuild>,
pub expr: ext::Parens<syn::Expr>,
}
#[derive(ParseEnum, ToTokensEnum, Debug)]
enum DeclStorageType {
Map(DeclStorageMap),
Simple(syn::Type),
}
#[derive(ParseStruct, ToTokensStruct, Debug)]
struct DeclStorageMap {
pub map_keyword: ext::CustomToken<MapKeyword>,
pub key: syn::Type,
pub ass_keyword: Token![=>],
pub value: syn::Type,
}
#[derive(ParseStruct, ToTokensStruct, Debug)]
struct DeclStorageDefault {
pub equal_token: Token![=],
pub expr: syn::Expr,
}
custom_keyword_impl!(SpecificHiddenCrate, "hiddencrate", "hiddencrate as keyword");
custom_keyword_impl!(DeclStorageConfig, "config", "build as keyword");
custom_keyword!(ConfigKeyword, "config", "config as keyword");
custom_keyword!(BuildKeyword, "build", "build as keyword");
custom_keyword_impl!(DeclStorageBuild, "build", "storage build config");
custom_keyword_impl!(AddExtraGenesis, "add_extra_genesis", "storage extra genesis");
custom_keyword_impl!(DeclStorageGetter, "get", "storage getter");
custom_keyword!(MapKeyword, "map", "map as keyword");
@@ -0,0 +1,636 @@
// Copyright 2017-2018 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/>.
// tag::description[]
//! `decl_storage` macro transformation
// end::description[]
use srml_support_procedural_tools::syn_ext as ext;
use srml_support_procedural_tools::{generate_crate_access, generate_hidden_includes};
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use syn::{
Ident,
GenericParam,
spanned::Spanned,
parse::{
Error,
Result,
}
};
use super::*;
// try macro but returning tokenized error
macro_rules! try_tok(( $expre : expr ) => {
match $expre {
Ok(r) => r,
Err (err) => {
return err.to_compile_error().into()
}
}
});
pub fn decl_storage_impl(input: TokenStream) -> TokenStream {
let def = parse_macro_input!(input as StorageDefinition);
// old macro naming convention (s replaces $)
let StorageDefinition {
hidden_crate,
visibility,
ident: storetype,
module_ident,
mod_param: strait,
crate_ident: cratename,
content: ext::Braces { content: storage_lines, ..},
extra_genesis,
..
} = def;
let hidden_crate_name = hidden_crate.map(|rc| rc.ident.content).map(|i| i.to_string())
.unwrap_or_else(|| "decl_storage".to_string());
let scrate = generate_crate_access(&hidden_crate_name, "srml-support");
let scrate_decl = generate_hidden_includes(
&hidden_crate_name,
"srml-support",
"srml_support",
);
let (
traitinstance,
traittypes,
) = if let GenericParam::Type(syn::TypeParam {ident, bounds, ..}) = strait {
(ident, bounds)
} else {
return try_tok!(Err(Error::new(strait.span(), "Missing declare store generic params")));
};
let traittype = if let Some(traittype) = traittypes.first() {
traittype.into_value()
} else {
return try_tok!(Err(Error::new(traittypes.span(), "Trait bound expected")));
};
let extra_genesis = try_tok!(decl_store_extra_genesis(
&scrate,
&traitinstance,
&traittype,
&storage_lines,
&extra_genesis,
));
let decl_storage_items = decl_storage_items(
&scrate,
&traitinstance,
&traittype,
&cratename,
&storage_lines,
);
let decl_store_items = decl_store_items(
&storage_lines,
);
let impl_store_items = impl_store_items(
&traitinstance,
&storage_lines,
);
let impl_store_fns = impl_store_fns(
&scrate,
&traitinstance,
&storage_lines,
);
let store_functions_to_metadata = store_functions_to_metadata(
&scrate,
&storage_lines,
);
let cratename_string = cratename.to_string();
let expanded = quote! {
#scrate_decl
#decl_storage_items
#visibility trait #storetype {
#decl_store_items
}
impl<#traitinstance: #traittype> #storetype for #module_ident<#traitinstance> {
#impl_store_items
}
impl<#traitinstance: #traittype> #module_ident<#traitinstance> {
#impl_store_fns
pub fn store_metadata() -> #scrate::storage::generator::StorageMetadata {
#scrate::storage::generator::StorageMetadata {
prefix: #scrate::storage::generator::DecodeDifferent::Encode(#cratename_string),
functions: #store_functions_to_metadata ,
}
}
}
#extra_genesis
};
expanded.into()
}
fn decl_store_extra_genesis(
scrate: &TokenStream2,
traitinstance: &Ident,
traittype: &syn::TypeParamBound,
storage_lines: &ext::Punctuated<DeclStorageLine, Token![;]>,
extra_genesis: &Option<AddExtraGenesis>,
) -> Result<TokenStream2> {
let mut is_trait_needed = false;
let mut has_trait_field = false;
let mut config_field = TokenStream2::new();
let mut config_field_default = TokenStream2::new();
let mut builders = TokenStream2::new();
for sline in storage_lines.inner.iter() {
let DeclStorageLine {
name,
getter,
config,
build,
storage_type,
default_value,
..
} = sline;
let is_simple = if let DeclStorageType::Simple(..) = storage_type { true } else { false };
let mut opt_build;
// need build line
if let (Some(ref getter), Some(ref config)) = (getter, config) {
let ident = if let Some(ident) = config.expr.content.as_ref() {
quote!( #ident )
} else {
let ident = &getter.getfn.content;
quote!( #ident )
};
let option_extracteed = if let DeclStorageType::Simple(ref st) = storage_type {
if ext::has_parametric_type(st, traitinstance) {
is_trait_needed = true;
has_trait_field = true;
}
ext::extract_type_option(st)
} else { None };
let is_option = option_extracteed.is_some();
let storage_type = option_extracteed.unwrap_or_else(|| quote!( #storage_type ));
config_field.extend(quote!( pub #ident: #storage_type, ));
opt_build = Some(build.as_ref().map(|b| &b.expr.content).map(|b|quote!( #b ))
.unwrap_or_else(|| quote!( (|config: &GenesisConfig<#traitinstance>| config.#ident.clone()) )));
let fielddefault = default_value.inner.get(0).as_ref().map(|d| &d.expr).map(|d|
if is_option {
quote!( #d.unwrap_or_default() )
} else {
quote!( #d )
}).unwrap_or_else(|| quote!( Default::default() ));
config_field_default.extend(quote!( #ident: #fielddefault, ));
} else {
opt_build = build.as_ref().map(|b| &b.expr.content).map(|b| quote!( #b ));
}
if let Some(builder) = opt_build {
is_trait_needed = true;
if is_simple {
builders.extend(quote!{{
use #scrate::codec::Encode;
let v = (#builder)(&self);
r.insert(Self::hash(<#name<#traitinstance>>::key()).to_vec(), v.encode());
}});
} else {
builders.extend(quote!{{
use #scrate::codec::Encode;
let data = (#builder)(&self);
for (k, v) in data.into_iter() {
r.insert(Self::hash(&<#name<#traitinstance>>::key_for(k)).to_vec(), v.encode());
}
}});
}
}
}
let mut has_scall = false;
let mut scall = quote!{ ( |_, _, _| {} ) };
let mut genesis_extrafields = TokenStream2::new();
let mut genesis_extrafields_default = TokenStream2::new();
// extra genesis
if let Some(eg) = extra_genesis {
for ex_content in eg.content.content.lines.inner.iter() {
match ex_content {
AddExtraGenesisLineEnum::AddExtraGenesisLine(AddExtraGenesisLine {
attrs,
extra_field,
extra_type,
default_value,
..
}) => {
if ext::has_parametric_type(&extra_type, traitinstance) {
is_trait_needed = true;
has_trait_field = true;
}
let extrafield = &extra_field.content;
genesis_extrafields.extend(quote!{
#attrs pub #extrafield: #extra_type,
});
let extra_default = default_value.inner.get(0).map(|d| &d.expr).map(|e| quote!{ #e })
.unwrap_or_else(|| quote!( Default::default() ));
genesis_extrafields_default.extend(quote!{
#extrafield: #extra_default,
});
},
AddExtraGenesisLineEnum::AddExtraGenesisBuild(DeclStorageBuild{ expr, .. }) => {
if has_scall {
return Err(Error::new(expr.span(), "Only one build expression allowed for extra genesis"));
}
let content = &expr.content;
scall = quote!( ( #content ) );
has_scall = true;
},
}
}
}
let is_extra_genesis_needed = has_scall
|| !config_field.is_empty()
|| !genesis_extrafields.is_empty()
|| !builders.is_empty();
Ok(if is_extra_genesis_needed {
let (fparam, sparam, ph_field, ph_default) = if is_trait_needed {
if has_trait_field {
// no phantom data required
(
quote!(<#traitinstance: #traittype>),
quote!(<#traitinstance>),
quote!(),
quote!(),
)
} else {
// need phantom data
(
quote!(<#traitinstance: #traittype>),
quote!(<#traitinstance>),
quote!{
#[serde(skip)]
pub _genesis_phantom_data: #scrate::storage::generator::PhantomData<#traitinstance>,
},
quote!{
_genesis_phantom_data: Default::default(),
},
)
}
} else {
// do not even need type parameter
(quote!(), quote!(), quote!(), quote!())
};
quote!{
#[derive(Serialize, Deserialize)]
#[cfg(feature = "std")]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub struct GenesisConfig#fparam {
#ph_field
#config_field
#genesis_extrafields
}
#[cfg(feature = "std")]
impl#fparam Default for GenesisConfig#sparam {
fn default() -> Self {
GenesisConfig {
#ph_default
#config_field_default
#genesis_extrafields_default
}
}
}
#[cfg(feature = "std")]
impl#fparam #scrate::runtime_primitives::BuildStorage for GenesisConfig#sparam {
fn build_storage(self) -> ::std::result::Result<(#scrate::runtime_primitives::StorageMap, #scrate::runtime_primitives::ChildrenStorageMap), String> {
let mut r: #scrate::runtime_primitives::StorageMap = Default::default();
let mut c: #scrate::runtime_primitives::ChildrenStorageMap = Default::default();
#builders
#scall(&mut r, &mut c, &self);
Ok((r, c))
}
}
}
} else {
quote!()
})
}
fn decl_storage_items(
scrate: &TokenStream2,
traitinstance: &Ident,
traittype: &syn::TypeParamBound,
cratename: &Ident,
storage_lines: &ext::Punctuated<DeclStorageLine, Token![;]>,
) -> TokenStream2 {
let mut impls = TokenStream2::new();
for sline in storage_lines.inner.iter() {
let DeclStorageLine {
name,
storage_type,
default_value,
visibility,
..
} = sline;
let (is_simple, extracted_opt, stk, gettype) = match storage_type {
DeclStorageType::Simple(ref st) => (true, ext::extract_type_option(st), None, st),
DeclStorageType::Map(ref map) => (false, ext::extract_type_option(&map.value), Some(&map.key), &map.value),
};
let is_option = extracted_opt.is_some();
let fielddefault = default_value.inner.get(0).as_ref().map(|d| &d.expr).map(|d| quote!( #d ))
.unwrap_or_else(|| quote!{ Default::default() });
let typ = extracted_opt.unwrap_or(quote!( #gettype ));
let option_simple_1 = if !is_option {
// raw type case
quote!( unwrap_or_else )
} else {
// Option<> type case
quote!( or_else )
};
let implementation = if is_simple {
let mutate_impl = if !is_option {
quote!{
<Self as #scrate::storage::generator::StorageValue<#typ>>::put(&val, storage)
}
} else {
quote!{
match val {
Some(ref val) => <Self as #scrate::storage::generator::StorageValue<#typ>>::put(&val, storage),
None => <Self as #scrate::storage::generator::StorageValue<#typ>>::kill(storage),
}
}
};
let key_string = cratename.to_string() + " " + &name.to_string();
// generator for value
quote!{
#visibility struct #name<#traitinstance: #traittype>(#scrate::storage::generator::PhantomData<#traitinstance>);
impl<#traitinstance: #traittype> #scrate::storage::generator::StorageValue<#typ> for #name<#traitinstance> {
type Query = #gettype;
/// Get the storage key.
fn key() -> &'static [u8] {
#key_string.as_bytes()
}
/// Load the value from the provided storage instance.
fn get<S: #scrate::GenericStorage>(storage: &S) -> Self::Query {
storage.get(<#name<#traitinstance> as #scrate::storage::generator::StorageValue<#typ>>::key())
.#option_simple_1(|| #fielddefault)
}
/// Take a value from storage, removing it afterwards.
fn take<S: #scrate::GenericStorage>(storage: &S) -> Self::Query {
storage.take(<#name<#traitinstance> as #scrate::storage::generator::StorageValue<#typ>>::key())
.#option_simple_1(|| #fielddefault)
}
/// Mutate the value under a key.
fn mutate<R, F: FnOnce(&mut Self::Query) -> R, S: #scrate::GenericStorage>(f: F, storage: &S) -> R {
let mut val = <Self as #scrate::storage::generator::StorageValue<#typ>>::get(storage);
let ret = f(&mut val);
#mutate_impl ;
ret
}
}
}
} else {
let kty = stk.expect("is not simple; qed");
let mutate_impl = if !is_option {
quote!{
<Self as #scrate::storage::generator::StorageMap<#kty, #typ>>::insert(key, &val, storage)
}
} else {
quote!{
match val {
Some(ref val) => <Self as #scrate::storage::generator::StorageMap<#kty, #typ>>::insert(key, &val, storage),
None => <Self as #scrate::storage::generator::StorageMap<#kty, #typ>>::remove(key, storage),
}
}
};
let prefix_string = cratename.to_string() + " " + &name.to_string();
// generator for map
quote!{
#visibility struct #name<#traitinstance: #traittype>(#scrate::storage::generator::PhantomData<#traitinstance>);
impl<#traitinstance: #traittype> #scrate::storage::generator::StorageMap<#kty, #typ> for #name<#traitinstance> {
type Query = #gettype;
/// Get the prefix key in storage.
fn prefix() -> &'static [u8] {
#prefix_string.as_bytes()
}
/// 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 = <#name<#traitinstance> as #scrate::storage::generator::StorageMap<#kty, #typ>>::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::GenericStorage>(key: &#kty, storage: &S) -> Self::Query {
let key = <#name<#traitinstance> as #scrate::storage::generator::StorageMap<#kty, #typ>>::key_for(key);
storage.get(&key[..]).#option_simple_1(|| #fielddefault)
}
/// Take the value, reading and removing it.
fn take<S: #scrate::GenericStorage>(key: &#kty, storage: &S) -> Self::Query {
let key = <#name<#traitinstance> as #scrate::storage::generator::StorageMap<#kty, #typ>>::key_for(key);
storage.take(&key[..]).#option_simple_1(|| #fielddefault)
}
/// Mutate the value under a key
fn mutate<R, F: FnOnce(&mut Self::Query) -> R, S: #scrate::GenericStorage>(key: &#kty, f: F, storage: &S) -> R {
let mut val = <Self as #scrate::storage::generator::StorageMap<#kty, #typ>>::take(key, storage);
let ret = f(&mut val);
#mutate_impl ;
ret
}
}
}
};
impls.extend(implementation)
}
impls
}
fn decl_store_items(
storage_lines: &ext::Punctuated<DeclStorageLine, Token![;]>,
) -> TokenStream2 {
storage_lines.inner.iter().map(|sline| &sline.name)
.fold(TokenStream2::new(), |mut items, name| {
items.extend(quote!(type #name;));
items
})
}
fn impl_store_items(
traitinstance: &Ident,
storage_lines: &ext::Punctuated<DeclStorageLine, Token![;]>,
) -> TokenStream2 {
storage_lines.inner.iter().map(|sline| &sline.name)
.fold(TokenStream2::new(), |mut items, name| {
items.extend(quote!(type #name = #name<#traitinstance>;));
items
})
}
fn impl_store_fns(
scrate: &TokenStream2,
traitinstance: &Ident,
storage_lines: &ext::Punctuated<DeclStorageLine, Token![;]>,
) -> TokenStream2 {
let mut items = TokenStream2::new();
for sline in storage_lines.inner.iter() {
let DeclStorageLine {
name,
getter,
storage_type,
..
} = sline;
if let Some(getter) = getter {
let get_fn = &getter.getfn.content;
let (is_simple, extracted_opt, stk, gettype) = match storage_type {
DeclStorageType::Simple(ref st) => (true, ext::extract_type_option(st), None, st),
DeclStorageType::Map(ref map) => (false, ext::extract_type_option(&map.value), Some(&map.key), &map.value),
};
let typ = extracted_opt.unwrap_or(quote!(#gettype));
let item = if is_simple {
quote!{
pub fn #get_fn() -> #gettype {
<#name<#traitinstance> as #scrate::storage::generator::StorageValue<#typ>> :: get(&#scrate::storage::RuntimeStorage)
}
}
} else {
let kty = stk.expect("is not simple; qed");
// map
quote!{
pub fn #get_fn<K: #scrate::storage::generator::Borrow<#kty>>(key: K) -> #gettype {
<#name<#traitinstance> as #scrate::storage::generator::StorageMap<#kty, #typ>> :: get(key.borrow(), &#scrate::storage::RuntimeStorage)
}
}
};
items.extend(item);
}
}
items
}
fn store_functions_to_metadata (
scrate: &TokenStream2,
storage_lines: &ext::Punctuated<DeclStorageLine, Token![;]>,
) -> TokenStream2 {
let mut items = TokenStream2::new();
for sline in storage_lines.inner.iter() {
let DeclStorageLine {
attrs,
name,
storage_type,
..
} = sline;
let (is_simple, extracted_opt, stk, gettype) = match storage_type {
DeclStorageType::Simple(ref st) => (true, ext::extract_type_option(st), None, st),
DeclStorageType::Map(ref map) => (false, ext::extract_type_option(&map.value), Some(&map.key), &map.value),
};
let is_option = extracted_opt.is_some();
let typ = extracted_opt.unwrap_or(quote!( #gettype ));
let stype = if is_simple {
let styp = typ.to_string().replace(" ","");
quote!{
#scrate::storage::generator::StorageFunctionType::Plain(
#scrate::storage::generator::DecodeDifferent::Encode(#styp),
)
}
} else {
let kty = stk.expect("is not simple; qed");
let kty = quote!(#kty).to_string();
let styp = typ.to_string().replace(" ","");
quote!{
#scrate::storage::generator::StorageFunctionType::Map {
key: #scrate::storage::generator::DecodeDifferent::Encode(#kty),
value: #scrate::storage::generator::DecodeDifferent::Encode(#styp),
}
}
};
let modifier = if !is_option {
quote!{
#scrate::storage::generator::StorageFunctionModifier::Default
}
} else {
quote!{
#scrate::storage::generator::StorageFunctionModifier::Optional
}
};
let mut docs = TokenStream2::new();
for attr in attrs.inner.iter().filter_map(|v| v.interpret_meta()) {
if let syn::Meta::NameValue(syn::MetaNameValue{
ref ident,
ref lit,
..
}) = attr {
if ident == "doc" {
docs.extend(quote!(#lit,));
}
}
}
let str_name = name.to_string();
let item = quote! {
#scrate::storage::generator::StorageFunctionMetadata {
name: #scrate::storage::generator::DecodeDifferent::Encode(#str_name),
modifier: #modifier,
ty: #stype,
documentation: #scrate::storage::generator::DecodeDifferent::Encode(&[ #docs ]),
},
};
items.extend(item);
}
quote!{
#scrate::storage::generator::DecodeDifferent::Encode(&[
#items
])
}
}
@@ -0,0 +1,10 @@
[package]
name = "srml-support-procedural-tools"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
[dependencies]
srml-support-procedural-tools-derive = { path = "./derive" }
proc-macro2 = "0.4"
quote = { version = "0.6" }
syn = { version = "0.15", features = ["full"] }
@@ -0,0 +1,12 @@
[package]
name = "srml-support-procedural-tools-derive"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
[lib]
proc-macro = true
[dependencies]
proc-macro2 = "0.4.24"
quote = { version = "0.6.10", features = ["proc-macro"] }
syn = { version = "0.15.21", features = ["proc-macro" ,"full", "extra-traits", "parsing"] }
@@ -0,0 +1,229 @@
// Copyright 2017-2018 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/>.
// tag::description[]
//! Use to derive parsing for parsing struct.
// end::description[]
#![recursion_limit = "128"]
#[macro_use]
extern crate syn;
#[macro_use]
extern crate quote;
extern crate proc_macro;
extern crate proc_macro2;
use proc_macro::TokenStream;
use proc_macro2::Span;
pub(crate) fn fields_idents(
fields: impl Iterator<Item = syn::Field>,
) -> impl Iterator<Item = proc_macro2::TokenStream> {
fields.enumerate().map(|(ix, field)| {
field.ident.clone().map(|i| quote!{#i}).unwrap_or_else(|| {
let f_ix: syn::Ident = syn::Ident::new(&format!("f_{}", ix), Span::call_site());
quote!( #f_ix )
})
})
}
pub(crate) fn fields_access(
fields: impl Iterator<Item = syn::Field>,
) -> impl Iterator<Item = proc_macro2::TokenStream> {
fields.enumerate().map(|(ix, field)| {
field.ident.clone().map(|i| quote!( #i )).unwrap_or_else(|| {
let f_ix: syn::Index = syn::Index {
index: ix as u32,
span: Span::call_site(),
};
quote!( #f_ix )
})
})
}
/// self defined parsing struct (use where clause on struct for it: not meant for good struct
/// design but fast parse impl).
#[proc_macro_derive(ParseStruct)]
pub fn derive_parse_struct(input: TokenStream) -> TokenStream {
let syn::ItemStruct {
ident,
generics,
fields,
..
} = parse_macro_input!(input as syn::ItemStruct);
let field_names = {
let name = fields_idents(fields.iter().map(Clone::clone));
quote!{
#(
#name,
)*
}
};
let field = fields_idents(fields.iter().map(Clone::clone));
let tokens = quote! {
impl #generics syn::parse::Parse for #ident #generics {
fn parse(input: syn::parse::ParseStream) -> syn::parse::Result<Self> {
#(
let #field = input.parse()?;
)*
Ok(Self {
#field_names
})
}
}
};
tokens.into()
}
#[proc_macro_derive(ToTokensStruct)]
pub fn derive_totokens_struct(input: TokenStream) -> TokenStream {
let syn::ItemStruct {
ident,
generics,
fields,
..
} = parse_macro_input!(input as syn::ItemStruct);
let fields = fields_access(fields.iter().map(Clone::clone));
let tokens = quote! {
impl #generics quote::ToTokens for #ident #generics {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
#(
self.#fields.to_tokens(tokens);
)*
}
}
};
tokens.into()
}
/// self defined parsing enum, variant are tested in order of definition.
/// Empty variant allways true.
/// Please use carefully, this will fully parse successfull variant twice.
#[proc_macro_derive(ParseEnum)]
pub fn derive_parse_enum(input: TokenStream) -> TokenStream {
let syn::ItemEnum {
ident,
generics,
variants,
..
} = parse_macro_input!(input as syn::ItemEnum);
let variants = variants.iter().map(|v| {
let variant_ident = v.ident.clone();
let fields_build = if v.fields.iter().count() > 0 {
let fields_id = fields_idents(v.fields.iter().map(Clone::clone));
quote!( (#(#fields_id), *) )
} else {
quote!()
};
let fields_procs = fields_idents(v.fields.iter().map(Clone::clone))
.map(|fident| {
quote!{
let mut #fident = match fork.parse() {
Ok(r) => r,
Err(_e) => break,
};
}
});
let fields_procs_again = fields_idents(v.fields.iter().map(Clone::clone))
.map(|fident| {
quote!{
#fident = input.parse().expect("was parsed just before");
}
});
// double parse to update input cursor position
// next syn crate version should be checked for a way
// to copy position/state from a fork
quote!{
let mut fork = input.fork();
loop {
#(#fields_procs)*
#(#fields_procs_again)*
return Ok(#ident::#variant_ident#fields_build);
}
}
});
let tokens = quote! {
impl #generics syn::parse::Parse for #ident #generics {
fn parse(input: syn::parse::ParseStream) -> syn::parse::Result<Self> {
#(
#variants
)*
// no early return from any variants
Err(
syn::parse::Error::new(
proc_macro2::Span::call_site(),
"derived enum no matching variants"
)
)
}
}
};
tokens.into()
}
/// only output field (empty field act as a None)
#[proc_macro_derive(ToTokensEnum)]
pub fn derive_totokens_enum(input: TokenStream) -> TokenStream {
let syn::ItemEnum {
ident,
generics,
variants,
..
} = parse_macro_input!(input as syn::ItemEnum);
let variants = variants.iter().map(|v| {
let v_ident = v.ident.clone();
let fields_build = if v.fields.iter().count() > 0 {
let fields_id = fields_idents(v.fields.iter().map(Clone::clone));
quote!( (#(#fields_id), *) )
} else {
quote!()
};
let field = fields_idents(v.fields.iter().map(Clone::clone));
quote! {
#ident::#v_ident#fields_build => {
#(
#field.to_tokens(tokens);
)*
},
}
});
let tokens = quote! {
impl #generics quote::ToTokens for #ident #generics {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
match self {
#(
#variants
)*
}
}
}
};
tokens.into()
}
@@ -0,0 +1,94 @@
// Copyright 2017-2018 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/>.
// tag::description[]
//! Proc macro helpers for procedural macros
// end::description[]
extern crate syn;
#[macro_use]
extern crate quote;
extern crate proc_macro2;
extern crate proc_macro;
#[macro_use] extern crate srml_support_procedural_tools_derive;
// reexport proc macros
pub use srml_support_procedural_tools_derive::*;
pub mod syn_ext;
#[macro_export]
macro_rules! custom_keyword_impl {
($name:ident, $keyident:expr, $keydisp:expr) => {
impl CustomKeyword for $name {
fn ident() -> &'static str { $keyident }
fn display() -> &'static str { $keydisp }
}
}
}
#[macro_export]
macro_rules! custom_keyword {
($name:ident, $keyident:expr, $keydisp:expr) => {
#[derive(Debug)]
struct $name;
custom_keyword_impl!($name, $keyident, $keydisp);
}
}
// TODO following functions are copied from sr-api-macros : do a merge to get a unique procedural
// macro tooling crate (this crate path does not look good for it)
use proc_macro2::{TokenStream, Span};
use syn::Ident;
fn generate_hidden_includes_mod_name(unique_id: &str) -> Ident {
Ident::new(&format!("sr_api_hidden_includes_{}", unique_id), Span::call_site())
}
/// Generates the access to the `subtrate_client` crate.
pub fn generate_crate_access(unique_id: &str, def_crate: &str) -> TokenStream {
if ::std::env::var("CARGO_PKG_NAME").unwrap() == def_crate {
quote!( crate )
} else {
let mod_name = generate_hidden_includes_mod_name(unique_id);
quote!( self::#mod_name::hidden_include )
}.into()
}
/// Generates the hidden includes that are required to make the macro independent from its scope.
pub fn generate_hidden_includes(unique_id: &str, def_crate: &str, crate_id: &str) -> TokenStream {
let crate_id = Ident::new(crate_id, Span::call_site());
if ::std::env::var("CARGO_PKG_NAME").unwrap() == def_crate {
TokenStream::new()
} else {
let mod_name = generate_hidden_includes_mod_name(unique_id);
quote!(
#[doc(hidden)]
mod #mod_name {
pub extern crate #crate_id as hidden_include;
}
)
}.into()
}
@@ -0,0 +1,336 @@
// Copyright 2017-2018 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/>.
// tag::description[]
//! Extension to syn types, mainly for parsing
// end::description[]
use syn::parse::{
Parse,
ParseStream,
Result,
};
use syn::token::CustomKeyword;
use proc_macro2::TokenStream as T2;
use quote::ToTokens;
use std::iter::once;
use syn::Ident;
/// stop parsing here getting remaining token as content
/// Warn duplicate stream (part of)
#[derive(ParseStruct, ToTokensStruct, Debug)]
pub struct StopParse {
pub inner: T2,
}
// inner macro really dependant on syn naming convention, do not export
macro_rules! groups_impl {
($name:ident, $tok:ident, $deli:ident, $parse:ident) => {
#[derive(Debug)]
pub struct $name<P> {
pub token: syn::token::$tok,
pub content: P,
}
impl<P: Parse> Parse for $name<P> {
fn parse(input: ParseStream) -> Result<Self> {
let syn::group::$name { token, content } = syn::group::$parse(input)?;
let content = content.parse()?;
Ok($name { token, content, })
}
}
impl<P: ToTokens> ToTokens for $name<P> {
fn to_tokens(&self, tokens: &mut T2) {
let mut inner_stream = T2::new();
self.content.to_tokens(&mut inner_stream);
let token_tree: proc_macro2::TokenTree =
proc_macro2::Group::new(proc_macro2::Delimiter::$deli, inner_stream).into();
tokens.extend(once(token_tree));
}
}
}
}
groups_impl!(Braces, Brace, Brace, parse_braces);
groups_impl!(Brackets, Bracket, Bracket, parse_brackets);
groups_impl!(Parens, Paren, Parenthesis, parse_parens);
#[derive(Debug)]
pub struct CustomToken<T>(std::marker::PhantomData<T>);
impl<T: CustomKeyword> Parse for CustomToken<T> {
fn parse(input: ParseStream) -> Result<Self> {
let ident: syn::Ident = input.parse()?;
if ident.to_string().as_str() != T::ident() {
return Err(syn::parse::Error::new_spanned(ident, "expected another custom token"))
}
Ok(CustomToken(std::marker::PhantomData))
}
}
impl<T: CustomKeyword> ToTokens for CustomToken<T> {
fn to_tokens(&self, tokens: &mut T2) {
use std::str::FromStr;
tokens.extend(T2::from_str(T::ident()).expect("custom keyword should parse to ident"));
}
}
impl<T: CustomKeyword> CustomKeyword for CustomToken<T> {
fn ident() -> &'static str { <T as CustomKeyword>::ident() }
fn display() -> &'static str { <T as CustomKeyword>::display() }
}
#[derive(Debug)]
pub struct PunctuatedInner<P,T,V> {
pub inner: syn::punctuated::Punctuated<P,T>,
pub variant: V,
}
#[derive(Debug)]
pub struct NoTrailing;
#[derive(Debug)]
pub struct Trailing;
pub type Punctuated<P,T> = PunctuatedInner<P,T,NoTrailing>;
pub type PunctuatedTrailing<P,T> = PunctuatedInner<P,T,Trailing>;
impl<P: Parse, T: Parse + syn::token::Token> Parse for PunctuatedInner<P,T,Trailing> {
fn parse(input: ParseStream) -> Result<Self> {
Ok(PunctuatedInner {
inner: syn::punctuated::Punctuated::parse_separated_nonempty(input)?,
variant: Trailing,
})
}
}
impl<P: Parse, T: Parse> Parse for PunctuatedInner<P,T,NoTrailing> {
fn parse(input: ParseStream) -> Result<Self> {
Ok(PunctuatedInner {
inner: syn::punctuated::Punctuated::parse_terminated(input)?,
variant: NoTrailing,
})
}
}
impl<P: ToTokens, T: ToTokens, V> ToTokens for PunctuatedInner<P,T,V> {
fn to_tokens(&self, tokens: &mut T2) {
self.inner.to_tokens(tokens)
}
}
/// Note that syn Meta is almost fine for use case (lacks only `ToToken`)
#[derive(Debug, Clone)]
pub struct Meta {
pub inner: syn::Meta,
}
impl Parse for Meta {
fn parse(input: ParseStream) -> Result<Self> {
Ok(Meta {
inner: syn::Meta::parse(input)?,
})
}
}
impl ToTokens for Meta {
fn to_tokens(&self, tokens: &mut T2) {
match self.inner {
syn::Meta::Word(ref ident) => {
let ident = ident.clone();
let toks = quote!{
#[#ident]
};
tokens.extend(toks);
},
syn::Meta::List(ref l) => l.to_tokens(tokens),
syn::Meta::NameValue(ref n) => n.to_tokens(tokens),
}
}
}
#[derive(Debug)]
pub struct OuterAttributes {
pub inner: Vec<syn::Attribute>,
}
impl Parse for OuterAttributes {
fn parse(input: ParseStream) -> Result<Self> {
let inner = syn::Attribute::parse_outer(input)?;
Ok(OuterAttributes {
inner,
})
}
}
impl ToTokens for OuterAttributes {
fn to_tokens(&self, tokens: &mut T2) {
for att in self.inner.iter() {
att.to_tokens(tokens);
}
}
}
#[derive(Debug)]
pub struct Seq<P> {
pub inner: Vec<P>,
}
impl<P: Parse> Parse for Seq<P> {
// Note that it cost a double parsing (same as enum derive)
fn parse(input: ParseStream) -> Result<Self> {
let mut inner = Vec::new();
loop {
let fork = input.fork();
let res: Result<P> = fork.parse();
match res {
Ok(_item) => {
// move cursor
let item: P = input.parse().expect("Same parsing ran before");
inner.push(item);
},
Err(_e) => break,
}
}
Ok(Seq { inner })
}
}
impl<P: ToTokens> ToTokens for Seq<P> {
fn to_tokens(&self, tokens: &mut T2) {
for p in self.inner.iter() {
p.to_tokens(tokens);
}
}
}
pub fn extract_type_option(typ: &syn::Type) -> Option<T2> {
if let syn::Type::Path(ref path) = typ {
path.path.segments.last().and_then(|v| {
if v.value().ident == "Option" {
if let syn::PathArguments::AngleBracketed(ref a) = v.value().arguments {
let args = &a.args;
Some(quote!{ #args })
} else {
None
}
} else {
None
}
})
} else {
None
}
}
pub fn is_parametric_type_def(typ: &syn::Type, default: bool) -> bool {
match *typ {
syn::Type::Path(ref path) => {
path.path.segments.iter().any(|v| {
if let syn::PathArguments::AngleBracketed(..) = v.arguments {
true
} else {
false
}
})
},
syn::Type::Slice(ref inner) => is_parametric_type_def(&inner.elem, default),
syn::Type::Array(ref inner) => is_parametric_type_def(&inner.elem, default),
syn::Type::Ptr(ref inner) => is_parametric_type_def(&inner.elem, default),
syn::Type::Reference(ref inner) => is_parametric_type_def(&inner.elem, default),
syn::Type::BareFn(ref inner) => inner.variadic.is_some(),
syn::Type::Never(..) => false,
syn::Type::Tuple(ref inner) =>
inner.elems.iter().any(|t| is_parametric_type_def(t, default)),
syn::Type::TraitObject(..) => true,
syn::Type::ImplTrait(..) => true,
syn::Type::Paren(ref inner) => is_parametric_type_def(&inner.elem, default),
syn::Type::Group(ref inner) => is_parametric_type_def(&inner.elem, default),
syn::Type::Infer(..) => true,
syn::Type::Macro(..) => default,
syn::Type::Verbatim(..) => default,
}
}
/// check if type has any type parameter, defaults to true for some cases.
pub fn is_parametric_type(typ: &syn::Type) -> bool {
is_parametric_type_def(typ, true)
}
fn has_parametric_type_def_in_path(path: &syn::Path, ident: &Ident, default: bool) -> bool {
path.segments.iter().any(|v| {
if ident == &v.ident {
return true;
}
if let syn::PathArguments::AngleBracketed(ref a) = v.arguments {
for arg in a.args.iter() {
if let syn::GenericArgument::Type(ref typ) = arg {
if has_parametric_type_def(typ, ident, default) {
return true;
}
}
// potentially missing matches here
}
false
} else {
false
}
})
}
pub fn has_parametric_type_def(typ: &syn::Type, ident: &Ident, default: bool) -> bool {
match *typ {
syn::Type::Path(ref path) => has_parametric_type_def_in_path(&path.path, ident, default),
syn::Type::Slice(ref inner) => has_parametric_type_def(&inner.elem, ident, default),
syn::Type::Array(ref inner) => has_parametric_type_def(&inner.elem, ident, default),
syn::Type::Ptr(ref inner) => has_parametric_type_def(&inner.elem, ident, default),
syn::Type::Reference(ref inner) => has_parametric_type_def(&inner.elem, ident, default),
syn::Type::BareFn(ref inner) => inner.variadic.is_some(),
syn::Type::Never(..) => false,
syn::Type::Tuple(ref inner) =>
inner.elems.iter().any(|t| has_parametric_type_def(t, ident, default)),
syn::Type::TraitObject(ref to) => {
to.bounds.iter().any(|bound| {
if let syn::TypeParamBound::Trait(ref t) = bound {
has_parametric_type_def_in_path(&t.path, ident, default)
} else { false }
})
},
syn::Type::ImplTrait(ref it) => {
it.bounds.iter().any(|bound| {
if let syn::TypeParamBound::Trait(ref t) = bound {
has_parametric_type_def_in_path(&t.path, ident, default)
} else { false }
})
},
syn::Type::Paren(ref inner) => has_parametric_type_def(&inner.elem, ident, default),
syn::Type::Group(ref inner) => has_parametric_type_def(&inner.elem, ident, default),
syn::Type::Infer(..) => default,
syn::Type::Macro(..) => default,
syn::Type::Verbatim(..) => default,
}
}
/// check if type has a type parameter, defaults to true for some cases.
pub fn has_parametric_type(typ: &syn::Type, ident: &Ident) -> bool {
has_parametric_type_def(typ, ident, true)
}
+6
View File
@@ -31,6 +31,9 @@ extern crate srml_metadata;
extern crate mashup;
#[macro_use]
extern crate srml_support_procedural;
#[cfg(test)]
#[macro_use]
extern crate pretty_assertions;
@@ -67,6 +70,9 @@ pub use self::dispatch::{Parameter, Dispatchable, Callable, IsSubType};
pub use self::metadata::RuntimeMetadata;
pub use runtime_io::print;
#[doc(inline)]
pub use srml_support_procedural::decl_storage;
#[macro_export]
macro_rules! fail {
( $y:expr ) => {{
-1
View File
@@ -188,7 +188,6 @@ mod tests {
StorageMethod : Option<u32>;
}
add_extra_genesis {
config(_marker) : ::std::marker::PhantomData<T>;
build(|_, _, _| {});
}
}
File diff suppressed because it is too large Load Diff