Skip to content

Commit f645542

Browse files
committed
Remove DiagnosticBuilderState.
Currently it's used to record whether a `DiagnosticBuilder` has been emitted or cancelled. But now that `emit` is consuming, it's impossible to emit a `DiagnosticBuilder` twice. So we don't need `DiagnosticBuilderState` any more. This commit replaces it with `Option<Box<Diagnostic>>`. We need the `Option` so that functions like `emit` and `cancel` can take the `Diagnostic` and then `drop` can check that the `Diagnostic` was taken. Basically, a simplified version of `DiagnosticBuilderState`. The `DiagCtxt` reference from `DiagnosticBuilderState` is now stored as its own field, removing the need for the `dcx` method. As well as make the code shorter and simpler, the commit removes: - One `ErrorGuaranteed::unchecked_claim_error_was_emitted` call. - Two `FIXME(eddyb)` comments that are no longer relevant. - The use of a dummy `Diagnostic` in `into_diagnostic`.
1 parent b37c73d commit f645542

File tree

3 files changed

+75
-144
lines changed

3 files changed

+75
-144
lines changed

compiler/rustc_errors/src/diagnostic_builder.rs

+73-142
Original file line numberDiff line numberDiff line change
@@ -35,19 +35,28 @@ where
3535
}
3636

3737
/// 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.
3842
///
3943
/// If there is some state in a downstream crate you would like to
4044
/// access in the methods of `DiagnosticBuilder` here, consider
4145
/// extending `DiagCtxtFlags`.
4246
#[must_use]
4347
pub struct DiagnosticBuilder<'a, G: EmissionGuarantee = ErrorGuaranteed> {
44-
state: DiagnosticBuilderState<'a>,
48+
pub dcx: &'a DiagCtxt,
4549

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>>,
5160

5261
_marker: PhantomData<G>,
5362
}
@@ -56,32 +65,9 @@ pub struct DiagnosticBuilder<'a, G: EmissionGuarantee = ErrorGuaranteed> {
5665
// twice, which would be bad.
5766
impl<G> !Clone for DiagnosticBuilder<'_, G> {}
5867

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.
8268
rustc_data_structures::static_assert_size!(
83-
DiagnosticBuilderState<'_>,
84-
std::mem::size_of::<&DiagCtxt>()
69+
DiagnosticBuilder<'_, ()>,
70+
2 * std::mem::size_of::<usize>()
8571
);
8672

8773
/// Trait for types that `DiagnosticBuilder::emit` can return as a "guarantee"
@@ -99,62 +85,44 @@ pub trait EmissionGuarantee: Sized {
9985
}
10086

10187
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+
10295
/// Most `emit_producing_guarantee` functions use this as a starting point.
10396
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()
113117
}
114118
}
115119

116-
// FIXME(eddyb) make `ErrorGuaranteed` impossible to create outside `.emit()`.
117120
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()
154123
}
155124
}
156125

157-
// FIXME(eddyb) should there be a `Option<ErrorGuaranteed>` impl as well?
158126
impl EmissionGuarantee for () {
159127
fn emit_producing_guarantee(db: DiagnosticBuilder<'_, Self>) -> Self::EmitResult {
160128
db.emit_producing_nothing();
@@ -206,7 +174,7 @@ macro_rules! forward0 {
206174
$(#[$attrs])*
207175
#[doc = concat!("See [`Diagnostic::", stringify!($n), "()`].")]
208176
pub fn $n(&mut self $(, $name: $ty)*) -> &mut Self {
209-
self.diagnostic.$n($($name),*);
177+
self.diag.as_mut().unwrap().$n($($name),*);
210178
self
211179
}
212180
};
@@ -227,12 +195,12 @@ macro_rules! forward {
227195
) => {
228196
#[doc = concat!("See [`Diagnostic::", stringify!($n), "()`].")]
229197
pub fn $n(&mut self, $($name: $ty),*) -> &mut Self {
230-
self.diagnostic.$n($($name),*);
198+
self.diag.as_mut().unwrap().$n($($name),*);
231199
self
232200
}
233201
#[doc = concat!("See [`Diagnostic::", stringify!($n_mv), "()`].")]
234202
pub fn $n_mv(mut self, $($name: $ty),*) -> Self {
235-
self.diagnostic.$n($($name),*);
203+
self.diag.as_mut().unwrap().$n($($name),*);
236204
self
237205
}
238206
};
@@ -242,13 +210,13 @@ impl<G: EmissionGuarantee> Deref for DiagnosticBuilder<'_, G> {
242210
type Target = Diagnostic;
243211

244212
fn deref(&self) -> &Diagnostic {
245-
&self.diagnostic
213+
self.diag.as_ref().unwrap()
246214
}
247215
}
248216

249217
impl<G: EmissionGuarantee> DerefMut for DiagnosticBuilder<'_, G> {
250218
fn deref_mut(&mut self) -> &mut Diagnostic {
251-
&mut self.diagnostic
219+
self.diag.as_mut().unwrap()
252220
}
253221
}
254222

@@ -262,13 +230,9 @@ impl<'a, G: EmissionGuarantee> DiagnosticBuilder<'a, G> {
262230
/// Creates a new `DiagnosticBuilder` with an already constructed
263231
/// diagnostic.
264232
#[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 {
266234
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 }
272236
}
273237

