Crates.io | proc-macro-error |
lib.rs | proc-macro-error |
version | 1.0.4 |
source | src |
created_at | 2019-07-11 07:51:11.667268 |
updated_at | 2020-07-31 18:08:20.096247 |
description | Almost drop-in replacement to panics in proc-macros |
homepage | |
repository | https://gitlab.com/CreepySkeleton/proc-macro-error |
max_upload_size | |
id | 148248 |
size | 86,515 |
This crate aims to make error reporting in proc-macros simple and easy to use.
Migrate from panic!
-based errors for as little effort as possible!
Also, you can explicitly append a dummy token stream to your errors.
To achieve his, this crate serves as a tiny shim around proc_macro::Diagnostic
and
compile_error!
. It detects the most preferable way to emit errors based on compiler's version.
When the underlying diagnostic type is finally stabilized, this crate will be simply
delegating to it, requiring no changes in your code!
So you can just use this crate and have both some of proc_macro::Diagnostic
functionality
available on stable ahead of time and your error-reporting code future-proof.
[dependencies]
proc-macro-error = "1.0"
Supports rustc 1.31 and up
Code:
#[proc_macro]
#[proc_macro_error]
pub fn make_fn(input: TokenStream) -> TokenStream {
let mut input = TokenStream2::from(input).into_iter();
let name = input.next().unwrap();
if let Some(second) = input.next() {
abort! { second,
"I don't like this part!";
note = "I see what you did there...";
help = "I need only one part, you know?";
}
}
quote!( fn #name() {} ).into()
}
This is how the error is rendered in a terminal:
And this is what your users will see in their IDE:
use proc_macro_error::{
proc_macro_error,
abort,
abort_call_site,
ResultExt,
OptionExt,
};
use proc_macro::TokenStream;
use syn::{DeriveInput, parse_macro_input};
use quote::quote;
// This is your main entry point
#[proc_macro]
// This attribute *MUST* be placed on top of the #[proc_macro] function
#[proc_macro_error]
pub fn make_answer(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
if let Err(err) = some_logic(&input) {
// we've got a span to blame, let's use it
// This immediately aborts the proc-macro and shows the error
//
// You can use `proc_macro::Span`, `proc_macro2::Span`, and
// anything that implements `quote::ToTokens` (almost every type from
// `syn` and `proc_macro2`)
abort!(err, "You made an error, go fix it: {}", err.msg);
}
// `Result` has some handy shortcuts if your error type implements
// `Into<Diagnostic>`. `Option` has one unconditionally.
more_logic(&input).expect_or_abort("What a careless user, behave!");
if !more_logic_for_logic_god(&input) {
// We don't have an exact location this time,
// so just highlight the proc-macro invocation itself
abort_call_site!(
"Bad, bad user! Now go stand in the corner and think about what you did!");
}
// Now all the processing is done, return `proc_macro::TokenStream`
quote!(/* stuff */).into()
}
proc_macro::Diagnostic
-like usageuse proc_macro_error::*;
use proc_macro::TokenStream;
use syn::{spanned::Spanned, DeriveInput, ItemStruct, Fields, Attribute , parse_macro_input};
use quote::quote;
fn process_attrs(attrs: &[Attribute]) -> Vec<Attribute> {
attrs
.iter()
.filter_map(|attr| match process_attr(attr) {
Ok(res) => Some(res),
Err(msg) => {
emit_error!(attr, "Invalid attribute: {}", msg);
None
}
})
.collect()
}
fn process_fields(_attrs: &Fields) -> Vec<TokenStream> {
// processing fields in pretty much the same way as attributes
unimplemented!()
}
#[proc_macro]
#[proc_macro_error]
pub fn make_answer(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as ItemStruct);
let attrs = process_attrs(&input.attrs);
// abort right now if some errors were encountered
// at the attributes processing stage
abort_if_dirty();
let fields = process_fields(&input.fields);
// no need to think about emitted errors
// #[proc_macro_error] will handle them for you
//
// just return a TokenStream as you normally would
quote!(/* stuff */).into()
}
structopt-derive
(abort-like usage)auto-impl
(emit-like usage)panic
is not for error reporting.proc_macro_error
will always be compatible with proc-macro Holy Trinity:
proc_macro2
, syn
, quote
crates. In other words, if the Trinity is available
to you - proc_macro_error
is available too.
Important!
If you want to use
#[proc_macro_error]
withsynstructure
, you're going to have to put the attribute inside thedecl_derive!
invocation. Unfortunately, due to some bug in pre-1.34 rustc, putting proc-macro attributes inside macro invocations doesn't work, so your MSRV is effectively 1.34.
Error handling in proc-macros sucks. There's not much of a choice today:
you either "bubble up" the error up to the top-level of the macro and convert it to
a compile_error!
invocation or just use a good old panic. Both these ways suck:
Former sucks because it's quite redundant to unroll a proper error handling
just for critical errors that will crash the macro anyway; so people mostly
choose not to bother with it at all and use panic. Simple .expect
is too tempting.
Also, if you do decide to implement this Result
-based architecture in your macro
you're going to have to rewrite it entirely once proc_macro::Diagnostic
is finally
stable. Not cool.
Later sucks because there's no way to carry out the span info via panic!
.
rustc
will highlight the invocation itself but not some specific token inside it.
Furthermore, panics aren't for error-reporting at all; panics are for bug-detecting
(like unwrapping on None
or out-of-range indexing) or for early development stages
when you need a prototype ASAP so error handling can wait. Mixing these usages only
messes things up.
There is proc_macro::Diagnostic
which is awesome but it has been experimental
for more than a year and is unlikely to be stabilized any time soon.
This crate's API is intentionally designed to be compatible with proc_macro::Diagnostic
and delegates to it whenever possible. Once Diagnostics
is stable this crate
will always delegate to it, no code changes will be required on user side.
That said, we need a solution, but this solution must meet these conditions:
panic!
. The main point: it must offer a way to carry the span information
over to user.panic!
. Ideally, a new
macro with similar semantics plus ability to carry out span info.proc_macro::Diagnostic
.This crate aims to provide such a mechanism. All you have to do is annotate your top-level
#[proc_macro]
function with #[proc_macro_error]
attribute and change panics to
abort!
/abort_call_site!
where appropriate, see the Guide.
Please note that this crate is not intended to be used in any way other
than error reporting in procedural macros, use Result
and ?
(possibly along with one of the
many helpers out there) for anything else.