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: &RefCell>| 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: &RefCell>| {
139 let mut cell: RefMut<'_, Option> = old_dummy.borrow_mut();
140 if let Some(ts: &mut TokenStream) = cell.as_mut() {
141 ts.extend(iter:dummy);
142 } else {
143 *cell = Some(dummy);
144 }
145 });
146}
147
148pub(crate) fn cleanup() -> Option<TokenStream> {
149 DUMMY_IMPL.with(|old_dummy: &RefCell>| old_dummy.replace(None))
150}
151