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 = 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:attr.into(), input:input.clone().into()) {
39 Ok(ts: TokenStream) => ts,
40 Err(e: Error) => {
41 let error: TokenStream = e.into_compile_error();
42 let input: TokenStream = 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: bool = settings.is_set(setting:ProcMacroHack);
89 let closure: TokenStream = if settings.is_set(setting: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 attrsIter<'_, Attribute>
111 .iter()
112 .any(|attr: &Attribute| attr.path_is_ident("proc_macro_hack"))
113}
114
115fn is_proc_macro(attrs: &[Attribute]) -> bool {
116 attrs.iter().any(|attr: &Attribute| {
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}
122