274238
/// Emit and consume the diagnostic.
@@ -289,14 +253,10 @@ impl<'a, G: EmissionGuarantee> DiagnosticBuilder<'a, G> {
289253
self.emit()
290254
}
291255

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
293257
/// 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.
298258
pub fn cancel(mut self) {
299-
self.state = DiagnosticBuilderState::AlreadyEmittedOrDuringCancellation;
259+
self.diag = None;
300260
drop(self);
301261
}
302262

@@ -312,44 +272,21 @@ impl<'a, G: EmissionGuarantee> DiagnosticBuilder<'a, G> {
312272
}
313273

314274
/// 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.
316276
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() {
329279
self.emit();
330280
return None;
331281
}
332282

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();
339284

340285
// Logging here is useful to help track down where in logs an error was
341286
// actually emitted.
342-
debug!("buffer: diagnostic={:?}", diagnostic);
287+
debug!("buffer: diag={:?}", diag);
343288

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))
353290
}
354291

355292
/// Buffers the diagnostic for later emission,
@@ -488,30 +425,24 @@ impl<'a, G: EmissionGuarantee> DiagnosticBuilder<'a, G> {
488425

489426
impl<G: EmissionGuarantee> Debug for DiagnosticBuilder<'_, G> {
490427
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
491-
self.diagnostic.fmt(f)
428+
self.diag.fmt(f)
492429
}
493430
}
494431

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.
497434
impl<G: EmissionGuarantee> Drop for DiagnosticBuilder<'_, G> {
498435
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");
512444
}
513-
// `.emit()` was previously called, or maybe we're during `.cancel()`.
514-
DiagnosticBuilderState::AlreadyEmittedOrDuringCancellation => {}
445+
_ => {}
515446
}
516447
}
517448
}

compiler/rustc_errors/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#![feature(rustdoc_internals)]
88
#![feature(array_windows)]
99
#![feature(associated_type_defaults)]
10+
#![feature(box_into_inner)]
1011
#![feature(extract_if)]
1112
#![feature(if_let_guard)]
1213
#![feature(let_chains)]

compiler/rustc_mir_transform/src/errors.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -181,8 +181,7 @@ pub(crate) struct UnsafeOpInUnsafeFn {
181181
impl<'a> DecorateLint<'a, ()> for UnsafeOpInUnsafeFn {
182182
#[track_caller]
183183
fn decorate_lint<'b>(self, diag: &'b mut DiagnosticBuilder<'a, ()>) {
184-
let dcx = diag.dcx().expect("lint should not yet be emitted");
185-
let desc = dcx.eagerly_translate_to_string(self.details.label(), [].into_iter());
184+
let desc = diag.dcx.eagerly_translate_to_string(self.details.label(), [].into_iter());
186185
diag.arg("details", desc);
187186
diag.span_label(self.details.span, self.details.label());
188187
self.details.add_subdiagnostics(diag);

0 commit comments

Comments
 (0)