Deprecated: The each() function is deprecated. This message will be suppressed on further calls in /home/zhenxiangba/zhenxiangba.com/public_html/phproxy-improved-master/index.php on line 456
proc-macro-error 0.1.0 - Docs.rs
[go: Go Back, main page]

proc-macro-error 0.1.0

Drop-in replacement to panics in proc-macros
Documentation
//! # Drop-in replacement to panics in proc-macros
//!
//! ## Motivation
//!
//! Error handling in proc-macros sucks. It's not much of a choice today:
//! you either "bubble up" the error up to top-level of you 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. I have yet to see a crate > 700
//!     lines of code that does it, simple `.expect` is too tempting.
//! - Later sucks because panics aren't for error-reporting; 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. But main disadvantage
//!     of this approach is even simpler: no way to highlight exact place that's an error's source.
//!
//! ## Solution
//!
//! That said, we need a solution, but this solution must meet these conditions:
//!
//! - It must be better than panics. 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.
//!
//! This crate aims to provide such a mechanism. All you have to do is enclose all
//! the code inside your top-level `#[proc_macro]` function in `filter_macro_errors!`
//! invocation and change panics to `span_error!`/`call_site_error!` where appropriate:
//!
//! ```ignore
//! // This is your main entry point
//! #[proc_macro]
//! pub fn make_answer(input: TokenStream) -> TokenStream {
//!     // This macro **must** be placed at the top level.
//!     // No need to touch the code inside though.
//!     filter_macro_errors! {
//!         // `parse_macro_input!` and its friends work just fine inside this macro
//!         let input = parse_macro_input!(input as MyParser);
//!
//!         if let Err(err) = some_logic(&input) {
//!             // we've got a span to blame, let's use it
//!             let span = err.span_should_be_highlighted();
//!             let msg = err.message();
//!             // This call jumps directly at the end of `filter_macro_errors!` invocation
//!             span_error!(span, "You made an error, go fix it: {}", msg);
//!         }
//!         
//!         // You can use some handy shortcuts if your error type
//!         // implements Into<MacroError>         
//!         use proc_macro_error::ResultExt;
//!         more_logic(&input).expect_or_exit("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
//!             call_site_error!(
//!                 "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()
//!     }
//!     
//!     // At this point we have a new shining `proc_macro::TokenStream`!
//! }
//! ```
//!
//! ## How it works
//! I must confess: I used panics as a try/catch mechanism. I've committed this
//! sin so others may live in peace and prosperity, god save my soul. 
//!
//! Essentially, the `filter_macro_errors!` macro is a 
//! ```C++
//! try { 
//!     /* your code */ 
//! } catch (MacroError) { 
//!     /* conversion to compile_error! */ 
//! }
//! ```
//!
//! `span_error!` and co are 
//! ```C++
//! throw MacroError::new(span, format!(msg...));
//! ```
//!
//! When you do `span_error!` you trigger panic
//! that will be caught by `filter_macro_errors!` and converted to `compile_error!` invocation.
//! All the panics triggered not by `span_error!` and co will be resumed as is.
//!
//! Panic catching is indeed *slow* but the macro is about to abort anyway so speed is not
//! a concern here. Please note that this crate is not intended to be used in any other way
//! than a proc-macro error reporting, use `Result` and `?` instead.
//!
//! ## Testing
//! TODO: fork https://github.com/laumann/compiletest-rs and make it understand explicit line numbers.


// reexports for use in macros
pub extern crate proc_macro;
pub extern crate proc_macro2;
extern crate quote;
extern crate syn;

use proc_macro2::{Span, TokenStream};
use quote::quote_spanned;

/// Makes a `MacroError` instance from provided arguments (`panic!`-like)
/// and triggers panic in hope it will be caught by `errors_filter_macro!`.
///
/// # Syntax
///
/// This macro is meant to be a `panic!` drop-in replacement so its syntax is very similar to `panic`,
/// but it has three forms instead of two:
/// 1. "panic-format-like" form: span, formatting `str` literal, comma-separated list of args.
///     First argument is a span, all the rest gets passed to `format!` to build the error message.
/// 2. "panic-single-arg-like" form: span, expr, no comma at the end.
///     First argument is a span, the second is our error message, it must implement `ToString`.
/// 3. "trigger_error-like" form: single expr.
///     Literally `trigger_error(arg)`. It's here just for convenience so `span_error!` can be used
///     with instances of `syn::Error`, `MacroError`, `&str`, `String` and so on...
///
#[macro_export]
macro_rules! span_error {
    ($span:expr, $fmt:literal, $($args:expr),*) => {{
        let msg = format!($fmt, $($args),*);
        let err = $crate::MacroError::new($span.into(), msg);
        $crate::trigger_error(err)
    }};

    ($span:expr, $msg:expr) => {{
        let err = $crate::MacroError::new($span.into(), $msg.to_string());
        $crate::trigger_error(err)
    }};

    ($err:expr) => { $crate::trigger_error($err) };
}

/// Shortcut for `span_error!(Span::call_site(), msg...)`. This macro
/// is still considered preferable over plain panic, see [#Motivation]
#[macro_export]
macro_rules! call_site_error {
    ($fmt:literal, $($args:expr),*) => {{
        use $crate::span_error;

        let span = $crate::proc_macro2::Span::call_site();
        span_error!(span, $fmt, $($args),*)
    }};

    ($fmt:expr) => {{
        use $crate::span_error;

        let span = $crate::proc_macro2::Span::call_site();
        span_error!(span, $fmt)
    }};
}

