diff --git a/substrate/Cargo.lock b/substrate/Cargo.lock index c99212ba06ba8ce1024167b84d4107db9e801077..ed0d5777d94fee6e5dc65376f34a94168786c342 100644 --- a/substrate/Cargo.lock +++ b/substrate/Cargo.lock @@ -852,6 +852,15 @@ dependencies = [ "nom", ] +[[package]] +name = "cfg-expr" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aacacf4d96c24b2ad6eb8ee6df040e4f27b0d0b39a5710c30091baa830485db" +dependencies = [ + "smallvec", +] + [[package]] name = "cfg-if" version = "0.1.10" @@ -2284,7 +2293,9 @@ name = "frame-support-procedural" version = "4.0.0-dev" dependencies = [ "Inflector", + "cfg-expr", "frame-support-procedural-tools", + "itertools", "proc-macro2", "quote", "syn", diff --git a/substrate/frame/support/procedural/Cargo.toml b/substrate/frame/support/procedural/Cargo.toml index 7ddec39cad9fb1b7773b8a825418a83c7d583069..06b8056aff9829079aeafe5975f77a7f61d7609a 100644 --- a/substrate/frame/support/procedural/Cargo.toml +++ b/substrate/frame/support/procedural/Cargo.toml @@ -16,6 +16,8 @@ proc-macro = true [dependencies] Inflector = "0.11.4" +cfg-expr = "0.10.3" +itertools = "0.10.3" proc-macro2 = "1.0.37" quote = "1.0.10" syn = { version = "1.0.98", features = ["full"] } diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/call.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/call.rs index 801b69035121dddcf982d12e573adad532a7881f..028e68f0198d17abfa1166ced3b35b5c054b569a 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/expand/call.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/call.rs @@ -18,6 +18,7 @@ use crate::construct_runtime::Pallet; use proc_macro2::TokenStream; use quote::quote; +use std::str::FromStr; use syn::Ident; pub fn expand_outer_dispatch( @@ -30,6 +31,7 @@ pub fn expand_outer_dispatch( let mut variant_patterns = Vec::new(); let mut query_call_part_macros = Vec::new(); let mut pallet_names = Vec::new(); + let mut pallet_attrs = Vec::new(); let system_path = &system_pallet.path; let pallets_with_call = pallet_decls.iter().filter(|decl| decl.exists_part("Call")); @@ -38,12 +40,24 @@ pub fn expand_outer_dispatch( let name = &pallet_declaration.name; let path = &pallet_declaration.path; let index = pallet_declaration.index; + let attr = + pallet_declaration.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { + let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) + .expect("was successfully parsed before; qed"); + quote! { + #acc + #attr + } + }); - variant_defs.extend( - quote!(#[codec(index = #index)] #name( #scrate::dispatch::CallableCallFor<#name, #runtime> ),), - ); + variant_defs.extend(quote! { + #attr + #[codec(index = #index)] + #name( #scrate::dispatch::CallableCallFor<#name, #runtime> ), + }); variant_patterns.push(quote!(Call::#name(call))); pallet_names.push(name); + pallet_attrs.push(attr); query_call_part_macros.push(quote! { #path::__substrate_call_check::is_call_part_defined!(#name); }); @@ -69,6 +83,7 @@ pub fn expand_outer_dispatch( use #scrate::dispatch::Callable; use core::mem::size_of; &[#( + #pallet_attrs ( stringify!(#pallet_names), size_of::< <#pallet_names as Callable<#runtime>>::Call >(), @@ -101,7 +116,10 @@ pub fn expand_outer_dispatch( impl #scrate::dispatch::GetDispatchInfo for Call { fn get_dispatch_info(&self) -> #scrate::dispatch::DispatchInfo { match self { - #( #variant_patterns => call.get_dispatch_info(), )* + #( + #pallet_attrs + #variant_patterns => call.get_dispatch_info(), + )* } } } @@ -110,6 +128,7 @@ pub fn expand_outer_dispatch( use #scrate::dispatch::GetCallName; match self { #( + #pallet_attrs #variant_patterns => { let function_name = call.get_call_name(); let pallet_name = stringify!(#pallet_names); @@ -121,6 +140,7 @@ pub fn expand_outer_dispatch( fn get_module_names() -> &'static [&'static str] { &[#( + #pallet_attrs stringify!(#pallet_names), )*] } @@ -129,6 +149,7 @@ pub fn expand_outer_dispatch( use #scrate::dispatch::{Callable, GetCallName}; match module { #( + #pallet_attrs stringify!(#pallet_names) => <<#pallet_names as Callable<#runtime>>::Call as GetCallName>::get_call_names(), @@ -157,6 +178,7 @@ pub fn expand_outer_dispatch( fn dispatch_bypass_filter(self, origin: Origin) -> #scrate::dispatch::DispatchResultWithPostInfo { match self { #( + #pallet_attrs #variant_patterns => #scrate::traits::UnfilteredDispatchable::dispatch_bypass_filter(call, origin), )* @@ -165,6 +187,7 @@ pub fn expand_outer_dispatch( } #( + #pallet_attrs impl #scrate::traits::IsSubType<#scrate::dispatch::CallableCallFor<#pallet_names, #runtime>> for Call { #[allow(unreachable_patterns)] fn is_sub_type(&self) -> Option<&#scrate::dispatch::CallableCallFor<#pallet_names, #runtime>> { @@ -176,6 +199,7 @@ pub fn expand_outer_dispatch( } } + #pallet_attrs impl From<#scrate::dispatch::CallableCallFor<#pallet_names, #runtime>> for Call { fn from(call: #scrate::dispatch::CallableCallFor<#pallet_names, #runtime>) -> Self { #variant_patterns diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/config.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/config.rs index a3d70f18529c7f2b5148290e92d4b1bd7d2dc058..9b731a5825a3c21fe9e322d6bdd18a6cf8aa7b28 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/expand/config.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/config.rs @@ -19,6 +19,7 @@ use crate::construct_runtime::Pallet; use inflector::Inflector; use proc_macro2::TokenStream; use quote::{format_ident, quote, ToTokens}; +use std::str::FromStr; use syn::Ident; pub fn expand_outer_config( @@ -40,11 +41,19 @@ pub fn expand_outer_config( let field_name = &Ident::new(&pallet_name.to_string().to_snake_case(), decl.name.span()); let part_is_generic = !pallet_entry.generics.params.is_empty(); + let attr = &decl.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { + let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) + .expect("was successfully parsed before; qed"); + quote! { + #acc + #attr + } + }); - types.extend(expand_config_types(runtime, decl, &config, part_is_generic)); - fields.extend(quote!(pub #field_name: #config,)); + types.extend(expand_config_types(attr, runtime, decl, &config, part_is_generic)); + fields.extend(quote!(#attr pub #field_name: #config,)); build_storage_calls - .extend(expand_config_build_storage_call(scrate, runtime, decl, field_name)); + .extend(expand_config_build_storage_call(scrate, attr, runtime, decl, field_name)); query_genesis_config_part_macros.push(quote! { #path::__substrate_genesis_config_check::is_genesis_config_defined!(#pallet_name); #[cfg(feature = "std")] @@ -88,6 +97,7 @@ pub fn expand_outer_config( } fn expand_config_types( + attr: &TokenStream, runtime: &Ident, decl: &Pallet, config: &Ident, @@ -97,14 +107,17 @@ fn expand_config_types( match (decl.instance.as_ref(), part_is_generic) { (Some(inst), true) => quote! { + #attr #[cfg(any(feature = "std", test))] pub type #config = #path::GenesisConfig<#runtime, #path::#inst>; }, (None, true) => quote! { + #attr #[cfg(any(feature = "std", test))] pub type #config = #path::GenesisConfig<#runtime>; }, (_, false) => quote! { + #attr #[cfg(any(feature = "std", test))] pub type #config = #path::GenesisConfig; }, @@ -113,6 +126,7 @@ fn expand_config_types( fn expand_config_build_storage_call( scrate: &TokenStream, + attr: &TokenStream, runtime: &Ident, decl: &Pallet, field_name: &Ident, @@ -125,6 +139,7 @@ fn expand_config_build_storage_call( }; quote! { + #attr #scrate::sp_runtime::BuildModuleGenesisStorage:: <#runtime, #instance>::build_module_genesis_storage(&self.#field_name, storage)?; } diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/event.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/event.rs index b242f9641562c039b17bf385ff46cf1282d1cb0b..f145327d37af59ae73d25bafccc316c3c4d924e6 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/expand/event.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/event.rs @@ -18,6 +18,7 @@ use crate::construct_runtime::Pallet; use proc_macro2::TokenStream; use quote::quote; +use std::str::FromStr; use syn::{Generics, Ident}; pub fn expand_outer_event( @@ -97,19 +98,35 @@ fn expand_event_variant( let path = &pallet.path; let variant_name = &pallet.name; let part_is_generic = !generics.params.is_empty(); + let attr = pallet.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { + let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) + .expect("was successfully parsed before; qed"); + quote! { + #acc + #attr + } + }); match instance { - Some(inst) if part_is_generic => { - quote!(#[codec(index = #index)] #variant_name(#path::Event<#runtime, #path::#inst>),) + Some(inst) if part_is_generic => quote! { + #attr + #[codec(index = #index)] + #variant_name(#path::Event<#runtime, #path::#inst>), }, - Some(inst) => { - quote!(#[codec(index = #index)] #variant_name(#path::Event<#path::#inst>),) + Some(inst) => quote! { + #attr + #[codec(index = #index)] + #variant_name(#path::Event<#path::#inst>), }, - None if part_is_generic => { - quote!(#[codec(index = #index)] #variant_name(#path::Event<#runtime>),) + None if part_is_generic => quote! { + #attr + #[codec(index = #index)] + #variant_name(#path::Event<#runtime>), }, - None => { - quote!(#[codec(index = #index)] #variant_name(#path::Event),) + None => quote! { + #attr + #[codec(index = #index)] + #variant_name(#path::Event), }, } } @@ -120,13 +137,23 @@ fn expand_event_conversion( pallet_event: &TokenStream, ) -> TokenStream { let variant_name = &pallet.name; + let attr = pallet.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { + let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) + .expect("was successfully parsed before; qed"); + quote! { + #acc + #attr + } + }); quote! { + #attr impl From<#pallet_event> for Event { fn from(x: #pallet_event) -> Self { Event::#variant_name(x) } } + #attr impl TryInto<#pallet_event> for Event { type Error = (); diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/inherent.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/inherent.rs index 0f0d538643240811e0a153bb87c89e99ad1f26ee..599b34ba87241e588dd018739a4465bfb0f473ef 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/expand/inherent.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/inherent.rs @@ -18,6 +18,7 @@ use crate::construct_runtime::Pallet; use proc_macro2::TokenStream; use quote::quote; +use std::str::FromStr; use syn::{Ident, TypePath}; pub fn expand_outer_inherent( @@ -28,14 +29,24 @@ pub fn expand_outer_inherent( scrate: &TokenStream, ) -> TokenStream { let mut pallet_names = Vec::new(); + let mut pallet_attrs = Vec::new(); let mut query_inherent_part_macros = Vec::new(); for pallet_decl in pallet_decls { if pallet_decl.exists_part("Inherent") { let name = &pallet_decl.name; let path = &pallet_decl.path; + let attr = pallet_decl.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { + let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) + .expect("was successfully parsed before; qed"); + quote! { + #acc + #attr + } + }); pallet_names.push(name); + pallet_attrs.push(attr); query_inherent_part_macros.push(quote! { #path::__substrate_inherent_check::is_inherent_part_defined!(#name); }); @@ -60,6 +71,7 @@ pub fn expand_outer_inherent( let mut inherents = Vec::new(); #( + #pallet_attrs if let Some(inherent) = #pallet_names::create_inherent(self) { let inherent = <#unchecked_extrinsic as #scrate::inherent::Extrinsic>::new( inherent.into(), @@ -90,22 +102,25 @@ pub fn expand_outer_inherent( let mut is_inherent = false; - #({ - let call = <#unchecked_extrinsic as ExtrinsicCall>::call(xt); - if let Some(call) = IsSubType::<_>::is_sub_type(call) { - if #pallet_names::is_inherent(call) { - is_inherent = true; - if let Err(e) = #pallet_names::check_inherent(call, self) { - result.put_error( - #pallet_names::INHERENT_IDENTIFIER, &e - ).expect("There is only one fatal error; qed"); - if e.is_fatal_error() { - return result; + #( + #pallet_attrs + { + let call = <#unchecked_extrinsic as ExtrinsicCall>::call(xt); + if let Some(call) = IsSubType::<_>::is_sub_type(call) { + if #pallet_names::is_inherent(call) { + is_inherent = true; + if let Err(e) = #pallet_names::check_inherent(call, self) { + result.put_error( + #pallet_names::INHERENT_IDENTIFIER, &e + ).expect("There is only one fatal error; qed"); + if e.is_fatal_error() { + return result; + } } } } } - })* + )* // Inherents are before any other extrinsics. // No module marked it as inherent thus it is not. @@ -115,6 +130,7 @@ pub fn expand_outer_inherent( } #( + #pallet_attrs match #pallet_names::is_inherent_required(self) { Ok(Some(e)) => { let found = block.extrinsics().iter().any(|xt| { @@ -177,14 +193,17 @@ pub fn expand_outer_inherent( false } else { let mut is_inherent = false; - #({ - let call = <#unchecked_extrinsic as ExtrinsicCall>::call(xt); - if let Some(call) = IsSubType::<_>::is_sub_type(call) { - if #pallet_names::is_inherent(&call) { - is_inherent = true; + #( + #pallet_attrs + { + let call = <#unchecked_extrinsic as ExtrinsicCall>::call(xt); + if let Some(call) = IsSubType::<_>::is_sub_type(call) { + if #pallet_names::is_inherent(&call) { + is_inherent = true; + } } } - })* + )* is_inherent }; diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/metadata.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/metadata.rs index 6e2dd5fc002c6ceb0a282e9780e35fc45d2bb0ca..ec90a0d30f98b1319d417c5e4e5387e05044df45 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/expand/metadata.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/metadata.rs @@ -18,6 +18,7 @@ use crate::construct_runtime::Pallet; use proc_macro2::TokenStream; use quote::quote; +use std::str::FromStr; use syn::{Ident, TypePath}; pub fn expand_runtime_metadata( @@ -47,8 +48,17 @@ pub fn expand_runtime_metadata( let event = expand_pallet_metadata_events(&filtered_names, runtime, scrate, decl); let constants = expand_pallet_metadata_constants(runtime, decl); let errors = expand_pallet_metadata_errors(runtime, decl); + let attr = decl.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { + let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) + .expect("was successfully parsed before; qed"); + quote! { + #acc + #attr + } + }); quote! { + #attr #scrate::metadata::PalletMetadata { name: stringify!(#name), index: #index, diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/origin.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/origin.rs index 46f08832f0bb4c4bd9079a7c80ae525d2c6f789d..a4bb0d9cbaa028083f4eb22482743df7aed4d54e 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/expand/origin.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/origin.rs @@ -18,6 +18,7 @@ use crate::construct_runtime::{Pallet, SYSTEM_PALLET_NAME}; use proc_macro2::TokenStream; use quote::quote; +use std::str::FromStr; use syn::{Generics, Ident}; pub fn expand_outer_origin( @@ -303,19 +304,35 @@ fn expand_origin_caller_variant( let part_is_generic = !generics.params.is_empty(); let variant_name = &pallet.name; let path = &pallet.path; + let attr = pallet.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { + let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) + .expect("was successfully parsed before; qed"); + quote! { + #acc + #attr + } + }); match instance { - Some(inst) if part_is_generic => { - quote!(#[codec(index = #index)] #variant_name(#path::Origin<#runtime, #path::#inst>),) + Some(inst) if part_is_generic => quote! { + #attr + #[codec(index = #index)] + #variant_name(#path::Origin<#runtime, #path::#inst>), }, - Some(inst) => { - quote!(#[codec(index = #index)] #variant_name(#path::Origin<#path::#inst>),) + Some(inst) => quote! { + #attr + #[codec(index = #index)] + #variant_name(#path::Origin<#path::#inst>), }, - None if part_is_generic => { - quote!(#[codec(index = #index)] #variant_name(#path::Origin<#runtime>),) + None if part_is_generic => quote! { + #attr + #[codec(index = #index)] + #variant_name(#path::Origin<#runtime>), }, - None => { - quote!(#[codec(index = #index)] #variant_name(#path::Origin),) + None => quote! { + #attr + #[codec(index = #index)] + #variant_name(#path::Origin), }, } } @@ -339,14 +356,24 @@ fn expand_origin_pallet_conversions( }; let doc_string = get_intra_doc_string(" Convert to runtime origin using", &path.module_name()); + let attr = pallet.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { + let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) + .expect("was successfully parsed before; qed"); + quote! { + #acc + #attr + } + }); quote! { + #attr impl From<#pallet_origin> for OriginCaller { fn from(x: #pallet_origin) -> Self { OriginCaller::#variant_name(x) } } + #attr impl From<#pallet_origin> for Origin { #[doc = #doc_string] fn from(x: #pallet_origin) -> Self { @@ -355,6 +382,7 @@ fn expand_origin_pallet_conversions( } } + #attr impl From<Origin> for #scrate::sp_std::result::Result<#pallet_origin, Origin> { /// NOTE: converting to pallet origin loses the origin filter information. fn from(val: Origin) -> Self { @@ -366,6 +394,7 @@ fn expand_origin_pallet_conversions( } } + #attr impl TryFrom<OriginCaller> for #pallet_origin { type Error = OriginCaller; fn try_from( diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/unsigned.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/unsigned.rs index c0306768020933add2113632f05528bf5c0495d6..310516793472f4d146b1b0d8fed49cceafb9bf86 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/expand/unsigned.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/unsigned.rs @@ -18,6 +18,7 @@ use crate::construct_runtime::Pallet; use proc_macro2::TokenStream; use quote::quote; +use std::str::FromStr; use syn::Ident; pub fn expand_outer_validate_unsigned( @@ -26,14 +27,24 @@ pub fn expand_outer_validate_unsigned( scrate: &TokenStream, ) -> TokenStream { let mut pallet_names = Vec::new(); + let mut pallet_attrs = Vec::new(); let mut query_validate_unsigned_part_macros = Vec::new(); for pallet_decl in pallet_decls { if pallet_decl.exists_part("ValidateUnsigned") { let name = &pallet_decl.name; let path = &pallet_decl.path; + let attr = pallet_decl.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { + let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) + .expect("was successfully parsed before; qed"); + quote! { + #acc + #attr + } + }); pallet_names.push(name); + pallet_attrs.push(attr); query_validate_unsigned_part_macros.push(quote! { #path::__substrate_validate_unsigned_check::is_validate_unsigned_part_defined!(#name); }); @@ -49,7 +60,10 @@ pub fn expand_outer_validate_unsigned( fn pre_dispatch(call: &Self::Call) -> Result<(), #scrate::unsigned::TransactionValidityError> { #[allow(unreachable_patterns)] match call { - #( Call::#pallet_names(inner_call) => #pallet_names::pre_dispatch(inner_call), )* + #( + #pallet_attrs + Call::#pallet_names(inner_call) => #pallet_names::pre_dispatch(inner_call), + )* // pre-dispatch should not stop inherent extrinsics, validation should prevent // including arbitrary (non-inherent) extrinsics to blocks. _ => Ok(()), @@ -63,7 +77,10 @@ pub fn expand_outer_validate_unsigned( ) -> #scrate::unsigned::TransactionValidity { #[allow(unreachable_patterns)] match call { - #( Call::#pallet_names(inner_call) => #pallet_names::validate_unsigned(source, inner_call), )* + #( + #pallet_attrs + Call::#pallet_names(inner_call) => #pallet_names::validate_unsigned(source, inner_call), + )* _ => #scrate::unsigned::UnknownTransaction::NoUnsignedValidator.into(), } } diff --git a/substrate/frame/support/procedural/src/construct_runtime/mod.rs b/substrate/frame/support/procedural/src/construct_runtime/mod.rs index 042af31be6bf469f1c7a9586c8d54777310823d8..cfd582d0e4472557924c18426f649c4964a95ff4 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/mod.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/mod.rs @@ -144,9 +144,11 @@ mod expand; mod parse; +use cfg_expr::Predicate; use frame_support_procedural_tools::{ generate_crate_access, generate_crate_access_2018, generate_hidden_includes, }; +use itertools::Itertools; use parse::{ ExplicitRuntimeDeclaration, ImplicitRuntimeDeclaration, Pallet, RuntimeDeclaration, WhereSection, @@ -154,6 +156,10 @@ use parse::{ use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; use quote::quote; +use std::{ + collections::{HashMap, HashSet}, + str::FromStr, +}; use syn::{Ident, Result}; /// The fixed name of the system pallet. @@ -223,6 +229,28 @@ fn construct_runtime_final_expansion( Please add this line: `System: frame_system::{Pallet, Call, Storage, Config, Event<T>},`", ) })?; + if !system_pallet.cfg_pattern.is_empty() { + return Err(syn::Error::new( + system_pallet.name.span(), + "`System` pallet declaration is feature gated, please remove any `#[cfg]` attributes", + )) + } + + let features = pallets + .iter() + .filter_map(|decl| { + (!decl.cfg_pattern.is_empty()).then(|| { + decl.cfg_pattern.iter().flat_map(|attr| { + attr.predicates().filter_map(|pred| match pred { + Predicate::Feature(feat) => Some(feat), + Predicate::Test => Some("test"), + _ => None, + }) + }) + }) + }) + .flatten() + .collect::<HashSet<_>>(); let hidden_crate_name = "construct_runtime"; let scrate = generate_crate_access(hidden_crate_name, "frame-support"); @@ -231,7 +259,7 @@ fn construct_runtime_final_expansion( let outer_event = expand::expand_outer_event(&name, &pallets, &scrate)?; let outer_origin = expand::expand_outer_origin(&name, system_pallet, &pallets, &scrate)?; - let all_pallets = decl_all_pallets(&name, pallets.iter()); + let all_pallets = decl_all_pallets(&name, pallets.iter(), &features); let pallet_to_index = decl_pallet_runtime_setup(&name, &pallets, &scrate); let dispatch = expand::expand_outer_dispatch(&name, system_pallet, &pallets, &scrate); @@ -293,40 +321,140 @@ fn construct_runtime_final_expansion( fn decl_all_pallets<'a>( runtime: &'a Ident, pallet_declarations: impl Iterator<Item = &'a Pallet>, + features: &HashSet<&str>, ) -> TokenStream2 { let mut types = TokenStream2::new(); - let mut names = Vec::new(); + let mut names_by_feature = features + .iter() + .powerset() + .map(|feat| (feat, Vec::new())) + .collect::<HashMap<_, _>>(); for pallet_declaration in pallet_declarations { let type_name = &pallet_declaration.name; let pallet = &pallet_declaration.path; let mut generics = vec![quote!(#runtime)]; generics.extend(pallet_declaration.instance.iter().map(|name| quote!(#pallet::#name))); + let mut attrs = Vec::new(); + for cfg in &pallet_declaration.cfg_pattern { + let feat = format!("#[cfg({})]\n", cfg.original()); + attrs.extend(TokenStream2::from_str(&feat).expect("was parsed successfully; qed")); + } let type_decl = quote!( + #(#attrs)* pub type #type_name = #pallet::Pallet <#(#generics),*>; ); types.extend(type_decl); - names.push(&pallet_declaration.name); + + if pallet_declaration.cfg_pattern.is_empty() { + for names in names_by_feature.values_mut() { + names.push(&pallet_declaration.name); + } + } else { + for (feature_set, names) in &mut names_by_feature { + // Rust tidbit: if we have multiple `#[cfg]` feature on the same item, then the + // predicates listed in all `#[cfg]` attributes are effectively joined by `and()`, + // meaning that all of them must match in order to activate the item + let is_feature_active = pallet_declaration.cfg_pattern.iter().all(|expr| { + expr.eval(|pred| match pred { + Predicate::Feature(f) => feature_set.contains(&f), + Predicate::Test => feature_set.contains(&&"test"), + _ => false, + }) + }); + if is_feature_active { + names.push(&pallet_declaration.name); + } + } + } } - let system_pallet = match names.iter().find(|n| **n == SYSTEM_PALLET_NAME) { - Some(name) => name, - None => - return syn::Error::new( - proc_macro2::Span::call_site(), - "`System` pallet declaration is missing. \ - Please add this line: `System: frame_system::{Pallet, Call, Storage, Config, Event<T>},`", - ) - .into_compile_error(), - }; + let all_pallets_without_system = names_by_feature.iter().map(|(feature_set, names)| { + let mut feature_set = feature_set.iter().collect::<HashSet<_>>(); + let test_cfg = feature_set.remove(&&&"test").then_some(quote!(test)).into_iter(); + let feature_set = feature_set.into_iter(); + let attr = quote!(#[cfg(all( #(#test_cfg),* #(feature = #feature_set),* ))]); + let names = names.iter().filter(|n| **n != SYSTEM_PALLET_NAME); + quote! { + #attr + /// All pallets included in the runtime as a nested tuple of types. + /// Excludes the System pallet. + pub type AllPalletsWithoutSystem = ( #(#names,)* ); + } + }); - let names_without_system = - names.iter().filter(|n| **n != SYSTEM_PALLET_NAME).collect::<Vec<_>>(); - let names_reversed = names.clone().into_iter().rev().collect::<Vec<_>>(); - let names_without_system_reverse = - names_without_system.clone().into_iter().rev().collect::<Vec<_>>(); - let names_reversed_with_system_first = std::iter::once(system_pallet) - .chain(names_without_system_reverse.clone().into_iter()) - .collect::<Vec<_>>(); + let all_pallets_with_system = names_by_feature.iter().map(|(feature_set, names)| { + let mut feature_set = feature_set.iter().collect::<HashSet<_>>(); + let test_cfg = feature_set.remove(&&&"test").then_some(quote!(test)).into_iter(); + let feature_set = feature_set.into_iter(); + let attr = quote!(#[cfg(all( #(#test_cfg),* #(feature = #feature_set),* ))]); + quote! { + #attr + /// All pallets included in the runtime as a nested tuple of types. + pub type AllPalletsWithSystem = ( #(#names,)* ); + } + }); + + let all_pallets_without_system_reversed = + names_by_feature.iter().map(|(feature_set, names)| { + let mut feature_set = feature_set.iter().collect::<HashSet<_>>(); + let test_cfg = feature_set.remove(&&&"test").then_some(quote!(test)).into_iter(); + let feature_set = feature_set.into_iter(); + let attr = quote!(#[cfg(all( #(#test_cfg),* #(feature = #feature_set),* ))]); + let names = names.iter().filter(|n| **n != SYSTEM_PALLET_NAME).rev(); + quote! { + #attr + /// All pallets included in the runtime as a nested tuple of types in reversed order. + /// Excludes the System pallet. + #[deprecated(note = "Using reverse pallet orders is deprecated. use only \ + `AllPalletWithSystem or AllPalletsWithoutSystem`")] + pub type AllPalletsWithoutSystemReversed = ( #(#names,)* ); + } + }); + + let all_pallets_with_system_reversed = names_by_feature.iter().map(|(feature_set, names)| { + let mut feature_set = feature_set.iter().collect::<HashSet<_>>(); + let test_cfg = feature_set.remove(&&&"test").then_some(quote!(test)).into_iter(); + let feature_set = feature_set.into_iter(); + let attr = quote!(#[cfg(all( #(#test_cfg),* #(feature = #feature_set),* ))]); + let names = names.iter().rev(); + quote! { + #attr + /// All pallets included in the runtime as a nested tuple of types in reversed order. + #[deprecated(note = "Using reverse pallet orders is deprecated. use only \ + `AllPalletWithSystem or AllPalletsWithoutSystem`")] + pub type AllPalletsWithSystemReversed = ( #(#names,)* ); + } + }); + + let system_pallet = + match names_by_feature[&Vec::new()].iter().find(|n| **n == SYSTEM_PALLET_NAME) { + Some(name) => name, + None => + return syn::Error::new( + proc_macro2::Span::call_site(), + "`System` pallet declaration is missing. \ + Please add this line: `System: frame_system,`", + ) + .into_compile_error(), + }; + + let all_pallets_reversed_with_system_first = + names_by_feature.iter().map(|(feature_set, names)| { + let mut feature_set = feature_set.iter().collect::<HashSet<_>>(); + let test_cfg = feature_set.remove(&&&"test").then_some(quote!(test)).into_iter(); + let feature_set = feature_set.into_iter(); + let attr = quote!(#[cfg(all( #(#test_cfg),* #(feature = #feature_set),* ))]); + let names = std::iter::once(system_pallet) + .chain(names.iter().rev().filter(|n| **n != SYSTEM_PALLET_NAME)); + quote! { + #attr + /// All pallets included in the runtime as a nested tuple of types in reversed order. + /// With the system pallet first. + #[deprecated(note = "Using reverse pallet orders is deprecated. use only \ + `AllPalletWithSystem or AllPalletsWithoutSystem`")] + pub type AllPalletsReversedWithSystemFirst = ( #(#names,)* ); + } + }); quote!( #types @@ -342,29 +470,15 @@ fn decl_all_pallets<'a>( https://github.com/paritytech/substrate/pull/10043")] pub type AllPallets = AllPalletsWithSystem; - /// All pallets included in the runtime as a nested tuple of types. - pub type AllPalletsWithSystem = ( #(#names),* ); + #( #all_pallets_with_system )* - /// All pallets included in the runtime as a nested tuple of types. - /// Excludes the System pallet. - pub type AllPalletsWithoutSystem = ( #(#names_without_system),* ); - - /// All pallets included in the runtime as a nested tuple of types in reversed order. - /// Excludes the System pallet. - #[deprecated(note = "Using reverse pallet orders is deprecated. use only \ - `AllPalletWithSystem or AllPalletsWithoutSystem`")] - pub type AllPalletsWithoutSystemReversed =( #(#names_without_system_reverse),* ); - - /// All pallets included in the runtime as a nested tuple of types in reversed order. - #[deprecated(note = "Using reverse pallet orders is deprecated. use only \ - `AllPalletWithSystem or AllPalletsWithoutSystem`")] - pub type AllPalletsWithSystemReversed = ( #(#names_reversed),* ); - - /// All pallets included in the runtime as a nested tuple of types in reversed order. - /// With the system pallet first. - #[deprecated(note = "Using reverse pallet orders is deprecated. use only \ - `AllPalletWithSystem or AllPalletsWithoutSystem`")] - pub type AllPalletsReversedWithSystemFirst = ( #(#names_reversed_with_system_first),* ); + #( #all_pallets_without_system )* + + #( #all_pallets_with_system_reversed )* + + #( #all_pallets_without_system_reversed )* + + #( #all_pallets_reversed_with_system_first )* ) } @@ -387,6 +501,19 @@ fn decl_pallet_runtime_setup( } }) .collect::<Vec<_>>(); + let pallet_attrs = pallet_declarations + .iter() + .map(|pallet| { + pallet.cfg_pattern.iter().fold(TokenStream2::new(), |acc, pattern| { + let attr = TokenStream2::from_str(&format!("#[cfg({})]", pattern.original())) + .expect("was successfully parsed before; qed"); + quote! { + #acc + #attr + } + }) + }) + .collect::<Vec<_>>(); quote!( /// Provides an implementation of `PalletInfo` to provide information @@ -397,6 +524,7 @@ fn decl_pallet_runtime_setup( fn index<P: 'static>() -> Option<usize> { let type_id = #scrate::sp_std::any::TypeId::of::<P>(); #( + #pallet_attrs if type_id == #scrate::sp_std::any::TypeId::of::<#names>() { return Some(#indices) } @@ -408,6 +536,7 @@ fn decl_pallet_runtime_setup( fn name<P: 'static>() -> Option<&'static str> { let type_id = #scrate::sp_std::any::TypeId::of::<P>(); #( + #pallet_attrs if type_id == #scrate::sp_std::any::TypeId::of::<#names>() { return Some(#name_strings) } @@ -419,6 +548,7 @@ fn decl_pallet_runtime_setup( fn module_name<P: 'static>() -> Option<&'static str> { let type_id = #scrate::sp_std::any::TypeId::of::<P>(); #( + #pallet_attrs if type_id == #scrate::sp_std::any::TypeId::of::<#names>() { return Some(#module_names) } @@ -430,6 +560,7 @@ fn decl_pallet_runtime_setup( fn crate_version<P: 'static>() -> Option<#scrate::traits::CrateVersion> { let type_id = #scrate::sp_std::any::TypeId::of::<P>(); #( + #pallet_attrs if type_id == #scrate::sp_std::any::TypeId::of::<#names>() { return Some( <#pallet_structs as #scrate::traits::PalletInfoAccess>::crate_version() diff --git a/substrate/frame/support/procedural/src/construct_runtime/parse.rs b/substrate/frame/support/procedural/src/construct_runtime/parse.rs index 711da85c10cfc8ecc0436fcf94d434a860a83411..7a5acf43b92b031209137d2e5886ed9c713162f8 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/parse.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/parse.rs @@ -23,7 +23,7 @@ use syn::{ parse::{Parse, ParseStream}, punctuated::Punctuated, spanned::Spanned, - token, Error, Ident, Path, Result, Token, + token, Attribute, Error, Ident, Path, Result, Token, }; mod keyword { @@ -185,6 +185,8 @@ impl Parse for WhereDefinition { pub struct PalletDeclaration { /// The name of the pallet, e.g.`System` in `System: frame_system`. pub name: Ident, + /// Optional attributes tagged right above a pallet declaration. + pub attrs: Vec<Attribute>, /// Optional fixed index, e.g. `MyPallet ... = 3,`. pub index: Option<u8>, /// The path of the pallet, e.g. `frame_system` in `System: frame_system`. @@ -212,6 +214,8 @@ pub enum SpecifiedParts { impl Parse for PalletDeclaration { fn parse(input: ParseStream) -> Result<Self> { + let attrs = input.call(Attribute::parse_outer)?; + let name = input.parse()?; let _: Token![:] = input.parse()?; let path = input.parse()?; @@ -279,7 +283,7 @@ impl Parse for PalletDeclaration { None }; - Ok(Self { name, path, instance, pallet_parts, specified_parts, index }) + Ok(Self { attrs, name, path, instance, pallet_parts, specified_parts, index }) } } @@ -535,6 +539,8 @@ pub struct Pallet { pub instance: Option<Ident>, /// The pallet parts to use for the pallet. pub pallet_parts: Vec<PalletPart>, + /// Expressions specified inside of a #[cfg] attribute. + pub cfg_pattern: Vec<cfg_expr::Expression>, } impl Pallet { @@ -647,11 +653,32 @@ fn convert_pallets(pallets: Vec<PalletDeclaration>) -> syn::Result<PalletsConver SpecifiedParts::All => (), } + let cfg_pattern = pallet + .attrs + .iter() + .map(|attr| { + if attr.path.segments.len() != 1 || attr.path.segments[0].ident != "cfg" { + let msg = "Unsupported attribute, only #[cfg] is supported on pallet \ + declarations in `construct_runtime`"; + return Err(syn::Error::new(attr.span(), msg)) + } + + attr.parse_args_with(|input: syn::parse::ParseStream| { + // Required, otherwise the parse stream doesn't advance and will result in + // an error. + let input = input.parse::<proc_macro2::TokenStream>()?; + cfg_expr::Expression::parse(&input.to_string()) + .map_err(|e| syn::Error::new(attr.span(), e.to_string())) + }) + }) + .collect::<Result<Vec<_>>>()?; + Ok(Pallet { name: pallet.name, index: final_index, path: pallet.path, instance: pallet.instance, + cfg_pattern, pallet_parts, }) }) diff --git a/substrate/frame/support/test/tests/construct_runtime_ui/feature_gated_system_pallet.rs b/substrate/frame/support/test/tests/construct_runtime_ui/feature_gated_system_pallet.rs new file mode 100644 index 0000000000000000000000000000000000000000..79b5632babd95cd38ca6fd95d8671b4e847cbe4d --- /dev/null +++ b/substrate/frame/support/test/tests/construct_runtime_ui/feature_gated_system_pallet.rs @@ -0,0 +1,14 @@ +use frame_support::construct_runtime; + +construct_runtime! { + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic + { + #[cfg(test)] + System: frame_system::{Pallet, Call, Storage, Config, Event<T>}, + } +} + +fn main() {} diff --git a/substrate/frame/support/test/tests/construct_runtime_ui/feature_gated_system_pallet.stderr b/substrate/frame/support/test/tests/construct_runtime_ui/feature_gated_system_pallet.stderr new file mode 100644 index 0000000000000000000000000000000000000000..a86a839615aa01d71d806aea01fc7b4c03f5e0f4 --- /dev/null +++ b/substrate/frame/support/test/tests/construct_runtime_ui/feature_gated_system_pallet.stderr @@ -0,0 +1,5 @@ +error: `System` pallet declaration is feature gated, please remove any `#[cfg]` attributes + --> tests/construct_runtime_ui/feature_gated_system_pallet.rs:10:3 + | +10 | System: frame_system::{Pallet, Call, Storage, Config, Event<T>}, + | ^^^^^^ diff --git a/substrate/frame/support/test/tests/construct_runtime_ui/invalid_meta_literal.rs b/substrate/frame/support/test/tests/construct_runtime_ui/invalid_meta_literal.rs new file mode 100644 index 0000000000000000000000000000000000000000..a1d39fa76ea851e1c93e694a4e563179c3ca0d98 --- /dev/null +++ b/substrate/frame/support/test/tests/construct_runtime_ui/invalid_meta_literal.rs @@ -0,0 +1,15 @@ +use frame_support::construct_runtime; + +construct_runtime! { + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic + { + System: system::{Pallet}, + #[cfg(feature = 1)] + Balance: balances::{Config, Call}, + } +} + +fn main() {} diff --git a/substrate/frame/support/test/tests/construct_runtime_ui/invalid_meta_literal.stderr b/substrate/frame/support/test/tests/construct_runtime_ui/invalid_meta_literal.stderr new file mode 100644 index 0000000000000000000000000000000000000000..68366a3410bf16a405ac005119d72e3600b8a06f --- /dev/null +++ b/substrate/frame/support/test/tests/construct_runtime_ui/invalid_meta_literal.stderr @@ -0,0 +1,6 @@ +error: feature = 1 + ^ expected one of `<key>`, `all`, `any`, `not` here + --> tests/construct_runtime_ui/invalid_meta_literal.rs:10:3 + | +10 | #[cfg(feature = 1)] + | ^ diff --git a/substrate/frame/support/test/tests/construct_runtime_ui/unsupported_meta_structure.rs b/substrate/frame/support/test/tests/construct_runtime_ui/unsupported_meta_structure.rs new file mode 100644 index 0000000000000000000000000000000000000000..b93adf9a780a771fdb9fead346b49f3eb32f48d1 --- /dev/null +++ b/substrate/frame/support/test/tests/construct_runtime_ui/unsupported_meta_structure.rs @@ -0,0 +1,15 @@ +use frame_support::construct_runtime; + +construct_runtime! { + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic + { + System: system::{Pallet}, + #[cfg(feature(test))] + Balance: balances::{Config, Call}, + } +} + +fn main() {} diff --git a/substrate/frame/support/test/tests/construct_runtime_ui/unsupported_meta_structure.stderr b/substrate/frame/support/test/tests/construct_runtime_ui/unsupported_meta_structure.stderr new file mode 100644 index 0000000000000000000000000000000000000000..98d99a0d34997aaf1512c89950c91d401cc15e79 --- /dev/null +++ b/substrate/frame/support/test/tests/construct_runtime_ui/unsupported_meta_structure.stderr @@ -0,0 +1,6 @@ +error: feature(test) + ^ expected one of `=`, `,`, `)` here + --> tests/construct_runtime_ui/unsupported_meta_structure.rs:10:3 + | +10 | #[cfg(feature(test))] + | ^ diff --git a/substrate/frame/support/test/tests/construct_runtime_ui/unsupported_pallet_attr.rs b/substrate/frame/support/test/tests/construct_runtime_ui/unsupported_pallet_attr.rs new file mode 100644 index 0000000000000000000000000000000000000000..3ec8b9db1d435013aa4ab9c7b7808f1f97d95b95 --- /dev/null +++ b/substrate/frame/support/test/tests/construct_runtime_ui/unsupported_pallet_attr.rs @@ -0,0 +1,15 @@ +use frame_support::construct_runtime; + +construct_runtime! { + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic + { + System: system::{Pallet}, + #[attr] + Balance: balances::{Config, Call}, + } +} + +fn main() {} diff --git a/substrate/frame/support/test/tests/construct_runtime_ui/unsupported_pallet_attr.stderr b/substrate/frame/support/test/tests/construct_runtime_ui/unsupported_pallet_attr.stderr new file mode 100644 index 0000000000000000000000000000000000000000..fceb2b8a99db8f18578c099e02bf5b0612874efb --- /dev/null +++ b/substrate/frame/support/test/tests/construct_runtime_ui/unsupported_pallet_attr.stderr @@ -0,0 +1,5 @@ +error: Unsupported attribute, only #[cfg] is supported on pallet declarations in `construct_runtime` + --> tests/construct_runtime_ui/unsupported_pallet_attr.rs:10:3 + | +10 | #[attr] + | ^ diff --git a/substrate/frame/support/test/tests/pallet.rs b/substrate/frame/support/test/tests/pallet.rs index e96e52d3d2c6e3a354398e2fa2d499c6dc899d6b..497b7bb04c36aac7b9da4ea8aca74635a884804d 100644 --- a/substrate/frame/support/test/tests/pallet.rs +++ b/substrate/frame/support/test/tests/pallet.rs @@ -626,6 +626,8 @@ frame_support::construct_runtime!( System: frame_system exclude_parts { Pallet, Storage }, Example: pallet, Example2: pallet2 exclude_parts { Call }, + #[cfg(feature = "example3")] + Example3: pallet3, Example4: pallet4 use_parts { Call }, } );