tempo_precompiles_macros/
storable.rs1use proc_macro2::TokenStream;
4use quote::{format_ident, quote};
5use syn::{Data, DeriveInput, Fields, Ident, Type};
6
7use crate::{
8 FieldInfo,
9 layout::{gen_handler_field_decl, gen_handler_field_init},
10 packing::{self, LayoutField, PackingConstants},
11 storable_primitives::gen_struct_arrays,
12 utils::{extract_mapping_types, extract_storable_array_sizes, to_snake_case},
13};
14
15pub(crate) fn derive_impl(input: DeriveInput) -> syn::Result<TokenStream> {
21 let strukt = &input.ident;
23 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
24
25 let fields = match &input.data {
27 Data::Struct(data_struct) => match &data_struct.fields {
28 Fields::Named(fields_named) => &fields_named.named,
29 _ => {
30 return Err(syn::Error::new_spanned(
31 &input.ident,
32 "`Storable` can only be derived for structs with named fields",
33 ));
34 }
35 },
36 _ => {
37 return Err(syn::Error::new_spanned(
38 &input.ident,
39 "`Storable` can only be derived for structs",
40 ));
41 }
42 };
43
44 if fields.is_empty() {
45 return Err(syn::Error::new_spanned(
46 &input.ident,
47 "`Storable` cannot be derived for empty structs",
48 ));
49 }
50
51 let field_infos: Vec<_> = fields
53 .iter()
54 .map(|f| FieldInfo {
55 name: f.ident.as_ref().unwrap().clone(),
56 ty: f.ty.clone(),
57 slot: None,
58 base_slot: None,
59 })
60 .collect();
61
62 let layout_fields = packing::allocate_slots(&field_infos)?;
64
65 let mod_ident = format_ident!("__packing_{}", to_snake_case(&strukt.to_string()));
67 let packing_module = gen_packing_module_from_ir(&layout_fields, &mod_ident);
68
69 let len = fields.len();
71 let (direct_fields, direct_names, mapping_names) = field_infos.iter().fold(
72 (Vec::with_capacity(len), Vec::with_capacity(len), Vec::new()),
73 |mut out, field_info| {
74 if extract_mapping_types(&field_info.ty).is_none() {
75 out.0.push((&field_info.name, &field_info.ty));
77 out.1.push(&field_info.name);
78 } else {
79 out.2.push(&field_info.name);
81 }
82 out
83 },
84 );
85
86 let direct_tys: Vec<_> = direct_fields.iter().map(|(_, ty)| *ty).collect();
88
89 let load_impl = gen_load_store_impl(&direct_fields, &mod_ident, true);
91 let store_impl = gen_load_store_impl(&direct_fields, &mod_ident, false);
92 let delete_impl = gen_delete_impl(&direct_fields, &mod_ident);
93
94 let handler_struct = gen_handler_struct(strukt, &layout_fields, &mod_ident);
96 let handler_name = format_ident!("{}Handler", strukt);
97
98 let expanded = quote! {
99 #packing_module
100 #handler_struct
101
102 impl #impl_generics crate::storage::StorableType for #strukt #ty_generics #where_clause {
104 const LAYOUT: crate::storage::Layout = crate::storage::Layout::Slots(#mod_ident::SLOT_COUNT);
106
107 const IS_DYNAMIC: bool = #(
109 <#direct_tys as crate::storage::StorableType>::IS_DYNAMIC
110 )||*;
111
112 type Handler = #handler_name;
113
114 fn handle(slot: ::alloy::primitives::U256, _ctx: crate::storage::LayoutCtx, address: ::alloy::primitives::Address) -> Self::Handler {
115 #handler_name::new(slot, address)
116 }
117 }
118
119 impl #impl_generics crate::storage::Storable for #strukt #ty_generics #where_clause {
121 fn load<S: crate::storage::StorageOps>(
122 storage: &S,
123 base_slot: ::alloy::primitives::U256,
124 ctx: crate::storage::LayoutCtx
125 ) -> crate::error::Result<Self> {
126 use crate::storage::Storable;
127 debug_assert_eq!(ctx, crate::storage::LayoutCtx::FULL, "Struct types can only be loaded with LayoutCtx::FULL");
128
129 #load_impl
130
131 Ok(Self {
132 #(#direct_names),*,
133 #(#mapping_names: Default::default()),*
134 })
135 }
136
137 fn store<S: crate::storage::StorageOps>(
138 &self,
139 storage: &mut S,
140 base_slot: ::alloy::primitives::U256,
141 ctx: crate::storage::LayoutCtx
142 ) -> crate::error::Result<()> {
143 use crate::storage::Storable;
144 debug_assert_eq!(ctx, crate::storage::LayoutCtx::FULL, "Struct types can only be stored with LayoutCtx::FULL");
145
146 #store_impl
147
148 Ok(())
149 }
150
151 fn delete<S: crate::storage::StorageOps>(
152 storage: &mut S,
153 base_slot: ::alloy::primitives::U256,
154 ctx: crate::storage::LayoutCtx
155 ) -> crate::error::Result<()> {
156 use crate::storage::Storable;
157 debug_assert_eq!(ctx, crate::storage::LayoutCtx::FULL, "Struct types can only be deleted with LayoutCtx::FULL");
158
159 #delete_impl
160
161 Ok(())
162 }
163 }
164 };
165
166 let array_impls = if let Some(sizes) = extract_storable_array_sizes(&input.attrs)? {
168 let struct_type = quote! { #strukt #ty_generics };
170 gen_struct_arrays(struct_type, &sizes)
171 } else {
172 quote! {}
173 };
174
175 let combined = quote! {
177 #expanded
178 #array_impls
179 };
180
181 Ok(combined)
182}
183
184fn gen_packing_module_from_ir(fields: &[LayoutField<'_>], mod_ident: &Ident) -> TokenStream {
186 let last_field = &fields[fields.len() - 1];
188 let last_slot_const = PackingConstants::new(last_field.name).slot();
189 let packing_constants = packing::gen_constants_from_ir(fields, true);
190
191 quote! {
192 pub mod #mod_ident {
193 use super::*;
194
195 #packing_constants
196 pub const SLOT_COUNT: usize = (#last_slot_const.saturating_add(::alloy::primitives::U256::ONE)).as_limbs()[0] as usize;
197 }
198 }
199}
200
201fn gen_handler_struct(
205 struct_name: &Ident,
206 fields: &[LayoutField<'_>],
207 mod_ident: &Ident,
208) -> TokenStream {
209 let handler_name = format_ident!("{}Handler", struct_name);
210
211 let handler_fields = fields.iter().map(gen_handler_field_decl);
213
214 let field_inits = fields
216 .iter()
217 .enumerate()
218 .map(|(idx, field)| gen_handler_field_init(field, idx, fields, Some(mod_ident)));
219
220 quote! {
221 #[derive(Debug, Clone)]
225 pub struct #handler_name {
226 address: ::alloy::primitives::Address,
227 base_slot: ::alloy::primitives::U256,
228 #(#handler_fields,)*
229 }
230
231 impl #handler_name {
232 #[inline]
234 pub fn new(base_slot: ::alloy::primitives::U256, address: ::alloy::primitives::Address) -> Self {
235 Self {
236 base_slot,
237 #(#field_inits,)*
238 address,
239 }
240 }
241
242 #[inline]
247 pub fn base_slot(&self) -> ::alloy::primitives::U256 {
248 self.base_slot
249 }
250
251 #[inline]
253 fn as_slot(&self) -> crate::storage::Slot<#struct_name> {
254 crate::storage::Slot::<#struct_name>::new(
255 self.base_slot,
256 self.address
257 )
258 }
259 }
260
261 impl crate::storage::Handler<#struct_name> for #handler_name {
262 #[inline]
263 fn read(&self) -> crate::error::Result<#struct_name> {
264 self.as_slot().read()
265 }
266
267 #[inline]
268 fn write(&mut self, value: #struct_name) -> crate::error::Result<()> {
269 self.as_slot().write(value)
270 }
271
272 #[inline]
273 fn delete(&mut self) -> crate::error::Result<()> {
274 self.as_slot().delete()
275 }
276
277 #[inline]
279 fn t_read(&self) -> crate::error::Result<#struct_name> {
280 self.as_slot().t_read()
281 }
282
283 #[inline]
285 fn t_write(&mut self, value: #struct_name) -> crate::error::Result<()> {
286 self.as_slot().t_write(value)
287 }
288
289 #[inline]
291 fn t_delete(&mut self) -> crate::error::Result<()> {
292 self.as_slot().t_delete()
293 }
294 }
295 }
296}
297
298fn gen_load_store_impl(fields: &[(&Ident, &Type)], packing: &Ident, is_load: bool) -> TokenStream {
300 let field_ops = fields.iter().enumerate().map(|(idx, (name, ty))| {
301 let (prev_slot_const_ref, next_slot_const_ref) =
302 packing::get_neighbor_slot_refs(idx, fields, packing, |(name, _ty)| name);
303
304 let loc_const = PackingConstants::new(name).location();
306 let layout_ctx = packing::gen_layout_ctx_expr(
307 ty,
308 false,
309 quote! { #packing::#loc_const.offset_slots },
310 quote! { #packing::#loc_const.offset_bytes },
311 prev_slot_const_ref,
312 next_slot_const_ref,
313 );
314
315 if is_load {
316 quote! {
317 let #name = <#ty as crate::storage::Storable>::load(
318 storage,
319 base_slot + ::alloy::primitives::U256::from(#packing::#loc_const.offset_slots),
320 #layout_ctx
321 )?;
322 }
323 } else {
324 quote! {{
325 let target_slot = base_slot + ::alloy::primitives::U256::from(#packing::#loc_const.offset_slots);
326 <#ty as crate::storage::Storable>::store(&self.#name, storage, target_slot, #layout_ctx)?;
327 }}
328 }
329 });
330
331 quote! {
332 #(#field_ops)*
333 }
334}
335
336fn gen_delete_impl(fields: &[(&Ident, &Type)], packing: &Ident) -> TokenStream {
338 let dynamic_deletes = fields.iter().map(|(name, ty)| {
340 let loc_const = PackingConstants::new(name).location();
341 quote! {
342 if <#ty as crate::storage::StorableType>::IS_DYNAMIC {
343 <#ty as crate::storage::Storable>::delete(
344 storage,
345 base_slot + ::alloy::primitives::U256::from(#packing::#loc_const.offset_slots),
346 crate::storage::LayoutCtx::FULL
347 )?;
348 }
349 }
350 });
351
352 let is_static_slot = fields.iter().map(|(name, ty)| {
354 let loc_const = PackingConstants::new(name).location();
355 quote! {
356 ((#packing::#loc_const.offset_slots..#packing::#loc_const.offset_slots + <#ty as crate::storage::StorableType>::SLOTS)
357 .contains(&slot_offset) &&
358 !<#ty as crate::storage::StorableType>::IS_DYNAMIC)
359 }
360 });
361
362 quote! {
363 #(#dynamic_deletes)*
364
365 for slot_offset in 0..#packing::SLOT_COUNT {
366 if #(#is_static_slot)||* {
368 storage.store(
369 base_slot + ::alloy::primitives::U256::from(slot_offset),
370 ::alloy::primitives::U256::ZERO
371 )?;
372 }
373 }
374 }
375}