//! # proc-macro-error2
//!
//! 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](dummy/index.html) to your errors).
//!
//! To achieve his, this crate serves as a tiny shim around `proc_macro::Diagnostic` and
//! `compile_error!`. It detects the best way of emitting available based on compiler's version.
//! When the underlying diagnostic type is finally stabilized, this crate will simply be
//! 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.
//!
//! ## Cargo features
//!
//! This crate provides *enabled by default* `syn-error` feature that gates
//! `impl From<syn::Error> for Diagnostic` conversion. If you don't use `syn` and want
//! to cut off some of compilation time, you can disable it via
//!
//! ```toml
//! [dependencies]
//! proc-macro-error2 = { version = "2.0.0", default-features = false }
//! ```
//!
//! ***Please note that disabling this feature makes sense only if you don't depend on `syn`
//! directly or indirectly, and you very likely do.**
//!
//! ## 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 a panic occurs somewhere in your macro no errors will be displayed. This is not a
//!   technical limitation but rather intentional design. `panic` is not for error reporting.
//!
//! ### `#[proc_macro_error]` attribute
//!
//! **This attribute MUST be present on the top level of your macro** (the function
//! annotated with any of `#[proc_macro]`, `#[proc_macro_derive]`, `#[proc_macro_attribute]`).
//!
//! This attribute performs the setup and cleanup necessary to make things work.
//!
//! In most cases you'll need the simple `#[proc_macro_error]` form without any
//! additional settings. Feel free to [skip the "Syntax" section](#macros).
//!
//! #### Syntax
//!
//! `#[proc_macro_error]` or `#[proc_macro_error(settings...)]`, where `settings...`
//! is a comma-separated list of:
//!
//! - `proc_macro_hack`:
//!
//!     In order to correctly cooperate with `#[proc_macro_hack]`, `#[proc_macro_error]`
//!     attribute must be placed *before* (above) it, like this:
//!
//!     ```no_run
//!     # use proc_macro2::TokenStream;
//!     # const IGNORE: &str = "
//!     #[proc_macro_error]
//!     #[proc_macro_hack]
//!     #[proc_macro]
//!     # ";
//!     fn my_macro(input: TokenStream) -> TokenStream {
//!         unimplemented!()
//!     }
//!     ```
//!
//!     If, for some reason, you can't place it like that you can use
//!     `#[proc_macro_error(proc_macro_hack)]` instead.
//!
//!     # Note
//!
//!     If `proc-macro-hack` was detected (by any means) `allow_not_macro`
//!     and `assert_unwind_safe` will be applied automatically.
//!
//! - `allow_not_macro`:
//!
//!     By default, the attribute checks that it's applied to a proc-macro.
//!     If none of `#[proc_macro]`, `#[proc_macro_derive]` nor `#[proc_macro_attribute]` are
//!     present it will panic. It's the intention - this crate is supposed to be used only with
//!     proc-macros.
//!
//!     This setting is made to bypass the check, useful in certain circumstances.
//!
//!     Pay attention: the function this attribute is applied to must return
//!     `proc_macro::TokenStream`.
//!
//!     This setting is implied if `proc-macro-hack` was detected.
//!
//! - `assert_unwind_safe`:
//!
//!     By default, your code must be [unwind safe]. If your code is not unwind safe,
//!     but you believe it's correct, you can use this setting to bypass the check.
//!     You would need this for code that uses `lazy_static` or `thread_local` with
//!     `Cell/RefCell` inside (and the like).
//!
//!     This setting is implied if `#[proc_macro_error]` is applied to a function
//!     marked as `#[proc_macro]`, `#[proc_macro_derive]` or `#[proc_macro_attribute]`.
//!
//!     This setting is also implied if `proc-macro-hack` was detected.
//!
//! ## Macros
//!
//! Most of the time you want to use the macros. Syntax is described in the next section below.
//!
//! You'll need to decide how you want to emit errors:
//!
//! * Emit the error and abort. Very much panic-like usage. Served by [`abort!`] and
//!   [`abort_call_site!`].
//! * Emit the error but do not abort right away, looking for other errors to report.
//!   Served by [`emit_error!`] and [`emit_call_site_error!`].
//!
//! You **can** mix these usages.
//!
//! `abort` and `emit_error` take a "source span" as the first argument. This source
//! will be used to highlight the place the error originates from. It must be one of:
//!
//! * *Something* that implements [`ToTokens`] (most types in `syn` and `proc-macro2` do).
//!   This source is the preferable one since it doesn't lose span information on multi-token
//!   spans, see [this issue](https://gitlab.com/CreepySkeleton/proc-macro-error/-/issues/6)
//!   for details.
//! * [`proc_macro::Span`]
//! * [`proc-macro2::Span`]
//!
//! The rest is your message in format-like style.
//!
//! See [the next section](#syntax-1) for detailed syntax.
//!
//! - [`abort!`]:
//!
//!     Very much panic-like usage - abort right away and show the error.
//!     Expands to [`!`] (never type).
//!
//! - [`abort_call_site!`]:
//!
//!     Shortcut for `abort!(Span::call_site(), ...)`. Expands to [`!`] (never type).
//!
//! - [`emit_error!`]:
//!
//!     [`proc_macro::Diagnostic`]-like usage - emit the error but keep going,
//!     looking for other errors to report.
//!     The compilation will fail nonetheless. Expands to [`()`] (unit type).
//!
//! - [`emit_call_site_error!`]:
//!
//!     Shortcut for `emit_error!(Span::call_site(), ...)`. Expands to [`()`] (unit type).
//!
//! - [`emit_warning!`]:
//!
//!     Like `emit_error!` but emit a warning instead of error. The compilation won't fail
//!     because of warnings.
//!     Expands to [`()`] (unit type).
//!
//!     **Beware**: warnings are nightly only, they are completely ignored on stable.
//!
//! - [`emit_call_site_warning!`]:
//!
//!     Shortcut for `emit_warning!(Span::call_site(), ...)`. Expands to [`()`] (unit type).
//!
//! - [`diagnostic`]:
//!
//!     Build an instance of `Diagnostic` in format-like style.
//!
//! #### Syntax
//!
//! All the macros have pretty much the same syntax:
//!
//! 1.  ```ignore
//!     abort!(single_expr)
//!     ```
//!     Shortcut for `Diagnostic::from(expr).abort()`.
//!
//! 2.  ```ignore
//!     abort!(span, message)
//!     ```
//!     The first argument is an expression the span info should be taken from.
//!
//!     The second argument is the error message, it must implement [`ToString`].
//!
//! 3.  ```ignore
//!     abort!(span, format_literal, format_args...)
//!     ```
//!
//!     This form is pretty much the same as 2, except `format!(format_literal, format_args...)`
//!     will be used to for the message instead of [`ToString`].
//!
//! That's it. `abort!`, `emit_warning`, `emit_error` share this exact syntax.
//!
//! `abort_call_site!`, `emit_call_site_warning`, `emit_call_site_error` lack 1 form
//! and do not take span in 2'th and 3'th forms. Those are essentially shortcuts for
//! `macro!(Span::call_site(), args...)`.
//!
//! `diagnostic!` requires a [`Level`] instance between `span` and second argument
//! (1'th form is the same).
//!
//! > **Important!**
//! >
//! > If you have some type from `proc_macro` or `syn` to point to, do not call `.span()`
//! > on it but rather use it directly:
//! > ```no_run
//! > # use proc_macro_error2::abort;
//! > # let input = proc_macro2::TokenStream::new();
//! > let ty: syn::Type = syn::parse2(input).unwrap();
//! > abort!(ty, "BOOM");
//! > //     ^^ <-- avoid .span()
//! > ```
//! >
//! > `.span()` calls work too, but you may experience regressions in message quality.
//!
//! #### Note attachments
//!
//! 3.  Every macro can have "note" attachments (only 2 and 3 form).
//!   ```ignore
//!   let opt_help = if have_some_info { Some("did you mean `this`?") } else { None };
//!
//!   abort!(
//!       span, message; // <--- attachments start with `;` (semicolon)
//!
//!       help = "format {} {}", "arg1", "arg2"; // <--- every attachment ends with `;`,
//!                                              //      maybe except the last one
//!
//!       note = "to_string"; // <--- one arg uses `.to_string()` instead of `format!()`
//!
//!       yay = "I see what {} did here", "you"; // <--- "help =" and "hint =" are mapped
//!                                              // to Diagnostic::help,
//!                                              // anything else is Diagnostic::note
//!
//!       wow = note_span => "custom span"; // <--- attachments can have their own span
//!                                         //      it takes effect only on nightly though
//!
//!       hint =? opt_help; // <-- "optional" attachment, get displayed only if `Some`
//!                         //     must be single `Option` expression
//!
//!       note =? note_span => opt_help // <-- optional attachments can have custom spans too
//!   );
//!   ```
//!

