soroban_sdk_macros/
derive_enum_int.rs

1use itertools::MultiUnzip;
2use proc_macro2::TokenStream as TokenStream2;
3use quote::{format_ident, quote};
4use stellar_xdr::curr as stellar_xdr;
5use stellar_xdr::{ScSpecUdtEnumV0, StringM};
6use syn::{spanned::Spanned, Attribute, DataEnum, Error, ExprLit, Ident, Lit, Path, Visibility};
7
8use stellar_xdr::{ScSpecEntry, ScSpecUdtEnumCaseV0, WriteXdr};
9
10use crate::{doc::docs_from_attrs, DEFAULT_XDR_RW_LIMITS};
11
12// TODO: Add conversions to/from ScVal types.
13
14pub fn derive_type_enum_int(
15    path: &Path,
16    vis: &Visibility,
17    enum_ident: &Ident,
18    attrs: &[Attribute],
19    data: &DataEnum,
20    spec: bool,
21    lib: &Option<String>,
22) -> TokenStream2 {
23    // Collect errors as they are encountered and emit them at the end.
24    let mut errors = Vec::<Error>::new();
25
26    let variants = &data.variants;
27    let (spec_cases, try_froms, try_intos): (Vec<_>, Vec<_>, Vec<_>) = variants
28        .iter()
29        .map(|v| {
30            let ident = &v.ident;
31            let name = &ident.to_string();
32            let discriminant: u32 = if let syn::Expr::Lit(ExprLit {
33                lit: Lit::Int(ref lit_int),
34                ..
35            }) = v.discriminant.as_ref().unwrap().1
36            {
37                lit_int.base10_parse().unwrap_or_else(|_| {
38                    errors.push(Error::new(
39                        lit_int.span(),
40                        "unsupported discriminant value on enum variant, must be parseable as u32",
41                    ));
42                    0
43                })
44            } else {
45                errors.push(Error::new(
46                    v.discriminant.as_ref().unwrap().1.span(),
47                    "unsupported discriminant value on enum variant",
48                ));
49                0
50            };
51            let spec_case = ScSpecUdtEnumCaseV0 {
52                doc: docs_from_attrs(&v.attrs),
53                name: name.try_into().unwrap_or_else(|_| StringM::default()),
54                value: discriminant,
55            };
56            let try_from = quote! { #discriminant => Self::#ident };
57            let try_into = quote! { #enum_ident::#ident => #discriminant.into() };
58            (spec_case, try_from, try_into)
59        })
60        .multiunzip();
61
62    // If errors have occurred, render them instead.
63    if !errors.is_empty() {
64        let compile_errors = errors.iter().map(Error::to_compile_error);
65        return quote! { #(#compile_errors)* };
66    }
67
68    // Generated code spec.
69    let spec_gen = if spec {
70        let spec_entry = ScSpecEntry::UdtEnumV0(ScSpecUdtEnumV0 {
71            doc: docs_from_attrs(attrs),
72            lib: lib.as_deref().unwrap_or_default().try_into().unwrap(),
73            name: enum_ident.to_string().try_into().unwrap(),
74            cases: spec_cases.try_into().unwrap(),
75        });
76        let spec_xdr = spec_entry.to_xdr(DEFAULT_XDR_RW_LIMITS).unwrap();
77        let spec_xdr_lit = proc_macro2::Literal::byte_string(spec_xdr.as_slice());
78        let spec_xdr_len = spec_xdr.len();
79        let spec_ident = format_ident!("__SPEC_XDR_TYPE_{}", enum_ident.to_string().to_uppercase());
80        Some(quote! {
81            #[cfg_attr(target_family = "wasm", link_section = "contractspecv0")]
82            pub static #spec_ident: [u8; #spec_xdr_len] = #enum_ident::spec_xdr();
83
84            impl #enum_ident {
85                pub const fn spec_xdr() -> [u8; #spec_xdr_len] {
86                    *#spec_xdr_lit
87                }
88            }
89        })
90    } else {
91        None
92    };
93
94    // Output.
95    let mut output = quote! {
96        #spec_gen
97
98        impl #path::TryFromVal<#path::Env, #path::Val> for #enum_ident {
99            type Error = #path::ConversionError;
100            #[inline(always)]
101            fn try_from_val(env: &#path::Env, val: &#path::Val) -> Result<Self, #path::ConversionError> {
102                use #path::TryIntoVal;
103                let discriminant: u32 = val.try_into_val(env)?;
104                Ok(match discriminant {
105                    #(#try_froms,)*
106                    _ => Err(#path::ConversionError{})?,
107                })
108            }
109        }
110
111        impl #path::TryFromVal<#path::Env, #enum_ident> for #path::Val {
112            type Error = #path::ConversionError;
113            #[inline(always)]
114            fn try_from_val(env: &#path::Env, val: &#enum_ident) -> Result<Self, #path::ConversionError> {
115                Ok(match val {
116                    #(#try_intos,)*
117                })
118            }
119        }
120
121        impl #path::TryFromVal<#path::Env, &#enum_ident> for #path::Val {
122            type Error = #path::ConversionError;
123            #[inline(always)]
124            fn try_from_val(env: &#path::Env, val: &&#enum_ident) -> Result<Self, #path::ConversionError> {
125                <_ as #path::TryFromVal<#path::Env, #enum_ident>>::try_from_val(env, *val)
126            }
127        }
128    };
129
130    // Additional output when testutils are enabled.
131    if cfg!(feature = "testutils") {
132        let arbitrary_tokens =
133            crate::arbitrary::derive_arbitrary_enum_int(path, vis, enum_ident, data);
134        output.extend(quote! {
135            impl #path::TryFromVal<#path::Env, #path::xdr::ScVal> for #enum_ident {
136                type Error = #path::xdr::Error;
137                #[inline(always)]
138                fn try_from_val(env: &#path::Env, val: &#path::xdr::ScVal) -> Result<Self, #path::xdr::Error> {
139                    if let #path::xdr::ScVal::U32(discriminant) = val {
140                        Ok(match *discriminant {
141                            #(#try_froms,)*
142                            _ => Err(#path::xdr::Error::Invalid)?,
143                        })
144                    } else {
145                        Err(#path::xdr::Error::Invalid)
146                    }
147                }
148            }
149
150            impl TryInto<#path::xdr::ScVal> for &#enum_ident {
151                type Error = #path::xdr::Error;
152                #[inline(always)]
153                fn try_into(self) -> Result<#path::xdr::ScVal, #path::xdr::Error> {
154                    Ok(match self {
155                        #(#try_intos,)*
156                    })
157                }
158            }
159
160            impl TryInto<#path::xdr::ScVal> for #enum_ident {
161                type Error = #path::xdr::Error;
162                #[inline(always)]
163                fn try_into(self) -> Result<#path::xdr::ScVal, #path::xdr::Error> {
164                    Ok(match self {
165                        #(#try_intos,)*
166                    })
167                }
168            }
169
170            #arbitrary_tokens
171        });
172    }
173    output
174}