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.