alloy_sol_macro_expander/expand/
enum.rs

1//! [`ItemEnum`] expansion.
2
3use super::ExpCtxt;
4use alloy_sol_macro_input::{derives_mapped, mk_doc, ContainsSolAttrs};
5use ast::{ItemEnum, Spanned};
6use proc_macro2::TokenStream;
7use quote::quote;
8use syn::Result;
9
10/// Expands an [`ItemEnum`]:
11///
12/// ```ignore (pseudo-code)
13/// #[repr(u8)]
14/// pub enum #name {
15///     #(#variant,)*
16/// }
17///
18/// impl SolEnum for #name {
19///     ...
20/// }
21/// ```
22pub(super) fn expand(cx: &ExpCtxt<'_>, enumm: &ItemEnum) -> Result<TokenStream> {
23    let ItemEnum { name, variants, .. } = enumm;
24
25    let (sol_attrs, mut attrs) = enumm.split_attrs()?;
26    cx.derives(&mut attrs, [], false);
27    let docs = sol_attrs.docs.or(cx.attrs.docs).unwrap_or(true);
28
29    let name_s = name.to_string();
30
31    let count = variants.len();
32    if count == 0 {
33        return Err(syn::Error::new(enumm.span(), "enum has no variants"));
34    }
35    if count > 256 {
36        return Err(syn::Error::new(enumm.span(), "enum has too many variants"));
37    }
38    let max = (count - 1) as u8;
39
40    let has_invalid_variant = max != u8::MAX;
41    let invalid_variant = has_invalid_variant.then(|| {
42        let comma = (!variants.trailing_punct()).then(syn::token::Comma::default);
43
44        let has_serde = derives_mapped(&attrs).any(|path| {
45            let Some(last) = path.segments.last() else {
46                return false;
47            };
48            last.ident == "Serialize" || last.ident == "Deserialize"
49        });
50        let serde_other = has_serde.then(|| quote!(#[serde(other)]));
51
52        quote! {
53            #comma
54            /// Invalid variant.
55            ///
56            /// This is only used when decoding an out-of-range `u8` value.
57            #[doc(hidden)]
58            #serde_other
59            __Invalid = u8::MAX,
60        }
61    });
62    let detokenize_unwrap = if has_invalid_variant {
63        quote! { unwrap_or(Self::__Invalid) }
64    } else {
65        quote! { expect("unreachable") }
66    };
67
68    let alloy_sol_types = &cx.crates.sol_types;
69
70    let uint8 = quote!(alloy_sol_types::sol_data::Uint<8>);
71    let uint8_st = quote!(<#uint8 as alloy_sol_types::SolType>);
72
73    let index_to_variant = variants.iter().enumerate().map(|(idx, variant)| {
74        let ident = &variant.ident;
75        let idx = idx as u8;
76        quote! { #idx => ::core::result::Result::Ok(Self::#ident), }
77    });
78
79    let doc = docs.then(|| mk_doc(format!("```solidity\n{enumm}\n```")));
80    let tokens = quote! {
81        #(#attrs)*
82        #doc
83        #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields, clippy::style)]
84        #[derive(Clone, Copy)]
85        #[repr(u8)]
86        pub enum #name {
87            #variants
88            #invalid_variant
89        }
90
91        #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields, clippy::style)]
92        const _: () = {
93            use #alloy_sol_types as alloy_sol_types;
94
95            #[automatically_derived]
96            impl ::core::convert::From<#name> for u8 {
97                #[inline]
98                fn from(v: #name) -> Self {
99                    v as u8
100                }
101            }
102
103            #[automatically_derived]
104            impl ::core::convert::TryFrom<u8> for #name {
105                type Error = alloy_sol_types::Error;
106
107                #[inline]
108                fn try_from(value: u8) -> alloy_sol_types::Result<Self> {
109                    match value {
110                        #(#index_to_variant)*
111                        value => ::core::result::Result::Err(alloy_sol_types::Error::InvalidEnumValue {
112                            name: #name_s,
113                            value,
114                            max: #max,
115                        })
116                    }
117                }
118            }
119
120            #[automatically_derived]
121            impl alloy_sol_types::SolValue for #name {
122                type SolType = Self;
123            }
124
125            #[automatically_derived]
126            impl alloy_sol_types::private::SolTypeValue<#name> for #name {
127                #[inline]
128                fn stv_to_tokens(&self) -> #uint8_st::Token<'_> {
129                    alloy_sol_types::Word::with_last_byte(*self as u8).into()
130                }
131
132                #[inline]
133                fn stv_eip712_data_word(&self) -> alloy_sol_types::Word {
134                    #uint8_st::eip712_data_word(&(*self as u8))
135                }
136
137                #[inline]
138                fn stv_abi_encode_packed_to(&self, out: &mut alloy_sol_types::private::Vec<u8>) {
139                    out.push(*self as u8);
140                }
141            }
142
143            #[automatically_derived]
144            impl alloy_sol_types::SolType for #name {
145                type RustType = #name;
146                type Token<'a> = #uint8_st::Token<'a>;
147
148                const SOL_NAME: &'static str = #uint8_st::SOL_NAME;
149                const ENCODED_SIZE: ::core::option::Option<usize> = #uint8_st::ENCODED_SIZE;
150                const PACKED_ENCODED_SIZE: ::core::option::Option<usize> = #uint8_st::PACKED_ENCODED_SIZE;
151
152                #[inline]
153                fn valid_token(token: &Self::Token<'_>) -> bool {
154                    Self::type_check(token).is_ok()
155                }
156
157                #[inline]
158                fn type_check(token: &Self::Token<'_>) -> alloy_sol_types::Result<()> {
159                    #uint8_st::type_check(token)?;
160                    <Self as ::core::convert::TryFrom<u8>>::try_from(
161                        #uint8_st::detokenize(*token)
162                    ).map(::core::mem::drop)
163                }
164
165                #[inline]
166                fn detokenize(token: Self::Token<'_>) -> Self::RustType {
167                    <Self as ::core::convert::TryFrom<u8>>::try_from(
168                        #uint8_st::detokenize(token)
169                    ).#detokenize_unwrap
170                }
171            }
172
173            #[automatically_derived]
174            impl alloy_sol_types::EventTopic for #name {
175                #[inline]
176                fn topic_preimage_length(rust: &Self::RustType) -> usize {
177                    <#uint8 as alloy_sol_types::EventTopic>::topic_preimage_length(&(*rust as u8))
178                }
179
180                #[inline]
181                fn encode_topic_preimage(rust: &Self::RustType, out: &mut alloy_sol_types::private::Vec<u8>) {
182                    <#uint8 as alloy_sol_types::EventTopic>::encode_topic_preimage(&(*rust as u8), out);
183                }
184
185                #[inline]
186                fn encode_topic(rust: &Self::RustType) -> alloy_sol_types::abi::token::WordToken {
187                    <#uint8 as alloy_sol_types::EventTopic>::encode_topic(&(*rust as u8))
188                }
189            }
190
191            #[automatically_derived]
192            impl alloy_sol_types::SolEnum for #name {
193                const COUNT: usize = #count;
194            }
195        };
196    };
197    Ok(tokens)
198}