soroban_sdk_macros/
derive_enum_int.rs1use 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
12pub 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 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.is_empty() {
64 let compile_errors = errors.iter().map(Error::to_compile_error);
65 return quote! { #(#compile_errors)* };
66 }
67
68 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 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 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}