Skip to main content

alloy_sol_macro_expander/expand/
function.rs

1//! [`ItemFunction`] expansion.
2
3use super::{
4    ExpCtxt, FieldKind, anon_name, expand_fields, expand_from_into_tuples, expand_tokenize,
5    expand_tuple_types,
6};
7use alloy_sol_macro_input::{ContainsSolAttrs, mk_doc};
8use ast::{FunctionKind, ItemFunction, Spanned};
9use proc_macro2::TokenStream;
10use quote::{format_ident, quote};
11use syn::Result;
12
13/// Expands an [`ItemFunction`]:
14///
15/// ```ignore (pseudo-code)
16/// pub struct #{name}Call {
17///     #(pub #argument_name: #argument_type,)*
18/// }
19///
20/// pub struct #{name}Return {
21///     #(pub #return_name: #return_type,)*
22/// }
23///
24/// impl SolCall for #{name}Call {
25///     type Return = #{name}Return;
26///     ...
27/// }
28/// ```
29pub(super) fn expand(cx: &ExpCtxt<'_>, function: &ItemFunction) -> Result<TokenStream> {
30    let ItemFunction { parameters, returns, name, kind, .. } = function;
31
32    if matches!(kind, FunctionKind::Constructor(_)) {
33        return expand_constructor(cx, function);
34    }
35
36    if name.is_none() {
37        // ignore functions without names (modifiers...)
38        return Ok(quote!());
39    }
40
41    let returns = returns.as_ref().map(|r| &r.returns).unwrap_or_default();
42
43    cx.assert_resolved(parameters)?;
44    if !returns.is_empty() {
45        cx.assert_resolved(returns)?;
46    }
47
48    let (sol_attrs, mut call_attrs) = function.split_attrs()?;
49    let mut return_attrs = call_attrs.clone();
50    cx.derives(&mut call_attrs, parameters, true);
51    if !returns.is_empty() {
52        cx.derives(&mut return_attrs, returns, true);
53    }
54    let docs = sol_attrs.docs.or(cx.attrs.docs).unwrap_or(true);
55    let abi = sol_attrs.abi.or(cx.attrs.abi).unwrap_or(false);
56
57    let call_name = cx.call_name(function);
58    let return_name = cx.return_name(function);
59
60    let call_fields = expand_fields(parameters, cx);
61    let return_fields = expand_fields(returns, cx);
62
63    let call_tuple = expand_tuple_types(parameters.types(), cx).0;
64    let return_tuple = expand_tuple_types(returns.types(), cx).0;
65
66    let converts = expand_from_into_tuples(&call_name, parameters, cx, FieldKind::Deconstruct);
67    let return_converts = expand_from_into_tuples(&return_name, returns, cx, FieldKind::Original);
68
69    let signature = cx.function_signature(function);
70    let selector = crate::utils::selector(&signature);
71    let tokenize_impl = expand_tokenize(parameters, cx, FieldKind::Deconstruct);
72
73    let call_doc = docs.then(|| {
74        let selector = hex::encode_prefixed(selector.array.as_slice());
75        mk_doc(format!(
76            "Function with signature `{signature}` and selector `{selector}`.\n\
77            ```solidity\n{function}\n```"
78        ))
79    });
80    let return_doc = docs.then(|| {
81        mk_doc(format!(
82            "Container type for the return parameters of the [`{signature}`]({call_name}) function."
83        ))
84    });
85
86    let abi: Option<TokenStream> = abi.then(|| {
87        if_json! {
88            let function = super::to_abi::generate(function, cx);
89            quote! {
90                #[automatically_derived]
91                impl alloy_sol_types::JsonAbiExt for #call_name {
92                    type Abi = alloy_sol_types::private::alloy_json_abi::Function;
93
94                    fn abi() -> Self::Abi {
95                        #function
96                    }
97                }
98            }
99        }
100    });
101
102    let call_struct = if parameters.is_empty() {
103        quote! {
104            pub struct #call_name;
105        }
106    } else if parameters.len() == 1 && parameters[0].name.is_none() {
107        let ty = cx.expand_rust_type(&parameters[0].ty);
108        quote! {
109            pub struct #call_name(pub #ty);
110        }
111    } else {
112        quote! {
113            pub struct #call_name {
114                #(#call_fields),*
115            }
116        }
117    };
118
119    let alloy_sol_types = &cx.crates.sol_types;
120
121    let decode_sequence =
122        quote!(<Self::ReturnTuple<'_> as alloy_sol_types::SolType>::abi_decode_sequence(data));
123
124    // Determine whether the return type should directly yield result or the <name>Return struct.
125    let is_single_return = returns.len() == 1;
126    let return_type =
127        if is_single_return { cx.expand_rust_type(&returns[0].ty) } else { quote!(#return_name) };
128    let tokenize_returns_impl = if is_single_return {
129        quote!()
130    } else {
131        let imp = expand_tokenize(returns, cx, FieldKind::Original);
132        quote! {
133            impl #return_name {
134                fn _tokenize(&self) -> <#call_name as alloy_sol_types::SolCall>::ReturnToken<'_> {
135                    #imp
136                }
137            }
138        }
139    };
140    let tokenize_returns = if is_single_return {
141        let ty = cx.expand_type(&returns[0].ty);
142        quote! { (<#ty as alloy_sol_types::SolType>::tokenize(ret),) }
143    } else {
144        quote! { #return_name::_tokenize(ret) }
145    };
146    let decode_returns = if is_single_return {
147        let name = anon_name((0, returns[0].name.as_ref()));
148        quote! {
149            #decode_sequence.map(|r| {
150                let r: #return_name = r.into();
151                r.#name
152            })
153        }
154    } else {
155        quote!(#decode_sequence.map(Into::into))
156    };
157
158    let decode_sequence_with_config = quote!(
159        <Self::ReturnTuple<'_> as alloy_sol_types::SolType>::abi_decode_sequence_with_config(
160            data, config,
161        )
162    );
163    let decode_returns_with_config = if is_single_return {
164        let name = anon_name((0, returns[0].name.as_ref()));
165        quote! {
166            #decode_sequence_with_config.map(|r| {
167                let r: #return_name = r.into();
168                r.#name
169            })
170        }
171    } else {
172        quote!(#decode_sequence_with_config.map(Into::into))
173    };
174
175    let tokens = quote! {
176        #(#call_attrs)*
177        #call_doc
178        #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)]
179        #[derive(Clone)]
180        #call_struct
181
182        #(#return_attrs)*
183        #return_doc
184        #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)]
185        #[derive(Clone)]
186        pub struct #return_name {
187            #(#return_fields),*
188        }
189
190        #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields, clippy::style)]
191        const _: () = {
192            use #alloy_sol_types as alloy_sol_types;
193
194            { #converts }
195            { #return_converts }
196
197            #tokenize_returns_impl
198
199            #[automatically_derived]
200            impl alloy_sol_types::SolCall for #call_name {
201                type Parameters<'a> = #call_tuple;
202                type Token<'a> = <Self::Parameters<'a> as alloy_sol_types::SolType>::Token<'a>;
203
204                type Return = #return_type;
205
206                type ReturnTuple<'a> = #return_tuple;
207                type ReturnToken<'a> = <Self::ReturnTuple<'a> as alloy_sol_types::SolType>::Token<'a>;
208
209                const SIGNATURE: &'static str = #signature;
210                const SELECTOR: [u8; 4] = #selector;
211
212                #[inline]
213                fn new<'a>(tuple: <Self::Parameters<'a> as alloy_sol_types::SolType>::RustType) -> Self {
214                    tuple.into()
215                }
216
217                #[inline]
218                fn tokenize(&self) -> Self::Token<'_> {
219                    #tokenize_impl
220                }
221
222                #[inline]
223                fn tokenize_returns(ret: &Self::Return) -> Self::ReturnToken<'_> {
224                    #tokenize_returns
225                }
226
227                #[inline]
228                fn abi_decode_returns(data: &[u8]) -> alloy_sol_types::Result<Self::Return> {
229                    #decode_returns
230                }
231
232                #[inline]
233                fn abi_decode_returns_with_config(
234                    data: &[u8],
235                    config: alloy_sol_types::abi::AbiDecoderConfig,
236                ) -> alloy_sol_types::Result<Self::Return> {
237                    #decode_returns_with_config
238                }
239
240                #[inline]
241                // TODO: Deprecate in favor of a validating decoder configuration.
242                // #[deprecated(note = "use a validating decoder configuration")]
243                fn abi_decode_returns_validate(data: &[u8]) -> alloy_sol_types::Result<Self::Return> {
244                    Self::abi_decode_returns_with_config(
245                        data,
246                        alloy_sol_types::abi::AbiDecoderConfig::new().validate(true),
247                    )
248                }
249            }
250
251            #abi
252        };
253    };
254    Ok(tokens)
255}
256
257fn expand_constructor(cx: &ExpCtxt<'_>, constructor: &ItemFunction) -> Result<TokenStream> {
258    let ItemFunction { parameters, .. } = constructor;
259
260    let (sol_attrs, call_attrs) = constructor.split_attrs()?;
261    let docs = sol_attrs.docs.or(cx.attrs.docs).unwrap_or(true);
262
263    let alloy_sol_types = &cx.crates.sol_types;
264
265    let call_name = format_ident!("constructorCall").with_span(constructor.kind.span());
266    let call_fields = expand_fields(parameters, cx);
267    let call_tuple = expand_tuple_types(parameters.types(), cx).0;
268    let converts = expand_from_into_tuples(&call_name, parameters, cx, FieldKind::Original);
269    let tokenize_impl = expand_tokenize(parameters, cx, FieldKind::Original);
270
271    let call_doc = docs.then(|| {
272        mk_doc(format!(
273            "Constructor`.\n\
274            ```solidity\n{constructor}\n```"
275        ))
276    });
277
278    let tokens = quote! {
279        #(#call_attrs)*
280        #call_doc
281        #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)]
282        #[derive(Clone)]
283        pub struct #call_name {
284            #(#call_fields),*
285        }
286
287        const _: () = {
288            use #alloy_sol_types as alloy_sol_types;
289
290            { #converts }
291
292            #[automatically_derived]
293            impl alloy_sol_types::SolConstructor for #call_name {
294                type Parameters<'a> = #call_tuple;
295                type Token<'a> = <Self::Parameters<'a> as alloy_sol_types::SolType>::Token<'a>;
296
297                #[inline]
298                fn new<'a>(tuple: <Self::Parameters<'a> as alloy_sol_types::SolType>::RustType) -> Self {
299                    tuple.into()
300                }
301
302                #[inline]
303                fn tokenize(&self) -> Self::Token<'_> {
304                    #tokenize_impl
305                }
306            }
307        };
308    };
309    Ok(tokens)
310}