alloy_sol_macro_expander/expand/
struct.rs
1use super::{expand_fields, expand_from_into_tuples, expand_tokenize, ExpCtxt};
4use alloy_sol_macro_input::{mk_doc, ContainsSolAttrs};
5use ast::{Item, ItemStruct, Spanned, Type};
6use proc_macro2::TokenStream;
7use quote::quote;
8use std::num::NonZeroU16;
9use syn::Result;
10
11pub(super) fn expand(cx: &ExpCtxt<'_>, s: &ItemStruct) -> Result<TokenStream> {
28 let ItemStruct { name, fields, .. } = s;
29
30 let (sol_attrs, mut attrs) = s.split_attrs()?;
31
32 cx.derives(&mut attrs, fields, true);
33 let docs = sol_attrs.docs.or(cx.attrs.docs).unwrap_or(true);
34
35 let (field_types, field_names): (Vec<_>, Vec<_>) =
36 fields.iter().map(|f| (cx.expand_type(&f.ty), f.name.as_ref().unwrap())).unzip();
37
38 let eip712_encode_type_fns = expand_encode_type_fns(cx, fields, name);
39
40 let tokenize_impl = expand_tokenize(fields, cx);
41
42 let encode_data_impl = match fields.len() {
43 0 => unreachable!("struct with zero fields"),
44 1 => {
45 let name = *field_names.first().unwrap();
46 let ty = field_types.first().unwrap();
47 quote!(<#ty as alloy_sol_types::SolType>::eip712_data_word(&self.#name).0.to_vec())
48 }
49 _ => quote! {
50 [#(
51 <#field_types as alloy_sol_types::SolType>::eip712_data_word(&self.#field_names).0,
52 )*].concat()
53 },
54 };
55
56 let alloy_sol_types = &cx.crates.sol_types;
57
58 let attrs = attrs.iter();
59 let convert = expand_from_into_tuples(&name.0, fields, cx);
60 let name_s = name.as_string();
61 let fields = expand_fields(fields, cx);
62
63 let doc = docs.then(|| mk_doc(format!("```solidity\n{s}\n```")));
64 let tokens = quote! {
65 #(#attrs)*
66 #doc
67 #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)]
68 #[derive(Clone)]
69 pub struct #name {
70 #(#fields),*
71 }
72
73 #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields, clippy::style)]
74 const _: () = {
75 use #alloy_sol_types as alloy_sol_types;
76
77 #convert
78
79 #[automatically_derived]
80 impl alloy_sol_types::SolValue for #name {
81 type SolType = Self;
82 }
83
84 #[automatically_derived]
85 impl alloy_sol_types::private::SolTypeValue<Self> for #name {
86 #[inline]
87 fn stv_to_tokens(&self) -> <Self as alloy_sol_types::SolType>::Token<'_> {
88 #tokenize_impl
89 }
90
91 #[inline]
92 fn stv_abi_encoded_size(&self) -> usize {
93 if let Some(size) = <Self as alloy_sol_types::SolType>::ENCODED_SIZE {
94 return size;
95 }
96
97 let tuple = <UnderlyingRustTuple<'_> as ::core::convert::From<Self>>::from(self.clone());
99 <UnderlyingSolTuple<'_> as alloy_sol_types::SolType>::abi_encoded_size(&tuple)
100 }
101
102 #[inline]
103 fn stv_eip712_data_word(&self) -> alloy_sol_types::Word {
104 <Self as alloy_sol_types::SolStruct>::eip712_hash_struct(self)
105 }
106
107 #[inline]
108 fn stv_abi_encode_packed_to(&self, out: &mut alloy_sol_types::private::Vec<u8>) {
109 let tuple = <UnderlyingRustTuple<'_> as ::core::convert::From<Self>>::from(self.clone());
111 <UnderlyingSolTuple<'_> as alloy_sol_types::SolType>::abi_encode_packed_to(&tuple, out)
112 }
113
114 #[inline]
115 fn stv_abi_packed_encoded_size(&self) -> usize {
116 if let Some(size) = <Self as alloy_sol_types::SolType>::PACKED_ENCODED_SIZE {
117 return size;
118 }
119
120 let tuple = <UnderlyingRustTuple<'_> as ::core::convert::From<Self>>::from(self.clone());
122 <UnderlyingSolTuple<'_> as alloy_sol_types::SolType>::abi_packed_encoded_size(&tuple)
123 }
124 }
125
126 #[automatically_derived]
127 impl alloy_sol_types::SolType for #name {
128 type RustType = Self;
129 type Token<'a> = <UnderlyingSolTuple<'a> as alloy_sol_types::SolType>::Token<'a>;
130
131 const SOL_NAME: &'static str = <Self as alloy_sol_types::SolStruct>::NAME;
132 const ENCODED_SIZE: Option<usize> =
133 <UnderlyingSolTuple<'_> as alloy_sol_types::SolType>::ENCODED_SIZE;
134 const PACKED_ENCODED_SIZE: Option<usize> =
135 <UnderlyingSolTuple<'_> as alloy_sol_types::SolType>::PACKED_ENCODED_SIZE;
136
137 #[inline]
138 fn valid_token(token: &Self::Token<'_>) -> bool {
139 <UnderlyingSolTuple<'_> as alloy_sol_types::SolType>::valid_token(token)
140 }
141
142 #[inline]
143 fn detokenize(token: Self::Token<'_>) -> Self::RustType {
144 let tuple = <UnderlyingSolTuple<'_> as alloy_sol_types::SolType>::detokenize(token);
145 <Self as ::core::convert::From<UnderlyingRustTuple<'_>>>::from(tuple)
146 }
147 }
148
149 #[automatically_derived]
150 impl alloy_sol_types::SolStruct for #name {
151 const NAME: &'static str = #name_s;
152
153 #eip712_encode_type_fns
154
155 #[inline]
156 fn eip712_encode_data(&self) -> alloy_sol_types::private::Vec<u8> {
157 #encode_data_impl
158 }
159 }
160
161 #[automatically_derived]
162 impl alloy_sol_types::EventTopic for #name {
163 #[inline]
164 fn topic_preimage_length(rust: &Self::RustType) -> usize {
165 0usize
166 #(
167 + <#field_types as alloy_sol_types::EventTopic>::topic_preimage_length(&rust.#field_names)
168 )*
169 }
170
171 #[inline]
172 fn encode_topic_preimage(rust: &Self::RustType, out: &mut alloy_sol_types::private::Vec<u8>) {
173 out.reserve(<Self as alloy_sol_types::EventTopic>::topic_preimage_length(rust));
174 #(
175 <#field_types as alloy_sol_types::EventTopic>::encode_topic_preimage(&rust.#field_names, out);
176 )*
177 }
178
179 #[inline]
180 fn encode_topic(rust: &Self::RustType) -> alloy_sol_types::abi::token::WordToken {
181 let mut out = alloy_sol_types::private::Vec::new();
182 <Self as alloy_sol_types::EventTopic>::encode_topic_preimage(rust, &mut out);
183 alloy_sol_types::abi::token::WordToken(
184 alloy_sol_types::private::keccak256(out)
185 )
186 }
187 }
188 };
189 };
190 Ok(tokens)
191}
192
193fn expand_encode_type_fns(
194 cx: &ExpCtxt<'_>,
195 fields: &ast::Parameters<syn::token::Semi>,
196 name: &ast::SolIdent,
197) -> TokenStream {
198 let mut fields = fields.clone();
200 fields.visit_types_mut(|ty| {
201 let Type::Custom(name) = ty else { return };
202 match cx.try_item(name) {
203 Some(Item::Struct(_)) | None => {}
205 Some(Item::Contract(_)) => *ty = Type::Address(ty.span(), None),
207 Some(Item::Enum(_)) => *ty = Type::Uint(ty.span(), NonZeroU16::new(8)),
208 Some(Item::Udt(udt)) => *ty = udt.ty.clone(),
209 Some(item) => {
210 proc_macro_error2::abort!(item.span(), "Invalid type in struct field: {:?}", item)
211 }
212 }
213 });
214
215 let root = fields.eip712_signature(name.as_string());
216
217 let custom = fields.iter().filter(|f| f.ty.has_custom());
218 let n_custom = custom.clone().count();
219
220 let components_impl = if n_custom > 0 {
221 let bits = custom.map(|field| {
222 let mut ty = None;
224 field.ty.visit(|field_ty| {
225 if ty.is_none() && field_ty.is_custom() {
226 ty = Some(field_ty.clone());
227 }
228 });
229 let ty = cx.expand_type(&ty.unwrap());
231
232 quote! {
233 components.push(<#ty as alloy_sol_types::SolStruct>::eip712_root_type());
234 components.extend(<#ty as alloy_sol_types::SolStruct>::eip712_components());
235 }
236 });
237 let capacity = proc_macro2::Literal::usize_unsuffixed(n_custom);
238 quote! {
239 let mut components = alloy_sol_types::private::Vec::with_capacity(#capacity);
240 #(#bits)*
241 components
242 }
243 } else {
244 quote! { alloy_sol_types::private::Vec::new() }
245 };
246
247 let encode_type_impl_opt = (n_custom == 0).then(|| {
248 quote! {
249 #[inline]
250 fn eip712_encode_type() -> alloy_sol_types::private::Cow<'static, str> {
251 <Self as alloy_sol_types::SolStruct>::eip712_root_type()
252 }
253 }
254 });
255
256 quote! {
257 #[inline]
258 fn eip712_root_type() -> alloy_sol_types::private::Cow<'static, str> {
259 alloy_sol_types::private::Cow::Borrowed(#root)
260 }
261
262 #[inline]
263 fn eip712_components() -> alloy_sol_types::private::Vec<alloy_sol_types::private::Cow<'static, str>> {
264 #components_impl
265 }
266
267 #encode_type_impl_opt
268 }
269}