@@ -16,7 +16,7 @@ use tracing::debug;
1616#[ must_use]
1717#[ derive( Clone ) ]
1818pub struct DiagnosticBuilder < ' a > {
19- handler : & ' a Handler ,
19+ state : DiagnosticBuilderState < ' a > ,
2020
2121 /// `Diagnostic` is a large type, and `DiagnosticBuilder` is often used as a
2222 /// return value, especially within the frequently-used `PResult` type.
@@ -25,6 +25,34 @@ pub struct DiagnosticBuilder<'a> {
2525 diagnostic : Box < Diagnostic > ,
2626}
2727
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+
2856/// In general, the `DiagnosticBuilder` uses deref to allow access to
2957/// the fields and methods of the embedded `diagnostic` in a
3058/// transparent way. *However,* many of the methods are intended to
@@ -78,8 +106,18 @@ impl<'a> DerefMut for DiagnosticBuilder<'a> {
78106impl < ' a > DiagnosticBuilder < ' a > {
79107 /// Emit the diagnostic.
80108 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+ }
83121 }
84122
85123 /// Emit the diagnostic unless `delay` is true,
@@ -93,6 +131,17 @@ impl<'a> DiagnosticBuilder<'a> {
93131 self . emit ( ) ;
94132 }
95133
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+
96145 /// Stashes diagnostic for possible later improvement in a different,
97146 /// later stage of the compiler. The diagnostic can be accessed with
98147 /// the provided `span` and `key` through [`Handler::steal_diagnostic()`].
@@ -105,22 +154,29 @@ impl<'a> DiagnosticBuilder<'a> {
105154 }
106155
107156 /// 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 .
109158 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 ( ) {
113169 self . emit ( ) ;
114170 return None ;
115171 }
116172
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 , "" ) ;
122175 let diagnostic = std:: mem:: replace ( & mut * self . diagnostic , dummy) ;
123176
177+ // Disable the ICE on `Drop`.
178+ self . cancel ( ) ;
179+
124180 // Logging here is useful to help track down where in logs an error was
125181 // actually emitted.
126182 debug ! ( "buffer: diagnostic={:?}" , diagnostic) ;
@@ -314,7 +370,10 @@ impl<'a> DiagnosticBuilder<'a> {
314370 /// diagnostic.
315371 crate fn new_diagnostic ( handler : & ' a Handler , diagnostic : Diagnostic ) -> DiagnosticBuilder < ' a > {
316372 debug ! ( "Created new diagnostic" ) ;
317- DiagnosticBuilder { handler, diagnostic : Box :: new ( diagnostic) }
373+ DiagnosticBuilder {
374+ state : DiagnosticBuilderState :: Emittable ( handler) ,
375+ diagnostic : Box :: new ( diagnostic) ,
376+ }
318377 }
319378}
320379
@@ -324,19 +383,26 @@ impl<'a> Debug for DiagnosticBuilder<'a> {
324383 }
325384}
326385
327- /// Destructor bomb - a `DiagnosticBuilder` must be either emitted or canceled
386+ /// Destructor bomb - a `DiagnosticBuilder` must be either emitted or cancelled
328387/// or we emit a bug.
329388impl < ' a > Drop for DiagnosticBuilder < ' a > {
330389 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 => { }
340406 }
341407 }
342408}
0 commit comments