Introduce #[pallet::call_index] attribute to dispatchables (#11381)

* Introduce #[pallet::call_index] attribute to dispatchables

* cargo fmt

* Add more docs and prevent duplicates of call indices

* Add UI test for conflicting call indices

* cargo fmt

Co-authored-by: parity-processbot <>
This commit is contained in:
Keith Yeung
2022-05-23 17:47:36 +01:00
committed by GitHub
parent 3122c4fb3c
commit 8d3f692e0c
11 changed files with 207 additions and 17 deletions
@@ -42,6 +42,7 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream {
let pallet_ident = &def.pallet_struct.pallet;
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))
@@ -177,6 +178,7 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream {
),
#(
#( #[doc = #fn_doc] )*
#[codec(index = #call_index)]
#fn_name {
#(
#[allow(missing_docs)]
@@ -18,6 +18,7 @@
use super::helper;
use frame_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.
@@ -25,6 +26,7 @@ mod keyword {
syn::custom_keyword!(Call);
syn::custom_keyword!(OriginFor);
syn::custom_keyword!(weight);
syn::custom_keyword!(call_index);
syn::custom_keyword!(compact);
syn::custom_keyword!(T);
syn::custom_keyword!(pallet);
@@ -55,14 +57,17 @@ pub struct CallVariantDef {
pub args: Vec<(bool, syn::Ident, Box<syn::Type>)>,
/// Weight formula.
pub weight: syn::Expr,
/// Call index of the dispatchable.
pub call_index: u8,
/// Docs, used for metadata.
pub docs: Vec<syn::Lit>,
}
/// Attributes for functions in call impl block.
/// Parse for `#[pallet::weight(expr)]`
pub struct FunctionAttr {
weight: syn::Expr,
/// Parse for `#[pallet::weight(expr)]` or `#[pallet::call_index(expr)]
pub enum FunctionAttr {
CallIndex(u8),
Weight(syn::Expr),
}
impl syn::parse::Parse for FunctionAttr {
@@ -72,11 +77,22 @@ impl syn::parse::Parse for FunctionAttr {
syn::bracketed!(content in input);
content.parse::<keyword::pallet>()?;
content.parse::<syn::Token![::]>()?;
content.parse::<keyword::weight>()?;
let weight_content;
syn::parenthesized!(weight_content in content);
Ok(FunctionAttr { weight: weight_content.parse::<syn::Expr>()? })
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>()?;
Ok(FunctionAttr::CallIndex(index.base10_parse()?))
} else {
Err(lookahead.error())
}
}
}
@@ -145,6 +161,8 @@ impl CallDef {
}
let mut methods = vec![];
let mut indices = HashMap::new();
let mut last_index: Option<u8> = None;
for impl_item in &mut item.items {
if let syn::ImplItem::Method(method) = impl_item {
if !matches!(method.vis, syn::Visibility::Public(_)) {
@@ -182,18 +200,58 @@ impl CallDef {
return Err(syn::Error::new(method.sig.span(), msg))
}
let mut call_var_attrs: Vec<FunctionAttr> =
helper::take_item_pallet_attrs(&mut method.attrs)?;
let (mut weight_attrs, mut call_idx_attrs): (Vec<FunctionAttr>, Vec<FunctionAttr>) =
helper::take_item_pallet_attrs(&mut method.attrs)?.into_iter().partition(
|attr| {
if let FunctionAttr::Weight(_) = attr {
true
} else {
false
}
},
);
if call_var_attrs.len() != 1 {
let msg = if call_var_attrs.is_empty() {
if weight_attrs.len() != 1 {
let msg = if weight_attrs.is_empty() {
"Invalid pallet::call, requires weight attribute i.e. `#[pallet::weight($expr)]`"
} else {
"Invalid pallet::call, too many weight attributes given"
};
return Err(syn::Error::new(method.sig.span(), msg))
}
let weight = call_var_attrs.pop().unwrap().weight;
let weight = match weight_attrs.pop().unwrap() {
FunctionAttr::Weight(w) => w,
_ => unreachable!("checked during creation of the let binding"),
};
if call_idx_attrs.len() > 1 {
let msg = "Invalid pallet::call, too many call_index attributes given";
return Err(syn::Error::new(method.sig.span(), msg))
}
let call_index = call_idx_attrs.pop().map(|attr| match attr {
FunctionAttr::CallIndex(idx) => idx,
_ => unreachable!("checked during creation of the let binding"),
});
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) {
@@ -223,7 +281,13 @@ impl CallDef {
let docs = get_doc_literals(&method.attrs);
methods.push(CallVariantDef { name: method.sig.ident.clone(), weight, args, docs });
methods.push(CallVariantDef {
name: method.sig.ident.clone(),
weight,
call_index: final_index,
args,
docs,
});
} else {
let msg = "Invalid pallet::call, only method accepted";
return Err(syn::Error::new(impl_item.span(), msg))