bin-proto-derive 0.12.3

Derive macros for bin-proto
Documentation
use proc_macro2::{Span, TokenStream};
use std::fmt;
use syn::{parenthesized, punctuated::Punctuated, Error, Result, Token};

#[allow(clippy::struct_excessive_bools)]
#[derive(Default)]
pub struct Attrs {
    pub bits: Option<syn::Expr>,
    pub ctx: Option<Ctx>,
    pub ctx_generics: Option<Vec<syn::GenericParam>>,
    pub skip_encode: bool,
    pub skip_decode: bool,
    pub discriminant: Option<syn::Expr>,
    pub discriminant_type: Option<syn::Type>,
    pub untagged: bool,
    pub magic: Option<syn::Expr>,
    pub pad_after: Option<syn::Expr>,
    pub pad_before: Option<syn::Expr>,
    pub tag: Option<Tag>,
    pub write_value: Option<syn::Expr>,
    pub other: bool,
    pub crate_path: Option<TokenStream>,
}

pub enum Ctx {
    Concrete(syn::Type),
    Bounds(Vec<syn::TypeParamBound>),
}

#[allow(clippy::large_enum_variant)]
pub enum Tag {
    External(syn::Expr),
    Prepend {
        typ: syn::Type,
        write_value: Option<syn::Expr>,
        bits: Option<syn::Expr>,
    },
}

#[derive(Clone, Copy, PartialEq, Eq)]
pub enum AttrKind {
    Enum,
    Struct,
    Variant,
    Field,
}

impl fmt::Display for AttrKind {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Enum => write!(f, "enum"),
            Self::Struct => write!(f, "struct"),
            Self::Variant => write!(f, "variant"),
            Self::Field => write!(f, "field"),
        }
    }
}

macro_rules! expect_attr_kind {
    ($pat:pat, $kind:expr, $meta:expr) => {
        if let Some(kind) = $kind {
            if !matches!(kind, $pat) {
                return Err($meta.error(format!(
                    "attribute '{}' cannot be applied to {}",
                    $meta.path.require_ident()?,
                    kind
                )));
            }
        }
    };
}

