alloy_sol_macro_expander/expand/
function.rs
1use 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
10pub(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 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}