1use darling::FromMeta;
2use itertools::Itertools;
3use proc_macro2::{
4 Ident,
5 TokenStream,
6};
7use quote::quote;
8use syn::{
9 parse2,
10 Error,
11 Expr,
12 ItemEnum,
13};
14
15use crate::{
16 format_err_spanned,
17 types::AttributeArgs,
18 utils::AttributeParser,
19};
20
21fn default_require_ret_val() -> bool {
22 true
23}
24
25#[derive(FromMeta)]
26struct ErrorAttrs {
27 #[darling(default = "default_require_ret_val")]
28 require_ret_val: bool,
29}
30
31#[derive(FromMeta)]
32struct ErrorVariantAttrs {
33 critical: Option<()>,
34 ret_val: Option<Expr>,
35}
36
37struct RetValInfo<'a> {
38 variant_name: &'a Ident,
39 ret_val: Expr,
40}
41
42pub fn generate(attrs: TokenStream, input: TokenStream) -> Result<TokenStream, Error> {
43 let mut enum_item: ItemEnum = parse2(input)?;
44 let ident = enum_item.ident.clone();
45 let enum_attrs = ErrorAttrs::from_list(&syn::parse2::<AttributeArgs>(attrs)?)?;
46
47 let (impl_generics, ty_generics, where_clause) = enum_item.generics.split_for_impl();
48
49 let mut critical_variant = None;
50
51 let mut ret_val_variants = vec![];
52
53 for variant in enum_item.variants.iter_mut() {
54 let variant_name = &variant.ident;
55
56 let (obce_attrs, mut other_attrs) = variant.attrs.iter().cloned().split_attrs()?;
57
58 let variant_attrs = ErrorVariantAttrs::from_list(&obce_attrs)?;
59
60 if variant_attrs.critical.is_some() {
61 other_attrs.push(syn::parse_quote! {
62 #[cfg(feature = "substrate")]
63 });
64
65 let previous_critical_variant = critical_variant.replace(quote! {
66 #[cfg(feature = "substrate")]
67 impl #impl_generics ::obce::substrate::SupportCriticalError for #ident #ty_generics #where_clause {
68 fn try_to_critical(self) -> Result<::obce::substrate::CriticalError, Self> {
69 match self {
70 Self::#variant_name(error) => Ok(error),
71 _ => Err(self)
72 }
73 }
74 }
75 });
76
77 if let Some(variant) = previous_critical_variant {
78 return Err(format_err_spanned!(
79 variant,
80 "only one enum variant can be marked as `#[obce(critical)]`",
81 ))
82 }
83 }
84
85 if let Some(ret_val) = variant_attrs.ret_val {
86 ret_val_variants.push(RetValInfo { variant_name, ret_val });
87 } else if enum_attrs.require_ret_val && !ret_val_variants.is_empty() {
88 return Err(format_err_spanned!(
89 variant,
90 "you have to mark this variant with `ret_val` or set `require_ret_val` to `false`"
91 ))
92 }
93
94 variant.attrs = other_attrs;
95 }
96
97 if let Some(expr) = ret_val_variants.iter().map(|info| &info.ret_val).duplicates().next() {
98 return Err(format_err_spanned!(expr, "ret_val value is used twice"))
99 }
100
101 let formatted_ret_val = ret_val_variants.iter().map(|RetValInfo { variant_name, ret_val }| {
102 quote! {
103 #ident::#variant_name => Ok(Self::Converging(#ret_val)),
104 }
105 });
106
107 let ret_val_impl = quote! {
108 impl #impl_generics ::core::convert::TryFrom<#ident #ty_generics>
109 for ::obce::substrate::pallet_contracts::chain_extension::RetVal
110 #where_clause
111 {
112 type Error = #ident #ty_generics;
113
114 fn try_from(value: #ident #ty_generics) -> Result<Self, #ident #ty_generics> {
115 match value {
116 #(#formatted_ret_val)*
117 _ => Err(value)
118 }
119 }
120 }
121 };
122
123 Ok(quote! {
124 #[derive(Debug, Copy, Clone, PartialEq, Eq, ::scale::Encode, ::scale::Decode)]
125 #[cfg_attr(feature = "std", derive(::scale_info::TypeInfo))]
126 #enum_item
127
128 #critical_variant
129
130 #[cfg(feature = "substrate")]
131 #ret_val_impl
132 })
133}