fix: Complete snowbridge pezpallet rebrand and critical bug fixes

- snowbridge-pezpallet-* → pezsnowbridge-pezpallet-* (201 refs)
- pallet/ directories → pezpallet/ (4 locations)
- Fixed pezpallet.rs self-include recursion bug
- Fixed sc-chain-spec hardcoded crate name in derive macro
- Reverted .pezpallet_by_name() to .pallet_by_name() (subxt API)
- Added BizinikiwiConfig type alias for zombienet tests
- Deleted obsolete session state files

Verified: pezsnowbridge-pezpallet-*, pezpallet-staking,
pezpallet-staking-async, pezframe-benchmarking-cli all pass cargo check
This commit is contained in:
2025-12-16 09:57:23 +03:00
parent eea003e14d
commit 3139ffa25e
3022 changed files with 42157 additions and 23579 deletions
@@ -0,0 +1,614 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::{
deprecation::extract_or_return_allow_attrs,
pezpallet::{
expand::warnings::{weight_constant_warning, weight_witness_warning},
parse::{
call::{CallVariantDef, CallWeightDef},
helper::CallReturnType,
},
Def,
},
COUNTER,
};
use proc_macro2::TokenStream as TokenStream2;
use proc_macro_warning::Warning;
use quote::{quote, ToTokens};
use syn::spanned::Spanned;
/// Expand the weight to final token stream and accumulate warnings.
fn expand_weight(
prefix: &str,
pezframe_support: &syn::Path,
dev_mode: bool,
weight_warnings: &mut Vec<Warning>,
method: &CallVariantDef,
weight: &CallWeightDef,
) -> TokenStream2 {
match weight {
CallWeightDef::DevModeDefault => quote::quote!(
#pezframe_support::pezpallet_prelude::Weight::zero()
),
CallWeightDef::Immediate(e) => {
weight_constant_warning(e, dev_mode, weight_warnings);
weight_witness_warning(method, dev_mode, weight_warnings);
e.into_token_stream()
},
CallWeightDef::Inherited(t) => {
// Expand `<<T as Config>::WeightInfo>::$prefix$call_name()`.
let n = &syn::Ident::new(&format!("{}{}", prefix, method.name), method.name.span());
quote!({ < #t > :: #n () })
},
}
}
///
/// * Generate enum call and implement various trait on it.
/// * Implement Callable and call_function on `Pezpallet`
pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream {
let (span, where_clause, methods, docs) = match def.call.as_ref() {
Some(call) => {
let span = call.attr_span;
let where_clause = call.where_clause.clone();
let methods = call.methods.clone();
let docs = call.docs.clone();
(span, where_clause, methods, docs)
},
None => (def.item.span(), def.config.where_clause.clone(), Vec::new(), Vec::new()),
};
let pezframe_support = &def.pezframe_support;
let pezframe_system = &def.pezframe_system;
let type_impl_gen = &def.type_impl_generics(span);
let type_decl_bounded_gen = &def.type_decl_bounded_generics(span);
let type_use_gen = &def.type_use_generics(span);
let call_ident = syn::Ident::new("Call", span);
let pezpallet_ident = &def.pezpallet_struct.pezpallet;
let fn_name = methods.iter().map(|method| &method.name).collect::<Vec<_>>();
let call_index = methods.iter().map(|method| method.call_index).collect::<Vec<_>>();
let new_call_variant_fn_name = fn_name
.iter()
.map(|fn_name| quote::format_ident!("new_call_variant_{}", fn_name))
.collect::<Vec<_>>();
let new_call_variant_doc = fn_name
.iter()
.map(|fn_name| format!("Create a call with the variant `{}`.", fn_name))
.collect::<Vec<_>>();
let mut call_index_warnings = Vec::new();
// Emit a warning for each call that is missing `call_index` when not in dev-mode.
for method in &methods {
if method.explicit_call_index || def.dev_mode {
continue;
}
let warning = Warning::new_deprecated("ImplicitCallIndex")
.index(call_index_warnings.len())
.old("use implicit call indices")
.new("ensure that all calls have a `pezpallet::call_index` attribute or put the pezpallet into `dev` mode")
.help_links(&[
"https://github.com/pezkuwichain/kurdistan-sdk/issues/39",
"https://github.com/pezkuwichain/kurdistan-sdk/issues/36"
])
.span(method.name.span())
.build_or_panic();
call_index_warnings.push(warning);
}
let mut fn_weight = Vec::<TokenStream2>::new();
let mut weight_warnings = Vec::new();
for method in &methods {
let w = expand_weight(
"",
pezframe_support,
def.dev_mode,
&mut weight_warnings,
method,
&method.weight,
);
fn_weight.push(w);
}
debug_assert_eq!(fn_weight.len(), methods.len());
let fn_doc = methods.iter().map(|method| &method.docs).collect::<Vec<_>>();
let args_name = methods
.iter()
.map(|method| method.args.iter().map(|(_, name, _)| name.clone()).collect::<Vec<_>>())
.collect::<Vec<_>>();
let args_name_stripped = methods
.iter()
.map(|method| {
method
.args
.iter()
.map(|(_, name, _)| {
syn::Ident::new(name.to_string().trim_start_matches('_'), name.span())
})
.collect::<Vec<_>>()
})
.collect::<Vec<_>>();
let make_args_name_pattern = |ref_tok| {
args_name
.iter()
.zip(args_name_stripped.iter())
.map(|(args_name, args_name_stripped)| {
args_name
.iter()
.zip(args_name_stripped)
.map(|(args_name, args_name_stripped)| {
if args_name == args_name_stripped {
quote::quote!( #ref_tok #args_name )
} else {
quote::quote!( #args_name_stripped: #ref_tok #args_name )
}
})
.collect::<Vec<_>>()
})
.collect::<Vec<_>>()
};
let args_name_pattern = make_args_name_pattern(None);
let args_name_pattern_ref = make_args_name_pattern(Some(quote::quote!(ref)));
let args_type = methods
.iter()
.map(|method| method.args.iter().map(|(_, _, type_)| type_.clone()).collect::<Vec<_>>())
.collect::<Vec<_>>();
let args_compact_attr = methods.iter().map(|method| {
method
.args
.iter()
.map(|(is_compact, _, type_)| {
if *is_compact {
quote::quote_spanned!(type_.span() => #[codec(compact)] )
} else {
quote::quote!()
}
})
.collect::<Vec<_>>()
});
let default_docs =
[syn::parse_quote!(r"Contains a variant per dispatchable extrinsic that this pezpallet has.")];
let docs = if docs.is_empty() { &default_docs[..] } else { &docs[..] };
let maybe_compile_error = if def.call.is_none() {
quote::quote! {
compile_error!(concat!(
"`",
stringify!($pezpallet_name),
"` does not have #[pezpallet::call] defined, perhaps you should remove `Call` from \
construct_runtime?",
));
}
} else {
proc_macro2::TokenStream::new()
};
let count = COUNTER.with(|counter| counter.borrow_mut().inc());
let macro_ident = syn::Ident::new(&format!("__is_call_part_defined_{}", count), span);
let capture_docs = if cfg!(feature = "no-metadata-docs") { "never" } else { "always" };
// Wrap all calls inside of storage layers
if let Some(call) = def.call.as_ref() {
let item_impl =
&mut def.item.content.as_mut().expect("Checked by def parser").1[call.index];
let syn::Item::Impl(item_impl) = item_impl else {
unreachable!("Checked by def parser");
};
item_impl.items.iter_mut().enumerate().for_each(|(i, item)| {
if let syn::ImplItem::Fn(method) = item {
let return_type =
&call.methods.get(i).expect("def should be consistent with item").return_type;
let (ok_type, err_type) = match return_type {
CallReturnType::DispatchResult => (
quote::quote!(()),
quote::quote!(#pezframe_support::pezpallet_prelude::DispatchError),
),
CallReturnType::DispatchResultWithPostInfo => (
quote::quote!(#pezframe_support::dispatch::PostDispatchInfo),
quote::quote!(#pezframe_support::dispatch::DispatchErrorWithPostInfo),
),
};
let block = &method.block;
method.block = syn::parse_quote! {{
// We execute all dispatchable in a new storage layer, allowing them
// to return an error at any point, and undoing any storage changes.
#pezframe_support::storage::with_storage_layer::<#ok_type, #err_type, _>(
|| #block
)
}};
}
});
}
// Extracts #[allow] attributes, necessary so that we don't run into compiler warnings
let maybe_allow_attrs = methods
.iter()
.map(|method| {
let attrs = extract_or_return_allow_attrs(&method.attrs);
quote::quote! {
#(#attrs)*
}
})
.collect::<Vec<_>>();
let cfg_attrs = methods
.iter()
.map(|method| {
let attrs =
method.cfg_attrs.iter().map(|attr| attr.to_token_stream()).collect::<Vec<_>>();
quote::quote!( #( #attrs )* )
})
.collect::<Vec<_>>();
let feeless_checks = methods.iter().map(|method| &method.feeless_check).collect::<Vec<_>>();
let feeless_check =
feeless_checks.iter().zip(args_name.iter()).map(|(feeless_check, arg_name)| {
if let Some(check) = feeless_check {
quote::quote_spanned!(span => #check)
} else {
quote::quote_spanned!(span => |_origin, #( #arg_name, )*| { false })
}
});
let deprecation = match crate::deprecation::get_deprecation_enum(
&quote::quote! {#pezframe_support},
methods.iter().map(|item| (item.call_index as u8, item.attrs.as_ref())),
) {
Ok(deprecation) => deprecation,
Err(e) => return e.into_compile_error(),
};
// Implementation of the authorize function for each call
// `authorize_fn_pallet_impl` writes the user-defined authorize function as a function
// implementation for the pezpallet.
// `authorize_impl` is the call to this former function to implement `Authorize` trait.
let (authorize_fn_pallet_impl, authorize_impl) = methods
.iter()
.zip(args_name.iter())
.zip(args_type.iter())
.zip(cfg_attrs.iter())
.map(|(((method, arg_name), arg_type), cfg_attr)| {
if let Some(authorize_def) = &method.authorize {
let authorize_fn = &authorize_def.expr;
let attr_fn_getter = syn::Ident::new(
&format!("__macro_inner_authorize_call_for_{}", method.name),
authorize_fn.span(),
);
let source = syn::Ident::new("source", span);
let authorize_fn_pallet_impl = quote::quote_spanned!(authorize_fn.span() =>
// Closure don't have a writable type. So we fix the authorize token stream to
// be any implementation of a specific function.
// This allows to have good type inference on the closure.
//
// Then we wrap this into an implementation for `Pezpallet` in order to get access
// to `Self` as `Pezpallet` instead of `Call`.
#cfg_attr
impl<#type_impl_gen> Pezpallet<#type_use_gen> #where_clause {
#[doc(hidden)]
fn #attr_fn_getter() -> impl Fn(
#pezframe_support::pezpallet_prelude::TransactionSource,
#( &#arg_type ),*
) -> #pezframe_support::pezpallet_prelude::TransactionValidityWithRefund {
#authorize_fn
}
}
);
// `source` is from outside this block, so we can't use the authorize_fn span.
let authorize_impl = quote::quote!(
{
let authorize_fn = Pezpallet::<#type_use_gen>::#attr_fn_getter();
let res = authorize_fn(#source, #( #arg_name, )*);
Some(res)
}
);
(authorize_fn_pallet_impl, authorize_impl)
} else {
(Default::default(), quote::quote!(None))
}
})
.unzip::<_, _, Vec<TokenStream2>, Vec<TokenStream2>>();
// Implementation of the authorize function weight for each call
let mut authorize_fn_weight = Vec::<TokenStream2>::new();
for method in &methods {
let w = match &method.authorize {
Some(authorize_def) => expand_weight(
"authorize_",
pezframe_support,
def.dev_mode,
&mut weight_warnings,
method,
&authorize_def.weight,
),
// No authorize logic, weight is negligible
None => quote::quote!(#pezframe_support::pezpallet_prelude::Weight::zero()),
};
authorize_fn_weight.push(w);
}
assert_eq!(authorize_fn_weight.len(), methods.len());
quote::quote_spanned!(span =>
#[doc(hidden)]
mod warnings {
#(
#call_index_warnings
)*
#(
#weight_warnings
)*
}
#[allow(unused_imports)]
#[doc(hidden)]
pub mod __bizinikiwi_call_check {
#[macro_export]
#[doc(hidden)]
macro_rules! #macro_ident {
($pezpallet_name:ident) => {
#maybe_compile_error
};
}
#[doc(hidden)]
pub use #macro_ident as is_call_part_defined;
}
#( #[doc = #docs] )*
#[derive(
#pezframe_support::RuntimeDebugNoBound,
#pezframe_support::CloneNoBound,
#pezframe_support::EqNoBound,
#pezframe_support::PartialEqNoBound,
#pezframe_support::__private::codec::Encode,
#pezframe_support::__private::codec::Decode,
#pezframe_support::__private::codec::DecodeWithMemTracking,
#pezframe_support::__private::scale_info::TypeInfo,
)]
#[codec(encode_bound())]
#[codec(decode_bound())]
#[scale_info(skip_type_params(#type_use_gen), capture_docs = #capture_docs)]
#[allow(non_camel_case_types)]
pub enum #call_ident<#type_decl_bounded_gen> #where_clause {
#[doc(hidden)]
#[codec(skip)]
__Ignore(
::core::marker::PhantomData<(#type_use_gen,)>,
#pezframe_support::Never,
),
#(
#cfg_attrs
#( #[doc = #fn_doc] )*
#[codec(index = #call_index)]
#fn_name {
#(
#[allow(missing_docs)]
#args_compact_attr #args_name_stripped: #args_type
),*
},
)*
}
impl<#type_impl_gen> #call_ident<#type_use_gen> #where_clause {
#(
#cfg_attrs
#[doc = #new_call_variant_doc]
pub fn #new_call_variant_fn_name(
#( #args_name_stripped: #args_type ),*
) -> Self {
Self::#fn_name {
#( #args_name_stripped ),*
}
}
)*
}
impl<#type_impl_gen> #pezframe_support::dispatch::GetDispatchInfo
for #call_ident<#type_use_gen>
#where_clause
{
fn get_dispatch_info(&self) -> #pezframe_support::dispatch::DispatchInfo {
match *self {
#(
#cfg_attrs
Self::#fn_name { #( #args_name_pattern_ref, )* } => {
let __pallet_base_weight = #fn_weight;
let __pallet_weight = <
dyn #pezframe_support::dispatch::WeighData<( #( & #args_type, )* )>
>::weigh_data(&__pallet_base_weight, ( #( #args_name, )* ));
let __pallet_class = <
dyn #pezframe_support::dispatch::ClassifyDispatch<
( #( & #args_type, )* )
>
>::classify_dispatch(&__pallet_base_weight, ( #( #args_name, )* ));
let __pallet_pays_fee = <
dyn #pezframe_support::dispatch::PaysFee<( #( & #args_type, )* )>
>::pays_fee(&__pallet_base_weight, ( #( #args_name, )* ));
#pezframe_support::dispatch::DispatchInfo {
call_weight: __pallet_weight,
extension_weight: Default::default(),
class: __pallet_class,
pays_fee: __pallet_pays_fee,
}
},
)*
Self::__Ignore(_, _) => unreachable!("__Ignore cannot be used"),
}
}
}
impl<#type_impl_gen> #pezframe_support::dispatch::CheckIfFeeless for #call_ident<#type_use_gen>
#where_clause
{
type Origin = #pezframe_system::pezpallet_prelude::OriginFor<T>;
#[allow(unused_variables)]
fn is_feeless(&self, origin: &Self::Origin) -> bool {
match *self {
#(
#cfg_attrs
Self::#fn_name { #( #args_name_pattern_ref, )* } => {
let feeless_check = #feeless_check;
feeless_check(origin, #( #args_name, )*)
},
)*
Self::__Ignore(_, _) => unreachable!("__Ignore cannot be used"),
}
}
}
impl<#type_impl_gen> #pezframe_support::traits::GetCallName for #call_ident<#type_use_gen>
#where_clause
{
fn get_call_name(&self) -> &'static str {
match *self {
#( #cfg_attrs Self::#fn_name { .. } => stringify!(#fn_name), )*
Self::__Ignore(_, _) => unreachable!("__PhantomItem cannot be used."),
}
}
fn get_call_names() -> &'static [&'static str] {
&[ #( #cfg_attrs stringify!(#fn_name), )* ]
}
}
impl<#type_impl_gen> #pezframe_support::traits::GetCallIndex for #call_ident<#type_use_gen>
#where_clause
{
fn get_call_index(&self) -> u8 {
match *self {
#( #cfg_attrs Self::#fn_name { .. } => #call_index, )*
Self::__Ignore(_, _) => unreachable!("__PhantomItem cannot be used."),
}
}
fn get_call_indices() -> &'static [u8] {
&[ #( #cfg_attrs #call_index, )* ]
}
}
impl<#type_impl_gen> #pezframe_support::traits::UnfilteredDispatchable
for #call_ident<#type_use_gen>
#where_clause
{
type RuntimeOrigin = #pezframe_system::pezpallet_prelude::OriginFor<T>;
fn dispatch_bypass_filter(
self,
origin: Self::RuntimeOrigin
) -> #pezframe_support::dispatch::DispatchResultWithPostInfo {
#pezframe_support::dispatch_context::run_in_context(|| {
match self {
#(
#cfg_attrs
Self::#fn_name { #( #args_name_pattern, )* } => {
#pezframe_support::__private::pezsp_tracing::enter_span!(
#pezframe_support::__private::pezsp_tracing::trace_span!(stringify!(#fn_name))
);
#maybe_allow_attrs
#[allow(clippy::useless_conversion)]
<#pezpallet_ident<#type_use_gen>>::#fn_name(origin, #( #args_name, )* )
.map(Into::into).map_err(Into::into)
},
)*
Self::__Ignore(_, _) => {
let _ = origin; // Use origin for empty Call enum
unreachable!("__PhantomItem cannot be used.");
},
}
})
}
}
impl<#type_impl_gen> #pezframe_support::dispatch::Callable<T> for #pezpallet_ident<#type_use_gen>
#where_clause
{
type RuntimeCall = #call_ident<#type_use_gen>;
}
impl<#type_impl_gen> #pezpallet_ident<#type_use_gen> #where_clause {
#[allow(dead_code)]
#[doc(hidden)]
pub fn call_functions() -> #pezframe_support::__private::metadata_ir::PalletCallMetadataIR {
#pezframe_support::__private::metadata_ir::PalletCallMetadataIR {
ty: #pezframe_support::__private::scale_info::meta_type::<#call_ident<#type_use_gen>>(),
deprecation_info: #deprecation,
}
}
}
#( #authorize_fn_pallet_impl )*
impl<#type_impl_gen> #pezframe_support::traits::Authorize for #call_ident<#type_use_gen>
#where_clause
{
fn authorize(&self, source: #pezframe_support::pezpallet_prelude::TransactionSource) -> ::core::option::Option<::core::result::Result<
(
#pezframe_support::pezpallet_prelude::ValidTransaction,
#pezframe_support::pezpallet_prelude::Weight,
),
#pezframe_support::pezpallet_prelude::TransactionValidityError
>>
{
match *self {
#(
#cfg_attrs
Self::#fn_name { #( #args_name_pattern_ref, )* } => {
#authorize_impl
},
)*
Self::__Ignore(_, _) => {
let _ = source;
unreachable!("__Ignore cannot be used")
},
}
}
fn weight_of_authorize(&self) -> #pezframe_support::pezpallet_prelude::Weight {
match *self {
#(
#cfg_attrs
Self::#fn_name { #( #args_name_pattern_ref, )* } => {
#authorize_fn_weight
},
)*
Self::__Ignore(_, _) => unreachable!("__Ignore cannot be used"),
}
}
}
)
}
@@ -0,0 +1,40 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::pezpallet::Def;
use proc_macro2::TokenStream;
/// Expands `composite_enum` and adds the `VariantCount` implementation for it.
pub fn expand_composites(def: &mut Def) -> TokenStream {
let mut expand = quote::quote!();
let pezframe_support = &def.pezframe_support;
for composite in &def.composites {
let name = &composite.ident;
let (impl_generics, ty_generics, where_clause) = composite.generics.split_for_impl();
let variants_count = composite.variant_count;
// add `VariantCount` implementation for `composite_enum`
expand.extend(quote::quote_spanned!(composite.attr_span =>
impl #impl_generics #pezframe_support::traits::VariantCount for #name #ty_generics #where_clause {
const VARIANT_COUNT: u32 = #variants_count;
}
));
}
expand
}
@@ -0,0 +1,170 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::pezpallet::{parse::GenericKind, Def};
use proc_macro2::TokenStream;
use quote::quote;
use syn::{parse_quote, Item};
///
/// * Generate default rust doc
pub fn expand_config(def: &mut Def) -> TokenStream {
let config = &def.config;
let config_item = {
let item = &mut def.item.content.as_mut().expect("Checked by def parser").1[config.index];
if let Item::Trait(item) = item {
item
} else {
unreachable!("Checked by config parser")
}
};
config_item.attrs.insert(
0,
parse_quote!(
#[doc = r"
Configuration trait of this pezpallet.
The main purpose of this trait is to act as an interface between this pezpallet and the runtime in
which it is embedded in. A type, function, or constant in this trait is essentially left to be
configured by the runtime that includes this pezpallet.
Consequently, a runtime that wants to include this pezpallet must implement this trait."
]
),
);
config_item.attrs.retain(|attr| !attr.path().is_ident("deprecated"));
// insert `pezframe_system::Config` supertrait with `RuntimeEvent: From<Event<Self>>` if neither
// associated type nor type bound is defined.
if let Some(event) = &def.event {
if !def.is_pezframe_system {
let pezframe_system = &def.pezframe_system;
// can't use `type_use_gen()` since it returns `T`, not `Self`
let event_use_gen = match event.gen_kind {
GenericKind::None => quote!(),
GenericKind::Config => quote::quote_spanned! {event.attr_span => Self},
GenericKind::ConfigAndInstance => {
quote::quote_spanned! {event.attr_span => Self, I}
},
};
let supertrait_with_event_bound = syn::parse2::<syn::TypeParamBound>(
quote! { #pezframe_system::Config<RuntimeEvent: From<Event<#event_use_gen>>> },
)
.expect("Parsing super trait doesn't fail; qed");
config_item.supertraits.push(supertrait_with_event_bound.into());
}
}
// we only emit `DefaultConfig` if there are trait items, so an empty `DefaultConfig` is
// impossible consequently.
match &config.default_sub_trait {
Some(default_sub_trait) if default_sub_trait.items.len() > 0 => {
let trait_items = &default_sub_trait
.items
.iter()
.map(|item| {
if item.1 {
if let syn::TraitItem::Type(item) = item.0.clone() {
let mut item = item.clone();
item.bounds.clear();
syn::TraitItem::Type(item)
} else {
item.0.clone()
}
} else {
item.0.clone()
}
})
.collect::<Vec<_>>();
let type_param_bounds = if default_sub_trait.has_system {
let system = &def.pezframe_system;
quote::quote!(: #system::DefaultConfig)
} else {
quote::quote!()
};
quote!(
/// Based on [`Config`]. Auto-generated by
/// [`#[pezpallet::config(with_default)]`](`pezframe_support::pezpallet_macros::config`).
/// Can be used in tandem with
/// [`#[register_default_config]`](`pezframe_support::register_default_config`) and
/// [`#[derive_impl]`](`pezframe_support::derive_impl`) to derive test config traits
/// based on existing pezpallet config traits in a safe and developer-friendly way.
///
/// See [here](`pezframe_support::pezpallet_macros::config`) for more information and caveats about
/// the auto-generated `DefaultConfig` trait and how it is generated.
pub trait DefaultConfig #type_param_bounds {
#(#trait_items)*
}
)
},
_ => quote!(),
}
}
/// Generate the metadata for the associated types of the config trait.
///
/// Implements the `pezpallet_associated_types_metadata` function for the pezpallet.
pub fn expand_config_metadata(def: &Def) -> proc_macro2::TokenStream {
let pezframe_support = &def.pezframe_support;
let type_impl_gen = &def.type_impl_generics(proc_macro2::Span::call_site());
let type_use_gen = &def.type_use_generics(proc_macro2::Span::call_site());
let pezpallet_ident = &def.pezpallet_struct.pezpallet;
let trait_use_gen = &def.trait_use_generics(proc_macro2::Span::call_site());
let mut where_clauses = vec![&def.config.where_clause];
where_clauses.extend(def.extra_constants.iter().map(|d| &d.where_clause));
let completed_where_clause = super::merge_where_clauses(&where_clauses);
let types = def.config.associated_types_metadata.iter().map(|metadata| {
let ident = &metadata.ident;
let span = ident.span();
let ident_str = ident.to_string();
let cfgs = &metadata.cfg;
let no_docs = vec![];
let doc = if cfg!(feature = "no-metadata-docs") { &no_docs } else { &metadata.doc };
quote::quote_spanned!(span => {
#( #cfgs ) *
#pezframe_support::__private::metadata_ir::PalletAssociatedTypeMetadataIR {
name: #ident_str,
ty: #pezframe_support::__private::scale_info::meta_type::<
<T as Config #trait_use_gen>::#ident
>(),
docs: #pezframe_support::__private::vec![ #( #doc ),* ],
}
})
});
quote::quote!(
impl<#type_impl_gen> #pezpallet_ident<#type_use_gen> #completed_where_clause {
#[doc(hidden)]
pub fn pezpallet_associated_types_metadata()
-> #pezframe_support::__private::vec::Vec<#pezframe_support::__private::metadata_ir::PalletAssociatedTypeMetadataIR>
{
#pezframe_support::__private::vec![ #( #types ),* ]
}
}
)
}
@@ -0,0 +1,135 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::{deprecation::extract_or_return_allow_attrs, pezpallet::Def};
struct ConstDef {
/// Name of the associated type.
pub ident: syn::Ident,
/// The type in Get, e.g. `u32` in `type Foo: Get<u32>;`, but `Self` is replaced by `T`
pub type_: syn::Type,
/// The doc associated
pub doc: Vec<syn::Expr>,
/// default_byte implementation
pub default_byte_impl: proc_macro2::TokenStream,
/// Constant name for Metadata (optional)
pub metadata_name: Option<syn::Ident>,
/// Deprecation_info:
pub deprecation_info: proc_macro2::TokenStream,
}
/// Implement the `pezpallet_constants_metadata` function for the pezpallet.
pub fn expand_constants(def: &mut Def) -> proc_macro2::TokenStream {
let pezframe_support = &def.pezframe_support;
let type_impl_gen = &def.type_impl_generics(proc_macro2::Span::call_site());
let type_use_gen = &def.type_use_generics(proc_macro2::Span::call_site());
let pezpallet_ident = &def.pezpallet_struct.pezpallet;
let trait_use_gen = &def.trait_use_generics(proc_macro2::Span::call_site());
let mut where_clauses = vec![&def.config.where_clause];
where_clauses.extend(def.extra_constants.iter().map(|d| &d.where_clause));
let completed_where_clause = super::merge_where_clauses(&where_clauses);
let mut config_consts = vec![];
for const_ in def.config.consts_metadata.iter() {
let ident = &const_.ident;
let const_type = &const_.type_;
let deprecation_info = match crate::deprecation::get_deprecation(
&quote::quote! { #pezframe_support },
&const_.attrs,
) {
Ok(deprecation) => deprecation,
Err(e) => return e.into_compile_error(),
};
// Extracts #[allow] attributes, necessary so that we don't run into compiler warnings
let maybe_allow_attrs = extract_or_return_allow_attrs(&const_.attrs);
config_consts.push(ConstDef {
ident: const_.ident.clone(),
type_: const_.type_.clone(),
doc: const_.doc.clone(),
default_byte_impl: quote::quote!(
#(#maybe_allow_attrs)*
let value = <<T as Config #trait_use_gen>::#ident as
#pezframe_support::traits::Get<#const_type>>::get();
#pezframe_support::__private::codec::Encode::encode(&value)
),
metadata_name: None,
deprecation_info,
})
}
let mut extra_consts = vec![];
for const_ in def.extra_constants.iter().flat_map(|d| &d.extra_constants) {
let ident = &const_.ident;
let deprecation_info = match crate::deprecation::get_deprecation(
&quote::quote! { #pezframe_support },
&const_.attrs,
) {
Ok(deprecation) => deprecation,
Err(e) => return e.into_compile_error(),
};
// Extracts #[allow] attributes, necessary so that we don't run into compiler warnings
let maybe_allow_attrs = extract_or_return_allow_attrs(&const_.attrs);
extra_consts.push(ConstDef {
ident: const_.ident.clone(),
type_: const_.type_.clone(),
doc: const_.doc.clone(),
default_byte_impl: quote::quote!(
#(#maybe_allow_attrs)*
let value = <Pezpallet<#type_use_gen>>::#ident();
#pezframe_support::__private::codec::Encode::encode(&value)
),
metadata_name: const_.metadata_name.clone(),
deprecation_info,
})
}
let consts = config_consts.into_iter().chain(extra_consts.into_iter()).map(|const_| {
let const_type = &const_.type_;
let ident_str = format!("{}", const_.metadata_name.unwrap_or(const_.ident));
let no_docs = vec![];
let doc = if cfg!(feature = "no-metadata-docs") { &no_docs } else { &const_.doc };
let default_byte_impl = &const_.default_byte_impl;
let deprecation_info = &const_.deprecation_info;
quote::quote!({
#pezframe_support::__private::metadata_ir::PalletConstantMetadataIR {
name: #ident_str,
ty: #pezframe_support::__private::scale_info::meta_type::<#const_type>(),
value: { #default_byte_impl },
docs: #pezframe_support::__private::vec![ #( #doc ),* ],
deprecation_info: #deprecation_info
}
})
});
quote::quote!(
impl<#type_impl_gen> #pezpallet_ident<#type_use_gen> #completed_where_clause{
#[doc(hidden)]
pub fn pezpallet_constants_metadata()
-> #pezframe_support::__private::Vec<#pezframe_support::__private::metadata_ir::PalletConstantMetadataIR>
{
#pezframe_support::__private::vec![ #( #consts ),* ]
}
}
)
}
@@ -0,0 +1,103 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use proc_macro2::Span;
use crate::pezpallet::Def;
pub fn expand_doc_only(def: &mut Def) -> proc_macro2::TokenStream {
let dispatchables = if let Some(call_def) = &def.call {
let type_impl_generics = def.type_impl_generics(Span::call_site());
call_def
.methods
.iter()
.map(|method| {
let name = &method.name;
let args = &method
.args
.iter()
.map(|(_, arg_name, arg_type)| quote::quote!( #arg_name: #arg_type, ))
.collect::<proc_macro2::TokenStream>();
let docs = &method.docs;
let real = format!(" [`Pezpallet::{}`].", name);
quote::quote!(
#( #[doc = #docs] )*
///
/// # Warning: Doc-Only
///
/// This function is an automatically generated, and is doc-only, uncallable
/// stub. See the real version in
#[ doc = #real ]
pub fn #name<#type_impl_generics>(#args) { unreachable!(); }
)
})
.collect::<proc_macro2::TokenStream>()
} else {
quote::quote!()
};
let storage_types = def
.storages
.iter()
.map(|storage| {
let storage_name = &storage.ident;
let storage_type_docs = &storage.docs;
let real = format!("[`pezpallet::{}`].", storage_name);
quote::quote!(
#( #[doc = #storage_type_docs] )*
///
/// # Warning: Doc-Only
///
/// This type is automatically generated, and is doc-only. See the real version in
#[ doc = #real ]
pub struct #storage_name();
)
})
.collect::<proc_macro2::TokenStream>();
quote::quote!(
/// Auto-generated docs-only module listing all (public and private) defined storage types
/// for this pezpallet.
///
/// # Warning: Doc-Only
///
/// Members of this module cannot be used directly and are only provided for documentation
/// purposes.
///
/// To see the actual storage type, find a struct with the same name at the root of the
/// pezpallet, in the list of [*Type Definitions*](../index.html#types).
#[cfg(doc)]
pub mod storage_types {
use super::*;
#storage_types
}
/// Auto-generated docs-only module listing all defined dispatchables for this pezpallet.
///
/// # Warning: Doc-Only
///
/// Members of this module cannot be used directly and are only provided for documentation
/// purposes. To see the real version of each dispatchable, look for them in [`Pezpallet`] or
/// [`Call`].
#[cfg(doc)]
pub mod dispatchables {
use super::*;
#dispatchables
}
)
}
@@ -0,0 +1,172 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::pezpallet::Def;
use proc_macro2::TokenStream;
use quote::ToTokens;
use syn::{spanned::Spanned, Attribute, Lit, LitStr};
const DOC: &'static str = "doc";
const PALLET_DOC: &'static str = "pezpallet_doc";
/// Get the documentation file path from the `pezpallet_doc` attribute.
///
/// Supported format:
/// `#[pezpallet_doc(PATH)]`: The path of the file from which the documentation is loaded
fn parse_pallet_doc_value(attr: &Attribute) -> syn::Result<DocMetaValue> {
let lit: syn::LitStr = attr.parse_args().map_err(|_| {
let msg = "The `pezpallet_doc` received an unsupported argument. Supported format: `pezpallet_doc(\"PATH\")`";
syn::Error::new(attr.span(), msg)
})?;
Ok(DocMetaValue::Path(lit))
}
/// Get the value from the `doc` comment attribute:
///
/// Supported formats:
/// - `#[doc = "A doc string"]`: Documentation as a string literal
/// - `#[doc = include_str!(PATH)]`: Documentation obtained from a path
fn parse_doc_value(attr: &Attribute) -> syn::Result<Option<DocMetaValue>> {
if !attr.path().is_ident(DOC) {
return Ok(None);
}
let meta = attr.meta.require_name_value()?;
match &meta.value {
syn::Expr::Lit(lit) => Ok(Some(DocMetaValue::Lit(lit.lit.clone()))),
syn::Expr::Macro(mac) if mac.mac.path.is_ident("include_str") =>
Ok(Some(DocMetaValue::Path(mac.mac.parse_body()?))),
_ =>
Err(syn::Error::new(attr.span(), "Expected `= \"docs\"` or `= include_str!(\"PATH\")`")),
}
}
/// Supported documentation tokens.
#[derive(Debug)]
enum DocMetaValue {
/// Documentation with string literals.
///
/// `#[doc = "Lit"]`
Lit(Lit),
/// Documentation with `include_str!` macro.
///
/// The string literal represents the file `PATH`.
///
/// `#[doc = include_str!(PATH)]`
Path(LitStr),
}
impl ToTokens for DocMetaValue {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
DocMetaValue::Lit(lit) => lit.to_tokens(tokens),
DocMetaValue::Path(path_lit) => {
let decl = quote::quote!(include_str!(#path_lit));
tokens.extend(decl)
},
}
}
}
/// Extract the documentation from the given pezpallet definition
/// to include in the runtime metadata.
///
/// Implement a `pezpallet_documentation_metadata` function to fetch the
/// documentation that is included in the metadata.
///
/// The documentation is placed on the pezpallet similar to:
///
/// ```ignore
/// #[pezpallet]
/// /// Documentation for pezpallet
/// #[doc = "Documentation for pezpallet"]
/// #[doc = include_str!("../README.md")]
/// #[pezpallet_doc("../documentation1.md")]
/// #[pezpallet_doc("../documentation2.md")]
/// pub mod pezpallet {}
/// ```
///
/// # pezpallet_doc
///
/// The `pezpallet_doc` attribute can only be provided with one argument,
/// which is the file path that holds the documentation to be added to the metadata.
///
/// Unlike the `doc` attribute, the documentation provided to the `proc_macro` attribute is
/// not added to the pezpallet.
pub fn expand_documentation(def: &mut Def) -> proc_macro2::TokenStream {
let pezframe_support = &def.pezframe_support;
let type_impl_gen = &def.type_impl_generics(proc_macro2::Span::call_site());
let type_use_gen = &def.type_use_generics(proc_macro2::Span::call_site());
let pezpallet_ident = &def.pezpallet_struct.pezpallet;
let where_clauses = &def.config.where_clause;
// TODO: Use [drain_filter](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.drain_filter) when it is stable.
// The `pezpallet_doc` attributes are excluded from the generation of the pezpallet,
// but they are included in the runtime metadata.
let mut pezpallet_docs = Vec::with_capacity(def.item.attrs.len());
let mut index = 0;
while index < def.item.attrs.len() {
let attr = &def.item.attrs[index];
if attr.path().get_ident().map_or(false, |i| *i == PALLET_DOC) {
pezpallet_docs.push(def.item.attrs.remove(index));
// Do not increment the index, we have just removed the
// element from the attributes.
continue;
}
index += 1;
}
// Capture the `#[doc = include_str!("../README.md")]` and `#[doc = "Documentation"]`.
let docs = match def
.item
.attrs
.iter()
.filter_map(|v| parse_doc_value(v).transpose())
.collect::<syn::Result<Vec<_>>>()
{
Ok(r) => r,
Err(err) => return err.into_compile_error(),
};
// Capture the `#[pezpallet_doc("../README.md")]`.
let pezpallet_docs = match pezpallet_docs
.into_iter()
.map(|attr| parse_pallet_doc_value(&attr))
.collect::<syn::Result<Vec<_>>>()
{
Ok(docs) => docs,
Err(err) => return err.into_compile_error(),
};
let docs = docs.iter().chain(pezpallet_docs.iter());
quote::quote!(
impl<#type_impl_gen> #pezpallet_ident<#type_use_gen> #where_clauses{
#[doc(hidden)]
pub fn pezpallet_documentation_metadata()
-> #pezframe_support::__private::Vec<&'static str>
{
#pezframe_support::__private::vec![ #( #docs ),* ]
}
}
)
}
@@ -0,0 +1,227 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::{
deprecation::extract_or_return_allow_attrs,
pezpallet::{
parse::error::{VariantDef, VariantField},
Def,
},
COUNTER,
};
use pezframe_support_procedural_tools::get_doc_literals;
use quote::ToTokens;
use syn::spanned::Spanned;
///
/// * impl various trait on Error
pub fn expand_error(def: &mut Def) -> proc_macro2::TokenStream {
let count = COUNTER.with(|counter| counter.borrow_mut().inc());
let error_token_unique_id =
syn::Ident::new(&format!("__tt_error_token_{}", count), def.item.span());
let pezframe_support = &def.pezframe_support;
let pezframe_system = &def.pezframe_system;
let config_where_clause = &def.config.where_clause;
let error = if let Some(error) = &def.error {
error
} else {
return quote::quote! {
#[macro_export]
#[doc(hidden)]
macro_rules! #error_token_unique_id {
{
$caller:tt
your_tt_return = [{ $my_tt_return:path }]
} => {
$my_tt_return! {
$caller
}
};
}
pub use #error_token_unique_id as tt_error_token;
};
};
let error_ident = &error.error;
let type_impl_gen = &def.type_impl_generics(error.attr_span);
let type_use_gen = &def.type_use_generics(error.attr_span);
let phantom_variant: syn::Variant = syn::parse_quote!(
#[doc(hidden)]
#[codec(skip)]
__Ignore(
core::marker::PhantomData<(#type_use_gen)>,
#pezframe_support::Never,
)
);
let as_str_matches =
error
.variants
.iter()
.map(|VariantDef { ident: variant, field: field_ty, cfg_attrs, maybe_allow_attrs }| {
let variant_str = variant.to_string();
let cfg_attrs = cfg_attrs.iter().map(|attr| attr.to_token_stream());
match field_ty {
Some(VariantField { is_named: true }) => {
quote::quote_spanned!(error.attr_span => #( #cfg_attrs )* #(#maybe_allow_attrs)* Self::#variant { .. } => #variant_str,)
},
Some(VariantField { is_named: false }) => {
quote::quote_spanned!(error.attr_span => #( #cfg_attrs )* #(#maybe_allow_attrs)* Self::#variant(..) => #variant_str,)
},
None => {
quote::quote_spanned!(error.attr_span => #( #cfg_attrs )* #(#maybe_allow_attrs)* Self::#variant => #variant_str,)
},
}
});
let error_item = {
let item = &mut def.item.content.as_mut().expect("Checked by def parser").1[error.index];
if let syn::Item::Enum(item) = item {
item
} else {
unreachable!("Checked by error parser")
}
};
error_item.variants.insert(0, phantom_variant);
let capture_docs = if cfg!(feature = "no-metadata-docs") { "never" } else { "always" };
let deprecation = match crate::deprecation::get_deprecation_enum(
&quote::quote! {#pezframe_support},
error_item.variants.iter().enumerate().map(|(index, item)| {
let index = crate::deprecation::variant_index_for_deprecation(index as u8, item);
(index, item.attrs.as_ref())
}),
) {
Ok(deprecation) => deprecation,
Err(e) => return e.into_compile_error(),
};
// derive TypeInfo for error metadata
error_item.attrs.push(syn::parse_quote! {
#[derive(
#pezframe_support::__private::codec::Encode,
#pezframe_support::__private::codec::Decode,
#pezframe_support::__private::codec::DecodeWithMemTracking,
#pezframe_support::__private::scale_info::TypeInfo,
#pezframe_support::PalletError,
)]
});
error_item.attrs.push(syn::parse_quote!(
#[scale_info(skip_type_params(#type_use_gen), capture_docs = #capture_docs)]
));
if get_doc_literals(&error_item.attrs).is_empty() {
error_item.attrs.push(syn::parse_quote!(
#[doc = "The `Error` enum of this pezpallet."]
));
}
// Extracts #[allow] attributes, necessary so that we don't run into compiler warnings
let maybe_allow_attrs: Vec<syn::Attribute> =
extract_or_return_allow_attrs(&error_item.attrs).collect();
quote::quote_spanned!(error.attr_span =>
#(#maybe_allow_attrs)*
impl<#type_impl_gen> core::fmt::Debug for #error_ident<#type_use_gen>
#config_where_clause
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>)
-> core::fmt::Result
{
f.write_str(self.as_str())
}
}
#(#maybe_allow_attrs)*
impl<#type_impl_gen> #error_ident<#type_use_gen> #config_where_clause {
#[doc(hidden)]
pub fn as_str(&self) -> &'static str {
match &self {
#(#maybe_allow_attrs)* Self::__Ignore(_, _) => unreachable!("`__Ignore` can never be constructed"),
#( #as_str_matches )*
}
}
}
#(#maybe_allow_attrs)*
impl<#type_impl_gen> From<#error_ident<#type_use_gen>> for &'static str
#config_where_clause
{
fn from(err: #error_ident<#type_use_gen>) -> &'static str {
err.as_str()
}
}
#(#maybe_allow_attrs)*
impl<#type_impl_gen> From<#error_ident<#type_use_gen>>
for #pezframe_support::pezsp_runtime::DispatchError
#config_where_clause
{
fn from(err: #error_ident<#type_use_gen>) -> Self {
use #pezframe_support::__private::codec::Encode;
let index = <
<T as #pezframe_system::Config>::PalletInfo
as #pezframe_support::traits::PalletInfo
>::index::<Pezpallet<#type_use_gen>>()
.expect("Every active module has an index in the runtime; qed") as u8;
let mut encoded = err.encode();
encoded.resize(#pezframe_support::MAX_MODULE_ERROR_ENCODED_SIZE, 0);
#pezframe_support::pezsp_runtime::DispatchError::Module(#pezframe_support::pezsp_runtime::ModuleError {
index,
error: TryInto::try_into(encoded).expect("encoded error is resized to be equal to the maximum encoded error size; qed"),
message: Some(err.as_str()),
})
}
}
#[macro_export]
#[doc(hidden)]
macro_rules! #error_token_unique_id {
{
$caller:tt
your_tt_return = [{ $my_tt_return:path }]
} => {
$my_tt_return! {
$caller
error = [{ #error_ident }]
}
};
}
pub use #error_token_unique_id as tt_error_token;
#(#maybe_allow_attrs)*
impl<#type_impl_gen> #error_ident<#type_use_gen> #config_where_clause {
#[allow(dead_code)]
#[doc(hidden)]
pub fn error_metadata() -> #pezframe_support::__private::metadata_ir::PalletErrorMetadataIR {
#(#maybe_allow_attrs)*
#pezframe_support::__private::metadata_ir::PalletErrorMetadataIR {
ty: #pezframe_support::__private::scale_info::meta_type::<#error_ident<#type_use_gen>>(),
deprecation_info: #deprecation,
}
}
}
)
}
@@ -0,0 +1,204 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::{
deprecation::extract_or_return_allow_attrs,
pezpallet::{parse::event::PalletEventDepositAttr, Def},
COUNTER,
};
use pezframe_support_procedural_tools::get_doc_literals;
use syn::{spanned::Spanned, Ident};
///
/// * Add __Ignore variant on Event
/// * Impl various trait on Event including metadata
/// * if deposit_event is defined, implement deposit_event on module.
pub fn expand_event(def: &mut Def) -> proc_macro2::TokenStream {
let count = COUNTER.with(|counter| counter.borrow_mut().inc());
let (event, macro_ident) = if let Some(event) = &def.event {
let ident = Ident::new(&format!("__is_event_part_defined_{}", count), event.attr_span);
(event, ident)
} else {
let macro_ident =
Ident::new(&format!("__is_event_part_defined_{}", count), def.item.span());
return quote::quote! {
#[doc(hidden)]
pub mod __bizinikiwi_event_check {
#[macro_export]
#[doc(hidden)]
macro_rules! #macro_ident {
($pezpallet_name:ident) => {
compile_error!(concat!(
"`",
stringify!($pezpallet_name),
"` does not have #[pezpallet::event] defined, perhaps you should \
remove `Event` from construct_runtime?",
));
}
}
#[doc(hidden)]
pub use #macro_ident as is_event_part_defined;
}
};
};
let event_where_clause = &event.where_clause;
// NOTE: actually event where clause must be a subset of config where clause because of
// `type RuntimeEvent: From<Event<Self>>`. But we merge either way for potential better error
// message
let completed_where_clause =
super::merge_where_clauses(&[&event.where_clause, &def.config.where_clause]);
let event_ident = &event.event;
let pezframe_system = &def.pezframe_system;
let pezframe_support = &def.pezframe_support;
let event_use_gen = &event.gen_kind.type_use_gen(event.attr_span);
let event_impl_gen = &event.gen_kind.type_impl_gen(event.attr_span);
let event_item = {
let item = &mut def.item.content.as_mut().expect("Checked by def parser").1[event.index];
if let syn::Item::Enum(item) = item {
item
} else {
unreachable!("Checked by event parser")
}
};
// Phantom data is added for generic event.
if event.gen_kind.is_generic() {
let variant = syn::parse_quote!(
#[doc(hidden)]
#[codec(skip)]
__Ignore(
::core::marker::PhantomData<(#event_use_gen)>,
#pezframe_support::Never,
)
);
// Push ignore variant at the end.
event_item.variants.push(variant);
}
let deprecation = match crate::deprecation::get_deprecation_enum(
&quote::quote! {#pezframe_support},
event_item.variants.iter().enumerate().map(|(index, item)| {
let index = crate::deprecation::variant_index_for_deprecation(index as u8, item);
(index, item.attrs.as_ref())
}),
) {
Ok(deprecation) => deprecation,
Err(e) => return e.into_compile_error(),
};
if get_doc_literals(&event_item.attrs).is_empty() {
event_item
.attrs
.push(syn::parse_quote!(#[doc = "The `Event` enum of this pezpallet"]));
}
// derive some traits because system event require Clone, FullCodec, Eq, PartialEq and Debug
event_item.attrs.push(syn::parse_quote!(
#[derive(
#pezframe_support::CloneNoBound,
#pezframe_support::EqNoBound,
#pezframe_support::PartialEqNoBound,
#pezframe_support::DebugNoBound,
#pezframe_support::__private::codec::Encode,
#pezframe_support::__private::codec::Decode,
#pezframe_support::__private::codec::DecodeWithMemTracking,
#pezframe_support::__private::scale_info::TypeInfo,
)]
));
let capture_docs = if cfg!(feature = "no-metadata-docs") { "never" } else { "always" };
// skip requirement for type params to implement `TypeInfo`, and set docs capture
event_item.attrs.push(syn::parse_quote!(
#[scale_info(skip_type_params(#event_use_gen), capture_docs = #capture_docs)]
));
// Extracts #[allow] attributes, necessary so that we don't run into compiler warnings
let maybe_allow_attrs: Vec<syn::Attribute> =
extract_or_return_allow_attrs(&event_item.attrs).collect();
let deposit_event = if let Some(deposit_event) = &event.deposit_event {
let event_use_gen = &event.gen_kind.type_use_gen(event.attr_span);
let type_impl_gen = &def.type_impl_generics(event.attr_span);
let type_use_gen = &def.type_use_generics(event.attr_span);
let pezpallet_ident = &def.pezpallet_struct.pezpallet;
let PalletEventDepositAttr { fn_vis, fn_span, .. } = deposit_event;
quote::quote_spanned!(*fn_span =>
impl<#type_impl_gen> #pezpallet_ident<#type_use_gen> #completed_where_clause {
#(#maybe_allow_attrs)*
#fn_vis fn deposit_event(event: Event<#event_use_gen>) {
let event = <
<T as #pezframe_system::Config>::RuntimeEvent as
From<Event<#event_use_gen>>
>::from(event);
let event = <
<T as #pezframe_system::Config>::RuntimeEvent as
Into<<T as #pezframe_system::Config>::RuntimeEvent>
>::into(event);
<#pezframe_system::Pezpallet<T>>::deposit_event(event)
}
}
)
} else {
Default::default()
};
quote::quote_spanned!(event.attr_span =>
#[doc(hidden)]
pub mod __bizinikiwi_event_check {
#[macro_export]
#[doc(hidden)]
macro_rules! #macro_ident {
($pezpallet_name:ident) => {};
}
#[doc(hidden)]
pub use #macro_ident as is_event_part_defined;
}
#deposit_event
#(#maybe_allow_attrs)*
impl<#event_impl_gen> From<#event_ident<#event_use_gen>> for () #event_where_clause {
fn from(_: #event_ident<#event_use_gen>) {}
}
#(#maybe_allow_attrs)*
impl<#event_impl_gen> #event_ident<#event_use_gen> #event_where_clause {
#[allow(dead_code)]
#[doc(hidden)]
pub fn event_metadata<W: #pezframe_support::__private::scale_info::TypeInfo + 'static>() -> #pezframe_support::__private::metadata_ir::PalletEventMetadataIR {
#pezframe_support::__private::metadata_ir::PalletEventMetadataIR {
ty: #pezframe_support::__private::scale_info::meta_type::<W>(),
deprecation_info: #deprecation,
}
}
}
)
}
@@ -0,0 +1,50 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::pezpallet::Def;
///
/// * implement the trait `pezsp_runtime::BuildStorage`
pub fn expand_genesis_build(def: &mut Def) -> proc_macro2::TokenStream {
let genesis_config = if let Some(genesis_config) = &def.genesis_config {
genesis_config
} else {
return Default::default();
};
let genesis_build = def.genesis_build.as_ref().expect("Checked by def parser");
let pezframe_support = &def.pezframe_support;
let type_impl_gen = &genesis_config.gen_kind.type_impl_gen(genesis_build.attr_span);
let gen_cfg_ident = &genesis_config.genesis_config;
let gen_cfg_use_gen = &genesis_config.gen_kind.type_use_gen(genesis_build.attr_span);
let where_clause = &genesis_build.where_clause;
quote::quote_spanned!(genesis_build.attr_span =>
#pezframe_support::std_enabled! {
impl<#type_impl_gen> #pezframe_support::pezsp_runtime::BuildStorage for #gen_cfg_ident<#gen_cfg_use_gen> #where_clause
{
fn assimilate_storage(&self, storage: &mut #pezframe_support::pezsp_runtime::Storage) -> std::result::Result<(), std::string::String> {
#pezframe_support::__private::BasicExternalities::execute_with_storage(storage, || {
self.build();
Ok(())
})
}
}
}
)
}
@@ -0,0 +1,147 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::{pezpallet::Def, COUNTER};
use pezframe_support_procedural_tools::get_doc_literals;
use quote::ToTokens;
use syn::{spanned::Spanned, Ident};
///
/// * add various derive trait on GenesisConfig struct.
pub fn expand_genesis_config(def: &mut Def) -> proc_macro2::TokenStream {
let count = COUNTER.with(|counter| counter.borrow_mut().inc());
let (genesis_config, def_macro_ident, std_macro_ident) =
if let Some(genesis_config) = &def.genesis_config {
let def_macro_ident = Ident::new(
&format!("__is_genesis_config_defined_{}", count),
genesis_config.genesis_config.span(),
);
let std_macro_ident = Ident::new(
&format!("__is_std_macro_defined_for_genesis_{}", count),
genesis_config.genesis_config.span(),
);
(genesis_config, def_macro_ident, std_macro_ident)
} else {
let def_macro_ident =
Ident::new(&format!("__is_genesis_config_defined_{}", count), def.item.span());
let std_macro_ident =
Ident::new(&format!("__is_std_enabled_for_genesis_{}", count), def.item.span());
return quote::quote! {
#[doc(hidden)]
pub mod __bizinikiwi_genesis_config_check {
#[macro_export]
#[doc(hidden)]
macro_rules! #def_macro_ident {
($pezpallet_name:ident) => {
compile_error!(concat!(
"`",
stringify!($pezpallet_name),
"` does not have #[pezpallet::genesis_config] defined, perhaps you should \
remove `Config` from construct_runtime?",
));
}
}
#[macro_export]
#[doc(hidden)]
macro_rules! #std_macro_ident {
($pezpallet_name:ident, $pezpallet_path:expr) => {};
}
#[doc(hidden)]
pub use #def_macro_ident as is_genesis_config_defined;
#[doc(hidden)]
pub use #std_macro_ident as is_std_enabled_for_genesis;
}
};
};
let pezframe_support = &def.pezframe_support;
let genesis_config_item =
&mut def.item.content.as_mut().expect("Checked by def parser").1[genesis_config.index];
let serde_crate = format!("{}::__private::serde", pezframe_support.to_token_stream());
match genesis_config_item {
syn::Item::Enum(syn::ItemEnum { attrs, .. }) |
syn::Item::Struct(syn::ItemStruct { attrs, .. }) |
syn::Item::Type(syn::ItemType { attrs, .. }) => {
if get_doc_literals(attrs).is_empty() {
attrs.push(syn::parse_quote!(
#[doc = r"
Can be used to configure the
[genesis state](https://docs.pezkuwichain.io/build/genesis-configuration/)
of this pezpallet.
"]
));
}
attrs.push(syn::parse_quote!(
#[derive(#pezframe_support::Serialize, #pezframe_support::Deserialize)]
));
attrs.push(syn::parse_quote!( #[serde(rename_all = "camelCase")] ));
attrs.push(syn::parse_quote!( #[serde(deny_unknown_fields)] ));
attrs.push(syn::parse_quote!( #[serde(bound(serialize = ""))] ));
attrs.push(syn::parse_quote!( #[serde(bound(deserialize = ""))] ));
attrs.push(syn::parse_quote!( #[serde(crate = #serde_crate)] ));
},
_ => unreachable!("Checked by genesis_config parser"),
}
quote::quote! {
#[doc(hidden)]
pub mod __bizinikiwi_genesis_config_check {
#[macro_export]
#[doc(hidden)]
macro_rules! #def_macro_ident {
($pezpallet_name:ident) => {};
}
#[cfg(not(feature = "std"))]
#[macro_export]
#[doc(hidden)]
macro_rules! #std_macro_ident {
($pezpallet_name:ident, $pezpallet_path:expr) => {
compile_error!(concat!(
"`",
stringify!($pezpallet_name),
"` does not have the std feature enabled, this will cause the `",
$pezpallet_path,
"::GenesisConfig` type to not implement serde traits."
));
};
}
#[cfg(feature = "std")]
#[macro_export]
#[doc(hidden)]
macro_rules! #std_macro_ident {
($pezpallet_name:ident, $pezpallet_path:expr) => {};
}
#[doc(hidden)]
pub use #def_macro_ident as is_genesis_config_defined;
#[doc(hidden)]
pub use #std_macro_ident as is_std_enabled_for_genesis;
}
}
}
@@ -0,0 +1,339 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::pezpallet::Def;
/// * implement the individual traits using the Hooks trait
pub fn expand_hooks(def: &mut Def) -> proc_macro2::TokenStream {
let (where_clause, span, has_runtime_upgrade) = match def.hooks.as_ref() {
Some(hooks) => {
let where_clause = hooks.where_clause.clone();
let span = hooks.attr_span;
let has_runtime_upgrade = hooks.has_runtime_upgrade;
(where_clause, span, has_runtime_upgrade)
},
None => (def.config.where_clause.clone(), def.pezpallet_struct.attr_span, false),
};
let pezframe_support = &def.pezframe_support;
let type_impl_gen = &def.type_impl_generics(span);
let type_use_gen = &def.type_use_generics(span);
let pezpallet_ident = &def.pezpallet_struct.pezpallet;
let pezframe_system = &def.pezframe_system;
let pezpallet_name = quote::quote! {
<
<T as #pezframe_system::Config>::PalletInfo
as
#pezframe_support::traits::PalletInfo
>::name::<Self>().unwrap_or("<unknown pezpallet name>")
};
let initialize_on_chain_storage_version = if let Some(in_code_version) =
&def.pezpallet_struct.storage_version
{
quote::quote! {
#pezframe_support::__private::log::info!(
target: #pezframe_support::LOG_TARGET,
"🐥 New pezpallet {:?} detected in the runtime. Initializing the on-chain storage version to match the storage version defined in the pezpallet: {:?}",
#pezpallet_name,
#in_code_version
);
#in_code_version.put::<Self>();
}
} else {
quote::quote! {
let default_version = #pezframe_support::traits::StorageVersion::new(0);
#pezframe_support::__private::log::info!(
target: #pezframe_support::LOG_TARGET,
"🐥 New pezpallet {:?} detected in the runtime. The pezpallet has no defined storage version, so the on-chain version is being initialized to {:?}.",
#pezpallet_name,
default_version
);
default_version.put::<Self>();
}
};
let log_runtime_upgrade = if has_runtime_upgrade {
// a migration is defined here.
quote::quote! {
#pezframe_support::__private::log::info!(
target: #pezframe_support::LOG_TARGET,
"⚠️ {} declares internal migrations (which *might* execute). \
On-chain `{:?}` vs in-code storage version `{:?}`",
#pezpallet_name,
<Self as #pezframe_support::traits::GetStorageVersion>::on_chain_storage_version(),
<Self as #pezframe_support::traits::GetStorageVersion>::in_code_storage_version(),
);
}
} else {
// default.
quote::quote! {
#pezframe_support::__private::log::debug!(
target: #pezframe_support::LOG_TARGET,
"✅ no migration for {}",
#pezpallet_name,
);
}
};
let hooks_impl = if def.hooks.is_none() {
let pezframe_system = &def.pezframe_system;
quote::quote! {
impl<#type_impl_gen>
#pezframe_support::traits::Hooks<#pezframe_system::pezpallet_prelude::BlockNumberFor::<T>>
for #pezpallet_ident<#type_use_gen> #where_clause {}
}
} else {
proc_macro2::TokenStream::new()
};
// If a storage version is set, we should ensure that the storage version on chain matches the
// in-code storage version. This assumes that `Executive` is running custom migrations before
// the pallets are called.
let post_storage_version_check = if def.pezpallet_struct.storage_version.is_some() {
quote::quote! {
let on_chain_version = <Self as #pezframe_support::traits::GetStorageVersion>::on_chain_storage_version();
let in_code_version = <Self as #pezframe_support::traits::GetStorageVersion>::in_code_storage_version();
if on_chain_version != in_code_version {
#pezframe_support::__private::log::error!(
target: #pezframe_support::LOG_TARGET,
"{}: On chain storage version {:?} doesn't match in-code storage version {:?}.",
#pezpallet_name,
on_chain_version,
in_code_version,
);
return Err("On chain and in-code storage version do not match. Missing runtime upgrade?".into());
}
}
} else {
quote::quote! {
let on_chain_version = <Self as #pezframe_support::traits::GetStorageVersion>::on_chain_storage_version();
if on_chain_version != #pezframe_support::traits::StorageVersion::new(0) {
#pezframe_support::__private::log::error!(
target: #pezframe_support::LOG_TARGET,
"{}: On chain storage version {:?} is set to non zero, \
while the pezpallet is missing the `#[pezpallet::storage_version(VERSION)]` attribute.",
#pezpallet_name,
on_chain_version,
);
return Err("On chain storage version set, while the pezpallet doesn't \
have the `#[pezpallet::storage_version(VERSION)]` attribute.".into());
}
}
};
quote::quote_spanned!(span =>
#hooks_impl
impl<#type_impl_gen>
#pezframe_support::traits::OnFinalize<#pezframe_system::pezpallet_prelude::BlockNumberFor::<T>>
for #pezpallet_ident<#type_use_gen> #where_clause
{
fn on_finalize(n: #pezframe_system::pezpallet_prelude::BlockNumberFor::<T>) {
#pezframe_support::__private::pezsp_tracing::enter_span!(
#pezframe_support::__private::pezsp_tracing::trace_span!("on_finalize")
);
<
Self as #pezframe_support::traits::Hooks<
#pezframe_system::pezpallet_prelude::BlockNumberFor::<T>
>
>::on_finalize(n)
}
}
impl<#type_impl_gen>
#pezframe_support::traits::OnIdle<#pezframe_system::pezpallet_prelude::BlockNumberFor::<T>>
for #pezpallet_ident<#type_use_gen> #where_clause
{
fn on_idle(
n: #pezframe_system::pezpallet_prelude::BlockNumberFor::<T>,
remaining_weight: #pezframe_support::weights::Weight
) -> #pezframe_support::weights::Weight {
<
Self as #pezframe_support::traits::Hooks<
#pezframe_system::pezpallet_prelude::BlockNumberFor::<T>
>
>::on_idle(n, remaining_weight)
}
}
impl<#type_impl_gen>
#pezframe_support::traits::OnPoll<#pezframe_system::pezpallet_prelude::BlockNumberFor::<T>>
for #pezpallet_ident<#type_use_gen> #where_clause
{
fn on_poll(
n: #pezframe_system::pezpallet_prelude::BlockNumberFor::<T>,
weight: &mut #pezframe_support::weights::WeightMeter
) {
<
Self as #pezframe_support::traits::Hooks<
#pezframe_system::pezpallet_prelude::BlockNumberFor::<T>
>
>::on_poll(n, weight);
}
}
impl<#type_impl_gen>
#pezframe_support::traits::OnInitialize<#pezframe_system::pezpallet_prelude::BlockNumberFor::<T>>
for #pezpallet_ident<#type_use_gen> #where_clause
{
fn on_initialize(
n: #pezframe_system::pezpallet_prelude::BlockNumberFor::<T>
) -> #pezframe_support::weights::Weight {
#pezframe_support::__private::pezsp_tracing::enter_span!(
#pezframe_support::__private::pezsp_tracing::trace_span!("on_initialize")
);
<
Self as #pezframe_support::traits::Hooks<
#pezframe_system::pezpallet_prelude::BlockNumberFor::<T>
>
>::on_initialize(n)
}
}
impl<#type_impl_gen>
#pezframe_support::traits::BeforeAllRuntimeMigrations
for #pezpallet_ident<#type_use_gen> #where_clause
{
fn before_all_runtime_migrations() -> #pezframe_support::weights::Weight {
use #pezframe_support::traits::{Get, PalletInfoAccess};
use #pezframe_support::__private::hashing::twox_128;
use #pezframe_support::storage::unhashed::contains_prefixed_key;
#pezframe_support::__private::pezsp_tracing::enter_span!(
#pezframe_support::__private::pezsp_tracing::trace_span!("before_all")
);
// Check if the pezpallet has any keys set, including the storage version. If there are
// no keys set, the pezpallet was just added to the runtime and needs to have its
// version initialized.
let pezpallet_hashed_prefix = <Self as PalletInfoAccess>::name_hash();
let exists = contains_prefixed_key(&pezpallet_hashed_prefix);
if !exists {
#initialize_on_chain_storage_version
<T as #pezframe_system::Config>::DbWeight::get().reads_writes(1, 1)
} else {
<T as #pezframe_system::Config>::DbWeight::get().reads(1)
}
}
}
impl<#type_impl_gen>
#pezframe_support::traits::OnRuntimeUpgrade
for #pezpallet_ident<#type_use_gen> #where_clause
{
fn on_runtime_upgrade() -> #pezframe_support::weights::Weight {
#pezframe_support::__private::pezsp_tracing::enter_span!(
#pezframe_support::__private::pezsp_tracing::trace_span!("on_runtime_update")
);
// log info about the upgrade.
#log_runtime_upgrade
<
Self as #pezframe_support::traits::Hooks<
#pezframe_system::pezpallet_prelude::BlockNumberFor::<T>
>
>::on_runtime_upgrade()
}
#pezframe_support::try_runtime_enabled! {
fn pre_upgrade() -> Result<#pezframe_support::__private::Vec<u8>, #pezframe_support::pezsp_runtime::TryRuntimeError> {
<
Self
as
#pezframe_support::traits::Hooks<#pezframe_system::pezpallet_prelude::BlockNumberFor::<T>>
>::pre_upgrade()
}
fn post_upgrade(state: #pezframe_support::__private::Vec<u8>) -> Result<(), #pezframe_support::pezsp_runtime::TryRuntimeError> {
#post_storage_version_check
<
Self
as
#pezframe_support::traits::Hooks<#pezframe_system::pezpallet_prelude::BlockNumberFor::<T>>
>::post_upgrade(state)
}
}
}
impl<#type_impl_gen>
#pezframe_support::traits::OffchainWorker<#pezframe_system::pezpallet_prelude::BlockNumberFor::<T>>
for #pezpallet_ident<#type_use_gen> #where_clause
{
fn offchain_worker(n: #pezframe_system::pezpallet_prelude::BlockNumberFor::<T>) {
<
Self as #pezframe_support::traits::Hooks<
#pezframe_system::pezpallet_prelude::BlockNumberFor::<T>
>
>::offchain_worker(n)
}
}
// Integrity tests are only required for when `std` is enabled.
#pezframe_support::std_enabled! {
impl<#type_impl_gen>
#pezframe_support::traits::IntegrityTest
for #pezpallet_ident<#type_use_gen> #where_clause
{
fn integrity_test() {
#pezframe_support::__private::pezsp_io::TestExternalities::default().execute_with(|| {
<
Self as #pezframe_support::traits::Hooks<
#pezframe_system::pezpallet_prelude::BlockNumberFor::<T>
>
>::integrity_test()
});
}
}
}
#pezframe_support::try_runtime_enabled! {
impl<#type_impl_gen>
#pezframe_support::traits::TryState<#pezframe_system::pezpallet_prelude::BlockNumberFor::<T>>
for #pezpallet_ident<#type_use_gen> #where_clause
{
fn try_state(
n: #pezframe_system::pezpallet_prelude::BlockNumberFor::<T>,
_s: #pezframe_support::traits::TryStateSelect
) -> Result<(), #pezframe_support::pezsp_runtime::TryRuntimeError> {
#pezframe_support::__private::log::info!(
target: #pezframe_support::LOG_TARGET,
"🩺 Running {:?} try-state checks",
#pezpallet_name,
);
<
Self as #pezframe_support::traits::Hooks<
#pezframe_system::pezpallet_prelude::BlockNumberFor::<T>
>
>::try_state(n).inspect_err(|err| {
#pezframe_support::__private::log::error!(
target: #pezframe_support::LOG_TARGET,
"❌ {:?} try_state checks failed: {:?}",
#pezpallet_name,
err
);
})
}
}
}
)
}
@@ -0,0 +1,55 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::{pezpallet::Def, COUNTER};
use proc_macro2::TokenStream;
use quote::quote;
use syn::{spanned::Spanned, Ident};
pub fn expand_inherents(def: &mut Def) -> TokenStream {
let count = COUNTER.with(|counter| counter.borrow_mut().inc());
let macro_ident = Ident::new(&format!("__is_inherent_part_defined_{}", count), def.item.span());
let maybe_compile_error = if def.inherent.is_none() {
quote! {
compile_error!(concat!(
"`",
stringify!($pezpallet_name),
"` does not have #[pezpallet::inherent] defined, perhaps you should \
remove `Inherent` from construct_runtime?",
));
}
} else {
TokenStream::new()
};
quote! {
#[doc(hidden)]
pub mod __bizinikiwi_inherent_check {
#[macro_export]
#[doc(hidden)]
macro_rules! #macro_ident {
($pezpallet_name:ident) => {
#maybe_compile_error
}
}
#[doc(hidden)]
pub use #macro_ident as is_inherent_part_defined;
}
}
}
@@ -0,0 +1,43 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::{pezpallet::Def, NUMBER_OF_INSTANCE};
use proc_macro2::Span;
///
/// * Provide inherent instance to be used by construct_runtime
/// * Provide Instance1 ..= Instance16 for instantiable pezpallet
pub fn expand_instances(def: &mut Def) -> proc_macro2::TokenStream {
let pezframe_support = &def.pezframe_support;
let inherent_ident = syn::Ident::new(crate::INHERENT_INSTANCE_NAME, Span::call_site());
let instances = if def.config.has_instance {
(1..=NUMBER_OF_INSTANCE)
.map(|i| syn::Ident::new(&format!("Instance{}", i), Span::call_site()))
.collect()
} else {
vec![]
};
quote::quote!(
/// Hidden instance generated to be internally used when module is used without
/// instance.
#[doc(hidden)]
pub type #inherent_ident = ();
#( pub use #pezframe_support::instances::#instances; )*
)
}
@@ -0,0 +1,136 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
mod call;
mod composite;
mod config;
mod constants;
mod doc_only;
mod documentation;
mod error;
mod event;
mod genesis_build;
mod genesis_config;
mod hooks;
mod inherent;
mod instances;
mod origin;
mod pezpallet_struct;
mod storage;
mod tasks;
mod tt_default_parts;
mod type_value;
mod validate_unsigned;
mod view_functions;
mod warnings;
use crate::pezpallet::Def;
use quote::ToTokens;
/// Merge where clause together, `where` token span is taken from the first not none one.
pub fn merge_where_clauses(clauses: &[&Option<syn::WhereClause>]) -> Option<syn::WhereClause> {
let mut clauses = clauses.iter().filter_map(|f| f.as_ref());
let mut res = clauses.next()?.clone();
for other in clauses {
res.predicates.extend(other.predicates.iter().cloned())
}
Some(res)
}
/// Expand definition, in particular:
/// * add some bounds and variants to type defined,
/// * create some new types,
/// * impl stuff on them.
pub fn expand(mut def: Def) -> proc_macro2::TokenStream {
// Remove the `pezpallet_doc` attribute first.
let metadata_docs = documentation::expand_documentation(&mut def);
let constants = constants::expand_constants(&mut def);
let pezpallet_struct = pezpallet_struct::expand_pallet_struct(&mut def);
let config = config::expand_config(&mut def);
let associated_types = config::expand_config_metadata(&def);
let call = call::expand_call(&mut def);
let tasks = tasks::expand_tasks(&mut def);
let error = error::expand_error(&mut def);
let event = event::expand_event(&mut def);
let storages = storage::expand_storages(&mut def);
let view_functions = view_functions::expand_view_functions(&def);
let inherents = inherent::expand_inherents(&mut def);
let instances = instances::expand_instances(&mut def);
let hooks = hooks::expand_hooks(&mut def);
let genesis_build = genesis_build::expand_genesis_build(&mut def);
let genesis_config = genesis_config::expand_genesis_config(&mut def);
let type_values = type_value::expand_type_values(&mut def);
let origin = origin::expand_origin(&mut def);
let validate_unsigned = validate_unsigned::expand_validate_unsigned(&mut def);
let tt_default_parts = tt_default_parts::expand_tt_default_parts(&mut def);
let doc_only = doc_only::expand_doc_only(&mut def);
let composites = composite::expand_composites(&mut def);
let warnings = def.config.warnings;
def.item.attrs.insert(
0,
syn::parse_quote!(
#[doc = r"The `pezpallet` module in each FRAME pezpallet hosts the most important items needed
to construct this pezpallet.
The main components of this pezpallet are:
- [`Pezpallet`], which implements all of the dispatchable extrinsics of the pezpallet, among
other public functions.
- The subset of the functions that are dispatchable can be identified either in the
[`dispatchables`] module or in the [`Call`] enum.
- [`storage_types`], which contains the list of all types that are representing a
storage item. Otherwise, all storage items are listed among [*Type Definitions*](#types).
- [`Config`], which contains the configuration trait of this pezpallet.
- [`Event`] and [`Error`], which are listed among the [*Enums*](#enums).
"]
),
);
let new_items = quote::quote!(
#(
#warnings
)*
#metadata_docs
#constants
#pezpallet_struct
#config
#associated_types
#call
#tasks
#error
#event
#storages
#view_functions
#inherents
#instances
#hooks
#genesis_build
#genesis_config
#type_values
#origin
#validate_unsigned
#tt_default_parts
#doc_only
#composites
);
let item = &mut def.item.content.as_mut().expect("This is checked by parsing").1;
item.push(syn::Item::Verbatim(new_items));
def.item.into_token_stream()
}
@@ -0,0 +1,56 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::{pezpallet::Def, COUNTER};
use proc_macro2::TokenStream;
use quote::quote;
use syn::{spanned::Spanned, Ident};
/// expand the `is_origin_part_defined` macro.
pub fn expand_origin(def: &mut Def) -> TokenStream {
let count = COUNTER.with(|counter| counter.borrow_mut().inc());
let macro_ident = Ident::new(&format!("__is_origin_part_defined_{}", count), def.item.span());
let maybe_compile_error = if def.origin.is_none() {
quote! {
compile_error!(concat!(
"`",
stringify!($pezpallet_name),
"` does not have #[pezpallet::origin] defined, perhaps you should \
remove `Origin` from construct_runtime?",
));
}
} else {
TokenStream::new()
};
quote! {
#[doc(hidden)]
pub mod __bizinikiwi_origin_check {
#[macro_export]
#[doc(hidden)]
macro_rules! #macro_ident {
($pezpallet_name:ident) => {
#maybe_compile_error
}
}
#[doc(hidden)]
pub use #macro_ident as is_origin_part_defined;
}
}
}
@@ -0,0 +1,308 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::pezpallet::{expand::merge_where_clauses, Def};
use pezframe_support_procedural_tools::get_doc_literals;
///
/// * Add derive trait on Pezpallet
/// * Implement GetStorageVersion on Pezpallet
/// * Implement OnGenesis on Pezpallet
/// * Implement `fn error_metadata` on Pezpallet
/// * declare Module type alias for construct_runtime
/// * replace the first field type of `struct Pezpallet` with `PhantomData` if it is `_`
/// * implementation of `PalletInfoAccess` information
/// * implementation of `StorageInfoTrait` on Pezpallet
pub fn expand_pallet_struct(def: &mut Def) -> proc_macro2::TokenStream {
let pezframe_support = &def.pezframe_support;
let pezframe_system = &def.pezframe_system;
let type_impl_gen = &def.type_impl_generics(def.pezpallet_struct.attr_span);
let type_use_gen = &def.type_use_generics(def.pezpallet_struct.attr_span);
let type_decl_gen = &def.type_decl_generics(def.pezpallet_struct.attr_span);
let pezpallet_ident = &def.pezpallet_struct.pezpallet;
let config_where_clause = &def.config.where_clause;
let deprecation_status =
match crate::deprecation::get_deprecation(&quote::quote! {#pezframe_support}, &def.item.attrs)
{
Ok(deprecation) => deprecation,
Err(e) => return e.into_compile_error(),
};
let mut storages_where_clauses = vec![&def.config.where_clause];
storages_where_clauses.extend(def.storages.iter().map(|storage| &storage.where_clause));
let storages_where_clauses = merge_where_clauses(&storages_where_clauses);
let pezpallet_item = {
let pezpallet_module_items = &mut def.item.content.as_mut().expect("Checked by def").1;
let item = &mut pezpallet_module_items[def.pezpallet_struct.index];
if let syn::Item::Struct(item) = item {
item
} else {
unreachable!("Checked by pezpallet struct parser")
}
};
// If the first field type is `_` then we replace with `PhantomData`
if let Some(field) = pezpallet_item.fields.iter_mut().next() {
if field.ty == syn::parse_quote!(_) {
field.ty = syn::parse_quote!(
core::marker::PhantomData<(#type_use_gen)>
);
}
}
if get_doc_literals(&pezpallet_item.attrs).is_empty() {
pezpallet_item.attrs.push(syn::parse_quote!(
#[doc = r"
The `Pezpallet` struct, the main type that implements traits and standalone
functions within the pezpallet.
"]
));
}
pezpallet_item.attrs.push(syn::parse_quote!(
#[derive(
#pezframe_support::CloneNoBound,
#pezframe_support::EqNoBound,
#pezframe_support::PartialEqNoBound,
#pezframe_support::RuntimeDebugNoBound,
)]
));
let pezpallet_error_metadata = if let Some(error_def) = &def.error {
let error_ident = &error_def.error;
quote::quote_spanned!(def.pezpallet_struct.attr_span =>
impl<#type_impl_gen> #pezpallet_ident<#type_use_gen> #config_where_clause {
#[doc(hidden)]
#[allow(deprecated)]
pub fn error_metadata() -> Option<#pezframe_support::__private::metadata_ir::PalletErrorMetadataIR> {
Some(<#error_ident<#type_use_gen>>::error_metadata())
}
}
)
} else {
quote::quote_spanned!(def.pezpallet_struct.attr_span =>
impl<#type_impl_gen> #pezpallet_ident<#type_use_gen> #config_where_clause {
#[doc(hidden)]
pub fn error_metadata() -> Option<#pezframe_support::__private::metadata_ir::PalletErrorMetadataIR> {
None
}
}
)
};
let storage_info_span =
def.pezpallet_struct.without_storage_info.unwrap_or(def.pezpallet_struct.attr_span);
let storage_names = &def.storages.iter().map(|storage| &storage.ident).collect::<Vec<_>>();
let storage_cfg_attrs =
&def.storages.iter().map(|storage| &storage.cfg_attrs).collect::<Vec<_>>();
let storage_maybe_allow_attrs = &def
.storages
.iter()
.map(|storage| crate::deprecation::extract_or_return_allow_attrs(&storage.attrs).collect())
.collect::<Vec<Vec<_>>>();
// Depending on the flag `without_storage_info` and the storage attribute `unbounded`, we use
// partial or full storage info from storage.
let storage_info_traits = &def
.storages
.iter()
.map(|storage| {
if storage.unbounded || def.pezpallet_struct.without_storage_info.is_some() {
quote::quote_spanned!(storage_info_span => PartialStorageInfoTrait)
} else {
quote::quote_spanned!(storage_info_span => StorageInfoTrait)
}
})
.collect::<Vec<_>>();
let storage_info_methods = &def
.storages
.iter()
.map(|storage| {
if storage.unbounded || def.pezpallet_struct.without_storage_info.is_some() {
quote::quote_spanned!(storage_info_span => partial_storage_info)
} else {
quote::quote_spanned!(storage_info_span => storage_info)
}
})
.collect::<Vec<_>>();
let storage_info = quote::quote_spanned!(storage_info_span =>
impl<#type_impl_gen> #pezframe_support::traits::StorageInfoTrait
for #pezpallet_ident<#type_use_gen>
#storages_where_clauses
{
fn storage_info()
-> #pezframe_support::__private::Vec<#pezframe_support::traits::StorageInfo>
{
#[allow(unused_mut)]
let mut res = #pezframe_support::__private::vec![];
#(
#(#storage_cfg_attrs)*
#(#storage_maybe_allow_attrs)*
{
let mut storage_info = <
#storage_names<#type_use_gen>
as #pezframe_support::traits::#storage_info_traits
>::#storage_info_methods();
res.append(&mut storage_info);
}
)*
res
}
}
);
let (storage_version, in_code_storage_version_ty) =
if let Some(v) = def.pezpallet_struct.storage_version.as_ref() {
(quote::quote! { #v }, quote::quote! { #pezframe_support::traits::StorageVersion })
} else {
(
quote::quote! { core::default::Default::default() },
quote::quote! { #pezframe_support::traits::NoStorageVersionSet },
)
};
let whitelisted_storage_idents: Vec<syn::Ident> = def
.storages
.iter()
.filter_map(|s| s.whitelisted.then(|| s.ident.clone()))
.collect();
let whitelisted_storage_keys_impl = quote::quote![
use #pezframe_support::traits::{StorageInfoTrait, TrackedStorageKey, WhitelistedStorageKeys};
impl<#type_impl_gen> WhitelistedStorageKeys for #pezpallet_ident<#type_use_gen> #storages_where_clauses {
fn whitelisted_storage_keys() -> #pezframe_support::__private::Vec<TrackedStorageKey> {
use #pezframe_support::__private::vec;
vec![#(
TrackedStorageKey::new(#whitelisted_storage_idents::<#type_use_gen>::hashed_key().to_vec())
),*]
}
}
];
quote::quote_spanned!(def.pezpallet_struct.attr_span =>
#pezpallet_error_metadata
/// Type alias to `Pezpallet`, to be used by `construct_runtime`.
///
/// Generated by `pezpallet` attribute macro.
#[deprecated(note = "use `Pezpallet` instead")]
#[allow(dead_code)]
pub type Module<#type_decl_gen> = #pezpallet_ident<#type_use_gen>;
// Implement `GetStorageVersion` for `Pezpallet`
impl<#type_impl_gen> #pezframe_support::traits::GetStorageVersion
for #pezpallet_ident<#type_use_gen>
#config_where_clause
{
type InCodeStorageVersion = #in_code_storage_version_ty;
fn in_code_storage_version() -> Self::InCodeStorageVersion {
#storage_version
}
fn on_chain_storage_version() -> #pezframe_support::traits::StorageVersion {
#pezframe_support::traits::StorageVersion::get::<Self>()
}
}
// Implement `OnGenesis` for `Pezpallet`
impl<#type_impl_gen> #pezframe_support::traits::OnGenesis
for #pezpallet_ident<#type_use_gen>
#config_where_clause
{
fn on_genesis() {
let storage_version: #pezframe_support::traits::StorageVersion = #storage_version;
storage_version.put::<Self>();
}
}
// Implement `PalletInfoAccess` for `Pezpallet`
impl<#type_impl_gen> #pezframe_support::traits::PalletInfoAccess
for #pezpallet_ident<#type_use_gen>
#config_where_clause
{
fn index() -> usize {
<
<T as #pezframe_system::Config>::PalletInfo as #pezframe_support::traits::PalletInfo
>::index::<Self>()
.expect("Pezpallet is part of the runtime because pezpallet `Config` trait is \
implemented by the runtime")
}
fn name() -> &'static str {
<
<T as #pezframe_system::Config>::PalletInfo as #pezframe_support::traits::PalletInfo
>::name::<Self>()
.expect("Pezpallet is part of the runtime because pezpallet `Config` trait is \
implemented by the runtime")
}
fn name_hash() -> [u8; 16] {
<
<T as #pezframe_system::Config>::PalletInfo as #pezframe_support::traits::PalletInfo
>::name_hash::<Self>()
.expect("Pezpallet is part of the runtime because pezpallet `Config` trait is \
implemented by the runtime")
}
fn module_name() -> &'static str {
<
<T as #pezframe_system::Config>::PalletInfo as #pezframe_support::traits::PalletInfo
>::module_name::<Self>()
.expect("Pezpallet is part of the runtime because pezpallet `Config` trait is \
implemented by the runtime")
}
fn crate_version() -> #pezframe_support::traits::CrateVersion {
#pezframe_support::crate_to_crate_version!()
}
}
impl<#type_impl_gen> #pezframe_support::traits::PalletsInfoAccess
for #pezpallet_ident<#type_use_gen>
#config_where_clause
{
fn count() -> usize { 1 }
fn infos() -> #pezframe_support::__private::Vec<#pezframe_support::traits::PalletInfoData> {
use #pezframe_support::traits::PalletInfoAccess;
let item = #pezframe_support::traits::PalletInfoData {
index: Self::index(),
name: Self::name(),
module_name: Self::module_name(),
crate_version: Self::crate_version(),
};
#pezframe_support::__private::vec![item]
}
}
#storage_info
#whitelisted_storage_keys_impl
impl<#type_use_gen> #pezpallet_ident<#type_use_gen> {
#[allow(dead_code)]
#[doc(hidden)]
pub fn deprecation_info() -> #pezframe_support::__private::metadata_ir::ItemDeprecationInfoIR {
#deprecation_status
}
}
)
}
@@ -0,0 +1,946 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::{
counter_prefix,
deprecation::extract_or_return_allow_attrs,
pezpallet::{
parse::{
helper::two128_str,
storage::{Metadata, QueryKind, StorageDef, StorageGenerics},
},
Def,
},
};
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.
fn prefix_ident(storage: &StorageDef) -> syn::Ident {
let storage_ident = &storage.ident;
syn::Ident::new(&format!("_GeneratedPrefixForStorage{}", storage_ident), storage_ident.span())
}
/// Generate the counter_prefix_ident related to the storage.
/// counter_prefix_ident is used for the prefix struct to be given to counted storage map.
fn counter_prefix_ident(storage_ident: &syn::Ident) -> syn::Ident {
syn::Ident::new(
&format!("_GeneratedCounterPrefixForStorage{}", storage_ident),
storage_ident.span(),
)
}
/// Check for duplicated storage prefixes. This step is necessary since users can specify an
/// alternative storage prefix using the #[pezpallet::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,
// A hashmap of all already used prefix and their associated error if duplication
used_prefixes: &mut HashMap<String, syn::Error>,
) -> syn::Result<()> {
let prefix = storage_def.prefix();
let dup_err = syn::Error::new(
storage_def.prefix_span(),
format!("Duplicate storage prefixes found for `{}`", prefix),
);
if let Some(other_dup_err) = used_prefixes.insert(prefix.clone(), dup_err.clone()) {
let mut err = dup_err;
err.combine(other_dup_err);
return Err(err);
}
if let Metadata::CountedMap { .. } = storage_def.metadata {
let counter_prefix = counter_prefix(&prefix);
let counter_dup_err = syn::Error::new(
storage_def.prefix_span(),
format!(
"Duplicate storage prefixes found for `{}`, used for counter associated to \
counted storage map",
counter_prefix,
),
);
if let Some(other_dup_err) = used_prefixes.insert(counter_prefix, counter_dup_err.clone()) {
let mut err = counter_dup_err;
err.combine(other_dup_err);
return Err(err);
}
}
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 pezpallet 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<Vec<ResultOnEmptyStructMetadata>> {
let pezframe_support = &def.pezframe_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];
let typ_item = match item {
syn::Item::Type(t) => t,
_ => unreachable!("Checked by def"),
};
typ_item.attrs.push(syn::parse_quote!(#[allow(type_alias_bounds)]));
let typ_path = match &mut *typ_item.ty {
syn::Type::Path(p) => p,
_ => unreachable!("Checked by def"),
};
let args = match &mut typ_path.path.segments[0].arguments {
syn::PathArguments::AngleBracketed(args) => args,
_ => unreachable!("Checked by def"),
};
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 {
quote::quote_spanned!(storage_def.attr_span => T)
};
let default_query_kind: syn::Type =
syn::parse_quote!(#pezframe_support::storage::types::OptionQuery);
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!(#pezframe_support::traits::GetDefault)
};
let default_max_values: syn::Type = syn::parse_quote!(#pezframe_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 pezpallet::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.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(value));
args.args.push(syn::GenericArgument::Type(on_empty));
},
StorageGenerics::Map { hasher, key, value, query_kind, on_empty, max_values } |
StorageGenerics::CountedMap {
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.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(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::DoubleMap {
hasher1,
key1,
hasher2,
key2,
value,
query_kind,
on_empty,
max_values,
} => {
args.args.push(syn::GenericArgument::Type(hasher1));
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.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(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 } |
StorageGenerics::CountedNMap {
keygen,
value,
query_kind,
on_empty,
max_values,
} => {
args.args.push(syn::GenericArgument::Type(keygen));
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(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));
},
}
} 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 { .. } | Metadata::CountedNMap { .. } => (2, 3, 4),
Metadata::Map { .. } | Metadata::CountedMap { .. } => (3, 4, 5),
Metadata::DoubleMap { .. } => (5, 6, 7),
};
if storage_def.use_default_hasher {
let hasher_indices: Vec<usize> = match storage_def.metadata {
Metadata::Map { .. } | Metadata::CountedMap { .. } => vec![1],
Metadata::DoubleMap { .. } => vec![1, 3],
_ => vec![],
};
for hasher_idx in hasher_indices {
args.args[hasher_idx] = syn::GenericArgument::Type(
syn::parse_quote!(#pezframe_support::Blake2_128Concat),
);
}
}
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(on_empty_struct_metadata)
}
fn augment_final_docs(def: &mut Def) {
// expand the docs with a new line showing the storage type (value, map, double map, etc), and
// the key/value type(s).
let mut push_string_literal = |doc_line: &str, storage: &mut StorageDef| {
let item = &mut def.item.content.as_mut().expect("Checked by def").1[storage.index];
let typ_item = match item {
syn::Item::Type(t) => t,
_ => unreachable!("Checked by def"),
};
typ_item.attrs.push(syn::parse_quote!(#[doc = ""]));
typ_item.attrs.push(syn::parse_quote!(#[doc = #doc_line]));
};
def.storages.iter_mut().for_each(|storage| match &storage.metadata {
Metadata::Value { value } => {
let doc_line = format!(
"Storage type is [`StorageValue`] with value type `{}`.",
value.to_token_stream()
);
push_string_literal(&doc_line, storage);
},
Metadata::Map { key, value } => {
let doc_line = format!(
"Storage type is [`StorageMap`] with key type `{}` and value type `{}`.",
key.to_token_stream(),
value.to_token_stream()
);
push_string_literal(&doc_line, storage);
},
Metadata::DoubleMap { key1, key2, value } => {
let doc_line = format!(
"Storage type is [`StorageDoubleMap`] with key1 type {}, key2 type {} and value type {}.",
key1.to_token_stream(),
key2.to_token_stream(),
value.to_token_stream()
);
push_string_literal(&doc_line, storage);
},
Metadata::NMap { keys, value, .. } => {
let doc_line = format!(
"Storage type is [`StorageNMap`] with keys type ({}) and value type {}.",
keys.iter()
.map(|k| k.to_token_stream().to_string())
.collect::<Vec<_>>()
.join(", "),
value.to_token_stream()
);
push_string_literal(&doc_line, storage);
},
Metadata::CountedNMap { keys, value, .. } => {
let doc_line = format!(
"Storage type is [`CountedStorageNMap`] with keys type ({}) and value type {}.",
keys.iter()
.map(|k| k.to_token_stream().to_string())
.collect::<Vec<_>>()
.join(", "),
value.to_token_stream()
);
push_string_literal(&doc_line, storage);
},
Metadata::CountedMap { key, value } => {
let doc_line = format!(
"Storage type is [`CountedStorageMap`] with key type {} and value type {}.",
key.to_token_stream(),
value.to_token_stream()
);
push_string_literal(&doc_line, storage);
},
});
}
///
/// * generate StoragePrefix structs (e.g. for a storage `MyStorage` a struct with the name
/// `_GeneratedPrefixForStorage$NameOfStorage` is generated) and implements StorageInstance trait.
/// * 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)]` on storages type alias
/// * generate metadatas
pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream {
let on_empty_struct_metadata = match process_generics(def) {
Ok(idents) => idents,
Err(e) => return e.into_compile_error(),
};
augment_final_docs(def);
// Check for duplicate prefixes
let mut prefix_set = HashMap::new();
let mut errors = def
.storages
.iter()
.filter_map(|storage_def| check_prefix_duplicates(storage_def, &mut prefix_set).err());
if let Some(mut final_error) = errors.next() {
errors.for_each(|error| final_error.combine(error));
return final_error.into_compile_error();
}
let pezframe_support = &def.pezframe_support;
let pezframe_system = &def.pezframe_system;
let pezpallet_ident = &def.pezpallet_struct.pezpallet;
let mut entries_builder = vec![];
for storage in def.storages.iter() {
let no_docs = vec![];
let docs = if cfg!(feature = "no-metadata-docs") { &no_docs } else { &storage.docs };
let ident = &storage.ident;
let gen = &def.type_use_generics(storage.attr_span);
let full_ident = quote::quote_spanned!(storage.attr_span => #ident<#gen> );
let cfg_attrs = &storage.cfg_attrs;
let deprecation = match crate::deprecation::get_deprecation(
&quote::quote! { #pezframe_support },
&storage.attrs,
) {
Ok(deprecation) => deprecation,
Err(e) => return e.into_compile_error(),
};
// Extracts #[allow] attributes, necessary so that we don't run into compiler warnings
let maybe_allow_attrs: Vec<syn::Attribute> =
extract_or_return_allow_attrs(&storage.attrs).collect();
entries_builder.push(quote::quote_spanned!(storage.attr_span =>
#(#cfg_attrs)*
#(#maybe_allow_attrs)*
(|entries: &mut #pezframe_support::__private::Vec<_>| {
{
<#full_ident as #pezframe_support::storage::StorageEntryMetadataBuilder>::build_metadata(
#deprecation,
#pezframe_support::__private::vec![
#( #docs, )*
],
entries,
);
}
})
))
}
let getters = def.storages.iter().map(|storage| {
if let Some(getter) = &storage.getter {
let completed_where_clause =
super::merge_where_clauses(&[&storage.where_clause, &def.config.where_clause]);
// Extracts #[allow] attributes, necessary so that we don't run into compiler warnings
let maybe_allow_attrs: Vec<syn::Attribute> =
extract_or_return_allow_attrs(&storage.attrs).collect();
let ident = &storage.ident;
let gen = &def.type_use_generics(storage.attr_span);
let type_impl_gen = &def.type_impl_generics(storage.attr_span);
let type_use_gen = &def.type_use_generics(storage.attr_span);
let full_ident = quote::quote_spanned!(storage.attr_span => #ident<#gen> );
let cfg_attrs = &storage.cfg_attrs;
// If the storage item is public, link it and otherwise just mention it.
//
// We can not just copy the docs from a non-public type as it may links to internal
// types which makes the compiler very unhappy :(
let getter_doc_line = if matches!(storage.vis, syn::Visibility::Public(_)) {
format!("An auto-generated getter for [`{}`].", storage.ident)
} else {
format!("An auto-generated getter for `{}`.", storage.ident)
};
match &storage.metadata {
Metadata::Value { value } => {
let query = match storage.query_kind.as_ref().expect("Checked by def") {
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 =>
#(#cfg_attrs)*
impl<#type_impl_gen> #pezpallet_ident<#type_use_gen> #completed_where_clause {
#[doc = #getter_doc_line]
#(#maybe_allow_attrs)*
pub fn #getter() -> #query {
<
#full_ident as #pezframe_support::storage::StorageValue<#value>
>::get()
}
}
)
},
Metadata::Map { key, value } => {
let query = match storage.query_kind.as_ref().expect("Checked by def") {
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 =>
#(#cfg_attrs)*
impl<#type_impl_gen> #pezpallet_ident<#type_use_gen> #completed_where_clause {
#[doc = #getter_doc_line]
#(#maybe_allow_attrs)*
pub fn #getter<KArg>(k: KArg) -> #query where
KArg: #pezframe_support::__private::codec::EncodeLike<#key>,
{
<
#full_ident as #pezframe_support::storage::StorageMap<#key, #value>
>::get(k)
}
}
)
},
Metadata::CountedMap { key, value } => {
let query = match storage.query_kind.as_ref().expect("Checked by def") {
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 =>
#(#cfg_attrs)*
impl<#type_impl_gen> #pezpallet_ident<#type_use_gen> #completed_where_clause {
#[doc = #getter_doc_line]
#(#maybe_allow_attrs)*
pub fn #getter<KArg>(k: KArg) -> #query where
KArg: #pezframe_support::__private::codec::EncodeLike<#key>,
{
// NOTE: we can't use any trait here because CountedStorageMap
// doesn't implement any.
<#full_ident>::get(k)
}
}
)
},
Metadata::DoubleMap { key1, key2, value } => {
let query = match storage.query_kind.as_ref().expect("Checked by def") {
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 =>
#(#cfg_attrs)*
impl<#type_impl_gen> #pezpallet_ident<#type_use_gen> #completed_where_clause {
#[doc = #getter_doc_line]
#(#maybe_allow_attrs)*
pub fn #getter<KArg1, KArg2>(k1: KArg1, k2: KArg2) -> #query where
KArg1: #pezframe_support::__private::codec::EncodeLike<#key1>,
KArg2: #pezframe_support::__private::codec::EncodeLike<#key2>,
{
<
#full_ident as
#pezframe_support::storage::StorageDoubleMap<#key1, #key2, #value>
>::get(k1, k2)
}
}
)
},
Metadata::NMap { keygen, value, .. } => {
let query = match storage.query_kind.as_ref().expect("Checked by def") {
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 =>
#(#cfg_attrs)*
impl<#type_impl_gen> #pezpallet_ident<#type_use_gen> #completed_where_clause {
#[doc = #getter_doc_line]
#(#maybe_allow_attrs)*
pub fn #getter<KArg>(key: KArg) -> #query
where
KArg: #pezframe_support::storage::types::EncodeLikeTuple<
<#keygen as #pezframe_support::storage::types::KeyGenerator>::KArg
>
+ #pezframe_support::storage::types::TupleToEncodedIter,
{
<
#full_ident as
#pezframe_support::storage::StorageNMap<#keygen, #value>
>::get(key)
}
}
)
},
Metadata::CountedNMap { keygen, value, .. } => {
let query = match storage.query_kind.as_ref().expect("Checked by def") {
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 =>
#(#cfg_attrs)*
impl<#type_impl_gen> #pezpallet_ident<#type_use_gen> #completed_where_clause {
#[doc = #getter_doc_line]
#(#maybe_allow_attrs)*
pub fn #getter<KArg>(key: KArg) -> #query
where
KArg: #pezframe_support::storage::types::EncodeLikeTuple<
<#keygen as #pezframe_support::storage::types::KeyGenerator>::KArg
>
+ #pezframe_support::storage::types::TupleToEncodedIter,
{
// NOTE: we can't use any trait here because CountedStorageNMap
// doesn't implement any.
<#full_ident>::get(key)
}
}
)
},
}
} else {
Default::default()
}
});
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);
let prefix_struct_vis = &storage_def.vis;
let prefix_struct_const = storage_def.prefix();
let config_where_clause = &def.config.where_clause;
let cfg_attrs = &storage_def.cfg_attrs;
let maybe_counter = match storage_def.metadata {
Metadata::CountedMap { .. } => {
let counter_prefix_struct_ident = counter_prefix_ident(&storage_def.ident);
let counter_prefix_struct_const = counter_prefix(&prefix_struct_const);
let storage_prefix_hash = two128_str(&counter_prefix_struct_const);
quote::quote_spanned!(storage_def.attr_span =>
#(#cfg_attrs)*
#[doc(hidden)]
#prefix_struct_vis struct #counter_prefix_struct_ident<#type_use_gen>(
core::marker::PhantomData<(#type_use_gen,)>
);
#(#cfg_attrs)*
impl<#type_impl_gen> #pezframe_support::traits::StorageInstance
for #counter_prefix_struct_ident<#type_use_gen>
#config_where_clause
{
fn pezpallet_prefix() -> &'static str {
<
<T as #pezframe_system::Config>::PalletInfo
as #pezframe_support::traits::PalletInfo
>::name::<Pezpallet<#type_use_gen>>()
.expect("No name found for the pezpallet in the runtime! This usually means that the pezpallet wasn't added to `construct_runtime!`.")
}
fn pezpallet_prefix_hash() -> [u8; 16] {
<
<T as #pezframe_system::Config>::PalletInfo
as #pezframe_support::traits::PalletInfo
>::name_hash::<Pezpallet<#type_use_gen>>()
.expect("No name_hash found for the pezpallet in the runtime! This usually means that the pezpallet wasn't added to `construct_runtime!`.")
}
const STORAGE_PREFIX: &'static str = #counter_prefix_struct_const;
fn storage_prefix_hash() -> [u8; 16] {
#storage_prefix_hash
}
}
#(#cfg_attrs)*
impl<#type_impl_gen> #pezframe_support::storage::types::CountedStorageMapInstance
for #prefix_struct_ident<#type_use_gen>
#config_where_clause
{
type CounterPrefix = #counter_prefix_struct_ident<#type_use_gen>;
}
)
},
Metadata::CountedNMap { .. } => {
let counter_prefix_struct_ident = counter_prefix_ident(&storage_def.ident);
let counter_prefix_struct_const = counter_prefix(&prefix_struct_const);
let storage_prefix_hash = two128_str(&counter_prefix_struct_const);
quote::quote_spanned!(storage_def.attr_span =>
#(#cfg_attrs)*
#[doc(hidden)]
#prefix_struct_vis struct #counter_prefix_struct_ident<#type_use_gen>(
core::marker::PhantomData<(#type_use_gen,)>
);
#(#cfg_attrs)*
impl<#type_impl_gen> #pezframe_support::traits::StorageInstance
for #counter_prefix_struct_ident<#type_use_gen>
#config_where_clause
{
fn pezpallet_prefix() -> &'static str {
<
<T as #pezframe_system::Config>::PalletInfo
as #pezframe_support::traits::PalletInfo
>::name::<Pezpallet<#type_use_gen>>()
.expect("No name found for the pezpallet in the runtime! This usually means that the pezpallet wasn't added to `construct_runtime!`.")
}
fn pezpallet_prefix_hash() -> [u8; 16] {
<
<T as #pezframe_system::Config>::PalletInfo
as #pezframe_support::traits::PalletInfo
>::name_hash::<Pezpallet<#type_use_gen>>()
.expect("No name_hash found for the pezpallet in the runtime! This usually means that the pezpallet wasn't added to `construct_runtime!`.")
}
const STORAGE_PREFIX: &'static str = #counter_prefix_struct_const;
fn storage_prefix_hash() -> [u8; 16] {
#storage_prefix_hash
}
}
#(#cfg_attrs)*
impl<#type_impl_gen> #pezframe_support::storage::types::CountedStorageNMapInstance
for #prefix_struct_ident<#type_use_gen>
#config_where_clause
{
type CounterPrefix = #counter_prefix_struct_ident<#type_use_gen>;
}
)
},
_ => proc_macro2::TokenStream::default(),
};
let storage_prefix_hash = two128_str(&prefix_struct_const);
quote::quote_spanned!(storage_def.attr_span =>
#maybe_counter
#(#cfg_attrs)*
#[doc(hidden)]
#prefix_struct_vis struct #prefix_struct_ident<#type_use_gen>(
core::marker::PhantomData<(#type_use_gen,)>
);
#(#cfg_attrs)*
impl<#type_impl_gen> #pezframe_support::traits::StorageInstance
for #prefix_struct_ident<#type_use_gen>
#config_where_clause
{
fn pezpallet_prefix() -> &'static str {
<
<T as #pezframe_system::Config>::PalletInfo
as #pezframe_support::traits::PalletInfo
>::name::<Pezpallet<#type_use_gen>>()
.expect("No name found for the pezpallet in the runtime! This usually means that the pezpallet wasn't added to `construct_runtime!`.")
}
fn pezpallet_prefix_hash() -> [u8; 16] {
<
<T as #pezframe_system::Config>::PalletInfo
as #pezframe_support::traits::PalletInfo
>::name_hash::<Pezpallet<#type_use_gen>>()
.expect("No name_hash found for the pezpallet in the runtime! This usually means that the pezpallet wasn't added to `construct_runtime!`.")
}
const STORAGE_PREFIX: &'static str = #prefix_struct_const;
fn storage_prefix_hash() -> [u8; 16] {
#storage_prefix_hash
}
}
)
});
let on_empty_structs = on_empty_struct_metadata.into_iter().map(|metadata| {
use crate::pezpallet::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> #pezframe_support::traits::Get<Result<#value_ty, #error_path>>
for #name
#config_where_clause
{
#[allow(deprecated)]
fn get() -> Result<#value_ty, #error_path> {
Err(<#error_path>::#variant_name)
}
}
)
});
// aggregated where clause of all storage types and the whole pezpallet.
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);
let type_impl_gen = &def.type_impl_generics(proc_macro2::Span::call_site());
let type_use_gen = &def.type_use_generics(proc_macro2::Span::call_site());
let try_decode_entire_state = {
let mut storage_names = def
.storages
.iter()
.filter_map(|storage| {
// A little hacky; don't generate for cfg gated storages to not get compile errors
// when building "frame-feature-testing" gated storages in the "pezframe-support-test"
// crate.
if storage.try_decode && storage.cfg_attrs.is_empty() {
let ident = &storage.ident;
let gen = &def.type_use_generics(storage.attr_span);
Some(quote::quote_spanned!(storage.attr_span => #ident<#gen> ))
} else {
None
}
})
.collect::<Vec<_>>();
storage_names.sort_by_cached_key(|ident| ident.to_string());
quote::quote!(
#pezframe_support::try_runtime_enabled! {
#[allow(deprecated)]
impl<#type_impl_gen> #pezframe_support::traits::TryDecodeEntireStorage
for #pezpallet_ident<#type_use_gen> #completed_where_clause
{
fn try_decode_entire_state() -> Result<usize, #pezframe_support::__private::Vec<#pezframe_support::traits::TryDecodeEntireStorageError>> {
let pezpallet_name = <<T as #pezframe_system::Config>::PalletInfo as #pezframe_support::traits::PalletInfo>
::name::<#pezpallet_ident<#type_use_gen>>()
.expect("Every active pezpallet has a name in the runtime; qed");
#pezframe_support::__private::log::debug!(target: "runtime::try-decode-state", "trying to decode pezpallet: {pezpallet_name}");
// NOTE: for now, we have to exclude storage items that are feature gated.
let mut errors = #pezframe_support::__private::Vec::new();
let mut decoded = 0usize;
#(
#pezframe_support::__private::log::debug!(target: "runtime::try-decode-state", "trying to decode storage: \
{pezpallet_name}::{}", stringify!(#storage_names));
match <#storage_names as #pezframe_support::traits::TryDecodeEntireStorage>::try_decode_entire_state() {
Ok(count) => {
decoded += count;
},
Err(err) => {
errors.extend(err);
},
}
)*
if errors.is_empty() {
Ok(decoded)
} else {
Err(errors)
}
}
}
}
)
};
quote::quote!(
impl<#type_impl_gen> #pezpallet_ident<#type_use_gen>
#completed_where_clause
{
#[doc(hidden)]
pub fn storage_metadata() -> #pezframe_support::__private::metadata_ir::PalletStorageMetadataIR {
#pezframe_support::__private::metadata_ir::PalletStorageMetadataIR {
prefix: <
<T as #pezframe_system::Config>::PalletInfo as
#pezframe_support::traits::PalletInfo
>::name::<#pezpallet_ident<#type_use_gen>>()
.expect("No name found for the pezpallet in the runtime! This usually means that the pezpallet wasn't added to `construct_runtime!`."),
entries: {
#[allow(unused_mut)]
let mut entries = #pezframe_support::__private::vec![];
#( #entries_builder(&mut entries); )*
entries
},
}
}
}
#( #getters )*
#( #prefix_structs )*
#( #on_empty_structs )*
#try_decode_entire_state
)
}
@@ -0,0 +1,232 @@
//! Contains logic for expanding task-related items.
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Home of the expansion code for the Tasks API
use crate::pezpallet::{parse::tasks::*, Def};
use inflector::Inflector;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote, ToTokens};
use syn::{parse_quote_spanned, spanned::Spanned};
impl TaskEnumDef {
/// Since we optionally allow users to manually specify a `#[pezpallet::task_enum]`, in the
/// event they _don't_ specify one (which is actually the most common behavior) we have to
/// generate one based on the existing [`TasksDef`]. This method performs that generation.
pub fn generate(tasks: &TasksDef, def: &Def) -> Self {
// We use the span of the attribute to indicate that the error comes from code generated
// for the specific section, otherwise the item impl.
let span = tasks
.tasks_attr
.as_ref()
.map_or_else(|| tasks.item_impl.span(), |attr| attr.span());
let type_decl_bounded_generics = def.type_decl_bounded_generics(span);
let variants = if tasks.tasks_attr.is_some() {
tasks
.tasks
.iter()
.map(|task| {
let ident = &task.item.sig.ident;
let ident =
format_ident!("{}", ident.to_string().to_class_case(), span = ident.span());
let args = task.item.sig.inputs.iter().collect::<Vec<_>>();
if args.is_empty() {
quote!(#ident)
} else {
quote!(#ident {
#(#args),*
})
}
})
.collect::<Vec<_>>()
} else {
Vec::new()
};
parse_quote_spanned! { span =>
/// Auto-generated enum that encapsulates all tasks defined by this pezpallet.
///
/// Conceptually similar to the [`Call`] enum, but for tasks. This is only
/// generated if there are tasks present in this pezpallet.
#[pezpallet::task_enum]
pub enum Task<#type_decl_bounded_generics> {
#(
#variants,
)*
}
}
}
}
impl TaskEnumDef {
fn expand_to_tokens(&self, def: &Def) -> TokenStream2 {
if let Some(attr) = &self.attr {
let ident = &self.item_enum.ident;
let vis = &self.item_enum.vis;
let attrs = &self.item_enum.attrs;
let generics = &self.item_enum.generics;
let variants = &self.item_enum.variants;
let pezframe_support = &def.pezframe_support;
let type_use_generics = &def.type_use_generics(attr.span());
let type_impl_generics = &def.type_impl_generics(attr.span());
// `item_enum` is short-hand / generated enum
quote! {
#(#attrs)*
#[derive(
#pezframe_support::CloneNoBound,
#pezframe_support::EqNoBound,
#pezframe_support::PartialEqNoBound,
#pezframe_support::pezpallet_prelude::Encode,
#pezframe_support::pezpallet_prelude::Decode,
#pezframe_support::pezpallet_prelude::DecodeWithMemTracking,
#pezframe_support::pezpallet_prelude::TypeInfo,
)]
#[codec(encode_bound())]
#[codec(decode_bound())]
#[scale_info(skip_type_params(#type_use_generics))]
#vis enum #ident #generics {
#variants
#[doc(hidden)]
#[codec(skip)]
__Ignore(core::marker::PhantomData<(#type_use_generics)>, #pezframe_support::Never),
}
impl<#type_impl_generics> core::fmt::Debug for #ident<#type_use_generics> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct(stringify!(#ident)).field("value", self).finish()
}
}
}
} else {
// `item_enum` is a manually specified enum (no attribute)
self.item_enum.to_token_stream()
}
}
}
impl TasksDef {
fn expand_to_tokens(&self, def: &Def) -> TokenStream2 {
let pezframe_support = &def.pezframe_support;
let enum_ident = syn::Ident::new("Task", self.enum_ident.span());
let enum_arguments = &self.enum_arguments;
let enum_use = quote!(#enum_ident #enum_arguments);
let task_fn_idents = self
.tasks
.iter()
.map(|task| {
format_ident!(
"{}",
&task.item.sig.ident.to_string().to_class_case(),
span = task.item.sig.ident.span()
)
})
.collect::<Vec<_>>();
let task_indices = self.tasks.iter().map(|task| &task.index_attr.meta.index);
let task_conditions = self.tasks.iter().map(|task| &task.condition_attr.meta.expr);
let task_weights = self.tasks.iter().map(|task| &task.weight_attr.meta.expr);
let task_iters = self.tasks.iter().map(|task| &task.list_attr.meta.expr);
let task_fn_impls = self.tasks.iter().map(|task| {
let mut task_fn_impl = task.item.clone();
task_fn_impl.attrs = vec![];
task_fn_impl
});
let task_fn_names = self.tasks.iter().map(|task| &task.item.sig.ident);
let task_arg_names = self.tasks.iter().map(|task| &task.arg_names).collect::<Vec<_>>();
let impl_generics = &self.item_impl.generics;
quote! {
impl #impl_generics #enum_use
{
#(#task_fn_impls)*
}
impl #impl_generics #pezframe_support::traits::Task for #enum_use
{
type Enumeration = #pezframe_support::__private::IntoIter<#enum_use>;
fn iter() -> Self::Enumeration {
let mut all_tasks = #pezframe_support::__private::vec![];
#(all_tasks
.extend(#task_iters.map(|(#(#task_arg_names),*)| #enum_ident::#task_fn_idents { #(#task_arg_names: #task_arg_names.clone()),* })
.collect::<#pezframe_support::__private::Vec<_>>());
)*
all_tasks.into_iter()
}
fn task_index(&self) -> u32 {
match self.clone() {
#(#enum_ident::#task_fn_idents { .. } => #task_indices,)*
Task::__Ignore(_, _) => unreachable!(),
}
}
fn is_valid(&self) -> bool {
match self.clone() {
#(#enum_ident::#task_fn_idents { #(#task_arg_names),* } => (#task_conditions)(#(#task_arg_names),* ),)*
Task::__Ignore(_, _) => unreachable!(),
}
}
fn run(&self) -> Result<(), #pezframe_support::pezpallet_prelude::DispatchError> {
match self.clone() {
#(#enum_ident::#task_fn_idents { #(#task_arg_names),* } => {
<#enum_use>::#task_fn_names(#( #task_arg_names, )* )
},)*
Task::__Ignore(_, _) => unreachable!(),
}
}
#[allow(unused_variables)]
fn weight(&self) -> #pezframe_support::pezpallet_prelude::Weight {
match self.clone() {
#(#enum_ident::#task_fn_idents { #(#task_arg_names),* } => #task_weights,)*
Task::__Ignore(_, _) => unreachable!(),
}
}
}
}
}
}
/// Generate code related to tasks.
pub fn expand_tasks(def: &Def) -> TokenStream2 {
let Some(tasks_def) = &def.tasks else {
return quote!();
};
let default_task_enum = TaskEnumDef::generate(&tasks_def, def);
let task_enum = def.task_enum.as_ref().unwrap_or_else(|| &default_task_enum);
let tasks_expansion = tasks_def.expand_to_tokens(def);
let task_enum_expansion = task_enum.expand_to_tokens(def);
quote! {
#tasks_expansion
#task_enum_expansion
}
}
@@ -0,0 +1,216 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::{
pezpallet::{CompositeKeyword, Def},
COUNTER,
};
use syn::spanned::Spanned;
/// Generate the `tt_default_parts` macro.
pub fn expand_tt_default_parts(def: &mut Def) -> proc_macro2::TokenStream {
let count = COUNTER.with(|counter| counter.borrow_mut().inc());
let default_parts_unique_id =
syn::Ident::new(&format!("__tt_default_parts_{}", count), def.item.span());
let extra_parts_unique_id =
syn::Ident::new(&format!("__tt_extra_parts_{}", count), def.item.span());
let default_parts_unique_id_v2 =
syn::Ident::new(&format!("__tt_default_parts_v2_{}", count), def.item.span());
let call_part = def.call.as_ref().map(|_| quote::quote!(Call,));
let task_part = def.tasks.as_ref().map(|_| quote::quote!(Task,));
let storage_part = (!def.storages.is_empty()).then(|| quote::quote!(Storage,));
let event_part = def.event.as_ref().map(|event| {
let gen = event.gen_kind.is_generic().then(|| quote::quote!( <T> ));
quote::quote!( Event #gen , )
});
let error_part = def.error.as_ref().map(|_| quote::quote!(Error<T>,));
let origin_part = def.origin.as_ref().map(|origin| {
let gen = origin.is_generic.then(|| quote::quote!( <T> ));
quote::quote!( Origin #gen , )
});
let config_part = def.genesis_config.as_ref().map(|genesis_config| {
let gen = genesis_config.gen_kind.is_generic().then(|| quote::quote!( <T> ));
quote::quote!( Config #gen , )
});
let inherent_part = def.inherent.as_ref().map(|_| quote::quote!(Inherent,));
let validate_unsigned_part =
def.validate_unsigned.as_ref().map(|_| quote::quote!(ValidateUnsigned,));
let freeze_reason_part = def
.composites
.iter()
.any(|c| matches!(c.composite_keyword, CompositeKeyword::FreezeReason(_)))
.then_some(quote::quote!(FreezeReason,));
let hold_reason_part = def
.composites
.iter()
.any(|c| matches!(c.composite_keyword, CompositeKeyword::HoldReason(_)))
.then_some(quote::quote!(HoldReason,));
let lock_id_part = def
.composites
.iter()
.any(|c| matches!(c.composite_keyword, CompositeKeyword::LockId(_)))
.then_some(quote::quote!(LockId,));
let slash_reason_part = def
.composites
.iter()
.any(|c| matches!(c.composite_keyword, CompositeKeyword::SlashReason(_)))
.then_some(quote::quote!(SlashReason,));
let call_part_v2 = def.call.as_ref().map(|_| quote::quote!(+ Call));
let task_part_v2 = def.tasks.as_ref().map(|_| quote::quote!(+ Task));
let storage_part_v2 = (!def.storages.is_empty()).then(|| quote::quote!(+ Storage));
let event_part_v2 = def.event.as_ref().map(|event| {
let gen = event.gen_kind.is_generic().then(|| quote::quote!(<T>));
quote::quote!(+ Event #gen)
});
let error_part_v2 = def.error.as_ref().map(|_| quote::quote!(+ Error<T>));
let origin_part_v2 = def.origin.as_ref().map(|origin| {
let gen = origin.is_generic.then(|| quote::quote!(<T>));
quote::quote!(+ Origin #gen)
});
let config_part_v2 = def.genesis_config.as_ref().map(|genesis_config| {
let gen = genesis_config.gen_kind.is_generic().then(|| quote::quote!(<T>));
quote::quote!(+ Config #gen)
});
let inherent_part_v2 = def.inherent.as_ref().map(|_| quote::quote!(+ Inherent));
let validate_unsigned_part_v2 =
def.validate_unsigned.as_ref().map(|_| quote::quote!(+ ValidateUnsigned));
let freeze_reason_part_v2 = def
.composites
.iter()
.any(|c| matches!(c.composite_keyword, CompositeKeyword::FreezeReason(_)))
.then_some(quote::quote!(+ FreezeReason));
let hold_reason_part_v2 = def
.composites
.iter()
.any(|c| matches!(c.composite_keyword, CompositeKeyword::HoldReason(_)))
.then_some(quote::quote!(+ HoldReason));
let lock_id_part_v2 = def
.composites
.iter()
.any(|c| matches!(c.composite_keyword, CompositeKeyword::LockId(_)))
.then_some(quote::quote!(+ LockId));
let slash_reason_part_v2 = def
.composites
.iter()
.any(|c| matches!(c.composite_keyword, CompositeKeyword::SlashReason(_)))
.then_some(quote::quote!(+ SlashReason));
quote::quote!(
// This macro follows the conventions as laid out by the `tt-call` crate. It does not
// accept any arguments and simply returns the pezpallet parts, separated by commas, then
// wrapped inside of braces and finally prepended with double colons, to the caller inside
// of a key named `tokens`.
//
// We need to accept a path argument here, because this macro gets expanded on the
// crate that called the `construct_runtime!` macro, and the actual path is unknown.
#[macro_export]
#[doc(hidden)]
macro_rules! #default_parts_unique_id {
{
$caller:tt
your_tt_return = [{ $my_tt_return:path }]
} => {
$my_tt_return! {
$caller
tokens = [{
expanded::{
Pezpallet, #call_part #storage_part #event_part #error_part #origin_part #config_part
#inherent_part #validate_unsigned_part #freeze_reason_part #task_part
#hold_reason_part #lock_id_part #slash_reason_part
}
}]
}
};
}
pub use #default_parts_unique_id as tt_default_parts;
// This macro is similar to the `tt_default_parts!`. It expands the pallets that are declared
// explicitly (`System: pezframe_system::{Pezpallet, Call}`) with extra parts.
//
// For example, after expansion an explicit pezpallet would look like:
// `System: expanded::{Error} ::{Pezpallet, Call}`.
//
// The `expanded` keyword is a marker of the final state of the `construct_runtime!`.
#[macro_export]
#[doc(hidden)]
macro_rules! #extra_parts_unique_id {
{
$caller:tt
your_tt_return = [{ $my_tt_return:path }]
} => {
$my_tt_return! {
$caller
tokens = [{
expanded::{
#error_part
}
}]
}
};
}
pub use #extra_parts_unique_id as tt_extra_parts;
#[macro_export]
#[doc(hidden)]
macro_rules! #default_parts_unique_id_v2 {
{
$caller:tt
your_tt_return = [{ $my_tt_return:path }]
} => {
$my_tt_return! {
$caller
tokens = [{
+ Pezpallet #call_part_v2 #storage_part_v2 #event_part_v2 #error_part_v2 #origin_part_v2 #config_part_v2
#inherent_part_v2 #validate_unsigned_part_v2 #freeze_reason_part_v2 #task_part_v2
#hold_reason_part_v2 #lock_id_part_v2 #slash_reason_part_v2
}]
}
};
}
pub use #default_parts_unique_id_v2 as tt_default_parts_v2;
)
}
@@ -0,0 +1,77 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::pezpallet::Def;
///
/// * Generate the struct
/// * implement the `Get<..>` on it
/// * Rename the name of the function to internal name
pub fn expand_type_values(def: &mut Def) -> proc_macro2::TokenStream {
let mut expand = quote::quote!();
let pezframe_support = &def.pezframe_support;
for type_value in &def.type_values {
let fn_name_str = &type_value.ident.to_string();
let fn_name_snakecase = inflector::cases::snakecase::to_snake_case(fn_name_str);
let fn_ident_renamed = syn::Ident::new(
&format!("__type_value_for_{}", fn_name_snakecase),
type_value.ident.span(),
);
let type_value_item = {
let item = &mut def.item.content.as_mut().expect("Checked by def").1[type_value.index];
if let syn::Item::Fn(item) = item {
item
} else {
unreachable!("Checked by error parser")
}
};
// Rename the type_value function name
type_value_item.sig.ident = fn_ident_renamed.clone();
let vis = &type_value.vis;
let ident = &type_value.ident;
let type_ = &type_value.type_;
let where_clause = &type_value.where_clause;
let (struct_impl_gen, struct_use_gen) = if type_value.is_generic {
(
def.type_impl_generics(type_value.attr_span),
def.type_use_generics(type_value.attr_span),
)
} else {
(Default::default(), Default::default())
};
let docs = &type_value.docs;
expand.extend(quote::quote_spanned!(type_value.attr_span =>
#( #[doc = #docs] )*
#vis struct #ident<#struct_use_gen>(core::marker::PhantomData<((), #struct_use_gen)>);
impl<#struct_impl_gen> #pezframe_support::traits::Get<#type_> for #ident<#struct_use_gen>
#where_clause
{
fn get() -> #type_ {
#fn_ident_renamed::<#struct_use_gen>()
}
}
));
}
expand
}
@@ -0,0 +1,56 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::{pezpallet::Def, COUNTER};
use proc_macro2::TokenStream;
use quote::quote;
use syn::{spanned::Spanned, Ident};
pub fn expand_validate_unsigned(def: &mut Def) -> TokenStream {
let count = COUNTER.with(|counter| counter.borrow_mut().inc());
let macro_ident =
Ident::new(&format!("__is_validate_unsigned_part_defined_{}", count), def.item.span());
let maybe_compile_error = if def.validate_unsigned.is_none() {
quote! {
compile_error!(concat!(
"`",
stringify!($pezpallet_name),
"` does not have #[pezpallet::validate_unsigned] defined, perhaps you should \
remove `ValidateUnsigned` from construct_runtime?",
));
}
} else {
TokenStream::new()
};
quote! {
#[doc(hidden)]
pub mod __bizinikiwi_validate_unsigned_check {
#[macro_export]
#[doc(hidden)]
macro_rules! #macro_ident {
($pezpallet_name:ident) => {
#maybe_compile_error
}
}
#[doc(hidden)]
pub use #macro_ident as is_validate_unsigned_part_defined;
}
}
}
@@ -0,0 +1,260 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::pezpallet::{parse::view_functions::ViewFunctionDef, Def};
use proc_macro2::{Span, TokenStream};
use syn::spanned::Spanned;
pub fn expand_view_functions(def: &Def) -> TokenStream {
let (span, where_clause, view_fns) = match def.view_functions.as_ref() {
Some(view_fns) =>
(view_fns.attr_span, view_fns.where_clause.clone(), view_fns.view_functions.clone()),
None => (def.item.span(), def.config.where_clause.clone(), Vec::new()),
};
let view_function_prefix_impl =
expand_view_function_prefix_impl(def, span, where_clause.as_ref());
let view_fn_impls = view_fns
.iter()
.map(|view_fn| expand_view_function(def, span, where_clause.as_ref(), view_fn));
let impl_dispatch_view_function =
impl_dispatch_view_function(def, span, where_clause.as_ref(), &view_fns);
let impl_view_function_metadata =
impl_view_function_metadata(def, span, where_clause.as_ref(), &view_fns);
quote::quote! {
#view_function_prefix_impl
#( #view_fn_impls )*
#impl_dispatch_view_function
#impl_view_function_metadata
}
}
fn expand_view_function_prefix_impl(
def: &Def,
span: Span,
where_clause: Option<&syn::WhereClause>,
) -> TokenStream {
let pezpallet_ident = &def.pezpallet_struct.pezpallet;
let pezframe_support = &def.pezframe_support;
let pezframe_system = &def.pezframe_system;
let type_impl_gen = &def.type_impl_generics(span);
let type_use_gen = &def.type_use_generics(span);
quote::quote! {
impl<#type_impl_gen> #pezframe_support::view_functions::ViewFunctionIdPrefix for #pezpallet_ident<#type_use_gen> #where_clause {
fn prefix() -> [::core::primitive::u8; 16usize] {
<
<T as #pezframe_system::Config>::PalletInfo
as #pezframe_support::traits::PalletInfo
>::name_hash::<Pezpallet<#type_use_gen>>()
.expect("No name_hash found for the pezpallet in the runtime! This usually means that the pezpallet wasn't added to `construct_runtime!`.")
}
}
}
}
fn expand_view_function(
def: &Def,
span: Span,
where_clause: Option<&syn::WhereClause>,
view_fn: &ViewFunctionDef,
) -> TokenStream {
let pezframe_support = &def.pezframe_support;
let pezpallet_ident = &def.pezpallet_struct.pezpallet;
let type_impl_gen = &def.type_impl_generics(span);
let type_decl_bounded_gen = &def.type_decl_bounded_generics(span);
let type_use_gen = &def.type_use_generics(span);
let capture_docs = if cfg!(feature = "no-metadata-docs") { "never" } else { "always" };
let view_function_struct_ident = view_fn.view_function_struct_ident();
let view_fn_name = &view_fn.name;
let (arg_names, arg_types) = match view_fn.args_names_types() {
Ok((arg_names, arg_types)) => (arg_names, arg_types),
Err(e) => return e.into_compile_error(),
};
let return_type = &view_fn.return_type;
let docs = &view_fn.docs;
let view_function_id_suffix_bytes_raw = match view_fn.view_function_id_suffix_bytes() {
Ok(view_function_id_suffix_bytes_raw) => view_function_id_suffix_bytes_raw,
Err(e) => return e.into_compile_error(),
};
let view_function_id_suffix_bytes = view_function_id_suffix_bytes_raw
.map(|byte| syn::LitInt::new(&format!("0x{:X}_u8", byte), Span::call_site()));
quote::quote! {
#( #[doc = #docs] )*
#[allow(missing_docs)]
#[derive(
#pezframe_support::RuntimeDebugNoBound,
#pezframe_support::CloneNoBound,
#pezframe_support::EqNoBound,
#pezframe_support::PartialEqNoBound,
#pezframe_support::__private::codec::Encode,
#pezframe_support::__private::codec::Decode,
#pezframe_support::__private::codec::DecodeWithMemTracking,
#pezframe_support::__private::scale_info::TypeInfo,
)]
#[codec(encode_bound())]
#[codec(decode_bound())]
#[scale_info(skip_type_params(#type_use_gen), capture_docs = #capture_docs)]
pub struct #view_function_struct_ident<#type_decl_bounded_gen> #where_clause {
#(
pub #arg_names: #arg_types,
)*
_marker: ::core::marker::PhantomData<(#type_use_gen,)>,
}
impl<#type_impl_gen> #view_function_struct_ident<#type_use_gen> #where_clause {
/// Create a new [`#view_function_struct_ident`] instance.
pub fn new(#( #arg_names: #arg_types, )*) -> Self {
Self {
#( #arg_names, )*
_marker: ::core::default::Default::default()
}
}
}
impl<#type_impl_gen> #pezframe_support::view_functions::ViewFunctionIdSuffix for #view_function_struct_ident<#type_use_gen> #where_clause {
const SUFFIX: [::core::primitive::u8; 16usize] = [ #( #view_function_id_suffix_bytes ),* ];
}
impl<#type_impl_gen> #pezframe_support::view_functions::ViewFunction for #view_function_struct_ident<#type_use_gen> #where_clause {
fn id() -> #pezframe_support::view_functions::ViewFunctionId {
#pezframe_support::view_functions::ViewFunctionId {
prefix: <#pezpallet_ident<#type_use_gen> as #pezframe_support::view_functions::ViewFunctionIdPrefix>::prefix(),
suffix: <Self as #pezframe_support::view_functions::ViewFunctionIdSuffix>::SUFFIX,
}
}
type ReturnType = #return_type;
fn invoke(self) -> Self::ReturnType {
let Self { #( #arg_names, )* _marker } = self;
#pezpallet_ident::<#type_use_gen> :: #view_fn_name( #( #arg_names, )* )
}
}
}
}
fn impl_dispatch_view_function(
def: &Def,
span: Span,
where_clause: Option<&syn::WhereClause>,
view_fns: &[ViewFunctionDef],
) -> TokenStream {
let pezframe_support = &def.pezframe_support;
let pezpallet_ident = &def.pezpallet_struct.pezpallet;
let type_impl_gen = &def.type_impl_generics(span);
let type_use_gen = &def.type_use_generics(span);
let query_match_arms = view_fns.iter().map(|view_fn| {
let view_function_struct_ident = view_fn.view_function_struct_ident();
quote::quote! {
<#view_function_struct_ident<#type_use_gen> as #pezframe_support::view_functions::ViewFunctionIdSuffix>::SUFFIX => {
<#view_function_struct_ident<#type_use_gen> as #pezframe_support::view_functions::ViewFunction>::execute(input, output)
}
}
});
quote::quote! {
impl<#type_impl_gen> #pezframe_support::view_functions::DispatchViewFunction
for #pezpallet_ident<#type_use_gen> #where_clause
{
#[deny(unreachable_patterns)]
fn dispatch_view_function<O: #pezframe_support::__private::codec::Output>(
id: & #pezframe_support::view_functions::ViewFunctionId,
input: &mut &[u8],
output: &mut O
) -> Result<(), #pezframe_support::view_functions::ViewFunctionDispatchError>
{
match id.suffix {
#( #query_match_arms )*
_ => Err(#pezframe_support::view_functions::ViewFunctionDispatchError::NotFound(id.clone())),
}
}
}
}
}
fn impl_view_function_metadata(
def: &Def,
span: Span,
where_clause: Option<&syn::WhereClause>,
view_fns: &[ViewFunctionDef],
) -> TokenStream {
let pezframe_support = &def.pezframe_support;
let pezpallet_ident = &def.pezpallet_struct.pezpallet;
let type_impl_gen = &def.type_impl_generics(span);
let type_use_gen = &def.type_use_generics(span);
let view_functions = view_fns.iter().map(|view_fn| {
let view_function_struct_ident = view_fn.view_function_struct_ident();
let name = &view_fn.name;
let inputs = view_fn.args.iter().filter_map(|fn_arg| {
match fn_arg {
syn::FnArg::Receiver(_) => None,
syn::FnArg::Typed(typed) => {
let pat = &typed.pat;
let ty = &typed.ty;
Some(quote::quote! {
#pezframe_support::__private::metadata_ir::PalletViewFunctionParamMetadataIR {
name: ::core::stringify!(#pat),
ty: #pezframe_support::__private::scale_info::meta_type::<#ty>(),
}
})
}
}
});
let no_docs = vec![];
let doc = if cfg!(feature = "no-metadata-docs") { &no_docs } else { &view_fn.docs };
let deprecation = match crate::deprecation::get_deprecation(
&quote::quote! { #pezframe_support },
&def.item.attrs,
) {
Ok(deprecation) => deprecation,
Err(e) => return e.into_compile_error(),
};
quote::quote! {
#pezframe_support::__private::metadata_ir::PalletViewFunctionMetadataIR {
name: ::core::stringify!(#name),
id: <#view_function_struct_ident<#type_use_gen> as #pezframe_support::view_functions::ViewFunction>::id().into(),
inputs: #pezframe_support::__private::pezsp_std::vec![ #( #inputs ),* ],
output: #pezframe_support::__private::scale_info::meta_type::<
<#view_function_struct_ident<#type_use_gen> as #pezframe_support::view_functions::ViewFunction>::ReturnType
>(),
docs: #pezframe_support::__private::pezsp_std::vec![ #( #doc ),* ],
deprecation_info: #deprecation,
}
}
});
quote::quote! {
impl<#type_impl_gen> #pezpallet_ident<#type_use_gen> #where_clause {
#[doc(hidden)]
pub fn pezpallet_view_functions_metadata()
-> #pezframe_support::__private::Vec<#pezframe_support::__private::metadata_ir::PalletViewFunctionMetadataIR> {
#pezframe_support::__private::vec![ #( #view_functions ),* ]
}
}
}
}
@@ -0,0 +1,98 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Generates warnings for undesirable pezpallet code.
use crate::pezpallet::parse::call::{CallVariantDef, CallWeightDef};
use proc_macro_warning::Warning;
use syn::{
spanned::Spanned,
visit::{self, Visit},
};
/// Warn if any of the call arguments starts with a underscore and is used in a weight formula.
pub(crate) fn weight_witness_warning(
method: &CallVariantDef,
dev_mode: bool,
warnings: &mut Vec<Warning>,
) {
if dev_mode {
return;
}
let CallWeightDef::Immediate(w) = &method.weight else { return };
let partial_warning = Warning::new_deprecated("UncheckedWeightWitness")
.old("not check weight witness data")
.new("ensure that all witness data for weight calculation is checked before usage")
.help_link("https://github.com/pezkuwichain/kurdistan-sdk/issues/108");
for (_, arg_ident, _) in method.args.iter() {
if !arg_ident.to_string().starts_with('_') || !contains_ident(w.clone(), &arg_ident) {
continue;
}
let warning = partial_warning
.clone()
.index(warnings.len())
.span(arg_ident.span())
.build_or_panic();
warnings.push(warning);
}
}
/// Warn if the weight is a constant and the pezpallet not in `dev_mode`.
pub(crate) fn weight_constant_warning(
weight: &syn::Expr,
dev_mode: bool,
warnings: &mut Vec<Warning>,
) {
if dev_mode {
return;
}
let syn::Expr::Lit(lit) = weight else { return };
let warning = Warning::new_deprecated("ConstantWeight")
.index(warnings.len())
.old("use hard-coded constant as call weight")
.new("benchmark all calls or put the pezpallet into `dev` mode")
.help_link("https://github.com/pezkuwichain/kurdistan-sdk/issues/48")
.span(lit.span())
.build_or_panic();
warnings.push(warning);
}
/// Returns whether `expr` contains `ident`.
fn contains_ident(mut expr: syn::Expr, ident: &syn::Ident) -> bool {
struct ContainsIdent {
ident: syn::Ident,
found: bool,
}
impl<'a> Visit<'a> for ContainsIdent {
fn visit_ident(&mut self, i: &syn::Ident) {
if *i == self.ident {
self.found = true;
}
}
}
let mut visitor = ContainsIdent { ident: ident.clone(), found: false };
visit::visit_expr(&mut visitor, &mut expr);
visitor.found
}
@@ -0,0 +1,61 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Implementation for pezpallet attribute macro.
//!
//! General workflow:
//! 1 - parse all pezpallet attributes:
//! This step remove all attributes `#[pezpallet::*]` from the ItemMod and build the `Def` struct
//! which holds the ItemMod without `#[pezpallet::*]` and information given by those attributes
//! 2 - expand from the parsed information
//! This step will modify the ItemMod by adding some derive attributes or phantom data variants
//! to user defined types. And also crate new types and implement block.
mod expand;
pub(crate) mod parse;
pub use parse::{composite::keyword::CompositeKeyword, Def};
use syn::spanned::Spanned;
mod keyword {
syn::custom_keyword!(dev_mode);
}
pub fn pezpallet(
attr: proc_macro::TokenStream,
item: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let mut dev_mode = false;
if !attr.is_empty() {
if let Ok(_) = syn::parse::<keyword::dev_mode>(attr.clone()) {
dev_mode = true;
} else {
let msg = "Invalid pezpallet macro call: unexpected attribute. Macro call must be \
bare, such as `#[pezframe_support::pezpallet]` or `#[pezpallet]`, or must specify the \
`dev_mode` attribute, such as `#[pezframe_support::pezpallet(dev_mode)]` or \
#[pezpallet(dev_mode)].";
let span = proc_macro2::TokenStream::from(attr).span();
return syn::Error::new(span, msg).to_compile_error().into();
}
}
let item = syn::parse_macro_input!(item as syn::ItemMod);
match parse::Def::try_from(item, dev_mode) {
Ok(def) => expand::expand(def).into(),
Err(e) => e.to_compile_error().into(),
}
}
@@ -0,0 +1,537 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use super::{helper, InheritedCallWeightAttr};
use pezframe_support_procedural_tools::get_doc_literals;
use proc_macro2::Span;
use quote::ToTokens;
use std::collections::HashMap;
use syn::spanned::Spanned;
/// List of additional token to be used for parsing.
mod keyword {
syn::custom_keyword!(Call);
syn::custom_keyword!(OriginFor);
syn::custom_keyword!(RuntimeOrigin);
syn::custom_keyword!(weight);
syn::custom_keyword!(call_index);
syn::custom_keyword!(compact);
syn::custom_keyword!(T);
syn::custom_keyword!(pezpallet);
syn::custom_keyword!(feeless_if);
syn::custom_keyword!(authorize);
syn::custom_keyword!(weight_of_authorize);
}
/// Definition of dispatchables typically `impl<T: Config> Pezpallet<T> { ... }`
pub struct CallDef {
/// The where_clause used.
pub where_clause: Option<syn::WhereClause>,
/// A set of usage of instance, must be check for consistency with trait.
pub instances: Vec<helper::InstanceUsage>,
/// The index of call item in pezpallet module.
pub index: usize,
/// Information on methods (used for expansion).
pub methods: Vec<CallVariantDef>,
/// The span of the pezpallet::call attribute.
pub attr_span: proc_macro2::Span,
/// Docs, specified on the impl Block.
pub docs: Vec<syn::Expr>,
}
/// The weight of a call or the weight of authorize.
#[derive(Clone)]
pub enum CallWeightDef {
/// Explicitly set on the call itself with `#[pezpallet::weight(…)]` or
/// `#[pezpallet::weight_of_authorize(…)]`. This value is used.
Immediate(syn::Expr),
/// The default value that should be set for dev-mode pallets. Usually zero.
DevModeDefault,
/// Inherits whatever value is configured on the pezpallet level.
///
/// The concrete value is not known at this point.
Inherited(syn::Type),
}
impl CallWeightDef {
fn try_from(
weight: Option<syn::Expr>,
inherited_call_weight: &Option<InheritedCallWeightAttr>,
dev_mode: bool,
) -> Option<Self> {
match (weight, inherited_call_weight) {
(Some(weight), _) => Some(CallWeightDef::Immediate(weight)),
(None, Some(inherited)) => Some(CallWeightDef::Inherited(inherited.typename.clone())),
(None, _) if dev_mode => Some(CallWeightDef::DevModeDefault),
(None, _) => None,
}
}
}
/// Definition of dispatchable typically: `#[weight...] fn foo(origin .., param1: ...) -> ..`
#[derive(Clone)]
pub struct CallVariantDef {
/// Function name.
pub name: syn::Ident,
/// Information on args: `(is_compact, name, type)`
pub args: Vec<(bool, syn::Ident, Box<syn::Type>)>,
/// Weight for the call.
pub weight: CallWeightDef,
/// Call index of the dispatchable.
pub call_index: u8,
/// Whether an explicit call index was specified.
pub explicit_call_index: bool,
/// Docs, used for metadata.
pub docs: Vec<syn::Expr>,
/// Attributes annotated at the top of the dispatchable function.
pub attrs: Vec<syn::Attribute>,
/// The `cfg` attributes.
pub cfg_attrs: Vec<syn::Attribute>,
/// The optional `feeless_if` attribute on the `pezpallet::call`.
pub feeless_check: Option<syn::ExprClosure>,
/// The return type of the call: `DispatchInfo` or `DispatchResultWithPostInfo`.
pub return_type: helper::CallReturnType,
/// The information related to `authorize` attribute.
/// `(authorize expression, weight of authorize)`
pub authorize: Option<AuthorizeDef>,
}
/// Definition related to the `authorize` attribute and other related attributes.
#[derive(Clone)]
pub struct AuthorizeDef {
/// The expression of the authorize attribute.
pub expr: syn::Expr,
/// The weight of the authorize attribute as define by the attribute
/// `[pezpallet::weight_of_authorize]`.
pub weight: CallWeightDef,
}
/// Attributes for functions in call impl block.
pub enum FunctionAttr {
/// Parse for `#[pezpallet::call_index(expr)]`
CallIndex(u8),
/// Parse for `#[pezpallet::weight(expr)]`
Weight(syn::Expr),
/// Parse for `#[pezpallet::feeless_if(expr)]`
FeelessIf(Span, syn::ExprClosure),
/// Parse for `#[pezpallet::authorize(expr)]`
Authorize(syn::Expr),
/// Parse for `#[pezpallet::weight_of_authorize(expr)]`
WeightOfAuthorize(syn::Expr),
}
impl syn::parse::Parse for FunctionAttr {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
input.parse::<syn::Token![#]>()?;
let content;
syn::bracketed!(content in input);
content.parse::<keyword::pezpallet>()?;
content.parse::<syn::Token![::]>()?;
let lookahead = content.lookahead1();
if lookahead.peek(keyword::weight) {
content.parse::<keyword::weight>()?;
let weight_content;
syn::parenthesized!(weight_content in content);
Ok(FunctionAttr::Weight(weight_content.parse::<syn::Expr>()?))
} else if lookahead.peek(keyword::call_index) {
content.parse::<keyword::call_index>()?;
let call_index_content;
syn::parenthesized!(call_index_content in content);
let index = call_index_content.parse::<syn::LitInt>()?;
if !index.suffix().is_empty() {
let msg = "Number literal must not have a suffix";
return Err(syn::Error::new(index.span(), msg));
}
Ok(FunctionAttr::CallIndex(index.base10_parse()?))
} else if lookahead.peek(keyword::feeless_if) {
content.parse::<keyword::feeless_if>()?;
let closure_content;
syn::parenthesized!(closure_content in content);
Ok(FunctionAttr::FeelessIf(
closure_content.span(),
closure_content.parse::<syn::ExprClosure>().map_err(|e| {
let msg = "Invalid feeless_if attribute: expected a closure";
let mut err = syn::Error::new(closure_content.span(), msg);
err.combine(e);
err
})?,
))
} else if lookahead.peek(keyword::authorize) {
content.parse::<keyword::authorize>()?;
let closure_content;
syn::parenthesized!(closure_content in content);
Ok(FunctionAttr::Authorize(closure_content.parse::<syn::Expr>()?))
} else if lookahead.peek(keyword::weight_of_authorize) {
content.parse::<keyword::weight_of_authorize>()?;
let closure_content;
syn::parenthesized!(closure_content in content);
Ok(FunctionAttr::WeightOfAuthorize(closure_content.parse::<syn::Expr>()?))
} else {
Err(lookahead.error())
}
}
}
/// Attribute for arguments in function in call impl block.
/// Parse for `#[pezpallet::compact]|
pub struct ArgAttrIsCompact;
impl syn::parse::Parse for ArgAttrIsCompact {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
input.parse::<syn::Token![#]>()?;
let content;
syn::bracketed!(content in input);
content.parse::<keyword::pezpallet>()?;
content.parse::<syn::Token![::]>()?;
content.parse::<keyword::compact>()?;
Ok(ArgAttrIsCompact)
}
}
/// Check the syntax is `OriginFor<T>`, `&OriginFor<T>` or `T::RuntimeOrigin`.
pub fn check_dispatchable_first_arg_type(ty: &syn::Type, is_ref: bool) -> syn::Result<()> {
pub struct CheckOriginFor(bool);
impl syn::parse::Parse for CheckOriginFor {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let is_ref = input.parse::<syn::Token![&]>().is_ok();
input.parse::<keyword::OriginFor>()?;
input.parse::<syn::Token![<]>()?;
input.parse::<keyword::T>()?;
input.parse::<syn::Token![>]>()?;
Ok(Self(is_ref))
}
}
pub struct CheckRuntimeOrigin;
impl syn::parse::Parse for CheckRuntimeOrigin {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
input.parse::<keyword::T>()?;
input.parse::<syn::Token![::]>()?;
input.parse::<keyword::RuntimeOrigin>()?;
Ok(Self)
}
}
let result_origin_for = syn::parse2::<CheckOriginFor>(ty.to_token_stream());
let result_runtime_origin = syn::parse2::<CheckRuntimeOrigin>(ty.to_token_stream());
return match (result_origin_for, result_runtime_origin) {
(Ok(CheckOriginFor(has_ref)), _) if is_ref == has_ref => Ok(()),
(_, Ok(_)) => Ok(()),
(_, _) => {
let msg = if is_ref {
"Invalid type: expected `&OriginFor<T>`"
} else {
"Invalid type: expected `OriginFor<T>` or `T::RuntimeOrigin`"
};
return Err(syn::Error::new(ty.span(), msg));
},
};
}
impl CallDef {
pub fn try_from(
attr_span: proc_macro2::Span,
index: usize,
item: &mut syn::Item,
dev_mode: bool,
inherited_call_weight: Option<InheritedCallWeightAttr>,
) -> syn::Result<Self> {
let item_impl = if let syn::Item::Impl(item) = item {
item
} else {
return Err(syn::Error::new(item.span(), "Invalid pezpallet::call, expected item impl"));
};
crate::deprecation::prevent_deprecation_attr_on_outer_enum(&item_impl.attrs)?;
let instances = vec![
helper::check_impl_gen(&item_impl.generics, item_impl.impl_token.span())?,
helper::check_pallet_struct_usage(&item_impl.self_ty)?,
];
if let Some((_, _, for_)) = item_impl.trait_ {
let msg = "Invalid pezpallet::call, expected no trait ident as in \
`impl<..> Pezpallet<..> { .. }`";
return Err(syn::Error::new(for_.span(), msg));
}
let mut methods = vec![];
let mut indices = HashMap::new();
let mut last_index: Option<u8> = None;
for item in &mut item_impl.items {
if let syn::ImplItem::Fn(method) = item {
if !matches!(method.vis, syn::Visibility::Public(_)) {
let msg = "Invalid pezpallet::call, dispatchable function must be public: \
`pub fn`";
let span = match method.vis {
syn::Visibility::Inherited => method.sig.span(),
_ => method.vis.span(),
};
return Err(syn::Error::new(span, msg));
}
match method.sig.inputs.first() {
None => {
let msg = "Invalid pezpallet::call, must have at least origin arg";
return Err(syn::Error::new(method.sig.span(), msg));
},
Some(syn::FnArg::Receiver(_)) => {
let msg = "Invalid pezpallet::call, first argument must be a typed argument, \
e.g. `origin: OriginFor<T>`";
return Err(syn::Error::new(method.sig.span(), msg));
},
Some(syn::FnArg::Typed(arg)) => {
check_dispatchable_first_arg_type(&arg.ty, false)?;
},
}
let return_type = helper::check_pallet_call_return_type(&method.sig)?;
let cfg_attrs: Vec<syn::Attribute> = helper::get_item_cfg_attrs(&method.attrs);
let mut call_index = None;
let mut weight = None;
let mut feeless_check = None;
let mut authorize = None;
let mut weight_of_authorize = None;
for attr in helper::take_item_pallet_attrs(&mut method.attrs)?.into_iter() {
match attr {
FunctionAttr::CallIndex(idx) => {
if call_index.is_some() {
let msg =
"Invalid pezpallet::call, too many call_index attributes given";
return Err(syn::Error::new(method.sig.span(), msg));
}
call_index = Some(idx);
},
FunctionAttr::Weight(w) => {
if weight.is_some() {
let msg = "Invalid pezpallet::call, too many weight attributes given";
return Err(syn::Error::new(method.sig.span(), msg));
}
weight = Some(w);
},
FunctionAttr::FeelessIf(span, closure) => {
if feeless_check.is_some() {
let msg =
"Invalid pezpallet::call, there can only be one feeless_if attribute";
return Err(syn::Error::new(span, msg));
}
feeless_check = Some(closure);
},
FunctionAttr::Authorize(expr) => {
if authorize.is_some() {
let msg =
"Invalid pezpallet::call, there can only be one authorize attribute";
return Err(syn::Error::new(method.sig.span(), msg));
}
authorize = Some(expr);
},
FunctionAttr::WeightOfAuthorize(expr) => {
if weight_of_authorize.is_some() {
let msg = "Invalid pezpallet::call, there can only be one weight_of_authorize attribute";
return Err(syn::Error::new(method.sig.span(), msg));
}
weight_of_authorize = Some(expr);
},
}
}
if weight_of_authorize.is_some() && authorize.is_none() {
let msg = "Invalid pezpallet::call, weight_of_authorize attribute must be used with authorize attribute";
return Err(syn::Error::new(weight_of_authorize.unwrap().span(), msg));
}
let authorize = if let Some(expr) = authorize {
let weight_of_authorize = CallWeightDef::try_from(
weight_of_authorize,
&inherited_call_weight,
dev_mode,
)
.ok_or_else(|| {
syn::Error::new(
method.sig.span(),
"A pezpallet::call using authorize requires either a concrete \
`#[pezpallet::weight_of_authorize($expr)]` or an inherited weight from \
the `#[pezpallet:call(weight($type))]` attribute, but \
none were given.",
)
})?;
Some(AuthorizeDef { expr, weight: weight_of_authorize })
} else {
None
};
let weight = CallWeightDef::try_from(weight, &inherited_call_weight, dev_mode)
.ok_or_else(|| {
syn::Error::new(
method.sig.span(),
"A pezpallet::call requires either a concrete `#[pezpallet::weight($expr)]` \
or an inherited weight from the `#[pezpallet:call(weight($type))]` \
attribute, but none were given.",
)
})?;
let explicit_call_index = call_index.is_some();
let final_index = match call_index {
Some(i) => i,
None =>
last_index.map_or(Some(0), |idx| idx.checked_add(1)).ok_or_else(|| {
let msg = "Call index doesn't fit into u8, index is 256";
syn::Error::new(method.sig.span(), msg)
})?,
};
last_index = Some(final_index);
if let Some(used_fn) = indices.insert(final_index, method.sig.ident.clone()) {
let msg = format!(
"Call indices are conflicting: Both functions {} and {} are at index {}",
used_fn, method.sig.ident, final_index,
);
let mut err = syn::Error::new(used_fn.span(), &msg);
err.combine(syn::Error::new(method.sig.ident.span(), msg));
return Err(err);
}
let mut args = vec![];
for arg in method.sig.inputs.iter_mut().skip(1) {
let arg = if let syn::FnArg::Typed(arg) = arg {
arg
} else {
unreachable!("Only first argument can be receiver");
};
let arg_attrs: Vec<ArgAttrIsCompact> =
helper::take_item_pallet_attrs(&mut arg.attrs)?;
if arg_attrs.len() > 1 {
let msg = "Invalid pezpallet::call, argument has too many attributes";
return Err(syn::Error::new(arg.span(), msg));
}
let arg_ident = if let syn::Pat::Ident(pat) = &*arg.pat {
pat.ident.clone()
} else {
let msg = "Invalid pezpallet::call, argument must be ident";
return Err(syn::Error::new(arg.pat.span(), msg));
};
args.push((!arg_attrs.is_empty(), arg_ident, arg.ty.clone()));
}
let docs = get_doc_literals(&method.attrs);
if let Some(ref feeless_check) = feeless_check {
if feeless_check.inputs.len() != args.len() + 1 {
let msg = "Invalid pezpallet::call, feeless_if closure must have same \
number of arguments as the dispatchable function";
return Err(syn::Error::new(feeless_check.span(), msg));
}
match feeless_check.inputs.first() {
None => {
let msg = "Invalid pezpallet::call, feeless_if closure must have at least origin arg";
return Err(syn::Error::new(feeless_check.span(), msg));
},
Some(syn::Pat::Type(arg)) => {
check_dispatchable_first_arg_type(&arg.ty, true)?;
},
_ => {
let msg = "Invalid pezpallet::call, feeless_if closure first argument must be a typed argument, \
e.g. `origin: OriginFor<T>`";
return Err(syn::Error::new(feeless_check.span(), msg));
},
}
for (feeless_arg, arg) in feeless_check.inputs.iter().skip(1).zip(args.iter()) {
let feeless_arg_type = if let syn::Pat::Type(syn::PatType { ty, .. }) =
feeless_arg.clone()
{
if let syn::Type::Reference(pat) = *ty {
pat.elem.clone()
} else {
let msg = "Invalid pezpallet::call, feeless_if closure argument must be a reference";
return Err(syn::Error::new(ty.span(), msg));
}
} else {
let msg = "Invalid pezpallet::call, feeless_if closure argument must be a type ascription pattern";
return Err(syn::Error::new(feeless_arg.span(), msg));
};
if feeless_arg_type != arg.2 {
let msg =
"Invalid pezpallet::call, feeless_if closure argument must have \
a reference to the same type as the dispatchable function argument";
return Err(syn::Error::new(feeless_arg.span(), msg));
}
}
let valid_return = match &feeless_check.output {
syn::ReturnType::Type(_, type_) => match *(type_.clone()) {
syn::Type::Path(syn::TypePath { path, .. }) => path.is_ident("bool"),
_ => false,
},
_ => false,
};
if !valid_return {
let msg = "Invalid pezpallet::call, feeless_if closure must return `bool`";
return Err(syn::Error::new(feeless_check.output.span(), msg));
}
}
methods.push(CallVariantDef {
name: method.sig.ident.clone(),
weight,
call_index: final_index,
explicit_call_index,
args,
docs,
attrs: method.attrs.clone(),
cfg_attrs,
feeless_check,
return_type,
authorize,
});
} else {
let msg = "Invalid pezpallet::call, only method accepted";
return Err(syn::Error::new(item.span(), msg));
}
}
Ok(Self {
index,
attr_span,
instances,
methods,
where_clause: item_impl.generics.where_clause.clone(),
docs: get_doc_literals(&item_impl.attrs),
})
}
}
@@ -0,0 +1,190 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use super::helper;
use quote::ToTokens;
use syn::spanned::Spanned;
pub mod keyword {
use super::*;
syn::custom_keyword!(FreezeReason);
syn::custom_keyword!(HoldReason);
syn::custom_keyword!(LockId);
syn::custom_keyword!(SlashReason);
syn::custom_keyword!(Task);
pub enum CompositeKeyword {
FreezeReason(FreezeReason),
HoldReason(HoldReason),
LockId(LockId),
SlashReason(SlashReason),
Task(Task),
}
impl ToTokens for CompositeKeyword {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
use CompositeKeyword::*;
match self {
FreezeReason(inner) => inner.to_tokens(tokens),
HoldReason(inner) => inner.to_tokens(tokens),
LockId(inner) => inner.to_tokens(tokens),
SlashReason(inner) => inner.to_tokens(tokens),
Task(inner) => inner.to_tokens(tokens),
}
}
}
impl syn::parse::Parse for CompositeKeyword {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(FreezeReason) {
Ok(Self::FreezeReason(input.parse()?))
} else if lookahead.peek(HoldReason) {
Ok(Self::HoldReason(input.parse()?))
} else if lookahead.peek(LockId) {
Ok(Self::LockId(input.parse()?))
} else if lookahead.peek(SlashReason) {
Ok(Self::SlashReason(input.parse()?))
} else if lookahead.peek(Task) {
Ok(Self::Task(input.parse()?))
} else {
Err(lookahead.error())
}
}
}
impl std::fmt::Display for CompositeKeyword {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use CompositeKeyword::*;
write!(
f,
"{}",
match self {
FreezeReason(_) => "FreezeReason",
HoldReason(_) => "HoldReason",
Task(_) => "Task",
LockId(_) => "LockId",
SlashReason(_) => "SlashReason",
}
)
}
}
}
pub struct CompositeDef {
/// The composite keyword used (contains span).
pub composite_keyword: keyword::CompositeKeyword,
/// Name of the associated type.
pub ident: syn::Ident,
/// Type parameters and where clause attached to a declaration of the pezpallet::composite_enum.
pub generics: syn::Generics,
/// The span of the pezpallet::composite_enum attribute.
pub attr_span: proc_macro2::Span,
/// Variant count of the pezpallet::composite_enum.
pub variant_count: u32,
}
impl CompositeDef {
pub fn try_from(
attr_span: proc_macro2::Span,
scrate: &syn::Path,
item: &mut syn::Item,
) -> syn::Result<Self> {
let item = if let syn::Item::Enum(item) = item {
// check variants: composite enums support only field-less enum variants. This is
// because fields can introduce too many possibilities, making it challenging to compute
// a fixed variant count.
for variant in &item.variants {
match variant.fields {
syn::Fields::Named(_) | syn::Fields::Unnamed(_) =>
return Err(syn::Error::new(
variant.ident.span(),
"The composite enum does not support variants with fields!",
)),
syn::Fields::Unit => (),
}
}
item
} else {
return Err(syn::Error::new(
item.span(),
"Invalid pezpallet::composite_enum, expected enum item",
));
};
if !matches!(item.vis, syn::Visibility::Public(_)) {
let msg = format!("Invalid pezpallet::composite_enum, `{}` must be public", item.ident);
return Err(syn::Error::new(item.span(), msg));
}
let has_instance = if item.generics.params.first().is_some() {
helper::check_config_def_gen(&item.generics, item.ident.span())?;
true
} else {
false
};
let has_derive_attr = item.attrs.iter().any(|attr| {
if let syn::Meta::List(syn::MetaList { path, .. }) = &attr.meta {
path.get_ident().map(|ident| ident == "derive").unwrap_or(false)
} else {
false
}
});
if !has_derive_attr {
let derive_attr: syn::Attribute = syn::parse_quote! {
#[derive(
Copy, Clone, Eq, PartialEq,
#scrate::__private::codec::Encode,
#scrate::__private::codec::Decode,
#scrate::__private::codec::DecodeWithMemTracking,
#scrate::__private::codec::MaxEncodedLen,
#scrate::__private::scale_info::TypeInfo,
#scrate::__private::RuntimeDebug,
)]
};
item.attrs.push(derive_attr);
}
if has_instance {
item.attrs.push(syn::parse_quote! {
#[scale_info(skip_type_params(I))]
});
item.variants.push(syn::parse_quote! {
#[doc(hidden)]
#[codec(skip)]
__Ignore(
::core::marker::PhantomData<I>,
)
});
}
let composite_keyword =
syn::parse2::<keyword::CompositeKeyword>(item.ident.to_token_stream())?;
Ok(CompositeDef {
composite_keyword,
attr_span,
generics: item.generics.clone(),
variant_count: item.variants.len() as u32,
ident: item.ident.clone(),
})
}
}
@@ -0,0 +1,727 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use super::helper;
use pezframe_support_procedural_tools::{get_cfg_attributes, get_doc_literals, is_using_frame_crate};
use proc_macro_warning::Warning;
use quote::ToTokens;
use syn::{parse_quote, spanned::Spanned, token, Token, TraitItemType};
/// List of additional token to be used for parsing.
mod keyword {
syn::custom_keyword!(Config);
syn::custom_keyword!(From);
syn::custom_keyword!(T);
syn::custom_keyword!(I);
syn::custom_keyword!(config);
syn::custom_keyword!(pezpallet);
syn::custom_keyword!(IsType);
syn::custom_keyword!(RuntimeEvent);
syn::custom_keyword!(Event);
syn::custom_keyword!(pezframe_system);
syn::custom_keyword!(disable_pezframe_system_supertrait_check);
syn::custom_keyword!(no_default);
syn::custom_keyword!(no_default_bounds);
syn::custom_keyword!(constant);
syn::custom_keyword!(include_metadata);
}
#[derive(Default)]
pub struct DefaultTrait {
/// A bool for each sub-trait item indicates whether the item has
/// `#[pezpallet::no_default_bounds]` attached to it. If true, the item will not have any bounds
/// in the generated default sub-trait.
pub items: Vec<(syn::TraitItem, bool)>,
pub has_system: bool,
}
/// Input definition for the pezpallet config.
pub struct ConfigDef {
/// The index of item in pezpallet module.
pub index: usize,
/// Whether the trait has instance (i.e. define with `Config<I = ()>`)
pub has_instance: bool,
/// Const associated type.
pub consts_metadata: Vec<ConstMetadataDef>,
/// Associated types metadata.
pub associated_types_metadata: Vec<AssociatedTypeMetadataDef>,
/// The where clause on trait definition but modified so `Self` is `T`.
pub where_clause: Option<syn::WhereClause>,
/// Whether a default sub-trait should be generated.
///
/// Contains default sub-trait items (instantiated by `#[pezpallet::config(with_default)]`).
/// Vec will be empty if `#[pezpallet::config(with_default)]` is not specified or if there are
/// no trait items.
pub default_sub_trait: Option<DefaultTrait>,
/// Compile time warnings. Mainly for deprecated items.
pub warnings: Vec<Warning>,
}
/// Input definition for an associated type in pezpallet config.
pub struct AssociatedTypeMetadataDef {
/// Name of the associated type.
pub ident: syn::Ident,
/// The doc associated.
pub doc: Vec<syn::Expr>,
/// The cfg associated.
pub cfg: Vec<syn::Attribute>,
}
impl From<&syn::TraitItemType> for AssociatedTypeMetadataDef {
fn from(trait_ty: &syn::TraitItemType) -> Self {
let ident = trait_ty.ident.clone();
let doc = get_doc_literals(&trait_ty.attrs);
let cfg = get_cfg_attributes(&trait_ty.attrs);
Self { ident, doc, cfg }
}
}
/// Input definition for a constant in pezpallet config.
pub struct ConstMetadataDef {
/// Name of the associated type.
pub ident: syn::Ident,
/// The type in Get, e.g. `u32` in `type Foo: Get<u32>;`, but `Self` is replaced by `T`
pub type_: syn::Type,
/// The doc associated
pub doc: Vec<syn::Expr>,
/// attributes
pub attrs: Vec<syn::Attribute>,
}
impl TryFrom<&syn::TraitItemType> for ConstMetadataDef {
type Error = syn::Error;
fn try_from(trait_ty: &syn::TraitItemType) -> Result<Self, Self::Error> {
let err = |span, msg| {
syn::Error::new(span, format!("Invalid usage of `#[pezpallet::constant]`: {}", msg))
};
let doc = get_doc_literals(&trait_ty.attrs);
let ident = trait_ty.ident.clone();
let bound = trait_ty
.bounds
.iter()
.find_map(|param_bound| {
let syn::TypeParamBound::Trait(trait_bound) = param_bound else { return None };
trait_bound.path.segments.last().and_then(|s| (s.ident == "Get").then(|| s))
})
.ok_or_else(|| err(trait_ty.span(), "`Get<T>` trait bound not found"))?;
let syn::PathArguments::AngleBracketed(ref ab) = bound.arguments else {
return Err(err(bound.span(), "Expected trait generic args"));
};
// Only one type argument is expected.
if ab.args.len() != 1 {
return Err(err(bound.span(), "Expected a single type argument"));
}
let syn::GenericArgument::Type(ref type_arg) = ab.args[0] else {
return Err(err(ab.args[0].span(), "Expected a type argument"));
};
let type_ = syn::parse2::<syn::Type>(replace_self_by_t(type_arg.to_token_stream()))
.expect("Internal error: replacing `Self` by `T` should result in valid type");
Ok(Self { ident, type_, doc, attrs: trait_ty.attrs.clone() })
}
}
/// Parse for `#[pezpallet::disable_pezframe_system_supertrait_check]`
pub struct DisableFrameSystemSupertraitCheck;
impl syn::parse::Parse for DisableFrameSystemSupertraitCheck {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
input.parse::<syn::Token![#]>()?;
let content;
syn::bracketed!(content in input);
content.parse::<syn::Ident>()?;
content.parse::<syn::Token![::]>()?;
content.parse::<keyword::disable_pezframe_system_supertrait_check>()?;
Ok(Self)
}
}
/// Parsing for the `typ` portion of `PalletAttr`
#[derive(derive_syn_parse::Parse, PartialEq, Eq)]
pub enum PalletAttrType {
#[peek(keyword::no_default, name = "no_default")]
NoDefault(keyword::no_default),
#[peek(keyword::no_default_bounds, name = "no_default_bounds")]
NoBounds(keyword::no_default_bounds),
#[peek(keyword::constant, name = "constant")]
Constant(keyword::constant),
#[peek(keyword::include_metadata, name = "include_metadata")]
IncludeMetadata(keyword::include_metadata),
}
/// Parsing for `#[pezpallet::X]`
#[derive(derive_syn_parse::Parse)]
pub struct PalletAttr {
_pound: Token![#],
#[bracket]
_bracket: token::Bracket,
#[inside(_bracket)]
_pallet: keyword::pezpallet,
#[prefix(Token![::] in _bracket)]
#[inside(_bracket)]
typ: PalletAttrType,
}
/// Parse for `IsType<<Self as $path>::RuntimeEvent>` and retrieve `$path`
pub struct IsTypeBoundEventParse(syn::Path);
impl syn::parse::Parse for IsTypeBoundEventParse {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
input.parse::<keyword::IsType>()?;
input.parse::<syn::Token![<]>()?;
input.parse::<syn::Token![<]>()?;
input.parse::<syn::Token![Self]>()?;
input.parse::<syn::Token![as]>()?;
let config_path = input.parse::<syn::Path>()?;
input.parse::<syn::Token![>]>()?;
input.parse::<syn::Token![::]>()?;
input.parse::<keyword::RuntimeEvent>()?;
input.parse::<syn::Token![>]>()?;
Ok(Self(config_path))
}
}
/// Parse for `From<Event>` or `From<Event<Self>>` or `From<Event<Self, I>>`
pub struct FromEventParse {
is_generic: bool,
has_instance: bool,
}
impl syn::parse::Parse for FromEventParse {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let mut is_generic = false;
let mut has_instance = false;
input.parse::<keyword::From>()?;
input.parse::<syn::Token![<]>()?;
input.parse::<keyword::Event>()?;
if input.peek(syn::Token![<]) {
is_generic = true;
input.parse::<syn::Token![<]>()?;
input.parse::<syn::Token![Self]>()?;
if input.peek(syn::Token![,]) {
input.parse::<syn::Token![,]>()?;
input.parse::<keyword::I>()?;
has_instance = true;
}
input.parse::<syn::Token![>]>()?;
}
input.parse::<syn::Token![>]>()?;
Ok(Self { is_generic, has_instance })
}
}
/// Check if trait_item is `type RuntimeEvent`, if so checks its bounds are those expected.
/// (Event type is reserved type)
fn check_event_type(
pezframe_system: &syn::Path,
trait_item: &syn::TraitItem,
trait_has_instance: bool,
) -> syn::Result<bool> {
let syn::TraitItem::Type(type_) = trait_item else { return Ok(false) };
if type_.ident != "RuntimeEvent" {
return Ok(false);
}
// Check event has no generics
if !type_.generics.params.is_empty() || type_.generics.where_clause.is_some() {
let msg =
"Invalid `type RuntimeEvent`, associated type `RuntimeEvent` is reserved and must have\
no generics nor where_clause";
return Err(syn::Error::new(trait_item.span(), msg));
}
// Check bound contains IsType and From
let has_is_type_bound = type_.bounds.iter().any(|s| {
syn::parse2::<IsTypeBoundEventParse>(s.to_token_stream())
.map_or(false, |b| has_expected_system_config(b.0, pezframe_system))
});
if !has_is_type_bound {
let msg =
"Invalid `type RuntimeEvent`, associated type `RuntimeEvent` is reserved and must \
bound: `IsType<<Self as pezframe_system::Config>::RuntimeEvent>`"
.to_string();
return Err(syn::Error::new(type_.span(), msg));
}
let from_event_bound = type_
.bounds
.iter()
.find_map(|s| syn::parse2::<FromEventParse>(s.to_token_stream()).ok());
let Some(from_event_bound) = from_event_bound else {
let msg =
"Invalid `type RuntimeEvent`, associated type `RuntimeEvent` is reserved and must \
bound: `From<Event>` or `From<Event<Self>>` or `From<Event<Self, I>>`";
return Err(syn::Error::new(type_.span(), msg));
};
if from_event_bound.is_generic && (from_event_bound.has_instance != trait_has_instance) {
let msg =
"Invalid `type RuntimeEvent`, associated type `RuntimeEvent` bounds inconsistent \
`From<Event..>`. Config and generic Event must be both with instance or \
without instance";
return Err(syn::Error::new(type_.span(), msg));
}
Ok(true)
}
/// Check that the path to `pezframe_system::Config` is valid, this is that the path is just
/// `pezframe_system::Config` or when using the `frame` crate it is
/// `pezkuwi_sdk_frame::xyz::pezframe_system::Config`.
fn has_expected_system_config(path: syn::Path, pezframe_system: &syn::Path) -> bool {
// Check if `pezframe_system` is actually 'pezframe_system'.
if path.segments.iter().all(|s| s.ident != "pezframe_system") {
return false;
}
let mut expected_system_config =
match (is_using_frame_crate(&path), is_using_frame_crate(&pezframe_system)) {
(true, false) =>
// We can't use the path to `pezframe_system` from `frame` if `pezframe_system` is not being
// in scope through `frame`.
return false,
(false, true) =>
// We know that the only valid pezframe_system path is one that is `pezframe_system`, as
// `frame` re-exports it as such.
syn::parse2::<syn::Path>(quote::quote!(pezframe_system)).expect("is a valid path; qed"),
(_, _) =>
// They are either both `pezframe_system` or both `pezkuwi_sdk_frame::xyz::pezframe_system`.
pezframe_system.clone(),
};
expected_system_config
.segments
.push(syn::PathSegment::from(syn::Ident::new("Config", path.span())));
// the parse path might be something like `pezframe_system::Config<...>`, so we
// only compare the idents along the path.
expected_system_config
.segments
.into_iter()
.map(|ps| ps.ident)
.collect::<Vec<_>>() ==
path.segments.into_iter().map(|ps| ps.ident).collect::<Vec<_>>()
}
/// Replace ident `Self` by `T`
pub fn replace_self_by_t(input: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
input
.into_iter()
.map(|token_tree| match token_tree {
proc_macro2::TokenTree::Group(group) =>
proc_macro2::Group::new(group.delimiter(), replace_self_by_t(group.stream())).into(),
proc_macro2::TokenTree::Ident(ident) if ident == "Self" =>
proc_macro2::Ident::new("T", ident.span()).into(),
other => other,
})
.collect()
}
/// Check that the trait item requires the `TypeInfo` bound (or similar).
fn contains_type_info_bound(ty: &TraitItemType) -> bool {
const KNOWN_TYPE_INFO_BOUNDS: &[&str] = &[
// Explicit TypeInfo trait.
"TypeInfo",
// Implicit known bizinikiwi traits that implement type info.
// Note: Aim to keep this list as small as possible.
"Parameter",
];
ty.bounds.iter().any(|bound| {
let syn::TypeParamBound::Trait(bound) = bound else { return false };
KNOWN_TYPE_INFO_BOUNDS
.iter()
.any(|known| bound.path.segments.last().map_or(false, |last| last.ident == *known))
})
}
impl ConfigDef {
pub fn try_from(
pezframe_system: &syn::Path,
index: usize,
item: &mut syn::Item,
enable_default: bool,
disable_associated_metadata: bool,
is_pezframe_system: bool,
) -> syn::Result<Self> {
let syn::Item::Trait(item) = item else {
let msg = "Invalid pezpallet::config, expected trait definition";
return Err(syn::Error::new(item.span(), msg));
};
if !matches!(item.vis, syn::Visibility::Public(_)) {
let msg = "Invalid pezpallet::config, trait must be public";
return Err(syn::Error::new(item.span(), msg));
}
syn::parse2::<keyword::Config>(item.ident.to_token_stream())?;
let where_clause = {
let stream = replace_self_by_t(item.generics.where_clause.to_token_stream());
syn::parse2::<Option<syn::WhereClause>>(stream).expect(
"Internal error: replacing `Self` by `T` should result in valid where
clause",
)
};
if item.generics.params.len() > 1 {
let msg = "Invalid pezpallet::config, expected no more than one generic";
return Err(syn::Error::new(item.generics.params[2].span(), msg));
}
let has_instance = if item.generics.params.first().is_some() {
helper::check_config_def_gen(&item.generics, item.ident.span())?;
true
} else {
false
};
let mut consts_metadata = vec![];
let mut associated_types_metadata = vec![];
let mut warnings = vec![];
let mut default_sub_trait = if enable_default {
Some(DefaultTrait { items: Default::default(), has_system: !is_pezframe_system })
} else {
None
};
for trait_item in &mut item.items {
let is_event = check_event_type(pezframe_system, trait_item, has_instance)?;
let mut already_no_default = false;
let mut already_constant = false;
let mut already_no_default_bounds = false;
let mut already_collected_associated_type = None;
// add deprecation notice for `RuntimeEvent`, iff pezpallet is not `pezframe_system`
if is_event && !is_pezframe_system {
if let syn::TraitItem::Type(type_event) = trait_item {
let allow_dep: syn::Attribute = parse_quote!(#[allow(deprecated)]);
// Check if the `#[allow(deprecated)]` attribute is present
if !type_event.attrs.iter().any(|attr| attr == &allow_dep) {
let warning = Warning::new_deprecated("RuntimeEvent")
.old("have `RuntimeEvent` associated type in the pezpallet config")
.new("remove it as it is redundant since associated bound gets appended automatically: \n
pub trait Config: pezframe_system::Config<RuntimeEvent: From<Event<Self>>> { }")
.help_link("https://github.com/pezkuwichain/kurdistan-sdk/issues/125")
.span(type_event.ident.span())
.build_or_panic();
warnings.push(warning);
}
}
}
while let Some(pezpallet_attr) =
helper::take_first_item_pallet_attr::<PalletAttr>(trait_item)?
{
match (pezpallet_attr.typ, &trait_item) {
(PalletAttrType::Constant(_), syn::TraitItem::Type(ref typ)) => {
if already_constant {
return Err(syn::Error::new(
pezpallet_attr._bracket.span.join(),
"Duplicate #[pezpallet::constant] attribute not allowed.",
));
}
already_constant = true;
consts_metadata.push(ConstMetadataDef::try_from(typ)?);
},
(PalletAttrType::Constant(_), _) =>
return Err(syn::Error::new(
trait_item.span(),
"Invalid #[pezpallet::constant] in #[pezpallet::config], expected type item",
)),
// Pezpallet developer has explicitly requested to include metadata for this associated type.
//
// They must provide a type item that implements `TypeInfo`.
(PalletAttrType::IncludeMetadata(_), syn::TraitItem::Type(ref typ)) => {
if already_collected_associated_type.is_some() {
return Err(syn::Error::new(
pezpallet_attr._bracket.span.join(),
"Duplicate #[pezpallet::include_metadata] attribute not allowed.",
));
}
already_collected_associated_type = Some(pezpallet_attr._bracket.span.join());
associated_types_metadata.push(AssociatedTypeMetadataDef::from(AssociatedTypeMetadataDef::from(typ)));
}
(PalletAttrType::IncludeMetadata(_), _) =>
return Err(syn::Error::new(
pezpallet_attr._bracket.span.join(),
"Invalid #[pezpallet::include_metadata] in #[pezpallet::config], expected type item",
)),
(PalletAttrType::NoDefault(_), _) => {
if !enable_default {
return Err(syn::Error::new(
pezpallet_attr._bracket.span.join(),
"`#[pezpallet::no_default]` can only be used if `#[pezpallet::config(with_default)]` \
has been specified"
));
}
if already_no_default {
return Err(syn::Error::new(
pezpallet_attr._bracket.span.join(),
"Duplicate #[pezpallet::no_default] attribute not allowed.",
));
}
already_no_default = true;
},
(PalletAttrType::NoBounds(_), _) => {
if !enable_default {
return Err(syn::Error::new(
pezpallet_attr._bracket.span.join(),
"`#[pezpallet:no_default_bounds]` can only be used if `#[pezpallet::config(with_default)]` \
has been specified"
));
}
if already_no_default_bounds {
return Err(syn::Error::new(
pezpallet_attr._bracket.span.join(),
"Duplicate #[pezpallet::no_default_bounds] attribute not allowed.",
));
}
already_no_default_bounds = true;
},
}
}
if let Some(span) = already_collected_associated_type {
// Events and constants are already propagated to the metadata
if is_event {
return Err(syn::Error::new(
span,
"Invalid #[pezpallet::include_metadata] for `type RuntimeEvent`. \
The associated type `RuntimeEvent` is already collected in the metadata.",
));
}
if already_constant {
return Err(syn::Error::new(
span,
"Invalid #[pezpallet::include_metadata]: conflict with #[pezpallet::constant]. \
Pezpallet constant already collect the metadata for the type.",
));
}
if let syn::TraitItem::Type(ref ty) = trait_item {
if !contains_type_info_bound(ty) {
let msg = format!(
"Invalid #[pezpallet::include_metadata] in #[pezpallet::config], collected type `{}` \
does not implement `TypeInfo` or `Parameter`",
ty.ident,
);
return Err(syn::Error::new(span, msg));
}
}
} else {
// Metadata of associated types is collected by default, if the associated type
// implements `TypeInfo`, or a similar trait that requires the `TypeInfo` bound.
if !disable_associated_metadata && !is_event && !already_constant {
if let syn::TraitItem::Type(ref ty) = trait_item {
// Collect the metadata of the associated type if it implements `TypeInfo`.
if contains_type_info_bound(ty) {
associated_types_metadata.push(AssociatedTypeMetadataDef::from(ty));
}
}
}
}
if !already_no_default && enable_default {
default_sub_trait
.as_mut()
.expect("is 'Some(_)' if 'enable_default'; qed")
.items
.push((trait_item.clone(), already_no_default_bounds));
}
}
let attr: Option<DisableFrameSystemSupertraitCheck> =
helper::take_first_item_pallet_attr(&mut item.attrs)?;
let disable_system_supertrait_check = attr.is_some();
let has_pezframe_system_supertrait = item.supertraits.iter().any(|s| {
syn::parse2::<syn::Path>(s.to_token_stream())
.map_or(false, |b| has_expected_system_config(b, pezframe_system))
});
if !has_pezframe_system_supertrait && !disable_system_supertrait_check {
let found = if item.supertraits.is_empty() {
"none".to_string()
} else {
let mut found = item
.supertraits
.iter()
.fold(String::new(), |acc, s| format!("{}`{}`, ", acc, quote::quote!(#s)));
found.pop();
found.pop();
found
};
let msg = format!(
"Invalid pezpallet::trait, expected explicit `{}::Config` as supertrait, \
found {}. \
(try `pub trait Config: pezframe_system::Config {{ ...` or \
`pub trait Config<I: 'static>: pezframe_system::Config {{ ...`). \
To disable this check, use `#[pezpallet::disable_pezframe_system_supertrait_check]`",
pezframe_system.to_token_stream(),
found,
);
return Err(syn::Error::new(item.span(), msg));
}
Ok(Self {
index,
has_instance,
consts_metadata,
associated_types_metadata,
where_clause,
default_sub_trait,
warnings,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn has_expected_system_config_works() {
let pezframe_system = syn::parse2::<syn::Path>(quote::quote!(pezframe_system)).unwrap();
let path = syn::parse2::<syn::Path>(quote::quote!(pezframe_system::Config)).unwrap();
assert!(has_expected_system_config(path, &pezframe_system));
}
#[test]
fn has_expected_system_config_works_with_assoc_type() {
let pezframe_system = syn::parse2::<syn::Path>(quote::quote!(pezframe_system)).unwrap();
let path =
syn::parse2::<syn::Path>(quote::quote!(pezframe_system::Config<RuntimeCall = Call>))
.unwrap();
assert!(has_expected_system_config(path, &pezframe_system));
}
#[test]
fn has_expected_system_config_works_with_frame() {
let path = syn::parse2::<syn::Path>(quote::quote!(pezframe_system::Config)).unwrap();
let pezframe_system =
syn::parse2::<syn::Path>(quote::quote!(pezkuwi_sdk_frame::deps::pezframe_system)).unwrap();
assert!(has_expected_system_config(path.clone(), &pezframe_system));
let pezframe_system =
syn::parse2::<syn::Path>(quote::quote!(frame::deps::pezframe_system)).unwrap();
assert!(has_expected_system_config(path, &pezframe_system));
}
#[test]
fn has_expected_system_config_works_with_frame_full_path() {
let pezframe_system =
syn::parse2::<syn::Path>(quote::quote!(pezkuwi_sdk_frame::deps::pezframe_system)).unwrap();
let path =
syn::parse2::<syn::Path>(quote::quote!(pezkuwi_sdk_frame::deps::pezframe_system::Config))
.unwrap();
assert!(has_expected_system_config(path, &pezframe_system));
let pezframe_system =
syn::parse2::<syn::Path>(quote::quote!(frame::deps::pezframe_system)).unwrap();
let path =
syn::parse2::<syn::Path>(quote::quote!(frame::deps::pezframe_system::Config)).unwrap();
assert!(has_expected_system_config(path, &pezframe_system));
}
#[test]
fn has_expected_system_config_works_with_other_frame_full_path() {
let pezframe_system =
syn::parse2::<syn::Path>(quote::quote!(pezkuwi_sdk_frame::xyz::pezframe_system)).unwrap();
let path =
syn::parse2::<syn::Path>(quote::quote!(pezkuwi_sdk_frame::xyz::pezframe_system::Config))
.unwrap();
assert!(has_expected_system_config(path, &pezframe_system));
let pezframe_system =
syn::parse2::<syn::Path>(quote::quote!(frame::xyz::pezframe_system)).unwrap();
let path =
syn::parse2::<syn::Path>(quote::quote!(frame::xyz::pezframe_system::Config)).unwrap();
assert!(has_expected_system_config(path, &pezframe_system));
}
#[test]
fn has_expected_system_config_does_not_works_with_mixed_frame_full_path() {
let pezframe_system =
syn::parse2::<syn::Path>(quote::quote!(pezkuwi_sdk_frame::xyz::pezframe_system)).unwrap();
let path =
syn::parse2::<syn::Path>(quote::quote!(pezkuwi_sdk_frame::deps::pezframe_system::Config))
.unwrap();
assert!(!has_expected_system_config(path, &pezframe_system));
}
#[test]
fn has_expected_system_config_does_not_works_with_other_mixed_frame_full_path() {
let pezframe_system =
syn::parse2::<syn::Path>(quote::quote!(pezkuwi_sdk_frame::deps::pezframe_system)).unwrap();
let path =
syn::parse2::<syn::Path>(quote::quote!(pezkuwi_sdk_frame::xyz::pezframe_system::Config))
.unwrap();
assert!(!has_expected_system_config(path, &pezframe_system));
}
#[test]
fn has_expected_system_config_does_not_work_with_frame_full_path_if_not_frame_crate() {
let pezframe_system = syn::parse2::<syn::Path>(quote::quote!(pezframe_system)).unwrap();
let path =
syn::parse2::<syn::Path>(quote::quote!(pezkuwi_sdk_frame::deps::pezframe_system::Config))
.unwrap();
assert!(!has_expected_system_config(path, &pezframe_system));
}
#[test]
fn has_expected_system_config_unexpected_pezframe_system() {
let pezframe_system =
syn::parse2::<syn::Path>(quote::quote!(framez::deps::pezframe_system)).unwrap();
let path = syn::parse2::<syn::Path>(quote::quote!(pezframe_system::Config)).unwrap();
assert!(!has_expected_system_config(path, &pezframe_system));
}
#[test]
fn has_expected_system_config_unexpected_path() {
let pezframe_system = syn::parse2::<syn::Path>(quote::quote!(pezframe_system)).unwrap();
let path = syn::parse2::<syn::Path>(quote::quote!(pezframe_system::ConfigSystem)).unwrap();
assert!(!has_expected_system_config(path, &pezframe_system));
}
#[test]
fn has_expected_system_config_not_pezframe_system() {
let pezframe_system = syn::parse2::<syn::Path>(quote::quote!(something)).unwrap();
let path = syn::parse2::<syn::Path>(quote::quote!(something::Config)).unwrap();
assert!(!has_expected_system_config(path, &pezframe_system));
}
}
@@ -0,0 +1,121 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use super::helper;
use crate::deprecation::extract_or_return_allow_attrs;
use quote::ToTokens;
use syn::{spanned::Spanned, Fields};
/// List of additional token to be used for parsing.
mod keyword {
syn::custom_keyword!(Error);
}
/// Records information about the error enum variant field.
pub struct VariantField {
/// Whether or not the field is named, i.e. whether it is a tuple variant or struct variant.
pub is_named: bool,
}
/// Records information about the error enum variants.
pub struct VariantDef {
/// The variant ident.
pub ident: syn::Ident,
/// The variant field, if any.
pub field: Option<VariantField>,
/// The `cfg` attributes.
pub cfg_attrs: Vec<syn::Attribute>,
/// The `allow` attributes.
pub maybe_allow_attrs: Vec<syn::Attribute>,
}
/// This checks error declaration as a enum declaration with only variants without fields nor
/// discriminant.
pub struct ErrorDef {
/// The index of error item in pezpallet module.
pub index: usize,
/// Variant definitions.
pub variants: Vec<VariantDef>,
/// A set of usage of instance, must be check for consistency with trait.
pub instances: Vec<helper::InstanceUsage>,
/// The keyword error used (contains span).
pub error: keyword::Error,
/// The span of the pezpallet::error attribute.
pub attr_span: proc_macro2::Span,
}
impl ErrorDef {
pub fn try_from(
attr_span: proc_macro2::Span,
index: usize,
item: &mut syn::Item,
) -> syn::Result<Self> {
let item = if let syn::Item::Enum(item) = item {
item
} else {
return Err(syn::Error::new(item.span(), "Invalid pezpallet::error, expected item enum"));
};
if !matches!(item.vis, syn::Visibility::Public(_)) {
let msg = "Invalid pezpallet::error, `Error` must be public";
return Err(syn::Error::new(item.span(), msg));
}
crate::deprecation::prevent_deprecation_attr_on_outer_enum(&item.attrs)?;
let instances =
vec![helper::check_type_def_gen_no_bounds(&item.generics, item.ident.span())?];
if item.generics.where_clause.is_some() {
let msg = "Invalid pezpallet::error, where clause is not allowed on pezpallet error item";
return Err(syn::Error::new(item.generics.where_clause.as_ref().unwrap().span(), msg));
}
let error = syn::parse2::<keyword::Error>(item.ident.to_token_stream())?;
let variants = item
.variants
.iter()
.map(|variant| {
let field_ty = match &variant.fields {
Fields::Unit => None,
Fields::Named(_) => Some(VariantField { is_named: true }),
Fields::Unnamed(_) => Some(VariantField { is_named: false }),
};
match &variant.discriminant {
None |
Some((_, syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Int(_), .. }))) => {},
Some((_, expr)) => {
let msg = "Invalid pezpallet::error, only integer discriminants are supported";
return Err(syn::Error::new(expr.span(), msg));
},
}
let cfg_attrs: Vec<syn::Attribute> = helper::get_item_cfg_attrs(&variant.attrs);
let maybe_allow_attrs = extract_or_return_allow_attrs(&variant.attrs).collect();
Ok(VariantDef {
ident: variant.ident.clone(),
field: field_ty,
cfg_attrs,
maybe_allow_attrs,
})
})
.collect::<Result<_, _>>()?;
Ok(ErrorDef { attr_span, index, variants, instances, error })
}
}
@@ -0,0 +1,143 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use super::helper;
use quote::ToTokens;
use syn::spanned::Spanned;
/// List of additional token to be used for parsing.
mod keyword {
syn::custom_keyword!(Event);
syn::custom_keyword!(pezpallet);
syn::custom_keyword!(generate_deposit);
syn::custom_keyword!(deposit_event);
}
/// Definition for pezpallet event enum.
pub struct EventDef {
/// The index of event item in pezpallet module.
pub index: usize,
/// The keyword Event used (contains span).
pub event: keyword::Event,
/// A set of usage of instance, must be check for consistency with trait.
pub instances: Vec<helper::InstanceUsage>,
/// The kind of generic the type `Event` has.
pub gen_kind: super::GenericKind,
/// Whether the function `deposit_event` must be generated.
pub deposit_event: Option<PalletEventDepositAttr>,
/// Where clause used in event definition.
pub where_clause: Option<syn::WhereClause>,
/// The span of the pezpallet::event attribute.
pub attr_span: proc_macro2::Span,
}
/// Attribute for a pezpallet's Event.
///
/// Syntax is:
/// * `#[pezpallet::generate_deposit($vis fn deposit_event)]`
pub struct PalletEventDepositAttr {
pub fn_vis: syn::Visibility,
// Span for the keyword deposit_event
pub fn_span: proc_macro2::Span,
// Span of the attribute
pub span: proc_macro2::Span,
}
impl syn::parse::Parse for PalletEventDepositAttr {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
input.parse::<syn::Token![#]>()?;
let content;
syn::bracketed!(content in input);
content.parse::<keyword::pezpallet>()?;
content.parse::<syn::Token![::]>()?;
let span = content.parse::<keyword::generate_deposit>()?.span();
let generate_content;
syn::parenthesized!(generate_content in content);
let fn_vis = generate_content.parse::<syn::Visibility>()?;
generate_content.parse::<syn::Token![fn]>()?;
let fn_span = generate_content.parse::<keyword::deposit_event>()?.span();
Ok(PalletEventDepositAttr { fn_vis, span, fn_span })
}
}
struct PalletEventAttrInfo {
deposit_event: Option<PalletEventDepositAttr>,
}
impl PalletEventAttrInfo {
fn from_attrs(attrs: Vec<PalletEventDepositAttr>) -> syn::Result<Self> {
let mut deposit_event = None;
for attr in attrs {
if deposit_event.is_none() {
deposit_event = Some(attr)
} else {
return Err(syn::Error::new(attr.span, "Duplicate attribute"));
}
}
Ok(PalletEventAttrInfo { deposit_event })
}
}
impl EventDef {
pub fn try_from(
attr_span: proc_macro2::Span,
index: usize,
item: &mut syn::Item,
) -> syn::Result<Self> {
let item = if let syn::Item::Enum(item) = item {
item
} else {
return Err(syn::Error::new(item.span(), "Invalid pezpallet::event, expected enum item"));
};
crate::deprecation::prevent_deprecation_attr_on_outer_enum(&item.attrs)?;
let event_attrs: Vec<PalletEventDepositAttr> =
helper::take_item_pallet_attrs(&mut item.attrs)?;
let attr_info = PalletEventAttrInfo::from_attrs(event_attrs)?;
let deposit_event = attr_info.deposit_event;
if !matches!(item.vis, syn::Visibility::Public(_)) {
let msg = "Invalid pezpallet::event, `Event` must be public";
return Err(syn::Error::new(item.span(), msg));
}
let where_clause = item.generics.where_clause.clone();
let mut instances = vec![];
// NOTE: Event is not allowed to be only generic on I because it is not supported
// by construct_runtime.
if let Some(u) = helper::check_type_def_optional_gen(&item.generics, item.ident.span())? {
instances.push(u);
} else {
// construct_runtime only allow non generic event for non instantiable pezpallet.
instances.push(helper::InstanceUsage { has_instance: false, span: item.ident.span() })
}
let has_instance = item.generics.type_params().any(|t| t.ident == "I");
let has_config = item.generics.type_params().any(|t| t.ident == "T");
let gen_kind = super::GenericKind::from_gens(has_config, has_instance)
.expect("Checked by `helper::check_type_def_optional_gen` above");
let event = syn::parse2::<keyword::Event>(item.ident.to_token_stream())?;
Ok(EventDef { attr_span, index, instances, deposit_event, event, gen_kind, where_clause })
}
}
@@ -0,0 +1,156 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use super::helper;
use pezframe_support_procedural_tools::get_doc_literals;
use syn::spanned::Spanned;
/// List of additional token to be used for parsing.
mod keyword {
syn::custom_keyword!(DispatchResultWithPostInfo);
syn::custom_keyword!(Call);
syn::custom_keyword!(OriginFor);
syn::custom_keyword!(weight);
syn::custom_keyword!(compact);
syn::custom_keyword!(T);
syn::custom_keyword!(pezpallet);
syn::custom_keyword!(constant_name);
}
/// Definition of extra constants typically `impl<T: Config> Pezpallet<T> { ... }`
pub struct ExtraConstantsDef {
/// The where_clause used.
pub where_clause: Option<syn::WhereClause>,
/// A set of usage of instance, must be check for consistency with trait.
pub instances: Vec<helper::InstanceUsage>,
/// The extra constant defined.
pub extra_constants: Vec<ExtraConstantDef>,
}
/// Input definition for an constant in pezpallet.
pub struct ExtraConstantDef {
/// Name of the function
pub ident: syn::Ident,
/// The type returned by the function
pub type_: syn::Type,
/// The doc associated
pub doc: Vec<syn::Expr>,
/// Optional MetaData Name
pub metadata_name: Option<syn::Ident>,
/// Attributes
pub attrs: Vec<syn::Attribute>,
}
/// Attributes for functions in extra_constants impl block.
/// Parse for `#[pezpallet::constant_name(ConstantName)]`
pub struct ExtraConstAttr {
metadata_name: syn::Ident,
}
impl syn::parse::Parse for ExtraConstAttr {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
input.parse::<syn::Token![#]>()?;
let content;
syn::bracketed!(content in input);
content.parse::<keyword::pezpallet>()?;
content.parse::<syn::Token![::]>()?;
content.parse::<keyword::constant_name>()?;
let metadata_name;
syn::parenthesized!(metadata_name in content);
Ok(ExtraConstAttr { metadata_name: metadata_name.parse::<syn::Ident>()? })
}
}
impl ExtraConstantsDef {
pub fn try_from(item: &mut syn::Item) -> syn::Result<Self> {
let item = if let syn::Item::Impl(item) = item {
item
} else {
return Err(syn::Error::new(
item.span(),
"Invalid pezpallet::extra_constants, expected item impl",
));
};
let instances = vec![
helper::check_impl_gen(&item.generics, item.impl_token.span())?,
helper::check_pallet_struct_usage(&item.self_ty)?,
];
if let Some((_, _, for_)) = item.trait_ {
let msg = "Invalid pezpallet::call, expected no trait ident as in \
`impl<..> Pezpallet<..> { .. }`";
return Err(syn::Error::new(for_.span(), msg));
}
let mut extra_constants = vec![];
for impl_item in &mut item.items {
let method = if let syn::ImplItem::Fn(method) = impl_item {
method
} else {
let msg = "Invalid pezpallet::call, only method accepted";
return Err(syn::Error::new(impl_item.span(), msg));
};
if !method.sig.inputs.is_empty() {
let msg = "Invalid pezpallet::extra_constants, method must have 0 args";
return Err(syn::Error::new(method.sig.span(), msg));
}
if !method.sig.generics.params.is_empty() {
let msg = "Invalid pezpallet::extra_constants, method must have 0 generics";
return Err(syn::Error::new(method.sig.generics.params[0].span(), msg));
}
if method.sig.generics.where_clause.is_some() {
let msg = "Invalid pezpallet::extra_constants, method must have no where clause";
return Err(syn::Error::new(method.sig.generics.where_clause.span(), msg));
}
let type_ = match &method.sig.output {
syn::ReturnType::Default => {
let msg = "Invalid pezpallet::extra_constants, method must have a return type";
return Err(syn::Error::new(method.span(), msg));
},
syn::ReturnType::Type(_, type_) => *type_.clone(),
};
// parse metadata_name
let mut extra_constant_attrs: Vec<ExtraConstAttr> =
helper::take_item_pallet_attrs(method)?;
if extra_constant_attrs.len() > 1 {
let msg =
"Invalid attribute in pezpallet::constant_name, only one attribute is expected";
return Err(syn::Error::new(extra_constant_attrs[1].metadata_name.span(), msg));
}
let metadata_name = extra_constant_attrs.pop().map(|attr| attr.metadata_name);
extra_constants.push(ExtraConstantDef {
ident: method.sig.ident.clone(),
type_,
doc: get_doc_literals(&method.attrs),
metadata_name,
attrs: method.attrs.clone(),
});
}
Ok(Self { instances, where_clause: item.generics.where_clause.clone(), extra_constants })
}
}
@@ -0,0 +1,55 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use super::helper;
use syn::spanned::Spanned;
/// Definition for pezpallet genesis build implementation.
pub struct GenesisBuildDef {
/// A set of usage of instance, must be check for consistency with trait.
pub instances: Option<Vec<helper::InstanceUsage>>,
/// The where_clause used.
pub where_clause: Option<syn::WhereClause>,
/// The span of the pezpallet::genesis_build attribute.
pub attr_span: proc_macro2::Span,
}
impl GenesisBuildDef {
pub fn try_from(attr_span: proc_macro2::Span, item: &mut syn::Item) -> syn::Result<Self> {
let item = if let syn::Item::Impl(item) = item {
item
} else {
let msg = "Invalid pezpallet::genesis_build, expected item impl";
return Err(syn::Error::new(item.span(), msg));
};
let item_trait = &item
.trait_
.as_ref()
.ok_or_else(|| {
let msg = "Invalid pezpallet::genesis_build, expected impl<..> GenesisBuild<..> \
for GenesisConfig<..>";
syn::Error::new(item.span(), msg)
})?
.1;
let instances =
helper::check_genesis_builder_usage(item_trait)?.map(|instances| vec![instances]);
Ok(Self { attr_span, instances, where_clause: item.generics.where_clause.clone() })
}
}
@@ -0,0 +1,73 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use super::helper;
use syn::spanned::Spanned;
/// Definition for pezpallet genesis config type.
///
/// Either:
/// * `struct GenesisConfig`
/// * `enum GenesisConfig`
pub struct GenesisConfigDef {
/// The index of item in pezpallet module.
pub index: usize,
/// The kind of generic the type `GenesisConfig` has.
pub gen_kind: super::GenericKind,
/// A set of usage of instance, must be check for consistency with trait.
pub instances: Vec<helper::InstanceUsage>,
/// The ident of genesis_config, can be used for span.
pub genesis_config: syn::Ident,
}
impl GenesisConfigDef {
pub fn try_from(index: usize, item: &mut syn::Item) -> syn::Result<Self> {
let item_span = item.span();
let (vis, ident, generics) = match &item {
syn::Item::Enum(item) => (&item.vis, &item.ident, &item.generics),
syn::Item::Struct(item) => (&item.vis, &item.ident, &item.generics),
_ => {
let msg = "Invalid pezpallet::genesis_config, expected enum or struct";
return Err(syn::Error::new(item.span(), msg));
},
};
let mut instances = vec![];
// NOTE: GenesisConfig is not allowed to be only generic on I because it is not supported
// by construct_runtime.
if let Some(u) = helper::check_type_def_optional_gen(generics, ident.span())? {
instances.push(u);
}
let has_instance = generics.type_params().any(|t| t.ident == "I");
let has_config = generics.type_params().any(|t| t.ident == "T");
let gen_kind = super::GenericKind::from_gens(has_config, has_instance)
.expect("Checked by `helper::check_type_def_optional_gen` above");
if !matches!(vis, syn::Visibility::Public(_)) {
let msg = "Invalid pezpallet::genesis_config, GenesisConfig must be public";
return Err(syn::Error::new(item_span, msg));
}
if ident != "GenesisConfig" {
let msg = "Invalid pezpallet::genesis_config, ident must `GenesisConfig`";
return Err(syn::Error::new(ident.span(), msg));
}
Ok(GenesisConfigDef { index, genesis_config: ident.clone(), instances, gen_kind })
}
}
@@ -0,0 +1,645 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::spanned::Spanned;
/// List of additional token to be used for parsing.
mod keyword {
syn::custom_keyword!(I);
syn::custom_keyword!(compact);
syn::custom_keyword!(GenesisBuild);
syn::custom_keyword!(BuildGenesisConfig);
syn::custom_keyword!(Config);
syn::custom_keyword!(T);
syn::custom_keyword!(Pezpallet);
syn::custom_keyword!(origin);
syn::custom_keyword!(DispatchResult);
syn::custom_keyword!(DispatchResultWithPostInfo);
}
/// A usage of instance, either the trait `Config` has been used with instance or without instance.
/// Used to check for consistency.
#[derive(Clone)]
pub struct InstanceUsage {
pub has_instance: bool,
pub span: proc_macro2::Span,
}
/// Trait implemented for syn items to get mutable references on their attributes.
///
/// NOTE: verbatim variants are not supported.
pub trait MutItemAttrs {
fn mut_item_attrs(&mut self) -> Option<&mut Vec<syn::Attribute>>;
}
/// Take the first pezpallet attribute (e.g. attribute like `#[pezpallet..]`) and decode it to `Attr`
pub(crate) fn take_first_item_pallet_attr<Attr>(
item: &mut impl MutItemAttrs,
) -> syn::Result<Option<Attr>>
where
Attr: syn::parse::Parse,
{
let Some(attrs) = item.mut_item_attrs() else { return Ok(None) };
let Some(index) = attrs.iter().position(|attr| {
attr.path().segments.first().map_or(false, |segment| segment.ident == "pezpallet")
}) else {
return Ok(None);
};
let pezpallet_attr = attrs.remove(index);
Ok(Some(syn::parse2(pezpallet_attr.into_token_stream())?))
}
/// Take all the pezpallet attributes (e.g. attribute like `#[pezpallet..]`) and decode them to `Attr`
pub(crate) fn take_item_pallet_attrs<Attr>(item: &mut impl MutItemAttrs) -> syn::Result<Vec<Attr>>
where
Attr: syn::parse::Parse,
{
let mut pezpallet_attrs = Vec::new();
while let Some(attr) = take_first_item_pallet_attr(item)? {
pezpallet_attrs.push(attr)
}
Ok(pezpallet_attrs)
}
/// Get all the cfg attributes (e.g. attribute like `#[cfg..]`) and decode them to `Attr`
pub fn get_item_cfg_attrs(attrs: &[syn::Attribute]) -> Vec<syn::Attribute> {
attrs
.iter()
.filter_map(|attr| {
if attr.path().segments.first().map_or(false, |segment| segment.ident == "cfg") {
Some(attr.clone())
} else {
None
}
})
.collect::<Vec<_>>()
}
impl MutItemAttrs for syn::Item {
fn mut_item_attrs(&mut self) -> Option<&mut Vec<syn::Attribute>> {
match self {
Self::Const(item) => Some(item.attrs.as_mut()),
Self::Enum(item) => Some(item.attrs.as_mut()),
Self::ExternCrate(item) => Some(item.attrs.as_mut()),
Self::Fn(item) => Some(item.attrs.as_mut()),
Self::ForeignMod(item) => Some(item.attrs.as_mut()),
Self::Impl(item) => Some(item.attrs.as_mut()),
Self::Macro(item) => Some(item.attrs.as_mut()),
Self::Mod(item) => Some(item.attrs.as_mut()),
Self::Static(item) => Some(item.attrs.as_mut()),
Self::Struct(item) => Some(item.attrs.as_mut()),
Self::Trait(item) => Some(item.attrs.as_mut()),
Self::TraitAlias(item) => Some(item.attrs.as_mut()),
Self::Type(item) => Some(item.attrs.as_mut()),
Self::Union(item) => Some(item.attrs.as_mut()),
Self::Use(item) => Some(item.attrs.as_mut()),
_ => None,
}
}
}
impl MutItemAttrs for syn::TraitItem {
fn mut_item_attrs(&mut self) -> Option<&mut Vec<syn::Attribute>> {
match self {
Self::Const(item) => Some(item.attrs.as_mut()),
Self::Fn(item) => Some(item.attrs.as_mut()),
Self::Type(item) => Some(item.attrs.as_mut()),
Self::Macro(item) => Some(item.attrs.as_mut()),
_ => None,
}
}
}
impl MutItemAttrs for Vec<syn::Attribute> {
fn mut_item_attrs(&mut self) -> Option<&mut Vec<syn::Attribute>> {
Some(self)
}
}
impl MutItemAttrs for syn::ItemMod {
fn mut_item_attrs(&mut self) -> Option<&mut Vec<syn::Attribute>> {
Some(&mut self.attrs)
}
}
impl MutItemAttrs for syn::ImplItemFn {
fn mut_item_attrs(&mut self) -> Option<&mut Vec<syn::Attribute>> {
Some(&mut self.attrs)
}
}
impl MutItemAttrs for syn::ItemType {
fn mut_item_attrs(&mut self) -> Option<&mut Vec<syn::Attribute>> {
Some(&mut self.attrs)
}
}
/// Parse for `()`
struct Unit;
impl syn::parse::Parse for Unit {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let content;
syn::parenthesized!(content in input);
if !content.is_empty() {
let msg = "unexpected tokens, expected nothing inside parenthesis as `()`";
return Err(syn::Error::new(content.span(), msg));
}
Ok(Self)
}
}
/// Parse for `'static`
struct StaticLifetime;
impl syn::parse::Parse for StaticLifetime {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let lifetime = input.parse::<syn::Lifetime>()?;
if lifetime.ident != "static" {
let msg = "unexpected tokens, expected `static`";
return Err(syn::Error::new(lifetime.ident.span(), msg));
}
Ok(Self)
}
}
/// Check the syntax: `I: 'static = ()`
///
/// `span` is used in case generics is empty (empty generics has span == call_site).
///
/// return the instance if found.
pub fn check_config_def_gen(gen: &syn::Generics, span: proc_macro2::Span) -> syn::Result<()> {
let expected = "expected `I: 'static = ()`";
pub struct CheckTraitDefGenerics;
impl syn::parse::Parse for CheckTraitDefGenerics {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
input.parse::<keyword::I>()?;
input.parse::<syn::Token![:]>()?;
input.parse::<StaticLifetime>()?;
input.parse::<syn::Token![=]>()?;
input.parse::<Unit>()?;
Ok(Self)
}
}
syn::parse2::<CheckTraitDefGenerics>(gen.params.to_token_stream()).map_err(|e| {
let msg = format!("Invalid generics: {}", expected);
let mut err = syn::Error::new(span, msg);
err.combine(e);
err
})?;
Ok(())
}
/// Check the syntax:
/// * either `T`
/// * or `T, I = ()`
///
/// `span` is used in case generics is empty (empty generics has span == call_site).
///
/// return the instance if found.
pub fn check_type_def_gen_no_bounds(
gen: &syn::Generics,
span: proc_macro2::Span,
) -> syn::Result<InstanceUsage> {
let expected = "expected `T` or `T, I = ()`";
pub struct Checker(InstanceUsage);
impl syn::parse::Parse for Checker {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let mut instance_usage = InstanceUsage { has_instance: false, span: input.span() };
input.parse::<keyword::T>()?;
if input.peek(syn::Token![,]) {
instance_usage.has_instance = true;
input.parse::<syn::Token![,]>()?;
input.parse::<keyword::I>()?;
input.parse::<syn::Token![=]>()?;
input.parse::<Unit>()?;
}
Ok(Self(instance_usage))
}
}
let i = syn::parse2::<Checker>(gen.params.to_token_stream())
.map_err(|e| {
let msg = format!("Invalid type def generics: {}", expected);
let mut err = syn::Error::new(span, msg);
err.combine(e);
err
})?
.0;
Ok(i)
}
/// Check the syntax:
/// * either `` (no generics
/// * or `T`
/// * or `T: Config`
/// * or `T, I = ()`
/// * or `T: Config<I>, I: 'static = ()`
///
/// `span` is used in case generics is empty (empty generics has span == call_site).
///
/// return some instance usage if there is some generic, or none otherwise.
pub fn check_type_def_optional_gen(
gen: &syn::Generics,
span: proc_macro2::Span,
) -> syn::Result<Option<InstanceUsage>> {
let expected = "expected `` or `T` or `T: Config` or `T, I = ()` or \
`T: Config<I>, I: 'static = ()`";
pub struct Checker(Option<InstanceUsage>);
impl syn::parse::Parse for Checker {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
if input.is_empty() {
return Ok(Self(None));
}
let mut instance_usage = InstanceUsage { span: input.span(), has_instance: false };
input.parse::<keyword::T>()?;
if input.is_empty() {
return Ok(Self(Some(instance_usage)));
}
let lookahead = input.lookahead1();
if lookahead.peek(syn::Token![,]) {
instance_usage.has_instance = true;
input.parse::<syn::Token![,]>()?;
input.parse::<keyword::I>()?;
input.parse::<syn::Token![=]>()?;
input.parse::<Unit>()?;
Ok(Self(Some(instance_usage)))
} else if lookahead.peek(syn::Token![:]) {
input.parse::<syn::Token![:]>()?;
input.parse::<keyword::Config>()?;
if input.is_empty() {
return Ok(Self(Some(instance_usage)));
}
instance_usage.has_instance = true;
input.parse::<syn::Token![<]>()?;
input.parse::<keyword::I>()?;
input.parse::<syn::Token![>]>()?;
input.parse::<syn::Token![,]>()?;
input.parse::<keyword::I>()?;
input.parse::<syn::Token![:]>()?;
input.parse::<StaticLifetime>()?;
input.parse::<syn::Token![=]>()?;
input.parse::<Unit>()?;
Ok(Self(Some(instance_usage)))
} else {
Err(lookahead.error())
}
}
}
let i = syn::parse2::<Checker>(gen.params.to_token_stream())
.map_err(|e| {
let msg = format!("Invalid type def generics: {}", expected);
let mut err = syn::Error::new(span, msg);
err.combine(e);
err
})?
.0
// Span can be call_site if generic is empty. Thus we replace it.
.map(|mut i| {
i.span = span;
i
});
Ok(i)
}
/// Check the syntax:
/// * either `Pezpallet<T>`
/// * or `Pezpallet<T, I>`
///
/// return the instance if found.
pub fn check_pallet_struct_usage(type_: &Box<syn::Type>) -> syn::Result<InstanceUsage> {
let expected = "expected `Pezpallet<T>` or `Pezpallet<T, I>`";
pub struct Checker(InstanceUsage);
impl syn::parse::Parse for Checker {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let mut instance_usage = InstanceUsage { span: input.span(), has_instance: false };
input.parse::<keyword::Pezpallet>()?;
input.parse::<syn::Token![<]>()?;
input.parse::<keyword::T>()?;
if input.peek(syn::Token![,]) {
instance_usage.has_instance = true;
input.parse::<syn::Token![,]>()?;
input.parse::<keyword::I>()?;
}
input.parse::<syn::Token![>]>()?;
Ok(Self(instance_usage))
}
}
let i = syn::parse2::<Checker>(type_.to_token_stream())
.map_err(|e| {
let msg = format!("Invalid pezpallet struct: {}", expected);
let mut err = syn::Error::new(type_.span(), msg);
err.combine(e);
err
})?
.0;
Ok(i)
}
/// Check the generic is:
/// * either `T: Config`
/// * or `T: Config<I>, I: 'static`
///
/// `span` is used in case generics is empty (empty generics has span == call_site).
///
/// return whether it contains instance.
pub fn check_impl_gen(gen: &syn::Generics, span: proc_macro2::Span) -> syn::Result<InstanceUsage> {
let expected = "expected `impl<T: Config>` or `impl<T: Config<I>, I: 'static>`";
pub struct Checker(InstanceUsage);
impl syn::parse::Parse for Checker {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let mut instance_usage = InstanceUsage { span: input.span(), has_instance: false };
input.parse::<keyword::T>()?;
input.parse::<syn::Token![:]>()?;
input.parse::<keyword::Config>()?;
if input.peek(syn::Token![<]) {
instance_usage.has_instance = true;
input.parse::<syn::Token![<]>()?;
input.parse::<keyword::I>()?;
input.parse::<syn::Token![>]>()?;
input.parse::<syn::Token![,]>()?;
input.parse::<keyword::I>()?;
input.parse::<syn::Token![:]>()?;
input.parse::<StaticLifetime>()?;
}
Ok(Self(instance_usage))
}
}
let i = syn::parse2::<Checker>(gen.params.to_token_stream())
.map_err(|e| {
let mut err = syn::Error::new(span, format!("Invalid generics: {}", expected));
err.combine(e);
err
})?
.0;
Ok(i)
}
/// Check the syntax:
/// * or `T`
/// * or `T: Config`
/// * or `T, I = ()`
/// * or `T: Config<I>, I: 'static = ()`
///
/// `span` is used in case generics is empty (empty generics has span == call_site).
///
/// return the instance if found.
pub fn check_type_def_gen(
gen: &syn::Generics,
span: proc_macro2::Span,
) -> syn::Result<InstanceUsage> {
let expected = "expected `T` or `T: Config` or `T, I = ()` or \
`T: Config<I>, I: 'static = ()`";
pub struct Checker(InstanceUsage);
impl syn::parse::Parse for Checker {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let mut instance_usage = InstanceUsage { span: input.span(), has_instance: false };
input.parse::<keyword::T>()?;
if input.is_empty() {
return Ok(Self(instance_usage));
}
let lookahead = input.lookahead1();
if lookahead.peek(syn::Token![,]) {
instance_usage.has_instance = true;
input.parse::<syn::Token![,]>()?;
input.parse::<keyword::I>()?;
input.parse::<syn::Token![=]>()?;
input.parse::<Unit>()?;
Ok(Self(instance_usage))
} else if lookahead.peek(syn::Token![:]) {
input.parse::<syn::Token![:]>()?;
input.parse::<keyword::Config>()?;
if input.is_empty() {
return Ok(Self(instance_usage));
}
instance_usage.has_instance = true;
input.parse::<syn::Token![<]>()?;
input.parse::<keyword::I>()?;
input.parse::<syn::Token![>]>()?;
input.parse::<syn::Token![,]>()?;
input.parse::<keyword::I>()?;
input.parse::<syn::Token![:]>()?;
input.parse::<StaticLifetime>()?;
input.parse::<syn::Token![=]>()?;
input.parse::<Unit>()?;
Ok(Self(instance_usage))
} else {
Err(lookahead.error())
}
}
}
let mut i = syn::parse2::<Checker>(gen.params.to_token_stream())
.map_err(|e| {
let msg = format!("Invalid type def generics: {}", expected);
let mut err = syn::Error::new(span, msg);
err.combine(e);
err
})?
.0;
// Span can be call_site if generic is empty. Thus we replace it.
i.span = span;
Ok(i)
}
/// Check the syntax:
/// * either `GenesisBuild<T>`
/// * or `GenesisBuild<T, I>`
/// * or `BuildGenesisConfig`
///
/// return the instance if found for `GenesisBuild`
/// return None for BuildGenesisConfig
pub fn check_genesis_builder_usage(type_: &syn::Path) -> syn::Result<Option<InstanceUsage>> {
let expected = "expected `BuildGenesisConfig` (or the deprecated `GenesisBuild<T>` or `GenesisBuild<T, I>`)";
pub struct Checker(Option<InstanceUsage>);
impl syn::parse::Parse for Checker {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let mut instance_usage = InstanceUsage { span: input.span(), has_instance: false };
if input.peek(keyword::GenesisBuild) {
input.parse::<keyword::GenesisBuild>()?;
input.parse::<syn::Token![<]>()?;
input.parse::<keyword::T>()?;
if input.peek(syn::Token![,]) {
instance_usage.has_instance = true;
input.parse::<syn::Token![,]>()?;
input.parse::<keyword::I>()?;
}
input.parse::<syn::Token![>]>()?;
return Ok(Self(Some(instance_usage)));
} else {
input.parse::<keyword::BuildGenesisConfig>()?;
return Ok(Self(None));
}
}
}
let i = syn::parse2::<Checker>(type_.to_token_stream())
.map_err(|e| {
let msg = format!("Invalid genesis builder: {}", expected);
let mut err = syn::Error::new(type_.span(), msg);
err.combine(e);
err
})?
.0;
Ok(i)
}
/// Check the syntax:
/// * either `` (no generics)
/// * or `T: Config`
/// * or `T: Config<I>, I: 'static`
///
/// `span` is used in case generics is empty (empty generics has span == call_site).
///
/// return the instance if found.
pub fn check_type_value_gen(
gen: &syn::Generics,
span: proc_macro2::Span,
) -> syn::Result<Option<InstanceUsage>> {
let expected = "expected `` or `T: Config` or `T: Config<I>, I: 'static`";
pub struct Checker(Option<InstanceUsage>);
impl syn::parse::Parse for Checker {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
if input.is_empty() {
return Ok(Self(None));
}
input.parse::<keyword::T>()?;
input.parse::<syn::Token![:]>()?;
input.parse::<keyword::Config>()?;
let mut instance_usage = InstanceUsage { span: input.span(), has_instance: false };
if input.is_empty() {
return Ok(Self(Some(instance_usage)));
}
instance_usage.has_instance = true;
input.parse::<syn::Token![<]>()?;
input.parse::<keyword::I>()?;
input.parse::<syn::Token![>]>()?;
input.parse::<syn::Token![,]>()?;
input.parse::<keyword::I>()?;
input.parse::<syn::Token![:]>()?;
input.parse::<StaticLifetime>()?;
Ok(Self(Some(instance_usage)))
}
}
let i = syn::parse2::<Checker>(gen.params.to_token_stream())
.map_err(|e| {
let msg = format!("Invalid type def generics: {}", expected);
let mut err = syn::Error::new(span, msg);
err.combine(e);
err
})?
.0
// Span can be call_site if generic is empty. Thus we replace it.
.map(|mut i| {
i.span = span;
i
});
Ok(i)
}
/// The possible return type of a dispatchable.
#[derive(Clone)]
pub enum CallReturnType {
DispatchResult,
DispatchResultWithPostInfo,
}
/// Check the keyword `DispatchResultWithPostInfo` or `DispatchResult`.
pub fn check_pallet_call_return_type(sig: &syn::Signature) -> syn::Result<CallReturnType> {
let syn::ReturnType::Type(_, type_) = &sig.output else {
let msg = "Invalid pezpallet::call, require return type \
DispatchResultWithPostInfo";
return Err(syn::Error::new(sig.span(), msg));
};
pub struct Checker(CallReturnType);
impl syn::parse::Parse for Checker {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(keyword::DispatchResultWithPostInfo) {
input.parse::<keyword::DispatchResultWithPostInfo>()?;
Ok(Self(CallReturnType::DispatchResultWithPostInfo))
} else if lookahead.peek(keyword::DispatchResult) {
input.parse::<keyword::DispatchResult>()?;
Ok(Self(CallReturnType::DispatchResult))
} else {
Err(lookahead.error())
}
}
}
syn::parse2::<Checker>(type_.to_token_stream()).map(|c| c.0)
}
pub(crate) fn two128_str(s: &str) -> TokenStream {
bytes_to_array(pezsp_crypto_hashing::twox_128(s.as_bytes()).into_iter())
}
pub(crate) fn bytes_to_array(bytes: impl IntoIterator<Item = u8>) -> TokenStream {
let bytes = bytes.into_iter();
quote!(
[ #( #bytes ),* ]
)
.into()
}
@@ -0,0 +1,79 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use super::helper;
use syn::spanned::Spanned;
/// Implementation of the pezpallet hooks.
pub struct HooksDef {
/// A set of usage of instance, must be check for consistency with trait.
pub instances: Vec<helper::InstanceUsage>,
/// The where_clause used.
pub where_clause: Option<syn::WhereClause>,
/// The span of the pezpallet::hooks attribute.
pub attr_span: proc_macro2::Span,
/// Boolean flag, set to true if the `on_runtime_upgrade` method of hooks was implemented.
pub has_runtime_upgrade: bool,
}
impl HooksDef {
pub fn try_from(attr_span: proc_macro2::Span, item: &mut syn::Item) -> syn::Result<Self> {
let item = if let syn::Item::Impl(item) = item {
item
} else {
let msg = "Invalid pezpallet::hooks, expected item impl";
return Err(syn::Error::new(item.span(), msg));
};
let instances = vec![
helper::check_pallet_struct_usage(&item.self_ty)?,
helper::check_impl_gen(&item.generics, item.impl_token.span())?,
];
let item_trait = &item
.trait_
.as_ref()
.ok_or_else(|| {
let msg = "Invalid pezpallet::hooks, expected impl<..> Hooks \
for Pezpallet<..>";
syn::Error::new(item.span(), msg)
})?
.1;
if item_trait.segments.len() != 1 || item_trait.segments[0].ident != "Hooks" {
let msg = format!(
"Invalid pezpallet::hooks, expected trait to be `Hooks` found `{}`\
, you can import from `pezframe_support::pezpallet_prelude`",
quote::quote!(#item_trait)
);
return Err(syn::Error::new(item_trait.span(), msg));
}
let has_runtime_upgrade = item.items.iter().any(|i| match i {
syn::ImplItem::Fn(method) => method.sig.ident == "on_runtime_upgrade",
_ => false,
});
Ok(Self {
attr_span,
instances,
has_runtime_upgrade,
where_clause: item.generics.where_clause.clone(),
})
}
}
@@ -0,0 +1,58 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use super::helper;
use syn::spanned::Spanned;
/// The definition of the pezpallet inherent implementation.
pub struct InherentDef {
/// A set of usage of instance, must be check for consistency with trait.
pub instances: Vec<helper::InstanceUsage>,
}
impl InherentDef {
pub fn try_from(item: &mut syn::Item) -> syn::Result<Self> {
let item = if let syn::Item::Impl(item) = item {
item
} else {
let msg = "Invalid pezpallet::inherent, expected item impl";
return Err(syn::Error::new(item.span(), msg));
};
if item.trait_.is_none() {
let msg = "Invalid pezpallet::inherent, expected impl<..> ProvideInherent for Pezpallet<..>";
return Err(syn::Error::new(item.span(), msg));
}
if let Some(last) = item.trait_.as_ref().unwrap().1.segments.last() {
if last.ident != "ProvideInherent" {
let msg = "Invalid pezpallet::inherent, expected trait ProvideInherent";
return Err(syn::Error::new(last.span(), msg));
}
} else {
let msg = "Invalid pezpallet::inherent, expected impl<..> ProvideInherent for Pezpallet<..>";
return Err(syn::Error::new(item.span(), msg));
}
let instances = vec![
helper::check_pallet_struct_usage(&item.self_ty)?,
helper::check_impl_gen(&item.generics, item.impl_token.span())?,
];
Ok(InherentDef { instances })
}
}
@@ -0,0 +1,833 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Parse for pezpallet macro.
//!
//! Parse the module into `Def` struct through `Def::try_from` function.
pub mod call;
pub mod composite;
pub mod config;
pub mod error;
pub mod event;
pub mod extra_constants;
pub mod genesis_build;
pub mod genesis_config;
pub mod helper;
pub mod hooks;
pub mod inherent;
pub mod origin;
pub mod pezpallet_struct;
pub mod storage;
pub mod tasks;
pub mod type_value;
pub mod validate_unsigned;
pub mod view_functions;
#[cfg(test)]
pub mod tests;
use composite::{keyword::CompositeKeyword, CompositeDef};
use pezframe_support_procedural_tools::generate_access_from_frame_or_crate;
use quote::ToTokens;
use syn::spanned::Spanned;
/// Parsed definition of a pezpallet.
pub struct Def {
/// The module items.
/// (their order must not be modified because they are registered in individual definitions).
pub item: syn::ItemMod,
pub config: config::ConfigDef,
pub pezpallet_struct: pezpallet_struct::PalletStructDef,
pub hooks: Option<hooks::HooksDef>,
pub call: Option<call::CallDef>,
pub tasks: Option<tasks::TasksDef>,
pub task_enum: Option<tasks::TaskEnumDef>,
pub storages: Vec<storage::StorageDef>,
pub error: Option<error::ErrorDef>,
pub event: Option<event::EventDef>,
pub origin: Option<origin::OriginDef>,
pub inherent: Option<inherent::InherentDef>,
pub genesis_config: Option<genesis_config::GenesisConfigDef>,
pub genesis_build: Option<genesis_build::GenesisBuildDef>,
pub validate_unsigned: Option<validate_unsigned::ValidateUnsignedDef>,
pub extra_constants: Option<extra_constants::ExtraConstantsDef>,
pub composites: Vec<composite::CompositeDef>,
pub type_values: Vec<type_value::TypeValueDef>,
pub pezframe_system: syn::Path,
pub pezframe_support: syn::Path,
pub dev_mode: bool,
pub view_functions: Option<view_functions::ViewFunctionsImplDef>,
pub is_pezframe_system: bool,
}
impl Def {
pub fn try_from(mut item: syn::ItemMod, dev_mode: bool) -> syn::Result<Self> {
let pezframe_system = generate_access_from_frame_or_crate("pezframe-system")?;
let pezframe_support = generate_access_from_frame_or_crate("pezframe-support")?;
let item_span = item.span();
let items = &mut item
.content
.as_mut()
.ok_or_else(|| {
let msg = "Invalid pezpallet definition, expected mod to be inlined.";
syn::Error::new(item_span, msg)
})?
.1;
let mut config = None;
let mut pezpallet_struct = None;
let mut hooks = None;
let mut call = None;
let mut tasks = None;
let mut task_enum = None;
let mut error = None;
let mut event = None;
let mut origin = None;
let mut inherent = None;
let mut genesis_config = None;
let mut genesis_build = None;
let mut validate_unsigned = None;
let mut extra_constants = None;
let mut storages = vec![];
let mut type_values = vec![];
let mut composites: Vec<CompositeDef> = vec![];
let mut view_functions = None;
let mut is_pezframe_system = false;
for (index, item) in items.iter_mut().enumerate() {
let pezpallet_attr: Option<PalletAttr> = helper::take_first_item_pallet_attr(item)?;
match pezpallet_attr {
Some(PalletAttr::Config{ with_default, pezframe_system_config: is_pezframe_system_val, without_automatic_metadata, ..}) if config.is_none() => {
is_pezframe_system = is_pezframe_system_val;
config = Some(config::ConfigDef::try_from(
&pezframe_system,
index,
item,
with_default,
without_automatic_metadata,
is_pezframe_system,
)?);
},
Some(PalletAttr::Pezpallet(span)) if pezpallet_struct.is_none() => {
let p = pezpallet_struct::PalletStructDef::try_from(span, index, item)?;
pezpallet_struct = Some(p);
},
Some(PalletAttr::Hooks(span)) if hooks.is_none() => {
let m = hooks::HooksDef::try_from(span, item)?;
hooks = Some(m);
},
Some(PalletAttr::RuntimeCall(cw, span)) if call.is_none() =>
call = Some(call::CallDef::try_from(span, index, item, dev_mode, cw)?),
Some(PalletAttr::Tasks(span)) if tasks.is_none() => {
let item_tokens = item.to_token_stream();
// `TasksDef::parse` needs to know if attr was provided so we artificially
// re-insert it here
tasks = Some(syn::parse2::<tasks::TasksDef>(quote::quote_spanned! { span =>
#[pezpallet::tasks_experimental]
#item_tokens
})?);
// replace item with a no-op because it will be handled by the expansion of tasks
*item = syn::Item::Verbatim(quote::quote!());
}
Some(PalletAttr::TaskCondition(span)) => return Err(syn::Error::new(
span,
"`#[pezpallet::task_condition]` can only be used on items within an `impl` statement."
)),
Some(PalletAttr::TaskIndex(span)) => return Err(syn::Error::new(
span,
"`#[pezpallet::task_index]` can only be used on items within an `impl` statement."
)),
Some(PalletAttr::TaskList(span)) => return Err(syn::Error::new(
span,
"`#[pezpallet::task_list]` can only be used on items within an `impl` statement."
)),
Some(PalletAttr::RuntimeTask(_)) if task_enum.is_none() =>
task_enum = Some(syn::parse2::<tasks::TaskEnumDef>(item.to_token_stream())?),
Some(PalletAttr::Error(span)) if error.is_none() =>
error = Some(error::ErrorDef::try_from(span, index, item)?),
Some(PalletAttr::RuntimeEvent(span)) if event.is_none() =>
event = Some(event::EventDef::try_from(span, index, item)?),
Some(PalletAttr::GenesisConfig(_)) if genesis_config.is_none() => {
let g = genesis_config::GenesisConfigDef::try_from(index, item)?;
genesis_config = Some(g);
},
Some(PalletAttr::GenesisBuild(span)) if genesis_build.is_none() => {
let g = genesis_build::GenesisBuildDef::try_from(span, item)?;
genesis_build = Some(g);
},
Some(PalletAttr::RuntimeOrigin(_)) if origin.is_none() =>
origin = Some(origin::OriginDef::try_from(item)?),
Some(PalletAttr::Inherent(_)) if inherent.is_none() =>
inherent = Some(inherent::InherentDef::try_from(item)?),
Some(PalletAttr::Storage(span)) =>
storages.push(storage::StorageDef::try_from(span, index, item, dev_mode)?),
Some(PalletAttr::ValidateUnsigned(_)) if validate_unsigned.is_none() => {
let v = validate_unsigned::ValidateUnsignedDef::try_from(item)?;
validate_unsigned = Some(v);
},
Some(PalletAttr::TypeValue(span)) =>
type_values.push(type_value::TypeValueDef::try_from(span, index, item)?),
Some(PalletAttr::ExtraConstants(_)) =>
extra_constants =
Some(extra_constants::ExtraConstantsDef::try_from(item)?),
Some(PalletAttr::Composite(span)) => {
let composite =
composite::CompositeDef::try_from(span, &pezframe_support, item)?;
if composites.iter().any(|def| {
match (&def.composite_keyword, &composite.composite_keyword) {
(
CompositeKeyword::FreezeReason(_),
CompositeKeyword::FreezeReason(_),
) |
(CompositeKeyword::HoldReason(_), CompositeKeyword::HoldReason(_)) |
(CompositeKeyword::LockId(_), CompositeKeyword::LockId(_)) |
(
CompositeKeyword::SlashReason(_),
CompositeKeyword::SlashReason(_),
) => true,
_ => false,
}
}) {
let msg = format!(
"Invalid duplicated `{}` definition",
composite.composite_keyword
);
return Err(syn::Error::new(composite.composite_keyword.span(), &msg))
}
composites.push(composite);
},
Some(PalletAttr::ViewFunctions(span)) => {
view_functions = Some(view_functions::ViewFunctionsImplDef::try_from(span, item)?);
}
Some(attr) => {
let msg = "Invalid duplicated attribute";
return Err(syn::Error::new(attr.span(), msg))
},
None => (),
}
}
if genesis_config.is_some() != genesis_build.is_some() {
let msg = format!(
"`#[pezpallet::genesis_config]` and `#[pezpallet::genesis_build]` attributes must be \
either both used or both not used, instead genesis_config is {} and genesis_build \
is {}",
genesis_config.as_ref().map_or("unused", |_| "used"),
genesis_build.as_ref().map_or("unused", |_| "used"),
);
return Err(syn::Error::new(item_span, msg));
}
Self::resolve_tasks(&item_span, &mut tasks, &mut task_enum, items)?;
let def = Def {
item,
config: config
.ok_or_else(|| syn::Error::new(item_span, "Missing `#[pezpallet::config]`"))?,
pezpallet_struct: pezpallet_struct
.ok_or_else(|| syn::Error::new(item_span, "Missing `#[pezpallet::pezpallet]`"))?,
hooks,
call,
tasks,
task_enum,
extra_constants,
genesis_config,
genesis_build,
validate_unsigned,
error,
event,
origin,
inherent,
storages,
composites,
type_values,
pezframe_system,
pezframe_support,
dev_mode,
view_functions,
is_pezframe_system,
};
def.check_instance_usage()?;
Ok(def)
}
/// Performs extra logic checks necessary for the `#[pezpallet::tasks_experimental]` feature.
fn resolve_tasks(
item_span: &proc_macro2::Span,
tasks: &mut Option<tasks::TasksDef>,
task_enum: &mut Option<tasks::TaskEnumDef>,
items: &mut Vec<syn::Item>,
) -> syn::Result<()> {
// fallback for manual (without macros) definition of tasks impl
Self::resolve_manual_tasks_impl(tasks, task_enum, items)?;
// fallback for manual (without macros) definition of task enum
Self::resolve_manual_task_enum(tasks, task_enum, items)?;
// ensure that if `task_enum` is specified, `tasks` is also specified
match (&task_enum, &tasks) {
(Some(_), None) =>
return Err(syn::Error::new(
*item_span,
"Missing `#[pezpallet::tasks_experimental]` impl",
)),
(None, Some(tasks)) =>
if tasks.tasks_attr.is_none() {
return Err(syn::Error::new(
tasks.item_impl.impl_token.span(),
"A `#[pezpallet::tasks_experimental]` attribute must be attached to your `Task` impl if the \
task enum has been omitted",
));
} else {
},
_ => (),
}
Ok(())
}
/// Tries to locate task enum based on the tasks impl target if attribute is not specified
/// but impl is present. If one is found, `task_enum` is set appropriately.
fn resolve_manual_task_enum(
tasks: &Option<tasks::TasksDef>,
task_enum: &mut Option<tasks::TaskEnumDef>,
items: &mut Vec<syn::Item>,
) -> syn::Result<()> {
let (None, Some(tasks)) = (&task_enum, &tasks) else { return Ok(()) };
let syn::Type::Path(type_path) = &*tasks.item_impl.self_ty else { return Ok(()) };
let type_path = type_path.path.segments.iter().collect::<Vec<_>>();
let (Some(seg), None) = (type_path.get(0), type_path.get(1)) else { return Ok(()) };
let mut result = None;
for item in items {
let syn::Item::Enum(item_enum) = item else { continue };
if item_enum.ident == seg.ident {
result = Some(syn::parse2::<tasks::TaskEnumDef>(item_enum.to_token_stream())?);
// replace item with a no-op because it will be handled by the expansion of
// `task_enum`. We use a no-op instead of simply removing it from the vec
// so that any indices collected by `Def::try_from` remain accurate
*item = syn::Item::Verbatim(quote::quote!());
break;
}
}
*task_enum = result;
Ok(())
}
/// Tries to locate a manual tasks impl (an impl implementing a trait whose last path segment is
/// `Task`) in the event that one has not been found already via the attribute macro
pub fn resolve_manual_tasks_impl(
tasks: &mut Option<tasks::TasksDef>,
task_enum: &Option<tasks::TaskEnumDef>,
items: &Vec<syn::Item>,
) -> syn::Result<()> {
let None = tasks else { return Ok(()) };
let mut result = None;
for item in items {
let syn::Item::Impl(item_impl) = item else { continue };
let Some((_, path, _)) = &item_impl.trait_ else { continue };
let Some(trait_last_seg) = path.segments.last() else { continue };
let syn::Type::Path(target_path) = &*item_impl.self_ty else { continue };
let target_path = target_path.path.segments.iter().collect::<Vec<_>>();
let (Some(target_ident), None) = (target_path.get(0), target_path.get(1)) else {
continue;
};
let matches_task_enum = match task_enum {
Some(task_enum) => task_enum.item_enum.ident == target_ident.ident,
None => true,
};
if trait_last_seg.ident == "Task" && matches_task_enum {
result = Some(syn::parse2::<tasks::TasksDef>(item_impl.to_token_stream())?);
break;
}
}
*tasks = result;
Ok(())
}
/// Check that usage of trait `Config` is consistent with the definition, i.e. it is used with
/// instance iff it is defined with instance.
fn check_instance_usage(&self) -> syn::Result<()> {
let mut instances = vec![];
instances.extend_from_slice(&self.pezpallet_struct.instances[..]);
instances.extend(&mut self.storages.iter().flat_map(|s| s.instances.clone()));
if let Some(call) = &self.call {
instances.extend_from_slice(&call.instances[..]);
}
if let Some(hooks) = &self.hooks {
instances.extend_from_slice(&hooks.instances[..]);
}
if let Some(event) = &self.event {
instances.extend_from_slice(&event.instances[..]);
}
if let Some(error) = &self.error {
instances.extend_from_slice(&error.instances[..]);
}
if let Some(inherent) = &self.inherent {
instances.extend_from_slice(&inherent.instances[..]);
}
if let Some(origin) = &self.origin {
instances.extend_from_slice(&origin.instances[..]);
}
if let Some(genesis_config) = &self.genesis_config {
instances.extend_from_slice(&genesis_config.instances[..]);
}
if let Some(genesis_build) = &self.genesis_build {
genesis_build.instances.as_ref().map(|i| instances.extend_from_slice(&i));
}
if let Some(extra_constants) = &self.extra_constants {
instances.extend_from_slice(&extra_constants.instances[..]);
}
if let Some(task_enum) = &self.task_enum {
instances.push(task_enum.instance_usage.clone());
}
let mut errors = instances.into_iter().filter_map(|instances| {
if instances.has_instance == self.config.has_instance {
return None;
}
let msg = if self.config.has_instance {
"Invalid generic declaration, trait is defined with instance but generic use none"
} else {
"Invalid generic declaration, trait is defined without instance but generic use \
some"
};
Some(syn::Error::new(instances.span, msg))
});
if let Some(mut first_error) = errors.next() {
for error in errors {
first_error.combine(error)
}
Err(first_error)
} else {
Ok(())
}
}
/// Depending on if pezpallet is instantiable:
/// * either `T: Config`
/// * or `T: Config<I>, I: 'static`
pub fn type_impl_generics(&self, span: proc_macro2::Span) -> proc_macro2::TokenStream {
if self.config.has_instance {
quote::quote_spanned!(span => T: Config<I>, I: 'static)
} else {
quote::quote_spanned!(span => T: Config)
}
}
/// Depending on if pezpallet is instantiable:
/// * either `T: Config`
/// * or `T: Config<I>, I: 'static = ()`
pub fn type_decl_bounded_generics(&self, span: proc_macro2::Span) -> proc_macro2::TokenStream {
if self.config.has_instance {
quote::quote_spanned!(span => T: Config<I>, I: 'static = ())
} else {
quote::quote_spanned!(span => T: Config)
}
}
/// Depending on if pezpallet is instantiable:
/// * either `T`
/// * or `T, I = ()`
pub fn type_decl_generics(&self, span: proc_macro2::Span) -> proc_macro2::TokenStream {
if self.config.has_instance {
quote::quote_spanned!(span => T, I = ())
} else {
quote::quote_spanned!(span => T)
}
}
/// Depending on if pezpallet is instantiable:
/// * either ``
/// * or `<I>`
/// to be used when using pezpallet trait `Config`
pub fn trait_use_generics(&self, span: proc_macro2::Span) -> proc_macro2::TokenStream {
if self.config.has_instance {
quote::quote_spanned!(span => <I>)
} else {
quote::quote_spanned!(span => )
}
}
/// Depending on if pezpallet is instantiable:
/// * either `T`
/// * or `T, I`
pub fn type_use_generics(&self, span: proc_macro2::Span) -> proc_macro2::TokenStream {
if self.config.has_instance {
quote::quote_spanned!(span => T, I)
} else {
quote::quote_spanned!(span => T)
}
}
}
/// Some generic kind for type which can be not generic, or generic over config,
/// or generic over config and instance, but not generic only over instance.
pub enum GenericKind {
None,
Config,
ConfigAndInstance,
}
impl GenericKind {
/// Return Err if it is only generics over instance but not over config.
pub fn from_gens(has_config: bool, has_instance: bool) -> Result<Self, ()> {
match (has_config, has_instance) {
(false, false) => Ok(GenericKind::None),
(true, false) => Ok(GenericKind::Config),
(true, true) => Ok(GenericKind::ConfigAndInstance),
(false, true) => Err(()),
}
}
/// Return the generic to be used when using the type.
///
/// Depending on its definition it can be: ``, `T` or `T, I`
pub fn type_use_gen(&self, span: proc_macro2::Span) -> proc_macro2::TokenStream {
match self {
GenericKind::None => quote::quote!(),
GenericKind::Config => quote::quote_spanned!(span => T),
GenericKind::ConfigAndInstance => quote::quote_spanned!(span => T, I),
}
}
/// Return the generic to be used in `impl<..>` when implementing on the type.
pub fn type_impl_gen(&self, span: proc_macro2::Span) -> proc_macro2::TokenStream {
match self {
GenericKind::None => quote::quote!(),
GenericKind::Config => quote::quote_spanned!(span => T: Config),
GenericKind::ConfigAndInstance => {
quote::quote_spanned!(span => T: Config<I>, I: 'static)
},
}
}
/// Return whereas the type has some generic.
pub fn is_generic(&self) -> bool {
match self {
GenericKind::None => false,
GenericKind::Config | GenericKind::ConfigAndInstance => true,
}
}
}
/// List of additional token to be used for parsing.
mod keyword {
syn::custom_keyword!(origin);
syn::custom_keyword!(call);
syn::custom_keyword!(tasks_experimental);
syn::custom_keyword!(task_enum);
syn::custom_keyword!(task_list);
syn::custom_keyword!(task_condition);
syn::custom_keyword!(task_index);
syn::custom_keyword!(weight);
syn::custom_keyword!(event);
syn::custom_keyword!(config);
syn::custom_keyword!(with_default);
syn::custom_keyword!(without_automatic_metadata);
syn::custom_keyword!(pezframe_system_config);
syn::custom_keyword!(hooks);
syn::custom_keyword!(inherent);
syn::custom_keyword!(error);
syn::custom_keyword!(storage);
syn::custom_keyword!(genesis_build);
syn::custom_keyword!(genesis_config);
syn::custom_keyword!(validate_unsigned);
syn::custom_keyword!(type_value);
syn::custom_keyword!(pezpallet);
syn::custom_keyword!(extra_constants);
syn::custom_keyword!(composite_enum);
syn::custom_keyword!(view_functions);
}
/// The possible values for the `#[pezpallet::config]` attribute.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
enum ConfigValue {
/// `#[pezpallet::config(with_default)]`
WithDefault(keyword::with_default),
/// `#[pezpallet::config(without_automatic_metadata)]`
WithoutAutomaticMetadata(keyword::without_automatic_metadata),
/// `#[pezpallet::config(pezframe_system_config)]`
FrameSystemConfig(keyword::pezframe_system_config),
}
impl syn::parse::Parse for ConfigValue {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(keyword::with_default) {
input.parse().map(ConfigValue::WithDefault)
} else if lookahead.peek(keyword::without_automatic_metadata) {
input.parse().map(ConfigValue::WithoutAutomaticMetadata)
} else if lookahead.peek(keyword::pezframe_system_config) {
input.parse().map(ConfigValue::FrameSystemConfig)
} else {
Err(lookahead.error())
}
}
}
/// Parse attributes for item in pezpallet module
/// syntax must be `pezpallet::` (e.g. `#[pezpallet::config]`)
enum PalletAttr {
Config {
span: proc_macro2::Span,
with_default: bool,
without_automatic_metadata: bool,
pezframe_system_config: bool,
},
Pezpallet(proc_macro2::Span),
Hooks(proc_macro2::Span),
/// A `#[pezpallet::call]` with optional attributes to specialize the behaviour.
///
/// # Attributes
///
/// Each attribute `attr` can take the form of `#[pezpallet::call(attr = …)]` or
/// `#[pezpallet::call(attr(…))]`. The possible attributes are:
///
/// ## `weight`
///
/// Can be used to reduce the repetitive weight annotation in the trivial case. It accepts one
/// argument that is expected to be an implementation of the `WeightInfo` or something that
/// behaves syntactically equivalent. This allows to annotate a `WeightInfo` for all the calls.
/// Now each call does not need to specify its own `#[pezpallet::weight]` but can instead use the
/// one from the `#[pezpallet::call]` definition. So instead of having to write it on each call:
///
/// ```ignore
/// #[pezpallet::call]
/// impl<T: Config> Pezpallet<T> {
/// #[pezpallet::weight(T::WeightInfo::create())]
/// pub fn create(
/// ```
/// you can now omit it on the call itself, if the name of the weigh function matches the call:
///
/// ```ignore
/// #[pezpallet::call(weight = <T as crate::Config>::WeightInfo)]
/// impl<T: Config> Pezpallet<T> {
/// pub fn create(
/// ```
///
/// It is possible to use this syntax together with instantiated pallets by using `Config<I>`
/// instead.
///
/// ### Dev Mode
///
/// Normally the `dev_mode` sets all weights of calls without a `#[pezpallet::weight]` annotation
/// to zero. Now when there is a `weight` attribute on the `#[pezpallet::call]`, then that is used
/// instead of the zero weight. So to say: it works together with `dev_mode`.
RuntimeCall(Option<InheritedCallWeightAttr>, proc_macro2::Span),
Error(proc_macro2::Span),
Tasks(proc_macro2::Span),
TaskList(proc_macro2::Span),
TaskCondition(proc_macro2::Span),
TaskIndex(proc_macro2::Span),
RuntimeTask(proc_macro2::Span),
RuntimeEvent(proc_macro2::Span),
RuntimeOrigin(proc_macro2::Span),
Inherent(proc_macro2::Span),
Storage(proc_macro2::Span),
GenesisConfig(proc_macro2::Span),
GenesisBuild(proc_macro2::Span),
ValidateUnsigned(proc_macro2::Span),
TypeValue(proc_macro2::Span),
ExtraConstants(proc_macro2::Span),
Composite(proc_macro2::Span),
ViewFunctions(proc_macro2::Span),
}
impl PalletAttr {
fn span(&self) -> proc_macro2::Span {
match self {
Self::Config { span, .. } => *span,
Self::Pezpallet(span) => *span,
Self::Hooks(span) => *span,
Self::Tasks(span) => *span,
Self::TaskCondition(span) => *span,
Self::TaskIndex(span) => *span,
Self::TaskList(span) => *span,
Self::Error(span) => *span,
Self::RuntimeTask(span) => *span,
Self::RuntimeCall(_, span) => *span,
Self::RuntimeEvent(span) => *span,
Self::RuntimeOrigin(span) => *span,
Self::Inherent(span) => *span,
Self::Storage(span) => *span,
Self::GenesisConfig(span) => *span,
Self::GenesisBuild(span) => *span,
Self::ValidateUnsigned(span) => *span,
Self::TypeValue(span) => *span,
Self::ExtraConstants(span) => *span,
Self::Composite(span) => *span,
Self::ViewFunctions(span) => *span,
}
}
}
impl syn::parse::Parse for PalletAttr {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
input.parse::<syn::Token![#]>()?;
let content;
syn::bracketed!(content in input);
content.parse::<keyword::pezpallet>()?;
content.parse::<syn::Token![::]>()?;
let lookahead = content.lookahead1();
if lookahead.peek(keyword::config) {
let span = content.parse::<keyword::config>()?.span();
if content.peek(syn::token::Paren) {
let inside_config;
// Parse (with_default, without_automatic_metadata) attributes.
let _paren = syn::parenthesized!(inside_config in content);
let fields: syn::punctuated::Punctuated<ConfigValue, syn::Token![,]> =
inside_config.parse_terminated(ConfigValue::parse, syn::Token![,])?;
let config_values = fields.iter().collect::<Vec<_>>();
let mut with_default = false;
let mut without_automatic_metadata = false;
let mut pezframe_system_config = false;
for config in config_values {
match config {
ConfigValue::WithDefault(_) => {
if with_default {
return Err(syn::Error::new(
span,
"Invalid duplicated attribute for `#[pezpallet::config]`. Please remove duplicates: with_default.",
));
}
with_default = true;
},
ConfigValue::WithoutAutomaticMetadata(_) => {
if without_automatic_metadata {
return Err(syn::Error::new(
span,
"Invalid duplicated attribute for `#[pezpallet::config]`. Please remove duplicates: without_automatic_metadata.",
));
}
without_automatic_metadata = true;
},
ConfigValue::FrameSystemConfig(_) => {
if pezframe_system_config {
return Err(syn::Error::new(
span,
"Invalid duplicated attribute for `#[pezpallet::config]`. Please remove duplicates: pezframe_system_config.",
));
}
pezframe_system_config = true;
},
}
}
Ok(PalletAttr::Config {
span,
with_default,
without_automatic_metadata,
pezframe_system_config,
})
} else {
Ok(PalletAttr::Config {
span,
with_default: false,
without_automatic_metadata: false,
pezframe_system_config: false,
})
}
} else if lookahead.peek(keyword::pezpallet) {
Ok(PalletAttr::Pezpallet(content.parse::<keyword::pezpallet>()?.span()))
} else if lookahead.peek(keyword::hooks) {
Ok(PalletAttr::Hooks(content.parse::<keyword::hooks>()?.span()))
} else if lookahead.peek(keyword::call) {
let span = content.parse::<keyword::call>().expect("peeked").span();
let attr = match content.is_empty() {
true => None,
false => Some(InheritedCallWeightAttr::parse(&content)?),
};
Ok(PalletAttr::RuntimeCall(attr, span))
} else if lookahead.peek(keyword::tasks_experimental) {
Ok(PalletAttr::Tasks(content.parse::<keyword::tasks_experimental>()?.span()))
} else if lookahead.peek(keyword::task_enum) {
Ok(PalletAttr::RuntimeTask(content.parse::<keyword::task_enum>()?.span()))
} else if lookahead.peek(keyword::task_condition) {
Ok(PalletAttr::TaskCondition(content.parse::<keyword::task_condition>()?.span()))
} else if lookahead.peek(keyword::task_index) {
Ok(PalletAttr::TaskIndex(content.parse::<keyword::task_index>()?.span()))
} else if lookahead.peek(keyword::task_list) {
Ok(PalletAttr::TaskList(content.parse::<keyword::task_list>()?.span()))
} else if lookahead.peek(keyword::error) {
Ok(PalletAttr::Error(content.parse::<keyword::error>()?.span()))
} else if lookahead.peek(keyword::event) {
Ok(PalletAttr::RuntimeEvent(content.parse::<keyword::event>()?.span()))
} else if lookahead.peek(keyword::origin) {
Ok(PalletAttr::RuntimeOrigin(content.parse::<keyword::origin>()?.span()))
} else if lookahead.peek(keyword::inherent) {
Ok(PalletAttr::Inherent(content.parse::<keyword::inherent>()?.span()))
} else if lookahead.peek(keyword::storage) {
Ok(PalletAttr::Storage(content.parse::<keyword::storage>()?.span()))
} else if lookahead.peek(keyword::genesis_config) {
Ok(PalletAttr::GenesisConfig(content.parse::<keyword::genesis_config>()?.span()))
} else if lookahead.peek(keyword::genesis_build) {
Ok(PalletAttr::GenesisBuild(content.parse::<keyword::genesis_build>()?.span()))
} else if lookahead.peek(keyword::validate_unsigned) {
Ok(PalletAttr::ValidateUnsigned(content.parse::<keyword::validate_unsigned>()?.span()))
} else if lookahead.peek(keyword::type_value) {
Ok(PalletAttr::TypeValue(content.parse::<keyword::type_value>()?.span()))
} else if lookahead.peek(keyword::extra_constants) {
Ok(PalletAttr::ExtraConstants(content.parse::<keyword::extra_constants>()?.span()))
} else if lookahead.peek(keyword::composite_enum) {
Ok(PalletAttr::Composite(content.parse::<keyword::composite_enum>()?.span()))
} else if lookahead.peek(keyword::view_functions) {
Ok(PalletAttr::ViewFunctions(content.parse::<keyword::view_functions>()?.span()))
} else {
Err(lookahead.error())
}
}
}
/// The optional weight annotation on a `#[pezpallet::call]` like `#[pezpallet::call(weight($type))]`.
#[derive(Clone)]
pub struct InheritedCallWeightAttr {
pub typename: syn::Type,
}
impl syn::parse::Parse for InheritedCallWeightAttr {
// Parses `(weight($type))` or `(weight = $type)`.
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let content;
syn::parenthesized!(content in input);
content.parse::<keyword::weight>()?;
let lookahead = content.lookahead1();
let buffer = if lookahead.peek(syn::token::Paren) {
let inner;
syn::parenthesized!(inner in content);
inner
} else if lookahead.peek(syn::Token![=]) {
content.parse::<syn::Token![=]>().expect("peeked");
content
} else {
return Err(lookahead.error());
};
Ok(Self { typename: buffer.parse()? })
}
}
@@ -0,0 +1,68 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use super::helper;
use syn::spanned::Spanned;
/// Definition of the pezpallet origin type.
///
/// Either:
/// * `type Origin`
/// * `struct Origin`
/// * `enum Origin`
pub struct OriginDef {
pub is_generic: bool,
/// A set of usage of instance, must be check for consistency with trait.
pub instances: Vec<helper::InstanceUsage>,
}
impl OriginDef {
pub fn try_from(item: &mut syn::Item) -> syn::Result<Self> {
let item_span = item.span();
let (vis, ident, generics) = match &item {
syn::Item::Enum(item) => (&item.vis, &item.ident, &item.generics),
syn::Item::Struct(item) => (&item.vis, &item.ident, &item.generics),
syn::Item::Type(item) => (&item.vis, &item.ident, &item.generics),
_ => {
let msg = "Invalid pezpallet::origin, expected enum or struct or type";
return Err(syn::Error::new(item.span(), msg));
},
};
let is_generic = !generics.params.is_empty();
let mut instances = vec![];
if let Some(u) = helper::check_type_def_optional_gen(generics, item.span())? {
instances.push(u);
} else {
// construct_runtime only allow generic event for instantiable pezpallet.
instances.push(helper::InstanceUsage { has_instance: false, span: ident.span() })
}
if !matches!(vis, syn::Visibility::Public(_)) {
let msg = "Invalid pezpallet::origin, Origin must be public";
return Err(syn::Error::new(item_span, msg));
}
if ident != "Origin" {
let msg = "Invalid pezpallet::origin, ident must `Origin`";
return Err(syn::Error::new(ident.span(), msg));
}
Ok(OriginDef { is_generic, instances })
}
}
@@ -0,0 +1,149 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use super::helper;
use quote::ToTokens;
use syn::spanned::Spanned;
/// List of additional token to be used for parsing.
mod keyword {
syn::custom_keyword!(pezpallet);
syn::custom_keyword!(Pezpallet);
syn::custom_keyword!(without_storage_info);
syn::custom_keyword!(storage_version);
}
/// Definition of the pezpallet pezpallet.
pub struct PalletStructDef {
/// The index of item in pezpallet pezpallet.
pub index: usize,
/// A set of usage of instance, must be check for consistency with config trait.
pub instances: Vec<helper::InstanceUsage>,
/// The keyword Pezpallet used (contains span).
pub pezpallet: keyword::Pezpallet,
/// The span of the pezpallet::pezpallet attribute.
pub attr_span: proc_macro2::Span,
/// Whether to specify the storages max encoded len when implementing `StorageInfoTrait`.
/// Contains the span of the attribute.
pub without_storage_info: Option<proc_macro2::Span>,
/// The in-code storage version of the pezpallet.
pub storage_version: Option<syn::Path>,
}
/// Parse for one variant of:
/// * `#[pezpallet::without_storage_info]`
/// * `#[pezpallet::storage_version(STORAGE_VERSION)]`
pub enum PalletStructAttr {
WithoutStorageInfoTrait(proc_macro2::Span),
StorageVersion { storage_version: syn::Path, span: proc_macro2::Span },
}
impl PalletStructAttr {
fn span(&self) -> proc_macro2::Span {
match self {
Self::WithoutStorageInfoTrait(span) | Self::StorageVersion { span, .. } => *span,
}
}
}
impl syn::parse::Parse for PalletStructAttr {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
input.parse::<syn::Token![#]>()?;
let content;
syn::bracketed!(content in input);
content.parse::<keyword::pezpallet>()?;
content.parse::<syn::Token![::]>()?;
let lookahead = content.lookahead1();
if lookahead.peek(keyword::without_storage_info) {
let span = content.parse::<keyword::without_storage_info>()?.span();
Ok(Self::WithoutStorageInfoTrait(span))
} else if lookahead.peek(keyword::storage_version) {
let span = content.parse::<keyword::storage_version>()?.span();
let version_content;
syn::parenthesized!(version_content in content);
let storage_version = version_content.parse::<syn::Path>()?;
Ok(Self::StorageVersion { storage_version, span })
} else {
Err(lookahead.error())
}
}
}
impl PalletStructDef {
pub fn try_from(
attr_span: proc_macro2::Span,
index: usize,
item: &mut syn::Item,
) -> syn::Result<Self> {
let item = if let syn::Item::Struct(item) = item {
item
} else {
let msg = "Invalid pezpallet::pezpallet, expected struct definition";
return Err(syn::Error::new(item.span(), msg));
};
let mut without_storage_info = None;
let mut storage_version_found = None;
let struct_attrs: Vec<PalletStructAttr> = helper::take_item_pallet_attrs(&mut item.attrs)?;
for attr in struct_attrs {
match attr {
PalletStructAttr::WithoutStorageInfoTrait(span)
if without_storage_info.is_none() =>
{
without_storage_info = Some(span);
},
PalletStructAttr::StorageVersion { storage_version, .. }
if storage_version_found.is_none() =>
{
storage_version_found = Some(storage_version);
},
attr => {
let msg = "Unexpected duplicated attribute";
return Err(syn::Error::new(attr.span(), msg));
},
}
}
let pezpallet = syn::parse2::<keyword::Pezpallet>(item.ident.to_token_stream())?;
if !matches!(item.vis, syn::Visibility::Public(_)) {
let msg = "Invalid pezpallet::pezpallet, Pezpallet must be public";
return Err(syn::Error::new(item.span(), msg));
}
if item.generics.where_clause.is_some() {
let msg = "Invalid pezpallet::pezpallet, where clause not supported on Pezpallet declaration";
return Err(syn::Error::new(item.generics.where_clause.span(), msg));
}
let instances =
vec![helper::check_type_def_gen_no_bounds(&item.generics, item.ident.span())?];
Ok(Self {
index,
instances,
pezpallet,
attr_span,
without_storage_info,
storage_version: storage_version_found,
})
}
}
@@ -0,0 +1,950 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use super::helper;
use pezframe_support_procedural_tools::get_doc_literals;
use quote::ToTokens;
use std::collections::HashMap;
use syn::spanned::Spanned;
/// List of additional token to be used for parsing.
mod keyword {
syn::custom_keyword!(Error);
syn::custom_keyword!(pezpallet);
syn::custom_keyword!(getter);
syn::custom_keyword!(storage_prefix);
syn::custom_keyword!(unbounded);
syn::custom_keyword!(whitelist_storage);
syn::custom_keyword!(disable_try_decode_storage);
syn::custom_keyword!(OptionQuery);
syn::custom_keyword!(ResultQuery);
syn::custom_keyword!(ValueQuery);
}
/// Parse for one of the following:
/// * `#[pezpallet::getter(fn dummy)]`
/// * `#[pezpallet::storage_prefix = "CustomName"]`
/// * `#[pezpallet::unbounded]`
/// * `#[pezpallet::whitelist_storage]
/// * `#[pezpallet::disable_try_decode_storage]`
pub enum PalletStorageAttr {
Getter(syn::Ident, proc_macro2::Span),
StorageName(syn::LitStr, proc_macro2::Span),
Unbounded(proc_macro2::Span),
WhitelistStorage(proc_macro2::Span),
DisableTryDecodeStorage(proc_macro2::Span),
}
impl PalletStorageAttr {
fn attr_span(&self) -> proc_macro2::Span {
match self {
Self::Getter(_, span) |
Self::StorageName(_, span) |
Self::Unbounded(span) |
Self::WhitelistStorage(span) => *span,
Self::DisableTryDecodeStorage(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::pezpallet>()?;
content.parse::<syn::Token![::]>()?;
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))
} else if lookahead.peek(keyword::unbounded) {
content.parse::<keyword::unbounded>()?;
Ok(Self::Unbounded(attr_span))
} else if lookahead.peek(keyword::whitelist_storage) {
content.parse::<keyword::whitelist_storage>()?;
Ok(Self::WhitelistStorage(attr_span))
} else if lookahead.peek(keyword::disable_try_decode_storage) {
content.parse::<keyword::disable_try_decode_storage>()?;
Ok(Self::DisableTryDecodeStorage(attr_span))
} else {
Err(lookahead.error())
}
}
}
struct PalletStorageAttrInfo {
getter: Option<syn::Ident>,
rename_as: Option<syn::LitStr>,
unbounded: bool,
whitelisted: bool,
try_decode: bool,
}
impl PalletStorageAttrInfo {
fn from_attrs(attrs: Vec<PalletStorageAttr>) -> syn::Result<Self> {
let mut getter = None;
let mut rename_as = None;
let mut unbounded = false;
let mut whitelisted = false;
let mut disable_try_decode_storage = false;
for attr in attrs {
match attr {
PalletStorageAttr::Getter(ident, ..) if getter.is_none() => getter = Some(ident),
PalletStorageAttr::StorageName(name, ..) if rename_as.is_none() =>
rename_as = Some(name),
PalletStorageAttr::Unbounded(..) if !unbounded => unbounded = true,
PalletStorageAttr::WhitelistStorage(..) if !whitelisted => whitelisted = true,
PalletStorageAttr::DisableTryDecodeStorage(..) if !disable_try_decode_storage =>
disable_try_decode_storage = true,
attr =>
return Err(syn::Error::new(
attr.attr_span(),
"Invalid attribute: Duplicate attribute",
)),
}
}
Ok(PalletStorageAttrInfo {
getter,
rename_as,
unbounded,
whitelisted,
try_decode: !disable_try_decode_storage,
})
}
}
/// The value and key types used by storages. Needed to expand metadata.
pub enum Metadata {
Value { value: syn::Type },
Map { value: syn::Type, key: syn::Type },
CountedMap { value: syn::Type, key: syn::Type },
DoubleMap { value: syn::Type, key1: syn::Type, key2: syn::Type },
NMap { keys: Vec<syn::Type>, keygen: syn::Type, value: syn::Type },
CountedNMap { keys: Vec<syn::Type>, keygen: syn::Type, value: syn::Type },
}
pub enum QueryKind {
OptionQuery,
ResultQuery(syn::Path, syn::Ident),
ValueQuery,
}
/// Definition of a storage, storage is a storage type like
/// `type MyStorage = StorageValue<MyStorageP, u32>`
/// The keys and values types are parsed in order to get metadata
pub struct StorageDef {
/// The index of storage item in pezpallet module.
pub index: usize,
/// Visibility of the storage type.
pub vis: syn::Visibility,
/// The type ident, to generate the StoragePrefix for.
pub ident: syn::Ident,
/// The keys and value metadata of the storage.
pub metadata: Metadata,
/// The doc associated to the storage.
pub docs: Vec<syn::Expr>,
/// A set of usage of instance, must be check for consistency with config.
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, 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>,
/// Where clause of type definition.
pub where_clause: Option<syn::WhereClause>,
/// The span of the pezpallet::storage attribute.
pub attr_span: proc_macro2::Span,
/// The `cfg` attributes.
pub cfg_attrs: Vec<syn::Attribute>,
/// If generics are named (e.g. `StorageValue<Value = u32, ..>`) then this contains all the
/// generics of the storage.
/// If generics are not named, this is none.
pub named_generics: Option<StorageGenerics>,
/// If the value stored in this storage is unbounded.
pub unbounded: bool,
/// Whether or not reads to this storage key will be ignored by benchmarking
pub whitelisted: bool,
/// Whether or not to try to decode the storage key when running try-runtime checks.
pub try_decode: bool,
/// Whether or not a default hasher is allowed to replace `_`
pub use_default_hasher: bool,
/// Attributes
pub attrs: Vec<syn::Attribute>,
}
/// The parsed generic from the
#[derive(Clone)]
pub enum StorageGenerics {
DoubleMap {
hasher1: syn::Type,
key1: syn::Type,
hasher2: syn::Type,
key2: syn::Type,
value: syn::Type,
query_kind: Option<syn::Type>,
on_empty: Option<syn::Type>,
max_values: Option<syn::Type>,
},
Map {
hasher: syn::Type,
key: syn::Type,
value: syn::Type,
query_kind: Option<syn::Type>,
on_empty: Option<syn::Type>,
max_values: Option<syn::Type>,
},
CountedMap {
hasher: syn::Type,
key: syn::Type,
value: syn::Type,
query_kind: Option<syn::Type>,
on_empty: Option<syn::Type>,
max_values: Option<syn::Type>,
},
Value {
value: syn::Type,
query_kind: Option<syn::Type>,
on_empty: Option<syn::Type>,
},
NMap {
keygen: syn::Type,
value: syn::Type,
query_kind: Option<syn::Type>,
on_empty: Option<syn::Type>,
max_values: Option<syn::Type>,
},
CountedNMap {
keygen: syn::Type,
value: syn::Type,
query_kind: Option<syn::Type>,
on_empty: Option<syn::Type>,
max_values: Option<syn::Type>,
},
}
impl StorageGenerics {
/// Return the metadata from the defined generics
fn metadata(&self) -> syn::Result<Metadata> {
let res = match self.clone() {
Self::DoubleMap { value, key1, key2, .. } => Metadata::DoubleMap { value, key1, key2 },
Self::Map { value, key, .. } => Metadata::Map { value, key },
Self::CountedMap { value, key, .. } => Metadata::CountedMap { value, key },
Self::Value { value, .. } => Metadata::Value { value },
Self::NMap { keygen, value, .. } =>
Metadata::NMap { keys: collect_keys(&keygen)?, keygen, value },
Self::CountedNMap { keygen, value, .. } =>
Metadata::CountedNMap { keys: collect_keys(&keygen)?, keygen, value },
};
Ok(res)
}
/// Return the query kind from the defined generics
fn query_kind(&self) -> Option<syn::Type> {
match &self {
Self::DoubleMap { query_kind, .. } |
Self::Map { query_kind, .. } |
Self::CountedMap { query_kind, .. } |
Self::Value { query_kind, .. } |
Self::NMap { query_kind, .. } |
Self::CountedNMap { query_kind, .. } => query_kind.clone(),
}
}
}
enum StorageKind {
Value,
Map,
CountedMap,
DoubleMap,
NMap,
CountedNMap,
}
/// Check the generics in the `map` contains the generics in `gen` may contains generics in
/// `optional_gen`, and doesn't contains any other.
fn check_generics(
map: &HashMap<String, syn::AssocType>,
mandatory_generics: &[&str],
optional_generics: &[&str],
storage_type_name: &str,
args_span: proc_macro2::Span,
) -> syn::Result<()> {
let mut errors = vec![];
let expectation = {
let mut e = format!(
"`{}` expect generics {}and optional generics {}",
storage_type_name,
mandatory_generics
.iter()
.map(|name| format!("`{}`, ", name))
.collect::<String>(),
&optional_generics.iter().map(|name| format!("`{}`, ", name)).collect::<String>(),
);
e.pop();
e.pop();
e.push('.');
e
};
for (gen_name, gen_binding) in map {
if !mandatory_generics.contains(&gen_name.as_str()) &&
!optional_generics.contains(&gen_name.as_str())
{
let msg = format!(
"Invalid pezpallet::storage, Unexpected generic `{}` for `{}`. {}",
gen_name, storage_type_name, expectation,
);
errors.push(syn::Error::new(gen_binding.span(), msg));
}
}
for mandatory_generic in mandatory_generics {
if !map.contains_key(&mandatory_generic.to_string()) {
let msg = format!(
"Invalid pezpallet::storage, cannot find `{}` generic, required for `{}`.",
mandatory_generic, storage_type_name
);
errors.push(syn::Error::new(args_span, msg));
}
}
let mut errors = errors.drain(..);
if let Some(mut error) = errors.next() {
for other_error in errors {
error.combine(other_error);
}
Err(error)
} else {
Ok(())
}
}
/// Returns `(named generics, metadata, query kind, use_default_hasher)`
fn process_named_generics(
storage: &StorageKind,
args_span: proc_macro2::Span,
args: &[syn::AssocType],
dev_mode: bool,
) -> syn::Result<(Option<StorageGenerics>, Metadata, Option<syn::Type>, bool)> {
let mut parsed = HashMap::<String, syn::AssocType>::new();
// Ensure no duplicate.
for arg in args {
if let Some(other) = parsed.get(&arg.ident.to_string()) {
let msg = "Invalid pezpallet::storage, Duplicated named generic";
let mut err = syn::Error::new(arg.ident.span(), msg);
err.combine(syn::Error::new(other.ident.span(), msg));
return Err(err);
}
parsed.insert(arg.ident.to_string(), arg.clone());
}
let mut map_mandatory_generics = vec!["Key", "Value"];
let mut map_optional_generics = vec!["QueryKind", "OnEmpty", "MaxValues"];
if dev_mode {
map_optional_generics.push("Hasher");
} else {
map_mandatory_generics.push("Hasher");
}
let generics = match storage {
StorageKind::Value => {
check_generics(
&parsed,
&["Value"],
&["QueryKind", "OnEmpty"],
"StorageValue",
args_span,
)?;
StorageGenerics::Value {
value: parsed
.remove("Value")
.map(|binding| binding.ty)
.expect("checked above as mandatory generic"),
query_kind: parsed.remove("QueryKind").map(|binding| binding.ty),
on_empty: parsed.remove("OnEmpty").map(|binding| binding.ty),
}
},
StorageKind::Map => {
check_generics(
&parsed,
&map_mandatory_generics,
&map_optional_generics,
"StorageMap",
args_span,
)?;
StorageGenerics::Map {
hasher: parsed
.remove("Hasher")
.map(|binding| binding.ty)
.unwrap_or(syn::parse_quote!(Blake2_128Concat)),
key: parsed
.remove("Key")
.map(|binding| binding.ty)
.expect("checked above as mandatory generic"),
value: parsed
.remove("Value")
.map(|binding| binding.ty)
.expect("checked above as mandatory generic"),
query_kind: parsed.remove("QueryKind").map(|binding| binding.ty),
on_empty: parsed.remove("OnEmpty").map(|binding| binding.ty),
max_values: parsed.remove("MaxValues").map(|binding| binding.ty),
}
},
StorageKind::CountedMap => {
check_generics(
&parsed,
&map_mandatory_generics,
&map_optional_generics,
"CountedStorageMap",
args_span,
)?;
StorageGenerics::CountedMap {
hasher: parsed
.remove("Hasher")
.map(|binding| binding.ty)
.unwrap_or(syn::Type::Verbatim(quote::quote! { Blake2_128Concat })),
key: parsed
.remove("Key")
.map(|binding| binding.ty)
.expect("checked above as mandatory generic"),
value: parsed
.remove("Value")
.map(|binding| binding.ty)
.expect("checked above as mandatory generic"),
query_kind: parsed.remove("QueryKind").map(|binding| binding.ty),
on_empty: parsed.remove("OnEmpty").map(|binding| binding.ty),
max_values: parsed.remove("MaxValues").map(|binding| binding.ty),
}
},
StorageKind::DoubleMap => {
let mut double_map_mandatory_generics = vec!["Key1", "Key2", "Value"];
if dev_mode {
map_optional_generics.extend(["Hasher1", "Hasher2"]);
} else {
double_map_mandatory_generics.extend(["Hasher1", "Hasher2"]);
}
check_generics(
&parsed,
&double_map_mandatory_generics,
&map_optional_generics,
"StorageDoubleMap",
args_span,
)?;
StorageGenerics::DoubleMap {
hasher1: parsed
.remove("Hasher1")
.map(|binding| binding.ty)
.unwrap_or(syn::parse_quote!(Blake2_128Concat)),
key1: parsed
.remove("Key1")
.map(|binding| binding.ty)
.expect("checked above as mandatory generic"),
hasher2: parsed
.remove("Hasher2")
.map(|binding| binding.ty)
.unwrap_or(syn::parse_quote!(Blake2_128Concat)),
key2: parsed
.remove("Key2")
.map(|binding| binding.ty)
.expect("checked above as mandatory generic"),
value: parsed
.remove("Value")
.map(|binding| binding.ty)
.expect("checked above as mandatory generic"),
query_kind: parsed.remove("QueryKind").map(|binding| binding.ty),
on_empty: parsed.remove("OnEmpty").map(|binding| binding.ty),
max_values: parsed.remove("MaxValues").map(|binding| binding.ty),
}
},
StorageKind::NMap => {
check_generics(
&parsed,
&["Key", "Value"],
&["QueryKind", "OnEmpty", "MaxValues"],
"StorageNMap",
args_span,
)?;
StorageGenerics::NMap {
keygen: parsed
.remove("Key")
.map(|binding| binding.ty)
.expect("checked above as mandatory generic"),
value: parsed
.remove("Value")
.map(|binding| binding.ty)
.expect("checked above as mandatory generic"),
query_kind: parsed.remove("QueryKind").map(|binding| binding.ty),
on_empty: parsed.remove("OnEmpty").map(|binding| binding.ty),
max_values: parsed.remove("MaxValues").map(|binding| binding.ty),
}
},
StorageKind::CountedNMap => {
check_generics(
&parsed,
&["Key", "Value"],
&["QueryKind", "OnEmpty", "MaxValues"],
"CountedStorageNMap",
args_span,
)?;
StorageGenerics::CountedNMap {
keygen: parsed
.remove("Key")
.map(|binding| binding.ty)
.expect("checked above as mandatory generic"),
value: parsed
.remove("Value")
.map(|binding| binding.ty)
.expect("checked above as mandatory generic"),
query_kind: parsed.remove("QueryKind").map(|binding| binding.ty),
on_empty: parsed.remove("OnEmpty").map(|binding| binding.ty),
max_values: parsed.remove("MaxValues").map(|binding| binding.ty),
}
},
};
let metadata = generics.metadata()?;
let query_kind = generics.query_kind();
Ok((Some(generics), metadata, query_kind, false))
}
/// Returns `(named generics, metadata, query kind, use_default_hasher)`
fn process_unnamed_generics(
storage: &StorageKind,
args_span: proc_macro2::Span,
args: &[syn::Type],
dev_mode: bool,
) -> syn::Result<(Option<StorageGenerics>, Metadata, Option<syn::Type>, bool)> {
let retrieve_arg = |arg_pos| {
args.get(arg_pos).cloned().ok_or_else(|| {
let msg = format!(
"Invalid pezpallet::storage, unexpected number of generic argument, \
expect at least {} args, found {}.",
arg_pos + 1,
args.len(),
);
syn::Error::new(args_span, msg)
})
};
let prefix_arg = retrieve_arg(0)?;
syn::parse2::<syn::Token![_]>(prefix_arg.to_token_stream()).map_err(|e| {
let msg = "Invalid pezpallet::storage, for unnamed generic arguments the type \
first generic argument must be `_`, the argument is then replaced by macro.";
let mut err = syn::Error::new(prefix_arg.span(), msg);
err.combine(e);
err
})?;
let use_default_hasher = |arg_pos| {
let arg = retrieve_arg(arg_pos)?;
if syn::parse2::<syn::Token![_]>(arg.to_token_stream()).is_ok() {
if dev_mode {
Ok(true)
} else {
let msg = "`_` can only be used in dev_mode. Please specify an appropriate hasher.";
Err(syn::Error::new(arg.span(), msg))
}
} else {
Ok(false)
}
};
let res = match storage {
StorageKind::Value =>
(None, Metadata::Value { value: retrieve_arg(1)? }, retrieve_arg(2).ok(), false),
StorageKind::Map => (
None,
Metadata::Map { key: retrieve_arg(2)?, value: retrieve_arg(3)? },
retrieve_arg(4).ok(),
use_default_hasher(1)?,
),
StorageKind::CountedMap => (
None,
Metadata::CountedMap { key: retrieve_arg(2)?, value: retrieve_arg(3)? },
retrieve_arg(4).ok(),
use_default_hasher(1)?,
),
StorageKind::DoubleMap => (
None,
Metadata::DoubleMap {
key1: retrieve_arg(2)?,
key2: retrieve_arg(4)?,
value: retrieve_arg(5)?,
},
retrieve_arg(6).ok(),
use_default_hasher(1)? && use_default_hasher(3)?,
),
StorageKind::NMap => {
let keygen = retrieve_arg(1)?;
let keys = collect_keys(&keygen)?;
(
None,
Metadata::NMap { keys, keygen, value: retrieve_arg(2)? },
retrieve_arg(3).ok(),
false,
)
},
StorageKind::CountedNMap => {
let keygen = retrieve_arg(1)?;
let keys = collect_keys(&keygen)?;
(
None,
Metadata::CountedNMap { keys, keygen, value: retrieve_arg(2)? },
retrieve_arg(3).ok(),
false,
)
},
};
Ok(res)
}
/// Returns `(named generics, metadata, query kind, use_default_hasher)`
fn process_generics(
segment: &syn::PathSegment,
dev_mode: bool,
) -> syn::Result<(Option<StorageGenerics>, Metadata, Option<syn::Type>, bool)> {
let storage_kind = match &*segment.ident.to_string() {
"StorageValue" => StorageKind::Value,
"StorageMap" => StorageKind::Map,
"CountedStorageMap" => StorageKind::CountedMap,
"StorageDoubleMap" => StorageKind::DoubleMap,
"StorageNMap" => StorageKind::NMap,
"CountedStorageNMap" => StorageKind::CountedNMap,
found => {
let msg = format!(
"Invalid pezpallet::storage, expected ident: `StorageValue` or \
`StorageMap` or `CountedStorageMap` or `StorageDoubleMap` or `StorageNMap` or `CountedStorageNMap` \
in order to expand metadata, found `{}`.",
found,
);
return Err(syn::Error::new(segment.ident.span(), msg));
},
};
let args_span = segment.arguments.span();
let args = match &segment.arguments {
syn::PathArguments::AngleBracketed(args) if !args.args.is_empty() => args,
_ => {
let msg = "Invalid pezpallet::storage, invalid number of generic generic arguments, \
expect more that 0 generic arguments.";
return Err(syn::Error::new(segment.span(), msg));
},
};
if args.args.iter().all(|gen| matches!(gen, syn::GenericArgument::Type(_))) {
let args = args
.args
.iter()
.map(|gen| match gen {
syn::GenericArgument::Type(gen) => gen.clone(),
_ => unreachable!("It is asserted above that all generics are types"),
})
.collect::<Vec<_>>();
process_unnamed_generics(&storage_kind, args_span, &args, dev_mode)
} else if args.args.iter().all(|gen| matches!(gen, syn::GenericArgument::AssocType(_))) {
let args = args
.args
.iter()
.map(|gen| match gen {
syn::GenericArgument::AssocType(gen) => gen.clone(),
_ => unreachable!("It is asserted above that all generics are bindings"),
})
.collect::<Vec<_>>();
process_named_generics(&storage_kind, args_span, &args, dev_mode)
} else {
let msg = "Invalid pezpallet::storage, invalid generic declaration for storage. Expect only \
type generics or binding generics, e.g. `<Name1 = Gen1, Name2 = Gen2, ..>` or \
`<Gen1, Gen2, ..>`.";
Err(syn::Error::new(segment.span(), msg))
}
}
/// Parse the 2nd type argument to `StorageNMap` and return its keys.
fn collect_keys(keygen: &syn::Type) -> syn::Result<Vec<syn::Type>> {
if let syn::Type::Tuple(tup) = keygen {
tup.elems.iter().map(extract_key).collect::<syn::Result<Vec<_>>>()
} else {
Ok(vec![extract_key(keygen)?])
}
}
/// In `Key<H, K>`, extract K and return it.
fn extract_key(ty: &syn::Type) -> syn::Result<syn::Type> {
let typ = if let syn::Type::Path(typ) = ty {
typ
} else {
let msg = "Invalid pezpallet::storage, expected type path";
return Err(syn::Error::new(ty.span(), msg));
};
let key_struct = typ.path.segments.last().ok_or_else(|| {
let msg = "Invalid pezpallet::storage, expected type path with at least one segment";
syn::Error::new(typ.path.span(), msg)
})?;
if key_struct.ident != "Key" && key_struct.ident != "NMapKey" {
let msg = "Invalid pezpallet::storage, expected Key or NMapKey struct";
return Err(syn::Error::new(key_struct.ident.span(), msg));
}
let ty_params = if let syn::PathArguments::AngleBracketed(args) = &key_struct.arguments {
args
} else {
let msg = "Invalid pezpallet::storage, expected angle bracketed arguments";
return Err(syn::Error::new(key_struct.arguments.span(), msg));
};
if ty_params.args.len() != 2 {
let msg = format!(
"Invalid pezpallet::storage, unexpected number of generic arguments \
for Key struct, expected 2 args, found {}",
ty_params.args.len()
);
return Err(syn::Error::new(ty_params.span(), msg));
}
let key = match &ty_params.args[1] {
syn::GenericArgument::Type(key_ty) => key_ty.clone(),
_ => {
let msg = "Invalid pezpallet::storage, expected type";
return Err(syn::Error::new(ty_params.args[1].span(), msg));
},
};
Ok(key)
}
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_else(|| 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_else(|| self.ident.span())
}
pub fn try_from(
attr_span: proc_macro2::Span,
index: usize,
item: &mut syn::Item,
dev_mode: bool,
) -> syn::Result<Self> {
let item = if let syn::Item::Type(item) = item {
item
} else {
return Err(syn::Error::new(item.span(), "Invalid pezpallet::storage, expect item type."));
};
let attrs: Vec<PalletStorageAttr> = helper::take_item_pallet_attrs(&mut item.attrs)?;
let PalletStorageAttrInfo { getter, rename_as, mut unbounded, whitelisted, try_decode } =
PalletStorageAttrInfo::from_attrs(attrs)?;
// set all storages to be unbounded if dev_mode is enabled
unbounded |= dev_mode;
let cfg_attrs = helper::get_item_cfg_attrs(&item.attrs);
let instances = vec![helper::check_type_def_gen(&item.generics, item.ident.span())?];
let where_clause = item.generics.where_clause.clone();
let docs = get_doc_literals(&item.attrs);
let typ = if let syn::Type::Path(typ) = &*item.ty {
typ
} else {
let msg = "Invalid pezpallet::storage, expected type path";
return Err(syn::Error::new(item.ty.span(), msg));
};
if typ.path.segments.len() != 1 {
let msg = "Invalid pezpallet::storage, expected type path with one segment";
return Err(syn::Error::new(item.ty.span(), msg));
}
let (named_generics, metadata, query_kind, use_default_hasher) =
process_generics(&typ.path.segments[0], dev_mode)?;
let query_kind = query_kind
.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 pezpallet::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 pezpallet::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 pezpallet::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, segments: error },
err_variant,
)))
},
gen_arg => {
let msg = format!(
"Invalid pezpallet::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))
},
}
})
.transpose()?
.unwrap_or(Some(QueryKind::OptionQuery));
if let (None, Some(getter)) = (query_kind.as_ref(), getter.as_ref()) {
let msg = "Invalid pezpallet::storage, cannot generate getter because QueryKind is not \
identifiable. QueryKind must be `OptionQuery`, `ResultQuery`, `ValueQuery`, or default \
one to be identifiable.";
return Err(syn::Error::new(getter.span(), msg));
}
Ok(StorageDef {
attr_span,
index,
vis: item.vis.clone(),
ident: item.ident.clone(),
instances,
metadata,
docs,
getter,
rename_as,
query_kind,
where_clause,
cfg_attrs,
named_generics,
unbounded,
whitelisted,
try_decode,
use_default_hasher,
attrs: item.attrs.clone(),
})
}
}
@@ -0,0 +1,949 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Home of the parsing code for the Tasks API
use std::collections::HashSet;
#[cfg(test)]
use crate::assert_parse_error_matches;
#[cfg(test)]
use crate::pezpallet::parse::tests::simulate_manifest_dir;
use super::helper;
use derive_syn_parse::Parse;
use proc_macro2::TokenStream as TokenStream2;
use quote::{quote, ToTokens};
use syn::{
parse::ParseStream,
parse2,
spanned::Spanned,
token::{Bracket, Paren, PathSep, Pound},
Error, Expr, Ident, ImplItem, ImplItemFn, ItemEnum, ItemImpl, LitInt, PathArguments, Result,
TypePath,
};
pub mod keywords {
use syn::custom_keyword;
custom_keyword!(tasks_experimental);
custom_keyword!(task_enum);
custom_keyword!(task_list);
custom_keyword!(task_condition);
custom_keyword!(task_index);
custom_keyword!(task_weight);
custom_keyword!(pezpallet);
}
/// Represents the `#[pezpallet::tasks_experimental]` attribute and its attached item. Also includes
/// metadata about the linked [`TaskEnumDef`] if applicable.
#[derive(Clone, Debug)]
pub struct TasksDef {
pub tasks_attr: Option<PalletTasksAttr>,
pub tasks: Vec<TaskDef>,
pub item_impl: ItemImpl,
pub enum_ident: Ident,
pub enum_arguments: PathArguments,
}
impl syn::parse::Parse for TasksDef {
fn parse(input: ParseStream) -> Result<Self> {
let item_impl: ItemImpl = input.parse()?;
let (tasks_attrs, normal_attrs) = partition_tasks_attrs(&item_impl);
let tasks_attr = match tasks_attrs.first() {
Some(attr) => Some(parse2::<PalletTasksAttr>(attr.to_token_stream())?),
None => None,
};
if let Some(extra_tasks_attr) = tasks_attrs.get(1) {
return Err(Error::new(
extra_tasks_attr.span(),
"unexpected extra `#[pezpallet::tasks_experimental]` attribute",
));
}
let tasks: Vec<TaskDef> = if tasks_attr.is_some() {
item_impl
.items
.clone()
.into_iter()
.filter(|impl_item| matches!(impl_item, ImplItem::Fn(_)))
.map(|item| parse2::<TaskDef>(item.to_token_stream()))
.collect::<Result<_>>()?
} else {
Vec::new()
};
let mut task_indices = HashSet::<LitInt>::new();
for task in tasks.iter() {
let task_index = &task.index_attr.meta.index;
if !task_indices.insert(task_index.clone()) {
return Err(Error::new(
task_index.span(),
format!("duplicate task index `{}`", task_index),
));
}
}
let mut item_impl = item_impl;
item_impl.attrs = normal_attrs;
// we require the path on the impl to be a TypePath
let enum_path = parse2::<TypePath>(item_impl.self_ty.to_token_stream())?;
let segments = enum_path.path.segments.iter().collect::<Vec<_>>();
let (Some(last_seg), None) = (segments.get(0), segments.get(1)) else {
return Err(Error::new(
enum_path.span(),
"if specified manually, the task enum must be defined locally in this \
pezpallet and cannot be a re-export",
));
};
let enum_ident = last_seg.ident.clone();
let enum_arguments = last_seg.arguments.clone();
Ok(TasksDef { tasks_attr, item_impl, tasks, enum_ident, enum_arguments })
}
}
/// Parsing for a `#[pezpallet::tasks_experimental]` attr.
pub type PalletTasksAttr = PalletTaskAttr<keywords::tasks_experimental>;
/// Parsing for any of the attributes that can be used within a `#[pezpallet::tasks_experimental]`
/// [`ItemImpl`].
pub type TaskAttr = PalletTaskAttr<TaskAttrMeta>;
/// Parsing for a `#[pezpallet::task_index]` attr.
pub type TaskIndexAttr = PalletTaskAttr<TaskIndexAttrMeta>;
/// Parsing for a `#[pezpallet::task_condition]` attr.
pub type TaskConditionAttr = PalletTaskAttr<TaskConditionAttrMeta>;
/// Parsing for a `#[pezpallet::task_list]` attr.
pub type TaskListAttr = PalletTaskAttr<TaskListAttrMeta>;
/// Parsing for a `#[pezpallet::task_weight]` attr.
pub type TaskWeightAttr = PalletTaskAttr<TaskWeightAttrMeta>;
/// Parsing for a `#[pezpallet:task_enum]` attr.
pub type PalletTaskEnumAttr = PalletTaskAttr<keywords::task_enum>;
/// Parsing for a manually-specified (or auto-generated) task enum, optionally including the
/// attached `#[pezpallet::task_enum]` attribute.
#[derive(Clone)]
pub struct TaskEnumDef {
pub attr: Option<PalletTaskEnumAttr>,
pub item_enum: ItemEnum,
pub instance_usage: helper::InstanceUsage,
}
impl syn::parse::Parse for TaskEnumDef {
fn parse(input: ParseStream) -> Result<Self> {
let mut item_enum = input.parse::<ItemEnum>()?;
let attr = extract_pallet_attr(&mut item_enum)?;
let attr = match attr {
Some(attr) => Some(parse2(attr)?),
None => None,
};
let instance_usage =
helper::check_type_def_gen(&item_enum.generics, item_enum.ident.span())?;
Ok(TaskEnumDef { attr, item_enum, instance_usage })
}
}
/// Represents an individual tasks within a [`TasksDef`].
#[derive(Debug, Clone)]
pub struct TaskDef {
pub index_attr: TaskIndexAttr,
pub condition_attr: TaskConditionAttr,
pub list_attr: TaskListAttr,
pub weight_attr: TaskWeightAttr,
pub item: ImplItemFn,
pub arg_names: Vec<Ident>,
}
impl syn::parse::Parse for TaskDef {
fn parse(input: ParseStream) -> Result<Self> {
let item = input.parse::<ImplItemFn>()?;
// we only want to activate TaskAttrType parsing errors for tasks-related attributes,
// so we filter them here
let task_attrs = partition_task_attrs(&item).0;
let task_attrs: Vec<TaskAttr> = task_attrs
.into_iter()
.map(|attr| parse2(attr.to_token_stream()))
.collect::<Result<_>>()?;
let Some(index_attr) = task_attrs
.iter()
.find(|attr| matches!(attr.meta, TaskAttrMeta::TaskIndex(_)))
.cloned()
else {
return Err(Error::new(
item.sig.ident.span(),
"missing `#[pezpallet::task_index(..)]` attribute",
));
};
let Some(condition_attr) = task_attrs
.iter()
.find(|attr| matches!(attr.meta, TaskAttrMeta::TaskCondition(_)))
.cloned()
else {
return Err(Error::new(
item.sig.ident.span(),
"missing `#[pezpallet::task_condition(..)]` attribute",
));
};
let Some(list_attr) = task_attrs
.iter()
.find(|attr| matches!(attr.meta, TaskAttrMeta::TaskList(_)))
.cloned()
else {
return Err(Error::new(
item.sig.ident.span(),
"missing `#[pezpallet::task_list(..)]` attribute",
));
};
let Some(weight_attr) = task_attrs
.iter()
.find(|attr| matches!(attr.meta, TaskAttrMeta::TaskWeight(_)))
.cloned()
else {
return Err(Error::new(
item.sig.ident.span(),
"missing `#[pezpallet::task_weight(..)]` attribute",
));
};
if let Some(duplicate) = task_attrs
.iter()
.filter(|attr| matches!(attr.meta, TaskAttrMeta::TaskCondition(_)))
.collect::<Vec<_>>()
.get(1)
{
return Err(Error::new(
duplicate.span(),
"unexpected extra `#[pezpallet::task_condition(..)]` attribute",
));
}
if let Some(duplicate) = task_attrs
.iter()
.filter(|attr| matches!(attr.meta, TaskAttrMeta::TaskList(_)))
.collect::<Vec<_>>()
.get(1)
{
return Err(Error::new(
duplicate.span(),
"unexpected extra `#[pezpallet::task_list(..)]` attribute",
));
}
if let Some(duplicate) = task_attrs
.iter()
.filter(|attr| matches!(attr.meta, TaskAttrMeta::TaskIndex(_)))
.collect::<Vec<_>>()
.get(1)
{
return Err(Error::new(
duplicate.span(),
"unexpected extra `#[pezpallet::task_index(..)]` attribute",
));
}
let mut arg_names = vec![];
for input in item.sig.inputs.iter() {
match input {
syn::FnArg::Typed(pat_type) => match &*pat_type.pat {
syn::Pat::Ident(ident) => arg_names.push(ident.ident.clone()),
_ => return Err(Error::new(input.span(), "unexpected pattern type")),
},
_ => return Err(Error::new(input.span(), "unexpected function argument type")),
}
}
let index_attr = index_attr.try_into().expect("we check the type above; QED");
let condition_attr = condition_attr.try_into().expect("we check the type above; QED");
let list_attr = list_attr.try_into().expect("we check the type above; QED");
let weight_attr = weight_attr.try_into().expect("we check the type above; QED");
Ok(TaskDef { index_attr, condition_attr, list_attr, weight_attr, item, arg_names })
}
}
/// The contents of a [`TasksDef`]-related attribute.
#[derive(Parse, Debug, Clone)]
pub enum TaskAttrMeta {
#[peek(keywords::task_list, name = "#[pezpallet::task_list(..)]")]
TaskList(TaskListAttrMeta),
#[peek(keywords::task_index, name = "#[pezpallet::task_index(..)")]
TaskIndex(TaskIndexAttrMeta),
#[peek(keywords::task_condition, name = "#[pezpallet::task_condition(..)")]
TaskCondition(TaskConditionAttrMeta),
#[peek(keywords::task_weight, name = "#[pezpallet::task_weight(..)")]
TaskWeight(TaskWeightAttrMeta),
}
/// The contents of a `#[pezpallet::task_list]` attribute.
#[derive(Parse, Debug, Clone)]
pub struct TaskListAttrMeta {
pub task_list: keywords::task_list,
#[paren]
_paren: Paren,
#[inside(_paren)]
pub expr: Expr,
}
/// The contents of a `#[pezpallet::task_index]` attribute.
#[derive(Parse, Debug, Clone)]
pub struct TaskIndexAttrMeta {
pub task_index: keywords::task_index,
#[paren]
_paren: Paren,
#[inside(_paren)]
pub index: LitInt,
}
/// The contents of a `#[pezpallet::task_condition]` attribute.
#[derive(Parse, Debug, Clone)]
pub struct TaskConditionAttrMeta {
pub task_condition: keywords::task_condition,
#[paren]
_paren: Paren,
#[inside(_paren)]
pub expr: Expr,
}
/// The contents of a `#[pezpallet::task_weight]` attribute.
#[derive(Parse, Debug, Clone)]
pub struct TaskWeightAttrMeta {
pub task_weight: keywords::task_weight,
#[paren]
_paren: Paren,
#[inside(_paren)]
pub expr: Expr,
}
/// The contents of a `#[pezpallet::task]` attribute.
#[derive(Parse, Debug, Clone)]
pub struct PalletTaskAttr<T: syn::parse::Parse + core::fmt::Debug + ToTokens> {
pub pound: Pound,
#[bracket]
_bracket: Bracket,
#[inside(_bracket)]
pub pezpallet: keywords::pezpallet,
#[inside(_bracket)]
pub colons: PathSep,
#[inside(_bracket)]
pub meta: T,
}
impl ToTokens for TaskListAttrMeta {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let task_list = self.task_list;
let expr = &self.expr;
tokens.extend(quote!(#task_list(#expr)));
}
}
impl ToTokens for TaskConditionAttrMeta {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let task_condition = self.task_condition;
let expr = &self.expr;
tokens.extend(quote!(#task_condition(#expr)));
}
}
impl ToTokens for TaskWeightAttrMeta {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let task_weight = self.task_weight;
let expr = &self.expr;
tokens.extend(quote!(#task_weight(#expr)));
}
}
impl ToTokens for TaskIndexAttrMeta {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let task_index = self.task_index;
let index = &self.index;
tokens.extend(quote!(#task_index(#index)))
}
}
impl ToTokens for TaskAttrMeta {
fn to_tokens(&self, tokens: &mut TokenStream2) {
match self {
TaskAttrMeta::TaskList(list) => tokens.extend(list.to_token_stream()),
TaskAttrMeta::TaskIndex(index) => tokens.extend(index.to_token_stream()),
TaskAttrMeta::TaskCondition(condition) => tokens.extend(condition.to_token_stream()),
TaskAttrMeta::TaskWeight(weight) => tokens.extend(weight.to_token_stream()),
}
}
}
impl<T: syn::parse::Parse + core::fmt::Debug + ToTokens> ToTokens for PalletTaskAttr<T> {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let pound = self.pound;
let pezpallet = self.pezpallet;
let colons = self.colons;
let meta = &self.meta;
tokens.extend(quote!(#pound[#pezpallet #colons #meta]));
}
}
impl TryFrom<PalletTaskAttr<TaskAttrMeta>> for TaskIndexAttr {
type Error = syn::Error;
fn try_from(value: PalletTaskAttr<TaskAttrMeta>) -> Result<Self> {
let pound = value.pound;
let pezpallet = value.pezpallet;
let colons = value.colons;
match value.meta {
TaskAttrMeta::TaskIndex(meta) => parse2(quote!(#pound[#pezpallet #colons #meta])),
_ =>
return Err(Error::new(
value.span(),
format!("`{:?}` cannot be converted to a `TaskIndexAttr`", value.meta),
)),
}
}
}
impl TryFrom<PalletTaskAttr<TaskAttrMeta>> for TaskConditionAttr {
type Error = syn::Error;
fn try_from(value: PalletTaskAttr<TaskAttrMeta>) -> Result<Self> {
let pound = value.pound;
let pezpallet = value.pezpallet;
let colons = value.colons;
match value.meta {
TaskAttrMeta::TaskCondition(meta) => parse2(quote!(#pound[#pezpallet #colons #meta])),
_ =>
return Err(Error::new(
value.span(),
format!("`{:?}` cannot be converted to a `TaskConditionAttr`", value.meta),
)),
}
}
}
impl TryFrom<PalletTaskAttr<TaskAttrMeta>> for TaskWeightAttr {
type Error = syn::Error;
fn try_from(value: PalletTaskAttr<TaskAttrMeta>) -> Result<Self> {
let pound = value.pound;
let pezpallet = value.pezpallet;
let colons = value.colons;
match value.meta {
TaskAttrMeta::TaskWeight(meta) => parse2(quote!(#pound[#pezpallet #colons #meta])),
_ =>
return Err(Error::new(
value.span(),
format!("`{:?}` cannot be converted to a `TaskWeightAttr`", value.meta),
)),
}
}
}
impl TryFrom<PalletTaskAttr<TaskAttrMeta>> for TaskListAttr {
type Error = syn::Error;
fn try_from(value: PalletTaskAttr<TaskAttrMeta>) -> Result<Self> {
let pound = value.pound;
let pezpallet = value.pezpallet;
let colons = value.colons;
match value.meta {
TaskAttrMeta::TaskList(meta) => parse2(quote!(#pound[#pezpallet #colons #meta])),
_ =>
return Err(Error::new(
value.span(),
format!("`{:?}` cannot be converted to a `TaskListAttr`", value.meta),
)),
}
}
}
fn extract_pallet_attr(item_enum: &mut ItemEnum) -> Result<Option<TokenStream2>> {
let mut duplicate = None;
let mut attr = None;
item_enum.attrs = item_enum
.attrs
.iter()
.filter(|found_attr| {
let segs = found_attr
.path()
.segments
.iter()
.map(|seg| seg.ident.clone())
.collect::<Vec<_>>();
let (Some(seg1), Some(_), None) = (segs.get(0), segs.get(1), segs.get(2)) else {
return true;
};
if seg1 != "pezpallet" {
return true;
}
if attr.is_some() {
duplicate = Some(found_attr.span());
}
attr = Some(found_attr.to_token_stream());
false
})
.cloned()
.collect();
if let Some(span) = duplicate {
return Err(Error::new(span, "only one `#[pezpallet::_]` attribute is supported on this item"));
}
Ok(attr)
}
fn partition_tasks_attrs(item_impl: &ItemImpl) -> (Vec<syn::Attribute>, Vec<syn::Attribute>) {
item_impl.attrs.clone().into_iter().partition(|attr| {
let mut path_segs = attr.path().segments.iter();
let (Some(prefix), Some(suffix), None) =
(path_segs.next(), path_segs.next(), path_segs.next())
else {
return false;
};
prefix.ident == "pezpallet" && suffix.ident == "tasks_experimental"
})
}
fn partition_task_attrs(item: &ImplItemFn) -> (Vec<syn::Attribute>, Vec<syn::Attribute>) {
item.attrs.clone().into_iter().partition(|attr| {
let mut path_segs = attr.path().segments.iter();
let (Some(prefix), Some(suffix)) = (path_segs.next(), path_segs.next()) else {
return false;
};
// N.B: the `PartialEq` impl between `Ident` and `&str` is more efficient than
// parsing and makes no stack or heap allocations
prefix.ident == "pezpallet" &&
(suffix.ident == "tasks_experimental" ||
suffix.ident == "task_list" ||
suffix.ident == "task_condition" ||
suffix.ident == "task_weight" ||
suffix.ident == "task_index")
})
}
#[test]
fn test_parse_task_list_() {
parse2::<TaskAttr>(quote!(#[pezpallet::task_list(Something::iter())])).unwrap();
parse2::<TaskAttr>(quote!(#[pezpallet::task_list(Numbers::<T, I>::iter_keys())])).unwrap();
parse2::<TaskAttr>(quote!(#[pezpallet::task_list(iter())])).unwrap();
assert_parse_error_matches!(
parse2::<TaskAttr>(quote!(#[pezpallet::task_list()])),
"expected an expression"
);
assert_parse_error_matches!(
parse2::<TaskAttr>(quote!(#[pezpallet::task_list])),
"expected parentheses"
);
}
#[test]
fn test_parse_task_index() {
parse2::<TaskAttr>(quote!(#[pezpallet::task_index(3)])).unwrap();
parse2::<TaskAttr>(quote!(#[pezpallet::task_index(0)])).unwrap();
parse2::<TaskAttr>(quote!(#[pezpallet::task_index(17)])).unwrap();
assert_parse_error_matches!(
parse2::<TaskAttr>(quote!(#[pezpallet::task_index])),
"expected parentheses"
);
assert_parse_error_matches!(
parse2::<TaskAttr>(quote!(#[pezpallet::task_index("hey")])),
"expected integer literal"
);
assert_parse_error_matches!(
parse2::<TaskAttr>(quote!(#[pezpallet::task_index(0.3)])),
"expected integer literal"
);
}
#[test]
fn test_parse_task_condition() {
parse2::<TaskAttr>(quote!(#[pezpallet::task_condition(|x| x.is_some())])).unwrap();
parse2::<TaskAttr>(quote!(#[pezpallet::task_condition(|_x| some_expr())])).unwrap();
parse2::<TaskAttr>(quote!(#[pezpallet::task_condition(|| some_expr())])).unwrap();
parse2::<TaskAttr>(quote!(#[pezpallet::task_condition(some_expr())])).unwrap();
}
#[test]
fn test_parse_tasks_attr() {
parse2::<PalletTasksAttr>(quote!(#[pezpallet::tasks_experimental])).unwrap();
assert_parse_error_matches!(
parse2::<PalletTasksAttr>(quote!(#[pezpallet::taskss])),
"expected `tasks_experimental`"
);
assert_parse_error_matches!(
parse2::<PalletTasksAttr>(quote!(#[pezpallet::tasks_])),
"expected `tasks_experimental`"
);
assert_parse_error_matches!(
parse2::<PalletTasksAttr>(quote!(#[pal::tasks])),
"expected `pezpallet`"
);
assert_parse_error_matches!(
parse2::<PalletTasksAttr>(quote!(#[pezpallet::tasks_experimental()])),
"unexpected token"
);
}
#[test]
fn test_parse_tasks_def_basic() {
simulate_manifest_dir("../../examples/basic", || {
let parsed = parse2::<TasksDef>(quote! {
#[pezpallet::tasks_experimental]
impl<T: Config<I>, I: 'static> Pezpallet<T, I> {
/// Add a pair of numbers into the totals and remove them.
#[pezpallet::task_list(Numbers::<T, I>::iter_keys())]
#[pezpallet::task_condition(|i| Numbers::<T, I>::contains_key(i))]
#[pezpallet::task_index(0)]
#[pezpallet::task_weight(0)]
pub fn add_number_into_total(i: u32) -> DispatchResult {
let v = Numbers::<T, I>::take(i).ok_or(Error::<T, I>::NotFound)?;
Total::<T, I>::mutate(|(total_keys, total_values)| {
*total_keys += i;
*total_values += v;
});
Ok(())
}
}
})
.unwrap();
assert_eq!(parsed.tasks.len(), 1);
});
}
#[test]
fn test_parse_tasks_def_basic_increment_decrement() {
simulate_manifest_dir("../../examples/basic", || {
let parsed = parse2::<TasksDef>(quote! {
#[pezpallet::tasks_experimental]
impl<T: Config<I>, I: 'static> Pezpallet<T, I> {
/// Get the value and check if it can be incremented
#[pezpallet::task_index(0)]
#[pezpallet::task_condition(|| {
let value = Value::<T>::get().unwrap();
value < 255
})]
#[pezpallet::task_list(Vec::<Task<T>>::new())]
#[pezpallet::task_weight(0)]
fn increment() -> DispatchResult {
let value = Value::<T>::get().unwrap_or_default();
if value >= 255 {
Err(Error::<T>::ValueOverflow.into())
} else {
let new_val = value.checked_add(1).ok_or(Error::<T>::ValueOverflow)?;
Value::<T>::put(new_val);
Pezpallet::<T>::deposit_event(Event::Incremented { new_val });
Ok(())
}
}
// Get the value and check if it can be decremented
#[pezpallet::task_index(1)]
#[pezpallet::task_condition(|| {
let value = Value::<T>::get().unwrap();
value > 0
})]
#[pezpallet::task_list(Vec::<Task<T>>::new())]
#[pezpallet::task_weight(0)]
fn decrement() -> DispatchResult {
let value = Value::<T>::get().unwrap_or_default();
if value == 0 {
Err(Error::<T>::ValueUnderflow.into())
} else {
let new_val = value.checked_sub(1).ok_or(Error::<T>::ValueUnderflow)?;
Value::<T>::put(new_val);
Pezpallet::<T>::deposit_event(Event::Decremented { new_val });
Ok(())
}
}
}
})
.unwrap();
assert_eq!(parsed.tasks.len(), 2);
});
}
#[test]
fn test_parse_tasks_def_duplicate_index() {
simulate_manifest_dir("../../examples/basic", || {
assert_parse_error_matches!(
parse2::<TasksDef>(quote! {
#[pezpallet::tasks_experimental]
impl<T: Config<I>, I: 'static> Pezpallet<T, I> {
#[pezpallet::task_list(Something::iter())]
#[pezpallet::task_condition(|i| i % 2 == 0)]
#[pezpallet::task_index(0)]
#[pezpallet::task_weight(0)]
pub fn foo(i: u32) -> DispatchResult {
Ok(())
}
#[pezpallet::task_list(Numbers::<T, I>::iter_keys())]
#[pezpallet::task_condition(|i| Numbers::<T, I>::contains_key(i))]
#[pezpallet::task_index(0)]
#[pezpallet::task_weight(0)]
pub fn bar(i: u32) -> DispatchResult {
Ok(())
}
}
}),
"duplicate task index `0`"
);
});
}
#[test]
fn test_parse_tasks_def_missing_task_list() {
simulate_manifest_dir("../../examples/basic", || {
assert_parse_error_matches!(
parse2::<TasksDef>(quote! {
#[pezpallet::tasks_experimental]
impl<T: Config<I>, I: 'static> Pezpallet<T, I> {
#[pezpallet::task_condition(|i| i % 2 == 0)]
#[pezpallet::task_index(0)]
pub fn foo(i: u32) -> DispatchResult {
Ok(())
}
}
}),
r"missing `#\[pezpallet::task_list\(\.\.\)\]`"
);
});
}
#[test]
fn test_parse_tasks_def_missing_task_condition() {
simulate_manifest_dir("../../examples/basic", || {
assert_parse_error_matches!(
parse2::<TasksDef>(quote! {
#[pezpallet::tasks_experimental]
impl<T: Config<I>, I: 'static> Pezpallet<T, I> {
#[pezpallet::task_list(Something::iter())]
#[pezpallet::task_index(0)]
pub fn foo(i: u32) -> DispatchResult {
Ok(())
}
}
}),
r"missing `#\[pezpallet::task_condition\(\.\.\)\]`"
);
});
}
#[test]
fn test_parse_tasks_def_missing_task_index() {
simulate_manifest_dir("../../examples/basic", || {
assert_parse_error_matches!(
parse2::<TasksDef>(quote! {
#[pezpallet::tasks_experimental]
impl<T: Config<I>, I: 'static> Pezpallet<T, I> {
#[pezpallet::task_condition(|i| i % 2 == 0)]
#[pezpallet::task_list(Something::iter())]
pub fn foo(i: u32) -> DispatchResult {
Ok(())
}
}
}),
r"missing `#\[pezpallet::task_index\(\.\.\)\]`"
);
});
}
#[test]
fn test_parse_tasks_def_missing_task_weight() {
simulate_manifest_dir("../../examples/basic", || {
assert_parse_error_matches!(
parse2::<TasksDef>(quote! {
#[pezpallet::tasks_experimental]
impl<T: Config<I>, I: 'static> Pezpallet<T, I> {
#[pezpallet::task_condition(|i| i % 2 == 0)]
#[pezpallet::task_list(Something::iter())]
#[pezpallet::task_index(0)]
pub fn foo(i: u32) -> DispatchResult {
Ok(())
}
}
}),
r"missing `#\[pezpallet::task_weight\(\.\.\)\]`"
);
});
}
#[test]
fn test_parse_tasks_def_unexpected_extra_task_list_attr() {
simulate_manifest_dir("../../examples/basic", || {
assert_parse_error_matches!(
parse2::<TasksDef>(quote! {
#[pezpallet::tasks_experimental]
impl<T: Config<I>, I: 'static> Pezpallet<T, I> {
#[pezpallet::task_condition(|i| i % 2 == 0)]
#[pezpallet::task_index(0)]
#[pezpallet::task_weight(0)]
#[pezpallet::task_list(Something::iter())]
#[pezpallet::task_list(SomethingElse::iter())]
pub fn foo(i: u32) -> DispatchResult {
Ok(())
}
}
}),
r"unexpected extra `#\[pezpallet::task_list\(\.\.\)\]`"
);
});
}
#[test]
fn test_parse_tasks_def_unexpected_extra_task_condition_attr() {
simulate_manifest_dir("../../examples/basic", || {
assert_parse_error_matches!(
parse2::<TasksDef>(quote! {
#[pezpallet::tasks_experimental]
impl<T: Config<I>, I: 'static> Pezpallet<T, I> {
#[pezpallet::task_condition(|i| i % 2 == 0)]
#[pezpallet::task_condition(|i| i % 4 == 0)]
#[pezpallet::task_index(0)]
#[pezpallet::task_list(Something::iter())]
#[pezpallet::task_weight(0)]
pub fn foo(i: u32) -> DispatchResult {
Ok(())
}
}
}),
r"unexpected extra `#\[pezpallet::task_condition\(\.\.\)\]`"
);
});
}
#[test]
fn test_parse_tasks_def_unexpected_extra_task_index_attr() {
simulate_manifest_dir("../../examples/basic", || {
assert_parse_error_matches!(
parse2::<TasksDef>(quote! {
#[pezpallet::tasks_experimental]
impl<T: Config<I>, I: 'static> Pezpallet<T, I> {
#[pezpallet::task_condition(|i| i % 2 == 0)]
#[pezpallet::task_index(0)]
#[pezpallet::task_index(0)]
#[pezpallet::task_list(Something::iter())]
#[pezpallet::task_weight(0)]
pub fn foo(i: u32) -> DispatchResult {
Ok(())
}
}
}),
r"unexpected extra `#\[pezpallet::task_index\(\.\.\)\]`"
);
});
}
#[test]
fn test_parse_tasks_def_extra_tasks_attribute() {
simulate_manifest_dir("../../examples/basic", || {
assert_parse_error_matches!(
parse2::<TasksDef>(quote! {
#[pezpallet::tasks_experimental]
#[pezpallet::tasks_experimental]
impl<T: Config<I>, I: 'static> Pezpallet<T, I> {}
}),
r"unexpected extra `#\[pezpallet::tasks_experimental\]` attribute"
);
});
}
#[test]
fn test_parse_task_enum_def_basic() {
simulate_manifest_dir("../../examples/basic", || {
parse2::<TaskEnumDef>(quote! {
#[pezpallet::task_enum]
pub enum Task<T: Config> {
Increment,
Decrement,
}
})
.unwrap();
});
}
#[test]
fn test_parse_task_enum_def_non_task_name() {
simulate_manifest_dir("../../examples/basic", || {
parse2::<TaskEnumDef>(quote! {
#[pezpallet::task_enum]
pub enum Something<T> {
Foo
}
})
.unwrap();
});
}
#[test]
fn test_parse_task_enum_def_missing_attr_allowed() {
simulate_manifest_dir("../../examples/basic", || {
parse2::<TaskEnumDef>(quote! {
pub enum Task<T: Config> {
Increment,
Decrement,
}
})
.unwrap();
});
}
#[test]
fn test_parse_task_enum_def_missing_attr_alternate_name_allowed() {
simulate_manifest_dir("../../examples/basic", || {
parse2::<TaskEnumDef>(quote! {
pub enum Foo<T> {
Red,
}
})
.unwrap();
});
}
#[test]
fn test_parse_task_enum_def_wrong_attr() {
simulate_manifest_dir("../../examples/basic", || {
assert_parse_error_matches!(
parse2::<TaskEnumDef>(quote! {
#[pezpallet::something]
pub enum Task<T: Config> {
Increment,
Decrement,
}
}),
"expected `task_enum`"
);
});
}
#[test]
fn test_parse_task_enum_def_wrong_item() {
simulate_manifest_dir("../../examples/basic", || {
assert_parse_error_matches!(
parse2::<TaskEnumDef>(quote! {
#[pezpallet::task_enum]
pub struct Something<T>;
}),
"expected `enum`"
);
});
}
@@ -0,0 +1,296 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use std::{panic, sync::Mutex};
use syn::parse_quote;
#[doc(hidden)]
pub mod __private {
pub use regex;
}
/// Allows you to assert that the input expression resolves to an error whose string
/// representation matches the specified regex literal.
///
/// ## Example:
///
/// ```
/// use super::tasks::*;
///
/// assert_parse_error_matches!(
/// parse2::<TaskEnumDef>(quote! {
/// #[pezpallet::task_enum]
/// pub struct Something;
/// }),
/// "expected `enum`"
/// );
/// ```
///
/// More complex regular expressions are also possible (anything that could pass as a regex for
/// use with the [`regex`] crate.):
///
/// ```ignore
/// assert_parse_error_matches!(
/// parse2::<TasksDef>(quote! {
/// #[pezpallet::tasks_experimental]
/// impl<T: Config<I>, I: 'static> Pezpallet<T, I> {
/// #[pezpallet::task_condition(|i| i % 2 == 0)]
/// #[pezpallet::task_index(0)]
/// pub fn foo(i: u32) -> DispatchResult {
/// Ok(())
/// }
/// }
/// }),
/// r"missing `#\[pezpallet::task_list\(\.\.\)\]`"
/// );
/// ```
///
/// Although this is primarily intended to be used with parsing errors, this macro is general
/// enough that it will work with any error with a reasonable [`core::fmt::Display`] impl.
#[macro_export]
macro_rules! assert_parse_error_matches {
($expr:expr, $reg:literal) => {
match $expr {
Ok(_) => panic!("Expected an `Error(..)`, but got Ok(..)"),
Err(e) => {
let error_message = e.to_string();
let re = $crate::pezpallet::parse::tests::__private::regex::Regex::new($reg)
.expect("Invalid regex pattern");
assert!(
re.is_match(&error_message),
"Error message \"{}\" does not match the pattern \"{}\"",
error_message,
$reg
);
},
}
};
}
/// Allows you to assert that an entire pezpallet parses successfully. A custom syntax is used for
/// specifying arguments so please pay attention to the docs below.
///
/// The general syntax is:
///
/// ```ignore
/// assert_pallet_parses! {
/// #[manifest_dir("../../examples/basic")]
/// #[pezframe_support::pezpallet]
/// pub mod pezpallet {
/// #[pezpallet::config]
/// pub trait Config: pezframe_system::Config {}
///
/// #[pezpallet::pezpallet]
/// pub struct Pezpallet<T>(_);
/// }
/// };
/// ```
///
/// The `#[manifest_dir(..)]` attribute _must_ be specified as the _first_ attribute on the
/// pezpallet module, and should reference the relative (to your current directory) path of a
/// directory containing containing the `Cargo.toml` of a valid pezpallet. Typically you will only
/// ever need to use the `examples/basic` pezpallet, but sometimes it might be advantageous to
/// specify a different one that has additional dependencies.
///
/// The reason this must be specified is that our underlying parsing of pallets depends on
/// reaching out into the file system to look for particular `Cargo.toml` dependencies via the
/// [`generate_access_from_frame_or_crate`] method, so to simulate this properly in a proc
/// macro crate, we need to temporarily convince this function that we are running from the
/// directory of a valid pezpallet.
#[macro_export]
macro_rules! assert_pallet_parses {
(
#[manifest_dir($manifest_dir:literal)]
$($tokens:tt)*
) => {
{
let mut pezpallet: Option<$crate::pezpallet::parse::Def> = None;
$crate::pezpallet::parse::tests::simulate_manifest_dir($manifest_dir, core::panic::AssertUnwindSafe(|| {
pezpallet = Some($crate::pezpallet::parse::Def::try_from(syn::parse_quote! {
$($tokens)*
}, false).unwrap());
}));
pezpallet.unwrap()
}
}
}
/// Similar to [`assert_pallet_parses`], except this instead expects the pezpallet not to parse,
/// and allows you to specify a regex matching the expected parse error.
///
/// This is identical syntactically to [`assert_pallet_parses`] in every way except there is a
/// second attribute that must be specified immediately after `#[manifest_dir(..)]` which is
/// `#[error_regex(..)]` which should contain a string/regex literal designed to match what you
/// consider to be the correct parsing error we should see when we try to parse this particular
/// pezpallet.
///
/// ## Example:
///
/// ```
/// assert_pallet_parse_error! {
/// #[manifest_dir("../../examples/basic")]
/// #[error_regex("Missing `\\#\\[pezpallet::pezpallet\\]`")]
/// #[pezframe_support::pezpallet]
/// pub mod pezpallet {
/// #[pezpallet::config]
/// pub trait Config: pezframe_system::Config {}
/// }
/// }
/// ```
#[macro_export]
macro_rules! assert_pallet_parse_error {
(
#[manifest_dir($manifest_dir:literal)]
#[error_regex($reg:literal)]
$($tokens:tt)*
) => {
$crate::pezpallet::parse::tests::simulate_manifest_dir($manifest_dir, || {
$crate::assert_parse_error_matches!(
$crate::pezpallet::parse::Def::try_from(
parse_quote! {
$($tokens)*
},
false
),
$reg
);
});
}
}
/// Safely runs the specified `closure` while simulating an alternative `CARGO_MANIFEST_DIR`,
/// restoring `CARGO_MANIFEST_DIR` to its original value upon completion regardless of whether
/// the closure panics.
///
/// This is useful in tests of `Def::try_from` and other pezpallet-related methods that internally
/// make use of [`generate_access_from_frame_or_crate`], which is sensitive to entries in the
/// "current" `Cargo.toml` files.
///
/// This function uses a [`Mutex`] to avoid a race condition created when multiple tests try to
/// modify and then restore the `CARGO_MANIFEST_DIR` ENV var in an overlapping way.
pub fn simulate_manifest_dir<P: AsRef<std::path::Path>, F: FnOnce() + std::panic::UnwindSafe>(
path: P,
closure: F,
) {
use std::{env::*, path::*};
/// Ensures that only one thread can modify/restore the `CARGO_MANIFEST_DIR` ENV var at a time,
/// avoiding a race condition because `cargo test` runs tests in parallel.
///
/// Although this forces all tests that use [`simulate_manifest_dir`] to run sequentially with
/// respect to each other, this is still several orders of magnitude faster than using UI
/// tests, even if they are run in parallel.
static MANIFEST_DIR_LOCK: Mutex<()> = Mutex::new(());
// avoid race condition when swapping out `CARGO_MANIFEST_DIR`
let guard = MANIFEST_DIR_LOCK.lock().unwrap();
// obtain the current/original `CARGO_MANIFEST_DIR`
let orig = PathBuf::from(
var("CARGO_MANIFEST_DIR").expect("failed to read ENV var `CARGO_MANIFEST_DIR`"),
);
// set `CARGO_MANIFEST_DIR` to the provided path, relative to current working dir
set_var("CARGO_MANIFEST_DIR", orig.join(path.as_ref()));
// safely run closure catching any panics
let result = panic::catch_unwind(closure);
// restore original `CARGO_MANIFEST_DIR` before unwinding
set_var("CARGO_MANIFEST_DIR", &orig);
// unlock the mutex so we don't poison it if there is a panic
drop(guard);
// unwind any panics originally encountered when running closure
result.unwrap();
}
mod tasks;
#[test]
fn test_parse_minimal_pallet() {
assert_pallet_parses! {
#[manifest_dir("../../examples/basic")]
#[pezframe_support::pezpallet]
pub mod pezpallet {
#[pezpallet::config]
pub trait Config: pezframe_system::Config {}
#[pezpallet::pezpallet]
pub struct Pezpallet<T>(_);
}
};
}
#[test]
fn test_parse_pallet_missing_pallet() {
assert_pallet_parse_error! {
#[manifest_dir("../../examples/basic")]
#[error_regex("Missing `\\#\\[pezpallet::pezpallet\\]`")]
#[pezframe_support::pezpallet]
pub mod pezpallet {
#[pezpallet::config]
pub trait Config: pezframe_system::Config {}
}
}
}
#[test]
fn test_parse_pallet_missing_config() {
assert_pallet_parse_error! {
#[manifest_dir("../../examples/basic")]
#[error_regex("Missing `\\#\\[pezpallet::config\\]`")]
#[pezframe_support::pezpallet]
pub mod pezpallet {
#[pezpallet::pezpallet]
pub struct Pezpallet<T>(_);
}
}
}
#[test]
fn test_parse_pallet_deprecated_attribute_on_error_enum() {
assert_pallet_parse_error! {
#[manifest_dir("../../examples/basic")]
#[error_regex("The `\\#\\[deprecated\\]` attribute should be applied to individual variants, not the enum as a whole\\.")]
#[pezframe_support::pezpallet]
pub mod pezpallet {
#[pezpallet::error]
#[deprecated(note = "This enum is deprecated and should not be used.")]
pub enum Error<T> {
Foo
}
}
}
}
#[test]
fn test_parse_pallet_deprecated_attribute_on_event_enum() {
assert_pallet_parse_error! {
#[manifest_dir("../../examples/basic")]
#[error_regex("The `\\#\\[deprecated\\]` attribute should be applied to individual variants, not the enum as a whole\\.")]
#[pezframe_support::pezpallet]
pub mod pezpallet {
#[pezpallet::event]
#[deprecated(note = "This enum is deprecated and should not be used.")]
pub enum Event<T> {
Foo
}
}
}
}
@@ -0,0 +1,240 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use syn::parse_quote;
#[test]
fn test_parse_pallet_with_task_enum_missing_impl() {
assert_pallet_parse_error! {
#[manifest_dir("../../examples/basic")]
#[error_regex("Missing `\\#\\[pezpallet::tasks_experimental\\]` impl")]
#[pezframe_support::pezpallet]
pub mod pezpallet {
#[pezpallet::task_enum]
pub enum Task<T: Config> {
Something,
}
#[pezpallet::config]
pub trait Config: pezframe_system::Config {}
#[pezpallet::pezpallet]
pub struct Pezpallet<T>(_);
}
}
}
#[test]
fn test_parse_pallet_with_task_enum_wrong_attribute() {
assert_pallet_parse_error! {
#[manifest_dir("../../examples/basic")]
#[error_regex("expected one of")]
#[pezframe_support::pezpallet]
pub mod pezpallet {
#[pezpallet::wrong_attribute]
pub enum Task<T: Config> {
Something,
}
#[pezpallet::task_list]
impl<T: Config> pezframe_support::traits::Task for Task<T>
where
T: TypeInfo,
{}
#[pezpallet::config]
pub trait Config: pezframe_system::Config {}
#[pezpallet::pezpallet]
pub struct Pezpallet<T>(_);
}
}
}
#[test]
fn test_parse_pallet_missing_task_enum() {
assert_pallet_parses! {
#[manifest_dir("../../examples/basic")]
#[pezframe_support::pezpallet]
pub mod pezpallet {
#[pezpallet::tasks_experimental]
#[cfg(test)] // aha, this means it's being eaten
impl<T: Config> pezframe_support::traits::Task for Task<T>
where
T: TypeInfo,
{}
#[pezpallet::config]
pub trait Config: pezframe_system::Config {}
#[pezpallet::pezpallet]
pub struct Pezpallet<T>(_);
}
};
}
#[test]
fn test_parse_pallet_task_list_in_wrong_place() {
assert_pallet_parse_error! {
#[manifest_dir("../../examples/basic")]
#[error_regex("can only be used on items within an `impl` statement.")]
#[pezframe_support::pezpallet]
pub mod pezpallet {
pub enum MyCustomTaskEnum<T: Config> {
Something,
}
#[pezpallet::task_list]
pub fn something() {
println!("hey");
}
#[pezpallet::config]
pub trait Config: pezframe_system::Config {}
#[pezpallet::pezpallet]
pub struct Pezpallet<T>(_);
}
}
}
#[test]
fn test_parse_pallet_manual_tasks_impl_without_manual_tasks_enum() {
assert_pallet_parse_error! {
#[manifest_dir("../../examples/basic")]
#[error_regex(".*attribute must be attached to your.*")]
#[pezframe_support::pezpallet]
pub mod pezpallet {
impl<T: Config> pezframe_support::traits::Task for Task<T>
where
T: TypeInfo,
{
type Enumeration = alloc::vec::IntoIter<Task<T>>;
fn iter() -> Self::Enumeration {
alloc::vec![Task::increment, Task::decrement].into_iter()
}
}
#[pezpallet::config]
pub trait Config: pezframe_system::Config {}
#[pezpallet::pezpallet]
pub struct Pezpallet<T>(_);
}
}
}
#[test]
fn test_parse_pallet_manual_task_enum_non_manual_impl() {
assert_pallet_parses! {
#[manifest_dir("../../examples/basic")]
#[pezframe_support::pezpallet]
pub mod pezpallet {
pub enum MyCustomTaskEnum<T: Config> {
Something,
}
#[pezpallet::tasks_experimental]
impl<T: Config> pezframe_support::traits::Task for MyCustomTaskEnum<T>
where
T: TypeInfo,
{}
#[pezpallet::config]
pub trait Config: pezframe_system::Config {}
#[pezpallet::pezpallet]
pub struct Pezpallet<T>(_);
}
};
}
#[test]
fn test_parse_pallet_non_manual_task_enum_manual_impl() {
assert_pallet_parses! {
#[manifest_dir("../../examples/basic")]
#[pezframe_support::pezpallet]
pub mod pezpallet {
#[pezpallet::task_enum]
pub enum MyCustomTaskEnum<T: Config> {
Something,
}
impl<T: Config> pezframe_support::traits::Task for MyCustomTaskEnum<T>
where
T: TypeInfo,
{}
#[pezpallet::config]
pub trait Config: pezframe_system::Config {}
#[pezpallet::pezpallet]
pub struct Pezpallet<T>(_);
}
};
}
#[test]
fn test_parse_pallet_manual_task_enum_manual_impl() {
assert_pallet_parses! {
#[manifest_dir("../../examples/basic")]
#[pezframe_support::pezpallet]
pub mod pezpallet {
pub enum MyCustomTaskEnum<T: Config> {
Something,
}
impl<T: Config> pezframe_support::traits::Task for MyCustomTaskEnum<T>
where
T: TypeInfo,
{}
#[pezpallet::config]
pub trait Config: pezframe_system::Config {}
#[pezpallet::pezpallet]
pub struct Pezpallet<T>(_);
}
};
}
#[test]
fn test_parse_pallet_manual_task_enum_mismatch_ident() {
assert_pallet_parses! {
#[manifest_dir("../../examples/basic")]
#[pezframe_support::pezpallet]
pub mod pezpallet {
pub enum WrongIdent<T: Config> {
Something,
}
#[pezpallet::tasks_experimental]
impl<T: Config> pezframe_support::traits::Task for MyCustomTaskEnum<T>
where
T: TypeInfo,
{}
#[pezpallet::config]
pub trait Config: pezframe_system::Config {}
#[pezpallet::pezpallet]
pub struct Pezpallet<T>(_);
}
};
}
@@ -0,0 +1,104 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use super::helper;
use syn::spanned::Spanned;
/// Definition of type value. Just a function which is expanded to a struct implementing `Get`.
pub struct TypeValueDef {
/// The index of error item in pezpallet module.
pub index: usize,
/// Visibility of the struct to generate.
pub vis: syn::Visibility,
/// Ident of the struct to generate.
pub ident: syn::Ident,
/// The type return by Get.
pub type_: Box<syn::Type>,
/// If type value is generic over `T` (or `T` and `I` for instantiable pezpallet)
pub is_generic: bool,
/// The where clause of the function.
pub where_clause: Option<syn::WhereClause>,
/// The span of the pezpallet::type_value attribute.
pub attr_span: proc_macro2::Span,
/// Docs on the item.
pub docs: Vec<syn::Expr>,
}
impl TypeValueDef {
pub fn try_from(
attr_span: proc_macro2::Span,
index: usize,
item: &mut syn::Item,
) -> syn::Result<Self> {
let item = if let syn::Item::Fn(item) = item {
item
} else {
let msg = "Invalid pezpallet::type_value, expected item fn";
return Err(syn::Error::new(item.span(), msg));
};
let mut docs = vec![];
for attr in &item.attrs {
if let syn::Meta::NameValue(meta) = &attr.meta {
if meta.path.get_ident().map_or(false, |ident| ident == "doc") {
docs.push(meta.value.clone());
continue;
}
}
let msg = "Invalid pezpallet::type_value, unexpected attribute, only doc attribute are \
allowed";
return Err(syn::Error::new(attr.span(), msg));
}
if let Some(span) = item
.sig
.constness
.as_ref()
.map(|t| t.span())
.or_else(|| item.sig.asyncness.as_ref().map(|t| t.span()))
.or_else(|| item.sig.unsafety.as_ref().map(|t| t.span()))
.or_else(|| item.sig.abi.as_ref().map(|t| t.span()))
.or_else(|| item.sig.variadic.as_ref().map(|t| t.span()))
{
let msg = "Invalid pezpallet::type_value, unexpected token";
return Err(syn::Error::new(span, msg));
}
if !item.sig.inputs.is_empty() {
let msg = "Invalid pezpallet::type_value, unexpected argument";
return Err(syn::Error::new(item.sig.inputs[0].span(), msg));
}
let vis = item.vis.clone();
let ident = item.sig.ident.clone();
let type_ = match item.sig.output.clone() {
syn::ReturnType::Type(_, type_) => type_,
syn::ReturnType::Default => {
let msg = "Invalid pezpallet::type_value, expected return type";
return Err(syn::Error::new(item.sig.span(), msg));
},
};
helper::check_type_value_gen(&item.sig.generics, item.sig.span())?;
let is_generic = item.sig.generics.type_params().count() > 0;
let where_clause = item.sig.generics.where_clause.clone();
Ok(TypeValueDef { attr_span, index, is_generic, vis, ident, type_, where_clause, docs })
}
}
@@ -0,0 +1,55 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use super::helper;
use syn::spanned::Spanned;
/// The definition of the pezpallet validate unsigned implementation.
pub struct ValidateUnsignedDef {}
impl ValidateUnsignedDef {
pub fn try_from(item: &mut syn::Item) -> syn::Result<Self> {
let item = if let syn::Item::Impl(item) = item {
item
} else {
let msg = "Invalid pezpallet::validate_unsigned, expected item impl";
return Err(syn::Error::new(item.span(), msg));
};
if item.trait_.is_none() {
let msg = "Invalid pezpallet::validate_unsigned, expected impl<..> ValidateUnsigned for \
Pezpallet<..>";
return Err(syn::Error::new(item.span(), msg));
}
if let Some(last) = item.trait_.as_ref().unwrap().1.segments.last() {
if last.ident != "ValidateUnsigned" {
let msg = "Invalid pezpallet::validate_unsigned, expected trait ValidateUnsigned";
return Err(syn::Error::new(last.span(), msg));
}
} else {
let msg = "Invalid pezpallet::validate_unsigned, expected impl<..> ValidateUnsigned for \
Pezpallet<..>";
return Err(syn::Error::new(item.span(), msg));
}
helper::check_pallet_struct_usage(&item.self_ty)?;
helper::check_impl_gen(&item.generics, item.impl_token.span())?;
Ok(ValidateUnsignedDef {})
}
}
@@ -0,0 +1,152 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governsing permissions and
// limitations under the License.
use pezframe_support_procedural_tools::get_doc_literals;
use inflector::Inflector;
use syn::spanned::Spanned;
/// Parsed representation of an impl block annotated with `pezpallet::view_functions`.
pub struct ViewFunctionsImplDef {
/// The where_clause used.
pub where_clause: Option<syn::WhereClause>,
/// The span of the pezpallet::view_functions attribute.
pub attr_span: proc_macro2::Span,
/// The view function definitions.
pub view_functions: Vec<ViewFunctionDef>,
}
impl ViewFunctionsImplDef {
pub fn try_from(attr_span: proc_macro2::Span, item: &mut syn::Item) -> syn::Result<Self> {
let syn::Item::Impl(item_impl) = item else {
return Err(syn::Error::new(
item.span(),
"Invalid pezpallet::view_functions, expected item impl",
));
};
let mut view_functions = Vec::new();
for item in &mut item_impl.items {
if let syn::ImplItem::Fn(method) = item {
if !matches!(method.vis, syn::Visibility::Public(_)) {
let msg = "Invalid pezpallet::view_functions, view function must be public: \
`pub fn`";
let span = match method.vis {
syn::Visibility::Inherited => method.sig.span(),
_ => method.vis.span(),
};
return Err(syn::Error::new(span, msg));
}
let view_fn_def = ViewFunctionDef::try_from(method.clone())?;
view_functions.push(view_fn_def)
} else {
return Err(syn::Error::new(
item.span(),
"Invalid pezpallet::view_functions, expected a function",
));
}
}
Ok(Self {
view_functions,
attr_span,
where_clause: item_impl.generics.where_clause.clone(),
})
}
}
/// Parsed representation of a view function definition.
#[derive(Clone)]
pub struct ViewFunctionDef {
pub name: syn::Ident,
pub docs: Vec<syn::Expr>,
pub args: Vec<syn::FnArg>,
pub return_type: syn::Type,
}
impl TryFrom<syn::ImplItemFn> for ViewFunctionDef {
type Error = syn::Error;
fn try_from(method: syn::ImplItemFn) -> Result<Self, Self::Error> {
let syn::ReturnType::Type(_, type_) = method.sig.output else {
return Err(syn::Error::new(method.sig.span(), "view functions must return a value"));
};
Ok(Self {
name: method.sig.ident.clone(),
docs: get_doc_literals(&method.attrs),
args: method.sig.inputs.iter().cloned().collect::<Vec<_>>(),
return_type: *type_.clone(),
})
}
}
impl ViewFunctionDef {
pub fn view_function_struct_ident(&self) -> syn::Ident {
syn::Ident::new(
&format!("{}ViewFunction", self.name.to_string().to_pascal_case()),
self.name.span(),
)
}
pub fn view_function_id_suffix_bytes(&self) -> Result<[u8; 16], syn::Error> {
let mut output = [0u8; 16];
// concatenate the signature string
let arg_types = self
.args_names_types()?
.1
.iter()
.map(|ty| quote::quote!(#ty).to_string().replace(" ", ""))
.collect::<Vec<_>>()
.join(",");
let return_type = &self.return_type;
let return_type = quote::quote!(#return_type).to_string().replace(" ", "");
let view_fn_signature = format!(
"{view_function_name}({arg_types}) -> {return_type}",
view_function_name = &self.name,
);
// hash the signature string
let hash = pezsp_crypto_hashing::twox_128(view_fn_signature.as_bytes());
output.copy_from_slice(&hash[..]);
Ok(output)
}
pub fn args_names_types(&self) -> Result<(Vec<syn::Ident>, Vec<syn::Type>), syn::Error> {
Ok(self
.args
.iter()
.map(|arg| {
let syn::FnArg::Typed(pat_type) = arg else {
return Err(syn::Error::new(
arg.span(),
"Unsupported argument in view function",
));
};
let syn::Pat::Ident(ident) = &*pat_type.pat else {
return Err(syn::Error::new(
pat_type.pat.span(),
"Unsupported pattern in view function argument",
));
};
Ok((ident.ident.clone(), *pat_type.ty.clone()))
})
.collect::<Result<Vec<(syn::Ident, syn::Type)>, syn::Error>>()?
.into_iter()
.unzip())
}
}