matched_enums_macro 1.3.0

Contains the macro for matchable enums.
Documentation
extern crate alloc;

use alloc::{vec, vec::Vec};

use quote::quote;
use syn::{
    Expr, ExprLit, Lit, MetaNameValue, Token, Type,
    parse::{Parse, ParseStream},
    punctuated::Punctuated,
    spanned::Spanned,
};

use crate::utils::try_convert_type_expr_into_type;

pub struct TopLevelAttribute {
    /// Flag that a runtime-matchable type should be created.
    pub allow_runtime_configurable: bool,

    /// Indicates whether the compile-time matchers may have gaps.
    /// This implements `try_from` over `from` and returns an error if none
    /// of the match predicates pass.
    pub use_partial_matching: bool,

    /// Datatype from which an enumartion can be constructed.
    pub value_types: Vec<Type>,
}

impl TopLevelAttribute {
    fn set_allow_runtime_configurable(&mut self, expr: &Expr) -> syn::Result<()> {
        self.allow_runtime_configurable = match expr {
            Expr::Lit(ExprLit {
                lit: Lit::Bool(val),
                ..
            }) => val.value(),
            _ => return Err(syn::Error::new(expr.span(), "Invalid expression")),
        };

        Ok(())
    }

    fn set_use_partial_matching(&mut self, expr: &Expr) -> syn::Result<()> {
        self.use_partial_matching = match expr {
            Expr::Lit(ExprLit {
                lit: Lit::Bool(val),
                ..
            }) => val.value(),
            _ => return Err(syn::Error::new(expr.span(), "Invalid expression")),
        };

        Ok(())
    }

    fn set_value_types(&mut self, expr: &Expr) -> syn::Result<()> {
        self.value_types = match expr {
            Expr::Array(arr) => {
                let mut types: Vec<Type> = Vec::with_capacity(arr.elems.len());
                for expr in &arr.elems {
                    types.push(try_convert_type_expr_into_type(expr)?);
                }

                types
            }

            _ => vec![try_convert_type_expr_into_type(expr)?],
        };

        Ok(())
    }
}

impl Parse for TopLevelAttribute {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let mut enum_attr = Self::default();

        let name_values = Punctuated::<MetaNameValue, Token![,]>::parse_terminated(input)?;

        for MetaNameValue {
            ref path,
            ref value,
            ..
        } in name_values
        {
            if path.is_ident("allow_runtime_configurable") {
                enum_attr.set_allow_runtime_configurable(value)?;
            } else if path.is_ident("value_type") || path.is_ident("value_types") {
                // `value_type` is still present for backwards compat.
                enum_attr.set_value_types(value)?;
            } else if path.is_ident("use_partial") {
                enum_attr.set_use_partial_matching(value)?;
            } else {
                return Err(syn::Error::new(
                    path.span(),
                    format_args!(
                        "Invalid attribute provided: '{}'",
                        path.get_ident().unwrap()
                    ),
                ));
            }
        }

        Ok(enum_attr)
    }
}

impl Default for TopLevelAttribute {
    fn default() -> Self {
        Self {
            allow_runtime_configurable: false,
            use_partial_matching: false,
            value_types: vec![syn::parse2(quote! {i32}).unwrap()],
        }
    }
}