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
//! `compile_error!` does not interrupt compilation right away. This means
//! `rustc` doesn't just show you the error and abort, it carries on the
//! compilation process, looking for other errors to report.
//!
//! Let's consider an example:
//!
//! ```rust,ignore
//! trait MyTrait {
//!     fn do_thing();
//! }
//!
//! // this proc macro is supposed to generate MyTrait impl
//! #[proc_macro_derive(MyTrait)]
//! fn example(input: TokenStream) -> TokenStream {
//!     // somewhere deep inside
//!     span_error!(span, "something's wrong");
//!
//!     // this implementation will be generated if no error happened
//!     quote! {
//!         impl MyTrait for #name {
//!             fn do_thing() {/* whatever */}
//!         }
//!     }
//! }
//!
//! // ================
//! // in main.rs
//!
//! // this derive triggers an error
//! #[derive(MyTrait)] // first BOOM!
//! struct Foo;
//!
//! fn main() {
//!     Foo::do_thing(); // second BOOM!
//! }
//! ```
//!
//! The problem is: the generated token stream contains only `compile_error!`
//! invocation, the impl was not generated. That means user will see two compilation
//! errors:
//! ```text
//! error: set_dummy test
//!  --> $DIR/probe.rs:9:10
//!   |
//! 9 |#[proc_macro_derive(MyTrait)]
//!   |                    ^^^^^^^
//!
//! error[E0277]: the trait bound `Foo: Default` is not satisfied
//!
//!   --> $DIR/probe.rs:14:10
//!
//!    |
//! 98 |     #[derive(MyTrait)]
//!    |              ^^^^^^^ the trait `Default` is not implemented for `Foo`
//!
//! ```
//!
//! But the second error is meaningless! We definitely need to fix this.
//!
//! Most used approach in cases like this is "dummy implementation" -
//! omit `impl MyTrait for #name` and fill functions bodies with `unimplemented!()`.
//!
//! This is how you do it:
//! ```rust,ignore
//!  trait MyTrait {
//!      fn do_thing();
//!  }
//!
//!  // this proc macro is supposed to generate MyTrait impl
//!  #[proc_macro_derive(MyTrait)]
//!  fn example(input: TokenStream) -> TokenStream {
//!      // first of all - we set a dummy impl which will be appended to
//!      // `compile_error!` invocations in case a trigger does happen
//!      proc_macro_error::set_dummy(Some(quote! {
//!          impl MyTrait for #name {
//!              fn do_thing() { unimplemented!() }
//!          }
//!      }));
//!
//!      // somewhere deep inside
//!      span_error!(span, "something's wrong");
//!
//!      // this implementation will be generated if no error happened
//!      quote! {
//!          impl MyTrait for #name {
//!              fn do_thing() {/* whatever */}
//!          }
//!      }
//!  }
//!
//!  // ================
//!  // in main.rs
//!
//!  // this derive triggers an error
//!  #[derive(MyTrait)] // first BOOM!
//!  struct Foo;
//!
//!  fn main() {
//!      Foo::do_thing(); // no more errors!
//!  }
//! ```

use proc_macro2::TokenStream;
use std::cell::RefCell;

thread_local! {
    pub(crate) static DUMMY_IMPL: RefCell<Option<TokenStream>> = RefCell::new(None);
}

pub(crate) fn take_dummy() -> Option<TokenStream> {
    DUMMY_IMPL.with(|dummy| dummy.replace(None))
}

/// Sets dummy token stream which will be appended to `compile_error!(msg);...`
/// invocations, should a trigger happen. Returns an old dummy, if set.
///
/// # Warning:
/// If you do `set_dummy(Some(ts))` you **must** do `set_dummy(None)`
/// before macro execution completes (`filer_macro_errors!` will do that for you)!
/// Otherwise `rustc` will fail with creepy
/// ```text
/// thread 'rustc' panicked at 'use-after-free in `proc_macro` handle', src\libcore\option.rs:1166:5
/// note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
/// error: proc-macro derive panicked
/// ```
pub fn set_dummy(dummy: Option<TokenStream>) -> Option<TokenStream> {
    DUMMY_IMPL.with(|old_dummy| old_dummy.replace(dummy))
}