@@ -35,19 +35,28 @@ where
35
35
}
36
36
37
37
/// Used for emitting structured error messages and other diagnostic information.
38
+ /// Each constructed `DiagnosticBuilder` must be consumed by a function such as
39
+ /// `emit`, `cancel`, `delay_as_bug`, or `into_diagnostic. A panic occurrs if a
40
+ /// `DiagnosticBuilder` is dropped without being consumed by one of these
41
+ /// functions.
38
42
///
39
43
/// If there is some state in a downstream crate you would like to
40
44
/// access in the methods of `DiagnosticBuilder` here, consider
41
45
/// extending `DiagCtxtFlags`.
42
46
#[ must_use]
43
47
pub struct DiagnosticBuilder < ' a , G : EmissionGuarantee = ErrorGuaranteed > {
44
- state : DiagnosticBuilderState < ' a > ,
48
+ pub dcx : & ' a DiagCtxt ,
45
49
46
- /// `Diagnostic` is a large type, and `DiagnosticBuilder` is often used as a
47
- /// return value, especially within the frequently-used `PResult` type.
48
- /// In theory, return value optimization (RVO) should avoid unnecessary
49
- /// copying. In practice, it does not (at the time of writing).
50
- diagnostic : Box < Diagnostic > ,
50
+ /// Why the `Option`? It is always `Some` until the `DiagnosticBuilder` is
51
+ /// consumed via `emit`, `cancel`, etc. At that point it is consumed and
52
+ /// replaced with `None`. Then `drop` checks that it is `None`; if not, it
53
+ /// panics because a diagnostic was built but not used.
54
+ ///
55
+ /// Why the Box? `Diagnostic` is a large type, and `DiagnosticBuilder` is
56
+ /// often used as a return value, especially within the frequently-used
57
+ /// `PResult` type. In theory, return value optimization (RVO) should avoid
58
+ /// unnecessary copying. In practice, it does not (at the time of writing).
59
+ diag : Option < Box < Diagnostic > > ,
51
60
52
61
_marker : PhantomData < G > ,
53
62
}
@@ -56,32 +65,9 @@ pub struct DiagnosticBuilder<'a, G: EmissionGuarantee = ErrorGuaranteed> {
56
65
// twice, which would be bad.
57
66
impl < G > !Clone for DiagnosticBuilder < ' _ , G > { }
58
67
59
- #[ derive( Clone ) ]
60
- enum DiagnosticBuilderState < ' a > {
61
- /// Initial state of a `DiagnosticBuilder`, before `.emit()` or `.cancel()`.
62
- ///
63
- /// The `Diagnostic` will be emitted through this `DiagCtxt`.
64
- Emittable ( & ' a DiagCtxt ) ,
65
-
66
- /// State of a `DiagnosticBuilder`, after `.emit()` or *during* `.cancel()`.
67
- ///
68
- /// The `Diagnostic` will be ignored when calling `.emit()`, and it can be
69
- /// assumed that `.emit()` was previously called, to end up in this state.
70
- ///
71
- /// While this is also used by `.cancel()`, this state is only observed by
72
- /// the `Drop` `impl` of `DiagnosticBuilder`, because `.cancel()` takes
73
- /// `self` by-value specifically to prevent any attempts to `.emit()`.
74
- ///
75
- // FIXME(eddyb) currently this doesn't prevent extending the `Diagnostic`,
76
- // despite that being potentially lossy, if important information is added
77
- // *after* the original `.emit()` call.
78
- AlreadyEmittedOrDuringCancellation ,
79
- }
80
-
81
- // `DiagnosticBuilderState` should be pointer-sized.
82
68
rustc_data_structures:: static_assert_size!(
83
- DiagnosticBuilderState <' _>,
84
- std:: mem:: size_of:: <& DiagCtxt >( )
69
+ DiagnosticBuilder <' _, ( ) >,
70
+ 2 * std:: mem:: size_of:: <usize >( )
85
71
) ;
86
72
87
73
/// Trait for types that `DiagnosticBuilder::emit` can return as a "guarantee"
@@ -99,62 +85,44 @@ pub trait EmissionGuarantee: Sized {
99
85
}
100
86
101
87
impl < ' a , G : EmissionGuarantee > DiagnosticBuilder < ' a , G > {
88
+ /// Takes the diagnostic. For use by methods that consume the
89
+ /// DiagnosticBuilder: `emit`, `cancel`, etc. Afterwards, `drop` is the
90
+ /// only code that will be run on `self`.
91
+ fn take_diag ( & mut self ) -> Diagnostic {
92
+ Box :: into_inner ( self . diag . take ( ) . unwrap ( ) )
93
+ }
94
+
102
95
/// Most `emit_producing_guarantee` functions use this as a starting point.
103
96
fn emit_producing_nothing ( mut self ) {
104
- match self . state {
105
- // First `.emit()` call, the `&DiagCtxt` is still available.
106
- DiagnosticBuilderState :: Emittable ( dcx) => {
107
- self . state = DiagnosticBuilderState :: AlreadyEmittedOrDuringCancellation ;
108
- dcx. emit_diagnostic_without_consuming ( & mut self . diagnostic ) ;
109
- }
110
- // `.emit()` was previously called, disallowed from repeating it.
111
- DiagnosticBuilderState :: AlreadyEmittedOrDuringCancellation => { }
112
- }
97
+ let diag = self . take_diag ( ) ;
98
+ self . dcx . emit_diagnostic ( diag) ;
99
+ }
100
+
101
+ /// `ErrorGuaranteed::emit_producing_guarantee` uses this.
102
+ // FIXME(eddyb) make `ErrorGuaranteed` impossible to create outside `.emit()`.
103
+ fn emit_producing_error_guaranteed ( mut self ) -> ErrorGuaranteed {
104
+ let diag = self . take_diag ( ) ;
105
+
106
+ // Only allow a guarantee if the `level` wasn't switched to a
107
+ // non-error. The field isn't `pub`, but the whole `Diagnostic` can be
108
+ // overwritten with a new one, thanks to `DerefMut`.
109
+ assert ! (
110
+ diag. is_error( ) ,
111
+ "emitted non-error ({:?}) diagnostic from `DiagnosticBuilder<ErrorGuaranteed>`" ,
112
+ diag. level,
113
+ ) ;
114
+
115
+ let guar = self . dcx . emit_diagnostic ( diag) ;
116
+ guar. unwrap ( )
113
117
}
114
118
}
115
119
116
- // FIXME(eddyb) make `ErrorGuaranteed` impossible to create outside `.emit()`.
117
120
impl EmissionGuarantee for ErrorGuaranteed {
118
- fn emit_producing_guarantee ( mut db : DiagnosticBuilder < ' _ , Self > ) -> Self :: EmitResult {
119
- // Contrast this with `emit_producing_nothing`.
120
- match db. state {
121
- // First `.emit()` call, the `&DiagCtxt` is still available.
122
- DiagnosticBuilderState :: Emittable ( dcx) => {
123
- db. state = DiagnosticBuilderState :: AlreadyEmittedOrDuringCancellation ;
124
- let guar = dcx. emit_diagnostic_without_consuming ( & mut db. diagnostic ) ;
125
-
126
- // Only allow a guarantee if the `level` wasn't switched to a
127
- // non-error - the field isn't `pub`, but the whole `Diagnostic`
128
- // can be overwritten with a new one, thanks to `DerefMut`.
129
- assert ! (
130
- db. diagnostic. is_error( ) ,
131
- "emitted non-error ({:?}) diagnostic \
132
- from `DiagnosticBuilder<ErrorGuaranteed>`",
133
- db. diagnostic. level,
134
- ) ;
135
- guar. unwrap ( )
136
- }
137
- // `.emit()` was previously called, disallowed from repeating it,
138
- // but can take advantage of the previous `.emit()`'s guarantee
139
- // still being applicable (i.e. as a form of idempotency).
140
- DiagnosticBuilderState :: AlreadyEmittedOrDuringCancellation => {
141
- // Only allow a guarantee if the `level` wasn't switched to a
142
- // non-error - the field isn't `pub`, but the whole `Diagnostic`
143
- // can be overwritten with a new one, thanks to `DerefMut`.
144
- assert ! (
145
- db. diagnostic. is_error( ) ,
146
- "`DiagnosticBuilder<ErrorGuaranteed>`'s diagnostic \
147
- became non-error ({:?}), after original `.emit()`",
148
- db. diagnostic. level,
149
- ) ;
150
- #[ allow( deprecated) ]
151
- ErrorGuaranteed :: unchecked_claim_error_was_emitted ( )
152
- }
153
- }
121
+ fn emit_producing_guarantee ( db : DiagnosticBuilder < ' _ , Self > ) -> Self :: EmitResult {
122
+ db. emit_producing_error_guaranteed ( )
154
123
}
155
124
}
156
125
157
- // FIXME(eddyb) should there be a `Option<ErrorGuaranteed>` impl as well?
158
126
impl EmissionGuarantee for ( ) {
159
127
fn emit_producing_guarantee ( db : DiagnosticBuilder < ' _ , Self > ) -> Self :: EmitResult {
160
128
db. emit_producing_nothing ( ) ;
@@ -206,7 +174,7 @@ macro_rules! forward0 {
206
174
$( #[ $attrs] ) *
207
175
#[ doc = concat!( "See [`Diagnostic::" , stringify!( $n) , "()`]." ) ]
208
176
pub fn $n( & mut self $( , $name: $ty) * ) -> & mut Self {
209
- self . diagnostic . $n( $( $name) ,* ) ;
177
+ self . diag . as_mut ( ) . unwrap ( ) . $n( $( $name) ,* ) ;
210
178
self
211
179
}
212
180
} ;
@@ -227,12 +195,12 @@ macro_rules! forward {
227
195
) => {
228
196
#[ doc = concat!( "See [`Diagnostic::" , stringify!( $n) , "()`]." ) ]
229
197
pub fn $n( & mut self , $( $name: $ty) ,* ) -> & mut Self {
230
- self . diagnostic . $n( $( $name) ,* ) ;
198
+ self . diag . as_mut ( ) . unwrap ( ) . $n( $( $name) ,* ) ;
231
199
self
232
200
}
233
201
#[ doc = concat!( "See [`Diagnostic::" , stringify!( $n_mv) , "()`]." ) ]
234
202
pub fn $n_mv( mut self , $( $name: $ty) ,* ) -> Self {
235
- self . diagnostic . $n( $( $name) ,* ) ;
203
+ self . diag . as_mut ( ) . unwrap ( ) . $n( $( $name) ,* ) ;
236
204
self
237
205
}
238
206
} ;
@@ -242,13 +210,13 @@ impl<G: EmissionGuarantee> Deref for DiagnosticBuilder<'_, G> {
242
210
type Target = Diagnostic ;
243
211
244
212
fn deref ( & self ) -> & Diagnostic {
245
- & self . diagnostic
213
+ self . diag . as_ref ( ) . unwrap ( )
246
214
}
247
215
}
248
216
249
217
impl < G : EmissionGuarantee > DerefMut for DiagnosticBuilder < ' _ , G > {
250
218
fn deref_mut ( & mut self ) -> & mut Diagnostic {
251
- & mut self . diagnostic
219
+ self . diag . as_mut ( ) . unwrap ( )
252
220
}
253
221
}
254
222
@@ -262,13 +230,9 @@ impl<'a, G: EmissionGuarantee> DiagnosticBuilder<'a, G> {
262
230
/// Creates a new `DiagnosticBuilder` with an already constructed
263
231
/// diagnostic.
264
232
#[ track_caller]
265
- pub ( crate ) fn new_diagnostic ( dcx : & ' a DiagCtxt , diagnostic : Diagnostic ) -> Self {
233
+ pub ( crate ) fn new_diagnostic ( dcx : & ' a DiagCtxt , diag : Diagnostic ) -> Self {
266
234
debug ! ( "Created new diagnostic" ) ;
267
- Self {
268
- state : DiagnosticBuilderState :: Emittable ( dcx) ,
269
- diagnostic : Box :: new ( diagnostic) ,
270
- _marker : PhantomData ,
271
- }
235
+ Self { dcx, diag : Some ( Box :: new ( diag) ) , _marker : PhantomData }
272
236
}
273
237
274
238
/// Emit and consume the diagnostic.
@@ -289,14 +253,10 @@ impl<'a, G: EmissionGuarantee> DiagnosticBuilder<'a, G> {
289
253
self . emit ( )
290
254
}
291
255
292
- /// Cancel the diagnostic (a structured diagnostic must either be emitted or
256
+ /// Cancel and consume the diagnostic. (A diagnostic must either be emitted or
293
257
/// cancelled or it will panic when dropped).
294
- ///
295
- /// This method takes `self` by-value to disallow calling `.emit()` on it,
296
- /// which may be expected to *guarantee* the emission of an error, either
297
- /// at the time of the call, or through a prior `.emit()` call.
298
258
pub fn cancel ( mut self ) {
299
- self . state = DiagnosticBuilderState :: AlreadyEmittedOrDuringCancellation ;
259
+ self . diag = None ;
300
260
drop ( self ) ;
301
261
}
302
262
@@ -312,44 +272,21 @@ impl<'a, G: EmissionGuarantee> DiagnosticBuilder<'a, G> {
312
272
}
313
273
314
274
/// Converts the builder to a `Diagnostic` for later emission,
315
- /// unless dcx has disabled such buffering, or `.emit()` was called .
275
+ /// unless dcx has disabled such buffering.
316
276
pub fn into_diagnostic ( mut self ) -> Option < ( Diagnostic , & ' a DiagCtxt ) > {
317
- let dcx = match self . state {
318
- // No `.emit()` calls, the `&DiagCtxt` is still available.
319
- DiagnosticBuilderState :: Emittable ( dcx) => dcx,
320
- // `.emit()` was previously called, nothing we can do.
321
- DiagnosticBuilderState :: AlreadyEmittedOrDuringCancellation => {
322
- return None ;
323
- }
324
- } ;
325
-
326
- if dcx. inner . lock ( ) . flags . dont_buffer_diagnostics
327
- || dcx. inner . lock ( ) . flags . treat_err_as_bug . is_some ( )
328
- {
277
+ let flags = self . dcx . inner . lock ( ) . flags ;
278
+ if flags. dont_buffer_diagnostics || flags. treat_err_as_bug . is_some ( ) {
329
279
self . emit ( ) ;
330
280
return None ;
331
281
}
332
282
333
- // Take the `Diagnostic` by replacing it with a dummy.
334
- let dummy = Diagnostic :: new ( Level :: Allow , DiagnosticMessage :: from ( "" ) ) ;
335
- let diagnostic = std:: mem:: replace ( & mut * self . diagnostic , dummy) ;
336
-
337
- // Disable the ICE on `Drop`.
338
- self . cancel ( ) ;
283
+ let diag = self . take_diag ( ) ;
339
284
340
285
// Logging here is useful to help track down where in logs an error was
341
286
// actually emitted.
342
- debug ! ( "buffer: diagnostic ={:?}" , diagnostic ) ;
287
+ debug ! ( "buffer: diag ={:?}" , diag ) ;
343
288
344
- Some ( ( diagnostic, dcx) )
345
- }
346
-
347
- /// Retrieves the [`DiagCtxt`] if available
348
- pub fn dcx ( & self ) -> Option < & DiagCtxt > {
349
- match self . state {
350
- DiagnosticBuilderState :: Emittable ( dcx) => Some ( dcx) ,
351
- DiagnosticBuilderState :: AlreadyEmittedOrDuringCancellation => None ,
352
- }
289
+ Some ( ( diag, self . dcx ) )
353
290
}
354
291
355
292
/// Buffers the diagnostic for later emission,
@@ -488,30 +425,24 @@ impl<'a, G: EmissionGuarantee> DiagnosticBuilder<'a, G> {
488
425
489
426
impl < G : EmissionGuarantee > Debug for DiagnosticBuilder < ' _ , G > {
490
427
fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
491
- self . diagnostic . fmt ( f)
428
+ self . diag . fmt ( f)
492
429
}
493
430
}
494
431
495
- /// Destructor bomb - a `DiagnosticBuilder` must be either emitted or cancelled
496
- /// or we emit a bug.
432
+ /// Destructor bomb: a `DiagnosticBuilder` must be consumed ( emitted,
433
+ /// cancelled, etc.) or we emit a bug.
497
434
impl < G : EmissionGuarantee > Drop for DiagnosticBuilder < ' _ , G > {
498
435
fn drop ( & mut self ) {
499
- match self . state {
500
- // No `.emit()` or `.cancel()` calls.
501
- DiagnosticBuilderState :: Emittable ( dcx) => {
502
- if !panicking ( ) {
503
- dcx. emit_diagnostic ( Diagnostic :: new (
504
- Level :: Bug ,
505
- DiagnosticMessage :: from (
506
- "the following error was constructed but not emitted" ,
507
- ) ,
508
- ) ) ;
509
- dcx. emit_diagnostic_without_consuming ( & mut self . diagnostic ) ;
510
- panic ! ( "error was constructed but not emitted" ) ;
511
- }
436
+ match self . diag . take ( ) {
437
+ Some ( diag) if !panicking ( ) => {
438
+ self . dcx . emit_diagnostic ( Diagnostic :: new (
439
+ Level :: Bug ,
440
+ DiagnosticMessage :: from ( "the following error was constructed but not emitted" ) ,
441
+ ) ) ;
442
+ self . dcx . emit_diagnostic ( * diag) ;
443
+ panic ! ( "error was constructed but not emitted" ) ;
512
444
}
513
- // `.emit()` was previously called, or maybe we're during `.cancel()`.
514
- DiagnosticBuilderState :: AlreadyEmittedOrDuringCancellation => { }
445
+ _ => { }
515
446
}
516
447
}
517
448
}
0 commit comments