//! ### Diagnostic type
//!
//! [`Diagnostic`] type is intentionally designed to be API compatible with [`proc_macro::Diagnostic`].
//! Not all API is implemented, only the part that can be reasonably implemented on stable.
//!
//!
//! [`abort!`]: macro.abort.html
//! [`abort_call_site!`]: macro.abort_call_site.html
//! [`emit_warning!`]: macro.emit_warning.html
//! [`emit_error!`]: macro.emit_error.html
//! [`emit_call_site_warning!`]: macro.emit_call_site_error.html
//! [`emit_call_site_error!`]: macro.emit_call_site_warning.html
//! [`diagnostic!`]: macro.diagnostic.html
//! [`Diagnostic`]: struct.Diagnostic.html
//!
//! [`proc_macro::Span`]: https://doc.rust-lang.org/proc_macro/struct.Span.html
//! [`proc_macro::Diagnostic`]: https://doc.rust-lang.org/proc_macro/struct.Diagnostic.html
//!
//! [unwind safe]: https://doc.rust-lang.org/std/panic/trait.UnwindSafe.html#what-is-unwind-safety
//! [`!`]: https://doc.rust-lang.org/std/primitive.never.html
//! [`()`]: https://doc.rust-lang.org/std/primitive.unit.html
//! [`ToString`]: https://doc.rust-lang.org/std/string/trait.ToString.html
//!
//! [`proc-macro2::Span`]: https://docs.rs/proc-macro2/1.0.10/proc_macro2/struct.Span.html
//! [`ToTokens`]: https://docs.rs/quote/1.0.3/quote/trait.ToTokens.html
//!

