proc_macro_error_attr/
lib.rs

1//! This is `#[proc_macro_error]` attribute to be used with
2//! [`proc-macro-error`](https://docs.rs/proc-macro-error/). There you go.
3
4extern crate proc_macro;
5
6use crate::parse::parse_input;
7use crate::parse::Attribute;
8use proc_macro::TokenStream;
9use proc_macro2::{Literal, Span, TokenStream as TokenStream2, TokenTree};
10use quote::{quote, quote_spanned};
11
12use crate::settings::{Setting::*, *};
13
14mod parse;
15mod settings;
16
17type Result<T> = std::result::Result<T, Error>;
18
19struct Error {
20    span: Span,
21    message: String,
22}
23
24impl Error {
25    fn new(span: Span, message: String) -> Self {
26        Error { span, message }
27    }
28
29    fn into_compile_error(self) -> TokenStream2 {
30        let mut message = Literal::string(&self.message);
31        message.set_span(self.span);
32        quote_spanned!(self.span=> compile_error!{#message})
33    }
34}
35
36#[proc_macro_attribute]
37pub fn proc_macro_error(attr: TokenStream, input: TokenStream) -> TokenStream {
38    match impl_proc_macro_error(attr.into(), input.clone().into()) {
39        Ok(ts) => ts,
40        Err(e) => {
41            let error = e.into_compile_error();
42            let input = TokenStream2::from(input);
43
44            quote!(#input #error).into()
45        }
46    }
47}
48
49fn impl_proc_macro_error(attr: TokenStream2, input: TokenStream2) -> Result<TokenStream> {
50    let (attrs, signature, body) = parse_input(input)?;
51    let mut settings = parse_settings(attr)?;
52
53    let is_proc_macro = is_proc_macro(&attrs);
54    if is_proc_macro {
55        settings.set(AssertUnwindSafe);
56    }
57
58    if detect_proc_macro_hack(&attrs) {
59        settings.set(ProcMacroHack);
60    }
61
62    if settings.is_set(ProcMacroHack) {
63        settings.set(AllowNotMacro);
64    }
65
66    if !(settings.is_set(AllowNotMacro) || is_proc_macro) {
67        return Err(Error::new(
68            Span::call_site(),
69            "#[proc_macro_error] attribute can be used only with procedural macros\n\n  \
70            = hint: if you are really sure that #[proc_macro_error] should be applied \
71            to this exact function, use #[proc_macro_error(allow_not_macro)]\n"
72                .into(),
73        ));
74    }
75
76    let body = gen_body(body, settings);
77
78    let res = quote! {
79        #(#attrs)*
80        #(#signature)*
81        { #body }
82    };
83    Ok(res.into())
84}
85
86#[cfg(not(always_assert_unwind))]
87fn gen_body(block: TokenTree, settings: Settings) -> proc_macro2::TokenStream {
88    let is_proc_macro_hack = settings.is_set(ProcMacroHack);
89    let closure = if settings.is_set(AssertUnwindSafe) {
90        quote!(::std::panic::AssertUnwindSafe(|| #block ))
91    } else {
92        quote!(|| #block)
93    };
94
95    quote!( ::proc_macro_error::entry_point(#closure, #is_proc_macro_hack) )
96}
97
98// FIXME:
99// proc_macro::TokenStream does not implement UnwindSafe until 1.37.0.
100// Considering this is the closure's return type the unwind safety check would fail
101// for virtually every closure possible, the check is meaningless.
102#[cfg(always_assert_unwind)]
103fn gen_body(block: TokenTree, settings: Settings) -> proc_macro2::TokenStream {
104    let is_proc_macro_hack = settings.is_set(ProcMacroHack);
105    let closure = quote!(::std::panic::AssertUnwindSafe(|| #block ));
106    quote!( ::proc_macro_error::entry_point(#closure, #is_proc_macro_hack) )
107}
108
109fn detect_proc_macro_hack(attrs: &[Attribute]) -> bool {
110    attrs
111        .iter()
112        .any(|attr| attr.path_is_ident("proc_macro_hack"))
113}
114
115fn is_proc_macro(attrs: &[Attribute]) -> bool {
116    attrs.iter().any(|attr| {
117        attr.path_is_ident("proc_macro")
118            || attr.path_is_ident("proc_macro_derive")
119            || attr.path_is_ident("proc_macro_attribute")
120    })
121}