solid-derive 0.1.3

Macros for the `solid` crate. Not intended to be used directly.
Documentation
use super::Solidity;
use proc_macro2::{
    Literal,
    TokenStream,
};
use syn::{
    Data,
    DeriveInput,
    Fields,
};

pub(super) fn impl_encode(ast: &DeriveInput) -> TokenStream {
    let ident = &ast.ident;

    let mut has_name = true;
    let mut name = Literal::string(ident.to_string().as_str());
    for attr in &ast.attrs {
        if let Some(ident) = &attr.path.get_ident() {
            if ident.to_string() == "solid" {
                let attribute = attr.parse_args::<Solidity>().unwrap();
                match attribute.ident.to_string().as_str() {
                    "constructor" => {
                        has_name = false;
                    }

                    "rename" => {
                        name = attribute.name.unwrap();
                    }

                    attribute => panic!("Unsupported key for solidity attribute: {:?}. Supported attribute keys are `rename` and `constructor`", attribute),
                }
            }
        }
    }

    let (impl_generics, ty_generics, where_clause) = &ast.generics.split_for_impl();

    let fields = match &ast.data {
        Data::Struct(data) => match &data.fields {
            Fields::Named(fields) => &fields.named,
            Fields::Unnamed(fields) => &fields.unnamed,
            Fields::Unit => todo!(),
        },

        _ => todo!(),
    };

    let count = fields.iter().count();

    let field = fields.iter().map(|field| field.ident.clone());

    let ty1 = fields.iter().map(|field| field.ty.clone());
    let ty2 = fields.iter().map(|field| field.ty.clone());
    let ty3 = fields.iter().map(|field| field.ty.clone());
    let ty4 = fields.iter().map(|field| field.ty.clone());

    let encode = quote! {
        fn encode(&self) -> Vec<u8> {
            let name_offset: usize = if #has_name {
                4
            } else {
                0
            };

            let len = self.required_len() + name_offset as u64;

            let mut buf = vec![0u8; len as usize];

            let mut offset = (#count * 32) as usize + name_offset;
            let mut index = 0usize;

            if #has_name {
                let mut selector = solid::Selector::new();
                #(
                    selector = selector.push::<#ty1>();
                )*

                buf[0..4].copy_from_slice(&selector.build(#name));
            }

            #(
                let bytes = self.#field.encode();
                if <#ty2 as solid::encode::Encode>::is_dynamic() {
                    buf[index * 32 + 24..(index + 1) * 32].copy_from_slice(&(offset as u64).to_be_bytes());
                    buf[offset..offset + bytes.len()].copy_from_slice(&bytes);
                    offset += bytes.len();
                } else {
                    buf[index * 32 + name_offset..(index + 1) * 32 + name_offset].copy_from_slice(&bytes);
                }
                index += 1;
            )*

            buf
        }
    };

    let field = fields.iter().map(|field| field.ident.clone());

    let required_len = quote! {
        fn required_len(&self) -> u64 {
            let mut len = 0u64;

            #(
                len += if <#ty3 as solid::encode::Encode>::is_dynamic() {
                    32 + self.#field.required_len()
                } else {
                    32
                };
            )*

            len
        }
    };

    let is_dynamic = quote! {
        fn is_dynamic() -> bool {
            true
        }
    };

    let into_type = quote! {
        fn into_type() -> std::borrow::Cow::<'static, str> {
            let mut ty = Vec::new();
            #(
                ty.push(<#ty4 as solid::into_type::IntoType>::into_type());
            )*

            std::borrow::Cow::Owned(format!("({})", ty.join(",")))
        }
    };

    quote! {
        impl #impl_generics solid::encode::Encode for #ident #ty_generics #where_clause {
            #encode

            #required_len

            #is_dynamic
        }

        impl #impl_generics solid::into_type::IntoType for #ident #ty_generics #where_clause {
            #into_type
        }
    }
}