bauer-macros 0.5.0

Macro implementation for the `bauer` crate. This should only be accessed through the `bauer` crate.
Documentation
use std::str::FromStr;

use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use strum::{AsRefStr, IntoStaticStr, VariantArray};
use syn::{
    Ident, ItemConst, LitStr, Token, Type, Visibility,
    ext::IdentExt,
    parse::{Parse, ParseStream},
    parse_quote,
};

use crate::{
    attr::{build_fn::BuildFnAttr, error::ErrorAttr},
    util::{
        OptionalToken,
        parse::{parethesised_or_braced, parse_attributes, parse_docs},
    },
};

macro_rules! bail {
    ($span: expr => $message: literal $(, $args: expr)*$(,)?) => {
        return Err(syn::Error::new(
            $span,
            format!($message, $($args),*),
        ))
    }
}

#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum Kind {
    #[default]
    Owned,
    Borrowed,
    TypeState,
}

impl FromStr for Kind {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "owned" => Ok(Self::Owned),
            "borrowed" => Ok(Self::Borrowed),
            "type-state" => Ok(Self::TypeState),
            _ => Err(format!(
                "Unknown kind \"{}\".  Valid kinds are: \"owned\", \"borrowed\", \"type-state\"",
                s
            )),
        }
    }
}

impl Parse for Kind {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let s: LitStr = input.parse()?;
        match Kind::from_str(&s.value()) {
            Ok(v) => Ok(v),
            Err(e) => Err(syn::Error::new(s.span(), e)),
        }
    }
}

#[derive(Clone, Debug)]
pub(crate) struct On {
    pattern: Type,
    attributes: TokenStream,
}

impl On {
    pub fn apply(&self, field_ty: &Type) -> syn::Result<Option<TokenStream>> {
        use crate::util::pattern::{pattern_match_type, replace};

        pattern_match_type(&self.pattern, field_ty)
            .map(|matches| replace(&matches, self.attributes.clone()))
            .transpose()
    }
}

impl Parse for On {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let pattern: Type = input.parse()?;
        let _arrow: Token![=>] = input.parse()?;
        let attributes: TokenStream = input.parse()?;
        Ok(Self {
            pattern,
            attributes,
        })
    }
}

#[derive(Clone, Copy, VariantArray, IntoStaticStr, AsRefStr, Debug, PartialEq, Eq)]
#[strum(serialize_all = "snake_case")]
#[repr(usize)]
enum Attribute {
    Kind = 0,
    Prefix,
    Suffix,
    Visibility,
    Crate,
    Const,
    #[allow(clippy::enum_variant_names)]
    Attributes,
    Doc,
    BuildFn,
    BuilderFn,
    Error,
    On,
}

impl Attribute {
    #[inline]
    const fn single_use(&self) -> bool {
        match self {
            Attribute::Kind => true,
            Attribute::Prefix => true,
            Attribute::Suffix => true,
            Attribute::Visibility => true,
            Attribute::Crate => true,
            Attribute::Const => true,
            Attribute::Attributes => false,
            Attribute::Doc => false,
            Attribute::BuildFn => true,
            Attribute::BuilderFn => true,
            Attribute::Error => true,
            Attribute::On => false,
        }
    }

    fn matches(self, ident: &Ident) -> bool {
        if ident == self.as_ref() {
            return true;
        }

        match self {
            Self::Attributes => ident == "attribute",
            _ => false,
        }
    }

    fn parse(ident: &Ident) -> syn::Result<Self> {
        Self::VARIANTS
            .iter()
            .copied()
            .find(|e| e.matches(ident))
            .ok_or_else(|| {
                syn::Error::new(
                    ident.span(),
                    format!(
                        "Unknown attribute '{}'.  Valid attribute are: '{}'",
                        ident,
                        Self::VARIANTS
                            .iter()
                            .map(<&str>::from)
                            .collect::<Vec<_>>()
                            .join(", ")
                    ),
                )
            })
    }
}

