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 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#[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 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
119pub type PalletTasksAttr = PalletTaskAttr<keywords::tasks_experimental>;
121
122pub type TaskAttr = PalletTaskAttr<TaskAttrMeta>;
125
126pub type TaskIndexAttr = PalletTaskAttr<TaskIndexAttrMeta>;
128
129pub type TaskConditionAttr = PalletTaskAttr<TaskConditionAttrMeta>;
131
132pub type TaskListAttr = PalletTaskAttr<TaskListAttrMeta>;
134
135pub type TaskWeightAttr = PalletTaskAttr<TaskWeightAttrMeta>;
137
138pub type PalletTaskEnumAttr = PalletTaskAttr<keywords::task_enum>;
140
141#[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#[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 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#[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#[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#[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#[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#[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#[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 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 #[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 #[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 #[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}