diff options
| author | Eduard-Mihai Burtescu <eddyb@lyken.rs> | 2022-01-27 09:44:25 +0000 |
|---|---|---|
| committer | Eduard-Mihai Burtescu <eddyb@lyken.rs> | 2022-02-23 06:38:52 +0000 |
| commit | b7e95dee65c35db8f8e07046d445b12d92cbae12 (patch) | |
| tree | af9131de515325e67c68ab9bbd3c514298ca57f4 /compiler/rustc_errors/src | |
| parent | 0b9d70cf6d47df456280f83b58c04c96aa58e89e (diff) | |
| download | rust-b7e95dee65c35db8f8e07046d445b12d92cbae12.tar.gz rust-b7e95dee65c35db8f8e07046d445b12d92cbae12.zip | |
rustc_errors: let `DiagnosticBuilder::emit` return a "guarantee of emission".
Diffstat (limited to 'compiler/rustc_errors/src')
| -rw-r--r-- | compiler/rustc_errors/src/diagnostic_builder.rs | 234 | ||||
| -rw-r--r-- | compiler/rustc_errors/src/lib.rs | 56 |
2 files changed, 205 insertions, 85 deletions
diff --git a/compiler/rustc_errors/src/diagnostic_builder.rs b/compiler/rustc_errors/src/diagnostic_builder.rs index 7978e1cc162..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,7 +16,24 @@ use tracing::debug; /// extending `HandlerFlags`, accessed via `self.handler.flags`. #[must_use] #[derive(Clone)] -pub struct DiagnosticBuilder<'a> { +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 @@ -38,8 +56,8 @@ enum DiagnosticBuilderState<'a> { /// 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 `DiagnosticBuilder`, as `.cancel()` takes `self` - /// by-value specifically to prevent any attempts to `.emit()`. + /// 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 @@ -53,6 +71,133 @@ rustc_data_structures::static_assert_size!( 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 @@ -83,52 +228,43 @@ macro_rules! forward { $(#[$attrs])* #[doc = concat!("See [`Diagnostic::", stringify!($n), "()`].")] pub fn $n(&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) { - match self.state { - // First `.emit()` call, the `&Handler` is still available. - DiagnosticBuilderState::Emittable(handler) => { - handler.emit_diagnostic(&self); - self.state = DiagnosticBuilderState::AlreadyEmittedOrDuringCancellation; - } - // `.emit()` was previously called, disallowed from repeating it. - DiagnosticBuilderState::AlreadyEmittedOrDuringCancellation => { - // FIXME(eddyb) rely on this to return a "proof" that an error - // was/will be emitted, despite doing no emission *here and now*. - } - } + #[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.downgrade_to_delayed_bug(); } - self.emit(); + self.emit() } /// Cancel the diagnostic (a structured diagnostic must either be emitted or @@ -138,7 +274,7 @@ impl<'a> DiagnosticBuilder<'a> { /// 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.state = DiagnosticBuilderState::AlreadyEmittedOrDuringCancellation; + self.inner.state = DiagnosticBuilderState::AlreadyEmittedOrDuringCancellation; drop(self); } @@ -156,7 +292,7 @@ impl<'a> DiagnosticBuilder<'a> { /// Converts the builder to a `Diagnostic` for later emission, /// unless handler has disabled such buffering, or `.emit()` was called. pub fn into_diagnostic(mut self) -> Option<(Diagnostic, &'a Handler)> { - let handler = match self.state { + 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. @@ -172,7 +308,7 @@ impl<'a> DiagnosticBuilder<'a> { // Take the `Diagnostic` by replacing it with a dummy. let dummy = Diagnostic::new(Level::Allow, ""); - let diagnostic = std::mem::replace(&mut *self.diagnostic, dummy); + let diagnostic = std::mem::replace(&mut *self.inner.diagnostic, dummy); // Disable the ICE on `Drop`. self.cancel(); @@ -347,57 +483,27 @@ impl<'a> DiagnosticBuilder<'a> { 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 { - state: DiagnosticBuilderState::Emittable(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 cancelled /// or we emit a bug. -impl<'a> Drop for DiagnosticBuilder<'a> { +impl Drop for DiagnosticBuilderInner<'_> { fn drop(&mut self) { match self.state { // No `.emit()` or `.cancel()` calls. DiagnosticBuilderState::Emittable(handler) => { if !panicking() { - let mut db = DiagnosticBuilder::new( - handler, + handler.emit_diagnostic(&Diagnostic::new( Level::Bug, "the following error was constructed but not emitted", - ); - db.emit(); - handler.emit_diagnostic(&self); + )); + handler.emit_diagnostic(&self.diagnostic); panic!(); } } diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs index b92b1cac2e8..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.) @@ -609,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 @@ -627,7 +629,11 @@ impl Handler { /// 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<'_> { + 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 @@ -638,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 @@ -651,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 @@ -662,17 +668,21 @@ impl Handler { /// 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<'_> { + 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 @@ -684,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 @@ -692,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 @@ -714,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 @@ -726,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) } @@ -804,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 @@ -1222,7 +1236,7 @@ impl DelayedDiagnostic { } } -#[derive(Copy, PartialEq, Clone, Hash, Debug, Encodable, Decodable)] +#[derive(Copy, PartialEq, Eq, Clone, Hash, Debug, Encodable, Decodable)] pub enum Level { Bug, DelayedBug, |
