reth_codecs_derive/compact/
generator.rs

1//! Code generator for the `Compact` trait.
2
3use super::*;
4use crate::ZstdConfig;
5use syn::{Attribute, LitStr};
6
7/// Generates code to implement the `Compact` trait for a data type.
8pub fn generate_from_to(
9    ident: &Ident,
10    attrs: &[Attribute],
11    has_lifetime: bool,
12    fields: &FieldList,
13    zstd: Option<ZstdConfig>,
14) -> TokenStream2 {
15    let flags = format_ident!("{ident}Flags");
16
17    let reth_codecs = parse_reth_codecs_path(attrs).unwrap();
18
19    let to_compact = generate_to_compact(fields, ident, zstd.clone(), &reth_codecs);
20    let from_compact = generate_from_compact(fields, ident, zstd);
21
22    let lifetime = if has_lifetime {
23        quote! { 'a }
24    } else {
25        quote! {}
26    };
27
28    let impl_compact = if has_lifetime {
29        quote! {
30           impl<#lifetime> #reth_codecs::Compact for #ident<#lifetime>
31        }
32    } else {
33        quote! {
34           impl #reth_codecs::Compact for #ident
35        }
36    };
37
38    let has_ref_fields = fields.iter().any(|field| {
39        if let FieldTypes::StructField(field) = field {
40            field.is_reference
41        } else {
42            false
43        }
44    });
45
46    let fn_from_compact = if has_ref_fields {
47        quote! { unimplemented!("from_compact not supported with ref structs") }
48    } else {
49        quote! {
50            let (flags, mut buf) = #flags::from(buf);
51            #from_compact
52        }
53    };
54
55    // Build function
56    quote! {
57        #impl_compact {
58            fn to_compact<B>(&self, buf: &mut B) -> usize where B: #reth_codecs::__private::bytes::BufMut + AsMut<[u8]> {
59                let mut flags = #flags::default();
60                let mut total_length = 0;
61                #(#to_compact)*
62                total_length
63            }
64
65            fn from_compact(mut buf: &[u8], len: usize) -> (Self, &[u8]) {
66                #fn_from_compact
67            }
68        }
69    }
70}
71
72/// Generates code to implement the `Compact` trait method `to_compact`.
73fn generate_from_compact(
74    fields: &FieldList,
75    ident: &Ident,
76    zstd: Option<ZstdConfig>,
77) -> TokenStream2 {
78    let mut lines = vec![];
79    let mut known_types =
80        vec!["B256", "Address", "Bloom", "Vec", "TxHash", "BlockHash", "FixedBytes", "Cow"];
81
82    // Only types without `Bytes` should be added here. It's currently manually added, since
83    // it's hard to figure out with derive_macro which types have Bytes fields.
84    //
85    // This removes the requirement of the field to be placed last in the struct.
86    known_types.extend_from_slice(&["TxKind", "AccessList", "Signature", "CheckpointBlockRange"]);
87
88    // let mut handle = FieldListHandler::new(fields);
89    let is_enum = fields.iter().any(|field| matches!(field, FieldTypes::EnumVariant(_)));
90
91    if is_enum {
92        let enum_lines = EnumHandler::new(fields).generate_from(ident);
93
94        // Builds the object instantiation.
95        lines.push(quote! {
96            let obj = match flags.variant() {
97                #(#enum_lines)*
98                _ => unreachable!()
99            };
100        });
101    } else {
102        let mut struct_handler = StructHandler::new(fields);
103        lines.append(&mut struct_handler.generate_from(known_types.as_slice()));
104
105        // Builds the object instantiation.
106        if struct_handler.is_wrapper {
107            lines.push(quote! {
108                let obj = #ident(placeholder);
109            });
110        } else {
111            let fields = fields.iter().filter_map(|field| {
112                if let FieldTypes::StructField(field) = field {
113                    let ident = format_ident!("{}", field.name);
114                    return Some(quote! {
115                        #ident: #ident,
116                    })
117                }
118                None
119            });
120
121            lines.push(quote! {
122                let obj = #ident {
123                    #(#fields)*
124                };
125            });
126        }
127    }
128
129    // If the type has compression support, then check the `__zstd` flag. Otherwise, use the default
130    // code branch. However, even if it's a type with compression support, not all values are
131    // to be compressed (thus the zstd flag). Ideally only the bigger ones.
132    if let Some(zstd) = zstd {
133        let decompressor = zstd.decompressor;
134        quote! {
135            if flags.__zstd() != 0 {
136                #decompressor.with(|decompressor| {
137                    let decompressor = &mut decompressor.borrow_mut();
138                    let decompressed = decompressor.decompress(buf);
139                    let mut original_buf = buf;
140
141                    let mut buf: &[u8] = decompressed;
142                    #(#lines)*
143                    (obj, original_buf)
144                })
145            } else {
146                #(#lines)*
147                (obj, buf)
148            }
149        }
150    } else {
151        quote! {
152            #(#lines)*
153            (obj, buf)
154        }
155    }
156}
157
158/// Generates code to implement the `Compact` trait method `from_compact`.
159fn generate_to_compact(
160    fields: &FieldList,
161    ident: &Ident,
162    zstd: Option<ZstdConfig>,
163    reth_codecs: &syn::Path,
164) -> Vec<TokenStream2> {
165    let mut lines = vec![quote! {
166        let mut buffer = #reth_codecs::__private::bytes::BytesMut::new();
167    }];
168
169    let is_enum = fields.iter().any(|field| matches!(field, FieldTypes::EnumVariant(_)));
170
171    if is_enum {
172        let enum_lines = EnumHandler::new(fields).generate_to(ident);
173
174        lines.push(quote! {
175            flags.set_variant(match self {
176                #(#enum_lines)*
177            });
178        })
179    } else {
180        lines.append(&mut StructHandler::new(fields).generate_to());
181    }
182
183    // Just because a type supports compression, doesn't mean all its values are to be compressed.
184    // We skip the smaller ones, and thus require a flag` __zstd` to specify if this value is
185    // compressed or not.
186    if zstd.is_some() {
187        lines.push(quote! {
188            let mut zstd = buffer.len() > 7;
189            if zstd {
190                flags.set___zstd(1);
191            }
192        });
193    }
194
195    // Places the flag bits.
196    lines.push(quote! {
197        let flags = flags.into_bytes();
198        total_length += flags.len() + buffer.len();
199        buf.put_slice(&flags);
200    });
201
202    if let Some(zstd) = zstd {
203        let compressor = zstd.compressor;
204        lines.push(quote! {
205            if zstd {
206                #compressor.with(|compressor| {
207                    let mut compressor = compressor.borrow_mut();
208
209                    let compressed = compressor.compress(&buffer).expect("Failed to compress.");
210                    buf.put(compressed.as_slice());
211                });
212            } else {
213                buf.put(buffer);
214            }
215        });
216    } else {
217        lines.push(quote! {
218            buf.put(buffer);
219        })
220    }
221
222    lines
223}
224
225/// Function to extract the crate path from `reth_codecs(crate = "...")` attribute.
226pub(crate) fn parse_reth_codecs_path(attrs: &[Attribute]) -> syn::Result<syn::Path> {
227    // let default_crate_path: syn::Path = syn::parse_str("reth-codecs").unwrap();
228    let mut reth_codecs_path: syn::Path = syn::parse_quote!(reth_codecs);
229    for attr in attrs {
230        if attr.path().is_ident("reth_codecs") {
231            attr.parse_nested_meta(|meta| {
232                if meta.path.is_ident("crate") {
233                    let value = meta.value()?;
234                    let lit: LitStr = value.parse()?;
235                    reth_codecs_path = syn::parse_str(&lit.value())?;
236                    Ok(())
237                } else {
238                    Err(meta.error("unsupported attribute"))
239                }
240            })?;
241        }
242    }
243
244    Ok(reth_codecs_path)
245}