Proc macro anyhow, a combination of ideas from
anyhow and
proc-macro-error to improve proc macro
development, especially focused on the error handling.
Error handling in proc-macros is unideal, as the top level functions of proc
macros can only return TokenStreams both in success and failure case. This
means that I often write code like this, moving the actual implementation in
a separate function to be able to use the ergonomic rust error handling with
e.g., ?.
use proc_macro2::TokenStream as TokenStream2;
#[proc_macro]
pub fn my_macro(input: TokenStream) -> TokenStream {
match actual_implementation(input.into()) {
Ok(output) => output,
Err(error) => error.into_compile_error(),
}
.into()
}
fn actual_implementation(input: TokenStream2) -> syn::Result<TokenStream2> {
// ..
}To activate the error handling, just add #[manyhow] above any
proc macro implementation, reducing the above example to:
use manyhow::manyhow;
use proc_macro2::TokenStream as TokenStream2;
#[manyhow]
#[proc_macro]
fn my_macro(input: TokenStream2) -> syn::Result<TokenStream2> {
// ..
}See Without macros to see what this expands to under the hood.
A proc macro function marked as #[manyhow] can take and return any
TokenStream and can also return Result<TokenStream, E> where E implements ToTokensError. As additional parameters a
dummy and/or emitter can
be specified.
The manyhow attribute takes one optional flag when used for proc_macro
and proc_macro_attribute. #[manyhow(input_as_dummy)] will take the input
of a function like proc_macro to initialize the dummy &mut TokenStream while #[manyhow(item_as_dummy)] on
proc_macro_attribute will initialize the dummy with the annotated item.
manyhow can be used without proc macros, and they can be disabled by
adding manyhow with default-features=false.
The usage is more or less the same, though with some added boilerplate from
needing to invoke one of function, attribute or derive
directly.
While the examples use closures, functions can be passed in as well. The above example would then change to:
use proc_macro2::TokenStream as TokenStream2;
#[proc_macro]
pub fn my_macro(input: TokenStream) -> TokenStream {
manyhow::function(
input,
false,
|input: TokenStream2| -> syn::Result<TokenStream2> {
// ..
},
)
}Emitter and dummy
TokenStream can also be used. function and
attribute take an additional boolean parameter controlling whether the
input/item will be used as initial dummy.
MacroHandlers (the trait defining what closures/functions can be used
with manyhow) can take a mutable reference to an Emitter. This
allows to collect errors, but not fail immediately.
Emitter::into_result can be used to return if an Emitter contains
any values.
use manyhow::{manyhow, Emitter, ErrorMessage};
use proc_macro2::TokenStream as TokenStream2;
#[manyhow]
#[proc_macro]
fn my_macro(input: TokenStream2, emitter: &mut Emitter) -> manyhow::Result<TokenStream2> {
// ..
emitter.emit(ErrorMessage::call_site("A fun error!"));
emitter.into_result()?;
// ..
}MacroHandlers also take a mutable reference to a TokenStream, to
enable emitting some dummy code to be used in case the macro errors.
This allows either appending tokens e.g., with ToTokens::to_tokens or
directly setting the dummy code e.g., *dummy = quote!{some tokens}.