@@ -16,7 +16,7 @@ use tracing::debug;
16
16
#[ must_use]
17
17
#[ derive( Clone ) ]
18
18
pub struct DiagnosticBuilder < ' a > {
19
- handler : & ' a Handler ,
19
+ state : DiagnosticBuilderState < ' a > ,
20
20
21
21
/// `Diagnostic` is a large type, and `DiagnosticBuilder` is often used as a
22
22
/// return value, especially within the frequently-used `PResult` type.
@@ -25,6 +25,34 @@ pub struct DiagnosticBuilder<'a> {
25
25
diagnostic : Box < Diagnostic > ,
26
26
}
27
27
28
+ #[ derive( Clone ) ]
29
+ enum DiagnosticBuilderState < ' a > {
30
+ /// Initial state of a `DiagnosticBuilder`, before `.emit()` or `.cancel()`.
31
+ ///
32
+ /// The `Diagnostic` will be emitted through this `Handler`.
33
+ Emittable ( & ' a Handler ) ,
34
+
35
+ /// State of a `DiagnosticBuilder`, after `.emit()` or *during* `.cancel()`.
36
+ ///
37
+ /// The `Diagnostic` will be ignored when calling `.emit()`, and it can be
38
+ /// assumed that `.emit()` was previously called, to end up in this state.
39
+ ///
40
+ /// While this is also used by `.cancel()`, this state is only observed by
41
+ /// the `Drop` `impl` of `DiagnosticBuilder`, as `.cancel()` takes `self`
42
+ /// by-value specifically to prevent any attempts to `.emit()`.
43
+ ///
44
+ // FIXME(eddyb) currently this doesn't prevent extending the `Diagnostic`,
45
+ // despite that being potentially lossy, if important information is added
46
+ // *after* the original `.emit()` call.
47
+ AlreadyEmittedOrDuringCancellation ,
48
+ }
49
+
50
+ // `DiagnosticBuilderState` should be pointer-sized.
51
+ rustc_data_structures:: static_assert_size!(
52
+ DiagnosticBuilderState <' _>,
53
+ std:: mem:: size_of:: <& Handler >( )
54
+ ) ;
55
+
28
56
/// In general, the `DiagnosticBuilder` uses deref to allow access to
29
57
/// the fields and methods of the embedded `diagnostic` in a
30
58
/// transparent way. *However,* many of the methods are intended to
@@ -78,8 +106,18 @@ impl<'a> DerefMut for DiagnosticBuilder<'a> {
78
106
impl < ' a > DiagnosticBuilder < ' a > {
79
107
/// Emit the diagnostic.
80
108
pub fn emit ( & mut self ) {
81
- self . handler . emit_diagnostic ( & self ) ;
82
- self . cancel ( ) ;
109
+ match self . state {
110
+ // First `.emit()` call, the `&Handler` is still available.
111
+ DiagnosticBuilderState :: Emittable ( handler) => {
112
+ handler. emit_diagnostic ( & self ) ;
113
+ self . state = DiagnosticBuilderState :: AlreadyEmittedOrDuringCancellation ;
114
+ }
115
+ // `.emit()` was previously called, disallowed from repeating it.
116
+ DiagnosticBuilderState :: AlreadyEmittedOrDuringCancellation => {
117
+ // FIXME(eddyb) rely on this to return a "proof" that an error
118
+ // was/will be emitted, despite doing no emission *here and now*.
119
+ }
120
+ }
83
121
}
84
122
85
123
/// Emit the diagnostic unless `delay` is true,
@@ -93,6 +131,17 @@ impl<'a> DiagnosticBuilder<'a> {
93
131
self . emit ( ) ;
94
132
}
95
133
134
+ /// Cancel the diagnostic (a structured diagnostic must either be emitted or
135
+ /// cancelled or it will panic when dropped).
136
+ ///
137
+ /// This method takes `self` by-value to disallow calling `.emit()` on it,
138
+ /// which may be expected to *guarantee* the emission of an error, either
139
+ /// at the time of the call, or through a prior `.emit()` call.
140
+ pub fn cancel ( mut self ) {
141
+ self . state = DiagnosticBuilderState :: AlreadyEmittedOrDuringCancellation ;
142
+ drop ( self ) ;
143
+ }
144
+
96
145
/// Stashes diagnostic for possible later improvement in a different,
97
146
/// later stage of the compiler. The diagnostic can be accessed with
98
147
/// the provided `span` and `key` through [`Handler::steal_diagnostic()`].
@@ -105,22 +154,29 @@ impl<'a> DiagnosticBuilder<'a> {
105
154
}
106
155
107
156
/// Converts the builder to a `Diagnostic` for later emission,
108
- /// unless handler has disabled such buffering.
157
+ /// unless handler has disabled such buffering, or `.emit()` was called .
109
158
pub fn into_diagnostic ( mut self ) -> Option < ( Diagnostic , & ' a Handler ) > {
110
- if self . handler . flags . dont_buffer_diagnostics
111
- || self . handler . flags . treat_err_as_bug . is_some ( )
112
- {
159
+ let handler = match self . state {
160
+ // No `.emit()` calls, the `&Handler` is still available.
161
+ DiagnosticBuilderState :: Emittable ( handler) => handler,
162
+ // `.emit()` was previously called, nothing we can do.
163
+ DiagnosticBuilderState :: AlreadyEmittedOrDuringCancellation => {
164
+ return None ;
165
+ }
166
+ } ;
167
+
168
+ if handler. flags . dont_buffer_diagnostics || handler. flags . treat_err_as_bug . is_some ( ) {
113
169
self . emit ( ) ;
114
170
return None ;
115
171
}
116
172
117
- let handler = self . handler ;
118
-
119
- // We must use `Level::Cancelled` for `dummy` to avoid an ICE about an
120
- // unused diagnostic.
121
- let dummy = Diagnostic :: new ( Level :: Cancelled , "" ) ;
173
+ // Take the `Diagnostic` by replacing it with a dummy.
174
+ let dummy = Diagnostic :: new ( Level :: Allow , "" ) ;
122
175
let diagnostic = std:: mem:: replace ( & mut * self . diagnostic , dummy) ;
123
176
177
+ // Disable the ICE on `Drop`.
178
+ self . cancel ( ) ;
179
+
124
180
// Logging here is useful to help track down where in logs an error was
125
181
// actually emitted.
126
182
debug ! ( "buffer: diagnostic={:?}" , diagnostic) ;
@@ -314,7 +370,10 @@ impl<'a> DiagnosticBuilder<'a> {
314
370
/// diagnostic.
315
371
crate fn new_diagnostic ( handler : & ' a Handler , diagnostic : Diagnostic ) -> DiagnosticBuilder < ' a > {
316
372
debug ! ( "Created new diagnostic" ) ;
317
- DiagnosticBuilder { handler, diagnostic : Box :: new ( diagnostic) }
373
+ DiagnosticBuilder {
374
+ state : DiagnosticBuilderState :: Emittable ( handler) ,
375
+ diagnostic : Box :: new ( diagnostic) ,
376
+ }
318
377
}
319
378
}
320
379
@@ -324,19 +383,26 @@ impl<'a> Debug for DiagnosticBuilder<'a> {
324
383
}
325
384
}
326
385
327
- /// Destructor bomb - a `DiagnosticBuilder` must be either emitted or canceled
386
+ /// Destructor bomb - a `DiagnosticBuilder` must be either emitted or cancelled
328
387
/// or we emit a bug.
329
388
impl < ' a > Drop for DiagnosticBuilder < ' a > {
330
389
fn drop ( & mut self ) {
331
- if !panicking ( ) && !self . cancelled ( ) {
332
- let mut db = DiagnosticBuilder :: new (
333
- self . handler ,
334
- Level :: Bug ,
335
- "the following error was constructed but not emitted" ,
336
- ) ;
337
- db. emit ( ) ;
338
- self . emit ( ) ;
339
- panic ! ( ) ;
390
+ match self . state {
391
+ // No `.emit()` or `.cancel()` calls.
392
+ DiagnosticBuilderState :: Emittable ( handler) => {
393
+ if !panicking ( ) {
394
+ let mut db = DiagnosticBuilder :: new (
395
+ handler,
396
+ Level :: Bug ,
397
+ "the following error was constructed but not emitted" ,
398
+ ) ;
399
+ db. emit ( ) ;
400
+ handler. emit_diagnostic ( & self ) ;
401
+ panic ! ( ) ;
402
+ }
403
+ }
404
+ // `.emit()` was previously called, or maybe we're during `.cancel()`.
405
+ DiagnosticBuilderState :: AlreadyEmittedOrDuringCancellation => { }
340
406
}
341
407
}
342
408
}
0 commit comments