290 lines
9.9 KiB
Markdown
290 lines
9.9 KiB
Markdown
> [!NOTE]
|
|
>
|
|
> This is a maintained fork of
|
|
> [`proc-macro-error-2`](https://github.com/GnomedDev/proc-macro-error-2), which
|
|
> itself was a fork of
|
|
> [`proc-macro-error`](https://github.com/CreepySkeleton/proc-macro-error). The
|
|
> goal is to keep the crate maintained while preserving the original API and
|
|
> behavior as much as possible.
|
|
>
|
|
> Notable changes from `proc-macro-error` to `proc-macro-error-2`:
|
|
>
|
|
> - Upgraded `syn` to `2`.
|
|
> - Raised the MSRV to `1.61`.
|
|
> - Removed automatic nightly detection; use the `nightly` feature for improved
|
|
> diagnostics.
|
|
>
|
|
> Notable changes in this fork compared to `proc-macro-error-2`:
|
|
>
|
|
> - Fixed the future-incompatibility warning from
|
|
> [`rust-lang/rust#127909`](https://github.com/rust-lang/rust/issues/127909)
|
|
> - [#3](https://github.com/gamma0987/proc-macro-error3/pull/3) Fixed the args
|
|
> expansion in macros missed the comma in the presence of trailing commas.
|
|
> - [#4](https://github.com/gamma0987/proc-macro-error3/pull/4) Fixed panics
|
|
> when parsing `syn::Error` fails.
|
|
> - The changelog contains all notable changes
|
|
|
|
# Makes error reporting in procedural macros nice and easy
|
|
|
|
[](https://docs.rs/proc-macro-error3)
|
|
[](https://github.com/rust-secure-code/safety-dance/)
|
|
|
|
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][crate::dummy] to your
|
|
errors.
|
|
|
|
To achieve this, 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.
|
|
|
|
```toml
|
|
[dependencies]
|
|
proc-macro-error3 = "3.0"
|
|
```
|
|
|
|
_Supports rustc 1.61 and up_
|
|
|
|
[Documentation and guide][guide]
|
|
|
|
## Quick example
|
|
|
|
Code:
|
|
|
|
```rust
|
|
#[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:
|
|
|
|
<p align="center">
|
|
<img src="https://user-images.githubusercontent.com/50968528/78830016-d3b46a80-79d6-11ea-9de2-972e8d7904ef.png" width="600">
|
|
</p>
|
|
|
|
And this is what your users will see in their IDE:
|
|
|
|
<p align="center">
|
|
<img src="https://user-images.githubusercontent.com/50968528/78830547-a9af7800-79d7-11ea-822e-59e29bda335c.png" width="600">
|
|
</p>
|
|
|
|
## Examples
|
|
|
|
### Panic-like usage
|
|
|
|
```rust
|
|
use proc_macro_error3::{
|
|
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 usage
|
|
|
|
```rust
|
|
use proc_macro_error3::*;
|
|
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()
|
|
}
|
|
```
|
|
|
|
## Real world examples
|
|
|
|
- [`structopt-derive`](https://github.com/TeXitoi/structopt/tree/master/structopt-derive)
|
|
(abort-like usage)
|
|
- [`auto-impl`](https://github.com/auto-impl-rs/auto_impl/) (emit-like usage)
|
|
|
|
## Limitations
|
|
|
|
- Warnings are emitted only on nightly, they are ignored on stable.
|
|
- "help" suggestions can't have their own span info on stable, (essentially
|
|
inheriting the parent span).
|
|
- If your macro happens to trigger a panic, no errors will be displayed. This is
|
|
not a technical limitation but rather intentional design. `panic` is not for
|
|
error reporting.
|
|
|
|
## MSRV policy
|
|
|
|
The MSRV is currently `1.61`, and this is considered a breaking change to
|
|
increase.
|
|
|
|
However, if an existing dependency requires a higher MSRV without a semver
|
|
breaking update, this may be raised.
|
|
|
|
## Motivation
|
|
|
|
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!`][compl_err] 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:
|
|
|
|
- It must be better than `panic!`. The main point: it must offer a way to carry
|
|
the span information over to user.
|
|
- It must take as little effort as possible to migrate from `panic!`. Ideally, a
|
|
new macro with similar semantics plus ability to carry out span info.
|
|
- It must maintain compatibility with [`proc_macro::Diagnostic`][] .
|
|
- **It must be usable on stable**.
|
|
|
|
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][guide].
|
|
|
|
## Disclaimer
|
|
|
|
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.
|
|
|
|
<br>
|
|
|
|
### License
|
|
|
|
<sup>
|
|
Licensed under either of <a href="LICENSE-APACHE">Apache License, Version
|
|
2.0</a> or <a href="LICENSE-MIT">MIT license</a> at your option.
|
|
</sup>
|
|
|
|
<br>
|
|
|
|
<sub>
|
|
Unless you explicitly state otherwise, any contribution intentionally submitted
|
|
for inclusion in this crate by you, as defined in the Apache-2.0 license, shall
|
|
be dual licensed as above, without any additional terms or conditions.
|
|
</sub>
|
|
|
|
[compl_err]: https://doc.rust-lang.org/std/macro.compile_error.html
|
|
[`proc_macro::Diagnostic`]:
|
|
https://doc.rust-lang.org/proc_macro/struct.Diagnostic.html
|
|
[crate::dummy]:
|
|
https://docs.rs/proc-macro-error3/latest/proc_macro_error3/dummy/index.html
|
|
[`abort_call_site!`]:
|
|
https://docs.rs/proc-macro-error3/latest/proc_macro_error3/macro.abort_call_site.html
|
|
[`abort!`]:
|
|
https://docs.rs/proc-macro-error3/latest/proc_macro_error3/macro.abort.html
|
|
[guide]: https://docs.rs/proc-macro-error3
|