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