impl Attrs {
    pub fn ctx_ty(&self) -> TokenStream {
        if let Some(Ctx::Concrete(ctx)) = &self.ctx {
            quote!(#ctx)
        } else {
            quote!(__Ctx)
        }
    }

    pub fn decode_magic(&self) -> TokenStream {
        if let Some(magic) = &self.magic {
            let crate_path = self.crate_path();
            quote!(
                let magic: [u8; (#magic).len()] = #crate_path::BitDecode::decode::<_, __E>(
                    __io_reader,
                    __ctx,
                    ()
                )?;
                if magic != *(#magic) {
                    return ::core::result::Result::Err(#crate_path::Error::Magic(#magic));
                }
            )
        } else {
            TokenStream::new()
        }
    }

    pub fn encode_magic(&self) -> TokenStream {
        if let Some(magic) = &self.magic {
            let crate_path = self.crate_path();
            quote!(#crate_path::BitEncode::encode::<_, __E>(#magic, __io_writer, __ctx, ())?;)
        } else {
            TokenStream::new()
        }
    }

    pub fn crate_path(&self) -> TokenStream {
        if let Some(path) = &self.crate_path {
            quote!(#path)
        } else {
            quote!(::bin_proto)
        }
    }

    #[allow(clippy::too_many_lines, clippy::cognitive_complexity)]
    pub fn parse(attribs: &[syn::Attribute], kind: Option<AttrKind>, span: Span) -> Result<Self> {
        let mut attrs = Self::default();

        let mut tag = None;
        let mut tag_type = None;
        let mut tag_value = None;
        let mut tag_bits = None;

        let mut ctx = None;
        let mut ctx_bounds = None;

        for attr in attribs {
            if attr.path().is_ident("bin_proto") {
                attr.parse_nested_meta(|meta| {
                    let Some(ident) = meta.path.get_ident() else {
                        return Err(meta.error("unrecognized attribute"));
                    };

                    match ident.to_string().as_str() {
                        "untagged" => {
                            expect_attr_kind!(AttrKind::Field, kind, meta);
                            attrs.untagged = true;
                        }
                        "discriminant_type" => {
                            expect_attr_kind!(AttrKind::Enum, kind, meta);
                            attrs.discriminant_type = Some(meta.value()?.parse()?);
                        }
                        "discriminant" => {
                            expect_attr_kind!(AttrKind::Variant, kind, meta);
                            attrs.discriminant = Some(meta.value()?.parse()?);
                        }
                        "ctx" => {
                            expect_attr_kind!(AttrKind::Enum | AttrKind::Struct, kind, meta);
                            ctx = Some(meta.value()?.parse()?);
                        }
                        "ctx_generics" => {
                            expect_attr_kind!(AttrKind::Enum | AttrKind::Struct, kind, meta);
                            let content;
                            parenthesized!(content in meta.input);
                            attrs.ctx_generics = Some(
                            Punctuated::<syn::GenericParam, Token![,]>::parse_separated_nonempty(
                                &content,
                            )?
                            .into_iter()
                            .collect(),
                        );
                        }
                        "ctx_bounds" => {
                            expect_attr_kind!(AttrKind::Enum | AttrKind::Struct, kind, meta);
                            let content;
                            parenthesized!(content in meta.input);
                            ctx_bounds = Some(
                            Punctuated::<syn::TypeParamBound, Token![,]>::parse_separated_nonempty(
                                &content,
                            )?
                            .into_iter()
                            .collect(),
                        );
                        }
                        "bits" => {
                            expect_attr_kind!(AttrKind::Enum | AttrKind::Field, kind, meta);
                            attrs.bits = Some(meta.value()?.parse()?);
                        }
                        "write_value" => {
                            expect_attr_kind!(AttrKind::Field, kind, meta);
                            attrs.write_value = Some(meta.value()?.parse()?);
                        }
                        "tag" => {
                            expect_attr_kind!(AttrKind::Field, kind, meta);
                            tag = Some(meta.value()?.parse()?);
                        }
                        "tag_type" => {
                            expect_attr_kind!(AttrKind::Field, kind, meta);
                            tag_type = Some(meta.value()?.parse()?);
                        }
                        "tag_value" => {
                            expect_attr_kind!(AttrKind::Field, kind, meta);
                            tag_value = Some(meta.value()?.parse()?);
                        }
                        "tag_bits" => {
                            expect_attr_kind!(AttrKind::Field, kind, meta);
                            tag_bits = Some(meta.value()?.parse()?);
                        }
                        "skip_encode" => {
                            expect_attr_kind!(AttrKind::Field | AttrKind::Variant, kind, meta);
                            attrs.skip_encode = true;
                        }
                        "skip_decode" => {
                            expect_attr_kind!(AttrKind::Field | AttrKind::Variant, kind, meta);
                            attrs.skip_decode = true;
                        }
                        "skip" => {
                            expect_attr_kind!(AttrKind::Field | AttrKind::Variant, kind, meta);
                            attrs.skip_encode = true;
                            attrs.skip_decode = true;
                        }
                        "pad_before" => {
                            expect_attr_kind!(AttrKind::Struct | AttrKind::Field, kind, meta);
                            attrs.pad_before = Some(meta.value()?.parse()?);
                        }
                        "pad_after" => {
                            expect_attr_kind!(AttrKind::Struct | AttrKind::Field, kind, meta);
                            attrs.pad_after = Some(meta.value()?.parse()?);
                        }
                        "magic" => {
                            expect_attr_kind!(AttrKind::Struct | AttrKind::Field, kind, meta);
                            attrs.magic = Some(meta.value()?.parse()?);
                        }
                        "other" => {
                            expect_attr_kind!(AttrKind::Variant, kind, meta);
                            attrs.other = true;
                        }
                        "crate" => {
                            expect_attr_kind!(AttrKind::Enum | AttrKind::Struct, kind, meta);
                            attrs.crate_path = Some(meta.value()?.parse()?);
                        }
                        _ => {
                            return Err(meta.error("unrecognized attribute"));
                        }
                    }

                    Ok(())
                })?;
            } else if attr.path().is_ident("repr")
                && kind.is_none_or(|kind| kind == AttrKind::Enum)
                && attrs.discriminant_type.is_none()
            {
                attrs.discriminant_type = Some(attr.parse_args()?);
            }
        }

        match (tag, tag_type, tag_value, tag_bits) {
            (Some(tag), None, None, None) => attrs.tag = Some(Tag::External(tag)),
            (None, Some(tag_type), tag_value, tag_bits) => {
                attrs.tag = Some(Tag::Prepend {
                    typ: tag_type,
                    write_value: tag_value,
                    bits: tag_bits,
                });
            }
            (None, None, None, None) => {}
            _ => {
                return Err(Error::new(
                    span,
                    "invalid configuration of 'tag', 'tag_type', or 'tag_value' attributes.",
                ));
            }
        }

        match (ctx, ctx_bounds) {
            (Some(ctx), None) => attrs.ctx = Some(Ctx::Concrete(ctx)),
            (None, Some(ctx_bounds)) => attrs.ctx = Some(Ctx::Bounds(ctx_bounds)),
            (None, None) => {}
            _ => {
                return Err(Error::new(
                    span,
                    "use of mutually exclusive 'ctx' and 'ctx_bounds' attributes.",
                ));
            }
        }

        if [attrs.bits.is_some(), attrs.untagged, attrs.tag.is_some()]
            .iter()
            .filter(|b| **b)
            .count()
            > 1
        {
            return Err(Error::new(
                span,
                "bits, untagged, and tag are mutually-exclusive attributes",
            ));
        }

        Ok(attrs)
    }
}