reth_codecs_derive/compact/
mod.rsuse proc_macro::TokenStream;
use proc_macro2::{Ident, TokenStream as TokenStream2};
use quote::{format_ident, quote};
use syn::{parse_macro_input, Data, DeriveInput, Generics};
mod generator;
use generator::*;
mod enums;
use enums::*;
mod flags;
use flags::*;
mod structs;
use structs::*;
type IsCompact = bool;
type FieldName = String;
type FieldType = String;
type UseAlternative = bool;
type StructFieldDescriptor = (FieldName, FieldType, IsCompact, UseAlternative);
type FieldList = Vec<FieldTypes>;
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum FieldTypes {
StructField(StructFieldDescriptor),
EnumVariant(String),
EnumUnnamedField((FieldType, UseAlternative)),
}
pub fn derive(input: TokenStream, is_zstd: bool) -> TokenStream {
let mut output = quote! {};
let DeriveInput { ident, data, generics, attrs, .. } = parse_macro_input!(input);
let has_lifetime = has_lifetime(&generics);
let fields = get_fields(&data);
output.extend(generate_flag_struct(&ident, has_lifetime, &fields, is_zstd));
output.extend(generate_from_to(&ident, &attrs, has_lifetime, &fields, is_zstd));
output.into()
}
pub fn has_lifetime(generics: &Generics) -> bool {
generics.lifetimes().next().is_some()
}
pub fn get_fields(data: &Data) -> FieldList {
let mut fields = vec![];
match data {
Data::Struct(data) => match data.fields {
syn::Fields::Named(ref data_fields) => {
for field in &data_fields.named {
load_field(field, &mut fields, false);
}
assert_eq!(fields.len(), data_fields.named.len(), "get_fields");
}
syn::Fields::Unnamed(ref data_fields) => {
assert_eq!(
data_fields.unnamed.len(),
1,
"Compact only allows one unnamed field. Consider making it a struct."
);
load_field(&data_fields.unnamed[0], &mut fields, false);
}
syn::Fields::Unit => todo!(),
},
Data::Enum(data) => {
for variant in &data.variants {
fields.push(FieldTypes::EnumVariant(variant.ident.to_string()));
match &variant.fields {
syn::Fields::Named(_) => {
panic!("Not allowed to have Enum Variants with multiple named fields. Make it a struct instead.")
}
syn::Fields::Unnamed(data_fields) => {
assert_eq!(
data_fields.unnamed.len(),
1,
"Compact only allows one unnamed field. Consider making it a struct."
);
load_field(&data_fields.unnamed[0], &mut fields, true);
}
syn::Fields::Unit => (),
}
}
}
Data::Union(_) => todo!(),
}
fields
}
fn load_field(field: &syn::Field, fields: &mut FieldList, is_enum: bool) {
match field.ty {
syn::Type::Reference(ref reference) => match &*reference.elem {
syn::Type::Path(path) => {
load_field_from_segments(&path.path.segments, is_enum, fields, field)
}
_ => unimplemented!("{:?}", &field.ident),
},
syn::Type::Path(ref path) => {
load_field_from_segments(&path.path.segments, is_enum, fields, field)
}
_ => unimplemented!("{:?}", &field.ident),
}
}
fn load_field_from_segments(
segments: &syn::punctuated::Punctuated<syn::PathSegment, syn::token::PathSep>,
is_enum: bool,
fields: &mut Vec<FieldTypes>,
field: &syn::Field,
) {
if !segments.is_empty() {
let mut ftype = String::new();
let mut use_alt_impl: UseAlternative = false;
for (index, segment) in segments.iter().enumerate() {
ftype.push_str(&segment.ident.to_string());
if index < segments.len() - 1 {
ftype.push_str("::");
}
use_alt_impl = should_use_alt_impl(&ftype, segment);
}
if is_enum {
fields.push(FieldTypes::EnumUnnamedField((ftype, use_alt_impl)));
} else {
let should_compact = is_flag_type(&ftype) ||
field.attrs.iter().any(|attr| {
attr.path().segments.iter().any(|path| path.ident == "maybe_zero")
});
fields.push(FieldTypes::StructField((
field.ident.as_ref().map(|i| i.to_string()).unwrap_or_default(),
ftype,
should_compact,
use_alt_impl,
)));
}
}
}
fn should_use_alt_impl(ftype: &str, segment: &syn::PathSegment) -> bool {
if ftype == "Vec" || ftype == "Option" {
if let syn::PathArguments::AngleBracketed(ref args) = segment.arguments {
if let Some(syn::GenericArgument::Type(syn::Type::Path(arg_path))) = args.args.last() {
if let (Some(path), 1) =
(arg_path.path.segments.first(), arg_path.path.segments.len())
{
if [
"B256",
"Address",
"Address",
"Bloom",
"TxHash",
"BlockHash",
"CompactPlaceholder",
]
.contains(&path.ident.to_string().as_str())
{
return true
}
}
}
}
}
false
}
pub fn get_bit_size(ftype: &str) -> u8 {
match ftype {
"TransactionKind" | "TxKind" | "bool" | "Option" | "Signature" => 1,
"TxType" => 2,
"u64" | "BlockNumber" | "TxNumber" | "ChainId" | "NumTransactions" => 4,
"u128" => 5,
"U256" => 6,
_ => 0,
}
}
pub fn is_flag_type(ftype: &str) -> bool {
get_bit_size(ftype) > 0
}
#[cfg(test)]
mod tests {
use super::*;
use similar_asserts::assert_eq;
use syn::parse2;
#[test]
fn gen() {
let f_struct = quote! {
#[derive(Debug, PartialEq, Clone)]
pub struct TestStruct {
f_u64: u64,
f_u256: U256,
f_bool_t: bool,
f_bool_f: bool,
f_option_none: Option<U256>,
f_option_some: Option<B256>,
f_option_some_u64: Option<u64>,
f_vec_empty: Vec<U256>,
f_vec_some: Vec<Address>,
}
};
let mut output = quote! {};
let DeriveInput { ident, data, attrs, .. } = parse2(f_struct).unwrap();
let fields = get_fields(&data);
output.extend(generate_flag_struct(&ident, false, &fields, false));
output.extend(generate_from_to(&ident, &attrs, false, &fields, false));
let should_output = quote! {
impl TestStruct {
#[doc = "Used bytes by [`TestStructFlags`]"]
pub const fn bitflag_encoded_bytes() -> usize {
2u8 as usize
}
#[doc = "Unused bits for new fields by [`TestStructFlags`]"]
pub const fn bitflag_unused_bits() -> usize {
1u8 as usize
}
}
pub use TestStruct_flags::TestStructFlags;
#[allow(non_snake_case)]
mod TestStruct_flags {
use bytes::Buf;
use modular_bitfield::prelude::*;
#[doc = "Fieldset that facilitates compacting the parent type. Used bytes: 2 | Unused bits: 1"]
#[bitfield]
#[derive(Clone, Copy, Debug, Default)]
pub struct TestStructFlags {
pub f_u64_len: B4,
pub f_u256_len: B6,
pub f_bool_t_len: B1,
pub f_bool_f_len: B1,
pub f_option_none_len: B1,
pub f_option_some_len: B1,
pub f_option_some_u64_len: B1,
#[skip]
unused: B1,
}
impl TestStructFlags {
#[doc = r" Deserializes this fieldset and returns it, alongside the original slice in an advanced position."]
pub fn from(mut buf: &[u8]) -> (Self, &[u8]) {
(
TestStructFlags::from_bytes([buf.get_u8(), buf.get_u8(),]),
buf
)
}
}
}
#[cfg(test)]
#[allow(dead_code)]
#[test_fuzz::test_fuzz]
fn fuzz_test_test_struct(obj: TestStruct) {
use reth_codecs::Compact;
let mut buf = vec![];
let len = obj.clone().to_compact(&mut buf);
let (same_obj, buf) = TestStruct::from_compact(buf.as_ref(), len);
assert_eq!(obj, same_obj);
}
#[test]
#[allow(missing_docs)]
pub fn fuzz_test_struct() {
fuzz_test_test_struct(TestStruct::default())
}
impl reth_codecs::Compact for TestStruct {
fn to_compact<B>(&self, buf: &mut B) -> usize where B: bytes::BufMut + AsMut<[u8]> {
let mut flags = TestStructFlags::default();
let mut total_length = 0;
let mut buffer = bytes::BytesMut::new();
let f_u64_len = self.f_u64.to_compact(&mut buffer);
flags.set_f_u64_len(f_u64_len as u8);
let f_u256_len = self.f_u256.to_compact(&mut buffer);
flags.set_f_u256_len(f_u256_len as u8);
let f_bool_t_len = self.f_bool_t.to_compact(&mut buffer);
flags.set_f_bool_t_len(f_bool_t_len as u8);
let f_bool_f_len = self.f_bool_f.to_compact(&mut buffer);
flags.set_f_bool_f_len(f_bool_f_len as u8);
let f_option_none_len = self.f_option_none.to_compact(&mut buffer);
flags.set_f_option_none_len(f_option_none_len as u8);
let f_option_some_len = self.f_option_some.specialized_to_compact(&mut buffer);
flags.set_f_option_some_len(f_option_some_len as u8);
let f_option_some_u64_len = self.f_option_some_u64.to_compact(&mut buffer);
flags.set_f_option_some_u64_len(f_option_some_u64_len as u8);
let f_vec_empty_len = self.f_vec_empty.to_compact(&mut buffer);
let f_vec_some_len = self.f_vec_some.specialized_to_compact(&mut buffer);
let flags = flags.into_bytes();
total_length += flags.len() + buffer.len();
buf.put_slice(&flags);
buf.put(buffer);
total_length
}
fn from_compact(mut buf: &[u8], len: usize) -> (Self, &[u8]) {
let (flags, mut buf) = TestStructFlags::from(buf);
let (f_u64, new_buf) = u64::from_compact(buf, flags.f_u64_len() as usize);
buf = new_buf;
let (f_u256, new_buf) = U256::from_compact(buf, flags.f_u256_len() as usize);
buf = new_buf;
let (f_bool_t, new_buf) = bool::from_compact(buf, flags.f_bool_t_len() as usize);
buf = new_buf;
let (f_bool_f, new_buf) = bool::from_compact(buf, flags.f_bool_f_len() as usize);
buf = new_buf;
let (f_option_none, new_buf) = Option::from_compact(buf, flags.f_option_none_len() as usize);
buf = new_buf;
let (f_option_some, new_buf) = Option::specialized_from_compact(buf, flags.f_option_some_len() as usize);
buf = new_buf;
let (f_option_some_u64, new_buf) = Option::from_compact(buf, flags.f_option_some_u64_len() as usize);
buf = new_buf;
let (f_vec_empty, new_buf) = Vec::from_compact(buf, buf.len());
buf = new_buf;
let (f_vec_some, new_buf) = Vec::specialized_from_compact(buf, buf.len());
buf = new_buf;
let obj = TestStruct {
f_u64: f_u64,
f_u256: f_u256,
f_bool_t: f_bool_t,
f_bool_f: f_bool_f,
f_option_none: f_option_none,
f_option_some: f_option_some,
f_option_some_u64: f_option_some_u64,
f_vec_empty: f_vec_empty,
f_vec_some: f_vec_some,
};
(obj, buf)
}
}
};
assert_eq!(
syn::parse2::<syn::File>(output).unwrap(),
syn::parse2::<syn::File>(should_output).unwrap()
);
}
}