mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-30 07:17:56 +00:00
Automatic pallet parts in construct_runtime (#9681)
* implement automatic parts * ui tests * rename * remove unnecessary exclude * better doc * better doc * fix genesis config * fix UI tests * fix UI test * Revert "fix UI test" This reverts commit a910351c0b24cfe42195cfd97d83a416640e3259. * implemented used_parts * Update frame/support/procedural/src/construct_runtime/mod.rs Co-authored-by: Keith Yeung <kungfukeith11@gmail.com> * doc + fmt * Update frame/support/procedural/src/construct_runtime/parse.rs Co-authored-by: Keith Yeung <kungfukeith11@gmail.com> * add doc in the macro * remove yet some more parts * fix ui test * more determnistic error message + fix ui tests * fix ui test * Apply suggestions from code review Co-authored-by: Keith Yeung <kungfukeith11@gmail.com> * do refactor + fix ui tests * fmt * fix test * fix test * fix ui test * Apply suggestions from code review Co-authored-by: Keith Yeung <kungfukeith11@gmail.com> * refactor * remove even more part in node-runtime * fix test * Add flow chart for the construct_runtime! execution flow * Fix typo * Ignore snippets that don't contain code * Refactor some code in expand_after * Rename expand_after to match_and_insert * cargo fmt * Fix rename * Remove frame_support argument to construct_runtime_parts * Make use of tt-call to simplify intermediate expansions * cargo fmt * Update match_and_insert documentation * Reset cursor to 0 when no matching patterns are found * Reorder struct fields on MatchAndInsertDef * Add test for dependency renames and fix frame-support import * Add more doc comments * Update frame/support/test/compile_pass/src/lib.rs Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com> Co-authored-by: Keith Yeung <kungfukeith11@gmail.com> Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
This commit is contained in:
committed by
GitHub
parent
0214fde9a6
commit
4292e18e50
@@ -17,13 +17,13 @@
|
||||
|
||||
use frame_support_procedural_tools::syn_ext as ext;
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use std::collections::HashSet;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use syn::{
|
||||
ext::IdentExt,
|
||||
parse::{Parse, ParseStream},
|
||||
punctuated::Punctuated,
|
||||
spanned::Spanned,
|
||||
token, Error, Ident, Path, PathArguments, PathSegment, Result, Token,
|
||||
token, Error, Ident, Path, Result, Token,
|
||||
};
|
||||
|
||||
mod keyword {
|
||||
@@ -38,26 +38,63 @@ mod keyword {
|
||||
syn::custom_keyword!(Origin);
|
||||
syn::custom_keyword!(Inherent);
|
||||
syn::custom_keyword!(ValidateUnsigned);
|
||||
syn::custom_keyword!(exclude_parts);
|
||||
syn::custom_keyword!(use_parts);
|
||||
}
|
||||
|
||||
/// Declaration of a runtime.
|
||||
///
|
||||
/// Pallet declare their part either explicitly or implicitly (using no part declaration)
|
||||
/// If all pallet have explicit parts then the runtime declaration is explicit, otherwise it is
|
||||
/// implicit.
|
||||
#[derive(Debug)]
|
||||
pub struct RuntimeDefinition {
|
||||
pub visibility_token: Token![pub],
|
||||
pub enum_token: Token![enum],
|
||||
pub enum RuntimeDeclaration {
|
||||
Implicit(ImplicitRuntimeDeclaration),
|
||||
Explicit(ExplicitRuntimeDeclaration),
|
||||
}
|
||||
|
||||
/// Declaration of a runtime with some pallet with implicit declaration of parts.
|
||||
#[derive(Debug)]
|
||||
pub struct ImplicitRuntimeDeclaration {
|
||||
pub name: Ident,
|
||||
pub where_section: WhereSection,
|
||||
pub pallets: ext::Braces<ext::Punctuated<PalletDeclaration, Token![,]>>,
|
||||
pub pallets: Vec<PalletDeclaration>,
|
||||
}
|
||||
|
||||
impl Parse for RuntimeDefinition {
|
||||
/// Declaration of a runtime with all pallet having explicit declaration of parts.
|
||||
#[derive(Debug)]
|
||||
pub struct ExplicitRuntimeDeclaration {
|
||||
pub name: Ident,
|
||||
pub where_section: WhereSection,
|
||||
pub pallets: Vec<Pallet>,
|
||||
pub pallets_token: token::Brace,
|
||||
}
|
||||
|
||||
impl Parse for RuntimeDeclaration {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
Ok(Self {
|
||||
visibility_token: input.parse()?,
|
||||
enum_token: input.parse()?,
|
||||
name: input.parse()?,
|
||||
where_section: input.parse()?,
|
||||
pallets: input.parse()?,
|
||||
})
|
||||
input.parse::<Token![pub]>()?;
|
||||
input.parse::<Token![enum]>()?;
|
||||
let name = input.parse::<syn::Ident>()?;
|
||||
let where_section = input.parse()?;
|
||||
let pallets =
|
||||
input.parse::<ext::Braces<ext::Punctuated<PalletDeclaration, Token![,]>>>()?;
|
||||
let pallets_token = pallets.token;
|
||||
|
||||
match convert_pallets(pallets.content.inner.into_iter().collect())? {
|
||||
PalletsConversion::Implicit(pallets) =>
|
||||
Ok(RuntimeDeclaration::Implicit(ImplicitRuntimeDeclaration {
|
||||
name,
|
||||
where_section,
|
||||
pallets,
|
||||
})),
|
||||
PalletsConversion::Explicit(pallets) =>
|
||||
Ok(RuntimeDeclaration::Explicit(ExplicitRuntimeDeclaration {
|
||||
name,
|
||||
where_section,
|
||||
pallets,
|
||||
pallets_token,
|
||||
})),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,14 +173,34 @@ impl Parse for WhereDefinition {
|
||||
}
|
||||
}
|
||||
|
||||
/// The declaration of a pallet.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PalletDeclaration {
|
||||
/// The name of the pallet, e.g.`System` in `System: frame_system`.
|
||||
pub name: Ident,
|
||||
/// Optional fixed index (e.g. `MyPallet ... = 3,`)
|
||||
/// Optional fixed index, e.g. `MyPallet ... = 3,`.
|
||||
pub index: Option<u8>,
|
||||
/// The path of the pallet, e.g. `frame_system` in `System: frame_system`.
|
||||
pub path: PalletPath,
|
||||
/// The instance of the pallet, e.g. `Instance1` in `Council: pallet_collective::<Instance1>`.
|
||||
pub instance: Option<Ident>,
|
||||
pub pallet_parts: Vec<PalletPart>,
|
||||
/// The declared pallet parts,
|
||||
/// e.g. `Some([Pallet, Call])` for `System: system::{Pallet, Call}`
|
||||
/// or `None` for `System: system`.
|
||||
pub pallet_parts: Option<Vec<PalletPart>>,
|
||||
/// The specified parts, either use_parts or exclude_parts.
|
||||
pub specified_parts: SpecifiedParts,
|
||||
}
|
||||
|
||||
/// The possible declaration of pallet parts to use.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum SpecifiedParts {
|
||||
/// Use all the pallet parts except those specified.
|
||||
Exclude(Vec<PalletPartNoGeneric>),
|
||||
/// Use only the specified pallet parts.
|
||||
Use(Vec<PalletPartNoGeneric>),
|
||||
/// Use the all the pallet parts.
|
||||
All,
|
||||
}
|
||||
|
||||
impl Parse for PalletDeclaration {
|
||||
@@ -151,38 +208,78 @@ impl Parse for PalletDeclaration {
|
||||
let name = input.parse()?;
|
||||
let _: Token![:] = input.parse()?;
|
||||
let path = input.parse()?;
|
||||
let instance = if input.peek(Token![<]) {
|
||||
|
||||
// Parse for instance.
|
||||
let instance = if input.peek(Token![::]) && input.peek3(Token![<]) {
|
||||
let _: Token![::] = input.parse()?;
|
||||
let _: Token![<] = input.parse()?;
|
||||
let res = Some(input.parse()?);
|
||||
let _: Token![>] = input.parse()?;
|
||||
let _: Token![::] = input.parse()?;
|
||||
res
|
||||
} else if !(input.peek(Token![::]) && input.peek3(token::Brace)) &&
|
||||
!input.peek(keyword::exclude_parts) &&
|
||||
!input.peek(keyword::use_parts) &&
|
||||
!input.peek(Token![=]) &&
|
||||
!input.peek(Token![,]) &&
|
||||
!input.is_empty()
|
||||
{
|
||||
return Err(input.error(
|
||||
"Unexpected tokens, expected one of `::$ident` `::{`, `exclude_parts`, `use_parts`, `=`, `,`",
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let pallet_parts = parse_pallet_parts(input)?;
|
||||
// Parse for explicit parts
|
||||
let pallet_parts = if input.peek(Token![::]) && input.peek3(token::Brace) {
|
||||
let _: Token![::] = input.parse()?;
|
||||
Some(parse_pallet_parts(input)?)
|
||||
} else if !input.peek(keyword::exclude_parts) &&
|
||||
!input.peek(keyword::use_parts) &&
|
||||
!input.peek(Token![=]) &&
|
||||
!input.peek(Token![,]) &&
|
||||
!input.is_empty()
|
||||
{
|
||||
return Err(input.error(
|
||||
"Unexpected tokens, expected one of `::{`, `exclude_parts`, `use_parts`, `=`, `,`",
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Parse for specified parts
|
||||
let specified_parts = if input.peek(keyword::exclude_parts) {
|
||||
let _: keyword::exclude_parts = input.parse()?;
|
||||
SpecifiedParts::Exclude(parse_pallet_parts_no_generic(input)?)
|
||||
} else if input.peek(keyword::use_parts) {
|
||||
let _: keyword::use_parts = input.parse()?;
|
||||
SpecifiedParts::Use(parse_pallet_parts_no_generic(input)?)
|
||||
} else if !input.peek(Token![=]) && !input.peek(Token![,]) && !input.is_empty() {
|
||||
return Err(input.error("Unexpected tokens, expected one of `exclude_parts`, `=`, `,`"))
|
||||
} else {
|
||||
SpecifiedParts::All
|
||||
};
|
||||
|
||||
// Parse for pallet index
|
||||
let index = if input.peek(Token![=]) {
|
||||
input.parse::<Token![=]>()?;
|
||||
let index = input.parse::<syn::LitInt>()?;
|
||||
let index = index.base10_parse::<u8>()?;
|
||||
Some(index)
|
||||
} else if !input.peek(Token![,]) && !input.is_empty() {
|
||||
return Err(input.error("Unexpected tokens, expected one of `=`, `,`"))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let parsed = Self { name, path, instance, pallet_parts, index };
|
||||
|
||||
Ok(parsed)
|
||||
Ok(Self { name, path, instance, pallet_parts, specified_parts, index })
|
||||
}
|
||||
}
|
||||
|
||||
/// A struct representing a path to a pallet. `PalletPath` is almost identical to the standard
|
||||
/// Rust path with a few restrictions:
|
||||
/// - No leading colons allowed
|
||||
/// - Path segments can only consist of identifers; angle-bracketed or parenthesized segments will
|
||||
/// result in a parsing error (except when specifying instances)
|
||||
/// - Path segments can only consist of identifers separated by colons
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PalletPath {
|
||||
pub inner: Path,
|
||||
@@ -202,34 +299,27 @@ impl PalletPath {
|
||||
|
||||
impl Parse for PalletPath {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let mut lookahead = input.lookahead1();
|
||||
let mut segments = Punctuated::new();
|
||||
let mut res =
|
||||
PalletPath { inner: Path { leading_colon: None, segments: Punctuated::new() } };
|
||||
|
||||
let lookahead = input.lookahead1();
|
||||
if lookahead.peek(Token![crate]) ||
|
||||
lookahead.peek(Token![self]) ||
|
||||
lookahead.peek(Token![super]) ||
|
||||
lookahead.peek(Ident)
|
||||
{
|
||||
let ident = input.call(Ident::parse_any)?;
|
||||
segments.push(PathSegment { ident, arguments: PathArguments::None });
|
||||
let _: Token![::] = input.parse()?;
|
||||
lookahead = input.lookahead1();
|
||||
res.inner.segments.push(ident.into());
|
||||
} else {
|
||||
return Err(lookahead.error())
|
||||
}
|
||||
|
||||
while lookahead.peek(Ident) {
|
||||
let ident = input.parse()?;
|
||||
segments.push(PathSegment { ident, arguments: PathArguments::None });
|
||||
let _: Token![::] = input.parse()?;
|
||||
lookahead = input.lookahead1();
|
||||
while input.peek(Token![::]) && input.peek3(Ident) {
|
||||
input.parse::<Token![::]>()?;
|
||||
let ident = input.parse::<Ident>()?;
|
||||
res.inner.segments.push(ident.into());
|
||||
}
|
||||
|
||||
if !lookahead.peek(token::Brace) && !lookahead.peek(Token![<]) {
|
||||
return Err(lookahead.error())
|
||||
}
|
||||
|
||||
Ok(Self { inner: Path { leading_colon: None, segments } })
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,3 +481,174 @@ fn remove_kind(
|
||||
Err(input.error(msg))
|
||||
}
|
||||
}
|
||||
|
||||
/// The declaration of a part without its generics
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PalletPartNoGeneric {
|
||||
keyword: PalletPartKeyword,
|
||||
}
|
||||
|
||||
impl Parse for PalletPartNoGeneric {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
Ok(Self { keyword: input.parse()? })
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse [`PalletPartNoGeneric`]'s from a braces enclosed list that is split by commas, e.g.
|
||||
///
|
||||
/// `{ Call, Event }`
|
||||
fn parse_pallet_parts_no_generic(input: ParseStream) -> Result<Vec<PalletPartNoGeneric>> {
|
||||
let pallet_parts: ext::Braces<ext::Punctuated<PalletPartNoGeneric, Token![,]>> =
|
||||
input.parse()?;
|
||||
|
||||
let mut resolved = HashSet::new();
|
||||
for part in pallet_parts.content.inner.iter() {
|
||||
if !resolved.insert(part.keyword.name()) {
|
||||
let msg = format!(
|
||||
"`{}` was already declared before. Please remove the duplicate declaration",
|
||||
part.keyword.name(),
|
||||
);
|
||||
return Err(Error::new(part.keyword.span(), msg))
|
||||
}
|
||||
}
|
||||
|
||||
Ok(pallet_parts.content.inner.into_iter().collect())
|
||||
}
|
||||
|
||||
/// The final definition of a pallet with the resulting fixed index and explicit parts.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Pallet {
|
||||
/// The name of the pallet, e.g.`System` in `System: frame_system`.
|
||||
pub name: Ident,
|
||||
/// Either automatically infered, or defined (e.g. `MyPallet ... = 3,`).
|
||||
pub index: u8,
|
||||
/// The path of the pallet, e.g. `frame_system` in `System: frame_system`.
|
||||
pub path: PalletPath,
|
||||
/// The instance of the pallet, e.g. `Instance1` in `Council: pallet_collective::<Instance1>`.
|
||||
pub instance: Option<Ident>,
|
||||
/// The pallet parts to use for the pallet.
|
||||
pub pallet_parts: Vec<PalletPart>,
|
||||
}
|
||||
|
||||
impl Pallet {
|
||||
/// Get resolved pallet parts
|
||||
pub fn pallet_parts(&self) -> &[PalletPart] {
|
||||
&self.pallet_parts
|
||||
}
|
||||
|
||||
/// Find matching parts
|
||||
pub fn find_part(&self, name: &str) -> Option<&PalletPart> {
|
||||
self.pallet_parts.iter().find(|part| part.name() == name)
|
||||
}
|
||||
|
||||
/// Return whether pallet contains part
|
||||
pub fn exists_part(&self, name: &str) -> bool {
|
||||
self.find_part(name).is_some()
|
||||
}
|
||||
}
|
||||
|
||||
/// Result of a conversion of a declaration of pallets.
|
||||
enum PalletsConversion {
|
||||
Implicit(Vec<PalletDeclaration>),
|
||||
Explicit(Vec<Pallet>),
|
||||
}
|
||||
|
||||
/// Convert from the parsed pallet declaration to their final information.
|
||||
///
|
||||
/// Check if all pallet have explicit declaration of their parts, if so then assign index to each
|
||||
/// pallet using same rules as rust for fieldless enum. I.e. implicit are assigned number
|
||||
/// incrementedly from last explicit or 0.
|
||||
fn convert_pallets(pallets: Vec<PalletDeclaration>) -> syn::Result<PalletsConversion> {
|
||||
if pallets.iter().any(|pallet| pallet.pallet_parts.is_none()) {
|
||||
return Ok(PalletsConversion::Implicit(pallets))
|
||||
}
|
||||
|
||||
let mut indices = HashMap::new();
|
||||
let mut last_index: Option<u8> = None;
|
||||
let mut names = HashMap::new();
|
||||
|
||||
let pallets = pallets
|
||||
.into_iter()
|
||||
.map(|pallet| {
|
||||
let final_index = match pallet.index {
|
||||
Some(i) => i,
|
||||
None => last_index.map_or(Some(0), |i| i.checked_add(1)).ok_or_else(|| {
|
||||
let msg = "Pallet index doesn't fit into u8, index is 256";
|
||||
syn::Error::new(pallet.name.span(), msg)
|
||||
})?,
|
||||
};
|
||||
|
||||
last_index = Some(final_index);
|
||||
|
||||
if let Some(used_pallet) = indices.insert(final_index, pallet.name.clone()) {
|
||||
let msg = format!(
|
||||
"Pallet indices are conflicting: Both pallets {} and {} are at index {}",
|
||||
used_pallet, pallet.name, final_index,
|
||||
);
|
||||
let mut err = syn::Error::new(used_pallet.span(), &msg);
|
||||
err.combine(syn::Error::new(pallet.name.span(), msg));
|
||||
return Err(err)
|
||||
}
|
||||
|
||||
if let Some(used_pallet) = names.insert(pallet.name.clone(), pallet.name.span()) {
|
||||
let msg = "Two pallets with the same name!";
|
||||
|
||||
let mut err = syn::Error::new(used_pallet, &msg);
|
||||
err.combine(syn::Error::new(pallet.name.span(), &msg));
|
||||
return Err(err)
|
||||
}
|
||||
|
||||
let mut pallet_parts = pallet.pallet_parts.expect("Checked above");
|
||||
|
||||
let available_parts =
|
||||
pallet_parts.iter().map(|part| part.keyword.name()).collect::<HashSet<_>>();
|
||||
|
||||
// Check parts are correctly specified
|
||||
match &pallet.specified_parts {
|
||||
SpecifiedParts::Exclude(parts) | SpecifiedParts::Use(parts) =>
|
||||
for part in parts {
|
||||
if !available_parts.contains(part.keyword.name()) {
|
||||
let msg = format!(
|
||||
"Invalid pallet part specified, the pallet `{}` doesn't have the \
|
||||
`{}` part. Available parts are: {}.",
|
||||
pallet.name,
|
||||
part.keyword.name(),
|
||||
pallet_parts.iter().fold(String::new(), |fold, part| {
|
||||
if fold.is_empty() {
|
||||
format!("`{}`", part.keyword.name())
|
||||
} else {
|
||||
format!("{}, `{}`", fold, part.keyword.name())
|
||||
}
|
||||
})
|
||||
);
|
||||
return Err(syn::Error::new(part.keyword.span(), msg))
|
||||
}
|
||||
},
|
||||
SpecifiedParts::All => (),
|
||||
}
|
||||
|
||||
// Set only specified parts.
|
||||
match pallet.specified_parts {
|
||||
SpecifiedParts::Exclude(excluded_parts) => pallet_parts.retain(|part| {
|
||||
!excluded_parts
|
||||
.iter()
|
||||
.any(|excluded_part| excluded_part.keyword.name() == part.keyword.name())
|
||||
}),
|
||||
SpecifiedParts::Use(used_parts) => pallet_parts.retain(|part| {
|
||||
used_parts.iter().any(|use_part| use_part.keyword.name() == part.keyword.name())
|
||||
}),
|
||||
SpecifiedParts::All => (),
|
||||
}
|
||||
|
||||
Ok(Pallet {
|
||||
name: pallet.name,
|
||||
index: final_index,
|
||||
path: pallet.path,
|
||||
instance: pallet.instance,
|
||||
pallet_parts,
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
Ok(PalletsConversion::Explicit(pallets))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user