#![cfg_attr(feature = "nightly", feature(proc_macro_diagnostic))]
#![forbid(unsafe_code)]

extern crate proc_macro;

pub use crate::{
    diagnostic::{Diagnostic, DiagnosticExt, Level},
    dummy::{append_dummy, set_dummy},
};
pub use proc_macro_error_attr2::proc_macro_error;

use proc_macro2::Span;
use quote::{quote, ToTokens};

use std::cell::Cell;
use std::panic::{catch_unwind, resume_unwind, UnwindSafe};

pub mod dummy;

mod diagnostic;
mod macros;
mod sealed;

#[cfg(not(feature = "nightly"))]
#[path = "imp/fallback.rs"]
mod imp;

#[cfg(feature = "nightly")]
#[path = "imp/delegate.rs"]
mod imp;

#[derive(Debug, Clone, Copy)]
#[must_use = "A SpanRange does nothing unless used"]
pub struct SpanRange {
    pub first: Span,
    pub last: Span,
}

impl SpanRange {
    /// Create a range with the `first` and `last` spans being the same.
    pub fn single_span(span: Span) -> Self {
        SpanRange {
            first: span,
            last: span,
        }
    }

    /// Create a `SpanRange` resolving at call site.
    pub fn call_site() -> Self {
        SpanRange::single_span(Span::call_site())
    }

    /// Construct span range from a `TokenStream`. This method always preserves all the
    /// range.
    ///
    /// ### Note
    ///
    /// If the stream is empty, the result is `SpanRange::call_site()`. If the stream
    /// consists of only one `TokenTree`, the result is `SpanRange::single_span(tt.span())`
    /// that doesn't lose anything.
    pub fn from_tokens(ts: &dyn ToTokens) -> Self {
        let mut spans = ts.to_token_stream().into_iter().map(|tt| tt.span());
        let first = spans.next().unwrap_or_else(Span::call_site);
        let last = spans.last().unwrap_or(first);

        SpanRange { first, last }
    }

    /// Join two span ranges. The resulting range will start at `self.first` and end at
    /// `other.last`.
    pub fn join_range(self, other: SpanRange) -> Self {
        SpanRange {
            first: self.first,
            last: other.last,
        }
    }

    /// Collapse the range into single span, preserving as much information as possible.
    #[must_use]
    pub fn collapse(self) -> Span {
        self.first.join(self.last).unwrap_or(self.first)
    }
}

/// This traits expands `Result<T, Into<Diagnostic>>` 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` 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;
}

