modtype_derive 0.7.0

Macros that implement modular arithmetic integer types
Documentation
use if_chain::if_chain;
use proc_macro2::Span;
use quote::quote;
use syn::spanned::Spanned;
use syn::{
    parse_macro_input, parse_quote, Data, DeriveInput, Fields, IntSuffix, Lit, Meta, MetaList,
    MetaNameValue, NestedMeta, Type,
};

use std::mem;

pub(crate) fn const_value(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    fn compile_error(span: Span, msg: &str) -> proc_macro::TokenStream {
        syn::Error::new(span, msg).to_compile_error().into()
    }

    let DeriveInput {
        attrs,
        ident,
        generics,
        data,
        ..
    } = parse_macro_input!(input as DeriveInput);

    if !generics.params.is_empty() {
        return compile_error(generics.span(), "The generics parameters must be empty");
    }

    let field_attrs = match data {
        Data::Struct(data) => match data.fields {
            Fields::Named(fields) => fields.named.into_iter().flat_map(|f| f.attrs).collect(),
            Fields::Unnamed(fields) => fields.unnamed.into_iter().flat_map(|f| f.attrs).collect(),
            Fields::Unit => vec![],
        },
        Data::Enum(data) => data.variants.into_iter().flat_map(|f| f.attrs).collect(),
        Data::Union(data) => data
            .fields
            .named
            .into_iter()
            .flat_map(|f| f.attrs)
            .collect(),
    };
    for attr in field_attrs {
        if let Ok(meta) = attr.parse_meta() {
            match &meta {
                Meta::Word(ident)
                | Meta::NameValue(MetaNameValue { ident, .. })
                | Meta::List(MetaList { ident, .. })
                    if ident == "modtype" =>
                {
                    return compile_error(ident.span(), "not allowed here");
                }
                _ => {}
            }
        }
    }

    static MSG: &str = "expected `modtype(const_value = #LitInt)` where the `LitInt` has a suffix";

    let mut int = None;

    for attr in &attrs {
        let meta = try_syn!(attr
            .parse_meta()
            .map_err(|e| syn::Error::new(e.span(), format!("invalid meta: {}", e))));
        match &meta {
            Meta::Word(ident) | Meta::NameValue(MetaNameValue { ident, .. })
                if ident == "modtype" =>
            {
                return compile_error(ident.span(), MSG);
            }
            Meta::List(MetaList { ident, nested, .. }) if ident == "modtype" => {
                let (value, ty, span) = if_chain! {
                    if nested.len() == 1;
                    if let NestedMeta::Meta(Meta::NameValue(name_value)) = &nested[0];
                    if name_value.ident == "const_value";
                    if let Lit::Int(int) = &name_value.lit;
                    if let Some::<Type>(ty) = match int.suffix() {
                        IntSuffix::I8 => Some(parse_quote!(i8)),
                        IntSuffix::I16 => Some(parse_quote!(i16)),
                        IntSuffix::I32 => Some(parse_quote!(i32)),
                        IntSuffix::I64 => Some(parse_quote!(i64)),
                        IntSuffix::I128 => Some(parse_quote!(i128)),
                        IntSuffix::Isize => Some(parse_quote!(isize)),
                        IntSuffix::U8 => Some(parse_quote!(u8)),
                        IntSuffix::U16 => Some(parse_quote!(u16)),
                        IntSuffix::U32 => Some(parse_quote!(u32)),
                        IntSuffix::U64 => Some(parse_quote!(u64)),
                        IntSuffix::U128 => Some(parse_quote!(u128)),
                        IntSuffix::Usize => Some(parse_quote!(usize)),
                        IntSuffix::None => None,
                    };
                    then {
                        (int.clone(), ty, ident.span())
                    } else {
                        return compile_error(ident.span(), MSG);
                    }
                };
                if mem::replace(&mut int, Some((value, ty))).is_some() {
                    return compile_error(span, "multiple definition");
                }
            }
            _ => {}
        }
    }

    let (int, ty) = match int {
        None => return compile_error(Span::call_site(), MSG),
        Some(int) => int,
    };

    quote!(
        impl ::modtype::ConstValue for #ident {
            type Value = #ty;
            const VALUE: #ty = #int;
        }
    )
    .into()
}