auto_enums_core 0.1.2

This library provides an attribute macro for to allow multiple return types by automatically generated enum.
Documentation
use std::cell::RefCell;

use proc_macro2::Ident;
use quote::quote;
use rand::{rngs::ThreadRng, Rng};
use smallvec::{smallvec, SmallVec};
use syn::*;

use crate::utils::{Result, *};

use super::*;

const DERIVE_ATTR: &str = "enum_derive";

thread_local! {
    static RNG: RefCell<ThreadRng> = RefCell::new(rand::thread_rng());
}

pub(super) type Builder = EnumBuilder;

struct EnumVariant(String);

pub(super) struct EnumBuilder {
    ident: String,
    variants: Stack<EnumVariant>,
    next: usize,
}

impl EnumVariant {
    fn new(id: usize) -> Self {
        EnumVariant(format!("__T{}", id))
    }

    fn ident(&self) -> Ident {
        ident_call_site(&self.0)
    }

    fn path(&self, ident: &str) -> Path {
        let segments: SmallVec<[_; 2]> =
            smallvec![ident_call_site(ident).into(), self.ident().into()];
        path(segments.into_iter())
    }

    fn expr_call(&self, ident: &str, attrs: Vec<Attribute>, expr: Expr) -> Expr {
        expr_call(attrs, self.path(ident), expr)
    }
}

impl EnumBuilder {
    pub(super) fn new() -> Self {
        EnumBuilder {
            ident: format!("__E{}", RNG.with(|rng| rng.borrow_mut().gen::<u32>())),
            variants: Stack::new(),
            next: 0,
        }
    }

    fn len(&self) -> usize {
        self.variants.len()
    }

    pub(super) fn is_empty(&self) -> bool {
        self.variants.is_empty()
    }

    fn iter(&self) -> impl Iterator<Item = Ident> + '_ {
        self.variants.iter().map(|f| f.ident())
    }

    fn push_variant(&mut self) {
        let field = EnumVariant::new(self.len());
        self.variants.push(field);
    }

    pub(super) fn reserve(&mut self, additional: usize) {
        (0..additional).for_each(|_| self.push_variant());
    }

    fn get_expr_call(&self, index: usize, attrs: Vec<Attribute>, expr: Expr) -> Expr {
        self.variants[index].expr_call(&self.ident, attrs, expr)
    }

    pub(super) fn next_expr_call(&mut self, attrs: Vec<Attribute>, expr: Expr) -> Expr {
        assert!(self.next <= self.len());

        if self.next == self.len() {
            self.push_variant();
        }

        let expr_call = self.get_expr_call(self.next, attrs, expr);
        self.next += 1;
        expr_call
    }

    pub(super) fn build(&self, args: &[Arg]) -> Result<ItemEnum> {
        if self.len() < 2 {
            Err("macro is required two or more branches or marker macros in total")?;
        }

        let attr = ident_call_site(DERIVE_ATTR);
        let ident = ident_call_site(&self.ident);
        let ty_generics = self.iter();
        let variants = self.iter();
        let fields = self.iter();

        syn::parse2(quote! {
            #[#attr(#(#args),*)]
            enum #ident<#(#ty_generics),*> {
                #(#variants(#fields),)*
            }
        })
        .map_err(|_| "failed generate an enum".into())
    }
}