1use 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#[derive(Clone, Debug)]
56pub struct TasksDef {
57 pub tasks_attr: Option<PalletTasksAttr>,
58 pub tasks: Vec<TaskDef>,
59 pub item_impl: ItemImpl,
60 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 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 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
125pub type PalletTasksAttr = PalletTaskAttr<keywords::tasks_experimental>;
127
128pub type TaskAttr = PalletTaskAttr<TaskAttrMeta>;
131
132pub type TaskIndexAttr = PalletTaskAttr<TaskIndexAttrMeta>;
134
135pub type TaskConditionAttr = PalletTaskAttr<TaskConditionAttrMeta>;
137
138pub type TaskListAttr = PalletTaskAttr<TaskListAttrMeta>;
140
141pub type TaskWeightAttr = PalletTaskAttr<TaskWeightAttrMeta>;
143
144pub type PalletTaskEnumAttr = PalletTaskAttr<keywords::task_enum>;
146
147#[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 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#[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 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#[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#[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#[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#[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#[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#[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 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 #[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 #[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 #[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}