proc-macro-error 0.3.1

Drop-in replacement to panics in proc-macros
Documentation
//! # 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]");
    }
}