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
+64
View File
@@ -2712,6 +2712,7 @@ dependencies = [
"impl-trait-for-tuples", "impl-trait-for-tuples",
"k256", "k256",
"log", "log",
"macro_magic",
"once_cell", "once_cell",
"parity-scale-codec", "parity-scale-codec",
"paste", "paste",
@@ -2745,6 +2746,7 @@ dependencies = [
"derive-syn-parse", "derive-syn-parse",
"frame-support-procedural-tools", "frame-support-procedural-tools",
"itertools", "itertools",
"macro_magic",
"proc-macro-warning", "proc-macro-warning",
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -2793,6 +2795,7 @@ dependencies = [
"sp-state-machine", "sp-state-machine",
"sp-std", "sp-std",
"sp-version", "sp-version",
"static_assertions",
"trybuild", "trybuild",
] ]
@@ -4766,6 +4769,53 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "macro_magic"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "686f3703e9d296140171bbd6a6fa560e6059a35299ee3ce8c5bd646afa0c3992"
dependencies = [
"macro_magic_core",
"macro_magic_macros",
"quote",
"syn 2.0.16",
]
[[package]]
name = "macro_magic_core"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e060da9399c1535b3f190084d7c6121bb3355b70c610110cff3f4978526f54e2"
dependencies = [
"derive-syn-parse",
"macro_magic_core_macros",
"proc-macro2",
"quote",
"syn 2.0.16",
]
[[package]]
name = "macro_magic_core_macros"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fb9865e03b9641e57448b9743915eadd0f642e41a41c4d9eaa8b5b861d887b0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.16",
]
[[package]]
name = "macro_magic_macros"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68cf083ba5ba151ce3230a7c687cb5c890498adae59c85f3be7e8e741a6c9f65"
dependencies = [
"macro_magic_core",
"quote",
"syn 2.0.16",
]
[[package]] [[package]]
name = "maplit" name = "maplit"
version = "1.0.2" version = "1.0.2"
@@ -6295,6 +6345,20 @@ dependencies = [
"sp-std", "sp-std",
] ]
[[package]]
name = "pallet-default-config-example"
version = "4.0.0-dev"
dependencies = [
"frame-support",
"frame-system",
"log",
"parity-scale-codec",
"scale-info",
"sp-io",
"sp-runtime",
"sp-std",
]
[[package]] [[package]]
name = "pallet-democracy" name = "pallet-democracy"
version = "4.0.0-dev" version = "4.0.0-dev"
+1
View File
@@ -109,6 +109,7 @@ members = [
"frame/examples/basic", "frame/examples/basic",
"frame/examples/offchain-worker", "frame/examples/offchain-worker",
"frame/examples/dev-mode", "frame/examples/dev-mode",
"frame/examples/default-config",
"frame/executive", "frame/executive",
"frame/nis", "frame/nis",
"frame/grandpa", "frame/grandpa",
@@ -0,0 +1,38 @@
[package]
name = "pallet-default-config-example"
version = "4.0.0-dev"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2021"
license = "MIT-0"
homepage = "https://substrate.io"
repository = "https://github.com/paritytech/substrate/"
description = "FRAME example pallet demonstrating derive_impl / default_config in action"
readme = "README.md"
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false }
log = { version = "0.4.17", default-features = false }
scale-info = { version = "2.5.0", default-features = false, features = ["derive"] }
frame-support = { default-features = false, path = "../../support" }
frame-system = { default-features = false, path = "../../system" }
sp-io = { default-features = false, path = "../../../primitives/io" }
sp-runtime = { default-features = false, path = "../../../primitives/runtime" }
sp-std = { default-features = false, path = "../../../primitives/std" }
[features]
default = ["std"]
std = [
"codec/std",
"frame-support/std",
"frame-system/std",
"log/std",
"scale-info/std",
"sp-io/std",
"sp-runtime/std",
"sp-std/std",
]
try-runtime = ["frame-support/try-runtime"]
@@ -0,0 +1,8 @@
# Default Config Example Pallet
An example pallet demonstrating the ability to derive default testing configs via
`#[derive_impl]` and `#[pallet::config(with_default)]`.
Run `cargo doc --package pallet-default-config-example --open` to view this pallet's documentation.
License: MIT-0
@@ -0,0 +1,210 @@
// 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.
//! # Default Config Pallet Example
//!
//! A simple example of a FRAME pallet that utilizes [`frame_support::derive_impl`] to demonstrate
//! the simpler way to implement `Config` trait of pallets. This example only showcases this in a
//! `mock.rs` environment, but the same applies to a real runtime as well.
//!
//! See the source code of [`tests`] for a real examples.
//!
//! Study the following types:
//!
//! - [`pallet::DefaultConfig`], and how it differs from [`pallet::Config`].
//! - [`pallet::config_preludes::TestDefaultConfig`] and how it implements
//! [`pallet::DefaultConfig`].
//! - Notice how [`pallet::DefaultConfig`] is independent of [`frame_system::Config`].
#![cfg_attr(not(feature = "std"), no_std)]
#[frame_support::pallet]
pub mod pallet {
use frame_support::pallet_prelude::*;
/// This pallet is annotated to have a default config. This will auto-generate
/// [`DefaultConfig`].
///
/// It will be an identical, but won't have anything that is `#[pallet::no_default]`.
#[pallet::config(with_default)]
pub trait Config: frame_system::Config {
/// The overarching event type. This is coming from the runtime, and cannot have a default.
/// In general, `Runtime*`-oriented types cannot have a sensible default.
#[pallet::no_default] // optional. `RuntimeEvent` is automatically excluded as well.
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
/// An input parameter to this pallet. This value can have a default, because it is not
/// reliant on `frame_system::Config` or the overarching runtime in any way.
type WithDefaultValue: Get<u32>;
/// Same as [`Config::WithDefaultValue`], but we don't intend to define a default for this
/// in our tests below.
type OverwrittenDefaultValue: Get<u32>;
/// An input parameter that relies on `<Self as frame_system::Config>::AccountId`. As of
/// now, such types cannot have defaults and need to be annotated as such, iff
/// `#[pallet::config(with_default)]` is enabled:
#[pallet::no_default]
type CannotHaveDefault: Get<Self::AccountId>;
/// Something that is a normal type, with default.
type WithDefaultType;
/// Same as [`Config::WithDefaultType`], but we don't intend to define a default for this
/// in our tests below.
type OverwrittenDefaultType;
}
/// Container for different types that implement [`DefaultConfig`]` of this pallet.
pub mod config_preludes {
// This will help use not need to disambiguate anything when using `derive_impl`.
use super::*;
/// A type providing default configurations for this pallet in testing environment.
pub struct TestDefaultConfig;
#[frame_support::register_default_impl(TestDefaultConfig)]
impl DefaultConfig for TestDefaultConfig {
type WithDefaultValue = frame_support::traits::ConstU32<42>;
type OverwrittenDefaultValue = frame_support::traits::ConstU32<42>;
type WithDefaultType = u32;
type OverwrittenDefaultType = u32;
}
/// A type providing default configurations for this pallet in a parachain environment.
pub struct ParachainDefaultConfig;
#[frame_support::register_default_impl(ParachainDefaultConfig)]
impl DefaultConfig for ParachainDefaultConfig {
type WithDefaultValue = frame_support::traits::ConstU32<66>;
type OverwrittenDefaultValue = frame_support::traits::ConstU32<66>;
type WithDefaultType = u32;
type OverwrittenDefaultType = u32;
}
}
#[pallet::pallet]
pub struct Pallet<T>(_);
#[pallet::event]
pub enum Event<T: Config> {}
}
#[cfg(any(test, doc))]
pub mod tests {
use super::*;
use frame_support::macro_magic::use_attr;
// Because `derive_impl` is a [macro_magic](https://crates.io/crates/macro_magic) attribute
// macro, [`#[use_attr]`](`frame_support::macro_magic::use_attr`) must be attached to any use
// statement that brings it into scope.
#[use_attr]
use frame_support::derive_impl;
use super::pallet as pallet_default_config_example;
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
type Block = frame_system::mocking::MockBlock<Test>;
frame_support::construct_runtime!(
pub enum Test where
Block = Block,
NodeBlock = Block,
UncheckedExtrinsic = UncheckedExtrinsic,
{
System: frame_system,
DefaultPallet: pallet_default_config_example,
}
);
#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)]
impl frame_system::Config for Test {
// 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 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`.
// type Index = u32;
// type BlockNumber = u32;
// type Header = sp_runtime::generic::Header<Self::BlockNumber, Self::Hashing>;
// type Hash = sp_core::hash::H256;
// type Hashing = sp_runtime::traits::BlakeTwo256;
// type AccountId = u64;
// type Lookup = sp_runtime::traits::IdentityLookup<u64>;
// type BlockHashCount = frame_support::traits::ConstU32<10>;
// type MaxConsumers = frame_support::traits::ConstU32<16>;
// type AccountData = ();
// type OnNewAccount = ();
// type OnKilledAccount = ();
// type SystemWeightInfo = ();
// type SS58Prefix = ();
// type Version = ();
// type BlockWeights = ();
// type BlockLength = ();
// type DbWeight = ();
// you could still overwrite any of them if desired.
type SS58Prefix = frame_support::traits::ConstU16<456>;
}
// Similarly, we use the defaults provided by own crate as well.
use pallet::config_preludes::TestDefaultConfig;
#[derive_impl(TestDefaultConfig as pallet::DefaultConfig)]
impl crate::pallet::Config for Test {
// These two both cannot have defaults.
type RuntimeEvent = RuntimeEvent;
// Note that the default account-id type in
// `frame_system::config_preludes::TestDefaultConfig` is `u64`.
type CannotHaveDefault = frame_support::traits::ConstU64<1>;
type OverwrittenDefaultValue = frame_support::traits::ConstU32<678>;
type OverwrittenDefaultType = u128;
}
#[test]
fn it_works() {
use frame_support::traits::Get;
use pallet::{Config, DefaultConfig};
// assert one of the value types that is not overwritten.
assert_eq!(
<<Test as Config>::WithDefaultValue as Get<u32>>::get(),
<<TestDefaultConfig as DefaultConfig>::WithDefaultValue as Get<u32>>::get()
);
// assert one of the value types that is overwritten.
assert_eq!(<<Test as Config>::OverwrittenDefaultValue as Get<u32>>::get(), 678u32);
// assert one of the types that is not overwritten.
assert_eq!(
std::any::TypeId::of::<<Test as Config>::WithDefaultType>(),
std::any::TypeId::of::<<TestDefaultConfig as DefaultConfig>::WithDefaultType>()
);
// assert one of the types that is overwritten.
assert_eq!(
std::any::TypeId::of::<<Test as Config>::OverwrittenDefaultType>(),
std::any::TypeId::of::<u128>()
)
}
}
+1
View File
@@ -29,6 +29,7 @@ sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../pr
sp-weights = { version = "5.0.0", default-features = false, path = "../../primitives/weights" } sp-weights = { version = "5.0.0", default-features = false, path = "../../primitives/weights" }
sp-debug-derive = { default-features = false, path = "../../primitives/debug-derive" } sp-debug-derive = { default-features = false, path = "../../primitives/debug-derive" }
tt-call = "1.0.8" tt-call = "1.0.8"
macro_magic = "0.3.3"
frame-support-procedural = { version = "4.0.0-dev", default-features = false, path = "./procedural" } frame-support-procedural = { version = "4.0.0-dev", default-features = false, path = "./procedural" }
paste = "1.0" paste = "1.0"
once_cell = { version = "1", default-features = false, optional = true } once_cell = { version = "1", default-features = false, optional = true }
@@ -24,6 +24,7 @@ quote = "1.0.28"
syn = { version = "2.0.16", features = ["full"] } syn = { version = "2.0.16", features = ["full"] }
frame-support-procedural-tools = { version = "4.0.0-dev", path = "./tools" } frame-support-procedural-tools = { version = "4.0.0-dev", path = "./tools" }
proc-macro-warning = { version = "0.4.1", default-features = false } proc-macro-warning = { version = "0.4.1", default-features = false }
macro_magic = { version = "0.3.3", features = ["proc_support"] }
[features] [features]
default = ["std"] default = ["std"]
@@ -24,7 +24,7 @@
//! `::{Call, ...}` or implicitly. //! `::{Call, ...}` or implicitly.
//! //!
//! In case a pallet defines its parts implicitly, then the pallet must provide the //! 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 //! 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 //! 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 //! 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 crate_version;
mod debug_no_bound; mod debug_no_bound;
mod default_no_bound; mod default_no_bound;
mod derive_impl;
mod dummy_part_checker; mod dummy_part_checker;
mod key_prefix; mod key_prefix;
mod match_and_insert; mod match_and_insert;
@@ -36,10 +37,12 @@ mod storage_alias;
mod transactional; mod transactional;
mod tt_macro; mod tt_macro;
use macro_magic::import_tokens_attr;
use proc_macro::TokenStream; use proc_macro::TokenStream;
use quote::quote; use quote::{quote, ToTokens};
use std::{cell::RefCell, str::FromStr}; use std::{cell::RefCell, str::FromStr};
pub(crate) use storage::INHERENT_INSTANCE_NAME; pub(crate) use storage::INHERENT_INSTANCE_NAME;
use syn::{parse_macro_input, ItemImpl};
thread_local! { thread_local! {
/// A global counter, can be used to generate a relatively unique identifier. /// 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() .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 /// Used internally to decorate pallet attribute macro stubs when they are erroneously used
/// outside of a pallet module /// outside of a pallet module
fn pallet_macro_stub() -> TokenStream { 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 /// [`pallet::event`](`macro@event`) must be present if `RuntimeEvent` exists as a config item
/// in your `#[pallet::config]`. /// 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] #[proc_macro_attribute]
pub fn config(_: TokenStream, _: TokenStream) -> TokenStream { pub fn config(_: TokenStream, _: TokenStream) -> TokenStream {
pallet_macro_stub() pallet_macro_stub()
@@ -16,14 +16,17 @@
// limitations under the License. // limitations under the License.
use crate::pallet::Def; use crate::pallet::Def;
use proc_macro2::TokenStream;
use quote::quote;
use syn::{parse_quote, Item};
/// ///
/// * Generate default rust doc /// * 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 = &def.config;
let config_item = { let config_item = {
let item = &mut def.item.content.as_mut().expect("Checked by def parser").1[config.index]; 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 item
} else { } else {
unreachable!("Checked by config parser") unreachable!("Checked by config parser")
@@ -32,8 +35,9 @@ pub fn expand_config(def: &mut Def) -> proc_macro2::TokenStream {
config_item.attrs.insert( config_item.attrs.insert(
0, 0,
syn::parse_quote!( parse_quote!(
#[doc = r"Configuration trait of this pallet. #[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 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 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 super::helper;
use frame_support_procedural_tools::get_doc_literals; use frame_support_procedural_tools::get_doc_literals;
use quote::ToTokens; use quote::ToTokens;
use syn::spanned::Spanned; use syn::{spanned::Spanned, token, Token};
/// List of additional token to be used for parsing. /// List of additional token to be used for parsing.
mod keyword { mod keyword {
@@ -27,12 +27,14 @@ mod keyword {
syn::custom_keyword!(T); syn::custom_keyword!(T);
syn::custom_keyword!(I); syn::custom_keyword!(I);
syn::custom_keyword!(config); syn::custom_keyword!(config);
syn::custom_keyword!(pallet);
syn::custom_keyword!(IsType); syn::custom_keyword!(IsType);
syn::custom_keyword!(RuntimeEvent); syn::custom_keyword!(RuntimeEvent);
syn::custom_keyword!(Event); syn::custom_keyword!(Event);
syn::custom_keyword!(constant);
syn::custom_keyword!(frame_system); syn::custom_keyword!(frame_system);
syn::custom_keyword!(disable_frame_system_supertrait_check); syn::custom_keyword!(disable_frame_system_supertrait_check);
syn::custom_keyword!(no_default);
syn::custom_keyword!(constant);
} }
/// Input definition for the pallet config. /// Input definition for the pallet config.
@@ -52,6 +54,12 @@ pub struct ConfigDef {
pub where_clause: Option<syn::WhereClause>, pub where_clause: Option<syn::WhereClause>,
/// The span of the pallet::config attribute. /// The span of the pallet::config attribute.
pub attr_span: proc_macro2::Span, 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. /// Input definition for a constant in pallet config.
@@ -123,40 +131,28 @@ impl syn::parse::Parse for DisableFrameSystemSupertraitCheck {
} }
} }
/// Parse for `#[pallet::constant]` /// Parsing for the `typ` portion of `PalletAttr`
pub struct TypeAttrConst { #[derive(derive_syn_parse::Parse, PartialEq, Eq)]
pound_token: syn::Token![#], pub enum PalletAttrType {
bracket_token: syn::token::Bracket, #[peek(keyword::no_default, name = "no_default")]
pallet_ident: syn::Ident, NoDefault(keyword::no_default),
path_sep_token: syn::token::PathSep, #[peek(keyword::constant, name = "constant")]
constant_keyword: keyword::constant, Constant(keyword::constant),
} }
impl syn::parse::Parse for TypeAttrConst { /// Parsing for `#[pallet::X]`
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> { #[derive(derive_syn_parse::Parse)]
let pound_token = input.parse::<syn::Token![#]>()?; pub struct PalletAttr {
let content; _pound: Token![#],
let bracket_token = syn::bracketed!(content in input); #[bracket]
let pallet_ident = content.parse::<syn::Ident>()?; _bracket: token::Bracket,
let path_sep_token = content.parse::<syn::Token![::]>()?; #[inside(_bracket)]
let constant_keyword = content.parse::<keyword::constant>()?; _pallet: keyword::pallet,
#[prefix(Token![::] in _bracket)]
Ok(Self { pound_token, bracket_token, pallet_ident, path_sep_token, constant_keyword }) #[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); pub struct ConfigBoundParse(syn::Ident);
impl syn::parse::Parse for ConfigBoundParse { impl syn::parse::Parse for ConfigBoundParse {
@@ -307,6 +303,7 @@ impl ConfigDef {
attr_span: proc_macro2::Span, attr_span: proc_macro2::Span,
index: usize, index: usize,
item: &mut syn::Item, item: &mut syn::Item,
enable_default: bool,
) -> syn::Result<Self> { ) -> syn::Result<Self> {
let item = if let syn::Item::Trait(item) = item { let item = if let syn::Item::Trait(item) = item {
item item
@@ -344,38 +341,59 @@ impl ConfigDef {
let mut has_event_type = false; let mut has_event_type = false;
let mut consts_metadata = vec![]; let mut consts_metadata = vec![];
let mut default_sub_trait = vec![];
for trait_item in &mut item.items { for trait_item in &mut item.items {
// Parse for event let is_event = check_event_type(frame_system, trait_item, has_instance)?;
has_event_type = has_event_type = has_event_type || is_event;
has_event_type || check_event_type(frame_system, trait_item, has_instance)?;
// Parse for constant let mut already_no_default = false;
let type_attrs_const: Vec<TypeAttrConst> = helper::take_item_pallet_attrs(trait_item)?; let mut already_constant = false;
if type_attrs_const.len() > 1 { while let Ok(Some(pallet_attr)) =
let msg = "Invalid attribute in pallet::config, only one attribute is expected"; helper::take_first_item_pallet_attr::<PalletAttr>(trait_item)
return Err(syn::Error::new(type_attrs_const[1].span(), msg)) {
} match (pallet_attr.typ, &trait_item) {
(PalletAttrType::Constant(_), syn::TraitItem::Type(ref typ)) => {
if type_attrs_const.len() == 1 { if already_constant {
match trait_item { return Err(syn::Error::new(
syn::TraitItem::Type(ref type_) => { pallet_attr._bracket.span.join(),
let constant = ConstMetadataDef::try_from(type_)?; "Duplicate #[pallet::constant] attribute not allowed.",
consts_metadata.push(constant); ))
}
already_constant = true;
consts_metadata.push(ConstMetadataDef::try_from(typ)?);
}, },
_ => { (PalletAttrType::Constant(_), _) =>
let msg = return Err(syn::Error::new(
"Invalid pallet::constant in pallet::config, expected type trait \ trait_item.span(),
item"; "Invalid #[pallet::constant] in #[pallet::config], expected type item",
return Err(syn::Error::new(trait_item.span(), msg)) )),
(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> = let attr: Option<DisableFrameSystemSupertraitCheck> =
helper::take_first_item_pallet_attr(&mut item.attrs)?; helper::take_first_item_pallet_attr(&mut item.attrs)?;
let disable_system_supertrait_check = attr.is_some(); let disable_system_supertrait_check = attr.is_some();
let has_frame_system_supertrait = item.supertraits.iter().any(|s| { let has_frame_system_supertrait = item.supertraits.iter().any(|s| {
@@ -407,6 +425,14 @@ impl ConfigDef {
return Err(syn::Error::new(item.span(), msg)) 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` /// 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 where
Attr: syn::parse::Parse, Attr: syn::parse::Parse,
{ {
@@ -64,7 +66,7 @@ where
} }
/// Take all the pallet attributes (e.g. attribute like `#[pallet..]`) and decode them to `Attr` /// 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 where
Attr: syn::parse::Parse, Attr: syn::parse::Parse,
{ {
@@ -100,8 +100,14 @@ impl Def {
let pallet_attr: Option<PalletAttr> = helper::take_first_item_pallet_attr(item)?; let pallet_attr: Option<PalletAttr> = helper::take_first_item_pallet_attr(item)?;
match pallet_attr { match pallet_attr {
Some(PalletAttr::Config(span)) if config.is_none() => Some(PalletAttr::Config(span, with_default)) if config.is_none() =>
config = Some(config::ConfigDef::try_from(&frame_system, span, index, item)?), config = Some(config::ConfigDef::try_from(
&frame_system,
span,
index,
item,
with_default,
)?),
Some(PalletAttr::Pallet(span)) if pallet_struct.is_none() => { Some(PalletAttr::Pallet(span)) if pallet_struct.is_none() => {
let p = pallet_struct::PalletStructDef::try_from(span, index, item)?; let p = pallet_struct::PalletStructDef::try_from(span, index, item)?;
pallet_struct = Some(p); pallet_struct = Some(p);
@@ -405,6 +411,7 @@ mod keyword {
syn::custom_keyword!(weight); syn::custom_keyword!(weight);
syn::custom_keyword!(event); syn::custom_keyword!(event);
syn::custom_keyword!(config); syn::custom_keyword!(config);
syn::custom_keyword!(with_default);
syn::custom_keyword!(hooks); syn::custom_keyword!(hooks);
syn::custom_keyword!(inherent); syn::custom_keyword!(inherent);
syn::custom_keyword!(error); syn::custom_keyword!(error);
@@ -423,7 +430,7 @@ mod keyword {
/// Parse attributes for item in pallet module /// Parse attributes for item in pallet module
/// syntax must be `pallet::` (e.g. `#[pallet::config]`) /// syntax must be `pallet::` (e.g. `#[pallet::config]`)
enum PalletAttr { enum PalletAttr {
Config(proc_macro2::Span), Config(proc_macro2::Span, bool),
Pallet(proc_macro2::Span), Pallet(proc_macro2::Span),
Hooks(proc_macro2::Span), Hooks(proc_macro2::Span),
/// A `#[pallet::call]` with optional attributes to specialize the behaviour. /// A `#[pallet::call]` with optional attributes to specialize the behaviour.
@@ -480,7 +487,7 @@ enum PalletAttr {
impl PalletAttr { impl PalletAttr {
fn span(&self) -> proc_macro2::Span { fn span(&self) -> proc_macro2::Span {
match self { match self {
Self::Config(span) => *span, Self::Config(span, _) => *span,
Self::Pallet(span) => *span, Self::Pallet(span) => *span,
Self::Hooks(span) => *span, Self::Hooks(span) => *span,
Self::RuntimeCall(_, span) => *span, Self::RuntimeCall(_, span) => *span,
@@ -509,7 +516,14 @@ impl syn::parse::Parse for PalletAttr {
let lookahead = content.lookahead1(); let lookahead = content.lookahead1();
if lookahead.peek(keyword::config) { 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) { } else if lookahead.peek(keyword::pallet) {
Ok(PalletAttr::Pallet(content.parse::<keyword::pallet>()?.span())) Ok(PalletAttr::Pallet(content.parse::<keyword::pallet>()?.span()))
} else if lookahead.peek(keyword::hooks) { } else if lookahead.peek(keyword::hooks) {
+13 -2
View File
@@ -211,6 +211,9 @@ impl TypeId for PalletId {
/// ``` /// ```
pub use frame_support_procedural::storage_alias; pub use frame_support_procedural::storage_alias;
#[macro_magic::use_attr]
pub use frame_support_procedural::derive_impl;
/// Create new implementations of the [`Get`](crate::traits::Get) trait. /// Create new implementations of the [`Get`](crate::traits::Get) trait.
/// ///
/// The so-called parameter type can be created in four different ways: /// The so-called parameter type can be created in four different ways:
@@ -822,6 +825,10 @@ macro_rules! assert_error_encoded_size {
#[doc(hidden)] #[doc(hidden)]
pub use serde::{Deserialize, Serialize}; pub use serde::{Deserialize, Serialize};
#[doc(hidden)]
#[cfg(not(no_std))]
pub use macro_magic;
#[cfg(test)] #[cfg(test)]
pub mod tests { pub mod tests {
use super::*; use super::*;
@@ -1557,6 +1564,7 @@ pub mod pallet_prelude {
}; };
pub use codec::{Decode, Encode, MaxEncodedLen}; pub use codec::{Decode, Encode, MaxEncodedLen};
pub use frame_support::pallet_macros::*; pub use frame_support::pallet_macros::*;
pub use frame_support_procedural::register_default_impl;
pub use scale_info::TypeInfo; pub use scale_info::TypeInfo;
pub use sp_runtime::{ pub use sp_runtime::{
traits::{MaybeSerializeDeserialize, Member, ValidateUnsigned}, traits::{MaybeSerializeDeserialize, Member, ValidateUnsigned},
@@ -2893,11 +2901,14 @@ pub mod pallet_macros {
pub use frame_support_procedural::{ pub use frame_support_procedural::{
call_index, compact, composite_enum, config, constant, call_index, compact, composite_enum, config, constant,
disable_frame_system_supertrait_check, error, event, extra_constants, generate_deposit, disable_frame_system_supertrait_check, error, event, extra_constants, generate_deposit,
generate_store, genesis_build, genesis_config, getter, hooks, inherent, origin, storage, generate_store, genesis_build, genesis_config, getter, hooks, inherent, no_default, origin,
storage_prefix, storage_version, type_value, unbounded, validate_unsigned, weight, storage, storage_prefix, storage_version, type_value, unbounded, validate_unsigned, weight,
whitelist_storage, whitelist_storage,
}; };
} }
#[doc(inline)]
pub use frame_support_procedural::register_default_impl;
// Generate a macro that will enable/disable code based on `std` feature being active. // Generate a macro that will enable/disable code based on `std` feature being active.
sp_core::generate_feature_enabled_macro!(std_enabled, feature = "std", $); sp_core::generate_feature_enabled_macro!(std_enabled, feature = "std", $);
+1
View File
@@ -12,6 +12,7 @@ repository = "https://github.com/paritytech/substrate/"
targets = ["x86_64-unknown-linux-gnu"] targets = ["x86_64-unknown-linux-gnu"]
[dependencies] [dependencies]
static_assertions = "1.1.0"
serde = { version = "1.0.136", default-features = false, features = ["derive"] } serde = { version = "1.0.136", default-features = false, features = ["derive"] }
codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] }
scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } scale-info = { version = "2.5.0", default-features = false, features = ["derive"] }
@@ -0,0 +1,38 @@
// 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.
#![cfg(not(feature = "disable-ui-tests"))]
#![cfg(test)]
#[rustversion::attr(not(stable), ignore)]
#[test]
fn derive_impl_ui() {
// Only run the ui tests when `RUN_UI_TESTS` is set.
if std::env::var("RUN_UI_TESTS").is_err() {
return
}
// As trybuild is using `cargo check`, we don't need the real WASM binaries.
std::env::set_var("SKIP_WASM_BUILD", "1");
// Deny all warnings since we emit warnings as part of a Pallet's UI.
std::env::set_var("RUSTFLAGS", "--deny warnings");
let t = trybuild::TestCases::new();
t.compile_fail("tests/derive_impl_ui/*.rs");
t.pass("tests/derive_impl_ui/pass/*.rs");
}
@@ -0,0 +1,41 @@
use frame_support::*;
pub trait Animal {
type Locomotion;
type Diet;
type SleepingStrategy;
type Environment;
fn animal_name() -> &'static str;
}
pub type RunsOnFourLegs = (usize, usize, usize, usize);
pub type RunsOnTwoLegs = (usize, usize);
pub type Swims = isize;
pub type Diurnal = bool;
pub type Nocturnal = Option<bool>;
pub type Omnivore = char;
pub type Land = ((), ());
pub type Sea = ((), (), ());
pub type Carnivore = (char, char);
pub struct FourLeggedAnimal {}
#[register_default_impl(FourLeggedAnimal)]
impl Animal for FourLeggedAnimal {
type Locomotion = RunsOnFourLegs;
type Diet = Omnivore;
type SleepingStrategy = Diurnal;
type Environment = Land;
fn animal_name() -> &'static str {
"A Four-Legged Animal"
}
}
pub struct AcquaticMammal {}
#[derive_impl(FourLeggedAnimal as Animal)]
struct Something {}
fn main() {}
@@ -0,0 +1,15 @@
error: expected `impl`
--> tests/derive_impl_ui/attached_to_non_impl.rs:24:1
|
24 | / #[register_default_impl(FourLeggedAnimal)]
25 | | impl Animal for FourLeggedAnimal {
26 | | type Locomotion = RunsOnFourLegs;
27 | | type Diet = Omnivore;
... |
37 | |
38 | | #[derive_impl(FourLeggedAnimal as Animal)]
| |_-----------------------------------------^
| |
| in this procedural macro expansion
|
= note: this error originates in the macro `__import_tokens_attr_derive_impl_inner` which comes from the expansion of the attribute macro `derive_impl` (in Nightly builds, run with -Z macro-backtrace for more info)
@@ -0,0 +1,48 @@
use frame_support::*;
pub trait Animal {
type Locomotion;
type Diet;
type SleepingStrategy;
type Environment;
fn animal_name() -> &'static str;
}
pub type RunsOnFourLegs = (usize, usize, usize, usize);
pub type RunsOnTwoLegs = (usize, usize);
pub type Swims = isize;
pub type Diurnal = bool;
pub type Nocturnal = Option<bool>;
pub type Omnivore = char;
pub type Land = ((), ());
pub type Sea = ((), (), ());
pub type Carnivore = (char, char);
pub struct FourLeggedAnimal {}
#[register_default_impl(FourLeggedAnimal)]
impl Animal for FourLeggedAnimal {
type Locomotion = RunsOnFourLegs;
type Diet = Omnivore;
type SleepingStrategy = Diurnal;
type Environment = Land;
fn animal_name() -> &'static str {
"A Four-Legged Animal"
}
}
pub struct AcquaticMammal {}
// Should throw: `error: cannot find macro `__export_tokens_tt_tiger` in this scope`
//
// Note that there is really no better way to clean up this error, tt_call suffers from the
// same downside but this is really the only rough edge when using macro magic.
#[derive_impl(Tiger as Animal)]
impl Animal for AcquaticMammal {
type Locomotion = (Swims, RunsOnFourLegs);
type Environment = (Land, Sea);
}
fn main() {}
@@ -0,0 +1,7 @@
error: cannot find macro `__export_tokens_tt_tiger` in this scope
--> tests/derive_impl_ui/bad_default_impl_path.rs:42:1
|
42 | #[derive_impl(Tiger as Animal)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: this error originates in the macro `frame_support::macro_magic::forward_tokens` (in Nightly builds, run with -Z macro-backtrace for more info)
@@ -0,0 +1,44 @@
use frame_support::*;
pub trait Animal {
type Locomotion;
type Diet;
type SleepingStrategy;
type Environment;
fn animal_name() -> &'static str;
}
pub type RunsOnFourLegs = (usize, usize, usize, usize);
pub type RunsOnTwoLegs = (usize, usize);
pub type Swims = isize;
pub type Diurnal = bool;
pub type Nocturnal = Option<bool>;
pub type Omnivore = char;
pub type Land = ((), ());
pub type Sea = ((), (), ());
pub type Carnivore = (char, char);
pub struct FourLeggedAnimal {}
#[register_default_impl(FourLeggedAnimal)]
impl Animal for FourLeggedAnimal {
type Locomotion = RunsOnFourLegs;
type Diet = Omnivore;
type SleepingStrategy = Diurnal;
type Environment = Land;
fn animal_name() -> &'static str {
"A Four-Legged Animal"
}
}
pub struct AcquaticMammal {}
#[derive_impl(FourLeggedAnimal as Insect)]
impl Animal for AcquaticMammal {
type Locomotion = (Swims, RunsOnFourLegs);
type Environment = (Land, Sea);
}
fn main() {}
@@ -0,0 +1,16 @@
error[E0433]: failed to resolve: use of undeclared type `Insect`
--> tests/derive_impl_ui/bad_disambiguation_path.rs:24:1
|
24 | / #[register_default_impl(FourLeggedAnimal)]
25 | | impl Animal for FourLeggedAnimal {
26 | | type Locomotion = RunsOnFourLegs;
27 | | type Diet = Omnivore;
... |
37 | |
38 | | #[derive_impl(FourLeggedAnimal as Insect)]
| | -----------------------------------------^
| |_|________________________________________|
| | use of undeclared type `Insect`
| in this procedural macro expansion
|
= note: this error originates in the macro `__import_tokens_attr_derive_impl_inner` which comes from the expansion of the attribute macro `derive_impl` (in Nightly builds, run with -Z macro-backtrace for more info)
@@ -0,0 +1,44 @@
use frame_support::*;
pub trait Animal {
type Locomotion;
type Diet;
type SleepingStrategy;
type Environment;
fn animal_name() -> &'static str;
}
pub type RunsOnFourLegs = (usize, usize, usize, usize);
pub type RunsOnTwoLegs = (usize, usize);
pub type Swims = isize;
pub type Diurnal = bool;
pub type Nocturnal = Option<bool>;
pub type Omnivore = char;
pub type Land = ((), ());
pub type Sea = ((), (), ());
pub type Carnivore = (char, char);
pub struct FourLeggedAnimal {}
#[register_default_impl(FourLeggedAnimal)]
impl Animal for FourLeggedAnimal {
type Locomotion = RunsOnFourLegs;
type Diet = Omnivore;
type SleepingStrategy = Diurnal;
type Environment = Land;
fn animal_name() -> &'static str {
"A Four-Legged Animal"
}
}
pub struct AcquaticMammal {}
#[derive_impl(FourLeggedAnimal as)]
impl Animal for AcquaticMammal {
type Locomotion = (Swims, RunsOnFourLegs);
type Environment = (Land, Sea);
}
fn main() {}
@@ -0,0 +1,7 @@
error: unexpected end of input, expected identifier
--> tests/derive_impl_ui/missing_disambiguation_path.rs:38:1
|
38 | #[derive_impl(FourLeggedAnimal as)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: this error originates in the attribute macro `derive_impl` (in Nightly builds, run with -Z macro-backtrace for more info)
@@ -0,0 +1,69 @@
use frame_support::*;
use static_assertions::assert_type_eq_all;
pub trait Animal {
type Locomotion;
type Diet;
type SleepingStrategy;
type Environment;
fn animal_name() -> &'static str;
}
pub type RunsOnFourLegs = (usize, usize, usize, usize);
pub type RunsOnTwoLegs = (usize, usize);
pub type Swims = isize;
pub type Diurnal = bool;
pub type Nocturnal = Option<bool>;
pub type Omnivore = char;
pub type Land = ((), ());
pub type Sea = ((), (), ());
pub type Carnivore = (char, char);
pub struct FourLeggedAnimal {}
#[register_default_impl(FourLeggedAnimal)]
impl Animal for FourLeggedAnimal {
type Locomotion = RunsOnFourLegs;
type Diet = Omnivore;
type SleepingStrategy = Diurnal;
type Environment = Land;
fn animal_name() -> &'static str {
"A Four-Legged Animal"
}
}
pub struct AcquaticMammal {}
// without omitting the `as X`
#[derive_impl(FourLeggedAnimal as Animal)]
impl Animal for AcquaticMammal {
type Locomotion = (Swims, RunsOnFourLegs);
type Environment = (Land, Sea);
}
assert_type_eq_all!(<AcquaticMammal as Animal>::Locomotion, (Swims, RunsOnFourLegs));
assert_type_eq_all!(<AcquaticMammal as Animal>::Environment, (Land, Sea));
assert_type_eq_all!(<AcquaticMammal as Animal>::Diet, Omnivore);
assert_type_eq_all!(<AcquaticMammal as Animal>::SleepingStrategy, Diurnal);
pub struct Lion {}
// test omitting the `as X`
#[derive_impl(FourLeggedAnimal)]
impl Animal for Lion {
type Diet = Carnivore;
type SleepingStrategy = Nocturnal;
fn animal_name() -> &'static str {
"Lion"
}
}
assert_type_eq_all!(<Lion as Animal>::Diet, Carnivore);
assert_type_eq_all!(<Lion as Animal>::SleepingStrategy, Nocturnal);
assert_type_eq_all!(<Lion as Animal>::Environment, Land);
assert_type_eq_all!(<Lion as Animal>::Locomotion, RunsOnFourLegs);
fn main() {}
@@ -0,0 +1,18 @@
#[frame_support::macro_magic::export_tokens]
struct MyCoolStruct {
field: u32,
}
// create a test receiver since `proc_support` isn't enabled so we're on our own in terms of
// what we can call
macro_rules! receiver {
($_tokens_var:ident, $($tokens:tt)*) => {
stringify!($($tokens)*)
};
}
fn main() {
let _instance: MyCoolStruct = MyCoolStruct { field: 3 };
let _str = __export_tokens_tt_my_cool_struct!(tokens, receiver);
// this compiling demonstrates that macro_magic is working properly
}
@@ -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]
type MyGetParam2: Get<u32>;
}
#[pallet::pallet]
pub struct Pallet<T>(core::marker::PhantomData<T>);
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}
#[pallet::call]
impl<T: Config> Pallet<T> {}
}
fn main() {}
@@ -0,0 +1,5 @@
error: `#[pallet:no_default]` can only be used if `#[pallet::config(with_default)]` has been specified
--> tests/pallet_ui/no_default_but_missing_with_default.rs:9:4
|
9 | #[pallet::no_default]
| ^^^^^^^^^^^^^^^^^^^^
@@ -1,5 +1,5 @@
error: Invalid pallet::constant in pallet::config, expected type trait item error: Invalid #[pallet::constant] in #[pallet::config], expected type item
--> $DIR/trait_invalid_item.rs:9:3 --> tests/pallet_ui/trait_invalid_item.rs:9:3
| |
9 | const U: u8 = 3; 9 | const U: u8 = 3;
| ^^^^^ | ^^^^^
@@ -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::constant]
type MyGetParam2: Get<u32>;
}
#[pallet::pallet]
pub struct Pallet<T>(core::marker::PhantomData<T>);
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}
#[pallet::call]
impl<T: Config> Pallet<T> {}
}
fn main() {}
@@ -0,0 +1,5 @@
error: Duplicate #[pallet::constant] attribute not allowed.
--> tests/pallet_ui/trait_item_duplicate_constant_attr.rs:9:4
|
9 | #[pallet::constant]
| ^^^^^^^^^^^^^^^^^^
@@ -0,0 +1,24 @@
#[frame_support::pallet]
mod pallet {
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
#[pallet::config(with_default)]
pub trait Config: frame_system::Config {
#[pallet::constant]
#[pallet::no_default]
#[pallet::no_default]
type MyGetParam2: Get<u32>;
}
#[pallet::pallet]
pub struct Pallet<T>(core::marker::PhantomData<T>);
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}
#[pallet::call]
impl<T: Config> Pallet<T> {}
}
fn main() {}
@@ -0,0 +1,5 @@
error: Duplicate #[pallet::no_default] attribute not allowed.
--> tests/pallet_ui/trait_item_duplicate_no_default.rs:10:4
|
10 | #[pallet::no_default]
| ^^^^^^^^^^^^^^^^^^^^
+48 -8
View File
@@ -201,12 +201,55 @@ pub mod pallet {
use crate::{self as frame_system, pallet_prelude::*, *}; use crate::{self as frame_system, pallet_prelude::*, *};
use frame_support::pallet_prelude::*; use frame_support::pallet_prelude::*;
/// Contains default types suitable for various environments
pub mod config_preludes {
use super::DefaultConfig;
/// Provides a viable default config that can be used with
/// [`derive_impl`](`frame_support::derive_impl`) to derive a testing pallet config
/// based on this one.
///
/// See `Test` in the `default-config` example pallet's `test.rs` for an example of
/// a downstream user of this particular `TestDefaultConfig`
pub struct TestDefaultConfig;
#[frame_support::register_default_impl(TestDefaultConfig)]
impl DefaultConfig for TestDefaultConfig {
type Index = u32;
type BlockNumber = u32;
type Header = sp_runtime::generic::Header<Self::BlockNumber, Self::Hashing>;
type Hash = sp_core::hash::H256;
type Hashing = sp_runtime::traits::BlakeTwo256;
type AccountId = u64;
type Lookup = sp_runtime::traits::IdentityLookup<u64>;
type BlockHashCount = frame_support::traits::ConstU32<10>;
type MaxConsumers = frame_support::traits::ConstU32<16>;
type AccountData = ();
type OnNewAccount = ();
type OnKilledAccount = ();
type SystemWeightInfo = ();
type SS58Prefix = ();
type Version = ();
type BlockWeights = ();
type BlockLength = ();
type DbWeight = ();
}
}
/// System configuration trait. Implemented by runtime. /// System configuration trait. Implemented by runtime.
#[pallet::config] #[pallet::config(with_default)]
#[pallet::disable_frame_system_supertrait_check] #[pallet::disable_frame_system_supertrait_check]
pub trait Config: 'static + Eq + Clone { pub trait Config: 'static + Eq + Clone {
/// The aggregated event type of the runtime.
type RuntimeEvent: Parameter
+ Member
+ From<Event<Self>>
+ Debug
+ IsType<<Self as frame_system::Config>::RuntimeEvent>;
/// The basic call filter to use in Origin. All origins are built with this filter as base, /// The basic call filter to use in Origin. All origins are built with this filter as base,
/// except Root. /// except Root.
#[pallet::no_default]
type BaseCallFilter: Contains<Self::RuntimeCall>; type BaseCallFilter: Contains<Self::RuntimeCall>;
/// Block & extrinsics weights: base values and limits. /// Block & extrinsics weights: base values and limits.
@@ -218,12 +261,14 @@ pub mod pallet {
type BlockLength: Get<limits::BlockLength>; type BlockLength: Get<limits::BlockLength>;
/// The `RuntimeOrigin` type used by dispatchable calls. /// The `RuntimeOrigin` type used by dispatchable calls.
#[pallet::no_default]
type RuntimeOrigin: Into<Result<RawOrigin<Self::AccountId>, Self::RuntimeOrigin>> type RuntimeOrigin: Into<Result<RawOrigin<Self::AccountId>, Self::RuntimeOrigin>>
+ From<RawOrigin<Self::AccountId>> + From<RawOrigin<Self::AccountId>>
+ Clone + Clone
+ OriginTrait<Call = Self::RuntimeCall>; + OriginTrait<Call = Self::RuntimeCall>;
/// The aggregated `RuntimeCall` type. /// The aggregated `RuntimeCall` type.
#[pallet::no_default]
type RuntimeCall: Parameter type RuntimeCall: Parameter
+ Dispatchable<RuntimeOrigin = Self::RuntimeOrigin> + Dispatchable<RuntimeOrigin = Self::RuntimeOrigin>
+ Debug + Debug
@@ -295,13 +340,6 @@ pub mod pallet {
/// The block header. /// The block header.
type Header: Parameter + traits::Header<Number = Self::BlockNumber, Hash = Self::Hash>; type Header: Parameter + traits::Header<Number = Self::BlockNumber, Hash = Self::Hash>;
/// The aggregated event type of the runtime.
type RuntimeEvent: Parameter
+ Member
+ From<Event<Self>>
+ Debug
+ IsType<<Self as frame_system::Config>::RuntimeEvent>;
/// Maximum number of block number to block hash mappings to keep (oldest pruned first). /// Maximum number of block number to block hash mappings to keep (oldest pruned first).
#[pallet::constant] #[pallet::constant]
type BlockHashCount: Get<Self::BlockNumber>; type BlockHashCount: Get<Self::BlockNumber>;
@@ -320,6 +358,7 @@ pub mod pallet {
/// runtime. /// runtime.
/// ///
/// For tests it is okay to use `()` as type, however it will provide "useless" data. /// For tests it is okay to use `()` as type, however it will provide "useless" data.
#[pallet::no_default]
type PalletInfo: PalletInfo; type PalletInfo: PalletInfo;
/// Data to be associated with an account (other than nonce/transaction counter, which this /// Data to be associated with an account (other than nonce/transaction counter, which this
@@ -351,6 +390,7 @@ pub mod pallet {
/// [`Pallet::update_code_in_storage`]). /// [`Pallet::update_code_in_storage`]).
/// It's unlikely that this needs to be customized, unless you are writing a parachain using /// It's unlikely that this needs to be customized, unless you are writing a parachain using
/// `Cumulus`, where the actual code change is deferred. /// `Cumulus`, where the actual code change is deferred.
#[pallet::no_default]
type OnSetCode: SetCode<Self>; type OnSetCode: SetCode<Self>;
/// The maximum number of consumers allowed on a single account. /// The maximum number of consumers allowed on a single account.