From c1f4cd7ba0bf4a88b44bcb887d3a05240ccd2491 Mon Sep 17 00:00:00 2001 From: Guillaume Thiolliere Date: Sun, 31 Oct 2021 14:55:10 +0100 Subject: [PATCH] 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 * doc + fmt * Update frame/support/procedural/src/construct_runtime/parse.rs Co-authored-by: Keith Yeung * 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 * do refactor + fix ui tests * fmt * fix test * fix test * fix ui test * Apply suggestions from code review Co-authored-by: Keith Yeung * 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 Co-authored-by: Keith Yeung Co-authored-by: Shawn Tabrizi --- Cargo.lock | 20 + Cargo.toml | 3 +- bin/node/cli/src/chain_spec.rs | 2 + bin/node/runtime/src/lib.rs | 80 ++-- bin/node/testing/src/genesis.rs | 2 + frame/support/Cargo.toml | 1 + .../procedural/src/construct_runtime/mod.rs | 262 +++++++++----- .../procedural/src/construct_runtime/parse.rs | 341 ++++++++++++++++-- frame/support/procedural/src/lib.rs | 128 +++++-- .../procedural/src/match_and_insert.rs | 159 ++++++++ .../procedural/src/pallet/expand/mod.rs | 3 + .../src/pallet/expand/tt_default_parts.rs | 82 +++++ frame/support/src/lib.rs | 4 +- frame/support/test/compile_pass/Cargo.toml | 30 ++ frame/support/test/compile_pass/src/lib.rs | 92 +++++ .../both_use_and_excluded_parts.rs | 33 ++ .../both_use_and_excluded_parts.stderr | 28 ++ .../construct_runtime_ui/duplicate_exclude.rs | 13 + .../duplicate_exclude.stderr | 5 + .../construct_runtime_ui/exclude_missspell.rs | 13 + .../exclude_missspell.stderr | 5 + .../exclude_undefined_part.rs | 38 ++ .../exclude_undefined_part.stderr | 28 ++ .../invalid_module_details.stderr | 6 +- .../invalid_token_after_module.stderr | 2 +- .../old_unsupported_pallet_decl.rs | 26 ++ .../old_unsupported_pallet_decl.stderr | 31 ++ .../use_undefined_part.rs | 38 ++ .../use_undefined_part.stderr | 28 ++ frame/support/test/tests/pallet.rs | 35 +- frame/support/test/tests/pallet_instance.rs | 15 +- 31 files changed, 1335 insertions(+), 218 deletions(-) create mode 100644 frame/support/procedural/src/match_and_insert.rs create mode 100644 frame/support/procedural/src/pallet/expand/tt_default_parts.rs create mode 100644 frame/support/test/compile_pass/Cargo.toml create mode 100644 frame/support/test/compile_pass/src/lib.rs create mode 100644 frame/support/test/tests/construct_runtime_ui/both_use_and_excluded_parts.rs create mode 100644 frame/support/test/tests/construct_runtime_ui/both_use_and_excluded_parts.stderr create mode 100644 frame/support/test/tests/construct_runtime_ui/duplicate_exclude.rs create mode 100644 frame/support/test/tests/construct_runtime_ui/duplicate_exclude.stderr create mode 100644 frame/support/test/tests/construct_runtime_ui/exclude_missspell.rs create mode 100644 frame/support/test/tests/construct_runtime_ui/exclude_missspell.stderr create mode 100644 frame/support/test/tests/construct_runtime_ui/exclude_undefined_part.rs create mode 100644 frame/support/test/tests/construct_runtime_ui/exclude_undefined_part.stderr create mode 100644 frame/support/test/tests/construct_runtime_ui/old_unsupported_pallet_decl.rs create mode 100644 frame/support/test/tests/construct_runtime_ui/old_unsupported_pallet_decl.stderr create mode 100644 frame/support/test/tests/construct_runtime_ui/use_undefined_part.rs create mode 100644 frame/support/test/tests/construct_runtime_ui/use_undefined_part.stderr diff --git a/Cargo.lock b/Cargo.lock index c3cfaec19532e..84ad0c0a563c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2058,6 +2058,7 @@ dependencies = [ "sp-state-machine", "sp-std", "sp-tracing", + "tt-call", ] [[package]] @@ -2113,6 +2114,19 @@ dependencies = [ "trybuild", ] +[[package]] +name = "frame-support-test-compile-pass" +version = "4.0.0-dev" +dependencies = [ + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-runtime", + "sp-version", +] + [[package]] name = "frame-support-test-pallet" version = "4.0.0-dev" @@ -10803,6 +10817,12 @@ dependencies = [ "toml", ] +[[package]] +name = "tt-call" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e66dcbec4290c69dd03c57e76c2469ea5c7ce109c6dd4351c13055cf71ea055" + [[package]] name = "twox-hash" version = "1.6.1" diff --git a/Cargo.toml b/Cargo.toml index 71473a4bc5689..4a228203159eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -120,6 +120,7 @@ members = [ "frame/support/procedural/tools", "frame/support/procedural/tools/derive", "frame/support/test", + "frame/support/test/compile_pass", "frame/system", "frame/system/benchmarking", "frame/system/rpc/runtime-api", @@ -271,4 +272,4 @@ yamux = { opt-level = 3 } zeroize = { opt-level = 3 } [profile.release] # Substrate runtime requires unwinding. -panic = "unwind" \ No newline at end of file +panic = "unwind" diff --git a/bin/node/cli/src/chain_spec.rs b/bin/node/cli/src/chain_spec.rs index 8499c66e0c9dc..7b1ed90017c36 100644 --- a/bin/node/cli/src/chain_spec.rs +++ b/bin/node/cli/src/chain_spec.rs @@ -364,6 +364,8 @@ pub fn testnet_genesis( assets: Default::default(), gilt: Default::default(), transaction_storage: Default::default(), + scheduler: Default::default(), + transaction_payment: Default::default(), } } diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 9154ef6ca53df..c0ad9bb006c92 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1248,47 +1248,47 @@ construct_runtime!( NodeBlock = node_primitives::Block, UncheckedExtrinsic = UncheckedExtrinsic { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - Utility: pallet_utility::{Pallet, Call, Event}, - Babe: pallet_babe::{Pallet, Call, Storage, Config, ValidateUnsigned}, - Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, - Authorship: pallet_authorship::{Pallet, Call, Storage, Inherent}, - Indices: pallet_indices::{Pallet, Call, Storage, Config, Event}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - TransactionPayment: pallet_transaction_payment::{Pallet, Storage}, - ElectionProviderMultiPhase: pallet_election_provider_multi_phase::{Pallet, Call, Storage, Event, ValidateUnsigned}, - Staking: pallet_staking::{Pallet, Call, Config, Storage, Event}, - Session: pallet_session::{Pallet, Call, Storage, Event, Config}, - Democracy: pallet_democracy::{Pallet, Call, Storage, Config, Event}, - Council: pallet_collective::::{Pallet, Call, Storage, Origin, Event, Config}, - TechnicalCommittee: pallet_collective::::{Pallet, Call, Storage, Origin, Event, Config}, - Elections: pallet_elections_phragmen::{Pallet, Call, Storage, Event, Config}, - TechnicalMembership: pallet_membership::::{Pallet, Call, Storage, Event, Config}, - Grandpa: pallet_grandpa::{Pallet, Call, Storage, Config, Event, ValidateUnsigned}, - Treasury: pallet_treasury::{Pallet, Call, Storage, Config, Event}, - Contracts: pallet_contracts::{Pallet, Call, Storage, Event}, - Sudo: pallet_sudo::{Pallet, Call, Config, Storage, Event}, - ImOnline: pallet_im_online::{Pallet, Call, Storage, Event, ValidateUnsigned, Config}, - AuthorityDiscovery: pallet_authority_discovery::{Pallet, Config}, - Offences: pallet_offences::{Pallet, Storage, Event}, + System: frame_system, + Utility: pallet_utility, + Babe: pallet_babe, + Timestamp: pallet_timestamp, + Authorship: pallet_authorship, + Indices: pallet_indices, + Balances: pallet_balances, + TransactionPayment: pallet_transaction_payment, + ElectionProviderMultiPhase: pallet_election_provider_multi_phase, + Staking: pallet_staking, + Session: pallet_session, + Democracy: pallet_democracy, + Council: pallet_collective::, + TechnicalCommittee: pallet_collective::, + Elections: pallet_elections_phragmen, + TechnicalMembership: pallet_membership::, + Grandpa: pallet_grandpa, + Treasury: pallet_treasury, + Contracts: pallet_contracts, + Sudo: pallet_sudo, + ImOnline: pallet_im_online, + AuthorityDiscovery: pallet_authority_discovery, + Offences: pallet_offences, Historical: pallet_session_historical::{Pallet}, - RandomnessCollectiveFlip: pallet_randomness_collective_flip::{Pallet, Storage}, - Identity: pallet_identity::{Pallet, Call, Storage, Event}, - Society: pallet_society::{Pallet, Call, Storage, Event, Config}, - Recovery: pallet_recovery::{Pallet, Call, Storage, Event}, - Vesting: pallet_vesting::{Pallet, Call, Storage, Event, Config}, - Scheduler: pallet_scheduler::{Pallet, Call, Storage, Event}, - Proxy: pallet_proxy::{Pallet, Call, Storage, Event}, - Multisig: pallet_multisig::{Pallet, Call, Storage, Event}, - Bounties: pallet_bounties::{Pallet, Call, Storage, Event}, - Tips: pallet_tips::{Pallet, Call, Storage, Event}, - Assets: pallet_assets::{Pallet, Call, Storage, Event, Config}, - Mmr: pallet_mmr::{Pallet, Storage}, - Lottery: pallet_lottery::{Pallet, Call, Storage, Event}, - Gilt: pallet_gilt::{Pallet, Call, Storage, Event, Config}, - Uniques: pallet_uniques::{Pallet, Call, Storage, Event}, - TransactionStorage: pallet_transaction_storage::{Pallet, Call, Storage, Inherent, Config, Event}, - BagsList: pallet_bags_list::{Pallet, Call, Storage, Event}, + RandomnessCollectiveFlip: pallet_randomness_collective_flip, + Identity: pallet_identity, + Society: pallet_society, + Recovery: pallet_recovery, + Vesting: pallet_vesting, + Scheduler: pallet_scheduler, + Proxy: pallet_proxy, + Multisig: pallet_multisig, + Bounties: pallet_bounties, + Tips: pallet_tips, + Assets: pallet_assets, + Mmr: pallet_mmr, + Lottery: pallet_lottery, + Gilt: pallet_gilt, + Uniques: pallet_uniques, + TransactionStorage: pallet_transaction_storage, + BagsList: pallet_bags_list, } ); diff --git a/bin/node/testing/src/genesis.rs b/bin/node/testing/src/genesis.rs index 845227c5acee9..80399a6670e86 100644 --- a/bin/node/testing/src/genesis.rs +++ b/bin/node/testing/src/genesis.rs @@ -101,5 +101,7 @@ pub fn config_endowed( assets: Default::default(), gilt: Default::default(), transaction_storage: Default::default(), + scheduler: Default::default(), + transaction_payment: Default::default(), } } diff --git a/frame/support/Cargo.toml b/frame/support/Cargo.toml index edb0ecd6442e8..4bc64d8b8e73a 100644 --- a/frame/support/Cargo.toml +++ b/frame/support/Cargo.toml @@ -25,6 +25,7 @@ sp-core = { version = "4.0.0-dev", default-features = false, path = "../../primi sp-arithmetic = { version = "4.0.0-dev", default-features = false, path = "../../primitives/arithmetic" } sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../../primitives/inherents" } sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking" } +tt-call = "1.0.8" frame-support-procedural = { version = "4.0.0-dev", default-features = false, path = "./procedural" } paste = "1.0" once_cell = { version = "1", default-features = false, optional = true } diff --git a/frame/support/procedural/src/construct_runtime/mod.rs b/frame/support/procedural/src/construct_runtime/mod.rs index 863df34266591..4315d4278183a 100644 --- a/frame/support/procedural/src/construct_runtime/mod.rs +++ b/frame/support/procedural/src/construct_runtime/mod.rs @@ -15,115 +15,205 @@ // See the License for the specific language governing permissions and // limitations under the License. +//! Implementation of `construct_runtime`. +//! +//! `construct_runtime` implementation is recursive and can generate code which will call itself in +//! order to get all the pallet parts for each pallet. +//! +//! Pallets define their parts (`Call`, `Storage`, ..) either explicitly with the syntax +//! `::{Call, ...}` or implicitly. +//! +//! In case a pallet defines its parts implicitly, then the pallet must provide the +//! `tt_default_parts` macro. `construct_rutime` will generate some code which utilizes `tt_call` +//! to call the `tt_default_parts` macro of the pallet. `tt_default_parts` will then return the +//! default pallet parts as input tokens to the `match_and_replace` macro, which ultimately +//! generates a call to `construct_runtime` again, this time with all the pallet parts explicitly +//! defined. +//! +//! E.g. +//! ```ignore +//! construct_runtime!( +//! //... +//! { +//! System: frame_system = 0, // Implicit definition of parts +//! Balances: pallet_balances = 1, // Implicit definition of parts +//! } +//! ); +//! ``` +//! This call has some implicit pallet parts, thus it will expand to: +//! ```ignore +//! frame_support::tt_call! { +//! macro = [{ pallet_balances::tt_default_parts }] +//! ~~> frame_support::match_and_insert! { +//! target = [{ +//! frame_support::tt_call! { +//! macro = [{ frame_system::tt_default_parts }] +//! ~~> frame_support::match_and_insert! { +//! target = [{ +//! construct_runtime!( +//! //... +//! { +//! System: frame_system = 0, +//! Balances: pallet_balances = 1, +//! } +//! ); +//! }] +//! pattern = [{ System: frame_system }] +//! } +//! } +//! }] +//! pattern = [{ Balances: pallet_balances }] +//! } +//! } +//! ``` +//! `tt_default_parts` must be defined. It returns the pallet parts inside some tokens, and +//! then `tt_call` will pipe the returned pallet parts into the input of `match_and_insert`. +//! Thus `match_and_insert` will initially receive the following inputs: +//! ```ignore +//! frame_support::match_and_insert! { +//! target = [{ +//! frame_support::match_and_insert! { +//! target = [{ +//! construct_runtime!( +//! //... +//! { +//! System: frame_system = 0, +//! Balances: pallet_balances = 1, +//! } +//! ) +//! }] +//! pattern = [{ System: frame_system }] +//! tokens = [{ ::{Pallet, Call} }] +//! }] +//! pattern = [{ Balances: pallet_balances }] +//! tokens = [{ ::{Pallet, Call} }] +//! } +//! ``` +//! After dealing with `pallet_balances`, the inner `match_and_insert` will expand to: +//! ```ignore +//! frame_support::match_and_insert! { +//! target = [{ +//! construct_runtime!( +//! //... +//! { +//! System: frame_system = 0, // Implicit definition of parts +//! Balances: pallet_balances::{Pallet, Call} = 1, // Explicit definition of parts +//! } +//! ) +//! }] +//! pattern = [{ System: frame_system }] +//! tokens = [{ ::{Pallet, Call} }] +//! } +//! ``` +//! Which will then finally expand to the following: +//! ```ignore +//! construct_runtime!( +//! //... +//! { +//! System: frame_system::{Pallet, Call}, +//! Balances: pallet_balances::{Pallet, Call}, +//! } +//! ) +//! ``` +//! This call has no implicit pallet parts, thus it will expand to the runtime construction: +//! ```ignore +//! pub struct Runtime { ... } +//! pub struct Call { ... } +//! impl Call ... +//! pub enum Origin { ... } +//! ... +//! ``` +//! +//! Visualizing the entire flow of `construct_runtime!`, it would look like the following: +//! +//! ```ignore +//! +--------------------+ +---------------------+ +-------------------+ +//! | | | (defined in pallet) | | | +//! | construct_runtime! | --> | tt_default_parts! | --> | match_and_insert! | +//! | w/ no pallet parts | | | | | +//! +--------------------+ +---------------------+ +-------------------+ +//! +//! +--------------------+ +//! | | +//! --> | construct_runtime! | +//! | w/ pallet parts | +//! +--------------------+ +//! ``` + mod expand; mod parse; use frame_support_procedural_tools::{ - generate_crate_access, generate_hidden_includes, syn_ext as ext, + generate_crate_access, generate_crate_access_2018, generate_hidden_includes, +}; +use parse::{ + ExplicitRuntimeDeclaration, ImplicitRuntimeDeclaration, Pallet, RuntimeDeclaration, + WhereSection, }; -use parse::{PalletDeclaration, PalletPart, PalletPath, RuntimeDefinition, WhereSection}; use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; use quote::quote; -use std::collections::HashMap; use syn::{Ident, Result}; /// The fixed name of the system pallet. const SYSTEM_PALLET_NAME: &str = "System"; -/// The complete definition of a pallet with the resulting fixed index. -#[derive(Debug, Clone)] -pub struct Pallet { - pub name: Ident, - pub index: u8, - pub path: PalletPath, - pub instance: Option, - pub pallet_parts: Vec, -} - -impl Pallet { - /// Get resolved pallet parts - fn pallet_parts(&self) -> &[PalletPart] { - &self.pallet_parts - } +/// Implementation of `construct_runtime` macro. Either expand to some code which will call +/// `construct_runtime` again, or expand to the final runtime definition. +pub fn construct_runtime(input: TokenStream) -> TokenStream { + let input_copy = input.clone(); + let definition = syn::parse_macro_input!(input as RuntimeDeclaration); - /// Find matching parts - fn find_part(&self, name: &str) -> Option<&PalletPart> { - self.pallet_parts.iter().find(|part| part.name() == name) - } + let res = match definition { + RuntimeDeclaration::Implicit(implicit_def) => + construct_runtime_intermediary_expansion(input_copy.into(), implicit_def), + RuntimeDeclaration::Explicit(explicit_decl) => + construct_runtime_final_expansion(explicit_decl), + }; - /// Return whether pallet contains part - fn exists_part(&self, name: &str) -> bool { - self.find_part(name).is_some() - } + res.unwrap_or_else(|e| e.to_compile_error()).into() } -/// Convert from the parsed pallet to their final information. -/// 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 complete_pallets(decl: impl Iterator) -> syn::Result> { - let mut indices = HashMap::new(); - let mut last_index: Option = None; - let mut names = HashMap::new(); - - decl.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) - } - - Ok(Pallet { - name: pallet.name, - index: final_index, - path: pallet.path, - instance: pallet.instance, - pallet_parts: pallet.pallet_parts, - }) - }) - .collect() -} +/// When some pallet have implicit parts definition then the macro will expand into a macro call to +/// `construct_runtime_args` of each pallets, see root documentation. +fn construct_runtime_intermediary_expansion( + input: TokenStream2, + definition: ImplicitRuntimeDeclaration, +) -> Result { + let frame_support = generate_crate_access_2018("frame-support")?; + let mut expansion = quote::quote!( + #frame_support::construct_runtime! { #input } + ); + for pallet in definition.pallets.iter().filter(|pallet| pallet.pallet_parts.is_none()) { + let pallet_path = &pallet.path; + let pallet_name = &pallet.name; + let pallet_instance = pallet.instance.as_ref().map(|instance| quote::quote!(::<#instance>)); + expansion = quote::quote!( + #frame_support::tt_call! { + macro = [{ #pallet_path::tt_default_parts }] + frame_support = [{ #frame_support }] + ~~> #frame_support::match_and_insert! { + target = [{ #expansion }] + pattern = [{ #pallet_name: #pallet_path #pallet_instance }] + } + } + ); + } -pub fn construct_runtime(input: TokenStream) -> TokenStream { - let definition = syn::parse_macro_input!(input as RuntimeDefinition); - construct_runtime_parsed(definition) - .unwrap_or_else(|e| e.to_compile_error()) - .into() + Ok(expansion.into()) } -fn construct_runtime_parsed(definition: RuntimeDefinition) -> Result { - let RuntimeDefinition { +/// All pallets have explicit definition of parts, this will expand to the runtime declaration. +fn construct_runtime_final_expansion( + definition: ExplicitRuntimeDeclaration, +) -> Result { + let ExplicitRuntimeDeclaration { name, - where_section: WhereSection { block, node_block, unchecked_extrinsic, .. }, - pallets: - ext::Braces { content: ext::Punctuated { inner: pallets, .. }, token: pallets_token }, - .. + where_section: WhereSection { block, node_block, unchecked_extrinsic }, + pallets, + pallets_token, } = definition; - let pallets = complete_pallets(pallets.into_iter())?; - let hidden_crate_name = "construct_runtime"; let scrate = generate_crate_access(&hidden_crate_name, "frame-support"); let scrate_decl = generate_hidden_includes(&hidden_crate_name, "frame-support"); diff --git a/frame/support/procedural/src/construct_runtime/parse.rs b/frame/support/procedural/src/construct_runtime/parse.rs index a0ec6dfa5803e..f80b7b1ac554c 100644 --- a/frame/support/procedural/src/construct_runtime/parse.rs +++ b/frame/support/procedural/src/construct_runtime/parse.rs @@ -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 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: Vec, +} + +/// Declaration of a runtime with all pallet having explicit declaration of parts. #[derive(Debug)] -pub struct RuntimeDefinition { - pub visibility_token: Token![pub], - pub enum_token: Token![enum], +pub struct ExplicitRuntimeDeclaration { pub name: Ident, pub where_section: WhereSection, - pub pallets: ext::Braces>, + pub pallets: Vec, + pub pallets_token: token::Brace, } -impl Parse for RuntimeDefinition { +impl Parse for RuntimeDeclaration { fn parse(input: ParseStream) -> Result { - Ok(Self { - visibility_token: input.parse()?, - enum_token: input.parse()?, - name: input.parse()?, - where_section: input.parse()?, - pallets: input.parse()?, - }) + input.parse::()?; + input.parse::()?; + let name = input.parse::()?; + let where_section = input.parse()?; + let pallets = + input.parse::>>()?; + 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, + /// 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::`. pub instance: Option, - pub pallet_parts: Vec, + /// The declared pallet parts, + /// e.g. `Some([Pallet, Call])` for `System: system::{Pallet, Call}` + /// or `None` for `System: system`. + pub pallet_parts: Option>, + /// 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), + /// Use only the specified pallet parts. + Use(Vec), + /// 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::()?; let index = input.parse::()?; let index = index.base10_parse::()?; 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 { - 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(); - } - - if !lookahead.peek(token::Brace) && !lookahead.peek(Token![<]) { - return Err(lookahead.error()) + while input.peek(Token![::]) && input.peek3(Ident) { + input.parse::()?; + let ident = input.parse::()?; + res.inner.segments.push(ident.into()); } - - 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 { + 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> { + let pallet_parts: ext::Braces> = + 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::`. + pub instance: Option, + /// The pallet parts to use for the pallet. + pub pallet_parts: Vec, +} + +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), + Explicit(Vec), +} + +/// 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) -> syn::Result { + 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 = 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::>(); + + // 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::>>()?; + + Ok(PalletsConversion::Explicit(pallets)) +} diff --git a/frame/support/procedural/src/lib.rs b/frame/support/procedural/src/lib.rs index 6987fc49b9a8c..d01bbf6ace526 100644 --- a/frame/support/procedural/src/lib.rs +++ b/frame/support/procedural/src/lib.rs @@ -26,6 +26,7 @@ mod debug_no_bound; mod default_no_bound; mod dummy_part_checker; mod key_prefix; +mod match_and_insert; mod pallet; mod partial_eq_no_bound; mod storage; @@ -297,52 +298,91 @@ pub fn decl_storage(input: TokenStream) -> TokenStream { /// /// # Example: /// -/// ```nocompile +/// ```ignore /// construct_runtime!( /// pub enum Runtime where /// Block = Block, /// NodeBlock = node::Block, /// UncheckedExtrinsic = UncheckedExtrinsic /// { -/// System: system::{Pallet, Call, Event, Config} = 0, -/// Test: test::{Pallet, Call} = 1, -/// Test2: test_with_long_module::{Pallet, Event}, +/// System: frame_system::{Pallet, Call, Event, Config} = 0, +/// Test: path::to::test::{Pallet, Call} = 1, /// /// // Pallets with instances -/// Test3_Instance1: test3::::{Pallet, Call, Storage, Event, Config, Origin}, -/// Test3_DefaultInstance: test3::{Pallet, Call, Storage, Event, Config, Origin} = 4, +/// Test2_Instance1: test2::::{Pallet, Call, Storage, Event, Config, Origin}, +/// Test2_DefaultInstance: test2::{Pallet, Call, Storage, Event, Config, Origin} = 4, +/// +/// // Pallets declared with `pallet` attribute macro: no need to define the parts +/// Test3_Instance1: test3::, +/// Test3_DefaultInstance: test3, +/// +/// // with `exclude_parts` keyword some part can be excluded. +/// Test4_Instance1: test4:: exclude_parts { Call, Origin }, +/// Test4_DefaultInstance: test4 exclude_parts { Storage }, +/// +/// // with `use_parts` keyword, a subset of the pallet parts can be specified. +/// Test4_Instance1: test4:: use_parts { Pallet, Call}, +/// Test4_DefaultInstance: test4 use_parts { Pallet }, /// } /// ) /// ``` /// -/// The identifier `System` is the name of the pallet and the lower case identifier `system` is the -/// name of the Rust module/crate for this Substrate pallet. The identifiers between the braces are -/// the pallet parts provided by the pallet. It is important to list these parts here to export -/// them correctly in the metadata or to make the pallet usable in the runtime. +/// Each pallet is declared as such: +/// * `Identifier`: name given to the pallet that uniquely identifies it. /// -/// We provide support for the following module parts in a pallet: +/// * `:`: colon separator /// -/// - `Pallet` - Required for all pallets -/// - `Call` - If the pallet has callable functions -/// - `Storage` - If the pallet uses storage -/// - `Event` or `Event` (if the event is generic) - If the pallet emits events -/// - `Origin` or `Origin` (if the origin is generic) - If the pallet has instanciable origins -/// - `Config` or `Config` (if the config is generic) - If the pallet builds the genesis storage -/// with `GenesisConfig` -/// - `Inherent` - If the pallet provides/can check inherents. -/// - `ValidateUnsigned` - If the pallet validates unsigned extrinsics. +/// * `path::to::pallet`: identifiers separated by colons which declare the path to a pallet +/// definition. /// -/// `= $n` is an optional part allowing to define at which index the pallet variants in -/// `OriginCaller`, `Call` and `Event` are encoded, and to define the ModuleToIndex value. +/// * `::` optional: specify the instance of the pallet to use. If not specified it will +/// use the default instance (or the only instance in case of non-instantiable pallets). /// -/// if `= $n` is not given, then index is resolved same as fieldless enum in Rust -/// (i.e. incrementedly from previous index): -/// ```nocompile -/// pallet1 .. = 2, -/// pallet2 .., // Here pallet2 is given index 3 -/// pallet3 .. = 0, -/// pallet4 .., // Here pallet4 is given index 1 -/// ``` +/// * `::{ Part1, Part2, .. }` optional if pallet declared with `frame_support::pallet`: Comma +/// separated parts declared with their generic. If a pallet is declared with +/// `frame_support::pallet` macro then the parts can be automatically derived if not explicitly +/// provided. We provide support for the following module parts in a pallet: +/// +/// - `Pallet` - Required for all pallets +/// - `Call` - If the pallet has callable functions +/// - `Storage` - If the pallet uses storage +/// - `Event` or `Event` (if the event is generic) - If the pallet emits events +/// - `Origin` or `Origin` (if the origin is generic) - If the pallet has instanciable origins +/// - `Config` or `Config` (if the config is generic) - If the pallet builds the genesis +/// storage with `GenesisConfig` +/// - `Inherent` - If the pallet provides/can check inherents. +/// - `ValidateUnsigned` - If the pallet validates unsigned extrinsics. +/// +/// It is important to list these parts here to export them correctly in the metadata or to make +/// the pallet usable in the runtime. +/// +/// * `exclude_parts { Part1, Part2 }` optional: comma separated parts without generics. I.e. one of +/// `Pallet`, `Call`, `Storage`, `Event`, `Origin`, `Config`, `Inherent`, `ValidateUnsigned`. It +/// is incompatible with `use_parts`. This specifies the part to exclude. In order to select +/// subset of the pallet parts. +/// +/// For example excluding the part `Call` can be useful if the runtime doesn't want to make the +/// pallet calls available. +/// +/// * `use_parts { Part1, Part2 }` optional: comma separated parts without generics. I.e. one of +/// `Pallet`, `Call`, `Storage`, `Event`, `Origin`, `Config`, `Inherent`, `ValidateUnsigned`. It +/// is incompatible with `exclude_parts`. This specifies the part to use. In order to select a +/// subset of the pallet parts. +/// +/// For example not using the part `Call` can be useful if the runtime doesn't want to make the +/// pallet calls available. +/// +/// * `= $n` optional: number to define at which index the pallet variants in `OriginCaller`, `Call` +/// and `Event` are encoded, and to define the ModuleToIndex value. +/// +/// if `= $n` is not given, then index is resolved in the same way as fieldless enum in Rust +/// (i.e. incrementedly from previous index): +/// ```nocompile +/// pallet1 .. = 2, +/// pallet2 .., // Here pallet2 is given index 3 +/// pallet3 .. = 0, +/// pallet4 .., // Here pallet4 is given index 1 +/// ``` /// /// # Note /// @@ -352,8 +392,8 @@ pub fn decl_storage(input: TokenStream) -> TokenStream { /// /// # Type definitions /// -/// * The macro generates a type alias for each pallet to their `Module` (or `Pallet`). E.g. `type -/// System = frame_system::Pallet` +/// * The macro generates a type alias for each pallet to their `Pallet`. E.g. `type System = +/// frame_system::Pallet` #[proc_macro] pub fn construct_runtime(input: TokenStream) -> TokenStream { construct_runtime::construct_runtime(input) @@ -498,3 +538,27 @@ pub fn impl_key_prefix_for_tuples(input: TokenStream) -> TokenStream { pub fn __generate_dummy_part_checker(input: TokenStream) -> TokenStream { dummy_part_checker::generate_dummy_part_checker(input) } + +/// Macro that inserts some tokens after the first match of some pattern. +/// +/// # Example: +/// +/// ```nocompile +/// match_and_insert!( +/// target = [{ Some content with { at some point match pattern } other match pattern are ignored }] +/// pattern = [{ match pattern }] // the match pattern cannot contain any group: `[]`, `()`, `{}` +/// // can relax this constraint, but will require modifying the match logic in code +/// tokens = [{ expansion tokens }] // content inside braces can be anything including groups +/// ); +/// ``` +/// +/// will generate: +/// +/// ```nocompile +/// Some content with { at some point match pattern expansion tokens } other match patterns are +/// ignored +/// ``` +#[proc_macro] +pub fn match_and_insert(input: TokenStream) -> TokenStream { + match_and_insert::match_and_insert(input) +} diff --git a/frame/support/procedural/src/match_and_insert.rs b/frame/support/procedural/src/match_and_insert.rs new file mode 100644 index 0000000000000..4ffc596e6dca0 --- /dev/null +++ b/frame/support/procedural/src/match_and_insert.rs @@ -0,0 +1,159 @@ +// This file is part of Substrate. + +// Copyright (C) 2021 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 of the `match_and_insert` macro. + +use proc_macro2::{Group, Span, TokenStream, TokenTree}; +use std::iter::once; +use syn::spanned::Spanned; + +mod keyword { + syn::custom_keyword!(target); + syn::custom_keyword!(pattern); + syn::custom_keyword!(tokens); +} + +pub fn match_and_insert(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let MatchAndInsertDef { pattern, tokens, target } = + syn::parse_macro_input!(input as MatchAndInsertDef); + + match expand_in_stream(&pattern, &mut Some(tokens), target) { + Ok(stream) => stream.into(), + Err(err) => err.to_compile_error().into(), + } +} + +struct MatchAndInsertDef { + // Token stream to search and insert tokens into. + target: TokenStream, + // Pattern to match against, this is ensured to have no TokenTree::Group nor TokenTree::Literal + // (i.e. contains only Punct or Ident), and not being empty. + pattern: Vec, + // Token stream to insert after the match pattern. + tokens: TokenStream, +} + +impl syn::parse::Parse for MatchAndInsertDef { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let mut target; + let _ = input.parse::()?; + let _ = input.parse::()?; + let _replace_with_bracket: syn::token::Bracket = syn::bracketed!(target in input); + let _replace_with_brace: syn::token::Brace = syn::braced!(target in target); + let target = target.parse()?; + + let mut pattern; + let _ = input.parse::()?; + let _ = input.parse::()?; + let _replace_with_bracket: syn::token::Bracket = syn::bracketed!(pattern in input); + let _replace_with_brace: syn::token::Brace = syn::braced!(pattern in pattern); + let pattern = pattern.parse::()?.into_iter().collect::>(); + + if let Some(t) = pattern.iter().find(|t| matches!(t, TokenTree::Group(_))) { + return Err(syn::Error::new(t.span(), "Unexpected group token tree")) + } + if let Some(t) = pattern.iter().find(|t| matches!(t, TokenTree::Literal(_))) { + return Err(syn::Error::new(t.span(), "Unexpected literal token tree")) + } + + if pattern.is_empty() { + return Err(syn::Error::new(Span::call_site(), "empty match pattern is invalid")) + } + + let mut tokens; + let _ = input.parse::()?; + let _ = input.parse::()?; + let _replace_with_bracket: syn::token::Bracket = syn::bracketed!(tokens in input); + let _replace_with_brace: syn::token::Brace = syn::braced!(tokens in tokens); + let tokens = tokens.parse()?; + + Ok(Self { tokens, pattern, target }) + } +} + +// Insert `tokens` after the first matching `pattern`. +// `tokens` must be some (Option is used for internal simplification). +// `pattern` must not be empty and should only contain Ident or Punct. +fn expand_in_stream( + pattern: &[TokenTree], + tokens: &mut Option, + stream: TokenStream, +) -> syn::Result { + assert!( + tokens.is_some(), + "`tokens` must be some, Option is used because `tokens` is used only once" + ); + assert!( + !pattern.is_empty(), + "`pattern` must not be empty, otherwise there is nothing to match against" + ); + + let stream_span = stream.span(); + let mut stream = stream.into_iter(); + let mut extended = TokenStream::new(); + let mut match_cursor = 0; + + while let Some(token) = stream.next() { + match token { + TokenTree::Group(group) => { + match_cursor = 0; + let group_stream = group.stream(); + match expand_in_stream(pattern, tokens, group_stream) { + Ok(s) => { + extended.extend(once(TokenTree::Group(Group::new(group.delimiter(), s)))); + extended.extend(stream); + return Ok(extended) + }, + Err(_) => { + extended.extend(once(TokenTree::Group(group))); + }, + } + }, + other => { + advance_match_cursor(&other, pattern, &mut match_cursor); + + extended.extend(once(other)); + + if match_cursor == pattern.len() { + extended + .extend(once(tokens.take().expect("tokens is used to replace only once"))); + extended.extend(stream); + return Ok(extended) + } + }, + } + } + // if we reach this point, it means the stream is empty and we haven't found a matching pattern + let msg = format!("Cannot find pattern `{:?}` in given token stream", pattern); + Err(syn::Error::new(stream_span, msg)) +} + +fn advance_match_cursor(other: &TokenTree, pattern: &[TokenTree], match_cursor: &mut usize) { + use TokenTree::{Ident, Punct}; + + let does_match_other_pattern = match (other, &pattern[*match_cursor]) { + (Ident(i1), Ident(i2)) => i1 == i2, + (Punct(p1), Punct(p2)) => p1.as_char() == p2.as_char(), + _ => false, + }; + + if does_match_other_pattern { + *match_cursor += 1; + } else { + *match_cursor = 0; + } +} diff --git a/frame/support/procedural/src/pallet/expand/mod.rs b/frame/support/procedural/src/pallet/expand/mod.rs index 083ad61fc5239..21acd3c0dd32e 100644 --- a/frame/support/procedural/src/pallet/expand/mod.rs +++ b/frame/support/procedural/src/pallet/expand/mod.rs @@ -29,6 +29,7 @@ mod origin; mod pallet_struct; mod storage; mod store_trait; +mod tt_default_parts; mod type_value; mod validate_unsigned; @@ -67,6 +68,7 @@ pub fn expand(mut def: Def) -> proc_macro2::TokenStream { let type_values = type_value::expand_type_values(&mut def); let origins = origin::expand_origins(&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); if get_doc_literals(&def.item.attrs).is_empty() { def.item.attrs.push(syn::parse_quote!( @@ -96,6 +98,7 @@ pub fn expand(mut def: Def) -> proc_macro2::TokenStream { #type_values #origins #validate_unsigned + #tt_default_parts ); def.item diff --git a/frame/support/procedural/src/pallet/expand/tt_default_parts.rs b/frame/support/procedural/src/pallet/expand/tt_default_parts.rs new file mode 100644 index 0000000000000..cfab7982bfdc9 --- /dev/null +++ b/frame/support/procedural/src/pallet/expand/tt_default_parts.rs @@ -0,0 +1,82 @@ +// This file is part of Substrate. + +// Copyright (C) 2021 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::{pallet::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 call_part = def.call.as_ref().map(|_| quote::quote!(Call,)); + + 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!( )); + quote::quote!( Event #gen , ) + }); + + let origin_part = def.origin.as_ref().map(|origin| { + let gen = origin.is_generic.then(|| quote::quote!( )); + 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!( )); + 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,)); + + 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 pallet 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 frame_support argument here, because this macro gets expanded on the + // crate that called the `construct_runtime!` macro, and said crate may have renamed + // frame-support, and so we need to pass in the frame-support path that said crate + // recognizes. + #[macro_export] + #[doc(hidden)] + macro_rules! #default_parts_unique_id { + { + $caller:tt + frame_support = [{ $($frame_support:ident)::* }] + } => { + $($frame_support)*::tt_return! { + $caller + tokens = [{ + ::{ + Pallet, #call_part #storage_part #event_part #origin_part #config_part + #inherent_part #validate_unsigned_part + } + }] + } + }; + } + + pub use #default_parts_unique_id as tt_default_parts; + ) +} diff --git a/frame/support/src/lib.rs b/frame/support/src/lib.rs index 1b93b5fb5975e..d81300a404c4f 100644 --- a/frame/support/src/lib.rs +++ b/frame/support/src/lib.rs @@ -50,6 +50,8 @@ pub use sp_runtime::RuntimeDebug; pub use sp_state_machine::BasicExternalities; #[doc(hidden)] pub use sp_std; +#[doc(hidden)] +pub use tt_call::*; #[macro_use] pub mod dispatch; @@ -573,7 +575,7 @@ pub fn debug(data: &impl sp_std::fmt::Debug) { #[doc(inline)] pub use frame_support_procedural::{ - construct_runtime, decl_storage, transactional, RuntimeDebugNoBound, + construct_runtime, decl_storage, match_and_insert, transactional, RuntimeDebugNoBound, }; #[doc(hidden)] diff --git a/frame/support/test/compile_pass/Cargo.toml b/frame/support/test/compile_pass/Cargo.toml new file mode 100644 index 0000000000000..bca833200d444 --- /dev/null +++ b/frame/support/test/compile_pass/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "frame-support-test-compile-pass" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2018" +license = "Apache-2.0" +publish = false +homepage = "https://substrate.dev" +repository = "https://github.com/paritytech/substrate/" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } +scale-info = { version = "1.0", default-features = false, features = ["derive"] } +sp-core = { version = "4.0.0-dev", default-features = false, path = "../../../../primitives/core" } +sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../../../primitives/runtime" } +sp-version = { version = "4.0.0-dev", default-features = false, path = "../../../../primitives/version" } +support = { package = "frame-support", version = "4.0.0-dev", default-features = false, path = "../../" } +system = { package = "frame-system", version = "4.0.0-dev", default-features = false, path = "../../../system" } + +[features] +default = ["std"] +std = [ + "codec/std", + "scale-info/std", + "support/std", + "system/std", +] diff --git a/frame/support/test/compile_pass/src/lib.rs b/frame/support/test/compile_pass/src/lib.rs new file mode 100644 index 0000000000000..17ba40574adf7 --- /dev/null +++ b/frame/support/test/compile_pass/src/lib.rs @@ -0,0 +1,92 @@ +// This file is part of Substrate. + +// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#![cfg_attr(not(feature = "std"), no_std)] +// `construct_runtime!` does a lot of recursion and requires us to increase the limit to 256. +#![recursion_limit = "256"] +//! This crate tests that `construct_runtime!` expands the pallet parts +//! correctly even when frame-support is renamed in Cargo.toml + +use sp_core::{sr25519, H256}; +use sp_runtime::{ + create_runtime_str, generic, + traits::{BlakeTwo256, IdentityLookup, Verify}, +}; +use sp_version::RuntimeVersion; +use support::{construct_runtime, parameter_types}; + +pub const VERSION: RuntimeVersion = RuntimeVersion { + spec_name: create_runtime_str!("frame-support-test-compile-pass"), + impl_name: create_runtime_str!("substrate-frame-support-test-compile-pass-runtime"), + authoring_version: 0, + spec_version: 0, + impl_version: 0, + apis: sp_version::create_apis_vec!([]), + transaction_version: 0, +}; + +pub type Signature = sr25519::Signature; +pub type AccountId = ::Signer; +pub type BlockNumber = u64; +pub type Index = u64; + +parameter_types! { + pub const BlockHashCount: BlockNumber = 2400; + pub const Version: RuntimeVersion = VERSION; + pub const SS58Prefix: u8 = 0; +} + +impl system::Config for Runtime { + type BaseCallFilter = support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type Index = u128; + type Hash = H256; + type Hashing = BlakeTwo256; + type Header = Header; + type Lookup = IdentityLookup; + type BlockHashCount = BlockHashCount; + type Version = Version; + type AccountData = (); + type Origin = Origin; + type BlockNumber = BlockNumber; + type AccountId = AccountId; + type Event = Event; + type PalletInfo = PalletInfo; + type Call = Call; + type DbWeight = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type OnSetCode = (); + type SystemWeightInfo = (); + type SS58Prefix = SS58Prefix; +} + +pub type Header = generic::Header; +pub type Block = generic::Block; +pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; + +construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic + { + System: system, + } +); diff --git a/frame/support/test/tests/construct_runtime_ui/both_use_and_excluded_parts.rs b/frame/support/test/tests/construct_runtime_ui/both_use_and_excluded_parts.rs new file mode 100644 index 0000000000000..98cd1f197f619 --- /dev/null +++ b/frame/support/test/tests/construct_runtime_ui/both_use_and_excluded_parts.rs @@ -0,0 +1,33 @@ +use frame_support::construct_runtime; +use sp_runtime::{generic, traits::BlakeTwo256}; +use sp_core::sr25519; + +#[frame_support::pallet] +mod pallet { + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(_); +} + +pub type Signature = sr25519::Signature; +pub type BlockNumber = u64; +pub type Header = generic::Header; +pub type Block = generic::Block; +pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; + +impl pallet::Config for Runtime {} + +construct_runtime! { + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic + { + System: system::{Pallet, Call, Storage, Config, Event}, + Pallet: pallet exclude_parts { Pallet } use_parts { Pallet }, + } +} + +fn main() {} diff --git a/frame/support/test/tests/construct_runtime_ui/both_use_and_excluded_parts.stderr b/frame/support/test/tests/construct_runtime_ui/both_use_and_excluded_parts.stderr new file mode 100644 index 0000000000000..608d57d6a97fc --- /dev/null +++ b/frame/support/test/tests/construct_runtime_ui/both_use_and_excluded_parts.stderr @@ -0,0 +1,28 @@ +error: Unexpected tokens, expected one of `=`, `,` + --> $DIR/both_use_and_excluded_parts.rs:29:43 + | +29 | Pallet: pallet exclude_parts { Pallet } use_parts { Pallet }, + | ^^^^^^^^^ + +error[E0412]: cannot find type `Call` in this scope + --> $DIR/both_use_and_excluded_parts.rs:18:64 + | +18 | pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; + | ^^^^ not found in this scope + | +help: consider importing one of these items + | +1 | use crate::pallet::Call; + | +1 | use frame_support_test::Call; + | +1 | use frame_system::Call; + | +1 | use test_pallet::Call; + | + +error[E0412]: cannot find type `Runtime` in this scope + --> $DIR/both_use_and_excluded_parts.rs:20:25 + | +20 | impl pallet::Config for Runtime {} + | ^^^^^^^ not found in this scope diff --git a/frame/support/test/tests/construct_runtime_ui/duplicate_exclude.rs b/frame/support/test/tests/construct_runtime_ui/duplicate_exclude.rs new file mode 100644 index 0000000000000..6d21c2a6e170a --- /dev/null +++ b/frame/support/test/tests/construct_runtime_ui/duplicate_exclude.rs @@ -0,0 +1,13 @@ +use frame_support::construct_runtime; + +construct_runtime! { + pub enum Runtime where + UncheckedExtrinsic = UncheckedExtrinsic, + Block = Block, + NodeBlock = Block, + { + System: frame_system exclude_parts { Call, Call }, + } +} + +fn main() {} diff --git a/frame/support/test/tests/construct_runtime_ui/duplicate_exclude.stderr b/frame/support/test/tests/construct_runtime_ui/duplicate_exclude.stderr new file mode 100644 index 0000000000000..75de56076528b --- /dev/null +++ b/frame/support/test/tests/construct_runtime_ui/duplicate_exclude.stderr @@ -0,0 +1,5 @@ +error: `Call` was already declared before. Please remove the duplicate declaration + --> $DIR/duplicate_exclude.rs:9:46 + | +9 | System: frame_system exclude_parts { Call, Call }, + | ^^^^ diff --git a/frame/support/test/tests/construct_runtime_ui/exclude_missspell.rs b/frame/support/test/tests/construct_runtime_ui/exclude_missspell.rs new file mode 100644 index 0000000000000..16cbf1e82cf89 --- /dev/null +++ b/frame/support/test/tests/construct_runtime_ui/exclude_missspell.rs @@ -0,0 +1,13 @@ +use frame_support::construct_runtime; + +construct_runtime! { + pub enum Runtime where + UncheckedExtrinsic = UncheckedExtrinsic, + Block = Block, + NodeBlock = Block, + { + System: frame_system exclude_part { Call }, + } +} + +fn main() {} diff --git a/frame/support/test/tests/construct_runtime_ui/exclude_missspell.stderr b/frame/support/test/tests/construct_runtime_ui/exclude_missspell.stderr new file mode 100644 index 0000000000000..82e6aa6c8e308 --- /dev/null +++ b/frame/support/test/tests/construct_runtime_ui/exclude_missspell.stderr @@ -0,0 +1,5 @@ +error: Unexpected tokens, expected one of `::$ident` `::{`, `exclude_parts`, `use_parts`, `=`, `,` + --> $DIR/exclude_missspell.rs:9:24 + | +9 | System: frame_system exclude_part { Call }, + | ^^^^^^^^^^^^ diff --git a/frame/support/test/tests/construct_runtime_ui/exclude_undefined_part.rs b/frame/support/test/tests/construct_runtime_ui/exclude_undefined_part.rs new file mode 100644 index 0000000000000..51be7e30bd3eb --- /dev/null +++ b/frame/support/test/tests/construct_runtime_ui/exclude_undefined_part.rs @@ -0,0 +1,38 @@ +use frame_support::construct_runtime; +use sp_runtime::{generic, traits::BlakeTwo256}; +use sp_core::sr25519; + +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::storage] + type Foo = StorageValue; +} + +pub type Signature = sr25519::Signature; +pub type BlockNumber = u64; +pub type Header = generic::Header; +pub type Block = generic::Block; +pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; + +impl pallet::Config for Runtime {} + +construct_runtime! { + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic + { + System: system::{Pallet, Call, Storage, Config, Event}, + Pallet: pallet exclude_parts { Call }, + } +} + +fn main() {} diff --git a/frame/support/test/tests/construct_runtime_ui/exclude_undefined_part.stderr b/frame/support/test/tests/construct_runtime_ui/exclude_undefined_part.stderr new file mode 100644 index 0000000000000..4e31cfb75c074 --- /dev/null +++ b/frame/support/test/tests/construct_runtime_ui/exclude_undefined_part.stderr @@ -0,0 +1,28 @@ +error: Invalid pallet part specified, the pallet `Pallet` doesn't have the `Call` part. Available parts are: `Pallet`, `Storage`. + --> $DIR/exclude_undefined_part.rs:34:34 + | +34 | Pallet: pallet exclude_parts { Call }, + | ^^^^ + +error[E0412]: cannot find type `Call` in this scope + --> $DIR/exclude_undefined_part.rs:23:64 + | +23 | pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; + | ^^^^ not found in this scope + | +help: consider importing one of these items + | +1 | use crate::pallet::Call; + | +1 | use frame_support_test::Call; + | +1 | use frame_system::Call; + | +1 | use test_pallet::Call; + | + +error[E0412]: cannot find type `Runtime` in this scope + --> $DIR/exclude_undefined_part.rs:25:25 + | +25 | impl pallet::Config for Runtime {} + | ^^^^^^^ not found in this scope diff --git a/frame/support/test/tests/construct_runtime_ui/invalid_module_details.stderr b/frame/support/test/tests/construct_runtime_ui/invalid_module_details.stderr index 50505b9130cbe..db96b8749ca11 100644 --- a/frame/support/test/tests/construct_runtime_ui/invalid_module_details.stderr +++ b/frame/support/test/tests/construct_runtime_ui/invalid_module_details.stderr @@ -1,5 +1,5 @@ -error: expected one of: identifier, curly braces, `<` - --> $DIR/invalid_module_details.rs:9:19 +error: Unexpected tokens, expected one of `::$ident` `::{`, `exclude_parts`, `use_parts`, `=`, `,` + --> $DIR/invalid_module_details.rs:9:17 | 9 | system: System::(), - | ^^ + | ^^ diff --git a/frame/support/test/tests/construct_runtime_ui/invalid_token_after_module.stderr b/frame/support/test/tests/construct_runtime_ui/invalid_token_after_module.stderr index 3b967f96d7b4e..6025de82bd206 100644 --- a/frame/support/test/tests/construct_runtime_ui/invalid_token_after_module.stderr +++ b/frame/support/test/tests/construct_runtime_ui/invalid_token_after_module.stderr @@ -1,4 +1,4 @@ -error: expected `::` +error: Unexpected tokens, expected one of `::$ident` `::{`, `exclude_parts`, `use_parts`, `=`, `,` --> $DIR/invalid_token_after_module.rs:9:18 | 9 | system: System ? diff --git a/frame/support/test/tests/construct_runtime_ui/old_unsupported_pallet_decl.rs b/frame/support/test/tests/construct_runtime_ui/old_unsupported_pallet_decl.rs new file mode 100644 index 0000000000000..706d444f23590 --- /dev/null +++ b/frame/support/test/tests/construct_runtime_ui/old_unsupported_pallet_decl.rs @@ -0,0 +1,26 @@ +use frame_support::construct_runtime; + +mod pallet_old { + pub trait Config: frame_system::Config {} + + decl_storage! { + trait Store for Module as Example {} + } + + decl_module! { + pub struct Module for enum Call where origin: T::Origin {} + } + +} +construct_runtime! { + pub enum Runtime where + UncheckedExtrinsic = UncheckedExtrinsic, + Block = Block, + NodeBlock = Block, + { + System: frame_system, + OldPallet: pallet_old, + } +} + +fn main() {} diff --git a/frame/support/test/tests/construct_runtime_ui/old_unsupported_pallet_decl.stderr b/frame/support/test/tests/construct_runtime_ui/old_unsupported_pallet_decl.stderr new file mode 100644 index 0000000000000..f8ec07e00106f --- /dev/null +++ b/frame/support/test/tests/construct_runtime_ui/old_unsupported_pallet_decl.stderr @@ -0,0 +1,31 @@ +error[E0433]: failed to resolve: could not find `tt_default_parts` in `pallet_old` + --> $DIR/old_unsupported_pallet_decl.rs:15:1 + | +15 | / construct_runtime! { +16 | | pub enum Runtime where +17 | | UncheckedExtrinsic = UncheckedExtrinsic, +18 | | Block = Block, +... | +23 | | } +24 | | } + | |_^ could not find `tt_default_parts` in `pallet_old` + | + = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: cannot find macro `decl_storage` in this scope + --> $DIR/old_unsupported_pallet_decl.rs:6:2 + | +6 | decl_storage! { + | ^^^^^^^^^^^^ + | + = note: consider importing this macro: + frame_support::decl_storage + +error: cannot find macro `decl_module` in this scope + --> $DIR/old_unsupported_pallet_decl.rs:10:2 + | +10 | decl_module! { + | ^^^^^^^^^^^ + | + = note: consider importing this macro: + frame_support::decl_module diff --git a/frame/support/test/tests/construct_runtime_ui/use_undefined_part.rs b/frame/support/test/tests/construct_runtime_ui/use_undefined_part.rs new file mode 100644 index 0000000000000..1664dcc42b755 --- /dev/null +++ b/frame/support/test/tests/construct_runtime_ui/use_undefined_part.rs @@ -0,0 +1,38 @@ +use frame_support::construct_runtime; +use sp_runtime::{generic, traits::BlakeTwo256}; +use sp_core::sr25519; + +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::storage] + type Foo = StorageValue; +} + +pub type Signature = sr25519::Signature; +pub type BlockNumber = u64; +pub type Header = generic::Header; +pub type Block = generic::Block; +pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; + +impl pallet::Config for Runtime {} + +construct_runtime! { + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic + { + System: system::{Pallet, Call, Storage, Config, Event}, + Pallet: pallet use_parts { Call }, + } +} + +fn main() {} diff --git a/frame/support/test/tests/construct_runtime_ui/use_undefined_part.stderr b/frame/support/test/tests/construct_runtime_ui/use_undefined_part.stderr new file mode 100644 index 0000000000000..ed41f0ce673a4 --- /dev/null +++ b/frame/support/test/tests/construct_runtime_ui/use_undefined_part.stderr @@ -0,0 +1,28 @@ +error: Invalid pallet part specified, the pallet `Pallet` doesn't have the `Call` part. Available parts are: `Pallet`, `Storage`. + --> $DIR/use_undefined_part.rs:34:30 + | +34 | Pallet: pallet use_parts { Call }, + | ^^^^ + +error[E0412]: cannot find type `Call` in this scope + --> $DIR/use_undefined_part.rs:23:64 + | +23 | pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; + | ^^^^ not found in this scope + | +help: consider importing one of these items + | +1 | use crate::pallet::Call; + | +1 | use frame_support_test::Call; + | +1 | use frame_system::Call; + | +1 | use test_pallet::Call; + | + +error[E0412]: cannot find type `Runtime` in this scope + --> $DIR/use_undefined_part.rs:25:25 + | +25 | impl pallet::Config for Runtime {} + | ^^^^^^^ not found in this scope diff --git a/frame/support/test/tests/pallet.rs b/frame/support/test/tests/pallet.rs index dc72be3ebdd49..a314f576187dc 100644 --- a/frame/support/test/tests/pallet.rs +++ b/frame/support/test/tests/pallet.rs @@ -509,6 +509,18 @@ pub mod pallet3 { pub struct Pallet(_); } +#[frame_support::pallet] +pub mod pallet4 { + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::call] + impl Pallet {} +} + frame_support::parameter_types!( pub const MyGetParam: u32 = 10; pub const MyGetParam2: u32 = 11; @@ -553,6 +565,8 @@ impl pallet2::Config for Runtime { type Event = Event; } +impl pallet4::Config for Runtime {} + pub type Header = sp_runtime::generic::Header; pub type Block = sp_runtime::generic::Block; pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; @@ -563,12 +577,21 @@ frame_support::construct_runtime!( NodeBlock = Block, UncheckedExtrinsic = UncheckedExtrinsic { - System: frame_system::{Call, Event}, - Example: pallet::{Pallet, Call, Event, Config, Storage, Inherent, Origin, ValidateUnsigned}, - Example2: pallet2::{Pallet, Call, Event, Config, Storage}, + // Exclude part `Storage` in order not to check its metadata in tests. + System: frame_system exclude_parts { Pallet, Storage }, + Example: pallet, + Example2: pallet2 exclude_parts { Call }, + Example4: pallet4 use_parts { Call }, } ); +// Test that the part `Call` is excluded from Example2 and included in Example4. +fn _ensure_call_is_correctly_excluded_and_included(call: Call) { + match call { + Call::System(_) | Call::Example(_) | Call::Example4(_) => (), + } +} + #[test] fn transactional_works() { TestExternalities::default().execute_with(|| { @@ -995,8 +1018,8 @@ fn migrate_from_pallet_version_to_storage_version() { AllPalletsWithSystem, >(&db_weight); - // 3 pallets, 2 writes and every write costs 5 weight. - assert_eq!(3 * 2 * 5, weight); + // 4 pallets, 2 writes and every write costs 5 weight. + assert_eq!(4 * 2 * 5, weight); // All pallet versions should be removed assert!(sp_io::storage::get(&pallet_version_key(Example::name())).is_none()); @@ -1268,7 +1291,7 @@ fn metadata() { }, ], }), - calls: Some(meta_type::>().into()), + calls: None, event: Some(PalletEventMetadata { ty: meta_type::() }), constants: vec![], error: None, diff --git a/frame/support/test/tests/pallet_instance.rs b/frame/support/test/tests/pallet_instance.rs index 3a1009402d6f2..c031ac9fe1bf5 100644 --- a/frame/support/test/tests/pallet_instance.rs +++ b/frame/support/test/tests/pallet_instance.rs @@ -302,13 +302,12 @@ frame_support::construct_runtime!( NodeBlock = Block, UncheckedExtrinsic = UncheckedExtrinsic { - System: frame_system::{Pallet, Call, Event}, - Example: pallet::{Pallet, Call, Event, Config, Storage, Inherent, Origin, ValidateUnsigned}, - Instance1Example: pallet::::{ - Pallet, Call, Event, Config, Storage, Inherent, Origin, ValidateUnsigned - }, - Example2: pallet2::{Pallet, Event, Config, Storage}, - Instance1Example2: pallet2::::{Pallet, Event, Config, Storage}, + // Exclude part `Storage` in order not to check its metadata in tests. + System: frame_system exclude_parts { Storage }, + Example: pallet, + Instance1Example: pallet::, + Example2: pallet2, + Instance1Example2: pallet2::, } ); @@ -601,7 +600,7 @@ fn metadata() { let system_pallet_metadata = PalletMetadata { index: 0, name: "System", - storage: None, + storage: None, // The storage metadatas have been excluded. calls: Some(scale_info::meta_type::>().into()), event: Some(PalletEventMetadata { ty: scale_info::meta_type::>(),