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