blob: a289806b5e2dcbd0fc1976bef46af078fc49efc3 [file] [log] [blame]
//! # proc-macro-error
//!
//! 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, there's ability to [append a dummy token stream][dummy] to your errors.
//!
//! ## Enticement
//!
//! Your errors look like this?
//! ```text
//! error: proc-macro derive panicked
//! --> $DIR/bool_default_value.rs:11:10
//! |
//! 11 | #[derive(StructOpt, Debug)]
//! | ^^^^^^^^^
//! |
//! = help: message: default_value is meaningless for bool
//! ```
//!
//! But you would like it to be like this!
//! ```text
//! error: default_value is meaningless for bool
//! --> $DIR/bool_default_value.rs:14:24
//! |
//! 14 | #[structopt(short, default_value = true)]
//! | ^^^^^^^^^^^^^
//! ```
//!
//! This is exactly what this crate is built for!!!
//!
//! ## Usage
//!
//! ### Panic-like usage
//!
//! ```rust
//! # fn some_logic(_input: &DeriveInput) -> Result<(), Dummy> { unimplemented!() }
//! # fn more_logic(_input: &DeriveInput) -> Result<(), Dummy> { unimplemented!() }
//! # fn more_logic_for_logic_god(_input: &DeriveInput) -> bool { unimplemented!() }
//! # struct Dummy {
//! # span: proc_macro2::Span,
//! # msg: String
//! # }
//! # impl Into<MacroError> for Dummy {
//! # fn into(self) -> MacroError { unimplemented!() }
//! # }
//! use proc_macro_error::*;
//! use proc_macro::TokenStream;
//! use syn::{DeriveInput, parse_macro_input};
//! use quote::quote;
//!
//! # static _IGNORE: &str = "
//! // 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
//! abort!(err.span, "You made an error, go fix it: {}", err.msg);
//! }
//!
//! // `Result` has some handy shortcuts if your error type implements
//! // `Into<MacroError>`. `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()
//! }
//! ```
//!
//! ### Multiple errors
//!
//! ```rust
//! use proc_macro_error::*;
//! use proc_macro::TokenStream;
//! use syn::{spanned::Spanned, DeriveInput, ItemStruct, Fields, Attribute , parse_macro_input};
//! use quote::quote;
//!
//! # fn process_attr(_a: &Attribute) -> Result<Attribute, String> { unimplemented!() }
//! 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.span(), "Invalid attribute: {}", msg);
//! None
//! }
//! })
//! .collect()
//! }
//!
//! fn process_fields(_attrs: &Fields) -> Vec<TokenStream> {
//! // processing fields in pretty much the same way as attributes
//! unimplemented!()
//! }
//!
//! # static _IGNORE: &str = "
//! #[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()
//! }
//! ```
//!
//! ## Limitations
//!
//! - No support for warnings.
//! - Very limited support for "help" suggestions.
//! - If a panic occurs somewhere in your macro no errors will be displayed.
//!
//! ## Motivation
//!
//! Error handling in proc-macros sucks. It's not much of a choice today:
//! you either "bubble up" the error up to the top-level of your 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. Almost nobody does it,
//! simple `.expect` is too tempting.
//!
//! - Later sucks because there's no way to carry out span info via `panic!`. `rustc` will highlight
//! the whole 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 and error handling can wait. Mixing these usages only
//! messes things up.
//!
//! - There is [`proc_macro::Diagnostics`] which is awesome but it has been experimental
//! for more than a year and is unlikely to be stabilized any time soon.
//!
//! This crate will be deprecated once `Diagnostics` is stable.
//!
//! 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 span information
//! over to user.
//! - It must require as little effort as possible to migrate from `panic!`. Ideally, a new
//! macro with the same semantics plus ability to carry out span info. A support for
//! emitting multiple errors would be great too.
//! - **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_errors]` attribute and change panics to
//! [`abort!`]/[`abort_call_site!`] where appropriate, see [**Usage**](#usage).
//!
//! ## Disclaimer
//! Please note that **this crate is not intended to be used in any other way
//! than a proc-macro error reporting**, use `Result` and `?` for anything else.
//!
//! [compl_err]: https://doc.rust-lang.org/std/macro.compile_error.html
//! [`proc_macro::Diagnostics`]: https://doc.rust-lang.org/proc_macro/struct.Diagnostic.html
// reexports for use in macros
pub extern crate proc_macro;
pub extern crate proc_macro2;
pub mod dummy;
pub mod multi;
pub mod single;
pub use self::dummy::set_dummy;
pub use self::multi::abort_if_dirty;
pub use self::single::MacroError;
pub use proc_macro_error_attr::proc_macro_error;
use quote::quote;
use std::panic::{catch_unwind, resume_unwind, UnwindSafe};
use std::sync::atomic::{AtomicBool, Ordering};
/// This traits expands [`Result<T, Into<MacroError>>`](std::result::Result) with some handy shortcuts.
pub trait ResultExt {
type Ok;
/// Behaves like [`Result::unwrap`]: if self is `Ok` yield the contained value,
/// otherwise abort macro execution via [`abort!`].
fn unwrap_or_abort(self) -> Self::Ok;
/// Behaves like [`Result::expect`]: if self is `Ok` yield the contained value,
/// otherwise abort macro execution via [`abort!`].
/// If it aborts then resulting error message will be preceded with `message`.
fn expect_or_abort(self, msg: &str) -> Self::Ok;
}
/// This traits expands [`Option<T>`][std::option::Option] with some handy shortcuts.
pub trait OptionExt {
type Some;
/// Behaves like [`Option::expect`]: if self is `Some` yield the contained value,
/// otherwise abort macro execution via [`abort_call_site!`].
/// If it aborts the `message` will be used for [`compile_error!`][compl_err] invocation.
///
/// [compl_err]: https://doc.rust-lang.org/std/macro.compile_error.html
fn expect_or_abort(self, msg: &str) -> Self::Some;
}
impl<T> OptionExt for Option<T> {
type Some = T;
fn expect_or_abort(self, message: &str) -> T {
match self {
Some(res) => res,
None => abort_call_site!(message),
}
}
}
/// This is the entry point for your proc-macro. It is **must** to be
/// used on the top level of the proc-macro (a function annotated with
/// `#[proc_macro*] attribute).
///
/// Typically, you use `#[proc_macro_error]` instead, see [module level docs][self].
pub fn entry_point<F>(f: F) -> proc_macro::TokenStream
where
F: FnOnce() -> proc_macro::TokenStream + UnwindSafe,
{
ENTERED_ENTRY_POINT.with(|flag| flag.store(true, Ordering::SeqCst));
let caught = catch_unwind(f);
let dummy = dummy::cleanup();
let err_storage = multi::cleanup();
ENTERED_ENTRY_POINT.with(|flag| flag.store(false, Ordering::SeqCst));
match caught {
Ok(ts) => {
if err_storage.is_empty() {
ts
} else {
quote!( #(#err_storage)* #dummy ).into()
}
}
Err(boxed) => match boxed.downcast::<AbortNow>() {
Ok(_) => {
assert!(!err_storage.is_empty());
quote!( #(#err_storage)* #dummy ).into()
}
Err(boxed) => resume_unwind(boxed),
},
}
}
thread_local! {
static ENTERED_ENTRY_POINT: AtomicBool = AtomicBool::new(false);
}
struct AbortNow;
fn check_correctness() {
if !ENTERED_ENTRY_POINT.with(|flag| flag.load(Ordering::SeqCst)) {
panic!("proc-macro-error API cannot be used outside of `entry_point` invocation. Perhaps you forgot to annotate your #[proc_macro] function with `#[proc_macro_error]");
}
}