byten_derive 0.0.13

Procedural macros for deriving binary codec traits (internal use by byten)
Documentation
use proc_macro2::Span;
use quote::{ToTokens, quote};
use syn::{Fields, FieldsNamed, Ident};

use crate::build_codec_schema;

use super::{BinarySchema, DecodeContext, EncodeContext, MeasureContext};

pub trait FieldsSchema: BinarySchema {
    fn wildcard_pattern(&self) -> proc_macro2::TokenStream;
}

pub fn interpret_fields_schema(fields: &Fields) -> syn::Result<Box<dyn FieldsSchema>> {
    Ok(match fields {
        Fields::Named(fields) => Box::new(NamedFieldsSchema::interpret(fields)?),
        Fields::Unnamed(fields) => Box::new(UnnamedFieldsSchema::interpret(fields)?),
        Fields::Unit => Box::new(UnitFieldsSchema {}),
    })
}

struct NamedFieldsSchema {
    fields: Vec<(Ident, Box<dyn BinarySchema>)>,
}

impl FieldsSchema for NamedFieldsSchema {
    fn wildcard_pattern(&self) -> proc_macro2::TokenStream {
        quote! { { .. } }
    }
}

impl NamedFieldsSchema {
    fn interpret(fields: &FieldsNamed) -> syn::Result<NamedFieldsSchema> {
        let fields = fields
            .named
            .iter()
            .map(|field| {
                let ident = field.ident.clone().ok_or_else(|| {
                    syn::Error::new_spanned(field, "Named field must have an identifier")
                })?;
                let codec = build_codec_schema(&field.attrs, Some(&field.ty))?;
                Ok((ident, codec))
            })
            .collect::<syn::Result<Vec<_>>>()?;
        Ok(NamedFieldsSchema { fields })
    }
}

