diff options
| author | bors <bors@rust-lang.org> | 2022-02-25 00:46:04 +0000 |
|---|---|---|
| committer | bors <bors@rust-lang.org> | 2022-02-25 00:46:04 +0000 |
| commit | d4de1f230ca30b7ce08fbf453daebf8b2e7ffcc9 (patch) | |
| tree | 922a1d8bf2850b2f35dda5bc6cc14e9194c375c1 /compiler/rustc_errors | |
| parent | 4e82f35492ea5c78e19609bf4468f0a686d9a756 (diff) | |
| parent | b7e95dee65c35db8f8e07046d445b12d92cbae12 (diff) | |
| download | rust-d4de1f230ca30b7ce08fbf453daebf8b2e7ffcc9.tar.gz rust-d4de1f230ca30b7ce08fbf453daebf8b2e7ffcc9.zip | |
Auto merge of #93368 - eddyb:diagbld-guarantee, r=estebank
rustc_errors: let `DiagnosticBuilder::emit` return a "guarantee of emission".
That is, `DiagnosticBuilder` is now generic over the return type of `.emit()`, so we'll now have:
* `DiagnosticBuilder<ErrorReported>` for error (incl. fatal/bug) diagnostics
* can only be created via a `const L: Level`-generic constructor, that limits allowed variants via a `where` clause, so not even `rustc_errors` can accidentally bypass this limitation
* asserts `diagnostic.is_error()` on emission, just in case the construction restriction was bypassed (e.g. by replacing the whole `Diagnostic` inside `DiagnosticBuilder`)
* `.emit()` returns `ErrorReported`, as a "proof" token that `.emit()` was called
(though note that this isn't a real guarantee until after completing the work on
#69426)
* `DiagnosticBuilder<()>` for everything else (warnings, notes, etc.)
* can also be obtained from other `DiagnosticBuilder`s by calling `.forget_guarantee()`
This PR is a companion to other ongoing work, namely:
* #69426
and it's ongoing implementation:
#93222
the API changes in this PR are needed to get statically-checked "only errors produce `ErrorReported` from `.emit()`", but doesn't itself provide any really strong guarantees without those other `ErrorReported` changes
* #93244
would make the choices of API changes (esp. naming) in this PR fit better overall
In order to be able to let `.emit()` return anything trustable, several changes had to be made:
* `Diagnostic`'s `level` field is now private to `rustc_errors`, to disallow arbitrary "downgrade"s from "some kind of error" to "warning" (or anything else that doesn't cause compilation to fail)
* it's still possible to replace the whole `Diagnostic` inside the `DiagnosticBuilder`, sadly, that's harder to fix, but it's unlikely enough that we can paper over it with asserts on `.emit()`
* `.cancel()` now consumes `DiagnosticBuilder`, preventing `.emit()` calls on a cancelled diagnostic
* it's also now done internally, through `DiagnosticBuilder`-private state, instead of having a `Level::Cancelled` variant that can be read (or worse, written) by the user
* this removes a hazard of calling `.cancel()` on an error then continuing to attach details to it, and even expect to be able to `.emit()` it
* warnings were switched to *only* `can_emit_warnings` on emission (instead of pre-cancelling early)
* `struct_dummy` was removed (as it relied on a pre-`Cancelled` `Diagnostic`)
* since `.emit()` doesn't consume the `DiagnosticBuilder` <sub>(I tried and gave up, it's much more work than this PR)</sub>,
we have to make `.emit()` idempotent wrt the guarantees it returns
* thankfully, `err.emit(); err.emit();` can return `ErrorReported` both times, as the second `.emit()` call has no side-effects *only* because the first one did do the appropriate emission
* `&mut Diagnostic` is now used in a lot of function signatures, which used to take `&mut DiagnosticBuilder` (in the interest of not having to make those functions generic)
* the APIs were already mostly identical, allowing for low-effort porting to this new setup
* only some of the suggestion methods needed some rework, to have the extra `DiagnosticBuilder` functionality on the `Diagnostic` methods themselves (that change is also present in #93259)
* `.emit()`/`.cancel()` aren't available, but IMO calling them from an "error decorator/annotator" function isn't a good practice, and can lead to strange behavior (from the caller's perspective)
* `.downgrade_to_delayed_bug()` was added, letting you convert any `.is_error()` diagnostic into a `delay_span_bug` one (which works because in both cases the guarantees available are the same)
This PR should ideally be reviewed commit-by-commit, since there is a lot of fallout in each.
r? `@estebank` cc `@Manishearth` `@nikomatsakis` `@mark-i-m`
Diffstat (limited to 'compiler/rustc_errors')
| -rw-r--r-- | compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs | 8 | ||||
| -rw-r--r-- | compiler/rustc_errors/src/diagnostic.rs | 75 | ||||
| -rw-r--r-- | compiler/rustc_errors/src/diagnostic_builder.rs | 354 | ||||
| -rw-r--r-- | compiler/rustc_errors/src/lib.rs | 178 |
4 files changed, 409 insertions, 206 deletions
diff --git a/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs b/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs index 9db8f751390..7d7ab1ed4e5 100644 --- a/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs +++ b/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs @@ -66,12 +66,14 @@ fn source_string(file: Lrc<SourceFile>, line: &Line) -> String { /// Maps `Diagnostic::Level` to `snippet::AnnotationType` fn annotation_type_for_level(level: Level) -> AnnotationType { match level { - Level::Bug | Level::Fatal | Level::Error { .. } => AnnotationType::Error, + Level::Bug | Level::DelayedBug | Level::Fatal | Level::Error { .. } => { + AnnotationType::Error + } Level::Warning => AnnotationType::Warning, Level::Note => AnnotationType::Note, Level::Help => AnnotationType::Help, - // FIXME(#59346): Not sure how to map these two levels - Level::Cancelled | Level::FailureNote => AnnotationType::Error, + // FIXME(#59346): Not sure how to map this level + Level::FailureNote => AnnotationType::Error, Level::Allow => panic!("Should not call with Allow"), } } diff --git a/compiler/rustc_errors/src/diagnostic.rs b/compiler/rustc_errors/src/diagnostic.rs index 8cfecafd20c..6d6ada86428 100644 --- a/compiler/rustc_errors/src/diagnostic.rs +++ b/compiler/rustc_errors/src/diagnostic.rs @@ -19,7 +19,10 @@ pub struct SuggestionsDisabled; #[must_use] #[derive(Clone, Debug, Encodable, Decodable)] pub struct Diagnostic { - pub level: Level, + // NOTE(eddyb) this is private to disallow arbitrary after-the-fact changes, + // outside of what methods in this crate themselves allow. + crate level: Level, + pub message: Vec<(String, Style)>, pub code: Option<DiagnosticId>, pub span: MultiSpan, @@ -117,11 +120,20 @@ impl Diagnostic { } } + #[inline(always)] + pub fn level(&self) -> Level { + self.level + } + pub fn is_error(&self) -> bool { match self.level { - Level::Bug | Level::Fatal | Level::Error { .. } | Level::FailureNote => true, + Level::Bug + | Level::DelayedBug + | Level::Fatal + | Level::Error { .. } + | Level::FailureNote => true, - Level::Warning | Level::Note | Level::Help | Level::Cancelled | Level::Allow => false, + Level::Warning | Level::Note | Level::Help | Level::Allow => false, } } @@ -139,15 +151,26 @@ impl Diagnostic { } } - /// Cancel the diagnostic (a structured diagnostic must either be emitted or - /// canceled or it will panic when dropped). - pub fn cancel(&mut self) { - self.level = Level::Cancelled; - } + /// Delay emission of this diagnostic as a bug. + /// + /// This can be useful in contexts where an error indicates a bug but + /// typically this only happens when other compilation errors have already + /// happened. In those cases this can be used to defer emission of this + /// diagnostic as a bug in the compiler only if no other errors have been + /// emitted. + /// + /// In the meantime, though, callsites are required to deal with the "bug" + /// locally in whichever way makes the most sense. + #[track_caller] + pub fn downgrade_to_delayed_bug(&mut self) -> &mut Self { + assert!( + self.is_error(), + "downgrade_to_delayed_bug: cannot downgrade {:?} to DelayedBug: not an error", + self.level + ); + self.level = Level::DelayedBug; - /// Check if this diagnostic [was cancelled][Self::cancel()]. - pub fn cancelled(&self) -> bool { - self.level == Level::Cancelled + self } /// Adds a span/label to be included in the resulting snippet. @@ -163,6 +186,20 @@ impl Diagnostic { self } + /// Labels all the given spans with the provided label. + /// See [`Self::span_label()`] for more information. + pub fn span_labels( + &mut self, + spans: impl IntoIterator<Item = Span>, + label: impl AsRef<str>, + ) -> &mut Self { + let label = label.as_ref(); + for span in spans { + self.span_label(span, label); + } + self + } + pub fn replace_span_with(&mut self, after: Span) -> &mut Self { let before = self.span.clone(); self.set_span(after); @@ -174,7 +211,7 @@ impl Diagnostic { self } - crate fn note_expected_found( + pub fn note_expected_found( &mut self, expected_label: &dyn fmt::Display, expected: DiagnosticStyledString, @@ -184,7 +221,7 @@ impl Diagnostic { self.note_expected_found_extra(expected_label, expected, found_label, found, &"", &"") } - crate fn note_unsuccessful_coercion( + pub fn note_unsuccessful_coercion( &mut self, expected: DiagnosticStyledString, found: DiagnosticStyledString, @@ -274,33 +311,33 @@ impl Diagnostic { /// Prints the span with a note above it. /// This is like [`Diagnostic::note()`], but it gets its own span. - crate fn span_note<S: Into<MultiSpan>>(&mut self, sp: S, msg: &str) -> &mut Self { + pub fn span_note<S: Into<MultiSpan>>(&mut self, sp: S, msg: &str) -> &mut Self { self.sub(Level::Note, msg, sp.into(), None); self } /// Add a warning attached to this diagnostic. - crate fn warn(&mut self, msg: &str) -> &mut Self { + pub fn warn(&mut self, msg: &str) -> &mut Self { self.sub(Level::Warning, msg, MultiSpan::new(), None); self } /// Prints the span with a warning above it. /// This is like [`Diagnostic::warn()`], but it gets its own span. - crate fn span_warn<S: Into<MultiSpan>>(&mut self, sp: S, msg: &str) -> &mut Self { + pub fn span_warn<S: Into<MultiSpan>>(&mut self, sp: S, msg: &str) -> &mut Self { self.sub(Level::Warning, msg, sp.into(), None); self } /// Add a help message attached to this diagnostic. - crate fn help(&mut self, msg: &str) -> &mut Self { + pub fn help(&mut self, msg: &str) -> &mut Self { self.sub(Level::Help, msg, MultiSpan::new(), None); self } /// Prints the span with some help above it. /// This is like [`Diagnostic::help()`], but it gets its own span. - crate fn span_help<S: Into<MultiSpan>>(&mut self, sp: S, msg: &str) -> &mut Self { + pub fn span_help<S: Into<MultiSpan>>(&mut self, sp: S, msg: &str) -> &mut Self { self.sub(Level::Help, msg, sp.into(), None); self } @@ -634,7 +671,7 @@ impl Diagnostic { self.code.clone() } - crate fn set_primary_message<M: Into<String>>(&mut self, msg: M) -> &mut Self { + pub fn set_primary_message<M: Into<String>>(&mut self, msg: M) -> &mut Self { self.message[0] = (msg.into(), Style::NoStyle); self } diff --git a/compiler/rustc_errors/src/diagnostic_builder.rs b/compiler/rustc_errors/src/diagnostic_builder.rs index 3c8751a7a35..49305d22684 100644 --- a/compiler/rustc_errors/src/diagnostic_builder.rs +++ b/compiler/rustc_errors/src/diagnostic_builder.rs @@ -1,9 +1,10 @@ -use crate::{Diagnostic, DiagnosticId, DiagnosticStyledString}; +use crate::{Diagnostic, DiagnosticId, DiagnosticStyledString, ErrorReported}; use crate::{Handler, Level, StashKey}; use rustc_lint_defs::Applicability; use rustc_span::{MultiSpan, Span}; use std::fmt::{self, Debug}; +use std::marker::PhantomData; use std::ops::{Deref, DerefMut}; use std::thread::panicking; use tracing::debug; @@ -15,8 +16,25 @@ use tracing::debug; /// extending `HandlerFlags`, accessed via `self.handler.flags`. #[must_use] #[derive(Clone)] -pub struct DiagnosticBuilder<'a> { - handler: &'a Handler, +pub struct DiagnosticBuilder<'a, G: EmissionGuarantee> { + inner: DiagnosticBuilderInner<'a>, + _marker: PhantomData<G>, +} + +/// This type exists only for `DiagnosticBuilder::forget_guarantee`, because it: +/// 1. lacks the `G` parameter and therefore `DiagnosticBuilder<G1>` can be +/// converted into `DiagnosticBuilder<G2>` while reusing the `inner` field +/// 2. can implement the `Drop` "bomb" instead of `DiagnosticBuilder`, as it +/// contains all of the data (`state` + `diagnostic`) of `DiagnosticBuilder` +/// +/// The `diagnostic` field is not `Copy` and can't be moved out of whichever +/// type implements the `Drop` "bomb", but because of the above two facts, that +/// never needs to happen - instead, the whole `inner: DiagnosticBuilderInner` +/// can be moved out of a `DiagnosticBuilder` and into another. +#[must_use] +#[derive(Clone)] +struct DiagnosticBuilderInner<'a> { + state: DiagnosticBuilderState<'a>, /// `Diagnostic` is a large type, and `DiagnosticBuilder` is often used as a /// return value, especially within the frequently-used `PResult` type. @@ -25,6 +43,161 @@ pub struct DiagnosticBuilder<'a> { diagnostic: Box<Diagnostic>, } +#[derive(Clone)] +enum DiagnosticBuilderState<'a> { + /// Initial state of a `DiagnosticBuilder`, before `.emit()` or `.cancel()`. + /// + /// The `Diagnostic` will be emitted through this `Handler`. + Emittable(&'a Handler), + + /// State of a `DiagnosticBuilder`, after `.emit()` or *during* `.cancel()`. + /// + /// The `Diagnostic` will be ignored when calling `.emit()`, and it can be + /// assumed that `.emit()` was previously called, to end up in this state. + /// + /// While this is also used by `.cancel()`, this state is only observed by + /// the `Drop` `impl` of `DiagnosticBuilderInner`, as `.cancel()` takes + /// `self` by-value specifically to prevent any attempts to `.emit()`. + /// + // FIXME(eddyb) currently this doesn't prevent extending the `Diagnostic`, + // despite that being potentially lossy, if important information is added + // *after* the original `.emit()` call. + AlreadyEmittedOrDuringCancellation, +} + +// `DiagnosticBuilderState` should be pointer-sized. +rustc_data_structures::static_assert_size!( + DiagnosticBuilderState<'_>, + std::mem::size_of::<&Handler>() +); + +/// Trait for types that `DiagnosticBuilder::emit` can return as a "guarantee" +/// (or "proof") token that the emission happened. +pub trait EmissionGuarantee: Sized { + /// Implementation of `DiagnosticBuilder::emit`, fully controlled by each + /// `impl` of `EmissionGuarantee`, to make it impossible to create a value + /// of `Self` without actually performing the emission. + #[track_caller] + fn diagnostic_builder_emit_producing_guarantee(db: &mut DiagnosticBuilder<'_, Self>) -> Self; +} + +/// Private module for sealing the `IsError` helper trait. +mod sealed_level_is_error { + use crate::Level; + + /// Sealed helper trait for statically checking that a `Level` is an error. + crate trait IsError<const L: Level> {} + + impl IsError<{ Level::Bug }> for () {} + impl IsError<{ Level::DelayedBug }> for () {} + impl IsError<{ Level::Fatal }> for () {} + // NOTE(eddyb) `Level::Error { lint: true }` is also an error, but lints + // don't need error guarantees, as their levels are always dynamic. + impl IsError<{ Level::Error { lint: false } }> for () {} +} + +impl<'a> DiagnosticBuilder<'a, ErrorReported> { + /// Convenience function for internal use, clients should use one of the + /// `struct_*` methods on [`Handler`]. + crate fn new_guaranteeing_error<const L: Level>(handler: &'a Handler, message: &str) -> Self + where + (): sealed_level_is_error::IsError<L>, + { + Self { + inner: DiagnosticBuilderInner { + state: DiagnosticBuilderState::Emittable(handler), + diagnostic: Box::new(Diagnostic::new_with_code(L, None, message)), + }, + _marker: PhantomData, + } + } + + /// Discard the guarantee `.emit()` would return, in favor of having the + /// type `DiagnosticBuilder<'a, ()>`. This may be necessary whenever there + /// is a common codepath handling both errors and warnings. + pub fn forget_guarantee(self) -> DiagnosticBuilder<'a, ()> { + DiagnosticBuilder { inner: self.inner, _marker: PhantomData } + } +} + +// FIXME(eddyb) make `ErrorReported` impossible to create outside `.emit()`. +impl EmissionGuarantee for ErrorReported { + fn diagnostic_builder_emit_producing_guarantee(db: &mut DiagnosticBuilder<'_, Self>) -> Self { + match db.inner.state { + // First `.emit()` call, the `&Handler` is still available. + DiagnosticBuilderState::Emittable(handler) => { + db.inner.state = DiagnosticBuilderState::AlreadyEmittedOrDuringCancellation; + + handler.emit_diagnostic(&db.inner.diagnostic); + + // Only allow a guarantee if the `level` wasn't switched to a + // non-error - the field isn't `pub`, but the whole `Diagnostic` + // can be overwritten with a new one, thanks to `DerefMut`. + assert!( + db.inner.diagnostic.is_error(), + "emitted non-error ({:?}) diagnostic \ + from `DiagnosticBuilder<ErrorReported>`", + db.inner.diagnostic.level, + ); + ErrorReported + } + // `.emit()` was previously called, disallowed from repeating it, + // but can take advantage of the previous `.emit()`'s guarantee + // still being applicable (i.e. as a form of idempotency). + DiagnosticBuilderState::AlreadyEmittedOrDuringCancellation => { + // Only allow a guarantee if the `level` wasn't switched to a + // non-error - the field isn't `pub`, but the whole `Diagnostic` + // can be overwritten with a new one, thanks to `DerefMut`. + assert!( + db.inner.diagnostic.is_error(), + "`DiagnosticBuilder<ErrorReported>`'s diagnostic \ + became non-error ({:?}), after original `.emit()`", + db.inner.diagnostic.level, + ); + ErrorReported + } + } + } +} + +impl<'a> DiagnosticBuilder<'a, ()> { + /// Convenience function for internal use, clients should use one of the + /// `struct_*` methods on [`Handler`]. + crate fn new(handler: &'a Handler, level: Level, message: &str) -> Self { + let diagnostic = Diagnostic::new_with_code(level, None, message); + Self::new_diagnostic(handler, diagnostic) + } + + /// Creates a new `DiagnosticBuilder` with an already constructed + /// diagnostic. + crate fn new_diagnostic(handler: &'a Handler, diagnostic: Diagnostic) -> Self { + debug!("Created new diagnostic"); + Self { + inner: DiagnosticBuilderInner { + state: DiagnosticBuilderState::Emittable(handler), + diagnostic: Box::new(diagnostic), + }, + _marker: PhantomData, + } + } +} + +// FIXME(eddyb) should there be a `Option<ErrorReported>` impl as well? +impl EmissionGuarantee for () { + fn diagnostic_builder_emit_producing_guarantee(db: &mut DiagnosticBuilder<'_, Self>) -> Self { + match db.inner.state { + // First `.emit()` call, the `&Handler` is still available. + DiagnosticBuilderState::Emittable(handler) => { + db.inner.state = DiagnosticBuilderState::AlreadyEmittedOrDuringCancellation; + + handler.emit_diagnostic(&db.inner.diagnostic); + } + // `.emit()` was previously called, disallowed from repeating it. + DiagnosticBuilderState::AlreadyEmittedOrDuringCancellation => {} + } + } +} + /// In general, the `DiagnosticBuilder` uses deref to allow access to /// the fields and methods of the embedded `diagnostic` in a /// transparent way. *However,* many of the methods are intended to @@ -55,60 +228,54 @@ macro_rules! forward { $(#[$attrs])* #[doc = concat!("See [`Diagnostic::", stringify!($n), "()`].")] pub fn $n(&mut self, $($name: $ty),*) -> &mut Self { - self.diagnostic.$n($($name),*); - self - } - }; - - // Forward pattern for &mut self -> &mut Self, with generic parameters. - ( - $(#[$attrs:meta])* - pub fn $n:ident<$($generic:ident: $bound:path),*>( - &mut self, - $($name:ident: $ty:ty),* - $(,)? - ) -> &mut Self - ) => { - $(#[$attrs])* - #[doc = concat!("See [`Diagnostic::", stringify!($n), "()`].")] - pub fn $n<$($generic: $bound),*>(&mut self, $($name: $ty),*) -> &mut Self { - self.diagnostic.$n($($name),*); + self.inner.diagnostic.$n($($name),*); self } }; } -impl<'a> Deref for DiagnosticBuilder<'a> { +impl<G: EmissionGuarantee> Deref for DiagnosticBuilder<'_, G> { type Target = Diagnostic; fn deref(&self) -> &Diagnostic { - &self.diagnostic + &self.inner.diagnostic } } -impl<'a> DerefMut for DiagnosticBuilder<'a> { +impl<G: EmissionGuarantee> DerefMut for DiagnosticBuilder<'_, G> { fn deref_mut(&mut self) -> &mut Diagnostic { - &mut self.diagnostic + &mut self.inner.diagnostic } } -impl<'a> DiagnosticBuilder<'a> { +impl<'a, G: EmissionGuarantee> DiagnosticBuilder<'a, G> { /// Emit the diagnostic. - pub fn emit(&mut self) { - self.handler.emit_diagnostic(&self); - self.cancel(); + #[track_caller] + pub fn emit(&mut self) -> G { + G::diagnostic_builder_emit_producing_guarantee(self) } /// Emit the diagnostic unless `delay` is true, /// in which case the emission will be delayed as a bug. /// /// See `emit` and `delay_as_bug` for details. - pub fn emit_unless(&mut self, delay: bool) { + #[track_caller] + pub fn emit_unless(&mut self, delay: bool) -> G { if delay { - self.delay_as_bug(); - } else { - self.emit(); + self.downgrade_to_delayed_bug(); } + self.emit() + } + + /// Cancel the diagnostic (a structured diagnostic must either be emitted or + /// cancelled or it will panic when dropped). + /// + /// This method takes `self` by-value to disallow calling `.emit()` on it, + /// which may be expected to *guarantee* the emission of an error, either + /// at the time of the call, or through a prior `.emit()` call. + pub fn cancel(mut self) { + self.inner.state = DiagnosticBuilderState::AlreadyEmittedOrDuringCancellation; + drop(self); } /// Stashes diagnostic for possible later improvement in a different, @@ -123,21 +290,28 @@ impl<'a> DiagnosticBuilder<'a> { } /// Converts the builder to a `Diagnostic` for later emission, - /// unless handler has disabled such buffering. + /// unless handler has disabled such buffering, or `.emit()` was called. pub fn into_diagnostic(mut self) -> Option<(Diagnostic, &'a Handler)> { - if self.handler.flags.dont_buffer_diagnostics - || self.handler.flags.treat_err_as_bug.is_some() - { + let handler = match self.inner.state { + // No `.emit()` calls, the `&Handler` is still available. + DiagnosticBuilderState::Emittable(handler) => handler, + // `.emit()` was previously called, nothing we can do. + DiagnosticBuilderState::AlreadyEmittedOrDuringCancellation => { + return None; + } + }; + + if handler.flags.dont_buffer_diagnostics || handler.flags.treat_err_as_bug.is_some() { self.emit(); return None; } - let handler = self.handler; + // Take the `Diagnostic` by replacing it with a dummy. + let dummy = Diagnostic::new(Level::Allow, ""); + let diagnostic = std::mem::replace(&mut *self.inner.diagnostic, dummy); - // We must use `Level::Cancelled` for `dummy` to avoid an ICE about an - // unused diagnostic. - let dummy = Diagnostic::new(Level::Cancelled, ""); - let diagnostic = std::mem::replace(&mut *self.diagnostic, dummy); + // Disable the ICE on `Drop`. + self.cancel(); // Logging here is useful to help track down where in logs an error was // actually emitted. @@ -162,12 +336,18 @@ impl<'a> DiagnosticBuilder<'a> { /// /// In the meantime, though, callsites are required to deal with the "bug" /// locally in whichever way makes the most sense. + #[track_caller] pub fn delay_as_bug(&mut self) { - self.level = Level::Bug; - self.handler.delay_as_bug((*self.diagnostic).clone()); - self.cancel(); + self.downgrade_to_delayed_bug(); + self.emit(); } + forward!( + #[track_caller] + pub fn downgrade_to_delayed_bug(&mut self,) -> &mut Self + ); + + forward!( /// Appends a labeled span to the diagnostic. /// /// Labels are used to convey additional context for the diagnostic's primary span. They will @@ -180,24 +360,16 @@ impl<'a> DiagnosticBuilder<'a> { /// the diagnostic was constructed. However, the label span is *not* considered a /// ["primary span"][`MultiSpan`]; only the `Span` supplied when creating the diagnostic is /// primary. - pub fn span_label(&mut self, span: Span, label: impl Into<String>) -> &mut Self { - self.diagnostic.span_label(span, label); - self - } + pub fn span_label(&mut self, span: Span, label: impl Into<String>) -> &mut Self); + forward!( /// Labels all the given spans with the provided label. /// See [`Diagnostic::span_label()`] for more information. pub fn span_labels( &mut self, spans: impl IntoIterator<Item = Span>, label: impl AsRef<str>, - ) -> &mut Self { - let label = label.as_ref(); - for span in spans { - self.diagnostic.span_label(span, label); - } - self - } + ) -> &mut Self); forward!(pub fn note_expected_found( &mut self, @@ -224,17 +396,17 @@ impl<'a> DiagnosticBuilder<'a> { ) -> &mut Self); forward!(pub fn note(&mut self, msg: &str) -> &mut Self); - forward!(pub fn span_note<S: Into<MultiSpan>>( + forward!(pub fn span_note( &mut self, - sp: S, + sp: impl Into<MultiSpan>, msg: &str, ) -> &mut Self); forward!(pub fn warn(&mut self, msg: &str) -> &mut Self); - forward!(pub fn span_warn<S: Into<MultiSpan>>(&mut self, sp: S, msg: &str) -> &mut Self); + forward!(pub fn span_warn(&mut self, sp: impl Into<MultiSpan>, msg: &str) -> &mut Self); forward!(pub fn help(&mut self, msg: &str) -> &mut Self); - forward!(pub fn span_help<S: Into<MultiSpan>>( + forward!(pub fn span_help( &mut self, - sp: S, + sp: impl Into<MultiSpan>, msg: &str, ) -> &mut Self); forward!(pub fn set_is_lint(&mut self,) -> &mut Self); @@ -308,55 +480,35 @@ impl<'a> DiagnosticBuilder<'a> { applicability: Applicability, ) -> &mut Self); - forward!(pub fn set_primary_message<M: Into<String>>(&mut self, msg: M) -> &mut Self); - forward!(pub fn set_span<S: Into<MultiSpan>>(&mut self, sp: S) -> &mut Self); + forward!(pub fn set_primary_message(&mut self, msg: impl Into<String>) -> &mut Self); + forward!(pub fn set_span(&mut self, sp: impl Into<MultiSpan>) -> &mut Self); forward!(pub fn code(&mut self, s: DiagnosticId) -> &mut Self); - - /// Convenience function for internal use, clients should use one of the - /// `struct_*` methods on [`Handler`]. - crate fn new(handler: &'a Handler, level: Level, message: &str) -> DiagnosticBuilder<'a> { - DiagnosticBuilder::new_with_code(handler, level, None, message) - } - - /// Convenience function for internal use, clients should use one of the - /// `struct_*` methods on [`Handler`]. - crate fn new_with_code( - handler: &'a Handler, - level: Level, - code: Option<DiagnosticId>, - message: &str, - ) -> DiagnosticBuilder<'a> { - let diagnostic = Diagnostic::new_with_code(level, code, message); - DiagnosticBuilder::new_diagnostic(handler, diagnostic) - } - - /// Creates a new `DiagnosticBuilder` with an already constructed - /// diagnostic. - crate fn new_diagnostic(handler: &'a Handler, diagnostic: Diagnostic) -> DiagnosticBuilder<'a> { - debug!("Created new diagnostic"); - DiagnosticBuilder { handler, diagnostic: Box::new(diagnostic) } - } } -impl<'a> Debug for DiagnosticBuilder<'a> { +impl<G: EmissionGuarantee> Debug for DiagnosticBuilder<'_, G> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.diagnostic.fmt(f) + self.inner.diagnostic.fmt(f) } } -/// Destructor bomb - a `DiagnosticBuilder` must be either emitted or canceled +/// Destructor bomb - a `DiagnosticBuilder` must be either emitted or cancelled /// or we emit a bug. -impl<'a> Drop for DiagnosticBuilder<'a> { +impl Drop for DiagnosticBuilderInner<'_> { fn drop(&mut self) { - if !panicking() && !self.cancelled() { - let mut db = DiagnosticBuilder::new( - self.handler, - Level::Bug, - "the following error was constructed but not emitted", - ); - db.emit(); - self.emit(); - panic!(); + match self.state { + // No `.emit()` or `.cancel()` calls. + DiagnosticBuilderState::Emittable(handler) => { + if !panicking() { + handler.emit_diagnostic(&Diagnostic::new( + Level::Bug, + "the following error was constructed but not emitted", + )); + handler.emit_diagnostic(&self.diagnostic); + panic!(); + } + } + // `.emit()` was previously called, or maybe we're during `.cancel()`. + DiagnosticBuilderState::AlreadyEmittedOrDuringCancellation => {} } } } diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs index a5c954cca13..463308c27b2 100644 --- a/compiler/rustc_errors/src/lib.rs +++ b/compiler/rustc_errors/src/lib.rs @@ -9,6 +9,8 @@ #![feature(let_else)] #![feature(nll)] #![cfg_attr(not(bootstrap), allow(rustc::potential_query_instability))] +#![feature(adt_const_params)] +#![allow(incomplete_features)] #[macro_use] extern crate rustc_macros; @@ -52,7 +54,7 @@ mod snippet; mod styled_buffer; pub use snippet::Style; -pub type PResult<'a, T> = Result<T, DiagnosticBuilder<'a>>; +pub type PResult<'a, T> = Result<T, DiagnosticBuilder<'a, ErrorReported>>; // `PResult` is used a lot. Make sure it doesn't unintentionally get bigger. // (See also the comment on `DiagnosticBuilder`'s `diagnostic` field.) @@ -491,10 +493,15 @@ impl Drop for HandlerInner { self.flush_delayed(bugs, "no errors encountered even though `delay_span_bug` issued"); } + // FIXME(eddyb) this explains what `delayed_good_path_bugs` are! + // They're `delayed_span_bugs` but for "require some diagnostic happened" + // instead of "require some error happened". Sadly that isn't ideal, as + // lints can be `#[allow]`'d, potentially leading to this triggering. + // Also, "good path" should be replaced with a better naming. if !self.has_any_message() { let bugs = std::mem::replace(&mut self.delayed_good_path_bugs, Vec::new()); self.flush_delayed( - bugs.into_iter().map(DelayedDiagnostic::decorate).collect(), + bugs.into_iter().map(DelayedDiagnostic::decorate), "no warnings or errors encountered even though `delayed_good_path_bugs` issued", ); } @@ -604,7 +611,7 @@ impl Handler { } /// Steal a previously stashed diagnostic with the given `Span` and `StashKey` as the key. - pub fn steal_diagnostic(&self, span: Span, key: StashKey) -> Option<DiagnosticBuilder<'_>> { + pub fn steal_diagnostic(&self, span: Span, key: StashKey) -> Option<DiagnosticBuilder<'_, ()>> { self.inner .borrow_mut() .stashed_diagnostics @@ -617,33 +624,17 @@ impl Handler { self.inner.borrow_mut().emit_stashed_diagnostics(); } - /// Construct a dummy builder with `Level::Cancelled`. - /// - /// Using this will neither report anything to the user (e.g. a warning), - /// nor will compilation cancel as a result. - pub fn struct_dummy(&self) -> DiagnosticBuilder<'_> { - DiagnosticBuilder::new(self, Level::Cancelled, "") - } - - /// Construct a builder at the `Warning` level at the given `span` and with the `msg`. - /// - /// The builder will be canceled if warnings cannot be emitted. - pub fn struct_span_warn(&self, span: impl Into<MultiSpan>, msg: &str) -> DiagnosticBuilder<'_> { - let mut result = self.struct_warn(msg); - result.set_span(span); - result - } - /// Construct a builder at the `Warning` level at the given `span` and with the `msg`. /// - /// This will "force" the warning meaning it will not be canceled even - /// if warnings cannot be emitted. - pub fn struct_span_force_warn( + /// Attempting to `.emit()` the builder will only emit if either: + /// * `can_emit_warnings` is `true` + /// * `is_force_warn` was set in `DiagnosticId::Lint` + pub fn struct_span_warn( &self, span: impl Into<MultiSpan>, msg: &str, - ) -> DiagnosticBuilder<'_> { - let mut result = self.struct_force_warn(msg); + ) -> DiagnosticBuilder<'_, ()> { + let mut result = self.struct_warn(msg); result.set_span(span); result } @@ -653,7 +644,7 @@ impl Handler { &self, span: impl Into<MultiSpan>, msg: &str, - ) -> DiagnosticBuilder<'_> { + ) -> DiagnosticBuilder<'_, ()> { let mut result = self.struct_allow(msg); result.set_span(span); result @@ -666,7 +657,7 @@ impl Handler { span: impl Into<MultiSpan>, msg: &str, code: DiagnosticId, - ) -> DiagnosticBuilder<'_> { + ) -> DiagnosticBuilder<'_, ()> { let mut result = self.struct_span_warn(span, msg); result.code(code); result @@ -674,30 +665,24 @@ impl Handler { /// Construct a builder at the `Warning` level with the `msg`. /// - /// The builder will be canceled if warnings cannot be emitted. - pub fn struct_warn(&self, msg: &str) -> DiagnosticBuilder<'_> { - let mut result = DiagnosticBuilder::new(self, Level::Warning, msg); - if !self.flags.can_emit_warnings { - result.cancel(); - } - result - } - - /// Construct a builder at the `Warning` level with the `msg`. - /// - /// This will "force" a warning meaning it will not be canceled even - /// if warnings cannot be emitted. - pub fn struct_force_warn(&self, msg: &str) -> DiagnosticBuilder<'_> { + /// Attempting to `.emit()` the builder will only emit if either: + /// * `can_emit_warnings` is `true` + /// * `is_force_warn` was set in `DiagnosticId::Lint` + pub fn struct_warn(&self, msg: &str) -> DiagnosticBuilder<'_, ()> { DiagnosticBuilder::new(self, Level::Warning, msg) } /// Construct a builder at the `Allow` level with the `msg`. - pub fn struct_allow(&self, msg: &str) -> DiagnosticBuilder<'_> { + pub fn struct_allow(&self, msg: &str) -> DiagnosticBuilder<'_, ()> { DiagnosticBuilder::new(self, Level::Allow, msg) } /// Construct a builder at the `Error` level at the given `span` and with the `msg`. - pub fn struct_span_err(&self, span: impl Into<MultiSpan>, msg: &str) -> DiagnosticBuilder<'_> { + pub fn struct_span_err( + &self, + span: impl Into<MultiSpan>, + msg: &str, + ) -> DiagnosticBuilder<'_, ErrorReported> { let mut result = self.struct_err(msg); result.set_span(span); result @@ -709,7 +694,7 @@ impl Handler { span: impl Into<MultiSpan>, msg: &str, code: DiagnosticId, - ) -> DiagnosticBuilder<'_> { + ) -> DiagnosticBuilder<'_, ErrorReported> { let mut result = self.struct_span_err(span, msg); result.code(code); result @@ -717,18 +702,22 @@ impl Handler { /// Construct a builder at the `Error` level with the `msg`. // FIXME: This method should be removed (every error should have an associated error code). - pub fn struct_err(&self, msg: &str) -> DiagnosticBuilder<'_> { - DiagnosticBuilder::new(self, Level::Error { lint: false }, msg) + pub fn struct_err(&self, msg: &str) -> DiagnosticBuilder<'_, ErrorReported> { + DiagnosticBuilder::new_guaranteeing_error::<{ Level::Error { lint: false } }>(self, msg) } /// This should only be used by `rustc_middle::lint::struct_lint_level`. Do not use it for hard errors. #[doc(hidden)] - pub fn struct_err_lint(&self, msg: &str) -> DiagnosticBuilder<'_> { + pub fn struct_err_lint(&self, msg: &str) -> DiagnosticBuilder<'_, ()> { DiagnosticBuilder::new(self, Level::Error { lint: true }, msg) } /// Construct a builder at the `Error` level with the `msg` and the `code`. - pub fn struct_err_with_code(&self, msg: &str, code: DiagnosticId) -> DiagnosticBuilder<'_> { + pub fn struct_err_with_code( + &self, + msg: &str, + code: DiagnosticId, + ) -> DiagnosticBuilder<'_, ErrorReported> { let mut result = self.struct_err(msg); result.code(code); result @@ -739,7 +728,7 @@ impl Handler { &self, span: impl Into<MultiSpan>, msg: &str, - ) -> DiagnosticBuilder<'_> { + ) -> DiagnosticBuilder<'_, ErrorReported> { let mut result = self.struct_fatal(msg); result.set_span(span); result @@ -751,24 +740,24 @@ impl Handler { span: impl Into<MultiSpan>, msg: &str, code: DiagnosticId, - ) -> DiagnosticBuilder<'_> { + ) -> DiagnosticBuilder<'_, ErrorReported> { let mut result = self.struct_span_fatal(span, msg); result.code(code); result } /// Construct a builder at the `Error` level with the `msg`. - pub fn struct_fatal(&self, msg: &str) -> DiagnosticBuilder<'_> { - DiagnosticBuilder::new(self, Level::Fatal, msg) + pub fn struct_fatal(&self, msg: &str) -> DiagnosticBuilder<'_, ErrorReported> { + DiagnosticBuilder::new_guaranteeing_error::<{ Level::Fatal }>(self, msg) } /// Construct a builder at the `Help` level with the `msg`. - pub fn struct_help(&self, msg: &str) -> DiagnosticBuilder<'_> { + pub fn struct_help(&self, msg: &str) -> DiagnosticBuilder<'_, ()> { DiagnosticBuilder::new(self, Level::Help, msg) } /// Construct a builder at the `Note` level with the `msg`. - pub fn struct_note_without_error(&self, msg: &str) -> DiagnosticBuilder<'_> { + pub fn struct_note_without_error(&self, msg: &str) -> DiagnosticBuilder<'_, ()> { DiagnosticBuilder::new(self, Level::Note, msg) } @@ -815,6 +804,8 @@ impl Handler { self.inner.borrow_mut().delay_span_bug(span, msg) } + // FIXME(eddyb) note the comment inside `impl Drop for HandlerInner`, that's + // where the explanation of what "good path" is (also, it should be renamed). pub fn delay_good_path_bug(&self, msg: &str) { self.inner.borrow_mut().delay_good_path_bug(msg) } @@ -827,7 +818,7 @@ impl Handler { self.emit_diag_at_span(Diagnostic::new(Note, msg), span); } - pub fn span_note_diag(&self, span: Span, msg: &str) -> DiagnosticBuilder<'_> { + pub fn span_note_diag(&self, span: Span, msg: &str) -> DiagnosticBuilder<'_, ()> { let mut db = DiagnosticBuilder::new(self, Note, msg); db.set_span(span); db @@ -915,10 +906,6 @@ impl Handler { pub fn emit_unused_externs(&self, lint_level: &str, unused_externs: &[&str]) { self.inner.borrow_mut().emit_unused_externs(lint_level, unused_externs) } - - pub fn delay_as_bug(&self, diagnostic: Diagnostic) { - self.inner.borrow_mut().delay_as_bug(diagnostic) - } } impl HandlerInner { @@ -936,9 +923,18 @@ impl HandlerInner { diags.iter().for_each(|diag| self.emit_diagnostic(diag)); } + // FIXME(eddyb) this should ideally take `diagnostic` by value. fn emit_diagnostic(&mut self, diagnostic: &Diagnostic) { - if diagnostic.cancelled() { - return; + if diagnostic.level == Level::DelayedBug { + // FIXME(eddyb) this should check for `has_errors` and stop pushing + // once *any* errors were emitted (and truncate `delayed_span_bugs` + // when an error is first emitted, also), but maybe there's a case + // in which that's not sound? otherwise this is really inefficient. + self.delayed_span_bugs.push(diagnostic.clone()); + + if !self.flags.report_delayed_bugs { + return; + } } if diagnostic.has_future_breakage() { @@ -1119,14 +1115,16 @@ impl HandlerInner { // FIXME: don't abort here if report_delayed_bugs is off self.span_bug(sp, msg); } - let mut diagnostic = Diagnostic::new(Level::Bug, msg); + let mut diagnostic = Diagnostic::new(Level::DelayedBug, msg); diagnostic.set_span(sp.into()); diagnostic.note(&format!("delayed at {}", std::panic::Location::caller())); - self.delay_as_bug(diagnostic) + self.emit_diagnostic(&diagnostic) } + // FIXME(eddyb) note the comment inside `impl Drop for HandlerInner`, that's + // where the explanation of what "good path" is (also, it should be renamed). fn delay_good_path_bug(&mut self, msg: &str) { - let diagnostic = Diagnostic::new(Level::Bug, msg); + let diagnostic = Diagnostic::new(Level::DelayedBug, msg); if self.flags.report_delayed_bugs { self.emit_diagnostic(&diagnostic); } @@ -1160,20 +1158,34 @@ impl HandlerInner { panic::panic_any(ExplicitBug); } - fn delay_as_bug(&mut self, diagnostic: Diagnostic) { - if self.flags.report_delayed_bugs { - self.emit_diagnostic(&diagnostic); - } - self.delayed_span_bugs.push(diagnostic); - } + fn flush_delayed(&mut self, bugs: impl IntoIterator<Item = Diagnostic>, explanation: &str) { + let mut no_bugs = true; + for mut bug in bugs { + if no_bugs { + // Put the overall explanation before the `DelayedBug`s, to + // frame them better (e.g. separate warnings from them). + self.emit_diagnostic(&Diagnostic::new(Bug, explanation)); + no_bugs = false; + } + + // "Undelay" the `DelayedBug`s (into plain `Bug`s). + if bug.level != Level::DelayedBug { + // NOTE(eddyb) not panicking here because we're already producing + // an ICE, and the more information the merrier. + bug.note(&format!( + "`flushed_delayed` got diagnostic with level {:?}, \ + instead of the expected `DelayedBug`", + bug.level, + )); + } + bug.level = Level::Bug; - fn flush_delayed(&mut self, bugs: Vec<Diagnostic>, explanation: &str) { - let has_bugs = !bugs.is_empty(); - for bug in bugs { self.emit_diagnostic(&bug); } - if has_bugs { - panic!("{}", explanation); + + // Panic with `ExplicitBug` to avoid "unexpected panic" messages. + if !no_bugs { + panic::panic_any(ExplicitBug); } } @@ -1224,9 +1236,10 @@ impl DelayedDiagnostic { } } -#[derive(Copy, PartialEq, Clone, Hash, Debug, Encodable, Decodable)] +#[derive(Copy, PartialEq, Eq, Clone, Hash, Debug, Encodable, Decodable)] pub enum Level { Bug, + DelayedBug, Fatal, Error { /// If this error comes from a lint, don't abort compilation even when abort_if_errors() is called. @@ -1235,7 +1248,6 @@ pub enum Level { Warning, Note, Help, - Cancelled, FailureNote, Allow, } @@ -1250,7 +1262,7 @@ impl Level { fn color(self) -> ColorSpec { let mut spec = ColorSpec::new(); match self { - Bug | Fatal | Error { .. } => { + Bug | DelayedBug | Fatal | Error { .. } => { spec.set_fg(Some(Color::Red)).set_intense(true); } Warning => { @@ -1263,20 +1275,19 @@ impl Level { spec.set_fg(Some(Color::Cyan)).set_intense(true); } FailureNote => {} - Allow | Cancelled => unreachable!(), + Allow => unreachable!(), } spec } pub fn to_str(self) -> &'static str { match self { - Bug => "error: internal compiler error", + Bug | DelayedBug => "error: internal compiler error", Fatal | Error { .. } => "error", Warning => "warning", Note => "note", Help => "help", FailureNote => "failure-note", - Cancelled => panic!("Shouldn't call on cancelled error"), Allow => panic!("Shouldn't call on allowed error"), } } @@ -1286,9 +1297,10 @@ impl Level { } } +// FIXME(eddyb) this doesn't belong here AFAICT, should be moved to callsite. pub fn add_elided_lifetime_in_path_suggestion( source_map: &SourceMap, - db: &mut DiagnosticBuilder<'_>, + diag: &mut Diagnostic, n: usize, path_span: Span, incl_angl_brckt: bool, @@ -1320,7 +1332,7 @@ pub fn add_elided_lifetime_in_path_suggestion( (insertion_span, anon_lts) } }; - db.span_suggestion( + diag.span_suggestion( replace_span, &format!("indicate the anonymous lifetime{}", pluralize!(n)), suggestion, |
