referrerpolicy=no-referrer-when-downgrade

frame_support_procedural/pallet/parse/
tasks.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! Home of the parsing code for the Tasks API
19
20use std::collections::HashSet;
21
22#[cfg(test)]
23use crate::assert_parse_error_matches;
24
25#[cfg(test)]
26use crate::pallet::parse::tests::simulate_manifest_dir;
27
28use super::helper;
29use derive_syn_parse::Parse;
30use proc_macro2::TokenStream as TokenStream2;
31use quote::{quote, ToTokens};
32use syn::{
33	parse::ParseStream,
34	parse2,
35	spanned::Spanned,
36	token::{Bracket, Paren, PathSep, Pound},
37	Error, Expr, Ident, ImplItem, ImplItemFn, ItemEnum, ItemImpl, LitInt, PathArguments, Result,
38	TypePath,
39};
40
41pub mod keywords {
42	use syn::custom_keyword;
43
44	custom_keyword!(tasks_experimental);
45	custom_keyword!(task_enum);
46	custom_keyword!(task_list);
47	custom_keyword!(task_condition);
48	custom_keyword!(task_index);
49	custom_keyword!(task_weight);
50	custom_keyword!(pallet);
51}
52
53/// Represents the `#[pallet::tasks_experimental]` attribute and its attached item. Also includes
54/// metadata about the linked [`TaskEnumDef`] if applicable.
55#[derive(Clone, Debug)]
56pub struct TasksDef {
57	pub tasks_attr: Option<PalletTasksAttr>,
58	pub tasks: Vec<TaskDef>,
59	pub item_impl: ItemImpl,
60	pub enum_ident: Ident,
61	pub enum_arguments: PathArguments,
62}
63
64impl syn::parse::Parse for TasksDef {
65	fn parse(input: ParseStream) -> Result<Self> {
66		let item_impl: ItemImpl = input.parse()?;
67		let (tasks_attrs, normal_attrs) = partition_tasks_attrs(&item_impl);
68		let tasks_attr = match tasks_attrs.first() {
69			Some(attr) => Some(parse2::<PalletTasksAttr>(attr.to_token_stream())?),
70			None => None,
71		};
72		if let Some(extra_tasks_attr) = tasks_attrs.get(1) {
73			return Err(Error::new(
74				extra_tasks_attr.span(),
75				"unexpected extra `#[pallet::tasks_experimental]` attribute",
76			))
77		}
78		let tasks: Vec<TaskDef> = if tasks_attr.is_some() {
79			item_impl
80				.items
81				.clone()
82				.into_iter()
83				.filter(|impl_item| matches!(impl_item, ImplItem::Fn(_)))
84				.map(|item| parse2::<TaskDef>(item.to_token_stream()))
85				.collect::<Result<_>>()?
86		} else {
87			Vec::new()
88		};
89		let mut task_indices = HashSet::<LitInt>::new();
90		for task in tasks.iter() {
91			let task_index = &task.index_attr.meta.index;
92			if !task_indices.insert(task_index.clone()) {
93				return Err(Error::new(
94					task_index.span(),
95					format!("duplicate task index `{}`", task_index),
96				))
97			}
98		}
99		let mut item_impl = item_impl;
100		item_impl.attrs = normal_attrs;
101
102		// we require the path on the impl to be a TypePath
103		let enum_path = parse2::<TypePath>(item_impl.self_ty.to_token_stream())?;
104		let segments = enum_path.path.segments.iter().collect::<Vec<_>>();
105		let (Some(last_seg), None) = (segments.get(0), segments.get(1)) else {
106			return Err(Error::new(
107				enum_path.span(),
108				"if specified manually, the task enum must be defined locally in this \
109				pallet and cannot be a re-export",
110			))
111		};
112		let enum_ident = last_seg.ident.clone();
113		let enum_arguments = last_seg.arguments.clone();
114
115		Ok(TasksDef { tasks_attr, item_impl, tasks, enum_ident, enum_arguments })
116	}
117}
118
119/// Parsing for a `#[pallet::tasks_experimental]` attr.
120pub type PalletTasksAttr = PalletTaskAttr<keywords::tasks_experimental>;
121
122/// Parsing for any of the attributes that can be used within a `#[pallet::tasks_experimental]`
123/// [`ItemImpl`].
124pub type TaskAttr = PalletTaskAttr<TaskAttrMeta>;
125
126/// Parsing for a `#[pallet::task_index]` attr.
127pub type TaskIndexAttr = PalletTaskAttr<TaskIndexAttrMeta>;
128
129/// Parsing for a `#[pallet::task_condition]` attr.
130pub type TaskConditionAttr = PalletTaskAttr<TaskConditionAttrMeta>;
131
132/// Parsing for a `#[pallet::task_list]` attr.
133pub type TaskListAttr = PalletTaskAttr<TaskListAttrMeta>;
134
135/// Parsing for a `#[pallet::task_weight]` attr.
136pub type TaskWeightAttr = PalletTaskAttr<TaskWeightAttrMeta>;
137
138/// Parsing for a `#[pallet:task_enum]` attr.
139pub type PalletTaskEnumAttr = PalletTaskAttr<keywords::task_enum>;
140
141/// Parsing for a manually-specified (or auto-generated) task enum, optionally including the
142/// attached `#[pallet::task_enum]` attribute.
143#[derive(Clone)]
144pub struct TaskEnumDef {
145	pub attr: Option<PalletTaskEnumAttr>,
146	pub item_enum: ItemEnum,
147	pub instance_usage: helper::InstanceUsage,
148}
149
150impl syn::parse::Parse for TaskEnumDef {
151	fn parse(input: ParseStream) -> Result<Self> {
152		let mut item_enum = input.parse::<ItemEnum>()?;
153		let attr = extract_pallet_attr(&mut item_enum)?;
154		let attr = match attr {
155			Some(attr) => Some(parse2(attr)?),
156			None => None,
157		};
158
159		let instance_usage =
160			helper::check_type_def_gen(&item_enum.generics, item_enum.ident.span())?;
161
162		Ok(TaskEnumDef { attr, item_enum, instance_usage })
163	}
164}
165
166/// Represents an individual tasks within a [`TasksDef`].
167#[derive(Debug, Clone)]
168pub struct TaskDef {
169	pub index_attr: TaskIndexAttr,
170	pub condition_attr: TaskConditionAttr,
171	pub list_attr: TaskListAttr,
172	pub weight_attr: TaskWeightAttr,
173	pub item: ImplItemFn,
174	pub arg_names: Vec<Ident>,
175}
176
177impl syn::parse::Parse for TaskDef {
178	fn parse(input: ParseStream) -> Result<Self> {
179		let item = input.parse::<ImplItemFn>()?;
180		// we only want to activate TaskAttrType parsing errors for tasks-related attributes,
181		// so we filter them here
182		let task_attrs = partition_task_attrs(&item).0;
183
184		let task_attrs: Vec<TaskAttr> = task_attrs
185			.into_iter()
186			.map(|attr| parse2(attr.to_token_stream()))
187			.collect::<Result<_>>()?;
188
189		let Some(index_attr) = task_attrs
190			.iter()
191			.find(|attr| matches!(attr.meta, TaskAttrMeta::TaskIndex(_)))
192			.cloned()
193		else {
194			return Err(Error::new(
195				item.sig.ident.span(),
196				"missing `#[pallet::task_index(..)]` attribute",
197			))
198		};
199
200		let Some(condition_attr) = task_attrs
201			.iter()
202			.find(|attr| matches!(attr.meta, TaskAttrMeta::TaskCondition(_)))
203			.cloned()
204		else {
205			return Err(Error::new(
206				item.sig.ident.span(),
207				"missing `#[pallet::task_condition(..)]` attribute",
208			))
209		};
210
211		let Some(list_attr) = task_attrs
212			.iter()
213			.find(|attr| matches!(attr.meta, TaskAttrMeta::TaskList(_)))
214			.cloned()
215		else {
216			return Err(Error::new(
217				item.sig.ident.span(),
218				"missing `#[pallet::task_list(..)]` attribute",
219			))
220		};
221
222		let Some(weight_attr) = task_attrs
223			.iter()
224			.find(|attr| matches!(attr.meta, TaskAttrMeta::TaskWeight(_)))
225			.cloned()
226		else {
227			return Err(Error::new(
228				item.sig.ident.span(),
229				"missing `#[pallet::task_weight(..)]` attribute",
230			))
231		};
232
233		if let Some(duplicate) = task_attrs
234			.iter()
235			.filter(|attr| matches!(attr.meta, TaskAttrMeta::TaskCondition(_)))
236			.collect::<Vec<_>>()
237			.get(1)
238		{
239			return Err(Error::new(
240				duplicate.span(),
241				"unexpected extra `#[pallet::task_condition(..)]` attribute",
242			))
243		}
244
245		if let Some(duplicate) = task_attrs
246			.iter()
247			.filter(|attr| matches!(attr.meta, TaskAttrMeta::TaskList(_)))
248			.collect::<Vec<_>>()
249			.get(1)
250		{
251			return Err(Error::new(
252				duplicate.span(),
253				"unexpected extra `#[pallet::task_list(..)]` attribute",
254			))
255		}
256
257		if let Some(duplicate) = task_attrs
258			.iter()
259			.filter(|attr| matches!(attr.meta, TaskAttrMeta::TaskIndex(_)))
260			.collect::<Vec<_>>()
261			.get(1)
262		{
263			return Err(Error::new(
264				duplicate.span(),
265				"unexpected extra `#[pallet::task_index(..)]` attribute",
266			))
267		}
268
269		let mut arg_names = vec![];
270		for input in item.sig.inputs.iter() {
271			match input {
272				syn::FnArg::Typed(pat_type) => match &*pat_type.pat {
273					syn::Pat::Ident(ident) => arg_names.push(ident.ident.clone()),
274					_ => return Err(Error::new(input.span(), "unexpected pattern type")),
275				},
276				_ => return Err(Error::new(input.span(), "unexpected function argument type")),
277			}
278		}
279
280		let index_attr = index_attr.try_into().expect("we check the type above; QED");
281		let condition_attr = condition_attr.try_into().expect("we check the type above; QED");
282		let list_attr = list_attr.try_into().expect("we check the type above; QED");
283		let weight_attr = weight_attr.try_into().expect("we check the type above; QED");
284
285		Ok(TaskDef { index_attr, condition_attr, list_attr, weight_attr, item, arg_names })
286	}
287}
288
289/// The contents of a [`TasksDef`]-related attribute.
290#[derive(Parse, Debug, Clone)]
291pub enum TaskAttrMeta {
292	#[peek(keywords::task_list, name = "#[pallet::task_list(..)]")]
293	TaskList(TaskListAttrMeta),
294	#[peek(keywords::task_index, name = "#[pallet::task_index(..)")]
295	TaskIndex(TaskIndexAttrMeta),
296	#[peek(keywords::task_condition, name = "#[pallet::task_condition(..)")]
297	TaskCondition(TaskConditionAttrMeta),
298	#[peek(keywords::task_weight, name = "#[pallet::task_weight(..)")]
299	TaskWeight(TaskWeightAttrMeta),
300}
301
302/// The contents of a `#[pallet::task_list]` attribute.
303#[derive(Parse, Debug, Clone)]
304pub struct TaskListAttrMeta {
305	pub task_list: keywords::task_list,
306	#[paren]
307	_paren: Paren,
308	#[inside(_paren)]
309	pub expr: Expr,
310}
311
312/// The contents of a `#[pallet::task_index]` attribute.
313#[derive(Parse, Debug, Clone)]
314pub struct TaskIndexAttrMeta {
315	pub task_index: keywords::task_index,
316	#[paren]
317	_paren: Paren,
318	#[inside(_paren)]
319	pub index: LitInt,
320}
321
322/// The contents of a `#[pallet::task_condition]` attribute.
323#[derive(Parse, Debug, Clone)]
324pub struct TaskConditionAttrMeta {
325	pub task_condition: keywords::task_condition,
326	#[paren]
327	_paren: Paren,
328	#[inside(_paren)]
329	pub expr: Expr,
330}
331
332/// The contents of a `#[pallet::task_weight]` attribute.
333#[derive(Parse, Debug, Clone)]
334pub struct TaskWeightAttrMeta {
335	pub task_weight: keywords::task_weight,
336	#[paren]
337	_paren: Paren,
338	#[inside(_paren)]
339	pub expr: Expr,
340}
341
342/// The contents of a `#[pallet::task]` attribute.
343#[derive(Parse, Debug, Clone)]
344pub struct PalletTaskAttr<T: syn::parse::Parse + core::fmt::Debug + ToTokens> {
345	pub pound: Pound,
346	#[bracket]
347	_bracket: Bracket,
348	#[inside(_bracket)]
349	pub pallet: keywords::pallet,
350	#[inside(_bracket)]
351	pub colons: PathSep,
352	#[inside(_bracket)]
353	pub meta: T,
354}
355
356impl ToTokens for TaskListAttrMeta {
357	fn to_tokens(&self, tokens: &mut TokenStream2) {
358		let task_list = self.task_list;
359		let expr = &self.expr;
360		tokens.extend(quote!(#task_list(#expr)));
361	}
362}
363
364impl ToTokens for TaskConditionAttrMeta {
365	fn to_tokens(&self, tokens: &mut TokenStream2) {
366		let task_condition = self.task_condition;
367		let expr = &self.expr;
368		tokens.extend(quote!(#task_condition(#expr)));
369	}
370}
371
372impl ToTokens for TaskWeightAttrMeta {
373	fn to_tokens(&self, tokens: &mut TokenStream2) {
374		let task_weight = self.task_weight;
375		let expr = &self.expr;
376		tokens.extend(quote!(#task_weight(#expr)));
377	}
378}
379
380impl ToTokens for TaskIndexAttrMeta {
381	fn to_tokens(&self, tokens: &mut TokenStream2) {
382		let task_index = self.task_index;
383		let index = &self.index;
384		tokens.extend(quote!(#task_index(#index)))
385	}
386}
387
388impl ToTokens for TaskAttrMeta {
389	fn to_tokens(&self, tokens: &mut TokenStream2) {
390		match self {
391			TaskAttrMeta::TaskList(list) => tokens.extend(list.to_token_stream()),
392			TaskAttrMeta::TaskIndex(index) => tokens.extend(index.to_token_stream()),
393			TaskAttrMeta::TaskCondition(condition) => tokens.extend(condition.to_token_stream()),
394			TaskAttrMeta::TaskWeight(weight) => tokens.extend(weight.to_token_stream()),
395		}
396	}
397}
398
399impl<T: syn::parse::Parse + core::fmt::Debug + ToTokens> ToTokens for PalletTaskAttr<T> {
400	fn to_tokens(&self, tokens: &mut TokenStream2) {
401		let pound = self.pound;
402		let pallet = self.pallet;
403		let colons = self.colons;
404		let meta = &self.meta;
405		tokens.extend(quote!(#pound[#pallet #colons #meta]));
406	}
407}
408
409impl TryFrom<PalletTaskAttr<TaskAttrMeta>> for TaskIndexAttr {
410	type Error = syn::Error;
411
412	fn try_from(value: PalletTaskAttr<TaskAttrMeta>) -> Result<Self> {
413		let pound = value.pound;
414		let pallet = value.pallet;
415		let colons = value.colons;
416		match value.meta {
417			TaskAttrMeta::TaskIndex(meta) => parse2(quote!(#pound[#pallet #colons #meta])),
418			_ =>
419				return Err(Error::new(
420					value.span(),
421					format!("`{:?}` cannot be converted to a `TaskIndexAttr`", value.meta),
422				)),
423		}
424	}
425}
426
427impl TryFrom<PalletTaskAttr<TaskAttrMeta>> for TaskConditionAttr {
428	type Error = syn::Error;
429
430	fn try_from(value: PalletTaskAttr<TaskAttrMeta>) -> Result<Self> {
431		let pound = value.pound;
432		let pallet = value.pallet;
433		let colons = value.colons;
434		match value.meta {
435			TaskAttrMeta::TaskCondition(meta) => parse2(quote!(#pound[#pallet #colons #meta])),
436			_ =>
437				return Err(Error::new(
438					value.span(),
439					format!("`{:?}` cannot be converted to a `TaskConditionAttr`", value.meta),
440				)),
441		}
442	}
443}
444
445impl TryFrom<PalletTaskAttr<TaskAttrMeta>> for TaskWeightAttr {
446	type Error = syn::Error;
447
448	fn try_from(value: PalletTaskAttr<TaskAttrMeta>) -> Result<Self> {
449		let pound = value.pound;
450		let pallet = value.pallet;
451		let colons = value.colons;
452		match value.meta {
453			TaskAttrMeta::TaskWeight(meta) => parse2(quote!(#pound[#pallet #colons #meta])),
454			_ =>
455				return Err(Error::new(
456					value.span(),
457					format!("`{:?}` cannot be converted to a `TaskWeightAttr`", value.meta),
458				)),
459		}
460	}
461}
462
463impl TryFrom<PalletTaskAttr<TaskAttrMeta>> for TaskListAttr {
464	type Error = syn::Error;
465
466	fn try_from(value: PalletTaskAttr<TaskAttrMeta>) -> Result<Self> {
467		let pound = value.pound;
468		let pallet = value.pallet;
469		let colons = value.colons;
470		match value.meta {
471			TaskAttrMeta::TaskList(meta) => parse2(quote!(#pound[#pallet #colons #meta])),
472			_ =>
473				return Err(Error::new(
474					value.span(),
475					format!("`{:?}` cannot be converted to a `TaskListAttr`", value.meta),
476				)),
477		}
478	}
479}
480
481fn extract_pallet_attr(item_enum: &mut ItemEnum) -> Result<Option<TokenStream2>> {
482	let mut duplicate = None;
483	let mut attr = None;
484	item_enum.attrs = item_enum
485		.attrs
486		.iter()
487		.filter(|found_attr| {
488			let segs = found_attr
489				.path()
490				.segments
491				.iter()
492				.map(|seg| seg.ident.clone())
493				.collect::<Vec<_>>();
494			let (Some(seg1), Some(_), None) = (segs.get(0), segs.get(1), segs.get(2)) else {
495				return true
496			};
497			if seg1 != "pallet" {
498				return true
499			}
500			if attr.is_some() {
501				duplicate = Some(found_attr.span());
502			}
503			attr = Some(found_attr.to_token_stream());
504			false
505		})
506		.cloned()
507		.collect();
508	if let Some(span) = duplicate {
509		return Err(Error::new(span, "only one `#[pallet::_]` attribute is supported on this item"))
510	}
511	Ok(attr)
512}
513
514fn partition_tasks_attrs(item_impl: &ItemImpl) -> (Vec<syn::Attribute>, Vec<syn::Attribute>) {
515	item_impl.attrs.clone().into_iter().partition(|attr| {
516		let mut path_segs = attr.path().segments.iter();
517		let (Some(prefix), Some(suffix), None) =
518			(path_segs.next(), path_segs.next(), path_segs.next())
519		else {
520			return false
521		};
522		prefix.ident == "pallet" && suffix.ident == "tasks_experimental"
523	})
524}
525
526fn partition_task_attrs(item: &ImplItemFn) -> (Vec<syn::Attribute>, Vec<syn::Attribute>) {
527	item.attrs.clone().into_iter().partition(|attr| {
528		let mut path_segs = attr.path().segments.iter();
529		let (Some(prefix), Some(suffix)) = (path_segs.next(), path_segs.next()) else {
530			return false
531		};
532		// N.B: the `PartialEq` impl between `Ident` and `&str` is more efficient than
533		// parsing and makes no stack or heap allocations
534		prefix.ident == "pallet" &&
535			(suffix.ident == "tasks_experimental" ||
536				suffix.ident == "task_list" ||
537				suffix.ident == "task_condition" ||
538				suffix.ident == "task_weight" ||
539				suffix.ident == "task_index")
540	})
541}
542
543#[test]
544fn test_parse_task_list_() {
545	parse2::<TaskAttr>(quote!(#[pallet::task_list(Something::iter())])).unwrap();
546	parse2::<TaskAttr>(quote!(#[pallet::task_list(Numbers::<T, I>::iter_keys())])).unwrap();
547	parse2::<TaskAttr>(quote!(#[pallet::task_list(iter())])).unwrap();
548	assert_parse_error_matches!(
549		parse2::<TaskAttr>(quote!(#[pallet::task_list()])),
550		"expected an expression"
551	);
552	assert_parse_error_matches!(
553		parse2::<TaskAttr>(quote!(#[pallet::task_list])),
554		"expected parentheses"
555	);
556}
557
558#[test]
559fn test_parse_task_index() {
560	parse2::<TaskAttr>(quote!(#[pallet::task_index(3)])).unwrap();
561	parse2::<TaskAttr>(quote!(#[pallet::task_index(0)])).unwrap();
562	parse2::<TaskAttr>(quote!(#[pallet::task_index(17)])).unwrap();
563	assert_parse_error_matches!(
564		parse2::<TaskAttr>(quote!(#[pallet::task_index])),
565		"expected parentheses"
566	);
567	assert_parse_error_matches!(
568		parse2::<TaskAttr>(quote!(#[pallet::task_index("hey")])),
569		"expected integer literal"
570	);
571	assert_parse_error_matches!(
572		parse2::<TaskAttr>(quote!(#[pallet::task_index(0.3)])),
573		"expected integer literal"
574	);
575}
576
577#[test]
578fn test_parse_task_condition() {
579	parse2::<TaskAttr>(quote!(#[pallet::task_condition(|x| x.is_some())])).unwrap();
580	parse2::<TaskAttr>(quote!(#[pallet::task_condition(|_x| some_expr())])).unwrap();
581	parse2::<TaskAttr>(quote!(#[pallet::task_condition(|| some_expr())])).unwrap();
582	parse2::<TaskAttr>(quote!(#[pallet::task_condition(some_expr())])).unwrap();
583}
584
585#[test]
586fn test_parse_tasks_attr() {
587	parse2::<PalletTasksAttr>(quote!(#[pallet::tasks_experimental])).unwrap();
588	assert_parse_error_matches!(
589		parse2::<PalletTasksAttr>(quote!(#[pallet::taskss])),
590		"expected `tasks_experimental`"
591	);
592	assert_parse_error_matches!(
593		parse2::<PalletTasksAttr>(quote!(#[pallet::tasks_])),
594		"expected `tasks_experimental`"
595	);
596	assert_parse_error_matches!(
597		parse2::<PalletTasksAttr>(quote!(#[pal::tasks])),
598		"expected `pallet`"
599	);
600	assert_parse_error_matches!(
601		parse2::<PalletTasksAttr>(quote!(#[pallet::tasks_experimental()])),
602		"unexpected token"
603	);
604}
605
606#[test]
607fn test_parse_tasks_def_basic() {
608	simulate_manifest_dir("../../examples/basic", || {
609		let parsed = parse2::<TasksDef>(quote! {
610			#[pallet::tasks_experimental]
611			impl<T: Config<I>, I: 'static> Pallet<T, I> {
612				/// Add a pair of numbers into the totals and remove them.
613				#[pallet::task_list(Numbers::<T, I>::iter_keys())]
614				#[pallet::task_condition(|i| Numbers::<T, I>::contains_key(i))]
615				#[pallet::task_index(0)]
616				#[pallet::task_weight(0)]
617				pub fn add_number_into_total(i: u32) -> DispatchResult {
618					let v = Numbers::<T, I>::take(i).ok_or(Error::<T, I>::NotFound)?;
619					Total::<T, I>::mutate(|(total_keys, total_values)| {
620						*total_keys += i;
621						*total_values += v;
622					});
623					Ok(())
624				}
625			}
626		})
627		.unwrap();
628		assert_eq!(parsed.tasks.len(), 1);
629	});
630}
631
632#[test]
633fn test_parse_tasks_def_basic_increment_decrement() {
634	simulate_manifest_dir("../../examples/basic", || {
635		let parsed = parse2::<TasksDef>(quote! {
636			#[pallet::tasks_experimental]
637			impl<T: Config<I>, I: 'static> Pallet<T, I> {
638				/// Get the value and check if it can be incremented
639				#[pallet::task_index(0)]
640				#[pallet::task_condition(|| {
641					let value = Value::<T>::get().unwrap();
642					value < 255
643				})]
644				#[pallet::task_list(Vec::<Task<T>>::new())]
645				#[pallet::task_weight(0)]
646				fn increment() -> DispatchResult {
647					let value = Value::<T>::get().unwrap_or_default();
648					if value >= 255 {
649						Err(Error::<T>::ValueOverflow.into())
650					} else {
651						let new_val = value.checked_add(1).ok_or(Error::<T>::ValueOverflow)?;
652						Value::<T>::put(new_val);
653						Pallet::<T>::deposit_event(Event::Incremented { new_val });
654						Ok(())
655					}
656				}
657
658				// Get the value and check if it can be decremented
659				#[pallet::task_index(1)]
660				#[pallet::task_condition(|| {
661					let value = Value::<T>::get().unwrap();
662					value > 0
663				})]
664				#[pallet::task_list(Vec::<Task<T>>::new())]
665				#[pallet::task_weight(0)]
666				fn decrement() -> DispatchResult {
667					let value = Value::<T>::get().unwrap_or_default();
668					if value == 0 {
669						Err(Error::<T>::ValueUnderflow.into())
670					} else {
671						let new_val = value.checked_sub(1).ok_or(Error::<T>::ValueUnderflow)?;
672						Value::<T>::put(new_val);
673						Pallet::<T>::deposit_event(Event::Decremented { new_val });
674						Ok(())
675					}
676				}
677			}
678		})
679		.unwrap();
680		assert_eq!(parsed.tasks.len(), 2);
681	});
682}
683
684#[test]
685fn test_parse_tasks_def_duplicate_index() {
686	simulate_manifest_dir("../../examples/basic", || {
687		assert_parse_error_matches!(
688			parse2::<TasksDef>(quote! {
689				#[pallet::tasks_experimental]
690				impl<T: Config<I>, I: 'static> Pallet<T, I> {
691					#[pallet::task_list(Something::iter())]
692					#[pallet::task_condition(|i| i % 2 == 0)]
693					#[pallet::task_index(0)]
694					#[pallet::task_weight(0)]
695					pub fn foo(i: u32) -> DispatchResult {
696						Ok(())
697					}
698
699					#[pallet::task_list(Numbers::<T, I>::iter_keys())]
700					#[pallet::task_condition(|i| Numbers::<T, I>::contains_key(i))]
701					#[pallet::task_index(0)]
702					#[pallet::task_weight(0)]
703					pub fn bar(i: u32) -> DispatchResult {
704						Ok(())
705					}
706				}
707			}),
708			"duplicate task index `0`"
709		);
710	});
711}
712
713#[test]
714fn test_parse_tasks_def_missing_task_list() {
715	simulate_manifest_dir("../../examples/basic", || {
716		assert_parse_error_matches!(
717			parse2::<TasksDef>(quote! {
718				#[pallet::tasks_experimental]
719				impl<T: Config<I>, I: 'static> Pallet<T, I> {
720					#[pallet::task_condition(|i| i % 2 == 0)]
721					#[pallet::task_index(0)]
722					pub fn foo(i: u32) -> DispatchResult {
723						Ok(())
724					}
725				}
726			}),
727			r"missing `#\[pallet::task_list\(\.\.\)\]`"
728		);
729	});
730}
731
732#[test]
733fn test_parse_tasks_def_missing_task_condition() {
734	simulate_manifest_dir("../../examples/basic", || {
735		assert_parse_error_matches!(
736			parse2::<TasksDef>(quote! {
737				#[pallet::tasks_experimental]
738				impl<T: Config<I>, I: 'static> Pallet<T, I> {
739					#[pallet::task_list(Something::iter())]
740					#[pallet::task_index(0)]
741					pub fn foo(i: u32) -> DispatchResult {
742						Ok(())
743					}
744				}
745			}),
746			r"missing `#\[pallet::task_condition\(\.\.\)\]`"
747		);
748	});
749}
750
751#[test]
752fn test_parse_tasks_def_missing_task_index() {
753	simulate_manifest_dir("../../examples/basic", || {
754		assert_parse_error_matches!(
755			parse2::<TasksDef>(quote! {
756				#[pallet::tasks_experimental]
757				impl<T: Config<I>, I: 'static> Pallet<T, I> {
758					#[pallet::task_condition(|i| i % 2 == 0)]
759					#[pallet::task_list(Something::iter())]
760					pub fn foo(i: u32) -> DispatchResult {
761						Ok(())
762					}
763				}
764			}),
765			r"missing `#\[pallet::task_index\(\.\.\)\]`"
766		);
767	});
768}
769
770#[test]
771fn test_parse_tasks_def_missing_task_weight() {
772	simulate_manifest_dir("../../examples/basic", || {
773		assert_parse_error_matches!(
774			parse2::<TasksDef>(quote! {
775				#[pallet::tasks_experimental]
776				impl<T: Config<I>, I: 'static> Pallet<T, I> {
777					#[pallet::task_condition(|i| i % 2 == 0)]
778					#[pallet::task_list(Something::iter())]
779					#[pallet::task_index(0)]
780					pub fn foo(i: u32) -> DispatchResult {
781						Ok(())
782					}
783				}
784			}),
785			r"missing `#\[pallet::task_weight\(\.\.\)\]`"
786		);
787	});
788}
789
790#[test]
791fn test_parse_tasks_def_unexpected_extra_task_list_attr() {
792	simulate_manifest_dir("../../examples/basic", || {
793		assert_parse_error_matches!(
794			parse2::<TasksDef>(quote! {
795				#[pallet::tasks_experimental]
796				impl<T: Config<I>, I: 'static> Pallet<T, I> {
797					#[pallet::task_condition(|i| i % 2 == 0)]
798					#[pallet::task_index(0)]
799					#[pallet::task_weight(0)]
800					#[pallet::task_list(Something::iter())]
801					#[pallet::task_list(SomethingElse::iter())]
802					pub fn foo(i: u32) -> DispatchResult {
803						Ok(())
804					}
805				}
806			}),
807			r"unexpected extra `#\[pallet::task_list\(\.\.\)\]`"
808		);
809	});
810}
811
812#[test]
813fn test_parse_tasks_def_unexpected_extra_task_condition_attr() {
814	simulate_manifest_dir("../../examples/basic", || {
815		assert_parse_error_matches!(
816			parse2::<TasksDef>(quote! {
817				#[pallet::tasks_experimental]
818				impl<T: Config<I>, I: 'static> Pallet<T, I> {
819					#[pallet::task_condition(|i| i % 2 == 0)]
820					#[pallet::task_condition(|i| i % 4 == 0)]
821					#[pallet::task_index(0)]
822					#[pallet::task_list(Something::iter())]
823					#[pallet::task_weight(0)]
824					pub fn foo(i: u32) -> DispatchResult {
825						Ok(())
826					}
827				}
828			}),
829			r"unexpected extra `#\[pallet::task_condition\(\.\.\)\]`"
830		);
831	});
832}
833
834#[test]
835fn test_parse_tasks_def_unexpected_extra_task_index_attr() {
836	simulate_manifest_dir("../../examples/basic", || {
837		assert_parse_error_matches!(
838			parse2::<TasksDef>(quote! {
839				#[pallet::tasks_experimental]
840				impl<T: Config<I>, I: 'static> Pallet<T, I> {
841					#[pallet::task_condition(|i| i % 2 == 0)]
842					#[pallet::task_index(0)]
843					#[pallet::task_index(0)]
844					#[pallet::task_list(Something::iter())]
845					#[pallet::task_weight(0)]
846					pub fn foo(i: u32) -> DispatchResult {
847						Ok(())
848					}
849				}
850			}),
851			r"unexpected extra `#\[pallet::task_index\(\.\.\)\]`"
852		);
853	});
854}
855
856#[test]
857fn test_parse_tasks_def_extra_tasks_attribute() {
858	simulate_manifest_dir("../../examples/basic", || {
859		assert_parse_error_matches!(
860			parse2::<TasksDef>(quote! {
861				#[pallet::tasks_experimental]
862				#[pallet::tasks_experimental]
863				impl<T: Config<I>, I: 'static> Pallet<T, I> {}
864			}),
865			r"unexpected extra `#\[pallet::tasks_experimental\]` attribute"
866		);
867	});
868}
869
870#[test]
871fn test_parse_task_enum_def_basic() {
872	simulate_manifest_dir("../../examples/basic", || {
873		parse2::<TaskEnumDef>(quote! {
874			#[pallet::task_enum]
875			pub enum Task<T: Config> {
876				Increment,
877				Decrement,
878			}
879		})
880		.unwrap();
881	});
882}
883
884#[test]
885fn test_parse_task_enum_def_non_task_name() {
886	simulate_manifest_dir("../../examples/basic", || {
887		parse2::<TaskEnumDef>(quote! {
888			#[pallet::task_enum]
889			pub enum Something<T> {
890				Foo
891			}
892		})
893		.unwrap();
894	});
895}
896
897#[test]
898fn test_parse_task_enum_def_missing_attr_allowed() {
899	simulate_manifest_dir("../../examples/basic", || {
900		parse2::<TaskEnumDef>(quote! {
901			pub enum Task<T: Config> {
902				Increment,
903				Decrement,
904			}
905		})
906		.unwrap();
907	});
908}
909
910#[test]
911fn test_parse_task_enum_def_missing_attr_alternate_name_allowed() {
912	simulate_manifest_dir("../../examples/basic", || {
913		parse2::<TaskEnumDef>(quote! {
914			pub enum Foo<T> {
915				Red,
916			}
917		})
918		.unwrap();
919	});
920}
921
922#[test]
923fn test_parse_task_enum_def_wrong_attr() {
924	simulate_manifest_dir("../../examples/basic", || {
925		assert_parse_error_matches!(
926			parse2::<TaskEnumDef>(quote! {
927				#[pallet::something]
928				pub enum Task<T: Config> {
929					Increment,
930					Decrement,
931				}
932			}),
933			"expected `task_enum`"
934		);
935	});
936}
937
938#[test]
939fn test_parse_task_enum_def_wrong_item() {
940	simulate_manifest_dir("../../examples/basic", || {
941		assert_parse_error_matches!(
942			parse2::<TaskEnumDef>(quote! {
943				#[pallet::task_enum]
944				pub struct Something<T>;
945			}),
946			"expected `enum`"
947		);
948	});
949}