1
- use proc_macro:: { Diagnostic , Span } ;
1
+ //! This module has two purposes:
2
+ //!
3
+ //! 1. Provide the convenience method `emit_with_attr_note` and add it via
4
+ //! extension trait to `Diagnostic`.
5
+ //!
6
+ //! 2. Make `Diagnostic` work on stable by providing an own `Diagnostic` type
7
+ //! that prints the messages in a less-nice way. That way, other modules
8
+ //! don't have to worry about the stable/nightly distinction. `SpanExt` is
9
+ //! an extension trait that adds the `err()` method to the `Span` type. That
10
+ //! method works exactly like `Span::error()` but returns "our"
11
+ //! `Diagnostic`. Other modules can simply `use diag::SpanExt` and use
12
+ //! `.err()` on spans.
13
+ //!
2
14
15
+ use proc_macro:: { Span , TokenStream } ;
3
16
17
+
18
+ /// Extension trait that adds a convenience method to `Diagnostic`. This is
19
+ /// simply to reduce duplicate code in other modules.
4
20
pub trait DiagnosticExt {
5
21
/// Helper function to add a note to the diagnostic (with a span pointing
6
- /// to the `auto_impl` attribute) and emit the error. Additionally,
7
- /// `Err(())` is always returned.
22
+ /// to the `auto_impl` attribute) and emit the error. An `Err(())` is
23
+ /// always returned.
8
24
fn emit_with_attr_note < T > ( self ) -> Result < T , ( ) > ;
9
25
}
10
26
@@ -16,3 +32,152 @@ impl DiagnosticExt for Diagnostic {
16
32
Err ( ( ) )
17
33
}
18
34
}
35
+
36
+
37
+ // ==============================================================
38
+ // Logic for stable/nightly mode starts here.
39
+ //
40
+ // First, we define a `Diagnostic` type. If we compile with the `nightly`
41
+ // feature, it's simple a typedef to `proc_macro::Diagnostic`. If we don't
42
+ // compile in nightly mode, we can't use that type, since it's still unstable.
43
+ // So in that case, we define our own type that tries to mimic the original
44
+ // `Diagnostic`.
45
+
46
+ #[ cfg( feature = "nightly" ) ]
47
+ crate type Diagnostic = :: proc_macro:: Diagnostic ;
48
+
49
+ #[ cfg( not( feature = "nightly" ) ) ]
50
+ crate struct Diagnostic {
51
+ span : Span ,
52
+ msg : String ,
53
+ }
54
+
55
+ // We provide the methods that `proc_macro::Diagnostic` also has here. Or
56
+ // rather: we only implement the subset that this crate actually uses.
57
+ //
58
+ // When we're not on the nightly compiler, we can't show a nice error. So how
59
+ // do we show the error then? The idea is to generate a token stream that
60
+ // contains `compile_error!(msg)` macro invocations. This macro is part of the
61
+ // standard library and emits `msg` as error. This is fairly useful for our
62
+ // case. However, a big limitation is that we can only emit one message. So in
63
+ // order to also show notes later added to the `Diagnostic`, we simply add
64
+ // "note: {the_note}" to the error string. This is crude and ugly, but it
65
+ // works.
66
+ //
67
+ // What about spans? Good question! Spans are important, but without a proper
68
+ // `Diagnostic` API, we can't properly support spans on errors and notes. The
69
+ // compiler will point to the `compile_error!()` invocation we generate. But we
70
+ // can use another hack to improve the situation slightly! On the token stream
71
+ // (containing `compile_error!()`) we generate, we can modify the spans of the
72
+ // individual token trees. If we set all spans to the span the error originates
73
+ // from, the compiler thinks that the `compile_error!()` code snippet has the
74
+ // span from the actual error source. That means that the error message will
75
+ // point to the actual error source!
76
+ //
77
+ // There is only a small problem: this only works when we get a proper span.
78
+ // Sadly, on stable, we can only get correct spans for individual token trees,
79
+ // not even token streams. We can't combine spans. As a consequence, spans are
80
+ // only correct if they come directly from a `TokenTree`. In general, errors
81
+ // coming from the `proxy` module have proper spans while errors from other
82
+ // modules don't have proper spans (on stable!). "Not proper" means that the
83
+ // span is simply `call_site()` -- it points to the `#[auto_impl()]` attribute.
84
+ //
85
+ // It could be worse, but it's simply true: for good error messages, nightly is
86
+ // required.
87
+ #[ cfg( not( feature = "nightly" ) ) ]
88
+ impl Diagnostic {
89
+ crate fn note ( mut self , msg : impl Into < String > ) -> Diagnostic {
90
+ self . msg += & format ! ( "\n \n note: {}" , msg. into( ) ) ;
91
+ self
92
+ }
93
+
94
+ crate fn span_note ( mut self , _: Span , msg : impl Into < String > ) -> Diagnostic {
95
+ // With out span fake method, we can only handle one span. We take the
96
+ // one of the original error and ignore additional ones.
97
+ self . msg += & format ! ( "\n \n note: {}" , msg. into( ) ) ;
98
+ self
99
+ }
100
+
101
+ crate fn emit ( self ) {
102
+ // Create the error token stream that contains the `compile_error!()`
103
+ // invocation.
104
+ let msg = & self . msg ;
105
+ let tokens = TokenStream :: from ( quote ! {
106
+ compile_error!( #msg) ;
107
+ } ) ;
108
+
109
+ // Set the span of each token tree to the span the error originates
110
+ // from.
111
+ let tokens = tokens. into_iter ( )
112
+ . map ( |mut tt| {
113
+ tt. set_span ( self . span ) ;
114
+ tt
115
+ } )
116
+ . collect ( ) ;
117
+
118
+ // Push it to the global list of error streams
119
+ ERROR_TOKENS . with ( |toks| {
120
+ toks. borrow_mut ( ) . push ( tokens)
121
+ } ) ;
122
+ }
123
+ }
124
+
125
+ // Another problem with our `Diagnostic` hack on stable: the real
126
+ // `Diagnostic::emit()` doesn't return anything and modifies global state (it
127
+ // prints directly to stdout). We can't simply print! In our case it would be
128
+ // correct to pass a `TokenStream` ass the `Err()` variant of a result back up
129
+ // the stack and display it at the end. Two problems with that approach:
130
+ //
131
+ // - That's not how this application was build. Instead, it's build with the
132
+ // future `proc_macro` API in mind. And we wouldn't want to change everything
133
+ // back once it's stable.
134
+ // - On nightly, we don't want to pass TokenStreams up the stack. We can't have
135
+ // a completely different structure on nightly vs. on stable.
136
+ //
137
+ // Thus, we just "simulate" the original `emit()` by also modifying global
138
+ // state. We simply have a list of error token streams. This list is added to
139
+ // the final token stream at the end (in case of an error). It's not a very
140
+ // nice solution, but it's only a hack while stable doesn't offer something
141
+ // proper.
142
+ #[ cfg( not( feature = "nightly" ) ) ]
143
+ use std:: cell:: RefCell ;
144
+
145
+ #[ cfg( not( feature = "nightly" ) ) ]
146
+ thread_local ! {
147
+ static ERROR_TOKENS : RefCell <Vec <TokenStream >> = RefCell :: new( vec![ ] ) ;
148
+ }
149
+
150
+ /// On stable, we just copy the error token streams from the global variable.
151
+ #[ cfg( not( feature = "nightly" ) ) ]
152
+ crate fn error_tokens ( ) -> TokenStream {
153
+ ERROR_TOKENS . with ( |toks| toks. borrow ( ) . iter ( ) . cloned ( ) . collect ( ) )
154
+ }
155
+
156
+ /// On nightly, we don't use and don't have a strange global variable. Instead,
157
+ /// we just return an empty token stream. That's not a problem because all of
158
+ /// our errors were already printed.
159
+ #[ cfg( feature = "nightly" ) ]
160
+ crate fn error_tokens ( ) -> TokenStream {
161
+ TokenStream :: new ( )
162
+ }
163
+
164
+ /// Extension trait to add the `err()` method to `Span`. This makes it easy to
165
+ /// start a `Diagnostic` from a span.
166
+ crate trait SpanExt {
167
+ fn err ( self , msg : impl Into < String > ) -> Diagnostic ;
168
+ }
169
+
170
+ impl SpanExt for Span {
171
+ #[ cfg( feature = "nightly" ) ]
172
+ fn err ( self , msg : impl Into < String > ) -> Diagnostic {
173
+ Diagnostic :: spanned ( self , :: proc_macro:: Level :: Error , msg)
174
+ }
175
+
176
+ #[ cfg( not( feature = "nightly" ) ) ]
177
+ fn err ( self , msg : impl Into < String > ) -> Diagnostic {
178
+ Diagnostic {
179
+ span : self ,
180
+ msg : msg. into ( ) ,
181
+ }
182
+ }
183
+ }
0 commit comments