• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
116 use proc_macro2::TokenStream;
117 use std::cell::RefCell;
118 
119 use crate::check_correctness;
120 
121 thread_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).
set_dummy(dummy: TokenStream) -> Option<TokenStream>129 pub 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.
append_dummy(dummy: TokenStream)136 pub 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 
cleanup() -> Option<TokenStream>148 pub(crate) fn cleanup() -> Option<TokenStream> {
149     DUMMY_IMPL.with(|old_dummy| old_dummy.replace(None))
150 }
151