use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
use syn::{
parse_macro_input,
Data,
DeriveInput,
Expr,
Lit,
Variant,
};
fn throw_error(message: &str) -> syn::Error {
syn::Error::new(Span::call_site(), message)
}
fn validate_enum_variant(variant: Variant, enum_name: &str) -> Result<(), syn::Error> {
let variant_name = variant.ident;
let (_, expression) = variant.discriminant.ok_or(throw_error(
format!(
"`{variant_name}` in the `{enum_name}` enum must have a hard-coded discriminant value"
)
.as_str(),
))?;
match expression {
Expr::Lit(expr) => match expr.lit {
Lit::Int(value) => {
let value = value.base10_parse::<usize>().map_err(|_| {
throw_error(
format!("[`{variant_name}`]: cannot parse `{value}` as `usize`").as_str(),
)
})?;
if value != 0 && !value.is_power_of_two() {
Err(throw_error(
format!("[`{variant_name}`]: `{value}` is neither zero nor a power of two")
.as_str(),
))
} else {
Ok(())
}
}
_ => Err(throw_error(
format!(
"`{variant_name}` in the `{enum_name}` enum must have an integer discriminant"
)
.as_str(),
)),
},
_ => Err(throw_error(
format!(
"`{variant_name}` in the `{enum_name}` enum must have a literal RHS expression"
)
.as_str(),
)),
}
}
#[proc_macro_derive(BitRole)]
pub fn derive_bit_role(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
match &input.data {
Data::Enum(value) => {
let name = input.ident;
let enum_name = name.to_string();
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
for variant in value.variants.clone() {
match validate_enum_variant(variant, &enum_name) {
Ok(_) => {}
Err(err) => return err.to_compile_error().into(),
}
}
let expanded = quote! {
use bit_roles::BitRoleImpl;
use std::marker::PhantomData;
impl #impl_generics Into<usize> for #name #ty_generics #where_clause {
fn into(self) -> usize {
self as usize
}
}
impl #impl_generics bit_roles::RoleVariant for #name #ty_generics #where_clause {}
impl #impl_generics BitRoleImpl<#name> for #name #ty_generics #where_clause {
fn empty() -> bit_roles::RoleManager<#name> {
bit_roles::RoleManager(0, PhantomData)
}
fn from_value(value: usize) -> bit_roles::RoleManager<#name> {
bit_roles::RoleManager(value, PhantomData)
}
}
};
TokenStream::from(expanded)
}
_ => throw_error("This macro can only be used with enums.")
.to_compile_error()
.into(),
}
}
#[proc_macro_derive(BitRoleUnchecked)]
pub fn derive_bit_role_unchecked(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
match &input.data {
Data::Enum(_) => {
let expanded = quote! {
use bit_roles::BitRoleUncheckedImpl;
use std::marker::PhantomData;
impl #impl_generics bit_roles::RoleVariant for #name #ty_generics #where_clause {}
impl #impl_generics BitRoleUncheckedImpl<#name> for #name #ty_generics #where_clause {
fn empty() -> bit_roles::RoleManagerUnchecked<#name> {
bit_roles::RoleManagerUnchecked(0, PhantomData)
}
fn from_value(value: usize) -> bit_roles::RoleManagerUnchecked<#name> {
bit_roles::RoleManagerUnchecked(value, PhantomData)
}
}
};
TokenStream::from(expanded)
}
_ => throw_error("This macro can only be used with enums.")
.to_compile_error()
.into(),
}
}