Skip to main content

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 `from_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(|decompressor| {
137                    let decompressed = decompressor.decompress(buf);
138                    let mut original_buf = buf;
139
140                    let mut buf: &[u8] = decompressed;
141                    #(#lines)*
142                    (obj, original_buf)
143                })
144            } else {
145                #(#lines)*
146                (obj, buf)
147            }
148        }
149    } else {
150        quote! {
151            #(#lines)*
152            (obj, buf)
153        }
154    }
155}
156
157/// Generates code to implement the `Compact` trait method `to_compact`.
158fn generate_to_compact(
159    fields: &FieldList,
160    ident: &Ident,
161    zstd: Option<ZstdConfig>,
162    reth_codecs: &syn::Path,
163) -> Vec<TokenStream2> {
164    let mut lines = vec![quote! {
165        let mut buffer = #reth_codecs::__private::bytes::BytesMut::new();
166    }];
167
168    let is_enum = fields.iter().any(|field| matches!(field, FieldTypes::EnumVariant(_)));
169
170    if is_enum {
171        let enum_lines = EnumHandler::new(fields).generate_to(ident);
172
173        lines.push(quote! {
174            flags.set_variant(match self {
175                #(#enum_lines)*
176            });
177        })
178    } else {
179        lines.append(&mut StructHandler::new(fields).generate_to());
180    }
181
182    // Just because a type supports compression, doesn't mean all its values are to be compressed.
183    // We skip the smaller ones, and thus require a flag` __zstd` to specify if this value is
184    // compressed or not.
185    if zstd.is_some() {
186        lines.push(quote! {
187            let mut zstd = buffer.len() > 7;
188            if zstd {
189                flags.set___zstd(1);
190            }
191        });
192    }
193
194    // Places the flag bits.
195    lines.push(quote! {
196        let flags = flags.into_bytes();
197        total_length += flags.len() + buffer.len();
198        buf.put_slice(&flags);
199    });
200
201    if let Some(zstd) = zstd {
202        let compressor = zstd.compressor;
203        lines.push(quote! {
204            if zstd {
205                #compressor(|compressor| {
206                    let compressed = compressor.compress(&buffer).expect("Failed to compress.");
207                    buf.put(compressed.as_slice());
208                });
209            } else {
210                buf.put(buffer);
211            }
212        });
213    } else {
214        lines.push(quote! {
215            buf.put(buffer);
216        })
217    }
218
219    lines
220}
221
222/// Function to extract the crate path from `reth_codecs(crate = "...")` attribute.
223pub(crate) fn parse_reth_codecs_path(attrs: &[Attribute]) -> syn::Result<syn::Path> {
224    // let default_crate_path: syn::Path = syn::parse_str("reth-codecs").unwrap();
225    let mut reth_codecs_path: syn::Path = syn::parse_quote!(reth_codecs);
226    for attr in attrs {
227        if attr.path().is_ident("reth_codecs") {
228            attr.parse_nested_meta(|meta| {
229                if meta.path.is_ident("crate") {
230                    let value = meta.value()?;
231                    let lit: LitStr = value.parse()?;
232                    reth_codecs_path = syn::parse_str(&lit.value())?;
233                    Ok(())
234                } else {
235                    Err(meta.error("unsupported attribute"))
236                }
237            })?;
238        }
239    }
240
241    Ok(reth_codecs_path)
242}