impl BinarySchema for NamedFieldsSchema {
    fn decode(&self, ctx: &DecodeContext) -> syn::Result<proc_macro2::TokenStream> {
        let fields = self
            .fields
            .iter()
            .map(|(ident, schema)| {
                let decode = schema.decode(&ctx.clone())?;
                Ok(quote! { #ident: #decode })
            })
            .collect::<syn::Result<Vec<_>>>()?;
        Ok(quote! { { #(#fields),* } })
    }

    fn encode(&self, ctx: &EncodeContext) -> syn::Result<proc_macro2::TokenStream> {
        let wrapper = &ctx.decoded;
        let type_path = &ctx.wrapper;
        let idents = self
            .fields
            .iter()
            .map(|(ident, _)| ident)
            .collect::<Vec<_>>();
        let variables = idents
            .iter()
            .map(|ident| Ident::new(format!("variant_{}", ident).as_str(), ident.span()))
            .collect::<Vec<_>>();
        let encodes = self
            .fields
            .iter()
            .zip(variables.iter())
            .map(|((_, schema), variable)| {
                schema.encode(&EncodeContext {
                    wrapper: quote! {},
                    decoded: variable.into_token_stream(),
                    encoded: ctx.encoded.clone(),
                    offset: ctx.offset.clone(),
                })
            })
            .collect::<syn::Result<Vec<_>>>()?;
        Ok(quote! {
            let #type_path { #(#idents: #variables,)* } = #wrapper else { unreachable!() };
            #(#encodes;)*
        })
    }

    fn measure(&self, ctx: &MeasureContext) -> syn::Result<proc_macro2::TokenStream> {
        let wrapper = &ctx.decoded;
        let type_path = &ctx.wrapper;
        let idents = self
            .fields
            .iter()
            .map(|(ident, _)| ident)
            .collect::<Vec<_>>();
        let variables = idents
            .iter()
            .map(|ident| Ident::new(format!("variant_{}", ident).as_str(), ident.span()))
            .collect::<Vec<_>>();
        let measures = self
            .fields
            .iter()
            .zip(variables.iter())
            .map(|((_, schema), variable)| {
                schema.measure(&MeasureContext {
                    wrapper: quote! {},
                    decoded: variable.into_token_stream(),
                })
            })
            .collect::<syn::Result<Vec<_>>>()?;
        Ok(quote! { {
            let #type_path { #(#idents: #variables,)* } = #wrapper else { unreachable!() };
            0 #( + #measures )*
        } })
    }

    fn measure_fixed(&self) -> syn::Result<proc_macro2::TokenStream> {
        let measures = self
            .fields
            .iter()
            .map(|(_, schema)| schema.measure_fixed())
            .collect::<syn::Result<Vec<_>>>()?;
        Ok(quote! {
            0 #( + #measures )*
        })
    }
}

struct UnnamedFieldsSchema {
    fields: Vec<Box<dyn BinarySchema>>,
}

impl FieldsSchema for UnnamedFieldsSchema {
    fn wildcard_pattern(&self) -> proc_macro2::TokenStream {
        quote! { ( .. ) }
    }
}

impl UnnamedFieldsSchema {
    fn interpret(fields: &syn::FieldsUnnamed) -> syn::Result<UnnamedFieldsSchema> {
        let fields = fields
            .unnamed
            .iter()
            .map(|field| {
                if let Some(ident) = &field.ident {
                    return Err(syn::Error::new_spanned(
                        ident,
                        "Unnamed field must not have an identifier",
                    ));
                }
                build_codec_schema(&field.attrs, Some(&field.ty))
            })
            .collect::<syn::Result<Vec<_>>>()?;
        Ok(UnnamedFieldsSchema { fields })
    }
}

impl BinarySchema for UnnamedFieldsSchema {
    fn decode(&self, ctx: &DecodeContext) -> syn::Result<proc_macro2::TokenStream> {
        let fields = self
            .fields
            .iter()
            .map(|schema| schema.decode(&ctx.clone()))
            .collect::<syn::Result<Vec<_>>>()?;
        Ok(quote! { ( #(#fields),* ) })
    }

    fn encode(&self, ctx: &EncodeContext) -> syn::Result<proc_macro2::TokenStream> {
        let decoded = &ctx.decoded;
        let wrapper = &ctx.wrapper;
        let variables = self
            .fields
            .iter()
            .enumerate()
            .map(|(index, _)| Ident::new(format!("variant_{}", index).as_str(), Span::call_site()))
            .collect::<Vec<_>>();
        let encodes = self
            .fields
            .iter()
            .zip(variables.iter())
            .map(|(schema, variable)| {
                schema.encode(&EncodeContext {
                    wrapper: quote! {},
                    decoded: variable.into_token_stream(),
                    encoded: ctx.encoded.clone(),
                    offset: ctx.offset.clone(),
                })
            })
            .collect::<syn::Result<Vec<_>>>()?;
        Ok(quote! {
            let #wrapper ( #(#variables),* ) = #decoded else { unreachable!() };
            #(#encodes;)*
        })
    }

    fn measure(&self, ctx: &MeasureContext) -> syn::Result<proc_macro2::TokenStream> {
        let decoded = &ctx.decoded;
        let wrapper = &ctx.wrapper;
        let variables = self
            .fields
            .iter()
            .enumerate()
            .map(|(index, _)| Ident::new(format!("variant_{}", index).as_str(), Span::call_site()))
            .collect::<Vec<_>>();
        let measures = self
            .fields
            .iter()
            .zip(variables.iter())
            .map(|(schema, variable)| {
                schema.measure(&MeasureContext {
                    wrapper: quote! {},
                    decoded: variable.into_token_stream(),
                })
            })
            .collect::<syn::Result<Vec<_>>>()?;
        Ok(quote! { {
            let #wrapper ( #(#variables),* ) = #decoded else { unreachable!() };
            0 #( + #measures )*
        } })
    }

    fn measure_fixed(&self) -> syn::Result<proc_macro2::TokenStream> {
        let measures = self
            .fields
            .iter()
            .map(|schema| schema.measure_fixed())
            .collect::<syn::Result<Vec<_>>>()?;
        Ok(quote! {
            0 #( + #measures )*
        })
    }
}

struct UnitFieldsSchema {}

impl FieldsSchema for UnitFieldsSchema {
    fn wildcard_pattern(&self) -> proc_macro2::TokenStream {
        quote! {}
    }
}

impl BinarySchema for UnitFieldsSchema {
    fn decode(&self, _ctx: &DecodeContext) -> syn::Result<proc_macro2::TokenStream> {
        Ok(quote! {})
    }

    fn encode(&self, _ctx: &EncodeContext) -> syn::Result<proc_macro2::TokenStream> {
        Ok(quote! {})
    }

    fn measure(&self, _ctx: &MeasureContext) -> syn::Result<proc_macro2::TokenStream> {
        Ok(quote! { 0 })
    }

    fn measure_fixed(&self) -> syn::Result<proc_macro2::TokenStream> {
        Ok(quote! { 0 })
    }
}