Matthew Maurer | b26ed88 | 2020-06-02 11:15:09 -0700 | [diff] [blame] | 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). |
| 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. |
| 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 | |
| 148 | pub(crate) fn cleanup() -> Option<TokenStream> { |
| 149 | DUMMY_IMPL.with(|old_dummy| old_dummy.replace(None)) |
| 150 | } |