How to report errors in a procedural macro using the quote macro?
Apart from panicking, there are currently two ways to reports errors from a proc-macro: the unstable Diagnostic
API and "the compile_error!
trick". Currently, the latter is mostly used because it works on stable. Let's see how they both work.
The compile_error!
trick
Since Rust 1.20, the compile_error!
macro exists in the standard library. It takes a string and leads to an error at compile time.
compile_error!("oopsie woopsie");
Which leads to (Playground):
error: oopsie woopsie
--> src/lib.rs:1:1
|
1 | compile_error!("oopsie woopsie");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This macro has been added for two cases: macro_rules!
macros and #[cfg]
. In both cases, library authors can add better errors if the user uses the macro incorrectly or has the wrong cfg
values.
But proc-macro programmers had an interesting idea. As you might know, the TokenStream
you return from your procedural macro can be created however you like. That includes the spans of those tokens: you can attach any spans you like to your output tokens. So the main idea is this:
Emit a tokenstream containing compile_error!("your error message");
but set the span of those tokens to the span of the input token that caused the error. There is even a macro in quote
which makes this easier: quote_spanned!
. In your case, we can write this:
let output = if ty.to_string() != "bool" {
quote_spanned! {
ty.span() =>
compile_error!("expected bool");
}
} else {
quote! {
const #name: #ty = false;
}
};
For your faulty input, the compiler now prints this:
error: expected bool
--> examples/main.rs:4:7
|
4 | #[something_else]
| ^^^^^^^^^^^^^^
Why exactly does this work? Well: the error for compile_error!
shows the code snippet containing the compile_error!
invocation. For that, the span of the compile_error!
invocation is used. But since we set the span to point to the faulty input token ty
, the compiler shows the snippet underlining that token.
This trick is also used by syn
to print nice errors. In fact, if you are using syn
anyway, you can use its Error
type and in particular the Error::to_compile_error
method which returns exactly the token stream we manually created with quote_spanned!
:
syn::Error::new(ty.span(), "expected bool").to_compile_error()
The Diagnostic
API
As this is still unstable, just a short example. The diagnostic API is more powerful than the trick above as you can have multiple spans, warnings and notes.
Diagnostic::spanned(ty.span().unwrap(), Level::Error, "expected bool").emit();
After that line, the error is printed, but you can still do stuff in your proc-macro. Usually, you would just return an empty token stream.
The accepted answer mentioned the unstable Diagnostic
API, which gives you much more power and control than the regular compile_error
. Until the Diagnostic
API stabilizes, which probably will not be any time soon, you can use the proc_macro_error
crate. It provides a Diagnostic
type that is designed to be API compatible with the unstable proc_macro::Diagnostic
. The entire API is not implemented, only the part that can be reasonably implemented on stable. You can use it by simply adding the provided annotation to your macro:
#[proc_macro_error]
#[proc_macro]
fn my_macro(input: TokenStream) -> TokenStream {
// ...
Diagnostic::spanned(ty.span().unwrap(), Level::Error, "expected bool").emit();
}
proc_macro_error
also provides some useful macros for emitting errors:
abort! { input,
"I don't like this part!";
note = "A notice message...";
help = "A help message...";
}
However, you might want to consider sticking to using the Diagnostic
type as it will make it easier to migrate to the the official Diagnostic
API when it stabilizes.