diff --git a/Cargo.lock b/Cargo.lock index 72a71ad05ddfb..3d51a0fb1ffa4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3002,6 +3002,7 @@ dependencies = [ "sp-std", "sp-tracing", "sp-weights", + "static_assertions", "tt-call", ] diff --git a/frame/balances/src/lib.rs b/frame/balances/src/lib.rs index 90bee7d99e121..f94b3230b917b 100644 --- a/frame/balances/src/lib.rs +++ b/frame/balances/src/lib.rs @@ -220,11 +220,14 @@ pub mod pallet { pub struct TestDefaultConfig; - #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] + #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig, no_aggregated_types)] impl frame_system::DefaultConfig for TestDefaultConfig {} #[frame_support::register_default_impl(TestDefaultConfig)] impl DefaultConfig for TestDefaultConfig { + #[inject_runtime_type] + type RuntimeEvent = (); + type Balance = u64; type ReserveIdentifier = (); @@ -242,6 +245,7 @@ pub mod pallet { #[pallet::config(with_default)] pub trait Config: frame_system::Config { /// The overarching event type. + #[pallet::no_default_bounds] type RuntimeEvent: From> + IsType<::RuntimeEvent>; diff --git a/frame/examples/default-config/src/lib.rs b/frame/examples/default-config/src/lib.rs index 8715b8c45ff0e..d2eade0ccff1e 100644 --- a/frame/examples/default-config/src/lib.rs +++ b/frame/examples/default-config/src/lib.rs @@ -87,7 +87,7 @@ pub mod pallet { /// A type providing default configurations for this pallet in testing environment. pub struct TestDefaultConfig; - #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] + #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig, no_aggregated_types)] impl frame_system::DefaultConfig for TestDefaultConfig {} #[frame_support::register_default_impl(TestDefaultConfig)] @@ -109,7 +109,7 @@ pub mod pallet { /// example, we simple derive `frame_system::config_preludes::TestDefaultConfig` again. pub struct OtherDefaultConfig; - #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] + #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig, no_aggregated_types)] impl frame_system::DefaultConfig for OtherDefaultConfig {} #[frame_support::register_default_impl(OtherDefaultConfig)] @@ -147,16 +147,7 @@ pub mod tests { #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Runtime { // these items are defined by frame-system as `no_default`, so we must specify them here. - // Note that these are types that actually rely on the outer runtime, and can't sensibly - // have an _independent_ default. type Block = Block; - type BlockHashCount = frame_support::traits::ConstU64<10>; - type BaseCallFilter = frame_support::traits::Everything; - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type RuntimeEvent = RuntimeEvent; - type PalletInfo = PalletInfo; - type OnSetCode = (); // all of this is coming from `frame_system::config_preludes::TestDefaultConfig`. @@ -177,6 +168,17 @@ pub mod tests { // type BlockWeights = (); // type BlockLength = (); // type DbWeight = (); + // type BaseCallFilter = frame_support::traits::Everything; + // type BlockHashCount = frame_support::traits::ConstU64<10>; + // type OnSetCode = (); + + // These are marked as `#[inject_runtime_type]`. Hence, they are being injected as + // types generated by `construct_runtime`. + + // type RuntimeOrigin = RuntimeOrigin; + // type RuntimeCall = RuntimeCall; + // type RuntimeEvent = RuntimeEvent; + // type PalletInfo = PalletInfo; // you could still overwrite any of them if desired. type SS58Prefix = frame_support::traits::ConstU16<456>; diff --git a/frame/support/Cargo.toml b/frame/support/Cargo.toml index 2b3d15e2ec0db..d115a83722ac8 100644 --- a/frame/support/Cargo.toml +++ b/frame/support/Cargo.toml @@ -44,6 +44,7 @@ environmental = { version = "1.1.4", default-features = false } sp-genesis-builder = { version = "0.1.0", default-features=false, path = "../../primitives/genesis-builder" } serde_json = { version = "1.0.85", default-features = false, features = ["alloc"] } docify = "0.2.1" +static_assertions = "1.1.0" aquamarine = { version = "0.3.2" } diff --git a/frame/support/procedural/src/derive_impl.rs b/frame/support/procedural/src/derive_impl.rs index 5ea44c35bb646..8b5e334f1f551 100644 --- a/frame/support/procedural/src/derive_impl.rs +++ b/frame/support/procedural/src/derive_impl.rs @@ -22,14 +22,46 @@ use macro_magic::mm_core::ForeignPath; use proc_macro2::TokenStream as TokenStream2; use quote::{quote, ToTokens}; use std::collections::HashSet; -use syn::{parse2, parse_quote, spanned::Spanned, Ident, ImplItem, ItemImpl, Path, Result, Token}; +use syn::{ + parse2, parse_quote, spanned::Spanned, token, Ident, ImplItem, ItemImpl, Path, Result, Token, +}; -#[derive(Parse)] +mod keyword { + syn::custom_keyword!(inject_runtime_type); + syn::custom_keyword!(no_aggregated_types); +} + +#[derive(derive_syn_parse::Parse, PartialEq, Eq)] +pub enum PalletAttrType { + #[peek(keyword::inject_runtime_type, name = "inject_runtime_type")] + RuntimeType(keyword::inject_runtime_type), +} + +#[derive(derive_syn_parse::Parse)] +pub struct PalletAttr { + _pound: Token![#], + #[bracket] + _bracket: token::Bracket, + #[inside(_bracket)] + typ: PalletAttrType, +} + +fn get_first_item_pallet_attr(item: &syn::ImplItemType) -> syn::Result> +where + Attr: syn::parse::Parse, +{ + item.attrs.get(0).map(|a| syn::parse2(a.into_token_stream())).transpose() +} + +#[derive(Parse, Debug)] pub struct DeriveImplAttrArgs { pub default_impl_path: Path, _as: Option, #[parse_if(_as.is_some())] pub disambiguation_path: Option, + _comma: Option, + #[parse_if(_comma.is_some())] + pub no_aggregated_types: Option, } impl ForeignPath for DeriveImplAttrArgs { @@ -43,6 +75,8 @@ impl ToTokens for DeriveImplAttrArgs { tokens.extend(self.default_impl_path.to_token_stream()); tokens.extend(self._as.to_token_stream()); tokens.extend(self.disambiguation_path.to_token_stream()); + tokens.extend(self._comma.to_token_stream()); + tokens.extend(self.no_aggregated_types.to_token_stream()); } } @@ -78,6 +112,7 @@ fn combine_impls( foreign_impl: ItemImpl, default_impl_path: Path, disambiguation_path: Path, + inject_runtime_types: bool, ) -> ItemImpl { let (existing_local_keys, existing_unsupported_items): (HashSet, HashSet) = local_impl @@ -96,7 +131,20 @@ fn combine_impls( // do not copy colliding items that have an ident return None } - if matches!(item, ImplItem::Type(_)) { + if let ImplItem::Type(typ) = item.clone() { + let mut typ = typ.clone(); + if let Ok(Some(PalletAttr { typ: PalletAttrType::RuntimeType(_), .. })) = + get_first_item_pallet_attr::(&mut typ) + { + let item: ImplItem = if inject_runtime_types { + parse_quote! { + type #ident = #ident; + } + } else { + item + }; + return Some(item) + } // modify and insert uncolliding type items let modified_item: ImplItem = parse_quote! { type #ident = <#default_impl_path as #disambiguation_path>::#ident; @@ -132,6 +180,7 @@ pub fn derive_impl( foreign_tokens: TokenStream2, local_tokens: TokenStream2, disambiguation_path: Option, + no_aggregated_types: Option, ) -> Result { let local_impl = parse2::(local_tokens)?; let foreign_impl = parse2::(foreign_tokens)?; @@ -151,8 +200,13 @@ pub fn derive_impl( }; // generate the combined impl - let combined_impl = - combine_impls(local_impl, foreign_impl, default_impl_path, disambiguation_path); + let combined_impl = combine_impls( + local_impl, + foreign_impl, + default_impl_path, + disambiguation_path, + no_aggregated_types.is_none(), + ); Ok(quote!(#combined_impl)) } diff --git a/frame/support/procedural/src/lib.rs b/frame/support/procedural/src/lib.rs index 2a46696ed4f70..9957cf1cff85c 100644 --- a/frame/support/procedural/src/lib.rs +++ b/frame/support/procedural/src/lib.rs @@ -38,7 +38,7 @@ use macro_magic::import_tokens_attr; use proc_macro::TokenStream; use quote::{quote, ToTokens}; use std::{cell::RefCell, str::FromStr}; -use syn::{parse_macro_input, Error, ItemImpl, ItemMod}; +use syn::{parse_macro_input, Error, ItemImpl, ItemMod, TraitItemType}; pub(crate) const INHERENT_INSTANCE_NAME: &str = "__InherentHiddenInstance"; @@ -596,6 +596,19 @@ pub fn storage_alias(attributes: TokenStream, input: TokenStream) -> TokenStream /// /// Conversely, the `default_impl_path` argument is required and cannot be omitted. /// +/// Optionally, `no_aggregated_types` can be specified as follows: +/// +/// ```ignore +/// #[derive_impl(default_impl_path as disambiguation_path, no_aggregated_types)] +/// impl SomeTrait for SomeStruct { +/// ... +/// } +/// ``` +/// +/// If specified, this indicates that the aggregated types (as denoted by impl items +/// attached with [`#[inject_runtime_type]`]) should not be injected with the respective concrete +/// types. By default, all such types are injected. +/// /// You can also make use of `#[pallet::no_default]` on specific items in your default impl that you /// want to ensure will not be copied over but that you nonetheless want to use locally in the /// context of the foreign impl and the pallet (or context) in which it is defined. @@ -759,6 +772,7 @@ pub fn derive_impl(attrs: TokenStream, input: TokenStream) -> TokenStream { attrs.into(), input.into(), custom_attrs.disambiguation_path, + custom_attrs.no_aggregated_types, ) .unwrap_or_else(|r| r.into_compile_error()) .into() @@ -774,6 +788,19 @@ pub fn no_default(_: TokenStream, _: TokenStream) -> TokenStream { pallet_macro_stub() } +/// The optional attribute `#[pallet::no_default_bounds]` can be attached to trait items within a +/// `Config` trait impl that has [`#[pallet::config(with_default)]`](`macro@config`) attached. +/// +/// Attaching this attribute to a trait item ensures that the generated trait `DefaultConfig` +/// will not have any bounds for this trait item. +/// +/// As an example, if you have a trait item `type AccountId: SomeTrait;` in your `Config` trait, +/// the generated `DefaultConfig` will only have `type AccountId;` with no trait bound. +#[proc_macro_attribute] +pub fn no_default_bounds(_: TokenStream, _: TokenStream) -> TokenStream { + pallet_macro_stub() +} + /// Attach this attribute to an impl statement that you want to use with /// [`#[derive_impl(..)]`](`macro@derive_impl`). /// @@ -843,6 +870,25 @@ pub fn register_default_impl(attrs: TokenStream, tokens: TokenStream) -> TokenSt } } +#[proc_macro_attribute] +pub fn inject_runtime_type(_: TokenStream, tokens: TokenStream) -> TokenStream { + let item = tokens.clone(); + let item = syn::parse_macro_input!(item as TraitItemType); + if item.ident != "RuntimeCall" && + item.ident != "RuntimeEvent" && + item.ident != "RuntimeOrigin" && + item.ident != "PalletInfo" + { + return syn::Error::new_spanned( + item, + "`#[inject_runtime_type]` can only be attached to `RuntimeCall`, `RuntimeEvent`, `RuntimeOrigin` or `PalletInfo`", + ) + .to_compile_error() + .into(); + } + tokens +} + /// Used internally to decorate pallet attribute macro stubs when they are erroneously used /// outside of a pallet module fn pallet_macro_stub() -> TokenStream { diff --git a/frame/support/procedural/src/pallet/expand/config.rs b/frame/support/procedural/src/pallet/expand/config.rs index c1b8eb022471f..5cf4035a8f8b9 100644 --- a/frame/support/procedural/src/pallet/expand/config.rs +++ b/frame/support/procedural/src/pallet/expand/config.rs @@ -52,7 +52,23 @@ Consequently, a runtime that wants to include this pallet must implement this tr // impossible consequently. match &config.default_sub_trait { Some(default_sub_trait) if default_sub_trait.items.len() > 0 => { - let trait_items = &default_sub_trait.items; + let trait_items = &default_sub_trait + .items + .iter() + .map(|item| { + if item.1 { + if let syn::TraitItem::Type(item) = item.0.clone() { + let mut item = item.clone(); + item.bounds.clear(); + syn::TraitItem::Type(item) + } else { + item.0.clone() + } + } else { + item.0.clone() + } + }) + .collect::>(); let type_param_bounds = if default_sub_trait.has_system { let system = &def.frame_system; diff --git a/frame/support/procedural/src/pallet/parse/config.rs b/frame/support/procedural/src/pallet/parse/config.rs index c60bf1f3cbea7..e505f8b04119b 100644 --- a/frame/support/procedural/src/pallet/parse/config.rs +++ b/frame/support/procedural/src/pallet/parse/config.rs @@ -34,12 +34,16 @@ mod keyword { syn::custom_keyword!(frame_system); syn::custom_keyword!(disable_frame_system_supertrait_check); syn::custom_keyword!(no_default); + syn::custom_keyword!(no_default_bounds); syn::custom_keyword!(constant); } #[derive(Default)] pub struct DefaultTrait { - pub items: Vec, + /// A bool for each sub-trait item indicates whether the item has + /// `#[pallet::no_default_bounds]` attached to it. If true, the item will not have any bounds + /// in the generated default sub-trait. + pub items: Vec<(syn::TraitItem, bool)>, pub has_system: bool, } @@ -142,6 +146,8 @@ impl syn::parse::Parse for DisableFrameSystemSupertraitCheck { pub enum PalletAttrType { #[peek(keyword::no_default, name = "no_default")] NoDefault(keyword::no_default), + #[peek(keyword::no_default_bounds, name = "no_default_bounds")] + NoBounds(keyword::no_default_bounds), #[peek(keyword::constant, name = "constant")] Constant(keyword::constant), } @@ -366,6 +372,7 @@ impl ConfigDef { let mut already_no_default = false; let mut already_constant = false; + let mut already_no_default_bounds = false; while let Ok(Some(pallet_attr)) = helper::take_first_item_pallet_attr::(trait_item) @@ -403,15 +410,31 @@ impl ConfigDef { already_no_default = true; }, + (PalletAttrType::NoBounds(_), _) => { + if !enable_default { + return Err(syn::Error::new( + pallet_attr._bracket.span.join(), + "`#[pallet:no_default_bounds]` can only be used if `#[pallet::config(with_default)]` \ + has been specified" + )) + } + if already_no_default_bounds { + return Err(syn::Error::new( + pallet_attr._bracket.span.join(), + "Duplicate #[pallet::no_default_bounds] attribute not allowed.", + )) + } + already_no_default_bounds = true; + }, } } - if !already_no_default && !is_event && enable_default { + if !already_no_default && enable_default { default_sub_trait .as_mut() .expect("is 'Some(_)' if 'enable_default'; qed") .items - .push(trait_item.clone()); + .push((trait_item.clone(), already_no_default_bounds)); } } diff --git a/frame/support/src/lib.rs b/frame/support/src/lib.rs index eee68233f3a56..a2a7e5ebc485f 100644 --- a/frame/support/src/lib.rs +++ b/frame/support/src/lib.rs @@ -830,6 +830,21 @@ pub mod pallet_prelude { }; pub use codec::{Decode, Encode, MaxEncodedLen}; pub use frame_support::pallet_macros::*; + /// The optional attribute `#[inject_runtime_type]` can be attached to `RuntimeCall`, + /// `RuntimeEvent`, `RuntimeOrigin` or `PalletInfo` in an impl statement that has + /// `#[register_default_impl]` attached to indicate that this item is generated by + /// `construct_runtime`. + /// + /// Attaching this attribute to such an item ensures that the combined impl generated via + /// [`#[derive_impl(..)]`](`macro@super::derive_impl`) will use the correct type + /// auto-generated by `construct_runtime!`. + #[doc = docify::embed!("src/tests/inject_runtime_type.rs", derive_impl_works_with_runtime_type_injection)] + /// + /// However, if `no_aggregated_types` is specified while using + /// `[`#[derive_impl(..)]`](`macro@super::derive_impl`)`, then these items are attached + /// verbatim to the combined impl. + #[doc = docify::embed!("src/tests/inject_runtime_type.rs", derive_impl_works_with_no_aggregated_types)] + pub use frame_support_procedural::inject_runtime_type; pub use frame_support_procedural::register_default_impl; pub use scale_info::TypeInfo; pub use sp_inherents::MakeFatalError; @@ -2176,8 +2191,8 @@ pub mod pallet_macros { call_index, compact, composite_enum, config, constant, disable_frame_system_supertrait_check, error, event, extra_constants, generate_deposit, generate_store, genesis_build, genesis_config, getter, hooks, import_section, inherent, - no_default, origin, pallet_section, storage, storage_prefix, storage_version, type_value, - unbounded, validate_unsigned, weight, whitelist_storage, + no_default, no_default_bounds, origin, pallet_section, storage, storage_prefix, + storage_version, type_value, unbounded, validate_unsigned, weight, whitelist_storage, }; } diff --git a/frame/support/src/tests/inject_runtime_type.rs b/frame/support/src/tests/inject_runtime_type.rs new file mode 100644 index 0000000000000..429a743d3b7b2 --- /dev/null +++ b/frame/support/src/tests/inject_runtime_type.rs @@ -0,0 +1,47 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::{Config, Runtime}; +use crate::{derive_impl, pallet_prelude::inject_runtime_type}; +use static_assertions::assert_type_eq_all; + +#[docify::export] +#[test] +fn derive_impl_works_with_runtime_type_injection() { + assert_type_eq_all!(::RuntimeOrigin, super::RuntimeOrigin); + assert_type_eq_all!(::RuntimeCall, super::RuntimeCall); + assert_type_eq_all!(::PalletInfo, super::PalletInfo); +} + +#[docify::export] +#[test] +fn derive_impl_works_with_no_aggregated_types() { + struct DummyRuntime; + + #[derive_impl( + super::frame_system::config_preludes::TestDefaultConfig as super::frame_system::DefaultConfig, + no_aggregated_types + )] + impl Config for DummyRuntime { + type Block = super::Block; + type AccountId = super::AccountId; + type PalletInfo = super::PalletInfo; + } + + assert_type_eq_all!(::RuntimeOrigin, ()); + assert_type_eq_all!(::RuntimeCall, ()); +} diff --git a/frame/support/src/tests/mod.rs b/frame/support/src/tests/mod.rs index 5ba9aef7fad16..db458880db683 100644 --- a/frame/support/src/tests/mod.rs +++ b/frame/support/src/tests/mod.rs @@ -25,6 +25,7 @@ use sp_runtime::{generic, traits::BlakeTwo256, BuildStorage}; pub use self::frame_system::{pallet_prelude::*, Config, Pallet}; +mod inject_runtime_type; mod storage_alias; #[pallet] @@ -34,17 +35,40 @@ pub mod frame_system { pub use crate::dispatch::RawOrigin; use crate::pallet_prelude::*; + pub mod config_preludes { + use super::{inject_runtime_type, DefaultConfig}; + pub struct TestDefaultConfig; + + #[crate::register_default_impl(TestDefaultConfig)] + impl DefaultConfig for TestDefaultConfig { + type AccountId = u64; + type BaseCallFilter = frame_support::traits::Everything; + #[inject_runtime_type] + type RuntimeOrigin = (); + #[inject_runtime_type] + type RuntimeCall = (); + #[inject_runtime_type] + type PalletInfo = (); + type DbWeight = (); + } + } + #[pallet::pallet] pub struct Pallet(_); - #[pallet::config] + #[pallet::config(with_default)] #[pallet::disable_frame_system_supertrait_check] pub trait Config: 'static { + #[pallet::no_default] type Block: Parameter + sp_runtime::traits::Block; type AccountId; + #[pallet::no_default_bounds] type BaseCallFilter: crate::traits::Contains; + #[pallet::no_default_bounds] type RuntimeOrigin; + #[pallet::no_default_bounds] type RuntimeCall; + #[pallet::no_default_bounds] type PalletInfo: crate::traits::PalletInfo; type DbWeight: Get; } @@ -168,14 +192,10 @@ crate::construct_runtime!( } ); +#[crate::derive_impl(self::frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl Config for Runtime { type Block = Block; type AccountId = AccountId; - type BaseCallFilter = crate::traits::Everything; - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type PalletInfo = PalletInfo; - type DbWeight = (); } fn new_test_ext() -> TestExternalities { diff --git a/frame/support/test/tests/derive_impl_ui/inject_runtime_type_fails_when_type_not_in_scope.rs b/frame/support/test/tests/derive_impl_ui/inject_runtime_type_fails_when_type_not_in_scope.rs new file mode 100644 index 0000000000000..0d8dc8eb1d472 --- /dev/null +++ b/frame/support/test/tests/derive_impl_ui/inject_runtime_type_fails_when_type_not_in_scope.rs @@ -0,0 +1,23 @@ +use frame_support::{*, pallet_prelude::inject_runtime_type}; +use static_assertions::assert_type_eq_all; + +pub trait Config { + type RuntimeCall; +} + +struct Pallet; + +#[register_default_impl(Pallet)] +impl Config for Pallet { + #[inject_runtime_type] + type RuntimeCall = (); +} + +struct SomePallet; + +#[derive_impl(Pallet)] // Injects type RuntimeCall = RuntimeCall; +impl Config for SomePallet {} + +assert_type_eq_all!(::RuntimeCall, u32); + +fn main() {} diff --git a/frame/support/test/tests/derive_impl_ui/inject_runtime_type_fails_when_type_not_in_scope.stderr b/frame/support/test/tests/derive_impl_ui/inject_runtime_type_fails_when_type_not_in_scope.stderr new file mode 100644 index 0000000000000..683131cceb8dc --- /dev/null +++ b/frame/support/test/tests/derive_impl_ui/inject_runtime_type_fails_when_type_not_in_scope.stderr @@ -0,0 +1,10 @@ +error[E0412]: cannot find type `RuntimeCall` in this scope + --> tests/derive_impl_ui/inject_runtime_type_fails_when_type_not_in_scope.rs:13:10 + | +13 | type RuntimeCall = (); + | ^^^^^^^^^^^ help: you might have meant to use the associated type: `Self::RuntimeCall` +... +18 | #[derive_impl(Pallet)] // Injects type RuntimeCall = RuntimeCall; + | ---------------------- in this macro invocation + | + = note: this error originates in the macro `__export_tokens_tt_pallet` which comes from the expansion of the macro `frame_support::macro_magic::forward_tokens` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/frame/support/test/tests/derive_impl_ui/inject_runtime_type_invalid.rs b/frame/support/test/tests/derive_impl_ui/inject_runtime_type_invalid.rs new file mode 100644 index 0000000000000..60ec710d0154c --- /dev/null +++ b/frame/support/test/tests/derive_impl_ui/inject_runtime_type_invalid.rs @@ -0,0 +1,25 @@ +use frame_support::{*, pallet_prelude::inject_runtime_type}; +use static_assertions::assert_type_eq_all; + +pub trait Config { + type RuntimeInfo; +} + +type RuntimeInfo = u32; + +struct Pallet; + +#[register_default_impl(Pallet)] +impl Config for Pallet { + #[inject_runtime_type] + type RuntimeInfo = (); +} + +struct SomePallet; + +#[derive_impl(Pallet)] // Injects type RuntimeInfo = RuntimeInfo; +impl Config for SomePallet {} + +assert_type_eq_all!(::RuntimeInfo, u32); + +fn main() {} diff --git a/frame/support/test/tests/derive_impl_ui/inject_runtime_type_invalid.stderr b/frame/support/test/tests/derive_impl_ui/inject_runtime_type_invalid.stderr new file mode 100644 index 0000000000000..c3382510744a7 --- /dev/null +++ b/frame/support/test/tests/derive_impl_ui/inject_runtime_type_invalid.stderr @@ -0,0 +1,14 @@ +error: `#[inject_runtime_type]` can only be attached to `RuntimeCall`, `RuntimeEvent`, `RuntimeOrigin` or `PalletInfo` + --> tests/derive_impl_ui/inject_runtime_type_invalid.rs:15:5 + | +15 | type RuntimeInfo = (); + | ^^^^^^^^^^^^^^^^^^^^^^ + +error[E0046]: not all trait items implemented, missing: `RuntimeInfo` + --> tests/derive_impl_ui/inject_runtime_type_invalid.rs:13:1 + | +5 | type RuntimeInfo; + | ---------------- `RuntimeInfo` from trait +... +13 | impl Config for Pallet { + | ^^^^^^^^^^^^^^^^^^^^^^ missing `RuntimeInfo` in implementation diff --git a/frame/support/test/tests/derive_impl_ui/pass/runtime_type_working.rs b/frame/support/test/tests/derive_impl_ui/pass/runtime_type_working.rs new file mode 100644 index 0000000000000..04ad008944682 --- /dev/null +++ b/frame/support/test/tests/derive_impl_ui/pass/runtime_type_working.rs @@ -0,0 +1,25 @@ +use frame_support::{*, pallet_prelude::inject_runtime_type}; +use static_assertions::assert_type_eq_all; + +pub trait Config { + type RuntimeCall; +} + +type RuntimeCall = u32; + +struct Pallet; + +#[register_default_impl(Pallet)] +impl Config for Pallet { + #[inject_runtime_type] + type RuntimeCall = (); +} + +struct SomePallet; + +#[derive_impl(Pallet)] // Injects type RuntimeCall = RuntimeCall; +impl Config for SomePallet {} + +assert_type_eq_all!(::RuntimeCall, u32); + +fn main() {} diff --git a/frame/support/test/tests/pallet_ui/default_config_with_no_default_in_system.rs b/frame/support/test/tests/pallet_ui/default_config_with_no_default_in_system.rs index 7127ecf78594e..026b74c914a13 100644 --- a/frame/support/test/tests/pallet_ui/default_config_with_no_default_in_system.rs +++ b/frame/support/test/tests/pallet_ui/default_config_with_no_default_in_system.rs @@ -5,7 +5,7 @@ mod pallet { #[pallet::config(with_default)] pub trait Config: frame_system::Config { #[pallet::constant] - type MyGetParam2: Get; + type MyGetParam2: Get; } #[pallet::pallet] diff --git a/frame/support/test/tests/pallet_ui/default_config_with_no_default_in_system.stderr b/frame/support/test/tests/pallet_ui/default_config_with_no_default_in_system.stderr index bc657f8f654ec..17d8181135674 100644 --- a/frame/support/test/tests/pallet_ui/default_config_with_no_default_in_system.stderr +++ b/frame/support/test/tests/pallet_ui/default_config_with_no_default_in_system.stderr @@ -1,5 +1,5 @@ -error[E0220]: associated type `RuntimeCall` not found for `Self` +error[E0220]: associated type `Block` not found for `Self` --> tests/pallet_ui/default_config_with_no_default_in_system.rs:8:31 | -8 | type MyGetParam2: Get; - | ^^^^^^^^^^^ associated type `RuntimeCall` not found +8 | type MyGetParam2: Get; + | ^^^^^ there is a similarly named associated type `Block` in the trait `frame_system::Config` diff --git a/frame/support/test/tests/pallet_ui/no_default_bounds_but_missing_with_default.rs b/frame/support/test/tests/pallet_ui/no_default_bounds_but_missing_with_default.rs new file mode 100644 index 0000000000000..123d791417498 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/no_default_bounds_but_missing_with_default.rs @@ -0,0 +1,23 @@ +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + #[pallet::constant] + #[pallet::no_default_bounds] + type MyGetParam2: Get; + } + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::call] + impl Pallet {} +} + +fn main() {} diff --git a/frame/support/test/tests/pallet_ui/no_default_bounds_but_missing_with_default.stderr b/frame/support/test/tests/pallet_ui/no_default_bounds_but_missing_with_default.stderr new file mode 100644 index 0000000000000..cbed14bca2cd4 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/no_default_bounds_but_missing_with_default.stderr @@ -0,0 +1,5 @@ +error: `#[pallet:no_default_bounds]` can only be used if `#[pallet::config(with_default)]` has been specified + --> tests/pallet_ui/no_default_bounds_but_missing_with_default.rs:9:4 + | +9 | #[pallet::no_default_bounds] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/frame/system/src/lib.rs b/frame/system/src/lib.rs index 52956085fe3bb..84b6dc031457d 100644 --- a/frame/system/src/lib.rs +++ b/frame/system/src/lib.rs @@ -205,7 +205,7 @@ pub mod pallet { /// Default implementations of [`DefaultConfig`], which can be used to implement [`Config`]. pub mod config_preludes { - use super::DefaultConfig; + use super::{inject_runtime_type, DefaultConfig}; /// Provides a viable default config that can be used with /// [`derive_impl`](`frame_support::derive_impl`) to derive a testing pallet config @@ -232,6 +232,17 @@ pub mod pallet { type BlockWeights = (); type BlockLength = (); type DbWeight = (); + #[inject_runtime_type] + type RuntimeEvent = (); + #[inject_runtime_type] + type RuntimeOrigin = (); + #[inject_runtime_type] + type RuntimeCall = (); + #[inject_runtime_type] + type PalletInfo = (); + type BaseCallFilter = frame_support::traits::Everything; + type BlockHashCount = frame_support::traits::ConstU64<10>; + type OnSetCode = (); } } @@ -240,6 +251,7 @@ pub mod pallet { #[pallet::disable_frame_system_supertrait_check] pub trait Config: 'static + Eq + Clone { /// The aggregated event type of the runtime. + #[pallet::no_default_bounds] type RuntimeEvent: Parameter + Member + From> @@ -256,7 +268,7 @@ pub mod pallet { /// [`frame_support::traits::InsideBoth`], [`frame_support::traits::TheseExcept`] or /// [`frame_support::traits::EverythingBut`] et al. The default would be /// [`frame_support::traits::Everything`]. - #[pallet::no_default] + #[pallet::no_default_bounds] type BaseCallFilter: Contains; /// Block & extrinsics weights: base values and limits. @@ -268,14 +280,14 @@ pub mod pallet { type BlockLength: Get; /// The `RuntimeOrigin` type used by dispatchable calls. - #[pallet::no_default] + #[pallet::no_default_bounds] type RuntimeOrigin: Into, Self::RuntimeOrigin>> + From> + Clone + OriginTrait; /// The aggregated `RuntimeCall` type. - #[pallet::no_default] + #[pallet::no_default_bounds] type RuntimeCall: Parameter + Dispatchable + Debug @@ -335,7 +347,7 @@ pub mod pallet { /// Maximum number of block number to block hash mappings to keep (oldest pruned first). #[pallet::constant] - #[pallet::no_default] + #[pallet::no_default_bounds] type BlockHashCount: Get>; /// The weight of runtime database operations the runtime can invoke. @@ -350,7 +362,9 @@ pub mod pallet { /// /// Expects the `PalletInfo` type that is being generated by `construct_runtime!` in the /// runtime. - #[pallet::no_default] + /// + /// For tests it is okay to use `()` as type, however it will provide "useless" data. + #[pallet::no_default_bounds] type PalletInfo: PalletInfo; /// Data to be associated with an account (other than nonce/transaction counter, which this @@ -382,7 +396,7 @@ pub mod pallet { /// [`Pallet::update_code_in_storage`]). /// It's unlikely that this needs to be customized, unless you are writing a parachain using /// `Cumulus`, where the actual code change is deferred. - #[pallet::no_default] + #[pallet::no_default_bounds] type OnSetCode: SetCode; /// The maximum number of consumers allowed on a single account.