Default Pallet Config Trait / derive_impl (#13454)

* first draft, probably won't work

* first draft, probably won't work

* good progress..

* good milestone, still a lot to do.

* EVERYTHING WORKS

* Update frame/support/procedural/src/derive_impl.rs

Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Update frame/support/procedural/src/derive_impl.rs

Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* clean up + cargo fmt

* import tokens WIP

* export_tokens working with impl Trait

* WIP / notes

* use macro_magic 0.2.0's export_tokens to access foreign items

* token importing working properly using macro_magic 0.2.5

* combine_impls almost working

* successfully get foreign path via macro_magic 0.2.6

* combine_impls using implementing_type generics

* working + clean up

* more clean up

* decrease rightwards drift and add docs to combine_impls

* add support for macros to impl_item_ident in case we hit that

* add docs for impl_item_ident method

* fix no_std issues

* re-export of macro_magic working in pallets 🎉

* clean up + fully resolve no_std issue with macro_magic with v0.2.11

* remove trait item code for different trait item types since this
is now handled directly by combine_impls

* clean up

* remove dev comments

* only generate default trait if #[pallet::default_trait] is attached

* authorship and most other pallets now compiling

* compiling 🎉

* add check for more than two pallet attributes on Config trait

* remove unused import in nomination-pool

* clean up debug code

* upgrade to macro_magic v0.2.12

* add neater #[register_default_config(SomeIdent)] macro

* really just a thin wrapper around #[export_tokens]

* upgrade to macro_magic 0.3.1

* rewrite parsing to be compatible with syn 2.x, compiling 🎉

* remove unused keywords

* macro stubs for the new pallet:: macros, preliminary docs

* upgrade to macro_magic v0.3.2

* rename register_default_config => register_default_impl

* bump to macro_magic v0.3.3

* custom disambiguation_path working as 2nd arg to derive_impl

* overhaul docs

* fixes, ident-style paths shortcut working

* remove ident-style shortcut because it makes testing difficult

* add passing UI tests for derive_impl

* switch to `ForeignPath as DisambiguationPath` syntax + update docs

* add UI test for bad foreign path

* add UI test for bad disambiguation path

* add UI test for missing disambiguation path

* add UI test for attached to non impl

* fix derive_impl_attr_args_parsing test

* move tests to bottom

* fix nightly issue

* add doc notes on importing/re-exporting

* remove explicit use of macro_magic::use_attr

Co-authored-by: Bastian Köcher <git@kchr.de>

* use explicit macro_magic::use_attr

Co-authored-by: Bastian Köcher <git@kchr.de>

* remove unneeded {}

Co-authored-by: Bastian Köcher <git@kchr.de>

* remove unneeded collect

Co-authored-by: Bastian Köcher <git@kchr.de>

* add docs for TestDefaultConfig

* remove unneeded `#[export_tokens]` on `DefaultConfig`

* add docs for auto-generated `DefaultConfig`

* no need to clone

Co-authored-by: Bastian Köcher <git@kchr.de>

* clean up combine_impls + compiling again

* remove unused dependency

* simplify struct definition

Co-authored-by: Bastian Köcher <git@kchr.de>

* fix register_default_impl docs

* reduce rightward drift / refactor

Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>

* fix derive_impl after keith's changes

* simplify disambiguation_path calculation

Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>

* compiling again

* simplify parsing of trait item

Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>

* rename preludes => prelude

Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>

* fix other places where we used preludes instead of prelude

* fix indents

* simplify PalletAttr parsing

Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>

* go back to having no_default and constant as keywords

* make it more clear that disambiguation_path is optional

* make default_trait_items just a Vec instead of Option<Vec>

* rename foreign_path => default_impl_path within substrate

* fix docs

* Change {} to ;

Co-authored-by: Bastian Köcher <git@kchr.de>

* highlight full end-to-end example with link

* add pallet-default-config-example, start by copying dev mode code

* update dev-mode specific docs

* use Person and Points instead of Dummy and Bar

* add docs to example pallet

* revert changes to pallets other than the default config example

* fix outdated references to basic example pallet

* re-order docs to be a bit more clear

* better errors for extra attributes

* add UI tests for duplicate/extra attributes on trait items

* change `#[pallet::default_config]` to option on `#[pallet::config()]`

* update UI tests
* add UI test covering missing `#[pallet::config(with_default)]` when
  `#[pallet::no_default]` is used

* add note about new optional conventions

* improve docs about `DefaultConfig` and link to these from a few places

* fix doc comment

* fix old comment referencing `pallet::default_config`

* use u32 instead of u64 for block number

Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>

* use () instead of u32 for `AccountData`

Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>

* use ConstU32<10> for BlockHashCount instead of ConstU64<10>

Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>

* people are not dummies

Co-authored-by: Liam Aharon <liam.aharon@hotmail.com>

* fix wording

Co-authored-by: Just van Stam <vstam1@users.noreply.github.com>

* Person => People and compiling again

* add docs for `prelude` module in frame_system

* update Cargo.lock

* cleaner example

* tweaks

* update docs more

* update docs more

* update docs more

* update docs more

* fix ui tests

* err

* Update frame/support/test/tests/pallet_ui.rs

* update ui tests

---------

Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
Co-authored-by: Sam Johnson <sam@durosoft.com>
Co-authored-by: parity-processbot <>
Co-authored-by: Bastian Köcher <git@kchr.de>
Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>
Co-authored-by: Liam Aharon <liam.aharon@hotmail.com>
Co-authored-by: Just van Stam <vstam1@users.noreply.github.com>
This commit is contained in:
Kian Paimani
2023-05-30 10:06:40 +02:00
committed by GitHub
parent a8edaf47d5
commit 263a5d6c1e
35 changed files with 1445 additions and 80 deletions
@@ -24,7 +24,7 @@
//! `::{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`
//! `tt_default_parts` macro. `construct_runtime` 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
@@ -0,0 +1,175 @@
// 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.
//! Implementation of the `derive_impl` attribute macro.
use derive_syn_parse::Parse;
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};
#[derive(Parse)]
pub struct DeriveImplAttrArgs {
pub default_impl_path: Path,
_as: Option<Token![as]>,
#[parse_if(_as.is_some())]
pub disambiguation_path: Option<Path>,
}
impl ForeignPath for DeriveImplAttrArgs {
fn foreign_path(&self) -> &Path {
&self.default_impl_path
}
}
impl ToTokens for DeriveImplAttrArgs {
fn to_tokens(&self, tokens: &mut TokenStream2) {
tokens.extend(self.default_impl_path.to_token_stream());
tokens.extend(self._as.to_token_stream());
tokens.extend(self.disambiguation_path.to_token_stream());
}
}
/// Gets the [`Ident`] representation of the given [`ImplItem`], if one exists. Otherwise
/// returns [`None`].
///
/// Used by [`combine_impls`] to determine whether we can compare [`ImplItem`]s by [`Ident`]
/// or not.
fn impl_item_ident(impl_item: &ImplItem) -> Option<&Ident> {
match impl_item {
ImplItem::Const(item) => Some(&item.ident),
ImplItem::Fn(item) => Some(&item.sig.ident),
ImplItem::Type(item) => Some(&item.ident),
ImplItem::Macro(item) => item.mac.path.get_ident(),
_ => None,
}
}
/// The real meat behind `derive_impl`. Takes in a `local_impl`, which is the impl for which we
/// want to implement defaults (i.e. the one the attribute macro is attached to), and a
/// `foreign_impl`, which is the impl containing the defaults we want to use, and returns an
/// [`ItemImpl`] containing the final generated impl.
///
/// This process has the following caveats:
/// * Colliding items that have an ident are not copied into `local_impl`
/// * Uncolliding items that have an ident are copied into `local_impl` but are qualified as `type
/// #ident = <#default_impl_path as #disambiguation_path>::#ident;`
/// * Items that lack an ident are de-duplicated so only unique items that lack an ident are copied
/// into `local_impl`. Items that lack an ident and also exist verbatim in `local_impl` are not
/// copied over.
fn combine_impls(
local_impl: ItemImpl,
foreign_impl: ItemImpl,
default_impl_path: Path,
disambiguation_path: Path,
) -> ItemImpl {
let (existing_local_keys, existing_unsupported_items): (HashSet<ImplItem>, HashSet<ImplItem>) =
local_impl
.items
.iter()
.cloned()
.partition(|impl_item| impl_item_ident(impl_item).is_some());
let existing_local_keys: HashSet<Ident> = existing_local_keys
.into_iter()
.filter_map(|item| impl_item_ident(&item).cloned())
.collect();
let mut final_impl = local_impl;
let extended_items = foreign_impl.items.into_iter().filter_map(|item| {
if let Some(ident) = impl_item_ident(&item) {
if existing_local_keys.contains(&ident) {
// do not copy colliding items that have an ident
return None
}
if matches!(item, ImplItem::Type(_)) {
// modify and insert uncolliding type items
let modified_item: ImplItem = parse_quote! {
type #ident = <#default_impl_path as #disambiguation_path>::#ident;
};
return Some(modified_item)
}
// copy uncolliding non-type items that have an ident
Some(item)
} else {
// do not copy colliding items that lack an ident
(!existing_unsupported_items.contains(&item))
// copy uncolliding items without an ident verbatim
.then_some(item)
}
});
final_impl.items.extend(extended_items);
final_impl
}
/// Internal implementation behind [`#[derive_impl(..)]`](`macro@crate::derive_impl`).
///
/// `default_impl_path`: the module path of the external `impl` statement whose tokens we are
/// importing via `macro_magic`
///
/// `foreign_tokens`: the tokens for the external `impl` statement
///
/// `local_tokens`: the tokens for the local `impl` statement this attribute is attached to
///
/// `disambiguation_path`: the module path of the external trait we will use to qualify
/// defaults imported from the external `impl` statement
pub fn derive_impl(
default_impl_path: TokenStream2,
foreign_tokens: TokenStream2,
local_tokens: TokenStream2,
disambiguation_path: Option<Path>,
) -> Result<TokenStream2> {
let local_impl = parse2::<ItemImpl>(local_tokens)?;
let foreign_impl = parse2::<ItemImpl>(foreign_tokens)?;
let default_impl_path = parse2::<Path>(default_impl_path)?;
// have disambiguation_path default to the item being impl'd in the foreign impl if we
// don't specify an `as [disambiguation_path]` in the macro attr
let disambiguation_path = match (disambiguation_path, foreign_impl.clone().trait_) {
(Some(disambiguation_path), _) => disambiguation_path,
(None, Some((_, foreign_impl_path, _))) => foreign_impl_path,
_ =>
return Err(syn::Error::new(
foreign_impl.span(),
"Impl statement must have a defined type being implemented \
for a defined type such as `impl A for B`",
)),
};
// generate the combined impl
let combined_impl =
combine_impls(local_impl, foreign_impl, default_impl_path, disambiguation_path);
Ok(quote!(#combined_impl))
}
#[test]
fn test_derive_impl_attr_args_parsing() {
parse2::<DeriveImplAttrArgs>(quote!(
some::path::TestDefaultConfig as some::path::DefaultConfig
))
.unwrap();
parse2::<DeriveImplAttrArgs>(quote!(
frame_system::prelude::testing::TestDefaultConfig as DefaultConfig
))
.unwrap();
parse2::<DeriveImplAttrArgs>(quote!(Something as some::path::DefaultConfig)).unwrap();
parse2::<DeriveImplAttrArgs>(quote!(Something as DefaultConfig)).unwrap();
parse2::<DeriveImplAttrArgs>(quote!(DefaultConfig)).unwrap();
assert!(parse2::<DeriveImplAttrArgs>(quote!()).is_err());
assert!(parse2::<DeriveImplAttrArgs>(quote!(Config Config)).is_err());
}
+318 -1
View File
@@ -25,6 +25,7 @@ mod construct_runtime;
mod crate_version;
mod debug_no_bound;
mod default_no_bound;
mod derive_impl;
mod dummy_part_checker;
mod key_prefix;
mod match_and_insert;
@@ -36,10 +37,12 @@ mod storage_alias;
mod transactional;
mod tt_macro;
use macro_magic::import_tokens_attr;
use proc_macro::TokenStream;
use quote::quote;
use quote::{quote, ToTokens};
use std::{cell::RefCell, str::FromStr};
pub(crate) use storage::INHERENT_INSTANCE_NAME;
use syn::{parse_macro_input, ItemImpl};
thread_local! {
/// A global counter, can be used to generate a relatively unique identifier.
@@ -777,6 +780,274 @@ pub fn storage_alias(_: TokenStream, input: TokenStream) -> TokenStream {
.into()
}
/// This attribute can be used to derive a full implementation of a trait based on a local partial
/// impl and an external impl containing defaults that can be overriden in the local impl.
///
/// For a full end-to-end example, see [below](#use-case-auto-derive-test-pallet-config-traits).
///
/// # Usage
///
/// The attribute should be attached to an impl block (strictly speaking a `syn::ItemImpl`) for
/// which we want to inject defaults in the event of missing trait items in the block.
///
/// The attribute minimally takes a single `default_impl_path` argument, which should be the module
/// path to an impl registered via [`#[register_default_impl]`](`macro@register_default_impl`) that
/// contains the default trait items we want to potentially inject, with the general form:
///
/// ```ignore
/// #[derive_impl(default_impl_path)]
/// impl SomeTrait for SomeStruct {
/// ...
/// }
/// ```
///
/// Optionally, a `disambiguation_path` can be specified as follows by providing `as path::here`
/// after the `default_impl_path`:
///
/// ```ignore
/// #[derive_impl(default_impl_path as disambiguation_path)]
/// impl SomeTrait for SomeStruct {
/// ...
/// }
/// ```
///
/// The `disambiguation_path`, if specified, should be the path to a trait that will be used to
/// qualify all default entries that are injected into the local impl. For example if your
/// `default_impl_path` is `some::path::TestTraitImpl` and your `disambiguation_path` is
/// `another::path::DefaultTrait`, any items injected into the local impl will be qualified as
/// `<some::path::TestTraitImpl as another::path::DefaultTrait>::specific_trait_item`.
///
/// If you omit the `as disambiguation_path` portion, the `disambiguation_path` will internally
/// default to `A` from the `impl A for B` part of the default impl. This is useful for scenarios
/// where all of the relevant types are already in scope via `use` statements.
///
/// Conversely, the `default_impl_path` argument is required and cannot be omitted.
///
/// 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.
///
/// ## Use-Case Example: Auto-Derive Test Pallet Config Traits
///
/// The `#[derive_imp(..)]` attribute can be used to derive a test pallet `Config` based on an
/// existing pallet `Config` that has been marked with
/// [`#[pallet::config(with_default)]`](`macro@config`) (which under the hood, generates a
/// `DefaultConfig` trait in the pallet in which the macro was invoked).
///
/// In this case, the `#[derive_impl(..)]` attribute should be attached to an `impl` block that
/// implements a compatible `Config` such as `frame_system::Config` for a test/mock runtime, and
/// should receive as its first argument the path to a `DefaultConfig` impl that has been registered
/// via [`#[register_default_impl]`](`macro@register_default_impl`), and as its second argument, the
/// path to the auto-generated `DefaultConfig` for the existing pallet `Config` we want to base our
/// test config off of.
///
/// The following is what the `basic` example pallet would look like with a default testing config:
///
/// ```ignore
/// #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::pallet::DefaultConfig)]
/// impl frame_system::Config for Test {
/// // These are all defined by system as mandatory.
/// type BaseCallFilter = frame_support::traits::Everything;
/// type RuntimeEvent = RuntimeEvent;
/// type RuntimeCall = RuntimeCall;
/// type RuntimeOrigin = RuntimeOrigin;
/// type OnSetCode = ();
/// type PalletInfo = PalletInfo;
/// type Header = Header;
/// // We decide to override this one.
/// type AccountData = pallet_balances::AccountData<u64>;
/// }
/// ```
///
/// where `TestDefaultConfig` was defined and registered as follows:
///
/// ```ignore
/// pub struct TestDefaultConfig;
///
/// #[register_default_impl(TestDefaultConfig)]
/// impl DefaultConfig for TestDefaultConfig {
/// type Version = ();
/// type BlockWeights = ();
/// type BlockLength = ();
/// type DbWeight = ();
/// type Index = u64;
/// type BlockNumber = u64;
/// type Hash = sp_core::hash::H256;
/// type Hashing = sp_runtime::traits::BlakeTwo256;
/// type AccountId = AccountId;
/// type Lookup = IdentityLookup<AccountId>;
/// type BlockHashCount = frame_support::traits::ConstU64<10>;
/// type AccountData = u32;
/// type OnNewAccount = ();
/// type OnKilledAccount = ();
/// type SystemWeightInfo = ();
/// type SS58Prefix = ();
/// type MaxConsumers = frame_support::traits::ConstU32<16>;
/// }
/// ```
///
/// The above call to `derive_impl` would expand to roughly the following:
///
/// ```ignore
/// impl frame_system::Config for Test {
/// use frame_system::config_preludes::TestDefaultConfig;
/// use frame_system::pallet::DefaultConfig;
///
/// type BaseCallFilter = frame_support::traits::Everything;
/// type RuntimeEvent = RuntimeEvent;
/// type RuntimeCall = RuntimeCall;
/// type RuntimeOrigin = RuntimeOrigin;
/// type OnSetCode = ();
/// type PalletInfo = PalletInfo;
/// type Header = Header;
/// type AccountData = pallet_balances::AccountData<u64>;
/// type Version = <TestDefaultConfig as DefaultConfig>::Version;
/// type BlockWeights = <TestDefaultConfig as DefaultConfig>::BlockWeights;
/// type BlockLength = <TestDefaultConfig as DefaultConfig>::BlockLength;
/// type DbWeight = <TestDefaultConfig as DefaultConfig>::DbWeight;
/// type Index = <TestDefaultConfig as DefaultConfig>::Index;
/// type BlockNumber = <TestDefaultConfig as DefaultConfig>::BlockNumber;
/// type Hash = <TestDefaultConfig as DefaultConfig>::Hash;
/// type Hashing = <TestDefaultConfig as DefaultConfig>::Hashing;
/// type AccountId = <TestDefaultConfig as DefaultConfig>::AccountId;
/// type Lookup = <TestDefaultConfig as DefaultConfig>::Lookup;
/// type BlockHashCount = <TestDefaultConfig as DefaultConfig>::BlockHashCount;
/// type OnNewAccount = <TestDefaultConfig as DefaultConfig>::OnNewAccount;
/// type OnKilledAccount = <TestDefaultConfig as DefaultConfig>::OnKilledAccount;
/// type SystemWeightInfo = <TestDefaultConfig as DefaultConfig>::SystemWeightInfo;
/// type SS58Prefix = <TestDefaultConfig as DefaultConfig>::SS58Prefix;
/// type MaxConsumers = <TestDefaultConfig as DefaultConfig>::MaxConsumers;
/// }
/// ```
///
/// You can then use the resulting `Test` config in test scenarios.
///
/// Note that items that are _not_ present in our local `DefaultConfig` are automatically copied
/// from the foreign trait (in this case `TestDefaultConfig`) into the local trait impl (in this
/// case `Test`), unless the trait item in the local trait impl is marked with
/// [`#[pallet::no_default]`](`macro@no_default`), in which case it cannot be overridden, and any
/// attempts to do so will result in a compiler error.
///
/// See `frame/examples/default-config/tests.rs` for a runnable end-to-end example pallet that makes
/// use of `derive_impl` to derive its testing config.
///
/// See [here](`macro@config`) for more information and caveats about the auto-generated
/// `DefaultConfig` trait.
///
/// ## Optional Conventions
///
/// Note that as an optional convention, we encourage creating a `config_preludes` module inside of
/// your pallet. This is the convention we follow for `frame_system`'s `TestDefaultConfig` which, as
/// shown above, is located at `frame_system::config_preludes::TestDefaultConfig`. This is just a
/// suggested convention -- there is nothing in the code that expects modules with these names to be
/// in place, so there is no imperative to follow this pattern unless desired.
///
/// In `config_preludes`, you can place types named like:
///
/// * `TestDefaultConfig`
/// * `ParachainDefaultConfig`
/// * `SolochainDefaultConfig`
///
/// Signifying in which context they can be used.
///
/// # Advanced Usage
///
/// ## Importing & Re-Exporting
///
/// Since `#[derive_impl(..)]` is a
/// [`macro_magic`](https://docs.rs/macro_magic/latest/macro_magic/)-based attribute macro, special
/// care must be taken when importing and re-exporting it. Glob imports will work properly, such as
/// `use frame_support::*` to bring `derive_impl` into scope, however any other use statements
/// involving `derive_impl` should have
/// [`#[macro_magic::use_attr]`](https://docs.rs/macro_magic/latest/macro_magic/attr.use_attr.html)
/// attached or your use statement will fail to fully bring the macro into scope.
///
/// This brings `derive_impl` into scope in the current context:
/// ```ignore
/// #[use_attr]
/// use frame_support::derive_impl;
/// ```
///
/// This brings `derive_impl` into scope and publicly re-exports it from the current context:
/// ```ignore
/// #[use_attr]
/// pub use frame_support::derive_impl;
/// ```
///
/// ## Expansion
///
/// The `#[derive_impl(default_impl_path as disambiguation_path)]` attribute will expand to the
/// local impl, with any extra items from the foreign impl that aren't present in the local impl
/// also included. In the case of a colliding trait item, the version of the item that exists in the
/// local impl will be retained. All imported items are qualified by the `disambiguation_path`, as
/// discussed above.
///
/// ## Handling of Unnamed Trait Items
///
/// Items that lack a `syn::Ident` for whatever reason are first checked to see if they exist,
/// verbatim, in the local/destination trait before they are copied over, so you should not need to
/// worry about collisions between identical unnamed items.
#[import_tokens_attr(frame_support::macro_magic)]
#[with_custom_parsing(derive_impl::DeriveImplAttrArgs)]
#[proc_macro_attribute]
pub fn derive_impl(attrs: TokenStream, input: TokenStream) -> TokenStream {
let custom_attrs = parse_macro_input!(__custom_tokens as derive_impl::DeriveImplAttrArgs);
derive_impl::derive_impl(
__source_path.into(),
attrs.into(),
input.into(),
custom_attrs.disambiguation_path,
)
.unwrap_or_else(|r| r.into_compile_error())
.into()
}
/// The optional attribute `#[pallet::no_default]` 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 that trait item will not be used as a
/// default with the [`#[derive_impl(..)]`](`macro@derive_impl`) attribute macro.
#[proc_macro_attribute]
pub fn no_default(_: TokenStream, _: TokenStream) -> TokenStream {
pallet_macro_stub()
}
/// Attach this attribute to an impl statement that you want to use with
/// [`#[derive_impl(..)]`](`macro@derive_impl`).
///
/// You must also provide an identifier/name as the attribute's argument. This is the name you
/// must provide to [`#[derive_impl(..)]`](`macro@derive_impl`) when you import this impl via
/// the `default_impl_path` argument. This name should be unique at the crate-level.
///
/// ## Example
///
/// ```ignore
/// pub struct ExampleTestDefaultConfig;
///
/// #[register_default_impl(ExampleTestDefaultConfig)]
/// impl DefaultConfig for ExampleTestDefaultConfig {
/// type Version = ();
/// type BlockWeights = ();
/// type BlockLength = ();
/// ...
/// type SS58Prefix = ();
/// type MaxConsumers = frame_support::traits::ConstU32<16>;
/// }
/// ```
/// This macro acts as a thin wrapper around macro_magic's `#[export_tokens]`. See the docs
/// [here](https://docs.rs/macro_magic/latest/macro_magic/attr.export_tokens.html) for more info.
#[proc_macro_attribute]
pub fn register_default_impl(attrs: TokenStream, tokens: TokenStream) -> TokenStream {
// ensure this is a impl statement
let item_impl = syn::parse_macro_input!(tokens as ItemImpl);
// internally wrap macro_magic's `#[export_tokens]` macro
match macro_magic::mm_core::export_tokens_internal(attrs, item_impl.to_token_stream(), true) {
Ok(tokens) => tokens.into(),
Err(err) => err.to_compile_error().into(),
}
}
/// Used internally to decorate pallet attribute macro stubs when they are erroneously used
/// outside of a pallet module
fn pallet_macro_stub() -> TokenStream {
@@ -809,6 +1080,52 @@ fn pallet_macro_stub() -> TokenStream {
///
/// [`pallet::event`](`macro@event`) must be present if `RuntimeEvent` exists as a config item
/// in your `#[pallet::config]`.
///
/// ## Optional: `with_default`
///
/// An optional `with_default` argument may also be specified. Doing so will automatically
/// generate a `DefaultConfig` trait inside your pallet which is suitable for use with
/// [`[#[derive_impl(..)]`](`macro@derive_impl`) to derive a default testing config:
///
/// ```ignore
/// #[pallet::config(with_default)]
/// pub trait Config: frame_system::Config {
/// type RuntimeEvent: Parameter
/// + Member
/// + From<Event<Self>>
/// + Debug
/// + IsType<<Self as frame_system::Config>::RuntimeEvent>;
///
/// #[pallet::no_default]
/// type BaseCallFilter: Contains<Self::RuntimeCall>;
/// // ...
/// }
/// ```
///
/// As shown above, you may also attach the [`#[pallet::no_default]`](`macro@no_default`)
/// attribute to specify that a particular trait item _cannot_ be used as a default when a test
/// `Config` is derived using the [`#[derive_impl(..)]`](`macro@derive_impl`) attribute macro.
/// This will cause that particular trait item to simply not appear in default testing configs
/// based on this config (the trait item will not be included in `DefaultConfig`).
///
/// ### `DefaultConfig` Caveats
///
/// The auto-generated `DefaultConfig` trait:
/// - is always a _subset_ of your pallet's `Config` trait.
/// - can only contain items that don't rely on externalities, such as `frame_system::Config`.
///
/// Trait items that _do_ rely on externalities should be marked with
/// [`#[pallet::no_default]`](`macro@no_default`)
///
/// Consequently:
/// - Any items that rely on externalities _must_ be marked with
/// [`#[pallet::no_default]`](`macro@no_default`) or your trait will fail to compile when used
/// with [`derive_impl`](`macro@derive_impl`).
/// - Items marked with [`#[pallet::no_default]`](`macro@no_default`) are entirely excluded from the
/// `DefaultConfig` trait, and therefore any impl of `DefaultConfig` doesn't need to implement
/// such items.
///
/// For more information, see [`macro@derive_impl`].
#[proc_macro_attribute]
pub fn config(_: TokenStream, _: TokenStream) -> TokenStream {
pallet_macro_stub()
@@ -16,14 +16,17 @@
// limitations under the License.
use crate::pallet::Def;
use proc_macro2::TokenStream;
use quote::quote;
use syn::{parse_quote, Item};
///
/// * Generate default rust doc
pub fn expand_config(def: &mut Def) -> proc_macro2::TokenStream {
pub fn expand_config(def: &mut Def) -> TokenStream {
let config = &def.config;
let config_item = {
let item = &mut def.item.content.as_mut().expect("Checked by def parser").1[config.index];
if let syn::Item::Trait(item) = item {
if let Item::Trait(item) = item {
item
} else {
unreachable!("Checked by config parser")
@@ -32,8 +35,9 @@ pub fn expand_config(def: &mut Def) -> proc_macro2::TokenStream {
config_item.attrs.insert(
0,
syn::parse_quote!(
#[doc = r"Configuration trait of this pallet.
parse_quote!(
#[doc = r"
Configuration trait of this pallet.
The main purpose of this trait is to act as an interface between this pallet and the runtime in
which it is embedded in. A type, function, or constant in this trait is essentially left to be
@@ -44,5 +48,25 @@ Consequently, a runtime that wants to include this pallet must implement this tr
),
);
Default::default()
// we only emit `DefaultConfig` if there are trait items, so an empty `DefaultConfig` is
// impossible consequently
if config.default_sub_trait.len() > 0 {
let trait_items = &config.default_sub_trait;
quote!(
/// Based on [`Config`]. Auto-generated by
/// [`#[pallet::config(with_default)]`](`frame_support::pallet_macros::config`).
/// Can be used in tandem with
/// [`#[register_default_config]`](`frame_support::register_default_config`) and
/// [`#[derive_impl]`](`frame_support::derive_impl`) to derive test config traits
/// based on existing pallet config traits in a safe and developer-friendly way.
///
/// See [here](`frame_support::pallet_macros::config`) for more information and caveats about
/// the auto-generated `DefaultConfig` trait and how it is generated.
pub trait DefaultConfig {
#(#trait_items)*
}
)
} else {
Default::default()
}
}
@@ -18,7 +18,7 @@
use super::helper;
use frame_support_procedural_tools::get_doc_literals;
use quote::ToTokens;
use syn::spanned::Spanned;
use syn::{spanned::Spanned, token, Token};
/// List of additional token to be used for parsing.
mod keyword {
@@ -27,12 +27,14 @@ mod keyword {
syn::custom_keyword!(T);
syn::custom_keyword!(I);
syn::custom_keyword!(config);
syn::custom_keyword!(pallet);
syn::custom_keyword!(IsType);
syn::custom_keyword!(RuntimeEvent);
syn::custom_keyword!(Event);
syn::custom_keyword!(constant);
syn::custom_keyword!(frame_system);
syn::custom_keyword!(disable_frame_system_supertrait_check);
syn::custom_keyword!(no_default);
syn::custom_keyword!(constant);
}
/// Input definition for the pallet config.
@@ -52,6 +54,12 @@ pub struct ConfigDef {
pub where_clause: Option<syn::WhereClause>,
/// The span of the pallet::config attribute.
pub attr_span: proc_macro2::Span,
/// Whether a default sub-trait should be generated.
///
/// Contains default sub-trait items (instantiated by `#[pallet::config(with_default)]`).
/// Vec will be empty if `#[pallet::config(with_default)]` is not specified or if there are
/// no trait items
pub default_sub_trait: Vec<syn::TraitItem>,
}
/// Input definition for a constant in pallet config.
@@ -123,40 +131,28 @@ impl syn::parse::Parse for DisableFrameSystemSupertraitCheck {
}
}
/// Parse for `#[pallet::constant]`
pub struct TypeAttrConst {
pound_token: syn::Token![#],
bracket_token: syn::token::Bracket,
pallet_ident: syn::Ident,
path_sep_token: syn::token::PathSep,
constant_keyword: keyword::constant,
/// Parsing for the `typ` portion of `PalletAttr`
#[derive(derive_syn_parse::Parse, PartialEq, Eq)]
pub enum PalletAttrType {
#[peek(keyword::no_default, name = "no_default")]
NoDefault(keyword::no_default),
#[peek(keyword::constant, name = "constant")]
Constant(keyword::constant),
}
impl syn::parse::Parse for TypeAttrConst {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let pound_token = input.parse::<syn::Token![#]>()?;
let content;
let bracket_token = syn::bracketed!(content in input);
let pallet_ident = content.parse::<syn::Ident>()?;
let path_sep_token = content.parse::<syn::Token![::]>()?;
let constant_keyword = content.parse::<keyword::constant>()?;
Ok(Self { pound_token, bracket_token, pallet_ident, path_sep_token, constant_keyword })
}
/// Parsing for `#[pallet::X]`
#[derive(derive_syn_parse::Parse)]
pub struct PalletAttr {
_pound: Token![#],
#[bracket]
_bracket: token::Bracket,
#[inside(_bracket)]
_pallet: keyword::pallet,
#[prefix(Token![::] in _bracket)]
#[inside(_bracket)]
typ: PalletAttrType,
}
impl ToTokens for TypeAttrConst {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
self.pound_token.to_tokens(tokens);
self.bracket_token.surround(tokens, |tokens| {
self.pallet_ident.to_tokens(tokens);
self.path_sep_token.to_tokens(tokens);
self.constant_keyword.to_tokens(tokens);
})
}
}
/// Parse for `$ident::Config`
pub struct ConfigBoundParse(syn::Ident);
impl syn::parse::Parse for ConfigBoundParse {
@@ -307,6 +303,7 @@ impl ConfigDef {
attr_span: proc_macro2::Span,
index: usize,
item: &mut syn::Item,
enable_default: bool,
) -> syn::Result<Self> {
let item = if let syn::Item::Trait(item) = item {
item
@@ -344,38 +341,59 @@ impl ConfigDef {
let mut has_event_type = false;
let mut consts_metadata = vec![];
let mut default_sub_trait = vec![];
for trait_item in &mut item.items {
// Parse for event
has_event_type =
has_event_type || check_event_type(frame_system, trait_item, has_instance)?;
let is_event = check_event_type(frame_system, trait_item, has_instance)?;
has_event_type = has_event_type || is_event;
// Parse for constant
let type_attrs_const: Vec<TypeAttrConst> = helper::take_item_pallet_attrs(trait_item)?;
let mut already_no_default = false;
let mut already_constant = false;
if type_attrs_const.len() > 1 {
let msg = "Invalid attribute in pallet::config, only one attribute is expected";
return Err(syn::Error::new(type_attrs_const[1].span(), msg))
}
if type_attrs_const.len() == 1 {
match trait_item {
syn::TraitItem::Type(ref type_) => {
let constant = ConstMetadataDef::try_from(type_)?;
consts_metadata.push(constant);
while let Ok(Some(pallet_attr)) =
helper::take_first_item_pallet_attr::<PalletAttr>(trait_item)
{
match (pallet_attr.typ, &trait_item) {
(PalletAttrType::Constant(_), syn::TraitItem::Type(ref typ)) => {
if already_constant {
return Err(syn::Error::new(
pallet_attr._bracket.span.join(),
"Duplicate #[pallet::constant] attribute not allowed.",
))
}
already_constant = true;
consts_metadata.push(ConstMetadataDef::try_from(typ)?);
},
_ => {
let msg =
"Invalid pallet::constant in pallet::config, expected type trait \
item";
return Err(syn::Error::new(trait_item.span(), msg))
(PalletAttrType::Constant(_), _) =>
return Err(syn::Error::new(
trait_item.span(),
"Invalid #[pallet::constant] in #[pallet::config], expected type item",
)),
(PalletAttrType::NoDefault(_), _) => {
if !enable_default {
return Err(syn::Error::new(
pallet_attr._bracket.span.join(),
"`#[pallet:no_default]` can only be used if `#[pallet::config(with_default)]` \
has been specified"
))
}
if already_no_default {
return Err(syn::Error::new(
pallet_attr._bracket.span.join(),
"Duplicate #[pallet::no_default] attribute not allowed.",
))
}
already_no_default = true;
},
}
}
if !already_no_default && !is_event && enable_default {
default_sub_trait.push(trait_item.clone());
}
}
let attr: Option<DisableFrameSystemSupertraitCheck> =
helper::take_first_item_pallet_attr(&mut item.attrs)?;
let disable_system_supertrait_check = attr.is_some();
let has_frame_system_supertrait = item.supertraits.iter().any(|s| {
@@ -407,6 +425,14 @@ impl ConfigDef {
return Err(syn::Error::new(item.span(), msg))
}
Ok(Self { index, has_instance, consts_metadata, has_event_type, where_clause, attr_span })
Ok(Self {
index,
has_instance,
consts_metadata,
has_event_type,
where_clause,
attr_span,
default_sub_trait,
})
}
}
@@ -47,7 +47,9 @@ pub trait MutItemAttrs {
}
/// Take the first pallet attribute (e.g. attribute like `#[pallet..]`) and decode it to `Attr`
pub fn take_first_item_pallet_attr<Attr>(item: &mut impl MutItemAttrs) -> syn::Result<Option<Attr>>
pub(crate) fn take_first_item_pallet_attr<Attr>(
item: &mut impl MutItemAttrs,
) -> syn::Result<Option<Attr>>
where
Attr: syn::parse::Parse,
{
@@ -64,7 +66,7 @@ where
}
/// Take all the pallet attributes (e.g. attribute like `#[pallet..]`) and decode them to `Attr`
pub fn take_item_pallet_attrs<Attr>(item: &mut impl MutItemAttrs) -> syn::Result<Vec<Attr>>
pub(crate) fn take_item_pallet_attrs<Attr>(item: &mut impl MutItemAttrs) -> syn::Result<Vec<Attr>>
where
Attr: syn::parse::Parse,
{
@@ -100,8 +100,14 @@ impl Def {
let pallet_attr: Option<PalletAttr> = helper::take_first_item_pallet_attr(item)?;
match pallet_attr {
Some(PalletAttr::Config(span)) if config.is_none() =>
config = Some(config::ConfigDef::try_from(&frame_system, span, index, item)?),
Some(PalletAttr::Config(span, with_default)) if config.is_none() =>
config = Some(config::ConfigDef::try_from(
&frame_system,
span,
index,
item,
with_default,
)?),
Some(PalletAttr::Pallet(span)) if pallet_struct.is_none() => {
let p = pallet_struct::PalletStructDef::try_from(span, index, item)?;
pallet_struct = Some(p);
@@ -405,6 +411,7 @@ mod keyword {
syn::custom_keyword!(weight);
syn::custom_keyword!(event);
syn::custom_keyword!(config);
syn::custom_keyword!(with_default);
syn::custom_keyword!(hooks);
syn::custom_keyword!(inherent);
syn::custom_keyword!(error);
@@ -423,7 +430,7 @@ mod keyword {
/// Parse attributes for item in pallet module
/// syntax must be `pallet::` (e.g. `#[pallet::config]`)
enum PalletAttr {
Config(proc_macro2::Span),
Config(proc_macro2::Span, bool),
Pallet(proc_macro2::Span),
Hooks(proc_macro2::Span),
/// A `#[pallet::call]` with optional attributes to specialize the behaviour.
@@ -480,7 +487,7 @@ enum PalletAttr {
impl PalletAttr {
fn span(&self) -> proc_macro2::Span {
match self {
Self::Config(span) => *span,
Self::Config(span, _) => *span,
Self::Pallet(span) => *span,
Self::Hooks(span) => *span,
Self::RuntimeCall(_, span) => *span,
@@ -509,7 +516,14 @@ impl syn::parse::Parse for PalletAttr {
let lookahead = content.lookahead1();
if lookahead.peek(keyword::config) {
Ok(PalletAttr::Config(content.parse::<keyword::config>()?.span()))
let span = content.parse::<keyword::config>()?.span();
let with_default = content.peek(syn::token::Paren);
if with_default {
let inside_config;
let _paren = syn::parenthesized!(inside_config in content);
inside_config.parse::<keyword::with_default>()?;
}
Ok(PalletAttr::Config(span, with_default))
} else if lookahead.peek(keyword::pallet) {
Ok(PalletAttr::Pallet(content.parse::<keyword::pallet>()?.span()))
} else if lookahead.peek(keyword::hooks) {