aboutsummaryrefslogtreecommitdiff
path: root/proc-macro-error/proc-macro-error/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'proc-macro-error/proc-macro-error/src/lib.rs')
-rw-r--r--proc-macro-error/proc-macro-error/src/lib.rs514
1 files changed, 514 insertions, 0 deletions
diff --git a/proc-macro-error/proc-macro-error/src/lib.rs b/proc-macro-error/proc-macro-error/src/lib.rs
new file mode 100644
index 0000000..5fb0223
--- /dev/null
+++ b/proc-macro-error/proc-macro-error/src/lib.rs
@@ -0,0 +1,514 @@
+//! # 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/index.html) to your errors.
+//!
+//! ## Limitations
+//!
+//! - Warnings are emitted only on nightly, they're ignored on stable.
+//! - "help" suggestions cannot have their own span info on stable, (they inherit parent span).
+//! - If a panic occurs somewhere in your macro no errors will be displayed. This is not a
+//! technical limitation but intentional design, `panic` is not for error reporting.
+//!
+//! ## Guide
+//!
+//! ### Macros
+//!
+//! First of all - **all the emitting-related API must be used within a function
+//! annotated with [`#[proc_macro_error]`](#proc_macro_error-attribute) attribute**. You'll just get a
+//! panic otherwise, no errors will be shown.
+//!
+//! For most of the time you will be using macros.
+//!
+//! - [`abort!`]:
+//!
+//! Very much panic-like usage - abort execution 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 do not abort the macro.
+//! 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 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().abort()`
+//!
+//! 2. ```ignore
+//! abort!(span, message)
+//! ```
+//! Shortcut for `Diagnostic::spanned(span, message.to_string()).abort()`
+//!
+//! 3. ```ignore
+//! abort!(span, format_literal, format_args...)
+//! ```
+//! Shortcut for `Diagnostic::spanned(span, format!(format_literal, format_args...)).abort()`
+//!
+//! 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 and 3 forms.
+//!
+//! `diagnostic!` require `Level` instance between `span` and second argument (1 form is the same).
+//!
+//! #### 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
+//! )
+//! ```
+//!
+//! ### `#[proc_macro_error]` attribute
+//!
+//! **This attribute MUST be present on the top level of your macro.**
+//!
+//! This attribute performs the setup and cleanup necessary to make things work.
+//!
+//! #### Syntax
+//!
+//! `#[proc_macro_error]` or `#[proc_macro_error(settings...)]`, where `settings...`
+//! is a comma-separated list of:
+//!
+//! - `proc_macro_hack`:
+//!
+//! To correctly cooperate with `#[proc_macro_hack]` `#[proc_macro_error]`
+//! attribute must be placed *before* (above) it, like this:
+//!
+//! ```ignore
+//! #[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.
+//!
+//! - `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.
+//!
+//! Please note: the function this attribute is applied to must return `proc_macro::TokenStream`.
+//!
+//! - `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. This is typically needed
+//! for code that uses `lazy_static` or `thread_local` with `Cell/RefCell` inside.
+//!
+//! This setting is implied if `#[proc_macro_error]` is applied to a function
+//! marked as `#[proc_macro]`, `#[proc_macro_derive]` or `#[proc_macro_attribute]`.
+//!
+//! ### 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
+//! [`emit_warning!`]: macro.emit_warning.html
+//! [`emit_error!`]: macro.emit_error.html
+//! [`abort_call_site!`]: macro.abort_call_site.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
+//! [proc_macro_error]: ./../proc_macro_error_attr/attr.proc_macro_error.html
+//! [`Diagnostic`]: struct.Diagnostic.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
+
+#![cfg_attr(pme_nightly, feature(proc_macro_diagnostic))]
+#![forbid(unsafe_code)]
+
+// reexports for use in macros
+#[doc(hidden)]
+pub extern crate proc_macro;
+#[doc(hidden)]
+pub extern crate proc_macro2;
+
+pub use self::dummy::set_dummy;
+pub use proc_macro_error_attr::proc_macro_error;
+
+use proc_macro2::{Span, TokenStream};
+use quote::quote;
+use quote::{quote_spanned, ToTokens};
+use std::cell::Cell;
+use std::panic::{catch_unwind, resume_unwind, UnwindSafe};
+
+pub mod dummy;
+
+mod macros;
+
+#[cfg(not(any(pme_nightly, nightly_fmt)))]
+#[path = "stable.rs"]
+mod imp;
+
+#[cfg(any(pme_nightly, nightly_fmt))]
+#[path = "nightly.rs"]
+mod imp;
+
+/// Represents a diagnostic level
+///
+/// # Warnings
+///
+/// Warnings are ignored on stable/beta
+#[derive(Debug, PartialEq)]
+pub enum Level {
+ Error,
+ Warning,
+ #[doc(hidden)]
+ NonExhaustive,
+}
+
+/// Represents a single diagnostic message
+#[derive(Debug)]
+pub struct Diagnostic {
+ level: Level,
+ span: Span,
+ msg: String,
+ suggestions: Vec<(SuggestionKind, String, Option<Span>)>,
+}
+
+/// 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;
+}
+
+impl Diagnostic {
+ /// Create a new diagnostic message that points to `Span::call_site()`
+ pub fn new(level: Level, message: String) -> Self {
+ Diagnostic::spanned(Span::call_site(), level, message)
+ }
+
+ /// Create a new diagnostic message that points to the `span`
+ pub fn spanned(span: Span, level: Level, message: String) -> Self {
+ Diagnostic {
+ level,
+ span,
+ msg: message,
+ suggestions: vec![],
+ }
+ }
+
+ /// Attach a "help" note to your main message, note will have it's own span on nightly.
+ ///
+ /// # Span
+ ///
+ /// The span is ignored on stable, the note effectively inherits its parent's (main message) span
+ pub fn span_help(mut self, span: Span, msg: String) -> Self {
+ self.suggestions
+ .push((SuggestionKind::Help, msg, Some(span)));
+ self
+ }
+
+ /// Attach a "help" note to your main message,
+ pub fn help(mut self, msg: String) -> Self {
+ self.suggestions.push((SuggestionKind::Help, msg, None));
+ self
+ }
+
+ /// Attach a note to your main message, note will have it's own span on nightly.
+ ///
+ /// # Span
+ ///
+ /// The span is ignored on stable, the note effectively inherits its parent's (main message) span
+ pub fn span_note(mut self, span: Span, msg: String) -> Self {
+ self.suggestions
+ .push((SuggestionKind::Note, msg, Some(span)));
+ self
+ }
+
+ /// Attach a note to your main message
+ pub fn note(mut self, msg: String) -> Self {
+ self.suggestions.push((SuggestionKind::Note, msg, None));
+ self
+ }
+
+ /// The message of main warning/error (no notes attached)
+ pub fn message(&self) -> &str {
+ &self.msg
+ }
+
+ /// Abort the proc-macro's execution and display the diagnostic.
+ ///
+ /// # Warnings
+ ///
+ /// Warnings do not get emitted on stable/beta but this function will abort anyway.
+ pub fn abort(self) -> ! {
+ self.emit();
+ abort_now()
+ }
+
+ /// Display the diagnostic while not aborting macro execution.
+ ///
+ /// # Warnings
+ ///
+ /// Warnings are ignored on stable/beta
+ pub fn emit(self) {
+ imp::emit_diagnostic(self);
+ }
+}
+
+/// 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();
+}
+
+#[doc(hidden)]
+impl Diagnostic {
+ pub fn span_suggestion(self, span: Span, suggestion: &str, msg: String) -> Self {
+ match suggestion {
+ "help" | "hint" => self.span_help(span, msg),
+ _ => self.span_note(span, msg),
+ }
+ }
+
+ pub fn suggestion(self, suggestion: &str, msg: String) -> Self {
+ match suggestion {
+ "help" | "hint" => self.help(msg),
+ _ => self.note(msg),
+ }
+ }
+}
+
+impl ToTokens for Diagnostic {
+ fn to_tokens(&self, ts: &mut TokenStream) {
+ use std::borrow::Cow;
+
+ fn ensure_lf(buf: &mut String, s: &str) {
+ if s.ends_with('\n') {
+ buf.push_str(s);
+ } else {
+ buf.push_str(s);
+ buf.push('\n');
+ }
+ }
+
+ let Diagnostic {
+ ref msg,
+ ref suggestions,
+ ref level,
+ ..
+ } = *self;
+
+ if *level == Level::Warning {
+ return;
+ }
+
+ let message = if suggestions.is_empty() {
+ Cow::Borrowed(msg)
+ } else {
+ let mut message = String::new();
+ ensure_lf(&mut message, msg);
+ message.push('\n');
+
+ for (kind, note, _span) in suggestions {
+ message.push_str(" = ");
+ message.push_str(kind.name());
+ message.push_str(": ");
+ ensure_lf(&mut message, note);
+ }
+ message.push('\n');
+
+ Cow::Owned(message)
+ };
+
+ let span = &self.span;
+ let msg = syn::LitStr::new(&*message, *span);
+ ts.extend(quote_spanned!(*span=> compile_error!(#msg); ));
+ }
+}
+
+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),
+ }
+ }
+}
+
+#[derive(Debug)]
+enum SuggestionKind {
+ Help,
+ Note,
+}
+
+impl SuggestionKind {
+ fn name(&self) -> &'static str {
+ match self {
+ SuggestionKind::Note => "note",
+ SuggestionKind::Help => "help",
+ }
+ }
+}
+
+impl From<syn::Error> for Diagnostic {
+ fn from(e: syn::Error) -> Self {
+ Diagnostic::spanned(e.span(), Level::Error, e.to_string())
+ }
+}
+
+/// 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(true));
+ let caught = catch_unwind(f);
+ let dummy = dummy::cleanup();
+ let err_storage = imp::cleanup();
+ ENTERED_ENTRY_POINT.with(|flag| flag.set(false));
+
+ let mut appendix = TokenStream::new();
+ if proc_macro_hack {
+ appendix.extend(quote! {
+ #[allow(unused)]
+ macro_rules! proc_macro_call {
+ () => ( unimplemented!() )
+ }
+ });
+ }
+
+ match caught {
+ Ok(ts) => {
+ if err_storage.is_empty() {
+ ts
+ } else {
+ quote!( #(#err_storage)* #dummy #appendix ).into()
+ }
+ }
+
+ Err(boxed) => match boxed.downcast::<AbortNow>() {
+ Ok(_) => quote!( #(#err_storage)* #dummy #appendix ).into(),
+ Err(boxed) => resume_unwind(boxed),
+ },
+ }
+}
+
+fn abort_now() -> ! {
+ check_correctness();
+ panic!(AbortNow)
+}
+
+thread_local! {
+ static ENTERED_ENTRY_POINT: Cell<bool> = Cell::new(false);
+}
+
+struct AbortNow;
+
+fn check_correctness() {
+ if !ENTERED_ENTRY_POINT.with(|flag| flag.get()) {
+ 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]"
+ );
+ }
+}