alloy_sol_macro_expander/expand/
function.rs1use 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
13pub(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 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(¶meters[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 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 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}