mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-27 09:17:58 +00:00
Tasks: general system for recognizing and executing service work (#1343)
`polkadot-sdk` version of original tasks PR located here: https://github.com/paritytech/substrate/pull/14329 Fixes #206 ## Status - [x] Generic `Task` trait - [x] `RuntimeTask` aggregated enum, compatible with `construct_runtime!` - [x] Casting between `Task` and `RuntimeTask` without needing `dyn` or `Box` - [x] Tasks Example pallet - [x] Runtime tests for Tasks example pallet - [x] Parsing for task-related macros - [x] Retrofit parsing to make macros optional - [x] Expansion for task-related macros - [x] Adds support for args in tasks - [x] Retrofit tasks example pallet to use macros instead of manual syntax - [x] Weights - [x] Cleanup - [x] UI tests - [x] Docs ## Target Syntax Adapted from https://github.com/paritytech/polkadot-sdk/issues/206#issue-1865172283 ```rust // NOTE: this enum is optional and is auto-generated by the other macros if not present #[pallet::task] pub enum Task<T: Config> { AddNumberIntoTotal { i: u32, } } /// Some running total. #[pallet::storage] pub(super) type Total<T: Config<I>, I: 'static = ()> = StorageValue<_, (u32, u32), ValueQuery>; /// Numbers to be added into the total. #[pallet::storage] pub(super) type Numbers<T: Config<I>, I: 'static = ()> = StorageMap<_, Twox64Concat, u32, u32, OptionQuery>; #[pallet::tasks_experimental] impl<T: Config<I>, I: 'static> Pallet<T, I> { /// Add a pair of numbers into the totals and remove them. #[pallet::task_list(Numbers::<T, I>::iter_keys())] #[pallet::task_condition(|i| Numbers::<T, I>::contains_key(i))] #[pallet::task_index(0)] pub fn add_number_into_total(i: u32) -> DispatchResult { let v = Numbers::<T, I>::take(i).ok_or(Error::<T, I>::NotFound)?; Total::<T, I>::mutate(|(total_keys, total_values)| { *total_keys += i; *total_values += v; }); Ok(()) } } ``` --------- Co-authored-by: Nikhil Gupta <17176722+gupnik@users.noreply.github.com> Co-authored-by: kianenigma <kian@parity.io> Co-authored-by: Nikhil Gupta <> Co-authored-by: Gavin Wood <gavin@parity.io> Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> Co-authored-by: gupnik <nikhilgupta.iitk@gmail.com>
This commit is contained in:
@@ -26,6 +26,7 @@ mod metadata;
|
||||
mod origin;
|
||||
mod outer_enums;
|
||||
mod slash_reason;
|
||||
mod task;
|
||||
mod unsigned;
|
||||
|
||||
pub use call::expand_outer_dispatch;
|
||||
@@ -38,4 +39,5 @@ pub use metadata::expand_runtime_metadata;
|
||||
pub use origin::expand_outer_origin;
|
||||
pub use outer_enums::{expand_outer_enum, OuterEnumType};
|
||||
pub use slash_reason::expand_outer_slash_reason;
|
||||
pub use task::expand_outer_task;
|
||||
pub use unsigned::expand_outer_validate_unsigned;
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
// 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::Pallet;
|
||||
use proc_macro2::{Ident, TokenStream as TokenStream2};
|
||||
use quote::quote;
|
||||
|
||||
/// Expands aggregate `RuntimeTask` enum.
|
||||
pub fn expand_outer_task(
|
||||
runtime_name: &Ident,
|
||||
pallet_decls: &[Pallet],
|
||||
scrate: &TokenStream2,
|
||||
) -> TokenStream2 {
|
||||
let mut from_impls = Vec::new();
|
||||
let mut task_variants = Vec::new();
|
||||
let mut variant_names = Vec::new();
|
||||
let mut task_paths = Vec::new();
|
||||
for decl in pallet_decls {
|
||||
if decl.find_part("Task").is_none() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let variant_name = &decl.name;
|
||||
let path = &decl.path;
|
||||
let index = decl.index;
|
||||
|
||||
from_impls.push(quote! {
|
||||
impl From<#path::Task<#runtime_name>> for RuntimeTask {
|
||||
fn from(hr: #path::Task<#runtime_name>) -> Self {
|
||||
RuntimeTask::#variant_name(hr)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryInto<#path::Task<#runtime_name>> for RuntimeTask {
|
||||
type Error = ();
|
||||
|
||||
fn try_into(self) -> Result<#path::Task<#runtime_name>, Self::Error> {
|
||||
match self {
|
||||
RuntimeTask::#variant_name(hr) => Ok(hr),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
task_variants.push(quote! {
|
||||
#[codec(index = #index)]
|
||||
#variant_name(#path::Task<#runtime_name>),
|
||||
});
|
||||
|
||||
variant_names.push(quote!(#variant_name));
|
||||
|
||||
task_paths.push(quote!(#path::Task));
|
||||
}
|
||||
|
||||
let prelude = quote!(#scrate::traits::tasks::__private);
|
||||
|
||||
const INCOMPLETE_MATCH_QED: &'static str =
|
||||
"cannot have an instantiated RuntimeTask without some Task variant in the runtime. QED";
|
||||
|
||||
let output = quote! {
|
||||
/// An aggregation of all `Task` enums across all pallets included in the current runtime.
|
||||
#[derive(
|
||||
Clone, Eq, PartialEq,
|
||||
#scrate::__private::codec::Encode,
|
||||
#scrate::__private::codec::Decode,
|
||||
#scrate::__private::scale_info::TypeInfo,
|
||||
#scrate::__private::RuntimeDebug,
|
||||
)]
|
||||
pub enum RuntimeTask {
|
||||
#( #task_variants )*
|
||||
}
|
||||
|
||||
#[automatically_derived]
|
||||
impl #scrate::traits::Task for RuntimeTask {
|
||||
type Enumeration = #prelude::IntoIter<RuntimeTask>;
|
||||
|
||||
fn is_valid(&self) -> bool {
|
||||
match self {
|
||||
#(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(),)*
|
||||
_ => unreachable!(#INCOMPLETE_MATCH_QED),
|
||||
}
|
||||
}
|
||||
|
||||
fn weight(&self) -> #scrate::pallet_prelude::Weight {
|
||||
match self {
|
||||
#(RuntimeTask::#variant_names(val) => val.weight(),)*
|
||||
_ => unreachable!(#INCOMPLETE_MATCH_QED),
|
||||
}
|
||||
}
|
||||
|
||||
fn task_index(&self) -> u32 {
|
||||
match self {
|
||||
#(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::<Vec<_>>());)*
|
||||
all_tasks.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
#( #from_impls )*
|
||||
};
|
||||
|
||||
output
|
||||
}
|
||||
@@ -386,6 +386,7 @@ fn construct_runtime_final_expansion(
|
||||
let pallet_to_index = decl_pallet_runtime_setup(&name, &pallets, &scrate);
|
||||
|
||||
let dispatch = expand::expand_outer_dispatch(&name, system_pallet, &pallets, &scrate);
|
||||
let tasks = expand::expand_outer_task(&name, &pallets, &scrate);
|
||||
let metadata = expand::expand_runtime_metadata(
|
||||
&name,
|
||||
&pallets,
|
||||
@@ -475,6 +476,8 @@ fn construct_runtime_final_expansion(
|
||||
|
||||
#dispatch
|
||||
|
||||
#tasks
|
||||
|
||||
#metadata
|
||||
|
||||
#outer_config
|
||||
|
||||
@@ -42,6 +42,7 @@ mod keyword {
|
||||
syn::custom_keyword!(ValidateUnsigned);
|
||||
syn::custom_keyword!(FreezeReason);
|
||||
syn::custom_keyword!(HoldReason);
|
||||
syn::custom_keyword!(Task);
|
||||
syn::custom_keyword!(LockId);
|
||||
syn::custom_keyword!(SlashReason);
|
||||
syn::custom_keyword!(exclude_parts);
|
||||
@@ -404,6 +405,7 @@ pub enum PalletPartKeyword {
|
||||
ValidateUnsigned(keyword::ValidateUnsigned),
|
||||
FreezeReason(keyword::FreezeReason),
|
||||
HoldReason(keyword::HoldReason),
|
||||
Task(keyword::Task),
|
||||
LockId(keyword::LockId),
|
||||
SlashReason(keyword::SlashReason),
|
||||
}
|
||||
@@ -434,6 +436,8 @@ impl Parse for PalletPartKeyword {
|
||||
Ok(Self::FreezeReason(input.parse()?))
|
||||
} else if lookahead.peek(keyword::HoldReason) {
|
||||
Ok(Self::HoldReason(input.parse()?))
|
||||
} else if lookahead.peek(keyword::Task) {
|
||||
Ok(Self::Task(input.parse()?))
|
||||
} else if lookahead.peek(keyword::LockId) {
|
||||
Ok(Self::LockId(input.parse()?))
|
||||
} else if lookahead.peek(keyword::SlashReason) {
|
||||
@@ -459,6 +463,7 @@ impl PalletPartKeyword {
|
||||
Self::ValidateUnsigned(_) => "ValidateUnsigned",
|
||||
Self::FreezeReason(_) => "FreezeReason",
|
||||
Self::HoldReason(_) => "HoldReason",
|
||||
Self::Task(_) => "Task",
|
||||
Self::LockId(_) => "LockId",
|
||||
Self::SlashReason(_) => "SlashReason",
|
||||
}
|
||||
@@ -471,7 +476,7 @@ impl PalletPartKeyword {
|
||||
|
||||
/// Returns the names of all pallet parts that allow to have a generic argument.
|
||||
fn all_generic_arg() -> &'static [&'static str] {
|
||||
&["Event", "Error", "Origin", "Config"]
|
||||
&["Event", "Error", "Origin", "Config", "Task"]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -489,6 +494,7 @@ impl ToTokens for PalletPartKeyword {
|
||||
Self::ValidateUnsigned(inner) => inner.to_tokens(tokens),
|
||||
Self::FreezeReason(inner) => inner.to_tokens(tokens),
|
||||
Self::HoldReason(inner) => inner.to_tokens(tokens),
|
||||
Self::Task(inner) => inner.to_tokens(tokens),
|
||||
Self::LockId(inner) => inner.to_tokens(tokens),
|
||||
Self::SlashReason(inner) => inner.to_tokens(tokens),
|
||||
}
|
||||
|
||||
@@ -646,7 +646,6 @@ pub fn storage_alias(attributes: TokenStream, input: TokenStream) -> TokenStream
|
||||
/// ```
|
||||
///
|
||||
/// where `TestDefaultConfig` was defined and registered as follows:
|
||||
///
|
||||
/// ```ignore
|
||||
/// pub struct TestDefaultConfig;
|
||||
///
|
||||
@@ -673,7 +672,6 @@ pub fn storage_alias(attributes: TokenStream, input: TokenStream) -> TokenStream
|
||||
/// ```
|
||||
///
|
||||
/// The above call to `derive_impl` would expand to roughly the following:
|
||||
///
|
||||
/// ```ignore
|
||||
/// impl frame_system::Config for Test {
|
||||
/// use frame_system::config_preludes::TestDefaultConfig;
|
||||
@@ -881,6 +879,7 @@ pub fn inject_runtime_type(_: TokenStream, tokens: TokenStream) -> TokenStream {
|
||||
let item = syn::parse_macro_input!(item as TraitItemType);
|
||||
if item.ident != "RuntimeCall" &&
|
||||
item.ident != "RuntimeEvent" &&
|
||||
item.ident != "RuntimeTask" &&
|
||||
item.ident != "RuntimeOrigin" &&
|
||||
item.ident != "RuntimeHoldReason" &&
|
||||
item.ident != "RuntimeFreezeReason" &&
|
||||
@@ -888,10 +887,11 @@ pub fn inject_runtime_type(_: TokenStream, tokens: TokenStream) -> TokenStream {
|
||||
{
|
||||
return syn::Error::new_spanned(
|
||||
item,
|
||||
"`#[inject_runtime_type]` can only be attached to `RuntimeCall`, `RuntimeEvent`, `RuntimeOrigin` or `PalletInfo`",
|
||||
"`#[inject_runtime_type]` can only be attached to `RuntimeCall`, `RuntimeEvent`, \
|
||||
`RuntimeTask`, `RuntimeOrigin` or `PalletInfo`",
|
||||
)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
.into()
|
||||
}
|
||||
tokens
|
||||
}
|
||||
@@ -1518,6 +1518,56 @@ pub fn composite_enum(_: TokenStream, _: TokenStream) -> TokenStream {
|
||||
pallet_macro_stub()
|
||||
}
|
||||
|
||||
///
|
||||
/// ---
|
||||
///
|
||||
/// **Rust-Analyzer users**: See the documentation of the Rust item in
|
||||
/// `frame_support::pallet_macros::tasks_experimental`.
|
||||
#[proc_macro_attribute]
|
||||
pub fn tasks_experimental(_: TokenStream, _: TokenStream) -> TokenStream {
|
||||
pallet_macro_stub()
|
||||
}
|
||||
|
||||
///
|
||||
/// ---
|
||||
///
|
||||
/// **Rust-Analyzer users**: See the documentation of the Rust item in
|
||||
/// `frame_support::pallet_macros::task_list`.
|
||||
#[proc_macro_attribute]
|
||||
pub fn task_list(_: TokenStream, _: TokenStream) -> TokenStream {
|
||||
pallet_macro_stub()
|
||||
}
|
||||
|
||||
///
|
||||
/// ---
|
||||
///
|
||||
/// **Rust-Analyzer users**: See the documentation of the Rust item in
|
||||
/// `frame_support::pallet_macros::task_condition`.
|
||||
#[proc_macro_attribute]
|
||||
pub fn task_condition(_: TokenStream, _: TokenStream) -> TokenStream {
|
||||
pallet_macro_stub()
|
||||
}
|
||||
|
||||
///
|
||||
/// ---
|
||||
///
|
||||
/// **Rust-Analyzer users**: See the documentation of the Rust item in
|
||||
/// `frame_support::pallet_macros::task_weight`.
|
||||
#[proc_macro_attribute]
|
||||
pub fn task_weight(_: TokenStream, _: TokenStream) -> TokenStream {
|
||||
pallet_macro_stub()
|
||||
}
|
||||
|
||||
///
|
||||
/// ---
|
||||
///
|
||||
/// **Rust-Analyzer users**: See the documentation of the Rust item in
|
||||
/// `frame_support::pallet_macros::task_index`.
|
||||
#[proc_macro_attribute]
|
||||
pub fn task_index(_: TokenStream, _: TokenStream) -> TokenStream {
|
||||
pallet_macro_stub()
|
||||
}
|
||||
|
||||
/// Can be attached to a module. Doing so will declare that module as importable into a pallet
|
||||
/// via [`#[import_section]`](`macro@import_section`).
|
||||
///
|
||||
|
||||
@@ -31,6 +31,7 @@ mod origin;
|
||||
mod pallet_struct;
|
||||
mod storage;
|
||||
mod store_trait;
|
||||
mod tasks;
|
||||
mod tt_default_parts;
|
||||
mod type_value;
|
||||
mod validate_unsigned;
|
||||
@@ -60,6 +61,7 @@ pub fn expand(mut def: Def) -> proc_macro2::TokenStream {
|
||||
let pallet_struct = pallet_struct::expand_pallet_struct(&mut def);
|
||||
let config = config::expand_config(&mut def);
|
||||
let call = call::expand_call(&mut def);
|
||||
let tasks = tasks::expand_tasks(&mut def);
|
||||
let error = error::expand_error(&mut def);
|
||||
let event = event::expand_event(&mut def);
|
||||
let storages = storage::expand_storages(&mut def);
|
||||
@@ -100,6 +102,7 @@ storage item. Otherwise, all storage items are listed among [*Type Definitions*]
|
||||
#pallet_struct
|
||||
#config
|
||||
#call
|
||||
#tasks
|
||||
#error
|
||||
#event
|
||||
#storages
|
||||
|
||||
@@ -0,0 +1,267 @@
|
||||
//! Contains logic for expanding task-related items.
|
||||
|
||||
// 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.
|
||||
|
||||
//! 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};
|
||||
|
||||
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 {
|
||||
let variants = if tasks.tasks_attr.is_some() {
|
||||
tasks
|
||||
.tasks
|
||||
.iter()
|
||||
.map(|task| {
|
||||
let ident = &task.item.sig.ident;
|
||||
let ident =
|
||||
format_ident!("{}", ident.to_string().to_class_case(), span = ident.span());
|
||||
|
||||
let args = task.item.sig.inputs.iter().collect::<Vec<_>>();
|
||||
|
||||
if args.is_empty() {
|
||||
quote!(#ident)
|
||||
} else {
|
||||
quote!(#ident {
|
||||
#(#args),*
|
||||
})
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
let mut task_enum_def: TaskEnumDef = parse_quote! {
|
||||
/// Auto-generated enum that encapsulates all tasks defined by this pallet.
|
||||
///
|
||||
/// Conceptually similar to the [`Call`] enum, but for tasks. This is only
|
||||
/// generated if there are tasks present in this pallet.
|
||||
#[pallet::task_enum]
|
||||
pub enum Task<#type_decl_bounded_generics> {
|
||||
#(
|
||||
#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() {
|
||||
// `item_enum` is short-hand / generated enum
|
||||
tokens.extend(quote! {
|
||||
#(#attrs)*
|
||||
#[derive(
|
||||
#scrate::CloneNoBound,
|
||||
#scrate::EqNoBound,
|
||||
#scrate::PartialEqNoBound,
|
||||
#scrate::pallet_prelude::Encode,
|
||||
#scrate::pallet_prelude::Decode,
|
||||
#scrate::pallet_prelude::TypeInfo,
|
||||
)]
|
||||
#[codec(encode_bound())]
|
||||
#[codec(decode_bound())]
|
||||
#[scale_info(skip_type_params(#type_use_generics))]
|
||||
#vis enum #ident #generics {
|
||||
#variants
|
||||
#[doc(hidden)]
|
||||
#[codec(skip)]
|
||||
__Ignore(core::marker::PhantomData<T>, #scrate::Never),
|
||||
}
|
||||
|
||||
impl<T: Config> 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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;
|
||||
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);
|
||||
|
||||
let task_fn_idents = self
|
||||
.tasks
|
||||
.iter()
|
||||
.map(|task| {
|
||||
format_ident!(
|
||||
"{}",
|
||||
&task.item.sig.ident.to_string().to_class_case(),
|
||||
span = task.item.sig.ident.span()
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let task_indices = self.tasks.iter().map(|task| &task.index_attr.meta.index);
|
||||
let task_conditions = self.tasks.iter().map(|task| &task.condition_attr.meta.expr);
|
||||
let task_weights = self.tasks.iter().map(|task| &task.weight_attr.meta.expr);
|
||||
let task_iters = self.tasks.iter().map(|task| &task.list_attr.meta.expr);
|
||||
|
||||
let task_fn_impls = self.tasks.iter().map(|task| {
|
||||
let mut task_fn_impl = task.item.clone();
|
||||
task_fn_impl.attrs = vec![];
|
||||
task_fn_impl
|
||||
});
|
||||
|
||||
let task_fn_names = self.tasks.iter().map(|task| &task.item.sig.ident);
|
||||
let task_arg_names = self.tasks.iter().map(|task| &task.arg_names).collect::<Vec<_>>();
|
||||
|
||||
let sp_std = quote!(#scrate::__private::sp_std);
|
||||
let impl_generics = &self.item_impl.generics;
|
||||
tokens.extend(quote! {
|
||||
impl #impl_generics #enum_use
|
||||
{
|
||||
#(#task_fn_impls)*
|
||||
}
|
||||
|
||||
impl #impl_generics #scrate::traits::Task for #enum_use
|
||||
{
|
||||
type Enumeration = #sp_std::vec::IntoIter<#enum_use>;
|
||||
|
||||
fn iter() -> Self::Enumeration {
|
||||
let mut all_tasks = #sp_std::vec![];
|
||||
#(all_tasks
|
||||
.extend(#task_iters.map(|(#(#task_arg_names),*)| #enum_ident::#task_fn_idents { #(#task_arg_names: #task_arg_names.clone()),* })
|
||||
.collect::<#sp_std::vec::Vec<_>>());
|
||||
)*
|
||||
all_tasks.into_iter()
|
||||
}
|
||||
|
||||
fn task_index(&self) -> u32 {
|
||||
match self.clone() {
|
||||
#(#enum_ident::#task_fn_idents { .. } => #task_indices,)*
|
||||
Task::__Ignore(_, _) => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_valid(&self) -> bool {
|
||||
match self.clone() {
|
||||
#(#enum_ident::#task_fn_idents { #(#task_arg_names),* } => (#task_conditions)(#(#task_arg_names),* ),)*
|
||||
Task::__Ignore(_, _) => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn run(&self) -> Result<(), #scrate::pallet_prelude::DispatchError> {
|
||||
match self.clone() {
|
||||
#(#enum_ident::#task_fn_idents { #(#task_arg_names),* } => {
|
||||
<#enum_use>::#task_fn_names(#( #task_arg_names, )* )
|
||||
},)*
|
||||
Task::__Ignore(_, _) => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn weight(&self) -> #scrate::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
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a fully-expanded [`TaskEnumDef`].
|
||||
#[derive(Parse)]
|
||||
pub struct ExpandedTaskEnum {
|
||||
pub item_enum: ItemEnum,
|
||||
pub debug_impl: ItemImpl,
|
||||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,8 @@ 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 storage_part = (!def.storages.is_empty()).then(|| quote::quote!(Storage,));
|
||||
|
||||
let event_part = def.event.as_ref().map(|event| {
|
||||
@@ -99,7 +101,7 @@ pub fn expand_tt_default_parts(def: &mut Def) -> proc_macro2::TokenStream {
|
||||
tokens = [{
|
||||
expanded::{
|
||||
Pallet, #call_part #storage_part #event_part #error_part #origin_part #config_part
|
||||
#inherent_part #validate_unsigned_part #freeze_reason_part
|
||||
#inherent_part #validate_unsigned_part #freeze_reason_part #task_part
|
||||
#hold_reason_part #lock_id_part #slash_reason_part
|
||||
}
|
||||
}]
|
||||
|
||||
@@ -286,8 +286,7 @@ impl CallDef {
|
||||
if weight_attrs.is_empty() && dev_mode {
|
||||
// inject a default O(1) weight when dev mode is enabled and no weight has
|
||||
// been specified on the call
|
||||
let empty_weight: syn::Expr = syn::parse(quote::quote!(0).into())
|
||||
.expect("we are parsing a quoted string; qed");
|
||||
let empty_weight: syn::Expr = syn::parse_quote!(0);
|
||||
weight_attrs.push(FunctionAttr::Weight(empty_weight));
|
||||
}
|
||||
|
||||
|
||||
@@ -26,11 +26,14 @@ pub mod keyword {
|
||||
syn::custom_keyword!(HoldReason);
|
||||
syn::custom_keyword!(LockId);
|
||||
syn::custom_keyword!(SlashReason);
|
||||
syn::custom_keyword!(Task);
|
||||
|
||||
pub enum CompositeKeyword {
|
||||
FreezeReason(FreezeReason),
|
||||
HoldReason(HoldReason),
|
||||
LockId(LockId),
|
||||
SlashReason(SlashReason),
|
||||
Task(Task),
|
||||
}
|
||||
|
||||
impl ToTokens for CompositeKeyword {
|
||||
@@ -41,6 +44,7 @@ pub mod keyword {
|
||||
HoldReason(inner) => inner.to_tokens(tokens),
|
||||
LockId(inner) => inner.to_tokens(tokens),
|
||||
SlashReason(inner) => inner.to_tokens(tokens),
|
||||
Task(inner) => inner.to_tokens(tokens),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -56,6 +60,8 @@ pub mod keyword {
|
||||
Ok(Self::LockId(input.parse()?))
|
||||
} else if lookahead.peek(SlashReason) {
|
||||
Ok(Self::SlashReason(input.parse()?))
|
||||
} else if lookahead.peek(Task) {
|
||||
Ok(Self::Task(input.parse()?))
|
||||
} else {
|
||||
Err(lookahead.error())
|
||||
}
|
||||
@@ -71,6 +77,7 @@ pub mod keyword {
|
||||
match self {
|
||||
FreezeReason(_) => "FreezeReason",
|
||||
HoldReason(_) => "HoldReason",
|
||||
Task(_) => "Task",
|
||||
LockId(_) => "LockId",
|
||||
SlashReason(_) => "SlashReason",
|
||||
}
|
||||
@@ -80,7 +87,7 @@ pub mod keyword {
|
||||
}
|
||||
|
||||
pub struct CompositeDef {
|
||||
/// The index of the HoldReason item in the pallet module.
|
||||
/// The index of the CompositeDef item in the pallet module.
|
||||
pub index: usize,
|
||||
/// The composite keyword used (contains span).
|
||||
pub composite_keyword: keyword::CompositeKeyword,
|
||||
|
||||
@@ -33,11 +33,16 @@ pub mod inherent;
|
||||
pub mod origin;
|
||||
pub mod pallet_struct;
|
||||
pub mod storage;
|
||||
pub mod tasks;
|
||||
pub mod type_value;
|
||||
pub mod validate_unsigned;
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests;
|
||||
|
||||
use composite::{keyword::CompositeKeyword, CompositeDef};
|
||||
use frame_support_procedural_tools::generate_access_from_frame_or_crate;
|
||||
use quote::ToTokens;
|
||||
use syn::spanned::Spanned;
|
||||
|
||||
/// Parsed definition of a pallet.
|
||||
@@ -49,6 +54,8 @@ pub struct Def {
|
||||
pub pallet_struct: pallet_struct::PalletStructDef,
|
||||
pub hooks: Option<hooks::HooksDef>,
|
||||
pub call: Option<call::CallDef>,
|
||||
pub tasks: Option<tasks::TasksDef>,
|
||||
pub task_enum: Option<tasks::TaskEnumDef>,
|
||||
pub storages: Vec<storage::StorageDef>,
|
||||
pub error: Option<error::ErrorDef>,
|
||||
pub event: Option<event::EventDef>,
|
||||
@@ -84,6 +91,8 @@ impl Def {
|
||||
let mut pallet_struct = None;
|
||||
let mut hooks = None;
|
||||
let mut call = None;
|
||||
let mut tasks = None;
|
||||
let mut task_enum = None;
|
||||
let mut error = None;
|
||||
let mut event = None;
|
||||
let mut origin = None;
|
||||
@@ -118,6 +127,32 @@ 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() => {
|
||||
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::<tasks::TasksDef>(quote::quote! {
|
||||
#[pallet::tasks_experimental]
|
||||
#item_tokens
|
||||
})?);
|
||||
|
||||
// replace item with a no-op because it will be handled by the expansion of tasks
|
||||
*item = syn::Item::Verbatim(quote::quote!());
|
||||
}
|
||||
Some(PalletAttr::TaskCondition(span)) => return Err(syn::Error::new(
|
||||
span,
|
||||
"`#[pallet::task_condition]` can only be used on items within an `impl` statement."
|
||||
)),
|
||||
Some(PalletAttr::TaskIndex(span)) => return Err(syn::Error::new(
|
||||
span,
|
||||
"`#[pallet::task_index]` can only be used on items within an `impl` statement."
|
||||
)),
|
||||
Some(PalletAttr::TaskList(span)) => return Err(syn::Error::new(
|
||||
span,
|
||||
"`#[pallet::task_list]` can only be used on items within an `impl` statement."
|
||||
)),
|
||||
Some(PalletAttr::RuntimeTask(_)) if task_enum.is_none() =>
|
||||
task_enum = Some(syn::parse2::<tasks::TaskEnumDef>(item.to_token_stream())?),
|
||||
Some(PalletAttr::Error(span)) if error.is_none() =>
|
||||
error = Some(error::ErrorDef::try_from(span, index, item)?),
|
||||
Some(PalletAttr::RuntimeEvent(span)) if event.is_none() =>
|
||||
@@ -190,6 +225,8 @@ impl Def {
|
||||
return Err(syn::Error::new(item_span, msg))
|
||||
}
|
||||
|
||||
Self::resolve_tasks(&item_span, &mut tasks, &mut task_enum, items)?;
|
||||
|
||||
let def = Def {
|
||||
item,
|
||||
config: config
|
||||
@@ -198,6 +235,8 @@ impl Def {
|
||||
.ok_or_else(|| syn::Error::new(item_span, "Missing `#[pallet::pallet]`"))?,
|
||||
hooks,
|
||||
call,
|
||||
tasks,
|
||||
task_enum,
|
||||
extra_constants,
|
||||
genesis_config,
|
||||
genesis_build,
|
||||
@@ -220,6 +259,99 @@ impl Def {
|
||||
Ok(def)
|
||||
}
|
||||
|
||||
/// Performs extra logic checks necessary for the `#[pallet::tasks_experimental]` feature.
|
||||
fn resolve_tasks(
|
||||
item_span: &proc_macro2::Span,
|
||||
tasks: &mut Option<tasks::TasksDef>,
|
||||
task_enum: &mut Option<tasks::TaskEnumDef>,
|
||||
items: &mut Vec<syn::Item>,
|
||||
) -> syn::Result<()> {
|
||||
// fallback for manual (without macros) definition of tasks impl
|
||||
Self::resolve_manual_tasks_impl(tasks, task_enum, items)?;
|
||||
|
||||
// fallback for manual (without macros) definition of task enum
|
||||
Self::resolve_manual_task_enum(tasks, task_enum, items)?;
|
||||
|
||||
// ensure that if `task_enum` is specified, `tasks` is also specified
|
||||
match (&task_enum, &tasks) {
|
||||
(Some(_), None) =>
|
||||
return Err(syn::Error::new(
|
||||
*item_span,
|
||||
"Missing `#[pallet::tasks_experimental]` impl",
|
||||
)),
|
||||
(None, Some(tasks)) =>
|
||||
if tasks.tasks_attr.is_none() {
|
||||
return Err(syn::Error::new(
|
||||
tasks.item_impl.impl_token.span(),
|
||||
"A `#[pallet::tasks_experimental]` attribute must be attached to your `Task` impl if the \
|
||||
task enum has been omitted",
|
||||
))
|
||||
} else {
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tries to locate task enum based on the tasks impl target if attribute is not specified
|
||||
/// but impl is present. If one is found, `task_enum` is set appropriately.
|
||||
fn resolve_manual_task_enum(
|
||||
tasks: &Option<tasks::TasksDef>,
|
||||
task_enum: &mut Option<tasks::TaskEnumDef>,
|
||||
items: &mut Vec<syn::Item>,
|
||||
) -> syn::Result<()> {
|
||||
let (None, Some(tasks)) = (&task_enum, &tasks) else { return Ok(()) };
|
||||
let syn::Type::Path(type_path) = &*tasks.item_impl.self_ty else { return Ok(()) };
|
||||
let type_path = type_path.path.segments.iter().collect::<Vec<_>>();
|
||||
let (Some(seg), None) = (type_path.get(0), type_path.get(1)) else { return Ok(()) };
|
||||
let mut result = None;
|
||||
for item in items {
|
||||
let syn::Item::Enum(item_enum) = item else { continue };
|
||||
if item_enum.ident == seg.ident {
|
||||
result = Some(syn::parse2::<tasks::TaskEnumDef>(item_enum.to_token_stream())?);
|
||||
// replace item with a no-op because it will be handled by the expansion of
|
||||
// `task_enum`. We use a no-op instead of simply removing it from the vec
|
||||
// so that any indices collected by `Def::try_from` remain accurate
|
||||
*item = syn::Item::Verbatim(quote::quote!());
|
||||
break
|
||||
}
|
||||
}
|
||||
*task_enum = result;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tries to locate a manual tasks impl (an impl impling a trait whose last path segment is
|
||||
/// `Task`) in the event that one has not been found already via the attribute macro
|
||||
pub fn resolve_manual_tasks_impl(
|
||||
tasks: &mut Option<tasks::TasksDef>,
|
||||
task_enum: &Option<tasks::TaskEnumDef>,
|
||||
items: &Vec<syn::Item>,
|
||||
) -> syn::Result<()> {
|
||||
let None = tasks else { return Ok(()) };
|
||||
let mut result = None;
|
||||
for item in items {
|
||||
let syn::Item::Impl(item_impl) = item else { continue };
|
||||
let Some((_, path, _)) = &item_impl.trait_ else { continue };
|
||||
let Some(trait_last_seg) = path.segments.last() else { continue };
|
||||
let syn::Type::Path(target_path) = &*item_impl.self_ty else { continue };
|
||||
let target_path = target_path.path.segments.iter().collect::<Vec<_>>();
|
||||
let (Some(target_ident), None) = (target_path.get(0), target_path.get(1)) else {
|
||||
continue
|
||||
};
|
||||
let matches_task_enum = match task_enum {
|
||||
Some(task_enum) => task_enum.item_enum.ident == target_ident.ident,
|
||||
None => true,
|
||||
};
|
||||
if trait_last_seg.ident == "Task" && matches_task_enum {
|
||||
result = Some(syn::parse2::<tasks::TasksDef>(item_impl.to_token_stream())?);
|
||||
break
|
||||
}
|
||||
}
|
||||
*tasks = result;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check that usage of trait `Event` is consistent with the definition, i.e. it is declared
|
||||
/// and trait defines type RuntimeEvent, or not declared and no trait associated type.
|
||||
fn check_event_usage(&self) -> syn::Result<()> {
|
||||
@@ -408,6 +540,11 @@ impl GenericKind {
|
||||
mod keyword {
|
||||
syn::custom_keyword!(origin);
|
||||
syn::custom_keyword!(call);
|
||||
syn::custom_keyword!(tasks_experimental);
|
||||
syn::custom_keyword!(task_enum);
|
||||
syn::custom_keyword!(task_list);
|
||||
syn::custom_keyword!(task_condition);
|
||||
syn::custom_keyword!(task_index);
|
||||
syn::custom_keyword!(weight);
|
||||
syn::custom_keyword!(event);
|
||||
syn::custom_keyword!(config);
|
||||
@@ -472,6 +609,11 @@ enum PalletAttr {
|
||||
/// instead of the zero weight. So to say: it works together with `dev_mode`.
|
||||
RuntimeCall(Option<InheritedCallWeightAttr>, proc_macro2::Span),
|
||||
Error(proc_macro2::Span),
|
||||
Tasks(proc_macro2::Span),
|
||||
TaskList(proc_macro2::Span),
|
||||
TaskCondition(proc_macro2::Span),
|
||||
TaskIndex(proc_macro2::Span),
|
||||
RuntimeTask(proc_macro2::Span),
|
||||
RuntimeEvent(proc_macro2::Span),
|
||||
RuntimeOrigin(proc_macro2::Span),
|
||||
Inherent(proc_macro2::Span),
|
||||
@@ -490,8 +632,13 @@ impl PalletAttr {
|
||||
Self::Config(span, _) => *span,
|
||||
Self::Pallet(span) => *span,
|
||||
Self::Hooks(span) => *span,
|
||||
Self::RuntimeCall(_, span) => *span,
|
||||
Self::Tasks(span) => *span,
|
||||
Self::TaskCondition(span) => *span,
|
||||
Self::TaskIndex(span) => *span,
|
||||
Self::TaskList(span) => *span,
|
||||
Self::Error(span) => *span,
|
||||
Self::RuntimeTask(span) => *span,
|
||||
Self::RuntimeCall(_, span) => *span,
|
||||
Self::RuntimeEvent(span) => *span,
|
||||
Self::RuntimeOrigin(span) => *span,
|
||||
Self::Inherent(span) => *span,
|
||||
@@ -535,6 +682,16 @@ impl syn::parse::Parse for PalletAttr {
|
||||
false => Some(InheritedCallWeightAttr::parse(&content)?),
|
||||
};
|
||||
Ok(PalletAttr::RuntimeCall(attr, span))
|
||||
} else if lookahead.peek(keyword::tasks_experimental) {
|
||||
Ok(PalletAttr::Tasks(content.parse::<keyword::tasks_experimental>()?.span()))
|
||||
} else if lookahead.peek(keyword::task_enum) {
|
||||
Ok(PalletAttr::RuntimeTask(content.parse::<keyword::task_enum>()?.span()))
|
||||
} else if lookahead.peek(keyword::task_condition) {
|
||||
Ok(PalletAttr::TaskCondition(content.parse::<keyword::task_condition>()?.span()))
|
||||
} else if lookahead.peek(keyword::task_index) {
|
||||
Ok(PalletAttr::TaskIndex(content.parse::<keyword::task_index>()?.span()))
|
||||
} else if lookahead.peek(keyword::task_list) {
|
||||
Ok(PalletAttr::TaskList(content.parse::<keyword::task_list>()?.span()))
|
||||
} else if lookahead.peek(keyword::error) {
|
||||
Ok(PalletAttr::Error(content.parse::<keyword::error>()?.span()))
|
||||
} else if lookahead.peek(keyword::event) {
|
||||
|
||||
@@ -0,0 +1,968 @@
|
||||
// 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.
|
||||
|
||||
//! Home of the parsing code for the Tasks API
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[cfg(test)]
|
||||
use crate::assert_parse_error_matches;
|
||||
|
||||
#[cfg(test)]
|
||||
use crate::pallet::parse::tests::simulate_manifest_dir;
|
||||
|
||||
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::{
|
||||
parse::ParseStream,
|
||||
parse2,
|
||||
spanned::Spanned,
|
||||
token::{Bracket, Paren, PathSep, Pound},
|
||||
Attribute, Error, Expr, Ident, ImplItem, ImplItemFn, ItemEnum, ItemImpl, LitInt, Path,
|
||||
PathArguments, Result, TypePath,
|
||||
};
|
||||
|
||||
pub mod keywords {
|
||||
use syn::custom_keyword;
|
||||
|
||||
custom_keyword!(tasks_experimental);
|
||||
custom_keyword!(task_enum);
|
||||
custom_keyword!(task_list);
|
||||
custom_keyword!(task_condition);
|
||||
custom_keyword!(task_index);
|
||||
custom_keyword!(task_weight);
|
||||
custom_keyword!(pallet);
|
||||
}
|
||||
|
||||
/// Represents the `#[pallet::tasks_experimental]` attribute and its attached item. Also includes
|
||||
/// metadata about the linked [`TaskEnumDef`] if applicable.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TasksDef {
|
||||
pub tasks_attr: Option<PalletTasksAttr>,
|
||||
pub tasks: Vec<TaskDef>,
|
||||
pub item_impl: ItemImpl,
|
||||
/// Path to `frame_support`
|
||||
pub scrate: Path,
|
||||
pub enum_ident: Ident,
|
||||
pub enum_arguments: PathArguments,
|
||||
}
|
||||
|
||||
impl syn::parse::Parse for TasksDef {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let item_impl: ItemImpl = input.parse()?;
|
||||
let (tasks_attrs, normal_attrs) = partition_tasks_attrs(&item_impl);
|
||||
let tasks_attr = match tasks_attrs.first() {
|
||||
Some(attr) => Some(parse2::<PalletTasksAttr>(attr.to_token_stream())?),
|
||||
None => None,
|
||||
};
|
||||
if let Some(extra_tasks_attr) = tasks_attrs.get(1) {
|
||||
return Err(Error::new(
|
||||
extra_tasks_attr.span(),
|
||||
"unexpected extra `#[pallet::tasks_experimental]` attribute",
|
||||
))
|
||||
}
|
||||
let tasks: Vec<TaskDef> = if tasks_attr.is_some() {
|
||||
item_impl
|
||||
.items
|
||||
.clone()
|
||||
.into_iter()
|
||||
.filter(|impl_item| matches!(impl_item, ImplItem::Fn(_)))
|
||||
.map(|item| parse2::<TaskDef>(item.to_token_stream()))
|
||||
.collect::<Result<_>>()?
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
let mut task_indices = HashSet::<LitInt>::new();
|
||||
for task in tasks.iter() {
|
||||
let task_index = &task.index_attr.meta.index;
|
||||
if !task_indices.insert(task_index.clone()) {
|
||||
return Err(Error::new(
|
||||
task_index.span(),
|
||||
format!("duplicate task index `{}`", task_index),
|
||||
))
|
||||
}
|
||||
}
|
||||
let mut item_impl = item_impl;
|
||||
item_impl.attrs = normal_attrs;
|
||||
|
||||
// we require the path on the impl to be a TypePath
|
||||
let enum_path = parse2::<TypePath>(item_impl.self_ty.to_token_stream())?;
|
||||
let segments = enum_path.path.segments.iter().collect::<Vec<_>>();
|
||||
let (Some(last_seg), None) = (segments.get(0), segments.get(1)) else {
|
||||
return Err(Error::new(
|
||||
enum_path.span(),
|
||||
"if specified manually, the task enum must be defined locally in this \
|
||||
pallet and cannot be a re-export",
|
||||
))
|
||||
};
|
||||
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 })
|
||||
}
|
||||
}
|
||||
|
||||
/// Parsing for a `#[pallet::tasks_experimental]` attr.
|
||||
pub type PalletTasksAttr = PalletTaskAttr<keywords::tasks_experimental>;
|
||||
|
||||
/// Parsing for any of the attributes that can be used within a `#[pallet::tasks_experimental]`
|
||||
/// [`ItemImpl`].
|
||||
pub type TaskAttr = PalletTaskAttr<TaskAttrMeta>;
|
||||
|
||||
/// Parsing for a `#[pallet::task_index]` attr.
|
||||
pub type TaskIndexAttr = PalletTaskAttr<TaskIndexAttrMeta>;
|
||||
|
||||
/// Parsing for a `#[pallet::task_condition]` attr.
|
||||
pub type TaskConditionAttr = PalletTaskAttr<TaskConditionAttrMeta>;
|
||||
|
||||
/// Parsing for a `#[pallet::task_list]` attr.
|
||||
pub type TaskListAttr = PalletTaskAttr<TaskListAttrMeta>;
|
||||
|
||||
/// Parsing for a `#[pallet::task_weight]` attr.
|
||||
pub type TaskWeightAttr = PalletTaskAttr<TaskWeightAttrMeta>;
|
||||
|
||||
/// Parsing for a `#[pallet:task_enum]` attr.
|
||||
pub type PalletTaskEnumAttr = PalletTaskAttr<keywords::task_enum>;
|
||||
|
||||
/// Parsing for a manually-specified (or auto-generated) task enum, optionally including the
|
||||
/// attached `#[pallet::task_enum]` attribute.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TaskEnumDef {
|
||||
pub attr: Option<PalletTaskEnumAttr>,
|
||||
pub item_enum: ItemEnum,
|
||||
pub scrate: Path,
|
||||
pub type_use_generics: TokenStream2,
|
||||
}
|
||||
|
||||
impl syn::parse::Parse for TaskEnumDef {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let mut item_enum = input.parse::<ItemEnum>()?;
|
||||
let attr = extract_pallet_attr(&mut item_enum)?;
|
||||
let attr = match attr {
|
||||
Some(attr) => Some(parse2(attr)?),
|
||||
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);
|
||||
|
||||
Ok(TaskEnumDef { attr, item_enum, scrate, type_use_generics })
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents an individual tasks within a [`TasksDef`].
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TaskDef {
|
||||
pub index_attr: TaskIndexAttr,
|
||||
pub condition_attr: TaskConditionAttr,
|
||||
pub list_attr: TaskListAttr,
|
||||
pub weight_attr: TaskWeightAttr,
|
||||
pub normal_attrs: Vec<Attribute>,
|
||||
pub item: ImplItemFn,
|
||||
pub arg_names: Vec<Ident>,
|
||||
}
|
||||
|
||||
impl syn::parse::Parse for TaskDef {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let item = input.parse::<ImplItemFn>()?;
|
||||
// we only want to activate TaskAttrType parsing errors for tasks-related attributes,
|
||||
// so we filter them here
|
||||
let (task_attrs, normal_attrs) = partition_task_attrs(&item);
|
||||
|
||||
let task_attrs: Vec<TaskAttr> = task_attrs
|
||||
.into_iter()
|
||||
.map(|attr| parse2(attr.to_token_stream()))
|
||||
.collect::<Result<_>>()?;
|
||||
|
||||
let Some(index_attr) = task_attrs
|
||||
.iter()
|
||||
.find(|attr| matches!(attr.meta, TaskAttrMeta::TaskIndex(_)))
|
||||
.cloned()
|
||||
else {
|
||||
return Err(Error::new(
|
||||
item.sig.ident.span(),
|
||||
"missing `#[pallet::task_index(..)]` attribute",
|
||||
))
|
||||
};
|
||||
|
||||
let Some(condition_attr) = task_attrs
|
||||
.iter()
|
||||
.find(|attr| matches!(attr.meta, TaskAttrMeta::TaskCondition(_)))
|
||||
.cloned()
|
||||
else {
|
||||
return Err(Error::new(
|
||||
item.sig.ident.span(),
|
||||
"missing `#[pallet::task_condition(..)]` attribute",
|
||||
))
|
||||
};
|
||||
|
||||
let Some(list_attr) = task_attrs
|
||||
.iter()
|
||||
.find(|attr| matches!(attr.meta, TaskAttrMeta::TaskList(_)))
|
||||
.cloned()
|
||||
else {
|
||||
return Err(Error::new(
|
||||
item.sig.ident.span(),
|
||||
"missing `#[pallet::task_list(..)]` attribute",
|
||||
))
|
||||
};
|
||||
|
||||
let Some(weight_attr) = task_attrs
|
||||
.iter()
|
||||
.find(|attr| matches!(attr.meta, TaskAttrMeta::TaskWeight(_)))
|
||||
.cloned()
|
||||
else {
|
||||
return Err(Error::new(
|
||||
item.sig.ident.span(),
|
||||
"missing `#[pallet::task_weight(..)]` attribute",
|
||||
))
|
||||
};
|
||||
|
||||
if let Some(duplicate) = task_attrs
|
||||
.iter()
|
||||
.filter(|attr| matches!(attr.meta, TaskAttrMeta::TaskCondition(_)))
|
||||
.collect::<Vec<_>>()
|
||||
.get(1)
|
||||
{
|
||||
return Err(Error::new(
|
||||
duplicate.span(),
|
||||
"unexpected extra `#[pallet::task_condition(..)]` attribute",
|
||||
))
|
||||
}
|
||||
|
||||
if let Some(duplicate) = task_attrs
|
||||
.iter()
|
||||
.filter(|attr| matches!(attr.meta, TaskAttrMeta::TaskList(_)))
|
||||
.collect::<Vec<_>>()
|
||||
.get(1)
|
||||
{
|
||||
return Err(Error::new(
|
||||
duplicate.span(),
|
||||
"unexpected extra `#[pallet::task_list(..)]` attribute",
|
||||
))
|
||||
}
|
||||
|
||||
if let Some(duplicate) = task_attrs
|
||||
.iter()
|
||||
.filter(|attr| matches!(attr.meta, TaskAttrMeta::TaskIndex(_)))
|
||||
.collect::<Vec<_>>()
|
||||
.get(1)
|
||||
{
|
||||
return Err(Error::new(
|
||||
duplicate.span(),
|
||||
"unexpected extra `#[pallet::task_index(..)]` attribute",
|
||||
))
|
||||
}
|
||||
|
||||
let mut arg_names = vec![];
|
||||
for input in item.sig.inputs.iter() {
|
||||
match input {
|
||||
syn::FnArg::Typed(pat_type) => match &*pat_type.pat {
|
||||
syn::Pat::Ident(ident) => arg_names.push(ident.ident.clone()),
|
||||
_ => return Err(Error::new(input.span(), "unexpected pattern type")),
|
||||
},
|
||||
_ => return Err(Error::new(input.span(), "unexpected function argument type")),
|
||||
}
|
||||
}
|
||||
|
||||
let index_attr = index_attr.try_into().expect("we check the type above; QED");
|
||||
let condition_attr = condition_attr.try_into().expect("we check the type above; QED");
|
||||
let list_attr = list_attr.try_into().expect("we check the type above; QED");
|
||||
let weight_attr = weight_attr.try_into().expect("we check the type above; QED");
|
||||
|
||||
Ok(TaskDef {
|
||||
index_attr,
|
||||
condition_attr,
|
||||
list_attr,
|
||||
weight_attr,
|
||||
normal_attrs,
|
||||
item,
|
||||
arg_names,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// The contents of a [`TasksDef`]-related attribute.
|
||||
#[derive(Parse, Debug, Clone)]
|
||||
pub enum TaskAttrMeta {
|
||||
#[peek(keywords::task_list, name = "#[pallet::task_list(..)]")]
|
||||
TaskList(TaskListAttrMeta),
|
||||
#[peek(keywords::task_index, name = "#[pallet::task_index(..)")]
|
||||
TaskIndex(TaskIndexAttrMeta),
|
||||
#[peek(keywords::task_condition, name = "#[pallet::task_condition(..)")]
|
||||
TaskCondition(TaskConditionAttrMeta),
|
||||
#[peek(keywords::task_weight, name = "#[pallet::task_weight(..)")]
|
||||
TaskWeight(TaskWeightAttrMeta),
|
||||
}
|
||||
|
||||
/// The contents of a `#[pallet::task_list]` attribute.
|
||||
#[derive(Parse, Debug, Clone)]
|
||||
pub struct TaskListAttrMeta {
|
||||
pub task_list: keywords::task_list,
|
||||
#[paren]
|
||||
_paren: Paren,
|
||||
#[inside(_paren)]
|
||||
pub expr: Expr,
|
||||
}
|
||||
|
||||
/// The contents of a `#[pallet::task_index]` attribute.
|
||||
#[derive(Parse, Debug, Clone)]
|
||||
pub struct TaskIndexAttrMeta {
|
||||
pub task_index: keywords::task_index,
|
||||
#[paren]
|
||||
_paren: Paren,
|
||||
#[inside(_paren)]
|
||||
pub index: LitInt,
|
||||
}
|
||||
|
||||
/// The contents of a `#[pallet::task_condition]` attribute.
|
||||
#[derive(Parse, Debug, Clone)]
|
||||
pub struct TaskConditionAttrMeta {
|
||||
pub task_condition: keywords::task_condition,
|
||||
#[paren]
|
||||
_paren: Paren,
|
||||
#[inside(_paren)]
|
||||
pub expr: Expr,
|
||||
}
|
||||
|
||||
/// The contents of a `#[pallet::task_weight]` attribute.
|
||||
#[derive(Parse, Debug, Clone)]
|
||||
pub struct TaskWeightAttrMeta {
|
||||
pub task_weight: keywords::task_weight,
|
||||
#[paren]
|
||||
_paren: Paren,
|
||||
#[inside(_paren)]
|
||||
pub expr: Expr,
|
||||
}
|
||||
|
||||
/// The contents of a `#[pallet::task]` attribute.
|
||||
#[derive(Parse, Debug, Clone)]
|
||||
pub struct PalletTaskAttr<T: syn::parse::Parse + core::fmt::Debug + ToTokens> {
|
||||
pub pound: Pound,
|
||||
#[bracket]
|
||||
_bracket: Bracket,
|
||||
#[inside(_bracket)]
|
||||
pub pallet: keywords::pallet,
|
||||
#[inside(_bracket)]
|
||||
pub colons: PathSep,
|
||||
#[inside(_bracket)]
|
||||
pub meta: T,
|
||||
}
|
||||
|
||||
impl ToTokens for TaskListAttrMeta {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
let task_list = self.task_list;
|
||||
let expr = &self.expr;
|
||||
tokens.extend(quote!(#task_list(#expr)));
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for TaskConditionAttrMeta {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
let task_condition = self.task_condition;
|
||||
let expr = &self.expr;
|
||||
tokens.extend(quote!(#task_condition(#expr)));
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for TaskWeightAttrMeta {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
let task_weight = self.task_weight;
|
||||
let expr = &self.expr;
|
||||
tokens.extend(quote!(#task_weight(#expr)));
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for TaskIndexAttrMeta {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
let task_index = self.task_index;
|
||||
let index = &self.index;
|
||||
tokens.extend(quote!(#task_index(#index)))
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for TaskAttrMeta {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
match self {
|
||||
TaskAttrMeta::TaskList(list) => tokens.extend(list.to_token_stream()),
|
||||
TaskAttrMeta::TaskIndex(index) => tokens.extend(index.to_token_stream()),
|
||||
TaskAttrMeta::TaskCondition(condition) => tokens.extend(condition.to_token_stream()),
|
||||
TaskAttrMeta::TaskWeight(weight) => tokens.extend(weight.to_token_stream()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: syn::parse::Parse + core::fmt::Debug + ToTokens> ToTokens for PalletTaskAttr<T> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
let pound = self.pound;
|
||||
let pallet = self.pallet;
|
||||
let colons = self.colons;
|
||||
let meta = &self.meta;
|
||||
tokens.extend(quote!(#pound[#pallet #colons #meta]));
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<PalletTaskAttr<TaskAttrMeta>> for TaskIndexAttr {
|
||||
type Error = syn::Error;
|
||||
|
||||
fn try_from(value: PalletTaskAttr<TaskAttrMeta>) -> Result<Self> {
|
||||
let pound = value.pound;
|
||||
let pallet = value.pallet;
|
||||
let colons = value.colons;
|
||||
match value.meta {
|
||||
TaskAttrMeta::TaskIndex(meta) => parse2(quote!(#pound[#pallet #colons #meta])),
|
||||
_ =>
|
||||
return Err(Error::new(
|
||||
value.span(),
|
||||
format!("`{:?}` cannot be converted to a `TaskIndexAttr`", value.meta),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<PalletTaskAttr<TaskAttrMeta>> for TaskConditionAttr {
|
||||
type Error = syn::Error;
|
||||
|
||||
fn try_from(value: PalletTaskAttr<TaskAttrMeta>) -> Result<Self> {
|
||||
let pound = value.pound;
|
||||
let pallet = value.pallet;
|
||||
let colons = value.colons;
|
||||
match value.meta {
|
||||
TaskAttrMeta::TaskCondition(meta) => parse2(quote!(#pound[#pallet #colons #meta])),
|
||||
_ =>
|
||||
return Err(Error::new(
|
||||
value.span(),
|
||||
format!("`{:?}` cannot be converted to a `TaskConditionAttr`", value.meta),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<PalletTaskAttr<TaskAttrMeta>> for TaskWeightAttr {
|
||||
type Error = syn::Error;
|
||||
|
||||
fn try_from(value: PalletTaskAttr<TaskAttrMeta>) -> Result<Self> {
|
||||
let pound = value.pound;
|
||||
let pallet = value.pallet;
|
||||
let colons = value.colons;
|
||||
match value.meta {
|
||||
TaskAttrMeta::TaskWeight(meta) => parse2(quote!(#pound[#pallet #colons #meta])),
|
||||
_ =>
|
||||
return Err(Error::new(
|
||||
value.span(),
|
||||
format!("`{:?}` cannot be converted to a `TaskWeightAttr`", value.meta),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<PalletTaskAttr<TaskAttrMeta>> for TaskListAttr {
|
||||
type Error = syn::Error;
|
||||
|
||||
fn try_from(value: PalletTaskAttr<TaskAttrMeta>) -> Result<Self> {
|
||||
let pound = value.pound;
|
||||
let pallet = value.pallet;
|
||||
let colons = value.colons;
|
||||
match value.meta {
|
||||
TaskAttrMeta::TaskList(meta) => parse2(quote!(#pound[#pallet #colons #meta])),
|
||||
_ =>
|
||||
return Err(Error::new(
|
||||
value.span(),
|
||||
format!("`{:?}` cannot be converted to a `TaskListAttr`", value.meta),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_pallet_attr(item_enum: &mut ItemEnum) -> Result<Option<TokenStream2>> {
|
||||
let mut duplicate = None;
|
||||
let mut attr = None;
|
||||
item_enum.attrs = item_enum
|
||||
.attrs
|
||||
.iter()
|
||||
.filter(|found_attr| {
|
||||
let segs = found_attr
|
||||
.path()
|
||||
.segments
|
||||
.iter()
|
||||
.map(|seg| seg.ident.clone())
|
||||
.collect::<Vec<_>>();
|
||||
let (Some(seg1), Some(_), None) = (segs.get(0), segs.get(1), segs.get(2)) else {
|
||||
return true
|
||||
};
|
||||
if seg1 != "pallet" {
|
||||
return true
|
||||
}
|
||||
if attr.is_some() {
|
||||
duplicate = Some(found_attr.span());
|
||||
}
|
||||
attr = Some(found_attr.to_token_stream());
|
||||
false
|
||||
})
|
||||
.cloned()
|
||||
.collect();
|
||||
if let Some(span) = duplicate {
|
||||
return Err(Error::new(span, "only one `#[pallet::_]` attribute is supported on this item"))
|
||||
}
|
||||
Ok(attr)
|
||||
}
|
||||
|
||||
fn partition_tasks_attrs(item_impl: &ItemImpl) -> (Vec<syn::Attribute>, Vec<syn::Attribute>) {
|
||||
item_impl.attrs.clone().into_iter().partition(|attr| {
|
||||
let mut path_segs = attr.path().segments.iter();
|
||||
let (Some(prefix), Some(suffix), None) =
|
||||
(path_segs.next(), path_segs.next(), path_segs.next())
|
||||
else {
|
||||
return false
|
||||
};
|
||||
prefix.ident == "pallet" && suffix.ident == "tasks_experimental"
|
||||
})
|
||||
}
|
||||
|
||||
fn partition_task_attrs(item: &ImplItemFn) -> (Vec<syn::Attribute>, Vec<syn::Attribute>) {
|
||||
item.attrs.clone().into_iter().partition(|attr| {
|
||||
let mut path_segs = attr.path().segments.iter();
|
||||
let (Some(prefix), Some(suffix)) = (path_segs.next(), path_segs.next()) else {
|
||||
return false
|
||||
};
|
||||
// N.B: the `PartialEq` impl between `Ident` and `&str` is more efficient than
|
||||
// parsing and makes no stack or heap allocations
|
||||
prefix.ident == "pallet" &&
|
||||
(suffix.ident == "tasks_experimental" ||
|
||||
suffix.ident == "task_list" ||
|
||||
suffix.ident == "task_condition" ||
|
||||
suffix.ident == "task_weight" ||
|
||||
suffix.ident == "task_index")
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_task_list_() {
|
||||
parse2::<TaskAttr>(quote!(#[pallet::task_list(Something::iter())])).unwrap();
|
||||
parse2::<TaskAttr>(quote!(#[pallet::task_list(Numbers::<T, I>::iter_keys())])).unwrap();
|
||||
parse2::<TaskAttr>(quote!(#[pallet::task_list(iter())])).unwrap();
|
||||
assert_parse_error_matches!(
|
||||
parse2::<TaskAttr>(quote!(#[pallet::task_list()])),
|
||||
"expected an expression"
|
||||
);
|
||||
assert_parse_error_matches!(
|
||||
parse2::<TaskAttr>(quote!(#[pallet::task_list])),
|
||||
"expected parentheses"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_task_index() {
|
||||
parse2::<TaskAttr>(quote!(#[pallet::task_index(3)])).unwrap();
|
||||
parse2::<TaskAttr>(quote!(#[pallet::task_index(0)])).unwrap();
|
||||
parse2::<TaskAttr>(quote!(#[pallet::task_index(17)])).unwrap();
|
||||
assert_parse_error_matches!(
|
||||
parse2::<TaskAttr>(quote!(#[pallet::task_index])),
|
||||
"expected parentheses"
|
||||
);
|
||||
assert_parse_error_matches!(
|
||||
parse2::<TaskAttr>(quote!(#[pallet::task_index("hey")])),
|
||||
"expected integer literal"
|
||||
);
|
||||
assert_parse_error_matches!(
|
||||
parse2::<TaskAttr>(quote!(#[pallet::task_index(0.3)])),
|
||||
"expected integer literal"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_task_condition() {
|
||||
parse2::<TaskAttr>(quote!(#[pallet::task_condition(|x| x.is_some())])).unwrap();
|
||||
parse2::<TaskAttr>(quote!(#[pallet::task_condition(|_x| some_expr())])).unwrap();
|
||||
parse2::<TaskAttr>(quote!(#[pallet::task_condition(|| some_expr())])).unwrap();
|
||||
parse2::<TaskAttr>(quote!(#[pallet::task_condition(some_expr())])).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_tasks_attr() {
|
||||
parse2::<PalletTasksAttr>(quote!(#[pallet::tasks_experimental])).unwrap();
|
||||
assert_parse_error_matches!(
|
||||
parse2::<PalletTasksAttr>(quote!(#[pallet::taskss])),
|
||||
"expected `tasks_experimental`"
|
||||
);
|
||||
assert_parse_error_matches!(
|
||||
parse2::<PalletTasksAttr>(quote!(#[pallet::tasks_])),
|
||||
"expected `tasks_experimental`"
|
||||
);
|
||||
assert_parse_error_matches!(
|
||||
parse2::<PalletTasksAttr>(quote!(#[pal::tasks])),
|
||||
"expected `pallet`"
|
||||
);
|
||||
assert_parse_error_matches!(
|
||||
parse2::<PalletTasksAttr>(quote!(#[pallet::tasks_experimental()])),
|
||||
"unexpected token"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_tasks_def_basic() {
|
||||
simulate_manifest_dir("../../examples/basic", || {
|
||||
let parsed = parse2::<TasksDef>(quote! {
|
||||
#[pallet::tasks_experimental]
|
||||
impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
/// Add a pair of numbers into the totals and remove them.
|
||||
#[pallet::task_list(Numbers::<T, I>::iter_keys())]
|
||||
#[pallet::task_condition(|i| Numbers::<T, I>::contains_key(i))]
|
||||
#[pallet::task_index(0)]
|
||||
#[pallet::task_weight(0)]
|
||||
pub fn add_number_into_total(i: u32) -> DispatchResult {
|
||||
let v = Numbers::<T, I>::take(i).ok_or(Error::<T, I>::NotFound)?;
|
||||
Total::<T, I>::mutate(|(total_keys, total_values)| {
|
||||
*total_keys += i;
|
||||
*total_values += v;
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(parsed.tasks.len(), 1);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_tasks_def_basic_increment_decrement() {
|
||||
simulate_manifest_dir("../../examples/basic", || {
|
||||
let parsed = parse2::<TasksDef>(quote! {
|
||||
#[pallet::tasks_experimental]
|
||||
impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
/// Get the value and check if it can be incremented
|
||||
#[pallet::task_index(0)]
|
||||
#[pallet::task_condition(|| {
|
||||
let value = Value::<T>::get().unwrap();
|
||||
value < 255
|
||||
})]
|
||||
#[pallet::task_list(Vec::<Task<T>>::new())]
|
||||
#[pallet::task_weight(0)]
|
||||
fn increment() -> DispatchResult {
|
||||
let value = Value::<T>::get().unwrap_or_default();
|
||||
if value >= 255 {
|
||||
Err(Error::<T>::ValueOverflow.into())
|
||||
} else {
|
||||
let new_val = value.checked_add(1).ok_or(Error::<T>::ValueOverflow)?;
|
||||
Value::<T>::put(new_val);
|
||||
Pallet::<T>::deposit_event(Event::Incremented { new_val });
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// Get the value and check if it can be decremented
|
||||
#[pallet::task_index(1)]
|
||||
#[pallet::task_condition(|| {
|
||||
let value = Value::<T>::get().unwrap();
|
||||
value > 0
|
||||
})]
|
||||
#[pallet::task_list(Vec::<Task<T>>::new())]
|
||||
#[pallet::task_weight(0)]
|
||||
fn decrement() -> DispatchResult {
|
||||
let value = Value::<T>::get().unwrap_or_default();
|
||||
if value == 0 {
|
||||
Err(Error::<T>::ValueUnderflow.into())
|
||||
} else {
|
||||
let new_val = value.checked_sub(1).ok_or(Error::<T>::ValueUnderflow)?;
|
||||
Value::<T>::put(new_val);
|
||||
Pallet::<T>::deposit_event(Event::Decremented { new_val });
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(parsed.tasks.len(), 2);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_tasks_def_duplicate_index() {
|
||||
simulate_manifest_dir("../../examples/basic", || {
|
||||
assert_parse_error_matches!(
|
||||
parse2::<TasksDef>(quote! {
|
||||
#[pallet::tasks_experimental]
|
||||
impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
#[pallet::task_list(Something::iter())]
|
||||
#[pallet::task_condition(|i| i % 2 == 0)]
|
||||
#[pallet::task_index(0)]
|
||||
#[pallet::task_weight(0)]
|
||||
pub fn foo(i: u32) -> DispatchResult {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[pallet::task_list(Numbers::<T, I>::iter_keys())]
|
||||
#[pallet::task_condition(|i| Numbers::<T, I>::contains_key(i))]
|
||||
#[pallet::task_index(0)]
|
||||
#[pallet::task_weight(0)]
|
||||
pub fn bar(i: u32) -> DispatchResult {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}),
|
||||
"duplicate task index `0`"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_tasks_def_missing_task_list() {
|
||||
simulate_manifest_dir("../../examples/basic", || {
|
||||
assert_parse_error_matches!(
|
||||
parse2::<TasksDef>(quote! {
|
||||
#[pallet::tasks_experimental]
|
||||
impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
#[pallet::task_condition(|i| i % 2 == 0)]
|
||||
#[pallet::task_index(0)]
|
||||
pub fn foo(i: u32) -> DispatchResult {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}),
|
||||
r"missing `#\[pallet::task_list\(\.\.\)\]`"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_tasks_def_missing_task_condition() {
|
||||
simulate_manifest_dir("../../examples/basic", || {
|
||||
assert_parse_error_matches!(
|
||||
parse2::<TasksDef>(quote! {
|
||||
#[pallet::tasks_experimental]
|
||||
impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
#[pallet::task_list(Something::iter())]
|
||||
#[pallet::task_index(0)]
|
||||
pub fn foo(i: u32) -> DispatchResult {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}),
|
||||
r"missing `#\[pallet::task_condition\(\.\.\)\]`"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_tasks_def_missing_task_index() {
|
||||
simulate_manifest_dir("../../examples/basic", || {
|
||||
assert_parse_error_matches!(
|
||||
parse2::<TasksDef>(quote! {
|
||||
#[pallet::tasks_experimental]
|
||||
impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
#[pallet::task_condition(|i| i % 2 == 0)]
|
||||
#[pallet::task_list(Something::iter())]
|
||||
pub fn foo(i: u32) -> DispatchResult {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}),
|
||||
r"missing `#\[pallet::task_index\(\.\.\)\]`"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_tasks_def_missing_task_weight() {
|
||||
simulate_manifest_dir("../../examples/basic", || {
|
||||
assert_parse_error_matches!(
|
||||
parse2::<TasksDef>(quote! {
|
||||
#[pallet::tasks_experimental]
|
||||
impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
#[pallet::task_condition(|i| i % 2 == 0)]
|
||||
#[pallet::task_list(Something::iter())]
|
||||
#[pallet::task_index(0)]
|
||||
pub fn foo(i: u32) -> DispatchResult {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}),
|
||||
r"missing `#\[pallet::task_weight\(\.\.\)\]`"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_tasks_def_unexpected_extra_task_list_attr() {
|
||||
simulate_manifest_dir("../../examples/basic", || {
|
||||
assert_parse_error_matches!(
|
||||
parse2::<TasksDef>(quote! {
|
||||
#[pallet::tasks_experimental]
|
||||
impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
#[pallet::task_condition(|i| i % 2 == 0)]
|
||||
#[pallet::task_index(0)]
|
||||
#[pallet::task_weight(0)]
|
||||
#[pallet::task_list(Something::iter())]
|
||||
#[pallet::task_list(SomethingElse::iter())]
|
||||
pub fn foo(i: u32) -> DispatchResult {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}),
|
||||
r"unexpected extra `#\[pallet::task_list\(\.\.\)\]`"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_tasks_def_unexpected_extra_task_condition_attr() {
|
||||
simulate_manifest_dir("../../examples/basic", || {
|
||||
assert_parse_error_matches!(
|
||||
parse2::<TasksDef>(quote! {
|
||||
#[pallet::tasks_experimental]
|
||||
impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
#[pallet::task_condition(|i| i % 2 == 0)]
|
||||
#[pallet::task_condition(|i| i % 4 == 0)]
|
||||
#[pallet::task_index(0)]
|
||||
#[pallet::task_list(Something::iter())]
|
||||
#[pallet::task_weight(0)]
|
||||
pub fn foo(i: u32) -> DispatchResult {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}),
|
||||
r"unexpected extra `#\[pallet::task_condition\(\.\.\)\]`"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_tasks_def_unexpected_extra_task_index_attr() {
|
||||
simulate_manifest_dir("../../examples/basic", || {
|
||||
assert_parse_error_matches!(
|
||||
parse2::<TasksDef>(quote! {
|
||||
#[pallet::tasks_experimental]
|
||||
impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
#[pallet::task_condition(|i| i % 2 == 0)]
|
||||
#[pallet::task_index(0)]
|
||||
#[pallet::task_index(0)]
|
||||
#[pallet::task_list(Something::iter())]
|
||||
#[pallet::task_weight(0)]
|
||||
pub fn foo(i: u32) -> DispatchResult {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}),
|
||||
r"unexpected extra `#\[pallet::task_index\(\.\.\)\]`"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_tasks_def_extra_tasks_attribute() {
|
||||
simulate_manifest_dir("../../examples/basic", || {
|
||||
assert_parse_error_matches!(
|
||||
parse2::<TasksDef>(quote! {
|
||||
#[pallet::tasks_experimental]
|
||||
#[pallet::tasks_experimental]
|
||||
impl<T: Config<I>, I: 'static> Pallet<T, I> {}
|
||||
}),
|
||||
r"unexpected extra `#\[pallet::tasks_experimental\]` attribute"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_task_enum_def_basic() {
|
||||
simulate_manifest_dir("../../examples/basic", || {
|
||||
parse2::<TaskEnumDef>(quote! {
|
||||
#[pallet::task_enum]
|
||||
pub enum Task<T: Config> {
|
||||
Increment,
|
||||
Decrement,
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_task_enum_def_non_task_name() {
|
||||
simulate_manifest_dir("../../examples/basic", || {
|
||||
parse2::<TaskEnumDef>(quote! {
|
||||
#[pallet::task_enum]
|
||||
pub enum Something {
|
||||
Foo
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_task_enum_def_missing_attr_allowed() {
|
||||
simulate_manifest_dir("../../examples/basic", || {
|
||||
parse2::<TaskEnumDef>(quote! {
|
||||
pub enum Task<T: Config> {
|
||||
Increment,
|
||||
Decrement,
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_task_enum_def_missing_attr_alternate_name_allowed() {
|
||||
simulate_manifest_dir("../../examples/basic", || {
|
||||
parse2::<TaskEnumDef>(quote! {
|
||||
pub enum Foo {
|
||||
Red,
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_task_enum_def_wrong_attr() {
|
||||
simulate_manifest_dir("../../examples/basic", || {
|
||||
assert_parse_error_matches!(
|
||||
parse2::<TaskEnumDef>(quote! {
|
||||
#[pallet::something]
|
||||
pub enum Task<T: Config> {
|
||||
Increment,
|
||||
Decrement,
|
||||
}
|
||||
}),
|
||||
"expected `task_enum`"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_task_enum_def_wrong_item() {
|
||||
simulate_manifest_dir("../../examples/basic", || {
|
||||
assert_parse_error_matches!(
|
||||
parse2::<TaskEnumDef>(quote! {
|
||||
#[pallet::task_enum]
|
||||
pub struct Something;
|
||||
}),
|
||||
"expected `enum`"
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
// 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 std::{panic, sync::Mutex};
|
||||
use syn::parse_quote;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub mod __private {
|
||||
pub use regex;
|
||||
}
|
||||
|
||||
/// Allows you to assert that the input expression resolves to an error whose string
|
||||
/// representation matches the specified regex literal.
|
||||
///
|
||||
/// ## Example:
|
||||
///
|
||||
/// ```
|
||||
/// use super::tasks::*;
|
||||
///
|
||||
/// assert_parse_error_matches!(
|
||||
/// parse2::<TaskEnumDef>(quote! {
|
||||
/// #[pallet::task_enum]
|
||||
/// pub struct Something;
|
||||
/// }),
|
||||
/// "expected `enum`"
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
/// More complex regular expressions are also possible (anything that could pass as a regex for
|
||||
/// use with the [`regex`] crate.):
|
||||
///
|
||||
/// ```ignore
|
||||
/// assert_parse_error_matches!(
|
||||
/// parse2::<TasksDef>(quote! {
|
||||
/// #[pallet::tasks_experimental]
|
||||
/// impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
/// #[pallet::task_condition(|i| i % 2 == 0)]
|
||||
/// #[pallet::task_index(0)]
|
||||
/// pub fn foo(i: u32) -> DispatchResult {
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// }
|
||||
/// }),
|
||||
/// r"missing `#\[pallet::task_list\(\.\.\)\]`"
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
/// Although this is primarily intended to be used with parsing errors, this macro is general
|
||||
/// enough that it will work with any error with a reasonable [`core::fmt::Display`] impl.
|
||||
#[macro_export]
|
||||
macro_rules! assert_parse_error_matches {
|
||||
($expr:expr, $reg:literal) => {
|
||||
match $expr {
|
||||
Ok(_) => panic!("Expected an `Error(..)`, but got Ok(..)"),
|
||||
Err(e) => {
|
||||
let error_message = e.to_string();
|
||||
let re = $crate::pallet::parse::tests::__private::regex::Regex::new($reg)
|
||||
.expect("Invalid regex pattern");
|
||||
assert!(
|
||||
re.is_match(&error_message),
|
||||
"Error message \"{}\" does not match the pattern \"{}\"",
|
||||
error_message,
|
||||
$reg
|
||||
);
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Allows you to assert that an entire pallet parses successfully. A custom syntax is used for
|
||||
/// specifying arguments so please pay attention to the docs below.
|
||||
///
|
||||
/// The general syntax is:
|
||||
///
|
||||
/// ```ignore
|
||||
/// assert_pallet_parses! {
|
||||
/// #[manifest_dir("../../examples/basic")]
|
||||
/// #[frame_support::pallet]
|
||||
/// pub mod pallet {
|
||||
/// #[pallet::config]
|
||||
/// pub trait Config: frame_system::Config {}
|
||||
///
|
||||
/// #[pallet::pallet]
|
||||
/// pub struct Pallet<T>(_);
|
||||
/// }
|
||||
/// };
|
||||
/// ```
|
||||
///
|
||||
/// The `#[manifest_dir(..)]` attribute _must_ be specified as the _first_ attribute on the
|
||||
/// pallet module, and should reference the relative (to your current directory) path of a
|
||||
/// directory containing containing the `Cargo.toml` of a valid pallet. Typically you will only
|
||||
/// ever need to use the `examples/basic` pallet, but sometimes it might be advantageous to
|
||||
/// specify a different one that has additional dependencies.
|
||||
///
|
||||
/// The reason this must be specified is that our underlying parsing of pallets depends on
|
||||
/// reaching out into the file system to look for particular `Cargo.toml` dependencies via the
|
||||
/// [`generate_access_from_frame_or_crate`] method, so to simulate this properly in a proc
|
||||
/// macro crate, we need to temporarily convince this function that we are running from the
|
||||
/// directory of a valid pallet.
|
||||
#[macro_export]
|
||||
macro_rules! assert_pallet_parses {
|
||||
(
|
||||
#[manifest_dir($manifest_dir:literal)]
|
||||
$($tokens:tt)*
|
||||
) => {
|
||||
{
|
||||
let mut pallet: Option<$crate::pallet::parse::Def> = None;
|
||||
$crate::pallet::parse::tests::simulate_manifest_dir($manifest_dir, core::panic::AssertUnwindSafe(|| {
|
||||
pallet = Some($crate::pallet::parse::Def::try_from(syn::parse_quote! {
|
||||
$($tokens)*
|
||||
}, false).unwrap());
|
||||
}));
|
||||
pallet.unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Similar to [`assert_pallet_parses`], except this instead expects the pallet not to parse,
|
||||
/// and allows you to specify a regex matching the expected parse error.
|
||||
///
|
||||
/// This is identical syntactically to [`assert_pallet_parses`] in every way except there is a
|
||||
/// second attribute that must be specified immediately after `#[manifest_dir(..)]` which is
|
||||
/// `#[error_regex(..)]` which should contain a string/regex literal designed to match what you
|
||||
/// consider to be the correct parsing error we should see when we try to parse this particular
|
||||
/// pallet.
|
||||
///
|
||||
/// ## Example:
|
||||
///
|
||||
/// ```
|
||||
/// assert_pallet_parse_error! {
|
||||
/// #[manifest_dir("../../examples/basic")]
|
||||
/// #[error_regex("Missing `\\#\\[pallet::pallet\\]`")]
|
||||
/// #[frame_support::pallet]
|
||||
/// pub mod pallet {
|
||||
/// #[pallet::config]
|
||||
/// pub trait Config: frame_system::Config {}
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! assert_pallet_parse_error {
|
||||
(
|
||||
#[manifest_dir($manifest_dir:literal)]
|
||||
#[error_regex($reg:literal)]
|
||||
$($tokens:tt)*
|
||||
) => {
|
||||
$crate::pallet::parse::tests::simulate_manifest_dir($manifest_dir, || {
|
||||
$crate::assert_parse_error_matches!(
|
||||
$crate::pallet::parse::Def::try_from(
|
||||
parse_quote! {
|
||||
$($tokens)*
|
||||
},
|
||||
false
|
||||
),
|
||||
$reg
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Safely runs the specified `closure` while simulating an alternative `CARGO_MANIFEST_DIR`,
|
||||
/// restoring `CARGO_MANIFEST_DIR` to its original value upon completion regardless of whether
|
||||
/// the closure panics.
|
||||
///
|
||||
/// This is useful in tests of `Def::try_from` and other pallet-related methods that internally
|
||||
/// make use of [`generate_access_from_frame_or_crate`], which is sensitive to entries in the
|
||||
/// "current" `Cargo.toml` files.
|
||||
///
|
||||
/// This function uses a [`Mutex`] to avoid a race condition created when multiple tests try to
|
||||
/// modify and then restore the `CARGO_MANIFEST_DIR` ENV var in an overlapping way.
|
||||
pub fn simulate_manifest_dir<P: AsRef<std::path::Path>, F: FnOnce() + std::panic::UnwindSafe>(
|
||||
path: P,
|
||||
closure: F,
|
||||
) {
|
||||
use std::{env::*, path::*};
|
||||
|
||||
/// Ensures that only one thread can modify/restore the `CARGO_MANIFEST_DIR` ENV var at a time,
|
||||
/// avoiding a race condition because `cargo test` runs tests in parallel.
|
||||
///
|
||||
/// Although this forces all tests that use [`simulate_manifest_dir`] to run sequentially with
|
||||
/// respect to each other, this is still several orders of magnitude faster than using UI
|
||||
/// tests, even if they are run in parallel.
|
||||
static MANIFEST_DIR_LOCK: Mutex<()> = Mutex::new(());
|
||||
|
||||
// avoid race condition when swapping out `CARGO_MANIFEST_DIR`
|
||||
let guard = MANIFEST_DIR_LOCK.lock().unwrap();
|
||||
|
||||
// obtain the current/original `CARGO_MANIFEST_DIR`
|
||||
let orig = PathBuf::from(
|
||||
var("CARGO_MANIFEST_DIR").expect("failed to read ENV var `CARGO_MANIFEST_DIR`"),
|
||||
);
|
||||
|
||||
// set `CARGO_MANIFEST_DIR` to the provided path, relative to current working dir
|
||||
set_var("CARGO_MANIFEST_DIR", orig.join(path.as_ref()));
|
||||
|
||||
// safely run closure catching any panics
|
||||
let result = panic::catch_unwind(closure);
|
||||
|
||||
// restore original `CARGO_MANIFEST_DIR` before unwinding
|
||||
set_var("CARGO_MANIFEST_DIR", &orig);
|
||||
|
||||
// unlock the mutex so we don't poison it if there is a panic
|
||||
drop(guard);
|
||||
|
||||
// unwind any panics originally encountered when running closure
|
||||
result.unwrap();
|
||||
}
|
||||
|
||||
mod tasks;
|
||||
|
||||
#[test]
|
||||
fn test_parse_minimal_pallet() {
|
||||
assert_pallet_parses! {
|
||||
#[manifest_dir("../../examples/basic")]
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
#[pallet::config]
|
||||
pub trait Config: frame_system::Config {}
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(_);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_pallet_missing_pallet() {
|
||||
assert_pallet_parse_error! {
|
||||
#[manifest_dir("../../examples/basic")]
|
||||
#[error_regex("Missing `\\#\\[pallet::pallet\\]`")]
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
#[pallet::config]
|
||||
pub trait Config: frame_system::Config {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_pallet_missing_config() {
|
||||
assert_pallet_parse_error! {
|
||||
#[manifest_dir("../../examples/basic")]
|
||||
#[error_regex("Missing `\\#\\[pallet::config\\]`")]
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(_);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
// 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 syn::parse_quote;
|
||||
|
||||
#[test]
|
||||
fn test_parse_pallet_with_task_enum_missing_impl() {
|
||||
assert_pallet_parse_error! {
|
||||
#[manifest_dir("../../examples/basic")]
|
||||
#[error_regex("Missing `\\#\\[pallet::tasks_experimental\\]` impl")]
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
#[pallet::task_enum]
|
||||
pub enum Task<T: Config> {
|
||||
Something,
|
||||
}
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: frame_system::Config {}
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_pallet_with_task_enum_wrong_attribute() {
|
||||
assert_pallet_parse_error! {
|
||||
#[manifest_dir("../../examples/basic")]
|
||||
#[error_regex("expected one of")]
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
#[pallet::wrong_attribute]
|
||||
pub enum Task<T: Config> {
|
||||
Something,
|
||||
}
|
||||
|
||||
#[pallet::task_list]
|
||||
impl<T: Config> frame_support::traits::Task for Task<T>
|
||||
where
|
||||
T: TypeInfo,
|
||||
{}
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: frame_system::Config {}
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_pallet_missing_task_enum() {
|
||||
assert_pallet_parses! {
|
||||
#[manifest_dir("../../examples/basic")]
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
#[pallet::tasks_experimental]
|
||||
#[cfg(test)] // aha, this means it's being eaten
|
||||
impl<T: Config> frame_support::traits::Task for Task<T>
|
||||
where
|
||||
T: TypeInfo,
|
||||
{}
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: frame_system::Config {}
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(_);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_pallet_task_list_in_wrong_place() {
|
||||
assert_pallet_parse_error! {
|
||||
#[manifest_dir("../../examples/basic")]
|
||||
#[error_regex("can only be used on items within an `impl` statement.")]
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
pub enum MyCustomTaskEnum<T: Config> {
|
||||
Something,
|
||||
}
|
||||
|
||||
#[pallet::task_list]
|
||||
pub fn something() {
|
||||
println!("hey");
|
||||
}
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: frame_system::Config {}
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_pallet_manual_tasks_impl_without_manual_tasks_enum() {
|
||||
assert_pallet_parse_error! {
|
||||
#[manifest_dir("../../examples/basic")]
|
||||
#[error_regex(".*attribute must be attached to your.*")]
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
|
||||
impl<T: Config> frame_support::traits::Task for Task<T>
|
||||
where
|
||||
T: TypeInfo,
|
||||
{
|
||||
type Enumeration = sp_std::vec::IntoIter<Task<T>>;
|
||||
|
||||
fn iter() -> Self::Enumeration {
|
||||
sp_std::vec![Task::increment, Task::decrement].into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: frame_system::Config {}
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_pallet_manual_task_enum_non_manual_impl() {
|
||||
assert_pallet_parses! {
|
||||
#[manifest_dir("../../examples/basic")]
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
pub enum MyCustomTaskEnum<T: Config> {
|
||||
Something,
|
||||
}
|
||||
|
||||
#[pallet::tasks_experimental]
|
||||
impl<T: Config> frame_support::traits::Task for MyCustomTaskEnum<T>
|
||||
where
|
||||
T: TypeInfo,
|
||||
{}
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: frame_system::Config {}
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(_);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_pallet_non_manual_task_enum_manual_impl() {
|
||||
assert_pallet_parses! {
|
||||
#[manifest_dir("../../examples/basic")]
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
#[pallet::task_enum]
|
||||
pub enum MyCustomTaskEnum<T: Config> {
|
||||
Something,
|
||||
}
|
||||
|
||||
impl<T: Config> frame_support::traits::Task for MyCustomTaskEnum<T>
|
||||
where
|
||||
T: TypeInfo,
|
||||
{}
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: frame_system::Config {}
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(_);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_pallet_manual_task_enum_manual_impl() {
|
||||
assert_pallet_parses! {
|
||||
#[manifest_dir("../../examples/basic")]
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
pub enum MyCustomTaskEnum<T: Config> {
|
||||
Something,
|
||||
}
|
||||
|
||||
impl<T: Config> frame_support::traits::Task for MyCustomTaskEnum<T>
|
||||
where
|
||||
T: TypeInfo,
|
||||
{}
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: frame_system::Config {}
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(_);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_pallet_manual_task_enum_mismatch_ident() {
|
||||
assert_pallet_parses! {
|
||||
#[manifest_dir("../../examples/basic")]
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
pub enum WrongIdent<T: Config> {
|
||||
Something,
|
||||
}
|
||||
|
||||
#[pallet::tasks_experimental]
|
||||
impl<T: Config> frame_support::traits::Task for MyCustomTaskEnum<T>
|
||||
where
|
||||
T: TypeInfo,
|
||||
{}
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: frame_system::Config {}
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(_);
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user