extern crate proc_macro;
use crate::parse::parse_input;
use crate::parse::Attribute;
use proc_macro::TokenStream;
use proc_macro2::{Literal, Span, TokenStream as TokenStream2, TokenTree};
use quote::{quote, quote_spanned};
use crate::settings::{Setting::*, *};
mod parse;
mod settings;
type Result<T> = std::result::Result<T, Error>;
struct Error {
span: Span,
message: String,
}
impl Error {
fn new(span: Span, message: String) -> Self {
Error { span, message }
}
fn into_compile_error(self) -> TokenStream2 {
let mut message = Literal::string(&self.message);
message.set_span(self.span);
quote_spanned!(self.span=> compile_error!{#message})
}
}
#[proc_macro_attribute]
pub fn proc_macro_error(attr: TokenStream, input: TokenStream) -> TokenStream {
match impl_proc_macro_error(attr.into(), input.clone().into()) {
Ok(ts) => ts,
Err(e) => {
let error = e.into_compile_error();
let input = TokenStream2::from(input);
quote!(#input #error).into()
}
}
}
fn impl_proc_macro_error(attr: TokenStream2, input: TokenStream2) -> Result<TokenStream> {
let (attrs, signature, body) = parse_input(input)?;
let mut settings = parse_settings(attr)?;
let is_proc_macro = is_proc_macro(&attrs);
if is_proc_macro {
settings.set(AssertUnwindSafe);
}
if detect_proc_macro_hack(&attrs) {
settings.set(ProcMacroHack);
}
if settings.is_set(ProcMacroHack) {
settings.set(AllowNotMacro);
}
if !(settings.is_set(AllowNotMacro) || is_proc_macro) {
return Err(Error::new(
Span::call_site(),
"#[proc_macro_error] attribute can be used only with procedural macros\n\n \
= hint: if you are really sure that #[proc_macro_error] should be applied \
to this exact function, use #[proc_macro_error(allow_not_macro)]\n"
.into(),
));
}
let body = gen_body(body, settings);
let res = quote! {
#(#attrs)*
#(#signature)*
{ #body }
};
Ok(res.into())
}
#[cfg(not(always_assert_unwind))]
fn gen_body(block: TokenTree, settings: Settings) -> proc_macro2::TokenStream {
let is_proc_macro_hack = settings.is_set(ProcMacroHack);
let closure = if settings.is_set(AssertUnwindSafe) {
quote!(::std::panic::AssertUnwindSafe(|| #block ))
} else {
quote!(|| #block)
};
quote!( ::proc_macro_error::entry_point(#closure, #is_proc_macro_hack) )
}
#[cfg(always_assert_unwind)]
fn gen_body(block: TokenTree, settings: Settings) -> proc_macro2::TokenStream {
let is_proc_macro_hack = settings.is_set(ProcMacroHack);
let closure = quote!(::std::panic::AssertUnwindSafe(|| #block ));
quote!( ::proc_macro_error::entry_point(#closure, #is_proc_macro_hack) )
}
fn detect_proc_macro_hack(attrs: &[Attribute]) -> bool {
attrs
.iter()
.any(|attr| attr.path_is_ident("proc_macro_hack"))
}
fn is_proc_macro(attrs: &[Attribute]) -> bool {
attrs.iter().any(|attr| {
attr.path_is_ident("proc_macro")
|| attr.path_is_ident("proc_macro_derive")
|| attr.path_is_ident("proc_macro_attribute")
})
}