/// This macro is supposed to be used at the top level of your `proc-macro`,
/// the function marked with a `#[proc_macro*]` attribute. It catches all the
/// errors triggered by `span_error!`, `call_site_error!`, and `trigger_error`.
/// Once caught, it converts it to a `proc_macro::TokenStream`
/// containing a `compile_error!` invocation.
///
/// ```ignore
/// #[proc_macro]
/// pub fn make_answer(input: TokenStream) -> TokenStream {
///     // this macro at the top level
///     filter_macro_errors! {
///         // `parse_macro_input!` and its friends work just fine inside this macro
///         let input = parse_macro_input!(input as MyParser);
///
///         if let Err(err) = some_logic(&input) {
///             /// we've got a span to blame, let's use it
///             let span = err.span_should_be_highlighted();
///             let msg = err.message();
///             // This call jumps directly at the end of `filter_macro_errors!` invocation
///             span_error!(span, "You made an error, go fix it: {}", msg);
///         }
///         
///         // You can use some handy shortcuts if your error type
///         // implements Into<MacroError>         
///         use proc_macro_error::ResultExt;
///         more_logic(&input).expect_or_exit("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
///             call_site_error!(
///                 "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()
///     }
///     
///     // At this point we have a new shining `proc_macro::TokenStream`!
/// }
/// ```
#[macro_export]
macro_rules! filter_macro_errors {
    ($($code:tt)*) => {
        let f = move || -> $crate::proc_macro::TokenStream { $($code)* };
        $crate::filter_macro_error_panics(f)
    };
}

/// An error in a proc-macro. This struct preserves
/// the given span so `rustc` can highlight the exact place in user code
/// responsible for the error.
///
/// You're not supposed to use this type directly, use `span_error!` and `call_site_error!`.
pub struct MacroError {
    span: Span,
    msg: String,
}

impl MacroError {
    /// Create an error with the span and message provided.
    pub fn new(span: Span, msg: String) -> Self {
        MacroError { span, msg }
    }

    /// A shortcut for `MacroError::new(Span::call_site(), message)`
    pub fn call_site(msg: String) -> Self {
        MacroError::new(Span::call_site(), msg)
    }

    /// Convert this error into a `TokenStream` containing these tokens: `compiler_error!(<message>);`.
    /// This `compiler_error!` invocation gets the span this error contains.
    pub fn into_compile_error(self) -> TokenStream {
        let MacroError { span, msg } = self;
        quote_spanned! { span=> compile_error!(#msg); }
    }
}

/// This traits expands `Result<T, Into<MacroError>>` 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 `span_error!`.
    fn unwrap_or_exit(self) -> Self::Ok;

    /// Behaves like `Result::expect`: if self is `Ok` yield the contained value,
    /// otherwise abort macro execution via `span_error!`.
    /// If it aborts then resulting message will be preceded with `message`.
    fn expect_or_exit(self, msg: &str) -> Self::Ok;
}

/// This traits expands `Option<T>` 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 `call_site_error!`.
    /// If it aborts the `message` will be used for `compile_error!` invocation.
    fn expect_or_exit(self, msg: &str) -> Self::Some;
}

impl<T, E: Into<MacroError>> ResultExt for Result<T, E> {
    type Ok = T;

    fn unwrap_or_exit(self) -> T {
        match self {
            Ok(res) => res,
            Err(e) => trigger_error(e),
        }
    }

    fn expect_or_exit(self, message: &str) -> T {
        match self {
            Ok(res) => res,
            Err(e) => {
                let MacroError { msg, span } = e.into();
                let msg = format!("{}: {}", message, msg);
                trigger_error(MacroError::new(span, msg))
            }
        }
    }
}

impl<T> OptionExt for Option<T> {
    type Some = T;

    fn expect_or_exit(self, message: &str) -> T {
        match self {
            Some(res) => res,
            None => call_site_error!(message),
        }
    }
}

impl From<syn::Error> for MacroError {
    fn from(e: syn::Error) -> Self {
        MacroError::new(e.span(), e.to_string())
    }
}

impl From<String> for MacroError {
    fn from(msg: String) -> Self {
        MacroError::call_site(msg)
    }
}

impl From<&str> for MacroError {
    fn from(msg: &str) -> Self {
        MacroError::call_site(msg.into())
    }
}

/// Trigger error, aborting the proc-macro's execution.
///
/// You're not supposed to use this function directly.
/// Use `span_error!` or `call_site_error!` instead.
pub fn trigger_error<T: Into<MacroError>>(err: T) -> ! {
    panic!(Payload(err.into()))
}

/// Execute the closure and catch all the panics triggered by
/// `trigger_error`, converting them to `proc_macro::TokenStream`.
/// All the other panics will be passed through as is.
///
/// You're not supposed to use this function directly, use `filter_macro_errors!`
/// instead.
pub fn filter_macro_error_panics<F>(f: F) -> proc_macro::TokenStream
where
    F: FnOnce() -> proc_macro::TokenStream,
{
    use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe};

    match catch_unwind(AssertUnwindSafe(f)) {
        Ok(stream) => stream,
        Err(boxed) => match boxed.downcast::<Payload>() {
            Ok(err) => err.0.into_compile_error().into(),
            Err(other) => resume_unwind(other),
        },
    }
}

struct Payload(MacroError);

// SAFE: Payload is private, a user can't use it to make any harm.
unsafe impl Send for Payload {}
unsafe impl Sync for Payload {}