#![allow(
clippy::needless_doctest_main,
)]
#![warn(
missing_debug_implementations,
missing_docs,
)]
#![deny(
unconditional_recursion,
rustdoc::broken_intra_doc_links,
)]
#![doc(test(
no_crate_inject,
attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables))
))]
#![cfg_attr(docsrs, allow(unused_attributes))]
use proc_macro;
use proc_macro2::{
TokenStream,
};
use quote::quote;
use syn::{
DeriveInput, Data, Fields, Path,
parse_macro_input,
};
mod helper;
mod meta;
use crate::meta::DerivePropertiesExt;
use crate::helper::{assert_variant, assert_discriminant_value};
const CLOG_ATTR_IDENT: &str = "hclog";
#[doc(hidden)]
#[proc_macro_derive(HCLog, attributes(hclog))]
pub fn hclog_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as DeriveInput);
parse_derive_macro(&input).unwrap_or_else(|err| err.into_compile_error()).into()
}
fn parse_derive_macro(ast: &DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
let ident = &ast.ident;
let generics = &ast.generics;
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let attrs = ast.parse_properties(CLOG_ATTR_IDENT)?;
let data = match &ast.data {
Data::Enum(v) => v,
_ => return Err(helper::assert_enum()),
};
let lmk_ident = syn::parse_str::<Path>("::hclog::ScopeKey")?;
let lvl_ident = syn::parse_str::<Path>("::hclog::Level")?;
let fav_ident = syn::parse_str::<Path>("::hclog::FacadeVariant")?;
let opt_ident = syn::parse_str::<Path>("::hclog::options::Options")?;
let res_ident = syn::parse_str::<Path>("::hclog::Result")?;
let mut init_trait_fns = vec![];
if let Some(ref logmod) = attrs.scope {
init_trait_fns.push(quote! { fn logscope() -> #lmk_ident { #logmod } });
}
if let Some(ref level) = attrs.default_level {
init_trait_fns.push(quote! { fn default_level() -> #lvl_ident { #level } });
}
if let Some(ref facade) = attrs.default_facade {
init_trait_fns.push(quote! { fn default_facade() -> #fav_ident { #facade } });
}
if let Some(ref options) = attrs.default_options {
init_trait_fns.push(quote! { fn default_options() -> #opt_ident { #(#options)* } });
}
let with_log = if attrs.logcompat {
quote! { options + hclog::options::LOGCOMPAT }
} else {
quote! { options }
};
let variants = &data.variants;
let mut v_idents = vec![];
let mut fmt_arms = vec![];
let mut lvl_arms = vec![];
let mut fav_arms = vec![];
let mut dsc_arms = vec![];
let mut idx = 0usize;
for variant in variants {
let v_ident = &variant.ident;
let v_attrs = variant.parse_properties(CLOG_ATTR_IDENT)?;
let v_discriminant = &variant.discriminant;
match &variant.fields {
Fields::Named(_) => return Err(assert_variant(variant.ident.span(), "Struct")),
Fields::Unnamed(_) => return Err(assert_variant(variant.ident.span(), "Tuple")),
Fields::Unit => (),
}
if let Some((_, syn::Expr::Lit(ref d))) = v_discriminant {
let syn::Lit::Int(ref a) = d.lit else {
return Err(syn::Error::new(v_ident.span(), "invalid discriminant"));
};
let value = a.base10_parse::<usize>()?;
if value != idx {
return Err(assert_discriminant_value(&v_ident, value, idx));
}
dsc_arms.push(quote! { (&#ident::#v_ident,) => #value, });
} else {
dsc_arms.push(quote! { (&#ident::#v_ident,) => #idx, });
}
idx+=1;
let v_display_name = match v_attrs.name {
Some(n) => quote! { #n },
None => quote! { stringify!(#v_ident) },
};
if let Some(level) = v_attrs.level {
if v_attrs.ignore {
lvl_arms.push(quote! {(&#ident::#v_ident,) => Some(Level::Off), });
} else {
lvl_arms.push(quote! {(&#ident::#v_ident,) => Some(#level), });
}
}
if let Some(facade) = v_attrs.facade {
if v_attrs.ignore {
fav_arms.push(quote! {(&#ident::#v_ident,) => Some(FacadeVariant::None), });
} else {
fav_arms.push(quote! {(&#ident::#v_ident,) => Some(#facade), });
}
}
if !v_attrs.ignore {
v_idents.push(quote! { #ident::#v_ident, });
}
fmt_arms.push(quote! {(&#ident::#v_ident,) => f.write_str(#v_display_name), });
}
let init_fav_fn = if !fav_arms.is_empty() {
quote! {
fn init_facade(&self) -> ::core::option::Option<#fav_ident> {
match (&*self,) {
#(#fav_arms)*
_ => None,
}
}
}
} else {
quote! {}
};
let init_lvl_fn = if !lvl_arms.is_empty() {
quote! {
fn init_level(&self) -> ::core::option::Option<#lvl_ident> {
match (&*self,) {
#(#lvl_arms)*
_ => None,
}
}
}
} else {
quote! {}
};
let output = TokenStream::from(quote! {
use hclog::{Scope, LogKey};
#[automatically_derived]
impl #impl_generics std::fmt::Display for #ident #ty_generics #where_clause {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match (&*self,) {
#(#fmt_arms)*
}
}
}
#[automatically_derived]
impl #impl_generics hclog::Scope for #ident #ty_generics #where_clause {
#(#init_trait_fns)*
fn init<S: std::fmt::Display>(name: S, level: #lvl_ident, facade: #fav_ident,
options: #opt_ident) -> #res_ident<()> {
hclog::init::<Self, S>(name, level, facade, #with_log)?;
hclog::add_submodules(&[#(#v_idents)*])?;
Ok(())
}
}
#[automatically_derived]
impl #impl_generics hclog::LogKey for #ident #ty_generics #where_clause {
fn log_key(&self) -> hclog::ContextKey {
match (&*self,) {
#(#dsc_arms)*
}
}
#init_lvl_fn
#init_fav_fn
}
});
helper::debug_print_generated(ast, &output);
Ok(output)
}