#[derive(Debug, Clone)]
pub struct BuilderAttr {
    pub kind: Kind,
    pub prefix: String,
    pub suffix: String,
    pub vis: Visibility,
    pub krate: Ident,
    pub konst: bool,
    pub attributes: Vec<syn::Attribute>,
    pub build_fn: BuildFnAttr,
    pub builder_fn: BuildFnAttr,
    pub error: ErrorAttr,
    pub on: Vec<On>,
    pub set_fields: [bool; const { Attribute::VARIANTS.len() }],
}

impl BuilderAttr {
    pub fn new(vis: Visibility) -> Self {
        Self {
            kind: Default::default(),
            prefix: Default::default(),
            suffix: Default::default(),
            vis,
            krate: format_ident!("bauer"),
            konst: false,
            attributes: Default::default(),
            build_fn: BuildFnAttr::default_build(),
            builder_fn: BuildFnAttr::default_builder(),
            error: Default::default(),
            on: Default::default(),
            set_fields: Default::default(),
        }
    }

    pub fn assert_crate(&self) -> ItemConst {
        let private_module = self.private_module();
        parse_quote! {
            const _: () = {
                #[allow(unused)]
                pub use #private_module as _;
            };
        }
    }

    pub fn private_module(&self) -> syn::Path {
        let krate = &self.krate;
        parse_quote! { ::#krate::__private }
    }

    pub fn konst_kw(&self) -> OptionalToken<Token![const]> {
        if self.konst {
            OptionalToken(Some(<Token![const]>::default()))
        } else {
            OptionalToken(None)
        }
    }

    pub fn self_param(&self) -> TokenStream {
        match self.kind {
            Kind::Owned => quote! { mut self },
            Kind::Borrowed => quote! { &mut self },
            Kind::TypeState => quote! { self },
        }
    }

    pub fn return_type(&self) -> TokenStream {
        match self.kind {
            Kind::Owned => quote! { Self },
            Kind::Borrowed => quote! { &mut Self },
            Kind::TypeState => {
                panic!("This function should not be called on Kind::TypeState")
            }
        }
    }

    pub fn parse(&mut self, input: ParseStream) -> syn::Result<()> {
        while input.peek(Ident::peek_any) {
            let ident = Ident::parse_any(input)?;
            let attr = Attribute::parse(&ident)?;

            if self.set_fields[attr as usize] && attr.single_use() {
                bail!(ident.span() => "`{}` may only be used once", <&str>::from(attr));
            }
            self.set_fields[attr as usize] = true;

            match Attribute::parse(&ident)? {
                Attribute::Kind => {
                    let _: Token![=] = input.parse()?;
                    self.kind = input.parse()?;
                }
                Attribute::Prefix => {
                    let _: Token![=] = input.parse()?;
                    self.prefix = input.parse::<LitStr>()?.value();
                }
                Attribute::Suffix => {
                    let _: Token![=] = input.parse()?;
                    self.suffix = input.parse::<LitStr>()?.value();
                }
                Attribute::Visibility => {
                    let _: Token![=] = input.parse()?;
                    self.vis = input.parse()?;
                }
                Attribute::Crate => {
                    let _: Token![=] = input.parse()?;
                    self.krate = input.parse()?;
                }
                Attribute::Const => {
                    self.konst = true;
                }
                Attribute::Attributes => {
                    let attrs = parethesised_or_braced(input)?;

                    if !attrs.is_empty() {
                        parse_attributes(&attrs, &mut self.attributes)?;
                    }
                }
                Attribute::Doc => {
                    let attrs = parethesised_or_braced(input)?;

                    if !attrs.is_empty() {
                        parse_docs(&attrs, ident.span(), &mut self.attributes)?;
                    }
                }
                Attribute::BuildFn => {
                    let build_fn = parethesised_or_braced(input)?;
                    self.build_fn.parse(&build_fn)?;
                }
                Attribute::BuilderFn => {
                    let builder_fn = parethesised_or_braced(input)?;
                    self.builder_fn.parse(&builder_fn)?;
                }
                Attribute::Error => {
                    let error = parethesised_or_braced(input)?;
                    self.error = ErrorAttr::parse(&error)?;
                }
                Attribute::On => {
                    let inner = parethesised_or_braced(input)?;
                    self.on.push(inner.parse()?);
                }
            }

            if input.peek(Token![,]) {
                let _: Token![,] = input.parse()?;
            } else {
                break;
            }
        }

        Ok(())
    }
}