alloy_sol_macro_expander/expand/
function.rs

1//! [`ItemFunction`] expansion.
2
3use super::{expand_fields, expand_from_into_tuples, expand_tokenize, expand_tuple_types, ExpCtxt};
4use alloy_sol_macro_input::{mk_doc, ContainsSolAttrs};
5use ast::{FunctionKind, ItemFunction, Spanned};
6use proc_macro2::TokenStream;
7use quote::{format_ident, quote};
8use syn::Result;
9
10/// Expands an [`ItemFunction`]:
11///
12/// ```ignore (pseudo-code)
13/// pub struct #{name}Call {
14///     #(pub #argument_name: #argument_type,)*
15/// }
16///
17/// pub struct #{name}Return {
18///     #(pub #return_name: #return_type,)*
19/// }
20///
21/// impl SolCall for #{name}Call {
22///     type Return = #{name}Return;
23///     ...
24/// }
25/// ```
26pub(super) fn expand(cx: &ExpCtxt<'_>, function: &ItemFunction) -> Result<TokenStream> {
27    let ItemFunction { parameters, returns, name, kind, .. } = function;
28
29    if matches!(kind, FunctionKind::Constructor(_)) {
30        return expand_constructor(cx, function);
31    }
32
33    if name.is_none() {
34        // ignore functions without names (modifiers...)
35        return Ok(quote!());
36    };
37
38    let returns = returns.as_ref().map(|r| &r.returns).unwrap_or_default();
39
40    cx.assert_resolved(parameters)?;
41    if !returns.is_empty() {
42        cx.assert_resolved(returns)?;
43    }
44
45    let (sol_attrs, mut call_attrs) = function.split_attrs()?;
46    let mut return_attrs = call_attrs.clone();
47    cx.derives(&mut call_attrs, parameters, true);
48    if !returns.is_empty() {
49        cx.derives(&mut return_attrs, returns, true);
50    }
51    let docs = sol_attrs.docs.or(cx.attrs.docs).unwrap_or(true);
52    let abi = sol_attrs.abi.or(cx.attrs.abi).unwrap_or(false);
53
54    let call_name = cx.call_name(function);
55    let return_name = cx.return_name(function);
56
57    let call_fields = expand_fields(parameters, cx);
58    let return_fields = expand_fields(returns, cx);
59
60    let call_tuple = expand_tuple_types(parameters.types(), cx).0;
61    let return_tuple = expand_tuple_types(returns.types(), cx).0;
62
63    let converts = expand_from_into_tuples(&call_name, parameters, cx);
64    let return_converts = expand_from_into_tuples(&return_name, returns, cx);
65
66    let signature = cx.function_signature(function);
67    let selector = crate::utils::selector(&signature);
68    let tokenize_impl = expand_tokenize(parameters, cx);
69
70    let call_doc = docs.then(|| {
71        let selector = hex::encode_prefixed(selector.array.as_slice());
72        mk_doc(format!(
73            "Function with signature `{signature}` and selector `{selector}`.\n\
74            ```solidity\n{function}\n```"
75        ))
76    });
77    let return_doc = docs.then(|| {
78        mk_doc(format!(
79            "Container type for the return parameters of the [`{signature}`]({call_name}) function."
80        ))
81    });
82
83    let abi: Option<TokenStream> = abi.then(|| {
84        if_json! {
85            let function = super::to_abi::generate(function, cx);
86            quote! {
87                #[automatically_derived]
88                impl alloy_sol_types::JsonAbiExt for #call_name {
89                    type Abi = alloy_sol_types::private::alloy_json_abi::Function;
90
91                    #[inline]
92                    fn abi() -> Self::Abi {
93                        #function
94                    }
95                }
96            }
97        }
98    });
99
100    let alloy_sol_types = &cx.crates.sol_types;
101
102    let tokens = quote! {
103        #(#call_attrs)*
104        #call_doc
105        #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)]
106        #[derive(Clone)]
107        pub struct #call_name {
108            #(#call_fields),*
109        }
110
111        #(#return_attrs)*
112        #return_doc
113        #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)]
114        #[derive(Clone)]
115        pub struct #return_name {
116            #(#return_fields),*
117        }
118
119        #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields, clippy::style)]
120        const _: () = {
121            use #alloy_sol_types as alloy_sol_types;
122
123            { #converts }
124            { #return_converts }
125
126            #[automatically_derived]
127            impl alloy_sol_types::SolCall for #call_name {
128                type Parameters<'a> = #call_tuple;
129                type Token<'a> = <Self::Parameters<'a> as alloy_sol_types::SolType>::Token<'a>;
130
131                type Return = #return_name;
132
133                type ReturnTuple<'a> = #return_tuple;
134                type ReturnToken<'a> = <Self::ReturnTuple<'a> as alloy_sol_types::SolType>::Token<'a>;
135
136                const SIGNATURE: &'static str = #signature;
137                const SELECTOR: [u8; 4] = #selector;
138
139                #[inline]
140                fn new<'a>(tuple: <Self::Parameters<'a> as alloy_sol_types::SolType>::RustType) -> Self {
141                    tuple.into()
142                }
143
144                #[inline]
145                fn tokenize(&self) -> Self::Token<'_> {
146                    #tokenize_impl
147                }
148
149                #[inline]
150                fn abi_decode_returns(data: &[u8], validate: bool) -> alloy_sol_types::Result<Self::Return> {
151                    <Self::ReturnTuple<'_> as alloy_sol_types::SolType>::abi_decode_sequence(data, validate).map(Into::into)
152                }
153            }
154
155            #abi
156        };
157    };
158    Ok(tokens)
159}
160
161fn expand_constructor(cx: &ExpCtxt<'_>, constructor: &ItemFunction) -> Result<TokenStream> {
162    let ItemFunction { parameters, .. } = constructor;
163
164    let (sol_attrs, call_attrs) = constructor.split_attrs()?;
165    let docs = sol_attrs.docs.or(cx.attrs.docs).unwrap_or(true);
166
167    let alloy_sol_types = &cx.crates.sol_types;
168
169    let call_name = format_ident!("constructorCall").with_span(constructor.kind.span());
170    let call_fields = expand_fields(parameters, cx);
171    let call_tuple = expand_tuple_types(parameters.types(), cx).0;
172    let converts = expand_from_into_tuples(&call_name, parameters, cx);
173    let tokenize_impl = expand_tokenize(parameters, cx);
174
175    let call_doc = docs.then(|| {
176        mk_doc(format!(
177            "Constructor`.\n\
178            ```solidity\n{constructor}\n```"
179        ))
180    });
181
182    let tokens = quote! {
183        #(#call_attrs)*
184        #call_doc
185        #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)]
186        #[derive(Clone)]
187        pub struct #call_name {
188            #(#call_fields),*
189        }
190
191        const _: () = {
192            use #alloy_sol_types as alloy_sol_types;
193
194            { #converts }
195
196            #[automatically_derived]
197            impl alloy_sol_types::SolConstructor for #call_name {
198                type Parameters<'a> = #call_tuple;
199                type Token<'a> = <Self::Parameters<'a> as alloy_sol_types::SolType>::Token<'a>;
200
201                #[inline]
202                fn new<'a>(tuple: <Self::Parameters<'a> as alloy_sol_types::SolType>::RustType) -> Self {
203                    tuple.into()
204                }
205
206                #[inline]
207                fn tokenize(&self) -> Self::Token<'_> {
208                    #tokenize_impl
209                }
210            }
211        };
212    };
213    Ok(tokens)
214}