1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
// LIB.rs
// by Lut99
//
// Created:
// 10 Dec 2022, 11:57:28
// Last edited:
// 08 Oct 2024, 15:13:58
// Auto updated?
// Yes
//
// Description:
//! Implements `#[derive(EnumDebug)]` for the `enum-debug` crate.
//
use proc_macro::TokenStream;
use quote::{quote, ToTokens};
use syn::punctuated::Punctuated;
use syn::spanned::Spanned as _;
use syn::token::Comma;
use syn::{parse_macro_input, Data, DeriveInput, Expr, ExprLit, Ident, Lit, Meta};
/***** HELPER MACROS *****/
/// Throws a [`syn::Error`].
macro_rules! err {
($span:expr, $message:expr) => {
syn::Error::new($span, $message).into_compile_error().into()
};
}
/***** LIBRARY *****/
/// Does the derivation for the EnumDebug.
#[proc_macro_derive(EnumDebug, attributes(enum_debug))]
pub fn derive_enum_debug(input: TokenStream) -> TokenStream {
let DeriveInput { ident, data, attrs, generics, .. } = parse_macro_input!(input);
// Match what we're parsing
match data {
Data::Enum(e) => {
// Create the default name
let name: String = ident.to_string();
let mut name = quote!(#name).into();
// Find if we also have to derive the thing
for attr in attrs {
// Only do our own
if !attr.path().is_ident("enum_debug") {
continue;
}
// Attempt to parse the list
let metas: Punctuated<Meta, Comma> = match attr.parse_args_with(Punctuated::parse_terminated) {
Ok(metas) => metas,
// Not for us
Err(err) => {
return err!(err.span(), "Failed to parse `enum_debug(...)` arguments as valid attributes");
},
};
// Parse the attributes
for meta in metas {
match meta {
Meta::Path(path) => {
if path.is_ident("path") {
// Override with the path
name = quote!(::std::any::type_name::<Self>());
// NOTE: Legacy here, path used to be the default but now `name` is no change compared to default behaviour
} else if !path.is_ident("name") {
return err!(path.span(), format!("Unknown attribute property '{}'", path.to_token_stream()));
}
},
Meta::NameValue(name_value) => {
if name_value.path.is_ident("name") {
// Set the literal as the string if it is one
match name_value.value {
Expr::Lit(ExprLit { lit: Lit::Str(set_name), .. }) => {
let set_name = set_name.value();
name = quote!(#set_name);
},
expr => {
return err!(expr.span(), "Name must be a string literal");
},
}
} else {
return err!(name_value.path.span(), format!("Unknown attribute property '{}'", name_value.path.to_token_stream()));
}
},
l => {
return err!(l.span(), format!("Unknown attribute property '{}'", l.to_token_stream()));
},
}
}
}
// Find the variants
let variants: Vec<&Ident> = e.variants.iter().map(|v| &v.ident).collect();
// Emit the enum itself, either with generics or without
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
quote! {
impl #impl_generics ::enum_debug::EnumDebug for #ident #ty_generics #where_clause {
#[inline]
fn type_name() -> &'static ::std::primitive::str { #name }
fn variant_names() -> &'static [&'static ::std::primitive::str] {
&[#(::std::stringify!(#variants)),*]
}
fn variant_name(&self) -> &'static ::std::primitive::str {
match self {
#(#ident::#variants{ .. } => ::std::stringify!(#variants),)*
#[allow(dead_code)]
_ => ::std::unreachable!(),
}
}
}
}
.into()
},
// Can only do enums, clearly
_ => {
err!(ident.span(), "EnumDebug can only be derived on enums")
},
}
}