From dc5050b8c752d88360d51f03897f1314d3c52cb5 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Mon, 27 Mar 2023 22:41:29 +0800 Subject: [PATCH 01/13] Implement #[pallet::hold_reason] --- .../construct_runtime/expand/hold_reason.rs | 63 ++++++++++++++ .../src/construct_runtime/expand/mod.rs | 2 + .../procedural/src/construct_runtime/mod.rs | 3 + .../procedural/src/construct_runtime/parse.rs | 6 ++ .../src/pallet/expand/tt_default_parts.rs | 4 +- .../procedural/src/pallet/parse/event.rs | 2 +- .../src/pallet/parse/hold_reason.rs | 86 +++++++++++++++++++ .../procedural/src/pallet/parse/mod.rs | 16 ++++ frame/support/test/tests/pallet.rs | 12 +++ .../tests/pallet_ui/hold_reason_non_enum.rs | 14 +++ .../pallet_ui/hold_reason_non_enum.stderr | 5 ++ .../tests/pallet_ui/hold_reason_not_pub.rs | 14 +++ .../pallet_ui/hold_reason_not_pub.stderr | 5 ++ 13 files changed, 230 insertions(+), 2 deletions(-) create mode 100644 frame/support/procedural/src/construct_runtime/expand/hold_reason.rs create mode 100644 frame/support/procedural/src/pallet/parse/hold_reason.rs create mode 100644 frame/support/test/tests/pallet_ui/hold_reason_non_enum.rs create mode 100644 frame/support/test/tests/pallet_ui/hold_reason_non_enum.stderr create mode 100644 frame/support/test/tests/pallet_ui/hold_reason_not_pub.rs create mode 100644 frame/support/test/tests/pallet_ui/hold_reason_not_pub.stderr diff --git a/frame/support/procedural/src/construct_runtime/expand/hold_reason.rs b/frame/support/procedural/src/construct_runtime/expand/hold_reason.rs new file mode 100644 index 0000000000000..bf86321eb5129 --- /dev/null +++ b/frame/support/procedural/src/construct_runtime/expand/hold_reason.rs @@ -0,0 +1,63 @@ +// 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 + +use crate::construct_runtime::{parse::PalletPath, Pallet}; +use proc_macro2::{Ident, TokenStream}; +use quote::quote; + +pub fn expand_outer_hold_reason(pallet_decls: &[Pallet], scrate: &TokenStream) -> TokenStream { + let mut conversion_fns = Vec::new(); + let mut hold_reason_variants = Vec::new(); + for decl in pallet_decls { + if let Some(_) = decl.find_part("HoldReason") { + let variant_name = &decl.name; + let path = &decl.path; + let index = &decl.index; + + conversion_fns.push(expand_conversion_fn(path, variant_name)); + + hold_reason_variants.push(quote! { + #[codec(index = #index)] + #variant_name(#path::HoldReason), + }); + } + } + + quote! { + #[derive( + Copy, Clone, Eq, PartialEq, Ord, PartialOrd, + #scrate::codec::Encode, #scrate::codec::Decode, #scrate::codec::MaxEncodedLen, + #scrate::scale_info::TypeInfo, + #scrate::RuntimeDebug, + )] + pub enum RuntimeHoldReason { + #( #hold_reason_variants )* + } + + #( #conversion_fns )* + } +} + +fn expand_conversion_fn(path: &PalletPath, variant_name: &Ident) -> TokenStream { + quote! { + impl From<#path::HoldReason> for RuntimeHoldReason { + fn from(hr: #path::HoldReason) -> Self { + RuntimeHoldReason::#variant_name(hr) + } + } + } +} diff --git a/frame/support/procedural/src/construct_runtime/expand/mod.rs b/frame/support/procedural/src/construct_runtime/expand/mod.rs index ace0b23bd7f85..2ec801cfb316c 100644 --- a/frame/support/procedural/src/construct_runtime/expand/mod.rs +++ b/frame/support/procedural/src/construct_runtime/expand/mod.rs @@ -18,6 +18,7 @@ mod call; mod config; mod event; +mod hold_reason; mod inherent; mod metadata; mod origin; @@ -26,6 +27,7 @@ mod unsigned; pub use call::expand_outer_dispatch; pub use config::expand_outer_config; pub use event::expand_outer_event; +pub use hold_reason::expand_outer_hold_reason; pub use inherent::expand_outer_inherent; pub use metadata::expand_runtime_metadata; pub use origin::expand_outer_origin; diff --git a/frame/support/procedural/src/construct_runtime/mod.rs b/frame/support/procedural/src/construct_runtime/mod.rs index 37f23efed36c1..4c84ee0a4d89f 100644 --- a/frame/support/procedural/src/construct_runtime/mod.rs +++ b/frame/support/procedural/src/construct_runtime/mod.rs @@ -265,6 +265,7 @@ fn construct_runtime_final_expansion( let inherent = expand::expand_outer_inherent(&name, &block, &unchecked_extrinsic, &pallets, &scrate); let validate_unsigned = expand::expand_outer_validate_unsigned(&name, &pallets, &scrate); + let hold_reason = expand::expand_outer_hold_reason(&pallets, &scrate); let integrity_test = decl_integrity_test(&scrate); let static_assertions = decl_static_assertions(&name, &pallets, &scrate); @@ -307,6 +308,8 @@ fn construct_runtime_final_expansion( #validate_unsigned + #hold_reason + #integrity_test #static_assertions diff --git a/frame/support/procedural/src/construct_runtime/parse.rs b/frame/support/procedural/src/construct_runtime/parse.rs index b0bebcd9e0f21..8af308f23a058 100644 --- a/frame/support/procedural/src/construct_runtime/parse.rs +++ b/frame/support/procedural/src/construct_runtime/parse.rs @@ -38,6 +38,7 @@ mod keyword { syn::custom_keyword!(Origin); syn::custom_keyword!(Inherent); syn::custom_keyword!(ValidateUnsigned); + syn::custom_keyword!(HoldReason); syn::custom_keyword!(exclude_parts); syn::custom_keyword!(use_parts); } @@ -370,6 +371,7 @@ pub enum PalletPartKeyword { Origin(keyword::Origin), Inherent(keyword::Inherent), ValidateUnsigned(keyword::ValidateUnsigned), + HoldReason(keyword::HoldReason), } impl Parse for PalletPartKeyword { @@ -392,6 +394,8 @@ impl Parse for PalletPartKeyword { Ok(Self::Inherent(input.parse()?)) } else if lookahead.peek(keyword::ValidateUnsigned) { Ok(Self::ValidateUnsigned(input.parse()?)) + } else if lookahead.peek(keyword::HoldReason) { + Ok(Self::HoldReason(input.parse()?)) } else { Err(lookahead.error()) } @@ -410,6 +414,7 @@ impl PalletPartKeyword { Self::Origin(_) => "Origin", Self::Inherent(_) => "Inherent", Self::ValidateUnsigned(_) => "ValidateUnsigned", + Self::HoldReason(_) => "HoldReason", } } @@ -435,6 +440,7 @@ impl Spanned for PalletPartKeyword { Self::Origin(inner) => inner.span(), Self::Inherent(inner) => inner.span(), Self::ValidateUnsigned(inner) => inner.span(), + Self::HoldReason(inner) => inner.span(), } } } diff --git a/frame/support/procedural/src/pallet/expand/tt_default_parts.rs b/frame/support/procedural/src/pallet/expand/tt_default_parts.rs index 564b526e93536..448078fdcc677 100644 --- a/frame/support/procedural/src/pallet/expand/tt_default_parts.rs +++ b/frame/support/procedural/src/pallet/expand/tt_default_parts.rs @@ -48,6 +48,8 @@ pub fn expand_tt_default_parts(def: &mut Def) -> proc_macro2::TokenStream { let validate_unsigned_part = def.validate_unsigned.as_ref().map(|_| quote::quote!(ValidateUnsigned,)); + let hold_reason_part = def.hold_reason.as_ref().map(|_| quote::quote!(HoldReason,)); + quote::quote!( // This macro follows the conventions as laid out by the `tt-call` crate. It does not // accept any arguments and simply returns the pallet parts, separated by commas, then @@ -70,7 +72,7 @@ pub fn expand_tt_default_parts(def: &mut Def) -> proc_macro2::TokenStream { tokens = [{ ::{ Pallet, #call_part #storage_part #event_part #origin_part #config_part - #inherent_part #validate_unsigned_part + #inherent_part #validate_unsigned_part #hold_reason_part } }] } diff --git a/frame/support/procedural/src/pallet/parse/event.rs b/frame/support/procedural/src/pallet/parse/event.rs index 64c3afd557335..0fb8ee4f54977 100644 --- a/frame/support/procedural/src/pallet/parse/event.rs +++ b/frame/support/procedural/src/pallet/parse/event.rs @@ -104,7 +104,7 @@ impl EventDef { let item = if let syn::Item::Enum(item) = item { item } else { - return Err(syn::Error::new(item.span(), "Invalid pallet::event, expected item enum")) + return Err(syn::Error::new(item.span(), "Invalid pallet::event, expected enum item")) }; let event_attrs: Vec = diff --git a/frame/support/procedural/src/pallet/parse/hold_reason.rs b/frame/support/procedural/src/pallet/parse/hold_reason.rs new file mode 100644 index 0000000000000..b9d2dc8904d17 --- /dev/null +++ b/frame/support/procedural/src/pallet/parse/hold_reason.rs @@ -0,0 +1,86 @@ +// 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. + +use quote::ToTokens; +use syn::spanned::Spanned; + +mod keyword { + syn::custom_keyword!(HoldReason); +} + +pub struct HoldReasonDef { + /// The index of the HoldReason item in the pallet module. + pub index: usize, + /// The HoldReason keyword used (contains span). + pub hold_reason: keyword::HoldReason, + /// The span of the pallet::hold_reason attribute. + pub attr_span: proc_macro2::Span, +} + +impl HoldReasonDef { + pub fn try_from( + attr_span: proc_macro2::Span, + index: usize, + scrate: &proc_macro2::Ident, + item: &mut syn::Item, + ) -> syn::Result { + let item = if let syn::Item::Enum(item) = item { + item + } else { + return Err(syn::Error::new( + item.span(), + "Invalid pallet::hold_reason, expected enum item", + )) + }; + + if !matches!(item.vis, syn::Visibility::Public(_)) { + let msg = "Invalid pallet::hold_reason, `HoldReason` must be public"; + return Err(syn::Error::new(item.span(), msg)) + } + + let has_derive_attr = item + .attrs + .iter() + .find(|attr| { + attr.parse_meta() + .ok() + .map(|meta| match meta { + syn::Meta::List(syn::MetaList { path, .. }) => + path.get_ident().map(|ident| ident == "derive").unwrap_or(false), + _ => false, + }) + .unwrap_or(false) + }) + .is_some(); + + if !has_derive_attr { + let derive_attr: syn::Attribute = syn::parse_quote! { + #[derive( + Copy, Clone, Eq, PartialEq, Ord, PartialOrd, + #scrate::codec::Encode, #scrate::codec::Decode, #scrate::codec::MaxEncodedLen, + #scrate::scale_info::TypeInfo, + #scrate::RuntimeDebug, + )] + }; + item.attrs.push(derive_attr); + } + + let hold_reason = syn::parse2::(item.ident.to_token_stream())?; + + Ok(HoldReasonDef { index, hold_reason, attr_span }) + } +} diff --git a/frame/support/procedural/src/pallet/parse/mod.rs b/frame/support/procedural/src/pallet/parse/mod.rs index 94e6f7a7c2f7d..b2592e6b80a7e 100644 --- a/frame/support/procedural/src/pallet/parse/mod.rs +++ b/frame/support/procedural/src/pallet/parse/mod.rs @@ -27,6 +27,7 @@ pub mod extra_constants; pub mod genesis_build; pub mod genesis_config; pub mod helper; +pub mod hold_reason; pub mod hooks; pub mod inherent; pub mod origin; @@ -56,6 +57,7 @@ pub struct Def { pub genesis_build: Option, pub validate_unsigned: Option, pub extra_constants: Option, + pub hold_reason: Option, pub type_values: Vec, pub frame_system: syn::Ident, pub frame_support: syn::Ident, @@ -89,6 +91,7 @@ impl Def { let mut genesis_build = None; let mut validate_unsigned = None; let mut extra_constants = None; + let mut hold_reason = None; let mut storages = vec![]; let mut type_values = vec![]; @@ -135,6 +138,13 @@ impl Def { Some(PalletAttr::ExtraConstants(_)) => extra_constants = Some(extra_constants::ExtraConstantsDef::try_from(index, item)?), + Some(PalletAttr::HoldReason(span)) => + hold_reason = Some(hold_reason::HoldReasonDef::try_from( + span, + index, + &frame_support, + item, + )?), Some(attr) => { let msg = "Invalid duplicated attribute"; return Err(syn::Error::new(attr.span(), msg)) @@ -171,6 +181,7 @@ impl Def { origin, inherent, storages, + hold_reason, type_values, frame_system, frame_support, @@ -385,6 +396,7 @@ mod keyword { syn::custom_keyword!(generate_store); syn::custom_keyword!(Store); syn::custom_keyword!(extra_constants); + syn::custom_keyword!(hold_reason); } /// Parse attributes for item in pallet module @@ -404,6 +416,7 @@ enum PalletAttr { ValidateUnsigned(proc_macro2::Span), TypeValue(proc_macro2::Span), ExtraConstants(proc_macro2::Span), + HoldReason(proc_macro2::Span), } impl PalletAttr { @@ -423,6 +436,7 @@ impl PalletAttr { Self::ValidateUnsigned(span) => *span, Self::TypeValue(span) => *span, Self::ExtraConstants(span) => *span, + Self::HoldReason(span) => *span, } } } @@ -464,6 +478,8 @@ impl syn::parse::Parse for PalletAttr { Ok(PalletAttr::TypeValue(content.parse::()?.span())) } else if lookahead.peek(keyword::extra_constants) { Ok(PalletAttr::ExtraConstants(content.parse::()?.span())) + } else if lookahead.peek(keyword::hold_reason) { + Ok(PalletAttr::HoldReason(content.parse::()?.span())) } else { Err(lookahead.error()) } diff --git a/frame/support/test/tests/pallet.rs b/frame/support/test/tests/pallet.rs index 52acdc5c26e03..ff5d6a6a7e74c 100644 --- a/frame/support/test/tests/pallet.rs +++ b/frame/support/test/tests/pallet.rs @@ -476,6 +476,11 @@ pub mod pallet { } } + #[pallet::hold_reason] + pub enum HoldReason { + Staking, + } + #[derive(codec::Encode, sp_runtime::RuntimeDebug)] #[cfg_attr(feature = "std", derive(codec::Decode))] pub enum InherentError { @@ -974,6 +979,13 @@ fn validate_unsigned_expand() { assert_eq!(validity, ValidTransaction::default()); } +#[test] +fn hold_reason_expand() { + let hold_reason: RuntimeHoldReason = pallet::HoldReason::Staking.into(); + + assert_eq!(hold_reason, RuntimeHoldReason::Example(pallet::HoldReason::Staking)); +} + #[test] fn pallet_expand_deposit_event() { TestExternalities::default().execute_with(|| { diff --git a/frame/support/test/tests/pallet_ui/hold_reason_non_enum.rs b/frame/support/test/tests/pallet_ui/hold_reason_non_enum.rs new file mode 100644 index 0000000000000..8001844eb2242 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/hold_reason_non_enum.rs @@ -0,0 +1,14 @@ +#[frame_support::pallet] +mod pallet { + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::hold_reason] + pub struct HoldReason; +} + +fn main() { +} \ No newline at end of file diff --git a/frame/support/test/tests/pallet_ui/hold_reason_non_enum.stderr b/frame/support/test/tests/pallet_ui/hold_reason_non_enum.stderr new file mode 100644 index 0000000000000..fc0dff3e9e19e --- /dev/null +++ b/frame/support/test/tests/pallet_ui/hold_reason_non_enum.stderr @@ -0,0 +1,5 @@ +error: Invalid pallet::hold_reason, expected enum item + --> tests/pallet_ui/hold_reason_non_enum.rs:10:2 + | +10 | pub struct HoldReason; + | ^^^ diff --git a/frame/support/test/tests/pallet_ui/hold_reason_not_pub.rs b/frame/support/test/tests/pallet_ui/hold_reason_not_pub.rs new file mode 100644 index 0000000000000..bb884d014f4d0 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/hold_reason_not_pub.rs @@ -0,0 +1,14 @@ +#[frame_support::pallet] +mod pallet { + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::hold_reason] + enum HoldReason {} +} + +fn main() { +} \ No newline at end of file diff --git a/frame/support/test/tests/pallet_ui/hold_reason_not_pub.stderr b/frame/support/test/tests/pallet_ui/hold_reason_not_pub.stderr new file mode 100644 index 0000000000000..402e188b1eab3 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/hold_reason_not_pub.stderr @@ -0,0 +1,5 @@ +error: Invalid pallet::hold_reason, `HoldReason` must be public + --> tests/pallet_ui/hold_reason_not_pub.rs:10:5 + | +10 | enum HoldReason {} + | ^^^^ From 284cf9a04e6fcd48dce40cee666cf892a409cbb9 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Tue, 28 Mar 2023 18:30:00 +0800 Subject: [PATCH 02/13] Appease clippy --- frame/support/procedural/src/pallet/parse/hold_reason.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/frame/support/procedural/src/pallet/parse/hold_reason.rs b/frame/support/procedural/src/pallet/parse/hold_reason.rs index b9d2dc8904d17..1f4ea39a7ad61 100644 --- a/frame/support/procedural/src/pallet/parse/hold_reason.rs +++ b/frame/support/procedural/src/pallet/parse/hold_reason.rs @@ -55,7 +55,7 @@ impl HoldReasonDef { let has_derive_attr = item .attrs .iter() - .find(|attr| { + .any(|attr| { attr.parse_meta() .ok() .map(|meta| match meta { @@ -64,8 +64,7 @@ impl HoldReasonDef { _ => false, }) .unwrap_or(false) - }) - .is_some(); + }); if !has_derive_attr { let derive_attr: syn::Attribute = syn::parse_quote! { From d179822c436c704b67a73d6bea49416446cff7ea Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Tue, 28 Mar 2023 18:30:41 +0800 Subject: [PATCH 03/13] cargo fmt --- .../src/pallet/parse/hold_reason.rs | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/frame/support/procedural/src/pallet/parse/hold_reason.rs b/frame/support/procedural/src/pallet/parse/hold_reason.rs index 1f4ea39a7ad61..b8fb563da5a40 100644 --- a/frame/support/procedural/src/pallet/parse/hold_reason.rs +++ b/frame/support/procedural/src/pallet/parse/hold_reason.rs @@ -52,19 +52,16 @@ impl HoldReasonDef { return Err(syn::Error::new(item.span(), msg)) } - let has_derive_attr = item - .attrs - .iter() - .any(|attr| { - attr.parse_meta() - .ok() - .map(|meta| match meta { - syn::Meta::List(syn::MetaList { path, .. }) => - path.get_ident().map(|ident| ident == "derive").unwrap_or(false), - _ => false, - }) - .unwrap_or(false) - }); + let has_derive_attr = item.attrs.iter().any(|attr| { + attr.parse_meta() + .ok() + .map(|meta| match meta { + syn::Meta::List(syn::MetaList { path, .. }) => + path.get_ident().map(|ident| ident == "derive").unwrap_or(false), + _ => false, + }) + .unwrap_or(false) + }); if !has_derive_attr { let derive_attr: syn::Attribute = syn::parse_quote! { From 30d7c4a1d8d00c2f92a105b80714890e51d8960d Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Tue, 28 Mar 2023 20:21:36 +0800 Subject: [PATCH 04/13] Update test expectations --- .../construct_runtime_ui/invalid_module_details_keyword.stderr | 2 +- .../test/tests/construct_runtime_ui/invalid_module_entry.stderr | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/support/test/tests/construct_runtime_ui/invalid_module_details_keyword.stderr b/frame/support/test/tests/construct_runtime_ui/invalid_module_details_keyword.stderr index 29df6e4bd8cb5..7bfd6e76dde58 100644 --- a/frame/support/test/tests/construct_runtime_ui/invalid_module_details_keyword.stderr +++ b/frame/support/test/tests/construct_runtime_ui/invalid_module_details_keyword.stderr @@ -1,4 +1,4 @@ -error: expected one of: `Pallet`, `Call`, `Storage`, `Event`, `Config`, `Origin`, `Inherent`, `ValidateUnsigned` +error: expected one of: `Pallet`, `Call`, `Storage`, `Event`, `Config`, `Origin`, `Inherent`, `ValidateUnsigned`, `HoldReason` --> $DIR/invalid_module_details_keyword.rs:9:20 | 9 | system: System::{enum}, diff --git a/frame/support/test/tests/construct_runtime_ui/invalid_module_entry.stderr b/frame/support/test/tests/construct_runtime_ui/invalid_module_entry.stderr index bd3e672dc8b40..18ee681b17235 100644 --- a/frame/support/test/tests/construct_runtime_ui/invalid_module_entry.stderr +++ b/frame/support/test/tests/construct_runtime_ui/invalid_module_entry.stderr @@ -1,4 +1,4 @@ -error: expected one of: `Pallet`, `Call`, `Storage`, `Event`, `Config`, `Origin`, `Inherent`, `ValidateUnsigned` +error: expected one of: `Pallet`, `Call`, `Storage`, `Event`, `Config`, `Origin`, `Inherent`, `ValidateUnsigned`, `HoldReason` --> $DIR/invalid_module_entry.rs:10:23 | 10 | Balance: balances::{Error}, From c9e8b5f130f11262ebe011020d8be10fcd1b5c54 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Tue, 28 Mar 2023 21:15:44 +0800 Subject: [PATCH 05/13] Update test expectations --- frame/support/test/tests/pallet_ui/event_wrong_item.stderr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/support/test/tests/pallet_ui/event_wrong_item.stderr b/frame/support/test/tests/pallet_ui/event_wrong_item.stderr index 21eb0ed35e936..0ef150dfd62e1 100644 --- a/frame/support/test/tests/pallet_ui/event_wrong_item.stderr +++ b/frame/support/test/tests/pallet_ui/event_wrong_item.stderr @@ -1,4 +1,4 @@ -error: Invalid pallet::event, expected item enum +error: Invalid pallet::event, expected enum item --> $DIR/event_wrong_item.rs:19:2 | 19 | pub struct Foo; From 1dfea46ee5bbcae2a4cc6ce37b6bc37a4923db54 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Thu, 30 Mar 2023 14:13:05 +0800 Subject: [PATCH 06/13] Support composite_enum attribute instead --- .../construct_runtime/expand/freeze_reason.rs | 67 +++++++++++++++++ .../construct_runtime/expand/hold_reason.rs | 14 ++-- .../src/construct_runtime/expand/lock_id.rs | 67 +++++++++++++++++ .../src/construct_runtime/expand/mod.rs | 6 ++ .../construct_runtime/expand/slash_reason.rs | 67 +++++++++++++++++ .../procedural/src/construct_runtime/mod.rs | 9 +++ .../procedural/src/construct_runtime/parse.rs | 18 +++++ .../src/pallet/expand/tt_default_parts.rs | 32 ++++++++- frame/support/procedural/src/pallet/mod.rs | 2 +- .../parse/{hold_reason.rs => composite.rs} | 71 +++++++++++++++++-- .../procedural/src/pallet/parse/mod.rs | 52 +++++++++----- frame/support/test/tests/pallet.rs | 18 ++++- .../tests/pallet_ui/hold_reason_non_enum.rs | 2 +- .../tests/pallet_ui/hold_reason_not_pub.rs | 2 +- .../test/tests/pallet_ui/lock_id_duplicate.rs | 17 +++++ .../tests/pallet_ui/lock_id_duplicate.stderr | 5 ++ 16 files changed, 413 insertions(+), 36 deletions(-) create mode 100644 frame/support/procedural/src/construct_runtime/expand/freeze_reason.rs create mode 100644 frame/support/procedural/src/construct_runtime/expand/lock_id.rs create mode 100644 frame/support/procedural/src/construct_runtime/expand/slash_reason.rs rename frame/support/procedural/src/pallet/parse/{hold_reason.rs => composite.rs} (54%) create mode 100644 frame/support/test/tests/pallet_ui/lock_id_duplicate.rs create mode 100644 frame/support/test/tests/pallet_ui/lock_id_duplicate.stderr diff --git a/frame/support/procedural/src/construct_runtime/expand/freeze_reason.rs b/frame/support/procedural/src/construct_runtime/expand/freeze_reason.rs new file mode 100644 index 0000000000000..889168bf6c215 --- /dev/null +++ b/frame/support/procedural/src/construct_runtime/expand/freeze_reason.rs @@ -0,0 +1,67 @@ +// 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 + +use crate::construct_runtime::{parse::PalletPath, Pallet}; +use proc_macro2::{Ident, TokenStream}; +use quote::quote; + +pub fn expand_outer_freeze_reason(pallet_decls: &[Pallet], scrate: &TokenStream) -> TokenStream { + let mut conversion_fns = Vec::new(); + let mut freeze_reason_variants = Vec::new(); + for decl in pallet_decls { + if let Some(_) = decl.find_part("FreezeReason") { + let variant_name = &decl.name; + let path = &decl.path; + let index = decl.index; + + conversion_fns.push(expand_conversion_fn(path, variant_name)); + + freeze_reason_variants.push(expand_variant(index, path, variant_name)); + } + } + + quote! { + #[derive( + Copy, Clone, Eq, PartialEq, Ord, PartialOrd, + #scrate::codec::Encode, #scrate::codec::Decode, #scrate::codec::MaxEncodedLen, + #scrate::scale_info::TypeInfo, + #scrate::RuntimeDebug, + )] + pub enum RuntimeFreezeReason { + #( #freeze_reason_variants )* + } + + #( #conversion_fns )* + } +} + +fn expand_conversion_fn(path: &PalletPath, variant_name: &Ident) -> TokenStream { + quote! { + impl From<#path::FreezeReason> for RuntimeFreezeReason { + fn from(hr: #path::FreezeReason) -> Self { + RuntimeFreezeReason::#variant_name(hr) + } + } + } +} + +fn expand_variant(index: u8, path: &PalletPath, variant_name: &Ident) -> TokenStream { + quote! { + #[codec(index = #index)] + #variant_name(#path::FreezeReason), + } +} diff --git a/frame/support/procedural/src/construct_runtime/expand/hold_reason.rs b/frame/support/procedural/src/construct_runtime/expand/hold_reason.rs index bf86321eb5129..60fc094a4d5e4 100644 --- a/frame/support/procedural/src/construct_runtime/expand/hold_reason.rs +++ b/frame/support/procedural/src/construct_runtime/expand/hold_reason.rs @@ -26,14 +26,11 @@ pub fn expand_outer_hold_reason(pallet_decls: &[Pallet], scrate: &TokenStream) - if let Some(_) = decl.find_part("HoldReason") { let variant_name = &decl.name; let path = &decl.path; - let index = &decl.index; + let index = decl.index; conversion_fns.push(expand_conversion_fn(path, variant_name)); - hold_reason_variants.push(quote! { - #[codec(index = #index)] - #variant_name(#path::HoldReason), - }); + hold_reason_variants.push(expand_variant(index, path, variant_name)); } } @@ -61,3 +58,10 @@ fn expand_conversion_fn(path: &PalletPath, variant_name: &Ident) -> TokenStream } } } + +fn expand_variant(index: u8, path: &PalletPath, variant_name: &Ident) -> TokenStream { + quote! { + #[codec(index = #index)] + #variant_name(#path::HoldReason), + } +} diff --git a/frame/support/procedural/src/construct_runtime/expand/lock_id.rs b/frame/support/procedural/src/construct_runtime/expand/lock_id.rs new file mode 100644 index 0000000000000..77c6fefa0aabd --- /dev/null +++ b/frame/support/procedural/src/construct_runtime/expand/lock_id.rs @@ -0,0 +1,67 @@ +// 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 + +use crate::construct_runtime::{parse::PalletPath, Pallet}; +use proc_macro2::{Ident, TokenStream}; +use quote::quote; + +pub fn expand_outer_lock_id(pallet_decls: &[Pallet], scrate: &TokenStream) -> TokenStream { + let mut conversion_fns = Vec::new(); + let mut lock_id_variants = Vec::new(); + for decl in pallet_decls { + if let Some(_) = decl.find_part("LockId") { + let variant_name = &decl.name; + let path = &decl.path; + let index = decl.index; + + conversion_fns.push(expand_conversion_fn(path, variant_name)); + + lock_id_variants.push(expand_variant(index, path, variant_name)); + } + } + + quote! { + #[derive( + Copy, Clone, Eq, PartialEq, Ord, PartialOrd, + #scrate::codec::Encode, #scrate::codec::Decode, #scrate::codec::MaxEncodedLen, + #scrate::scale_info::TypeInfo, + #scrate::RuntimeDebug, + )] + pub enum RuntimeLockId { + #( #lock_id_variants )* + } + + #( #conversion_fns )* + } +} + +fn expand_conversion_fn(path: &PalletPath, variant_name: &Ident) -> TokenStream { + quote! { + impl From<#path::LockId> for RuntimeLockId { + fn from(hr: #path::LockId) -> Self { + RuntimeLockId::#variant_name(hr) + } + } + } +} + +fn expand_variant(index: u8, path: &PalletPath, variant_name: &Ident) -> TokenStream { + quote! { + #[codec(index = #index)] + #variant_name(#path::LockId), + } +} diff --git a/frame/support/procedural/src/construct_runtime/expand/mod.rs b/frame/support/procedural/src/construct_runtime/expand/mod.rs index 2ec801cfb316c..0fd98bb4dda13 100644 --- a/frame/support/procedural/src/construct_runtime/expand/mod.rs +++ b/frame/support/procedural/src/construct_runtime/expand/mod.rs @@ -18,17 +18,23 @@ mod call; mod config; mod event; +mod freeze_reason; mod hold_reason; mod inherent; +mod lock_id; mod metadata; mod origin; +mod slash_reason; mod unsigned; pub use call::expand_outer_dispatch; pub use config::expand_outer_config; pub use event::expand_outer_event; +pub use freeze_reason::expand_outer_freeze_reason; pub use hold_reason::expand_outer_hold_reason; pub use inherent::expand_outer_inherent; +pub use lock_id::expand_outer_lock_id; pub use metadata::expand_runtime_metadata; pub use origin::expand_outer_origin; +pub use slash_reason::expand_outer_slash_reason; pub use unsigned::expand_outer_validate_unsigned; diff --git a/frame/support/procedural/src/construct_runtime/expand/slash_reason.rs b/frame/support/procedural/src/construct_runtime/expand/slash_reason.rs new file mode 100644 index 0000000000000..2bf647aa7c1f4 --- /dev/null +++ b/frame/support/procedural/src/construct_runtime/expand/slash_reason.rs @@ -0,0 +1,67 @@ +// 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 + +use crate::construct_runtime::{parse::PalletPath, Pallet}; +use proc_macro2::{Ident, TokenStream}; +use quote::quote; + +pub fn expand_outer_slash_reason(pallet_decls: &[Pallet], scrate: &TokenStream) -> TokenStream { + let mut conversion_fns = Vec::new(); + let mut slash_reason_variants = Vec::new(); + for decl in pallet_decls { + if let Some(_) = decl.find_part("SlashReason") { + let variant_name = &decl.name; + let path = &decl.path; + let index = decl.index; + + conversion_fns.push(expand_conversion_fn(path, variant_name)); + + slash_reason_variants.push(expand_variant(index, path, variant_name)); + } + } + + quote! { + #[derive( + Copy, Clone, Eq, PartialEq, Ord, PartialOrd, + #scrate::codec::Encode, #scrate::codec::Decode, #scrate::codec::MaxEncodedLen, + #scrate::scale_info::TypeInfo, + #scrate::RuntimeDebug, + )] + pub enum RuntimeSlashReason { + #( #slash_reason_variants )* + } + + #( #conversion_fns )* + } +} + +fn expand_conversion_fn(path: &PalletPath, variant_name: &Ident) -> TokenStream { + quote! { + impl From<#path::SlashReason> for RuntimeSlashReason { + fn from(hr: #path::SlashReason) -> Self { + RuntimeSlashReason::#variant_name(hr) + } + } + } +} + +fn expand_variant(index: u8, path: &PalletPath, variant_name: &Ident) -> TokenStream { + quote! { + #[codec(index = #index)] + #variant_name(#path::SlashReason), + } +} diff --git a/frame/support/procedural/src/construct_runtime/mod.rs b/frame/support/procedural/src/construct_runtime/mod.rs index 4c84ee0a4d89f..e96a08ba31134 100644 --- a/frame/support/procedural/src/construct_runtime/mod.rs +++ b/frame/support/procedural/src/construct_runtime/mod.rs @@ -265,7 +265,10 @@ fn construct_runtime_final_expansion( let inherent = expand::expand_outer_inherent(&name, &block, &unchecked_extrinsic, &pallets, &scrate); let validate_unsigned = expand::expand_outer_validate_unsigned(&name, &pallets, &scrate); + let freeze_reason = expand::expand_outer_freeze_reason(&pallets, &scrate); let hold_reason = expand::expand_outer_hold_reason(&pallets, &scrate); + let lock_id = expand::expand_outer_lock_id(&pallets, &scrate); + let slash_reason = expand::expand_outer_slash_reason(&pallets, &scrate); let integrity_test = decl_integrity_test(&scrate); let static_assertions = decl_static_assertions(&name, &pallets, &scrate); @@ -308,8 +311,14 @@ fn construct_runtime_final_expansion( #validate_unsigned + #freeze_reason + #hold_reason + #lock_id + + #slash_reason + #integrity_test #static_assertions diff --git a/frame/support/procedural/src/construct_runtime/parse.rs b/frame/support/procedural/src/construct_runtime/parse.rs index 8af308f23a058..5e6979f1d2b3b 100644 --- a/frame/support/procedural/src/construct_runtime/parse.rs +++ b/frame/support/procedural/src/construct_runtime/parse.rs @@ -38,7 +38,10 @@ mod keyword { syn::custom_keyword!(Origin); syn::custom_keyword!(Inherent); syn::custom_keyword!(ValidateUnsigned); + syn::custom_keyword!(FreezeReason); syn::custom_keyword!(HoldReason); + syn::custom_keyword!(LockId); + syn::custom_keyword!(SlashReason); syn::custom_keyword!(exclude_parts); syn::custom_keyword!(use_parts); } @@ -371,7 +374,10 @@ pub enum PalletPartKeyword { Origin(keyword::Origin), Inherent(keyword::Inherent), ValidateUnsigned(keyword::ValidateUnsigned), + FreezeReason(keyword::FreezeReason), HoldReason(keyword::HoldReason), + LockId(keyword::LockId), + SlashReason(keyword::SlashReason), } impl Parse for PalletPartKeyword { @@ -394,8 +400,14 @@ impl Parse for PalletPartKeyword { Ok(Self::Inherent(input.parse()?)) } else if lookahead.peek(keyword::ValidateUnsigned) { Ok(Self::ValidateUnsigned(input.parse()?)) + } else if lookahead.peek(keyword::FreezeReason) { + Ok(Self::FreezeReason(input.parse()?)) } else if lookahead.peek(keyword::HoldReason) { Ok(Self::HoldReason(input.parse()?)) + } else if lookahead.peek(keyword::LockId) { + Ok(Self::LockId(input.parse()?)) + } else if lookahead.peek(keyword::SlashReason) { + Ok(Self::SlashReason(input.parse()?)) } else { Err(lookahead.error()) } @@ -414,7 +426,10 @@ impl PalletPartKeyword { Self::Origin(_) => "Origin", Self::Inherent(_) => "Inherent", Self::ValidateUnsigned(_) => "ValidateUnsigned", + Self::FreezeReason(_) => "FreezeReason", Self::HoldReason(_) => "HoldReason", + Self::LockId(_) => "LockId", + Self::SlashReason(_) => "SlashReason", } } @@ -440,7 +455,10 @@ impl Spanned for PalletPartKeyword { Self::Origin(inner) => inner.span(), Self::Inherent(inner) => inner.span(), Self::ValidateUnsigned(inner) => inner.span(), + Self::FreezeReason(inner) => inner.span(), Self::HoldReason(inner) => inner.span(), + Self::LockId(inner) => inner.span(), + Self::SlashReason(inner) => inner.span(), } } } diff --git a/frame/support/procedural/src/pallet/expand/tt_default_parts.rs b/frame/support/procedural/src/pallet/expand/tt_default_parts.rs index 448078fdcc677..f36c765f7beb6 100644 --- a/frame/support/procedural/src/pallet/expand/tt_default_parts.rs +++ b/frame/support/procedural/src/pallet/expand/tt_default_parts.rs @@ -15,7 +15,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{pallet::Def, COUNTER}; +use crate::{ + pallet::{CompositeKeyword, Def}, + COUNTER, +}; use syn::spanned::Spanned; /// Generate the `tt_default_parts` macro. @@ -48,7 +51,29 @@ pub fn expand_tt_default_parts(def: &mut Def) -> proc_macro2::TokenStream { let validate_unsigned_part = def.validate_unsigned.as_ref().map(|_| quote::quote!(ValidateUnsigned,)); - let hold_reason_part = def.hold_reason.as_ref().map(|_| quote::quote!(HoldReason,)); + let freeze_reason_part = def + .composites + .iter() + .any(|c| matches!(c.composite_keyword, CompositeKeyword::FreezeReason(_))) + .then_some(quote::quote!(FreezeReason,)); + + let hold_reason_part = def + .composites + .iter() + .any(|c| matches!(c.composite_keyword, CompositeKeyword::HoldReason(_))) + .then_some(quote::quote!(HoldReason,)); + + let lock_id_part = def + .composites + .iter() + .any(|c| matches!(c.composite_keyword, CompositeKeyword::LockId(_))) + .then_some(quote::quote!(LockId,)); + + let slash_reason_part = def + .composites + .iter() + .any(|c| matches!(c.composite_keyword, CompositeKeyword::SlashReason(_))) + .then_some(quote::quote!(SlashReason,)); quote::quote!( // This macro follows the conventions as laid out by the `tt-call` crate. It does not @@ -72,7 +97,8 @@ pub fn expand_tt_default_parts(def: &mut Def) -> proc_macro2::TokenStream { tokens = [{ ::{ Pallet, #call_part #storage_part #event_part #origin_part #config_part - #inherent_part #validate_unsigned_part #hold_reason_part + #inherent_part #validate_unsigned_part #freeze_reason_part + #hold_reason_part #lock_id_part #slash_reason_part } }] } diff --git a/frame/support/procedural/src/pallet/mod.rs b/frame/support/procedural/src/pallet/mod.rs index 31048841c64e7..3618711051d7f 100644 --- a/frame/support/procedural/src/pallet/mod.rs +++ b/frame/support/procedural/src/pallet/mod.rs @@ -28,7 +28,7 @@ mod expand; mod parse; -pub use parse::Def; +pub use parse::{composite::keyword::CompositeKeyword, Def}; use syn::spanned::Spanned; mod keyword { diff --git a/frame/support/procedural/src/pallet/parse/hold_reason.rs b/frame/support/procedural/src/pallet/parse/composite.rs similarity index 54% rename from frame/support/procedural/src/pallet/parse/hold_reason.rs rename to frame/support/procedural/src/pallet/parse/composite.rs index b8fb563da5a40..ce36eef36262c 100644 --- a/frame/support/procedural/src/pallet/parse/hold_reason.rs +++ b/frame/support/procedural/src/pallet/parse/composite.rs @@ -18,20 +18,76 @@ use quote::ToTokens; use syn::spanned::Spanned; -mod keyword { +pub mod keyword { + use super::*; + + syn::custom_keyword!(FreezeReason); syn::custom_keyword!(HoldReason); + syn::custom_keyword!(LockId); + syn::custom_keyword!(SlashReason); + pub enum CompositeKeyword { + FreezeReason(FreezeReason), + HoldReason(HoldReason), + LockId(LockId), + SlashReason(SlashReason), + } + + impl Spanned for CompositeKeyword { + fn span(&self) -> proc_macro2::Span { + use CompositeKeyword::*; + match self { + FreezeReason(inner) => inner.span(), + HoldReason(inner) => inner.span(), + LockId(inner) => inner.span(), + SlashReason(inner) => inner.span(), + } + } + } + + impl syn::parse::Parse for CompositeKeyword { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let lookahead = input.lookahead1(); + if lookahead.peek(FreezeReason) { + Ok(Self::FreezeReason(input.parse()?)) + } else if lookahead.peek(HoldReason) { + Ok(Self::HoldReason(input.parse()?)) + } else if lookahead.peek(LockId) { + Ok(Self::LockId(input.parse()?)) + } else if lookahead.peek(SlashReason) { + Ok(Self::SlashReason(input.parse()?)) + } else { + Err(lookahead.error()) + } + } + } + + impl std::fmt::Display for CompositeKeyword { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use CompositeKeyword::*; + write!( + f, + "{}", + match self { + FreezeReason(_) => "FreezeReason", + HoldReason(_) => "HoldReason", + LockId(_) => "LockId", + SlashReason(_) => "SlashReason", + } + ) + } + } } -pub struct HoldReasonDef { +pub struct CompositeDef { /// The index of the HoldReason item in the pallet module. pub index: usize, - /// The HoldReason keyword used (contains span). - pub hold_reason: keyword::HoldReason, + /// The composite keyword used (contains span). + pub composite_keyword: keyword::CompositeKeyword, /// The span of the pallet::hold_reason attribute. pub attr_span: proc_macro2::Span, } -impl HoldReasonDef { +impl CompositeDef { pub fn try_from( attr_span: proc_macro2::Span, index: usize, @@ -75,8 +131,9 @@ impl HoldReasonDef { item.attrs.push(derive_attr); } - let hold_reason = syn::parse2::(item.ident.to_token_stream())?; + let composite_keyword = + syn::parse2::(item.ident.to_token_stream())?; - Ok(HoldReasonDef { index, hold_reason, attr_span }) + Ok(CompositeDef { index, composite_keyword, attr_span }) } } diff --git a/frame/support/procedural/src/pallet/parse/mod.rs b/frame/support/procedural/src/pallet/parse/mod.rs index b2592e6b80a7e..06b9899d03b97 100644 --- a/frame/support/procedural/src/pallet/parse/mod.rs +++ b/frame/support/procedural/src/pallet/parse/mod.rs @@ -20,6 +20,7 @@ //! Parse the module into `Def` struct through `Def::try_from` function. pub mod call; +pub mod composite; pub mod config; pub mod error; pub mod event; @@ -27,7 +28,6 @@ pub mod extra_constants; pub mod genesis_build; pub mod genesis_config; pub mod helper; -pub mod hold_reason; pub mod hooks; pub mod inherent; pub mod origin; @@ -36,6 +36,7 @@ pub mod storage; pub mod type_value; pub mod validate_unsigned; +use composite::{keyword::CompositeKeyword, CompositeDef}; use frame_support_procedural_tools::generate_crate_access_2018; use syn::spanned::Spanned; @@ -57,7 +58,7 @@ pub struct Def { pub genesis_build: Option, pub validate_unsigned: Option, pub extra_constants: Option, - pub hold_reason: Option, + pub composites: Vec, pub type_values: Vec, pub frame_system: syn::Ident, pub frame_support: syn::Ident, @@ -91,9 +92,9 @@ impl Def { let mut genesis_build = None; let mut validate_unsigned = None; let mut extra_constants = None; - let mut hold_reason = None; let mut storages = vec![]; let mut type_values = vec![]; + let mut composites: Vec = vec![]; for (index, item) in items.iter_mut().enumerate() { let pallet_attr: Option = helper::take_first_item_pallet_attr(item)?; @@ -138,13 +139,32 @@ impl Def { Some(PalletAttr::ExtraConstants(_)) => extra_constants = Some(extra_constants::ExtraConstantsDef::try_from(index, item)?), - Some(PalletAttr::HoldReason(span)) => - hold_reason = Some(hold_reason::HoldReasonDef::try_from( - span, - index, - &frame_support, - item, - )?), + Some(PalletAttr::Composite(span)) => { + let composite = + composite::CompositeDef::try_from(span, index, &frame_support, item)?; + if composites.iter().any(|def| { + match (&def.composite_keyword, &composite.composite_keyword) { + ( + CompositeKeyword::FreezeReason(_), + CompositeKeyword::FreezeReason(_), + ) | + (CompositeKeyword::HoldReason(_), CompositeKeyword::HoldReason(_)) | + (CompositeKeyword::LockId(_), CompositeKeyword::LockId(_)) | + ( + CompositeKeyword::SlashReason(_), + CompositeKeyword::SlashReason(_), + ) => true, + _ => false, + } + }) { + let msg = format!( + "Invalid duplicated `{}` definition", + composite.composite_keyword + ); + return Err(syn::Error::new(composite.composite_keyword.span(), &msg)) + } + composites.push(composite); + }, Some(attr) => { let msg = "Invalid duplicated attribute"; return Err(syn::Error::new(attr.span(), msg)) @@ -181,7 +201,7 @@ impl Def { origin, inherent, storages, - hold_reason, + composites, type_values, frame_system, frame_support, @@ -396,7 +416,7 @@ mod keyword { syn::custom_keyword!(generate_store); syn::custom_keyword!(Store); syn::custom_keyword!(extra_constants); - syn::custom_keyword!(hold_reason); + syn::custom_keyword!(composite_enum); } /// Parse attributes for item in pallet module @@ -416,7 +436,7 @@ enum PalletAttr { ValidateUnsigned(proc_macro2::Span), TypeValue(proc_macro2::Span), ExtraConstants(proc_macro2::Span), - HoldReason(proc_macro2::Span), + Composite(proc_macro2::Span), } impl PalletAttr { @@ -436,7 +456,7 @@ impl PalletAttr { Self::ValidateUnsigned(span) => *span, Self::TypeValue(span) => *span, Self::ExtraConstants(span) => *span, - Self::HoldReason(span) => *span, + Self::Composite(span) => *span, } } } @@ -478,8 +498,8 @@ impl syn::parse::Parse for PalletAttr { Ok(PalletAttr::TypeValue(content.parse::()?.span())) } else if lookahead.peek(keyword::extra_constants) { Ok(PalletAttr::ExtraConstants(content.parse::()?.span())) - } else if lookahead.peek(keyword::hold_reason) { - Ok(PalletAttr::HoldReason(content.parse::()?.span())) + } else if lookahead.peek(keyword::composite_enum) { + Ok(PalletAttr::Composite(content.parse::()?.span())) } else { Err(lookahead.error()) } diff --git a/frame/support/test/tests/pallet.rs b/frame/support/test/tests/pallet.rs index ff5d6a6a7e74c..093789369b427 100644 --- a/frame/support/test/tests/pallet.rs +++ b/frame/support/test/tests/pallet.rs @@ -476,7 +476,7 @@ pub mod pallet { } } - #[pallet::hold_reason] + #[pallet::composite_enum] pub enum HoldReason { Staking, } @@ -575,6 +575,16 @@ pub mod pallet2 { { fn build(&self) {} } + + #[pallet::composite_enum] + pub enum HoldReason { + Governance, + } + + #[pallet::composite_enum] + pub enum SlashReason { + Equivocation, + } } /// Test that the supertrait check works when we pass some parameter to the `frame_system::Config`. @@ -980,10 +990,14 @@ fn validate_unsigned_expand() { } #[test] -fn hold_reason_expand() { +fn composite_expand() { let hold_reason: RuntimeHoldReason = pallet::HoldReason::Staking.into(); + let hold_reason2: RuntimeHoldReason = pallet2::HoldReason::Governance.into(); + let slash_reason: RuntimeSlashReason = pallet2::SlashReason::Equivocation.into(); assert_eq!(hold_reason, RuntimeHoldReason::Example(pallet::HoldReason::Staking)); + assert_eq!(hold_reason2, RuntimeHoldReason::Example2(pallet2::HoldReason::Governance)); + assert_eq!(slash_reason, RuntimeSlashReason::Example2(pallet2::SlashReason::Equivocation)); } #[test] diff --git a/frame/support/test/tests/pallet_ui/hold_reason_non_enum.rs b/frame/support/test/tests/pallet_ui/hold_reason_non_enum.rs index 8001844eb2242..8008c465e61ad 100644 --- a/frame/support/test/tests/pallet_ui/hold_reason_non_enum.rs +++ b/frame/support/test/tests/pallet_ui/hold_reason_non_enum.rs @@ -6,7 +6,7 @@ mod pallet { #[pallet::pallet] pub struct Pallet(core::marker::PhantomData); - #[pallet::hold_reason] + #[pallet::composite_enum] pub struct HoldReason; } diff --git a/frame/support/test/tests/pallet_ui/hold_reason_not_pub.rs b/frame/support/test/tests/pallet_ui/hold_reason_not_pub.rs index bb884d014f4d0..626dad7411319 100644 --- a/frame/support/test/tests/pallet_ui/hold_reason_not_pub.rs +++ b/frame/support/test/tests/pallet_ui/hold_reason_not_pub.rs @@ -6,7 +6,7 @@ mod pallet { #[pallet::pallet] pub struct Pallet(core::marker::PhantomData); - #[pallet::hold_reason] + #[pallet::composite_enum] enum HoldReason {} } diff --git a/frame/support/test/tests/pallet_ui/lock_id_duplicate.rs b/frame/support/test/tests/pallet_ui/lock_id_duplicate.rs new file mode 100644 index 0000000000000..70418efc41421 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/lock_id_duplicate.rs @@ -0,0 +1,17 @@ +#[frame_support::pallet] +mod pallet { + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::composite_enum] + pub enum LockId {} + + #[pallet::composite_enum] + pub enum LockId {} +} + +fn main() { +} \ No newline at end of file diff --git a/frame/support/test/tests/pallet_ui/lock_id_duplicate.stderr b/frame/support/test/tests/pallet_ui/lock_id_duplicate.stderr new file mode 100644 index 0000000000000..1b7097d0a1095 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/lock_id_duplicate.stderr @@ -0,0 +1,5 @@ +error: Invalid duplicated `LockId` definition + --> tests/pallet_ui/lock_id_duplicate.rs:13:14 + | +13 | pub enum LockId {} + | ^^^^^^ From 5ae02ee4e0bfdd2488a19ef5e01633e944406400 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Thu, 30 Mar 2023 14:35:28 +0800 Subject: [PATCH 07/13] Update test expectations --- .../construct_runtime_ui/invalid_module_details_keyword.stderr | 2 +- .../test/tests/construct_runtime_ui/invalid_module_entry.stderr | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/support/test/tests/construct_runtime_ui/invalid_module_details_keyword.stderr b/frame/support/test/tests/construct_runtime_ui/invalid_module_details_keyword.stderr index 7bfd6e76dde58..42f79e96d4473 100644 --- a/frame/support/test/tests/construct_runtime_ui/invalid_module_details_keyword.stderr +++ b/frame/support/test/tests/construct_runtime_ui/invalid_module_details_keyword.stderr @@ -1,4 +1,4 @@ -error: expected one of: `Pallet`, `Call`, `Storage`, `Event`, `Config`, `Origin`, `Inherent`, `ValidateUnsigned`, `HoldReason` +error: expected one of: `Pallet`, `Call`, `Storage`, `Event`, `Config`, `Origin`, `Inherent`, `ValidateUnsigned`, `FreezeReason`, `HoldReason`, `LockId`, `SlashReason` --> $DIR/invalid_module_details_keyword.rs:9:20 | 9 | system: System::{enum}, diff --git a/frame/support/test/tests/construct_runtime_ui/invalid_module_entry.stderr b/frame/support/test/tests/construct_runtime_ui/invalid_module_entry.stderr index 18ee681b17235..6d535ca4335fc 100644 --- a/frame/support/test/tests/construct_runtime_ui/invalid_module_entry.stderr +++ b/frame/support/test/tests/construct_runtime_ui/invalid_module_entry.stderr @@ -1,4 +1,4 @@ -error: expected one of: `Pallet`, `Call`, `Storage`, `Event`, `Config`, `Origin`, `Inherent`, `ValidateUnsigned`, `HoldReason` +error: expected one of: `Pallet`, `Call`, `Storage`, `Event`, `Config`, `Origin`, `Inherent`, `ValidateUnsigned`, `FreezeReason`, `HoldReason`, `LockId`, `SlashReason` --> $DIR/invalid_module_entry.rs:10:23 | 10 | Balance: balances::{Error}, From 5203114bb0f9f2117ee2059f6472f114fe784aa1 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Sat, 1 Apr 2023 02:00:26 +0800 Subject: [PATCH 08/13] Change hold_reason to composite_enum --- frame/support/procedural/src/pallet/parse/composite.rs | 4 ++-- .../support/test/tests/pallet_ui/hold_reason_non_enum.stderr | 2 +- frame/support/test/tests/pallet_ui/hold_reason_not_pub.stderr | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frame/support/procedural/src/pallet/parse/composite.rs b/frame/support/procedural/src/pallet/parse/composite.rs index ce36eef36262c..96dace56d71cf 100644 --- a/frame/support/procedural/src/pallet/parse/composite.rs +++ b/frame/support/procedural/src/pallet/parse/composite.rs @@ -99,12 +99,12 @@ impl CompositeDef { } else { return Err(syn::Error::new( item.span(), - "Invalid pallet::hold_reason, expected enum item", + "Invalid pallet::composite_enum, expected enum item", )) }; if !matches!(item.vis, syn::Visibility::Public(_)) { - let msg = "Invalid pallet::hold_reason, `HoldReason` must be public"; + let msg = format!("Invalid pallet::composite_enum, `{}` must be public", item.ident); return Err(syn::Error::new(item.span(), msg)) } diff --git a/frame/support/test/tests/pallet_ui/hold_reason_non_enum.stderr b/frame/support/test/tests/pallet_ui/hold_reason_non_enum.stderr index fc0dff3e9e19e..7d86b8d4f1bd5 100644 --- a/frame/support/test/tests/pallet_ui/hold_reason_non_enum.stderr +++ b/frame/support/test/tests/pallet_ui/hold_reason_non_enum.stderr @@ -1,4 +1,4 @@ -error: Invalid pallet::hold_reason, expected enum item +error: Invalid pallet::composite_enum, expected enum item --> tests/pallet_ui/hold_reason_non_enum.rs:10:2 | 10 | pub struct HoldReason; diff --git a/frame/support/test/tests/pallet_ui/hold_reason_not_pub.stderr b/frame/support/test/tests/pallet_ui/hold_reason_not_pub.stderr index 402e188b1eab3..e8b0c14e967dd 100644 --- a/frame/support/test/tests/pallet_ui/hold_reason_not_pub.stderr +++ b/frame/support/test/tests/pallet_ui/hold_reason_not_pub.stderr @@ -1,4 +1,4 @@ -error: Invalid pallet::hold_reason, `HoldReason` must be public +error: Invalid pallet::composite_enum, `HoldReason` must be public --> tests/pallet_ui/hold_reason_not_pub.rs:10:5 | 10 | enum HoldReason {} From cda2f565d6be4453b3b2880131eeb636c75de478 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Sat, 1 Apr 2023 02:00:53 +0800 Subject: [PATCH 09/13] Add UI test for unsupported identifier when using composite_enum --- .../composite_enum_unsupported_identifier.rs | 13 +++++++++++++ .../composite_enum_unsupported_identifier.stderr | 5 +++++ 2 files changed, 18 insertions(+) create mode 100644 frame/support/test/tests/pallet_ui/composite_enum_unsupported_identifier.rs create mode 100644 frame/support/test/tests/pallet_ui/composite_enum_unsupported_identifier.stderr diff --git a/frame/support/test/tests/pallet_ui/composite_enum_unsupported_identifier.rs b/frame/support/test/tests/pallet_ui/composite_enum_unsupported_identifier.rs new file mode 100644 index 0000000000000..74692ee94efd2 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/composite_enum_unsupported_identifier.rs @@ -0,0 +1,13 @@ +#[frame_support::pallet] +mod pallet { + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::composite_enum] + pub enum HoldReasons {} +} + +fn main() {} \ No newline at end of file diff --git a/frame/support/test/tests/pallet_ui/composite_enum_unsupported_identifier.stderr b/frame/support/test/tests/pallet_ui/composite_enum_unsupported_identifier.stderr new file mode 100644 index 0000000000000..902e8923759b2 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/composite_enum_unsupported_identifier.stderr @@ -0,0 +1,5 @@ +error: expected one of: `FreezeReason`, `HoldReason`, `LockId`, `SlashReason` + --> tests/pallet_ui/composite_enum_unsupported_identifier.rs:10:11 + | +10 | pub enum HoldReasons {} + | ^^^^^^^^^^^ From 5c8f46a2e695fe47221e2d0179e0cb4b80ac6f72 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Sat, 1 Apr 2023 02:01:53 +0800 Subject: [PATCH 10/13] Fix comment --- frame/support/procedural/src/pallet/parse/composite.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/support/procedural/src/pallet/parse/composite.rs b/frame/support/procedural/src/pallet/parse/composite.rs index 96dace56d71cf..2406f10d50a33 100644 --- a/frame/support/procedural/src/pallet/parse/composite.rs +++ b/frame/support/procedural/src/pallet/parse/composite.rs @@ -83,7 +83,7 @@ pub struct CompositeDef { pub index: usize, /// The composite keyword used (contains span). pub composite_keyword: keyword::CompositeKeyword, - /// The span of the pallet::hold_reason attribute. + /// The span of the pallet::composite_enum attribute. pub attr_span: proc_macro2::Span, } From f52054b6f568cbb85e47e5b8b2b2072839359779 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Mon, 3 Apr 2023 18:51:36 +0800 Subject: [PATCH 11/13] Add documentation for pallet::composable_enum --- frame/support/procedural/src/lib.rs | 25 ++++++++++++++++++++++++ frame/support/src/lib.rs | 30 +++++++++++++++++++++++++---- 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/frame/support/procedural/src/lib.rs b/frame/support/procedural/src/lib.rs index 45f22202991e8..b61ced9b4af0f 100644 --- a/frame/support/procedural/src/lib.rs +++ b/frame/support/procedural/src/lib.rs @@ -1411,3 +1411,28 @@ pub fn validate_unsigned(_: TokenStream, _: TokenStream) -> TokenStream { pub fn origin(_: TokenStream, _: TokenStream) -> TokenStream { pallet_macro_stub() } + +/// The `#[pallet::composable_enum]` attribute allows you to define an enum that gets composed as an +/// aggregate enum by `construct_runtime`. This is similar in principle with `#[pallet::event]` and +/// `#[pallet::error]`. +/// +/// The attribute currently only supports enum definitions, and identifiers that are named +/// `FreezeReason`, `HoldReason`, `LockId` or `SlashReason`. Arbitrary identifiers for the enum are +/// not supported. The aggregate enum generated by `construct_runtime` will have the name of +/// `RuntimeFreezeReason`, `RuntimeHoldReason`, `RuntimeLockId` and `RuntimeSlashReason` +/// respectively. +/// +/// NOTE: The aggregate enum generated by `construct_runtime` generates a conversion function from +/// the pallet enum to the aggregate enum, and automatically derives the following traits: +/// +/// ```ignore +/// Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, MaxEncodedLen, TypeInfo, +/// RuntimeDebug +/// ``` +/// +/// For ease of usage, when no `#[derive]` attributes are found for the enum under +/// `#[pallet::composable_enum]`, the aforementioned traits are automatically derived for it. +#[proc_macro_attribute] +pub fn composable_enum(_: TokenStream, _: TokenStream) -> TokenStream { + pallet_macro_stub() +} diff --git a/frame/support/src/lib.rs b/frame/support/src/lib.rs index b845828455793..3aedf0becf106 100644 --- a/frame/support/src/lib.rs +++ b/frame/support/src/lib.rs @@ -1552,6 +1552,7 @@ pub mod pallet_prelude { /// * [`pallet::inherent`](#inherent-palletinherent-optional) /// * [`pallet::validate_unsigned`](#validate-unsigned-palletvalidate_unsigned-optional) /// * [`pallet::origin`](#origin-palletorigin-optional) +/// * [`pallet::composable_enum`](#composable-enum-palletcomposable_enum-optional) /// /// Note that at compile-time, the `#[pallet]` macro will analyze and expand all of these /// attributes, ultimately removing their AST nodes before they can be parsed as real @@ -2277,6 +2278,26 @@ pub mod pallet_prelude { /// /// Also see [`pallet::origin`](`frame_support::pallet_macros::origin`) /// +/// # Composable enum `#[pallet::composable_enum]` (optional) +/// +/// The `#[pallet::composable_enum]` attribute allows you to define an enum on the pallet which +/// will then instruct `construct_runtime` to amalgamate all similarly-named enums from other +/// pallets into an aggregate enum. This is similar in principle with how the aggregate enum is +/// generated for `#[pallet::event]` or `#[pallet::error]`. +/// +/// The item tagged with `#[pallet::composable_enum]` MUST be an enum declaration, and can ONLY +/// be the following identifiers: `FreezeReason`, `HoldReason`, `LockId` or `SlashReason`. +/// Custom identifiers are not supported. +/// +/// NOTE: For ease of usage, when no `#[derive]` attributes are detected, the +/// `#[pallet::composable_enum]` attribute will automatically derive the following traits for +/// the enum: +/// +/// ```ignore +/// Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, MaxEncodedLen, TypeInfo, +/// RuntimeDebug +/// ``` +/// /// # General notes on instantiable pallets /// /// An instantiable pallet is one where Config is generic, i.e. `Config`. This allows @@ -2808,10 +2829,11 @@ pub use frame_support_procedural::pallet; /// Contains macro stubs for all of the pallet:: macros pub mod pallet_macros { pub use frame_support_procedural::{ - call_index, compact, config, constant, disable_frame_system_supertrait_check, error, event, - extra_constants, generate_deposit, generate_storage_info, generate_store, genesis_build, - genesis_config, getter, hooks, inherent, origin, storage, storage_prefix, storage_version, - type_value, unbounded, validate_unsigned, weight, whitelist_storage, + call_index, compact, composable_enum, config, constant, + disable_frame_system_supertrait_check, error, event, extra_constants, generate_deposit, + generate_storage_info, generate_store, genesis_build, genesis_config, getter, hooks, + inherent, origin, storage, storage_prefix, storage_version, type_value, unbounded, + validate_unsigned, weight, whitelist_storage, }; } From 0a0502a16e2955f135d8c916e777f65de7f6bfe2 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Tue, 4 Apr 2023 01:53:34 +0800 Subject: [PATCH 12/13] More docs --- frame/support/procedural/src/lib.rs | 4 +++- frame/support/src/lib.rs | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/frame/support/procedural/src/lib.rs b/frame/support/procedural/src/lib.rs index b61ced9b4af0f..dea2d4943de00 100644 --- a/frame/support/procedural/src/lib.rs +++ b/frame/support/procedural/src/lib.rs @@ -1431,7 +1431,9 @@ pub fn origin(_: TokenStream, _: TokenStream) -> TokenStream { /// ``` /// /// For ease of usage, when no `#[derive]` attributes are found for the enum under -/// `#[pallet::composable_enum]`, the aforementioned traits are automatically derived for it. +/// `#[pallet::composable_enum]`, the aforementioned traits are automatically derived for it. The +/// inverse is also true: if there are any `#[derive]` attributes found for the enum, then no traits +/// will automatically be derived for it. #[proc_macro_attribute] pub fn composable_enum(_: TokenStream, _: TokenStream) -> TokenStream { pallet_macro_stub() diff --git a/frame/support/src/lib.rs b/frame/support/src/lib.rs index 3aedf0becf106..e11fd72919a4f 100644 --- a/frame/support/src/lib.rs +++ b/frame/support/src/lib.rs @@ -2297,6 +2297,9 @@ pub mod pallet_prelude { /// Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, MaxEncodedLen, TypeInfo, /// RuntimeDebug /// ``` +/// +/// The inverse is also true: if there are any #[derive] attributes present for the enum, then the +/// attribute will not automatically derive any of the traits described above. /// /// # General notes on instantiable pallets /// From cc0353f6b238883ccb71f58a923866a9a8696504 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Tue, 4 Apr 2023 17:42:17 +0800 Subject: [PATCH 13/13] cargo fmt --- frame/support/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frame/support/src/lib.rs b/frame/support/src/lib.rs index e11fd72919a4f..e0ebeb68a3eaf 100644 --- a/frame/support/src/lib.rs +++ b/frame/support/src/lib.rs @@ -2297,9 +2297,9 @@ pub mod pallet_prelude { /// Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, MaxEncodedLen, TypeInfo, /// RuntimeDebug /// ``` -/// -/// The inverse is also true: if there are any #[derive] attributes present for the enum, then the -/// attribute will not automatically derive any of the traits described above. +/// +/// The inverse is also true: if there are any #[derive] attributes present for the enum, then +/// the attribute will not automatically derive any of the traits described above. /// /// # General notes on instantiable pallets ///