Attribute Macro frame_support_procedural::derive_impl
source · #[derive_impl]
Expand description
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 overridden in the local impl.
For a full end-to-end example, see below.
§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]
that
contains the default trait items we want to potentially inject, with the general form:
#[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
:
#[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.
In case the default_impl_path
is scoped to a different module such as
some::path::TestTraitImpl
, the same scope is assumed for the disambiguation_path
, i.e.
some::A
. This enables the use of derive_impl
attribute without having to specify the
disambiguation_path
in most (if not all) uses within FRAME’s context.
Conversely, the default_impl_path
argument is required and cannot be omitted.
Optionally, no_aggregated_types
can be specified as follows:
#[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.
§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)]
(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]
, 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:
#[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 Block = Block;
// We decide to override this one.
type AccountData = pallet_balances::AccountData<u64>;
}
where TestDefaultConfig
was defined and registered as follows:
pub struct TestDefaultConfig;
#[register_default_impl(TestDefaultConfig)]
impl DefaultConfig for TestDefaultConfig {
type Version = ();
type BlockWeights = ();
type BlockLength = ();
type DbWeight = ();
type Nonce = 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:
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 Block = Block;
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 Nonce = <TestDefaultConfig as DefaultConfig>::Nonce;
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]
, 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 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
§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.