proc_macro_error/
dummy.rs

1//! Facility to emit dummy implementations (or whatever) in case
2//! an error happen.
3//!
4//! `compile_error!` does not abort a compilation right away. This means
5//! `rustc` doesn't just show you the error and abort, it carries on the
6//! compilation process looking for other errors to report.
7//!
8//! Let's consider an example:
9//!
10//! ```rust,ignore
11//! use proc_macro::TokenStream;
12//! use proc_macro_error::*;
13//!
14//! trait MyTrait {
15//!     fn do_thing();
16//! }
17//!
18//! // this proc macro is supposed to generate MyTrait impl
19//! #[proc_macro_derive(MyTrait)]
20//! #[proc_macro_error]
21//! fn example(input: TokenStream) -> TokenStream {
22//!     // somewhere deep inside
23//!     abort!(span, "something's wrong");
24//!
25//!     // this implementation will be generated if no error happened
26//!     quote! {
27//!         impl MyTrait for #name {
28//!             fn do_thing() {/* whatever */}
29//!         }
30//!     }
31//! }
32//!
33//! // ================
34//! // in main.rs
35//!
36//! // this derive triggers an error
37//! #[derive(MyTrait)] // first BOOM!
38//! struct Foo;
39//!
40//! fn main() {
41//!     Foo::do_thing(); // second BOOM!
42//! }
43//! ```
44//!
45//! The problem is: the generated token stream contains only `compile_error!`
46//! invocation, the impl was not generated. That means user will see two compilation
47//! errors:
48//!
49//! ```text
50//! error: something's wrong
51//!  --> $DIR/probe.rs:9:10
52//!   |
53//! 9 |#[proc_macro_derive(MyTrait)]
54//!   |                    ^^^^^^^
55//!
56//! error[E0599]: no function or associated item named `do_thing` found for type `Foo` in the current scope
57//!  --> src\main.rs:3:10
58//!   |
59//! 1 | struct Foo;
60//!   | ----------- function or associated item `do_thing` not found for this
61//! 2 | fn main() {
62//! 3 |     Foo::do_thing(); // second BOOM!
63//!   |          ^^^^^^^^ function or associated item not found in `Foo`
64//! ```
65//!
66//! But the second error is meaningless! We definitely need to fix this.
67//!
68//! Most used approach in cases like this is "dummy implementation" -
69//! omit `impl MyTrait for #name` and fill functions bodies with `unimplemented!()`.
70//!
71//! This is how you do it:
72//!
73//! ```rust,ignore
74//! use proc_macro::TokenStream;
75//! use proc_macro_error::*;
76//!
77//!  trait MyTrait {
78//!      fn do_thing();
79//!  }
80//!
81//!  // this proc macro is supposed to generate MyTrait impl
82//!  #[proc_macro_derive(MyTrait)]
83//!  #[proc_macro_error]
84//!  fn example(input: TokenStream) -> TokenStream {
85//!      // first of all - we set a dummy impl which will be appended to
86//!      // `compile_error!` invocations in case a trigger does happen
87//!      set_dummy(quote! {
88//!          impl MyTrait for #name {
89//!              fn do_thing() { unimplemented!() }
90//!          }
91//!      });
92//!
93//!      // somewhere deep inside
94//!      abort!(span, "something's wrong");
95//!
96//!      // this implementation will be generated if no error happened
97//!      quote! {
98//!          impl MyTrait for #name {
99//!              fn do_thing() {/* whatever */}
100//!          }
101//!      }
102//!  }
103//!
104//!  // ================
105//!  // in main.rs
106//!
107//!  // this derive triggers an error
108//!  #[derive(MyTrait)] // first BOOM!
109//!  struct Foo;
110//!
111//!  fn main() {
112//!      Foo::do_thing(); // no more errors!
113//!  }
114//! ```
115
116use proc_macro2::TokenStream;
117use std::cell::RefCell;
118
119use crate::check_correctness;
120
121thread_local! {
122    static DUMMY_IMPL: RefCell<Option<TokenStream>> = RefCell::new(None);
123}
124
125/// Sets dummy token stream which will be appended to `compile_error!(msg);...`
126/// invocations in case you'll emit any errors.
127///
128/// See [guide](../index.html#guide).
129pub fn set_dummy(dummy: TokenStream) -> Option<TokenStream> {
130    check_correctness();
131    DUMMY_IMPL.with(|old_dummy| old_dummy.replace(Some(dummy)))
132}
133
134/// Same as [`set_dummy`] but, instead of resetting, appends tokens to the
135/// existing dummy (if any). Behaves as `set_dummy` if no dummy is present.
136pub fn append_dummy(dummy: TokenStream) {
137    check_correctness();
138    DUMMY_IMPL.with(|old_dummy| {
139        let mut cell = old_dummy.borrow_mut();
140        if let Some(ts) = cell.as_mut() {
141            ts.extend(dummy);
142        } else {
143            *cell = Some(dummy);
144        }
145    });
146}
147
148pub(crate) fn cleanup() -> Option<TokenStream> {
149    DUMMY_IMPL.with(|old_dummy| old_dummy.replace(None))
150}