| 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 | |