1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
#![cfg_attr(nightly_diagnostics, feature(proc_macro_diagnostic, proc_macro_span))]

//! Diagnostic emulation on stable and nightly.
//!
//! # Usage
//!
//! 1. Depend on the library in your proc-macro.
//!
//! ```toml
//! [dependencies]
//! proc_macro2_diagnostics = "0.9"
//! ```
//!
//! 2. Import [`SpanDiagnosticExt`] and use its methods on a
//!    [`proc_macro2::Span`] to create [`Diagnostic`]s:
//!
//! ```rust
//! use syn::spanned::Spanned;
//! use proc_macro2::TokenStream;
//! use proc_macro2_diagnostics::{SpanDiagnosticExt, Diagnostic};
//!
//! fn my_macro(input: TokenStream) -> Result<TokenStream, Diagnostic> {
//!     Err(input.span().error("there's a problem here..."))
//! }
//! ```
//!
//! 3. If there's an error, emit the diagnostic as tokens:
//!
//! ```rust
//! extern crate proc_macro;
//!
//! # use proc_macro2::TokenStream;
//! # use proc_macro2_diagnostics::{SpanDiagnosticExt, Diagnostic};
//! # use syn::spanned::Spanned;
//! # fn my_macro(input: TokenStream) -> Result<TokenStream, Diagnostic> {
//! #     Err(input.span().error("there's a problem here..."))
//! # }
//! # /*
//! #[proc_macro]
//! # */
//! pub fn real_macro(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
//!     match my_macro(tokens.into()) {
//!         Ok(tokens) => tokens.into(),
//!         Err(diag) => diag.emit_as_expr_tokens().into()
//!     }
//! }
//! ```
//!
//! This does the right thing on nightly _or_ stable.
//!
//! # Caveats
//!
//! On stable, due to limitations, any top-level, non-error diagnostics are
//! emitted as errors. This will abort compilation. To avoid this, you may want
//! to `cfg`-gate emitting non-error diagnostics to nightly.
//!
//! # Colors
//!
//! By default, error messages are colored on stable. To disable, disable
//! default features:
//!
//! ```toml
//! [dependencies]
//! proc_macro2_diagnostics = { version = "0.9", default-features = false }
//! ```
//!
//! The compiler always colors diagnostics on nightly.

extern crate proc_macro;

mod ext;
mod diagnostic;
mod line;

pub use diagnostic::{Diagnostic, Level};
pub use ext::SpanDiagnosticExt;

// We stole this from proc_macro2. Checks whether nightly proc_macro things
// _actually_ work by checking if calls to proc_macro::Span panic.
#[cfg(nightly_diagnostics)]
fn nightly_works() -> bool {
    use std::panic::{self, PanicInfo};
    use std::sync::atomic::*;
    use std::sync::Once;

    static WORKS: AtomicUsize = AtomicUsize::new(0);
    static INIT: Once = Once::new();

    match WORKS.load(Ordering::SeqCst) {
        1 => return false,
        2 => return true,
        _ => {}
    }

    // Swap in a null panic hook to avoid printing "thread panicked" to stderr,
    // then use catch_unwind to determine whether the compiler's proc_macro is
    // working. When proc-macro2 is used from outside of a procedural macro all
    // of the proc_macro crate's APIs currently panic.
    //
    // The Once is to prevent the possibility of this ordering:
    //
    //     thread 1 calls take_hook, gets the user's original hook
    //     thread 1 calls set_hook with the null hook
    //     thread 2 calls take_hook, thinks null hook is the original hook
    //     thread 2 calls set_hook with the null hook
    //     thread 1 calls set_hook with the actual original hook
    //     thread 2 calls set_hook with what it thinks is the original hook
    //
    // in which the user's hook has been lost.
    //
    // There is still a race condition where a panic in a different thread can
    // happen during the interval that the user's original panic hook is
    // unregistered such that their hook is incorrectly not called. This is
    // sufficiently unlikely and less bad than printing panic messages to stderr
    // on correct use of this crate. Maybe there is a libstd feature request
    // here. For now, if a user needs to guarantee that this failure mode does
    // not occur, they need to call e.g. `proc_macro2::Span::call_site()` from
    // the main thread before launching any other threads.
    INIT.call_once(|| {
        type PanicHook = dyn Fn(&PanicInfo) + Sync + Send + 'static;

        let null_hook: Box<PanicHook> = Box::new(|_panic_info| { /* ignore */ });
        let sanity_check = &*null_hook as *const PanicHook;
        let original_hook = panic::take_hook();
        panic::set_hook(null_hook);

        let works = panic::catch_unwind(|| proc_macro::Span::call_site()).is_ok();
        WORKS.store(works as usize + 1, Ordering::SeqCst);

        let hopefully_null_hook = panic::take_hook();
        panic::set_hook(original_hook);
        if sanity_check != &*hopefully_null_hook {
            panic!("observed race condition in proc_macro2::nightly_works");
        }
    });

    nightly_works()
}