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