/// Abort macro execution and display all the emitted errors, if any.
///
/// Does nothing if no errors were emitted (warnings do not count).
pub fn abort_if_dirty() {
    imp::abort_if_dirty();
}

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

    fn unwrap_or_abort(self) -> T {
        match self {
            Ok(res) => res,
            Err(e) => e.into().abort(),
        }
    }

    fn expect_or_abort(self, message: &str) -> T {
        match self {
            Ok(res) => res,
            Err(e) => {
                let mut e = e.into();
                e.msg = format!("{}: {}", message, e.msg);
                e.abort()
            }
        }
    }
}

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 a proc-macro.
///
/// **NOT PUBLIC API, SUBJECT TO CHANGE WITHOUT ANY NOTICE**
#[doc(hidden)]
pub fn entry_point<F>(f: F, proc_macro_hack: bool) -> proc_macro::TokenStream
where
    F: FnOnce() -> proc_macro::TokenStream + UnwindSafe,
{
    ENTERED_ENTRY_POINT.with(|flag| flag.set(flag.get() + 1));
    let caught = catch_unwind(f);
    let dummy = dummy::cleanup();
    let err_storage = imp::cleanup();
    ENTERED_ENTRY_POINT.with(|flag| flag.set(flag.get() - 1));

    let gen_error = || {
        if proc_macro_hack {
            quote! {{
                macro_rules! proc_macro_call {
                    () => ( unimplemented!() )
                }

                #(#err_storage)*
                #dummy

                unimplemented!()
            }}
        } else {
            quote!( #(#err_storage)* #dummy )
        }
    };

    match caught {
        Ok(ts) => {
            if err_storage.is_empty() {
                ts
            } else {
                gen_error().into()
            }
        }

        Err(boxed) => match boxed.downcast::<AbortNow>() {
            Ok(_) => gen_error().into(),
            Err(boxed) => resume_unwind(boxed),
        },
    }
}

fn abort_now() -> ! {
    check_correctness();
    std::panic::panic_any(AbortNow)
}

thread_local! {
    static ENTERED_ENTRY_POINT: Cell<usize> = const { Cell::new(0) };
}

struct AbortNow;

fn check_correctness() {
    assert!(
        ENTERED_ENTRY_POINT.with(Cell::get) != 0,
        "proc-macro-error2 API cannot be used outside of `entry_point` invocation, \
             perhaps you forgot to annotate your #[proc_macro] function with `#[proc_macro_error]"
    );
}

/// **ALL THE STUFF INSIDE IS NOT PUBLIC API!!!**
#[doc(hidden)]
pub mod __export {
    // reexports for use in macros
    pub use proc_macro;
    pub use proc_macro2;

    use proc_macro2::Span;
    use quote::ToTokens;

    use crate::SpanRange;

    // inspired by
    // https://github.com/dtolnay/case-studies/blob/master/autoref-specialization/README.md#simple-application

    pub trait SpanAsSpanRange {
        #[allow(non_snake_case)]
        fn FIRST_ARG_MUST_EITHER_BE_Span_OR_IMPLEMENT_ToTokens_OR_BE_SpanRange(&self) -> SpanRange;
    }

    pub trait Span2AsSpanRange {
        #[allow(non_snake_case)]
        fn FIRST_ARG_MUST_EITHER_BE_Span_OR_IMPLEMENT_ToTokens_OR_BE_SpanRange(&self) -> SpanRange;
    }

    pub trait ToTokensAsSpanRange {
        #[allow(non_snake_case)]
        fn FIRST_ARG_MUST_EITHER_BE_Span_OR_IMPLEMENT_ToTokens_OR_BE_SpanRange(&self) -> SpanRange;
    }

    pub trait SpanRangeAsSpanRange {
        #[allow(non_snake_case)]
        fn FIRST_ARG_MUST_EITHER_BE_Span_OR_IMPLEMENT_ToTokens_OR_BE_SpanRange(&self) -> SpanRange;
    }

    impl<T: ToTokens> ToTokensAsSpanRange for &T {
        fn FIRST_ARG_MUST_EITHER_BE_Span_OR_IMPLEMENT_ToTokens_OR_BE_SpanRange(&self) -> SpanRange {
            let mut ts = self.to_token_stream().into_iter();
            let first = match ts.next() {
                Some(t) => t.span(),
                None => Span::call_site(),
            };

            let last = match ts.last() {
                Some(t) => t.span(),
                None => first,
            };

            SpanRange { first, last }
        }
    }

    impl Span2AsSpanRange for Span {
        fn FIRST_ARG_MUST_EITHER_BE_Span_OR_IMPLEMENT_ToTokens_OR_BE_SpanRange(&self) -> SpanRange {
            SpanRange {
                first: *self,
                last: *self,
            }
        }
    }

    impl SpanAsSpanRange for proc_macro::Span {
        fn FIRST_ARG_MUST_EITHER_BE_Span_OR_IMPLEMENT_ToTokens_OR_BE_SpanRange(&self) -> SpanRange {
            SpanRange {
                first: (*self).into(),
                last: (*self).into(),
            }
        }
    }

    impl SpanRangeAsSpanRange for SpanRange {
        fn FIRST_ARG_MUST_EITHER_BE_Span_OR_IMPLEMENT_ToTokens_OR_BE_SpanRange(&self) -> SpanRange {
            *self
        }
    }
}
