From 03db029216d04186a096d38e7103c23c71e01725 Mon Sep 17 00:00:00 2001 From: Guillaume Thiolliere Date: Thu, 24 Oct 2024 16:33:59 +0200 Subject: [PATCH] pallet macro: Support instantiable pallets in tasks (#5194) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix https://github.com/paritytech/polkadot-sdk/issues/5185 also implement handling of attr in expansion in construct-runtime --------- Co-authored-by: Oliver Tale-Yazdi Co-authored-by: Bastian Köcher --- prdoc/pr_5194.prdoc | 11 ++ .../src/construct_runtime/expand/task.rs | 57 +++++-- .../procedural/src/pallet/expand/tasks.rs | 155 +++++++----------- .../src/pallet/expand/tt_default_parts.rs | 4 +- .../procedural/src/pallet/parse/mod.rs | 7 +- .../procedural/src/pallet/parse/tasks.rs | 34 ++-- .../test/tests/pallet_ui/pass/task_valid.rs | 26 +++ .../test/tests/pallet_ui/task_invalid_gen.rs | 39 +++++ .../tests/pallet_ui/task_invalid_gen.stderr | 5 + .../test/tests/pallet_ui/task_invalid_gen2.rs | 39 +++++ .../tests/pallet_ui/task_invalid_gen2.stderr | 13 ++ substrate/frame/support/test/tests/tasks.rs | 135 +++++++++++++++ 12 files changed, 392 insertions(+), 133 deletions(-) create mode 100644 prdoc/pr_5194.prdoc create mode 100644 substrate/frame/support/test/tests/pallet_ui/task_invalid_gen.rs create mode 100644 substrate/frame/support/test/tests/pallet_ui/task_invalid_gen.stderr create mode 100644 substrate/frame/support/test/tests/pallet_ui/task_invalid_gen2.rs create mode 100644 substrate/frame/support/test/tests/pallet_ui/task_invalid_gen2.stderr create mode 100644 substrate/frame/support/test/tests/tasks.rs diff --git a/prdoc/pr_5194.prdoc b/prdoc/pr_5194.prdoc new file mode 100644 index 0000000000000..afb9d57e79e3f --- /dev/null +++ b/prdoc/pr_5194.prdoc @@ -0,0 +1,11 @@ +title: "FRAME: Support instantiable pallets in tasks." + +doc: + - audience: Runtime Dev + description: | + In FRAME, tasks can now be used in instantiable pallet. Also some fix for expansion with + conditional compilation in construct runtine. + +crates: + - name: frame-support-procedural + bump: patch diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/task.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/task.rs index 6531c0e9e0707..1302f86455f2c 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/expand/task.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/task.rs @@ -16,6 +16,7 @@ // limitations under the License use crate::construct_runtime::Pallet; +use core::str::FromStr; use proc_macro2::{Ident, TokenStream as TokenStream2}; use quote::quote; @@ -28,7 +29,8 @@ pub fn expand_outer_task( let mut from_impls = Vec::new(); let mut task_variants = Vec::new(); let mut variant_names = Vec::new(); - let mut task_paths = Vec::new(); + let mut task_types = Vec::new(); + let mut cfg_attrs = Vec::new(); for decl in pallet_decls { if decl.find_part("Task").is_none() { continue @@ -37,18 +39,31 @@ pub fn expand_outer_task( let variant_name = &decl.name; let path = &decl.path; let index = decl.index; + let instance = decl.instance.as_ref().map(|instance| quote!(, #path::#instance)); + let task_type = quote!(#path::Task<#runtime_name #instance>); + + let attr = decl.cfg_pattern.iter().fold(TokenStream2::new(), |acc, pattern| { + let attr = TokenStream2::from_str(&format!("#[cfg({})]", pattern.original())) + .expect("was successfully parsed before; qed"); + quote! { + #acc + #attr + } + }); from_impls.push(quote! { - impl From<#path::Task<#runtime_name>> for RuntimeTask { - fn from(hr: #path::Task<#runtime_name>) -> Self { + #attr + impl From<#task_type> for RuntimeTask { + fn from(hr: #task_type) -> Self { RuntimeTask::#variant_name(hr) } } - impl TryInto<#path::Task<#runtime_name>> for RuntimeTask { + #attr + impl TryInto<#task_type> for RuntimeTask { type Error = (); - fn try_into(self) -> Result<#path::Task<#runtime_name>, Self::Error> { + fn try_into(self) -> Result<#task_type, Self::Error> { match self { RuntimeTask::#variant_name(hr) => Ok(hr), _ => Err(()), @@ -58,13 +73,16 @@ pub fn expand_outer_task( }); task_variants.push(quote! { + #attr #[codec(index = #index)] - #variant_name(#path::Task<#runtime_name>), + #variant_name(#task_type), }); variant_names.push(quote!(#variant_name)); - task_paths.push(quote!(#path::Task)); + task_types.push(task_type); + + cfg_attrs.push(attr); } let prelude = quote!(#scrate::traits::tasks::__private); @@ -91,35 +109,50 @@ pub fn expand_outer_task( fn is_valid(&self) -> bool { match self { - #(RuntimeTask::#variant_names(val) => val.is_valid(),)* + #( + #cfg_attrs + RuntimeTask::#variant_names(val) => val.is_valid(), + )* _ => unreachable!(#INCOMPLETE_MATCH_QED), } } fn run(&self) -> Result<(), #scrate::traits::tasks::__private::DispatchError> { match self { - #(RuntimeTask::#variant_names(val) => val.run(),)* + #( + #cfg_attrs + RuntimeTask::#variant_names(val) => val.run(), + )* _ => unreachable!(#INCOMPLETE_MATCH_QED), } } fn weight(&self) -> #scrate::pallet_prelude::Weight { match self { - #(RuntimeTask::#variant_names(val) => val.weight(),)* + #( + #cfg_attrs + RuntimeTask::#variant_names(val) => val.weight(), + )* _ => unreachable!(#INCOMPLETE_MATCH_QED), } } fn task_index(&self) -> u32 { match self { - #(RuntimeTask::#variant_names(val) => val.task_index(),)* + #( + #cfg_attrs + RuntimeTask::#variant_names(val) => val.task_index(), + )* _ => unreachable!(#INCOMPLETE_MATCH_QED), } } fn iter() -> Self::Enumeration { let mut all_tasks = Vec::new(); - #(all_tasks.extend(#task_paths::iter().map(RuntimeTask::from).collect::>());)* + #( + #cfg_attrs + all_tasks.extend(<#task_types>::iter().map(RuntimeTask::from).collect::>()); + )* all_tasks.into_iter() } } diff --git a/substrate/frame/support/procedural/src/pallet/expand/tasks.rs b/substrate/frame/support/procedural/src/pallet/expand/tasks.rs index 7201c352d92cd..b6346ca8ff342 100644 --- a/substrate/frame/support/procedural/src/pallet/expand/tasks.rs +++ b/substrate/frame/support/procedural/src/pallet/expand/tasks.rs @@ -20,21 +20,25 @@ //! Home of the expansion code for the Tasks API use crate::pallet::{parse::tasks::*, Def}; -use derive_syn_parse::Parse; use inflector::Inflector; use proc_macro2::TokenStream as TokenStream2; use quote::{format_ident, quote, ToTokens}; -use syn::{parse_quote, spanned::Spanned, ItemEnum, ItemImpl}; +use syn::{parse_quote_spanned, spanned::Spanned}; impl TaskEnumDef { /// Since we optionally allow users to manually specify a `#[pallet::task_enum]`, in the /// event they _don't_ specify one (which is actually the most common behavior) we have to /// generate one based on the existing [`TasksDef`]. This method performs that generation. - pub fn generate( - tasks: &TasksDef, - type_decl_bounded_generics: TokenStream2, - type_use_generics: TokenStream2, - ) -> Self { + pub fn generate(tasks: &TasksDef, def: &Def) -> Self { + // We use the span of the attribute to indicate that the error comes from code generated + // for the specific section, otherwise the item impl. + let span = tasks + .tasks_attr + .as_ref() + .map_or_else(|| tasks.item_impl.span(), |attr| attr.span()); + + let type_decl_bounded_generics = def.type_decl_bounded_generics(span); + let variants = if tasks.tasks_attr.is_some() { tasks .tasks @@ -58,7 +62,8 @@ impl TaskEnumDef { } else { Vec::new() }; - let mut task_enum_def: TaskEnumDef = parse_quote! { + + parse_quote_spanned! { span => /// Auto-generated enum that encapsulates all tasks defined by this pallet. /// /// Conceptually similar to the [`Call`] enum, but for tasks. This is only @@ -69,33 +74,32 @@ impl TaskEnumDef { #variants, )* } - }; - task_enum_def.type_use_generics = type_use_generics; - task_enum_def + } } } -impl ToTokens for TaskEnumDef { - fn to_tokens(&self, tokens: &mut TokenStream2) { - let item_enum = &self.item_enum; - let ident = &item_enum.ident; - let vis = &item_enum.vis; - let attrs = &item_enum.attrs; - let generics = &item_enum.generics; - let variants = &item_enum.variants; - let scrate = &self.scrate; - let type_use_generics = &self.type_use_generics; - if self.attr.is_some() { +impl TaskEnumDef { + fn expand_to_tokens(&self, def: &Def) -> TokenStream2 { + if let Some(attr) = &self.attr { + let ident = &self.item_enum.ident; + let vis = &self.item_enum.vis; + let attrs = &self.item_enum.attrs; + let generics = &self.item_enum.generics; + let variants = &self.item_enum.variants; + let frame_support = &def.frame_support; + let type_use_generics = &def.type_use_generics(attr.span()); + let type_impl_generics = &def.type_impl_generics(attr.span()); + // `item_enum` is short-hand / generated enum - tokens.extend(quote! { + quote! { #(#attrs)* #[derive( - #scrate::CloneNoBound, - #scrate::EqNoBound, - #scrate::PartialEqNoBound, - #scrate::pallet_prelude::Encode, - #scrate::pallet_prelude::Decode, - #scrate::pallet_prelude::TypeInfo, + #frame_support::CloneNoBound, + #frame_support::EqNoBound, + #frame_support::PartialEqNoBound, + #frame_support::pallet_prelude::Encode, + #frame_support::pallet_prelude::Decode, + #frame_support::pallet_prelude::TypeInfo, )] #[codec(encode_bound())] #[codec(decode_bound())] @@ -104,32 +108,25 @@ impl ToTokens for TaskEnumDef { #variants #[doc(hidden)] #[codec(skip)] - __Ignore(core::marker::PhantomData, #scrate::Never), + __Ignore(core::marker::PhantomData<(#type_use_generics)>, #frame_support::Never), } - impl core::fmt::Debug for #ident<#type_use_generics> { + impl<#type_impl_generics> core::fmt::Debug for #ident<#type_use_generics> { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct(stringify!(#ident)).field("value", self).finish() } } - }); + } } else { // `item_enum` is a manually specified enum (no attribute) - tokens.extend(item_enum.to_token_stream()); + self.item_enum.to_token_stream() } } } -/// Represents an already-expanded [`TasksDef`]. -#[derive(Parse)] -pub struct ExpandedTasksDef { - pub task_item_impl: ItemImpl, - pub task_trait_impl: ItemImpl, -} - -impl ToTokens for TasksDef { - fn to_tokens(&self, tokens: &mut TokenStream2) { - let scrate = &self.scrate; +impl TasksDef { + fn expand_to_tokens(&self, def: &Def) -> TokenStream2 { + let frame_support = &def.frame_support; let enum_ident = syn::Ident::new("Task", self.enum_ident.span()); let enum_arguments = &self.enum_arguments; let enum_use = quote!(#enum_ident #enum_arguments); @@ -160,21 +157,21 @@ impl ToTokens for TasksDef { let task_arg_names = self.tasks.iter().map(|task| &task.arg_names).collect::>(); let impl_generics = &self.item_impl.generics; - tokens.extend(quote! { + quote! { impl #impl_generics #enum_use { #(#task_fn_impls)* } - impl #impl_generics #scrate::traits::Task for #enum_use + impl #impl_generics #frame_support::traits::Task for #enum_use { - type Enumeration = #scrate::__private::IntoIter<#enum_use>; + type Enumeration = #frame_support::__private::IntoIter<#enum_use>; fn iter() -> Self::Enumeration { - let mut all_tasks = #scrate::__private::vec![]; + let mut all_tasks = #frame_support::__private::vec![]; #(all_tasks .extend(#task_iters.map(|(#(#task_arg_names),*)| #enum_ident::#task_fn_idents { #(#task_arg_names: #task_arg_names.clone()),* }) - .collect::<#scrate::__private::Vec<_>>()); + .collect::<#frame_support::__private::Vec<_>>()); )* all_tasks.into_iter() } @@ -193,7 +190,7 @@ impl ToTokens for TasksDef { } } - fn run(&self) -> Result<(), #scrate::pallet_prelude::DispatchError> { + fn run(&self) -> Result<(), #frame_support::pallet_prelude::DispatchError> { match self.clone() { #(#enum_ident::#task_fn_idents { #(#task_arg_names),* } => { <#enum_use>::#task_fn_names(#( #task_arg_names, )* ) @@ -203,64 +200,32 @@ impl ToTokens for TasksDef { } #[allow(unused_variables)] - fn weight(&self) -> #scrate::pallet_prelude::Weight { + fn weight(&self) -> #frame_support::pallet_prelude::Weight { match self.clone() { #(#enum_ident::#task_fn_idents { #(#task_arg_names),* } => #task_weights,)* Task::__Ignore(_, _) => unreachable!(), } } } - }); + } } } -/// Expands the [`TasksDef`] in the enclosing [`Def`], if present, and returns its tokens. -/// -/// This modifies the underlying [`Def`] in addition to returning any tokens that were added. -pub fn expand_tasks_impl(def: &mut Def) -> TokenStream2 { - let Some(tasks) = &mut def.tasks else { return quote!() }; - let ExpandedTasksDef { task_item_impl, task_trait_impl } = parse_quote!(#tasks); - quote! { - #task_item_impl - #task_trait_impl - } -} +/// Generate code related to tasks. +pub fn expand_tasks(def: &Def) -> TokenStream2 { + let Some(tasks_def) = &def.tasks else { + return quote!(); + }; -/// Represents a fully-expanded [`TaskEnumDef`]. -#[derive(Parse)] -pub struct ExpandedTaskEnum { - pub item_enum: ItemEnum, - pub debug_impl: ItemImpl, -} + let default_task_enum = TaskEnumDef::generate(&tasks_def, def); -/// Modifies a [`Def`] to expand the underlying [`TaskEnumDef`] if present, and also returns -/// its tokens. A blank [`TokenStream2`] is returned if no [`TaskEnumDef`] has been generated -/// or defined. -pub fn expand_task_enum(def: &mut Def) -> TokenStream2 { - let Some(task_enum) = &mut def.task_enum else { return quote!() }; - let ExpandedTaskEnum { item_enum, debug_impl } = parse_quote!(#task_enum); - quote! { - #item_enum - #debug_impl - } -} + let task_enum = def.task_enum.as_ref().unwrap_or_else(|| &default_task_enum); + + let tasks_expansion = tasks_def.expand_to_tokens(def); + let task_enum_expansion = task_enum.expand_to_tokens(def); -/// Modifies a [`Def`] to expand the underlying [`TasksDef`] and also generate a -/// [`TaskEnumDef`] if applicable. The tokens for these items are returned if they are created. -pub fn expand_tasks(def: &mut Def) -> TokenStream2 { - if let Some(tasks_def) = &def.tasks { - if def.task_enum.is_none() { - def.task_enum = Some(TaskEnumDef::generate( - &tasks_def, - def.type_decl_bounded_generics(tasks_def.item_impl.span()), - def.type_use_generics(tasks_def.item_impl.span()), - )); - } - } - let tasks_extra_output = expand_tasks_impl(def); - let task_enum_extra_output = expand_task_enum(def); quote! { - #tasks_extra_output - #task_enum_extra_output + #tasks_expansion + #task_enum_expansion } } diff --git a/substrate/frame/support/procedural/src/pallet/expand/tt_default_parts.rs b/substrate/frame/support/procedural/src/pallet/expand/tt_default_parts.rs index 1975f059152c8..6d53de3133e89 100644 --- a/substrate/frame/support/procedural/src/pallet/expand/tt_default_parts.rs +++ b/substrate/frame/support/procedural/src/pallet/expand/tt_default_parts.rs @@ -33,7 +33,7 @@ pub fn expand_tt_default_parts(def: &mut Def) -> proc_macro2::TokenStream { let call_part = def.call.as_ref().map(|_| quote::quote!(Call,)); - let task_part = def.task_enum.as_ref().map(|_| quote::quote!(Task,)); + let task_part = def.tasks.as_ref().map(|_| quote::quote!(Task,)); let storage_part = (!def.storages.is_empty()).then(|| quote::quote!(Storage,)); @@ -85,7 +85,7 @@ pub fn expand_tt_default_parts(def: &mut Def) -> proc_macro2::TokenStream { let call_part_v2 = def.call.as_ref().map(|_| quote::quote!(+ Call)); - let task_part_v2 = def.task_enum.as_ref().map(|_| quote::quote!(+ Task)); + let task_part_v2 = def.tasks.as_ref().map(|_| quote::quote!(+ Task)); let storage_part_v2 = (!def.storages.is_empty()).then(|| quote::quote!(+ Storage)); diff --git a/substrate/frame/support/procedural/src/pallet/parse/mod.rs b/substrate/frame/support/procedural/src/pallet/parse/mod.rs index 5036f691690f6..c9a150effccbe 100644 --- a/substrate/frame/support/procedural/src/pallet/parse/mod.rs +++ b/substrate/frame/support/procedural/src/pallet/parse/mod.rs @@ -126,11 +126,11 @@ impl Def { }, Some(PalletAttr::RuntimeCall(cw, span)) if call.is_none() => call = Some(call::CallDef::try_from(span, index, item, dev_mode, cw)?), - Some(PalletAttr::Tasks(_)) if tasks.is_none() => { + Some(PalletAttr::Tasks(span)) if tasks.is_none() => { let item_tokens = item.to_token_stream(); // `TasksDef::parse` needs to know if attr was provided so we artificially // re-insert it here - tasks = Some(syn::parse2::(quote::quote! { + tasks = Some(syn::parse2::(quote::quote_spanned! { span => #[pallet::tasks_experimental] #item_tokens })?); @@ -404,6 +404,9 @@ impl Def { if let Some(extra_constants) = &self.extra_constants { instances.extend_from_slice(&extra_constants.instances[..]); } + if let Some(task_enum) = &self.task_enum { + instances.push(task_enum.instance_usage.clone()); + } let mut errors = instances.into_iter().filter_map(|instances| { if instances.has_instance == self.config.has_instance { diff --git a/substrate/frame/support/procedural/src/pallet/parse/tasks.rs b/substrate/frame/support/procedural/src/pallet/parse/tasks.rs index ed860849a4db4..5bff64643df12 100644 --- a/substrate/frame/support/procedural/src/pallet/parse/tasks.rs +++ b/substrate/frame/support/procedural/src/pallet/parse/tasks.rs @@ -25,8 +25,8 @@ use crate::assert_parse_error_matches; #[cfg(test)] use crate::pallet::parse::tests::simulate_manifest_dir; +use super::helper; use derive_syn_parse::Parse; -use frame_support_procedural_tools::generate_access_from_frame_or_crate; use proc_macro2::TokenStream as TokenStream2; use quote::{quote, ToTokens}; use syn::{ @@ -34,8 +34,8 @@ use syn::{ parse2, spanned::Spanned, token::{Bracket, Paren, PathSep, Pound}, - Error, Expr, Ident, ImplItem, ImplItemFn, ItemEnum, ItemImpl, LitInt, Path, PathArguments, - Result, TypePath, + Error, Expr, Ident, ImplItem, ImplItemFn, ItemEnum, ItemImpl, LitInt, PathArguments, Result, + TypePath, }; pub mod keywords { @@ -57,8 +57,6 @@ pub struct TasksDef { pub tasks_attr: Option, pub tasks: Vec, pub item_impl: ItemImpl, - /// Path to `frame_support` - pub scrate: Path, pub enum_ident: Ident, pub enum_arguments: PathArguments, } @@ -114,11 +112,7 @@ impl syn::parse::Parse for TasksDef { let enum_ident = last_seg.ident.clone(); let enum_arguments = last_seg.arguments.clone(); - // We do this here because it would be improper to do something fallible like this at - // the expansion phase. Fallible stuff should happen during parsing. - let scrate = generate_access_from_frame_or_crate("frame-support")?; - - Ok(TasksDef { tasks_attr, item_impl, tasks, scrate, enum_ident, enum_arguments }) + Ok(TasksDef { tasks_attr, item_impl, tasks, enum_ident, enum_arguments }) } } @@ -146,12 +140,11 @@ pub type PalletTaskEnumAttr = PalletTaskAttr; /// Parsing for a manually-specified (or auto-generated) task enum, optionally including the /// attached `#[pallet::task_enum]` attribute. -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct TaskEnumDef { pub attr: Option, pub item_enum: ItemEnum, - pub scrate: Path, - pub type_use_generics: TokenStream2, + pub instance_usage: helper::InstanceUsage, } impl syn::parse::Parse for TaskEnumDef { @@ -163,13 +156,10 @@ impl syn::parse::Parse for TaskEnumDef { None => None, }; - // We do this here because it would be improper to do something fallible like this at - // the expansion phase. Fallible stuff should happen during parsing. - let scrate = generate_access_from_frame_or_crate("frame-support")?; - - let type_use_generics = quote!(T); + let instance_usage = + helper::check_type_def_gen(&item_enum.generics, item_enum.ident.span())?; - Ok(TaskEnumDef { attr, item_enum, scrate, type_use_generics }) + Ok(TaskEnumDef { attr, item_enum, instance_usage }) } } @@ -896,7 +886,7 @@ fn test_parse_task_enum_def_non_task_name() { simulate_manifest_dir("../../examples/basic", || { parse2::(quote! { #[pallet::task_enum] - pub enum Something { + pub enum Something { Foo } }) @@ -921,7 +911,7 @@ fn test_parse_task_enum_def_missing_attr_allowed() { fn test_parse_task_enum_def_missing_attr_alternate_name_allowed() { simulate_manifest_dir("../../examples/basic", || { parse2::(quote! { - pub enum Foo { + pub enum Foo { Red, } }) @@ -951,7 +941,7 @@ fn test_parse_task_enum_def_wrong_item() { assert_parse_error_matches!( parse2::(quote! { #[pallet::task_enum] - pub struct Something; + pub struct Something; }), "expected `enum`" ); diff --git a/substrate/frame/support/test/tests/pallet_ui/pass/task_valid.rs b/substrate/frame/support/test/tests/pallet_ui/pass/task_valid.rs index 234e220f49d89..bc66c09de7e80 100644 --- a/substrate/frame/support/test/tests/pallet_ui/pass/task_valid.rs +++ b/substrate/frame/support/test/tests/pallet_ui/pass/task_valid.rs @@ -39,5 +39,31 @@ mod pallet { } } +#[frame_support::pallet(dev_mode)] +mod pallet_with_instance { + use frame_support::pallet_prelude::{ValueQuery, StorageValue}; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::storage] + pub type SomeStorage = StorageValue<_, u32, ValueQuery>; + + #[pallet::tasks_experimental] + impl, I> Pallet { + #[pallet::task_index(0)] + #[pallet::task_condition(|i, j| i == 0u32 && j == 2u64)] + #[pallet::task_list(vec![(0u32, 2u64), (2u32, 4u64)].iter())] + #[pallet::task_weight(0.into())] + fn foo(_i: u32, _j: u64) -> frame_support::pallet_prelude::DispatchResult { + >::get(); + Ok(()) + } + } +} + fn main() { } diff --git a/substrate/frame/support/test/tests/pallet_ui/task_invalid_gen.rs b/substrate/frame/support/test/tests/pallet_ui/task_invalid_gen.rs new file mode 100644 index 0000000000000..52ae19dcb02d6 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/task_invalid_gen.rs @@ -0,0 +1,39 @@ +// 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. + +#[frame_support::pallet(dev_mode)] +mod pallet_with_instance { + use frame_support::pallet_prelude::{ValueQuery, StorageValue}; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::storage] + pub type SomeStorage = StorageValue<_, u32, ValueQuery>; + + #[pallet::task_enum] + pub enum Task {} + + #[pallet::tasks_experimental] + impl frame_support::traits::Task for Task {} +} + +fn main() { +} diff --git a/substrate/frame/support/test/tests/pallet_ui/task_invalid_gen.stderr b/substrate/frame/support/test/tests/pallet_ui/task_invalid_gen.stderr new file mode 100644 index 0000000000000..1dc9e3d4aa11d --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/task_invalid_gen.stderr @@ -0,0 +1,5 @@ +error: Invalid generic declaration, trait is defined with instance but generic use none + --> tests/pallet_ui/task_invalid_gen.rs:32:11 + | +32 | pub enum Task {} + | ^^^^ diff --git a/substrate/frame/support/test/tests/pallet_ui/task_invalid_gen2.rs b/substrate/frame/support/test/tests/pallet_ui/task_invalid_gen2.rs new file mode 100644 index 0000000000000..56392cbad2dc8 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/task_invalid_gen2.rs @@ -0,0 +1,39 @@ +// 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. + +#[frame_support::pallet(dev_mode)] +mod pallet_with_instance { + use frame_support::pallet_prelude::{ValueQuery, StorageValue}; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::storage] + pub type SomeStorage = StorageValue<_, u32, ValueQuery>; + + #[pallet::task_enum] + pub enum Task {} + + #[pallet::tasks_experimental] + impl frame_support::traits::Task for Task {} +} + +fn main() { +} diff --git a/substrate/frame/support/test/tests/pallet_ui/task_invalid_gen2.stderr b/substrate/frame/support/test/tests/pallet_ui/task_invalid_gen2.stderr new file mode 100644 index 0000000000000..448825e601556 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/task_invalid_gen2.stderr @@ -0,0 +1,13 @@ +error: Invalid type def generics: expected `T` or `T: Config` or `T, I = ()` or `T: Config, I: 'static = ()` + --> tests/pallet_ui/task_invalid_gen2.rs:32:11 + | +32 | pub enum Task {} + | ^^^^ + +error: unexpected end of input, expected `T` + --> tests/pallet_ui/task_invalid_gen2.rs:18:1 + | +18 | #[frame_support::pallet(dev_mode)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `frame_support::pallet` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/substrate/frame/support/test/tests/tasks.rs b/substrate/frame/support/test/tests/tasks.rs new file mode 100644 index 0000000000000..97e58388362bb --- /dev/null +++ b/substrate/frame/support/test/tests/tasks.rs @@ -0,0 +1,135 @@ +// 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(feature = "experimental")] + +#[frame_support::pallet(dev_mode)] +mod my_pallet { + use frame_support::pallet_prelude::{StorageValue, ValueQuery}; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::storage] + pub type SomeStorage = StorageValue<_, (u32, u64), ValueQuery>; + + #[pallet::tasks_experimental] + impl, I> Pallet { + #[pallet::task_index(0)] + #[pallet::task_condition(|i, j| i == 0u32 && j == 2u64)] + #[pallet::task_list(vec![(0u32, 2u64), (2u32, 4u64)].iter())] + #[pallet::task_weight(0.into())] + fn foo(i: u32, j: u64) -> frame_support::pallet_prelude::DispatchResult { + >::put((i, j)); + Ok(()) + } + } +} + +// Another pallet for which we won't implement the default instance. +#[frame_support::pallet(dev_mode)] +mod my_pallet_2 { + use frame_support::pallet_prelude::{StorageValue, ValueQuery}; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::storage] + pub type SomeStorage = StorageValue<_, (u32, u64), ValueQuery>; + + #[pallet::tasks_experimental] + impl, I> Pallet { + #[pallet::task_index(0)] + #[pallet::task_condition(|i, j| i == 0u32 && j == 2u64)] + #[pallet::task_list(vec![(0u32, 2u64), (2u32, 4u64)].iter())] + #[pallet::task_weight(0.into())] + fn foo(i: u32, j: u64) -> frame_support::pallet_prelude::DispatchResult { + >::put((i, j)); + Ok(()) + } + } +} + +type BlockNumber = u32; +type AccountId = u64; +type Header = sp_runtime::generic::Header; +type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; +type Block = sp_runtime::generic::Block; + +frame_support::construct_runtime!( + pub enum Runtime + { + System: frame_system, + MyPallet: my_pallet, + MyPallet2: my_pallet::, + #[cfg(feature = "frame-feature-testing")] + MyPallet3: my_pallet::, + MyPallet4: my_pallet_2::, + } +); + +// NOTE: Needed for derive_impl expansion +use frame_support::derive_impl; +#[frame_support::derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Runtime { + type Block = Block; + type AccountId = AccountId; +} + +impl my_pallet::Config for Runtime {} + +impl my_pallet::Config for Runtime {} + +#[cfg(feature = "frame-feature-testing")] +impl my_pallet::Config for Runtime {} + +impl my_pallet_2::Config for Runtime {} + +fn new_test_ext() -> sp_io::TestExternalities { + use sp_runtime::BuildStorage; + + RuntimeGenesisConfig::default().build_storage().unwrap().into() +} + +#[test] +fn tasks_work() { + new_test_ext().execute_with(|| { + use frame_support::instances::{Instance1, Instance2}; + + let task = RuntimeTask::MyPallet(my_pallet::Task::::Foo { i: 0u32, j: 2u64 }); + + frame_support::assert_ok!(System::do_task(RuntimeOrigin::signed(1), task.clone(),)); + assert_eq!(my_pallet::SomeStorage::::get(), (0, 2)); + + let task = RuntimeTask::MyPallet2(my_pallet::Task::::Foo { i: 0u32, j: 2u64 }); + + frame_support::assert_ok!(System::do_task(RuntimeOrigin::signed(1), task.clone(),)); + assert_eq!(my_pallet::SomeStorage::::get(), (0, 2)); + + let task = + RuntimeTask::MyPallet4(my_pallet_2::Task::::Foo { i: 0u32, j: 2u64 }); + + frame_support::assert_ok!(System::do_task(RuntimeOrigin::signed(1), task.clone(),)); + assert_eq!(my_pallet_2::SomeStorage::::get(), (0, 2)); + }); +}