Skip to main content

alloy_sol_macro_expander/expand/
error.rs

1//! [`ItemError`] expansion.
2
3use super::{ExpCtxt, expand_fields, expand_from_into_tuples, expand_tokenize};
4use alloy_sol_macro_input::{ContainsSolAttrs, mk_doc};
5use ast::ItemError;
6use proc_macro2::TokenStream;
7use quote::quote;
8use syn::Result;
9
10/// Expands an [`ItemError`]:
11///
12/// ```ignore (pseudo-code)
13/// pub struct #name {
14///     #(pub #parameter_name: #parameter_type,)*
15/// }
16///
17/// impl SolError for #name {
18///     ...
19/// }
20/// ```
21pub(super) fn expand(cx: &ExpCtxt<'_>, error: &ItemError) -> Result<TokenStream> {
22    let ItemError { parameters: params, .. } = error;
23    cx.assert_resolved(params)?;
24
25    let (sol_attrs, mut attrs) = error.split_attrs()?;
26    cx.derives(&mut attrs, params, true);
27    let docs = sol_attrs.docs.or(cx.attrs.docs).unwrap_or(true);
28    let abi = sol_attrs.abi.or(cx.attrs.abi).unwrap_or(false);
29
30    let tokenize_impl = expand_tokenize(params, cx, super::FieldKind::Deconstruct);
31
32    let name = cx.overloaded_name(error.into());
33    let signature = cx.error_signature(error);
34    let selector = crate::utils::selector(&signature);
35
36    let alloy_sol_types = &cx.crates.sol_types;
37
38    let converts = expand_from_into_tuples(&name.0, params, cx, super::FieldKind::Deconstruct);
39
40    let doc = docs.then(|| {
41        let selector = hex::encode_prefixed(selector.array.as_slice());
42        mk_doc(format!(
43            "Custom error with signature `{signature}` and selector `{selector}`.\n\
44             ```solidity\n{error}\n```"
45        ))
46    });
47    let abi: Option<TokenStream> = abi.then(|| {
48        if_json! {
49            let error = super::to_abi::generate(error, cx);
50            quote! {
51                #[automatically_derived]
52                impl alloy_sol_types::JsonAbiExt for #name {
53                    type Abi = alloy_sol_types::private::alloy_json_abi::Error;
54
55                    fn abi() -> Self::Abi {
56                        #error
57                    }
58                }
59            }
60        }
61    });
62
63    let err_struct = match params.len() {
64        0 => {
65            // Expanded as a unit struct.
66            quote! {
67                pub struct #name;
68            }
69        }
70        1 if params[0].name.is_none() => {
71            let ty = cx.expand_rust_type(&params[0].ty);
72            // Expanded as tuple struct if only one _unnamed_ parameter.
73            quote! {
74                pub struct #name(pub #ty);
75            }
76        }
77        _ => {
78            let fields = expand_fields(params, cx);
79            quote! {
80                pub struct #name {
81                    #(#fields),*
82                }
83            }
84        }
85    };
86
87    let tokens = quote! {
88        #(#attrs)*
89        #doc
90        #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)]
91        #[derive(Clone)]
92        #err_struct
93
94        #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields, clippy::style)]
95        const _: () = {
96            use #alloy_sol_types as alloy_sol_types;
97
98            #converts
99
100            #[automatically_derived]
101            impl alloy_sol_types::SolError for #name {
102                type Parameters<'a> = UnderlyingSolTuple<'a>;
103                type Token<'a> = <Self::Parameters<'a> as alloy_sol_types::SolType>::Token<'a>;
104
105                const SIGNATURE: &'static str = #signature;
106                const SELECTOR: [u8; 4] = #selector;
107
108                #[inline]
109                fn new<'a>(tuple: <Self::Parameters<'a> as alloy_sol_types::SolType>::RustType) -> Self {
110                    tuple.into()
111                }
112
113                #[inline]
114                fn tokenize(&self) -> Self::Token<'_> {
115                    #tokenize_impl
116                }
117
118                #[inline]
119                // TODO: Deprecate in favor of a validating decoder configuration.
120                // #[deprecated(note = "use a validating decoder configuration")]
121                fn abi_decode_raw_validate(data: &[u8]) -> alloy_sol_types::Result<Self> {
122                    Self::abi_decode_raw_with_config(
123                        data,
124                        alloy_sol_types::abi::AbiDecoderConfig::new().validate(true),
125                    )
126                }
127            }
128
129            #abi
130        };
131    };
132    Ok(tokens)
133}