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
@@ -16,14 +16,17 @@
// limitations under the License.
use crate::pallet::Def;
use proc_macro2::TokenStream;
use quote::quote;
use syn::{parse_quote, Item};
///
/// * Generate default rust doc
pub fn expand_config(def: &mut Def) -> proc_macro2::TokenStream {
pub fn expand_config(def: &mut Def) -> TokenStream {
let config = &def.config;
let config_item = {
let item = &mut def.item.content.as_mut().expect("Checked by def parser").1[config.index];
if let syn::Item::Trait(item) = item {
if let Item::Trait(item) = item {
item
} else {
unreachable!("Checked by config parser")
@@ -32,8 +35,9 @@ pub fn expand_config(def: &mut Def) -> proc_macro2::TokenStream {
config_item.attrs.insert(
0,
syn::parse_quote!(
#[doc = r"Configuration trait of this pallet.
parse_quote!(
#[doc = r"
Configuration trait of this pallet.
The main purpose of this trait is to act as an interface between this pallet and the runtime in
which it is embedded in. A type, function, or constant in this trait is essentially left to be
@@ -44,5 +48,25 @@ Consequently, a runtime that wants to include this pallet must implement this tr
),
);
Default::default()
// we only emit `DefaultConfig` if there are trait items, so an empty `DefaultConfig` is
// impossible consequently
if config.default_sub_trait.len() > 0 {
let trait_items = &config.default_sub_trait;
quote!(
/// Based on [`Config`]. Auto-generated by
/// [`#[pallet::config(with_default)]`](`frame_support::pallet_macros::config`).
/// Can be used in tandem with
/// [`#[register_default_config]`](`frame_support::register_default_config`) and
/// [`#[derive_impl]`](`frame_support::derive_impl`) to derive test config traits
/// based on existing pallet config traits in a safe and developer-friendly way.
///
/// See [here](`frame_support::pallet_macros::config`) for more information and caveats about
/// the auto-generated `DefaultConfig` trait and how it is generated.
pub trait DefaultConfig {
#(#trait_items)*
}
)
} else {
Default::default()
}
}
@@ -18,7 +18,7 @@
use super::helper;
use frame_support_procedural_tools::get_doc_literals;
use quote::ToTokens;
use syn::spanned::Spanned;
use syn::{spanned::Spanned, token, Token};
/// List of additional token to be used for parsing.
mod keyword {
@@ -27,12 +27,14 @@ mod keyword {
syn::custom_keyword!(T);
syn::custom_keyword!(I);
syn::custom_keyword!(config);
syn::custom_keyword!(pallet);
syn::custom_keyword!(IsType);
syn::custom_keyword!(RuntimeEvent);
syn::custom_keyword!(Event);
syn::custom_keyword!(constant);
syn::custom_keyword!(frame_system);
syn::custom_keyword!(disable_frame_system_supertrait_check);
syn::custom_keyword!(no_default);
syn::custom_keyword!(constant);
}
/// Input definition for the pallet config.
@@ -52,6 +54,12 @@ pub struct ConfigDef {
pub where_clause: Option<syn::WhereClause>,
/// The span of the pallet::config attribute.
pub attr_span: proc_macro2::Span,
/// Whether a default sub-trait should be generated.
///
/// Contains default sub-trait items (instantiated by `#[pallet::config(with_default)]`).
/// Vec will be empty if `#[pallet::config(with_default)]` is not specified or if there are
/// no trait items
pub default_sub_trait: Vec<syn::TraitItem>,
}
/// Input definition for a constant in pallet config.
@@ -123,40 +131,28 @@ impl syn::parse::Parse for DisableFrameSystemSupertraitCheck {
}
}
/// Parse for `#[pallet::constant]`
pub struct TypeAttrConst {
pound_token: syn::Token![#],
bracket_token: syn::token::Bracket,
pallet_ident: syn::Ident,
path_sep_token: syn::token::PathSep,
constant_keyword: keyword::constant,
/// Parsing for the `typ` portion of `PalletAttr`
#[derive(derive_syn_parse::Parse, PartialEq, Eq)]
pub enum PalletAttrType {
#[peek(keyword::no_default, name = "no_default")]
NoDefault(keyword::no_default),
#[peek(keyword::constant, name = "constant")]
Constant(keyword::constant),
}
impl syn::parse::Parse for TypeAttrConst {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let pound_token = input.parse::<syn::Token![#]>()?;
let content;
let bracket_token = syn::bracketed!(content in input);
let pallet_ident = content.parse::<syn::Ident>()?;
let path_sep_token = content.parse::<syn::Token![::]>()?;
let constant_keyword = content.parse::<keyword::constant>()?;
Ok(Self { pound_token, bracket_token, pallet_ident, path_sep_token, constant_keyword })
}
/// Parsing for `#[pallet::X]`
#[derive(derive_syn_parse::Parse)]
pub struct PalletAttr {
_pound: Token![#],
#[bracket]
_bracket: token::Bracket,
#[inside(_bracket)]
_pallet: keyword::pallet,
#[prefix(Token![::] in _bracket)]
#[inside(_bracket)]
typ: PalletAttrType,
}
impl ToTokens for TypeAttrConst {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
self.pound_token.to_tokens(tokens);
self.bracket_token.surround(tokens, |tokens| {
self.pallet_ident.to_tokens(tokens);
self.path_sep_token.to_tokens(tokens);
self.constant_keyword.to_tokens(tokens);
})
}
}
/// Parse for `$ident::Config`
pub struct ConfigBoundParse(syn::Ident);
impl syn::parse::Parse for ConfigBoundParse {
@@ -307,6 +303,7 @@ impl ConfigDef {
attr_span: proc_macro2::Span,
index: usize,
item: &mut syn::Item,
enable_default: bool,
) -> syn::Result<Self> {
let item = if let syn::Item::Trait(item) = item {
item
@@ -344,38 +341,59 @@ impl ConfigDef {
let mut has_event_type = false;
let mut consts_metadata = vec![];
let mut default_sub_trait = vec![];
for trait_item in &mut item.items {
// Parse for event
has_event_type =
has_event_type || check_event_type(frame_system, trait_item, has_instance)?;
let is_event = check_event_type(frame_system, trait_item, has_instance)?;
has_event_type = has_event_type || is_event;
// Parse for constant
let type_attrs_const: Vec<TypeAttrConst> = helper::take_item_pallet_attrs(trait_item)?;
let mut already_no_default = false;
let mut already_constant = false;
if type_attrs_const.len() > 1 {
let msg = "Invalid attribute in pallet::config, only one attribute is expected";
return Err(syn::Error::new(type_attrs_const[1].span(), msg))
}
if type_attrs_const.len() == 1 {
match trait_item {
syn::TraitItem::Type(ref type_) => {
let constant = ConstMetadataDef::try_from(type_)?;
consts_metadata.push(constant);
while let Ok(Some(pallet_attr)) =
helper::take_first_item_pallet_attr::<PalletAttr>(trait_item)
{
match (pallet_attr.typ, &trait_item) {
(PalletAttrType::Constant(_), syn::TraitItem::Type(ref typ)) => {
if already_constant {
return Err(syn::Error::new(
pallet_attr._bracket.span.join(),
"Duplicate #[pallet::constant] attribute not allowed.",
))
}
already_constant = true;
consts_metadata.push(ConstMetadataDef::try_from(typ)?);
},
_ => {
let msg =
"Invalid pallet::constant in pallet::config, expected type trait \
item";
return Err(syn::Error::new(trait_item.span(), msg))
(PalletAttrType::Constant(_), _) =>
return Err(syn::Error::new(
trait_item.span(),
"Invalid #[pallet::constant] in #[pallet::config], expected type item",
)),
(PalletAttrType::NoDefault(_), _) => {
if !enable_default {
return Err(syn::Error::new(
pallet_attr._bracket.span.join(),
"`#[pallet:no_default]` can only be used if `#[pallet::config(with_default)]` \
has been specified"
))
}
if already_no_default {
return Err(syn::Error::new(
pallet_attr._bracket.span.join(),
"Duplicate #[pallet::no_default] attribute not allowed.",
))
}
already_no_default = true;
},
}
}
if !already_no_default && !is_event && enable_default {
default_sub_trait.push(trait_item.clone());
}
}
let attr: Option<DisableFrameSystemSupertraitCheck> =
helper::take_first_item_pallet_attr(&mut item.attrs)?;
let disable_system_supertrait_check = attr.is_some();
let has_frame_system_supertrait = item.supertraits.iter().any(|s| {
@@ -407,6 +425,14 @@ impl ConfigDef {
return Err(syn::Error::new(item.span(), msg))
}
Ok(Self { index, has_instance, consts_metadata, has_event_type, where_clause, attr_span })
Ok(Self {
index,
has_instance,
consts_metadata,
has_event_type,
where_clause,
attr_span,
default_sub_trait,
})
}
}
@@ -47,7 +47,9 @@ pub trait MutItemAttrs {
}
/// Take the first pallet attribute (e.g. attribute like `#[pallet..]`) and decode it to `Attr`
pub fn take_first_item_pallet_attr<Attr>(item: &mut impl MutItemAttrs) -> syn::Result<Option<Attr>>
pub(crate) fn take_first_item_pallet_attr<Attr>(
item: &mut impl MutItemAttrs,
) -> syn::Result<Option<Attr>>
where
Attr: syn::parse::Parse,
{
@@ -64,7 +66,7 @@ where
}
/// Take all the pallet attributes (e.g. attribute like `#[pallet..]`) and decode them to `Attr`
pub fn take_item_pallet_attrs<Attr>(item: &mut impl MutItemAttrs) -> syn::Result<Vec<Attr>>
pub(crate) fn take_item_pallet_attrs<Attr>(item: &mut impl MutItemAttrs) -> syn::Result<Vec<Attr>>
where
Attr: syn::parse::Parse,
{
@@ -100,8 +100,14 @@ impl Def {
let pallet_attr: Option<PalletAttr> = helper::take_first_item_pallet_attr(item)?;
match pallet_attr {
Some(PalletAttr::Config(span)) if config.is_none() =>
config = Some(config::ConfigDef::try_from(&frame_system, span, index, item)?),
Some(PalletAttr::Config(span, with_default)) if config.is_none() =>
config = Some(config::ConfigDef::try_from(
&frame_system,
span,
index,
item,
with_default,
)?),
Some(PalletAttr::Pallet(span)) if pallet_struct.is_none() => {
let p = pallet_struct::PalletStructDef::try_from(span, index, item)?;
pallet_struct = Some(p);
@@ -405,6 +411,7 @@ mod keyword {
syn::custom_keyword!(weight);
syn::custom_keyword!(event);
syn::custom_keyword!(config);
syn::custom_keyword!(with_default);
syn::custom_keyword!(hooks);
syn::custom_keyword!(inherent);
syn::custom_keyword!(error);
@@ -423,7 +430,7 @@ mod keyword {
/// Parse attributes for item in pallet module
/// syntax must be `pallet::` (e.g. `#[pallet::config]`)
enum PalletAttr {
Config(proc_macro2::Span),
Config(proc_macro2::Span, bool),
Pallet(proc_macro2::Span),
Hooks(proc_macro2::Span),
/// A `#[pallet::call]` with optional attributes to specialize the behaviour.
@@ -480,7 +487,7 @@ enum PalletAttr {
impl PalletAttr {
fn span(&self) -> proc_macro2::Span {
match self {
Self::Config(span) => *span,
Self::Config(span, _) => *span,
Self::Pallet(span) => *span,
Self::Hooks(span) => *span,
Self::RuntimeCall(_, span) => *span,
@@ -509,7 +516,14 @@ impl syn::parse::Parse for PalletAttr {
let lookahead = content.lookahead1();
if lookahead.peek(keyword::config) {
Ok(PalletAttr::Config(content.parse::<keyword::config>()?.span()))
let span = content.parse::<keyword::config>()?.span();
let with_default = content.peek(syn::token::Paren);
if with_default {
let inside_config;
let _paren = syn::parenthesized!(inside_config in content);
inside_config.parse::<keyword::with_default>()?;
}
Ok(PalletAttr::Config(span, with_default))
} else if lookahead.peek(keyword::pallet) {
Ok(PalletAttr::Pallet(content.parse::<keyword::pallet>()?.span()))
} else if lookahead.peek(keyword::hooks) {