solar_interface/diagnostics/
builder.rs

1use super::{
2    BugAbort, Diag, DiagCtxt, DiagId, DiagMsg, ErrorGuaranteed, ExplicitBug, FatalAbort, Level,
3    MultiSpan, Style,
4};
5use crate::Span;
6use solar_data_structures::Never;
7use std::{
8    fmt,
9    marker::PhantomData,
10    mem::ManuallyDrop,
11    ops::{Deref, DerefMut},
12    panic::Location,
13};
14
15// Since diagnostics are usually at the cold path, methods in this file are marked with
16// `#[inline(never)]` to prevent code bloat at callsites.
17
18/// Trait for types that `DiagBuilder::emit` can return as a "guarantee" (or "proof") token
19/// that the emission happened.
20pub trait EmissionGuarantee: Sized {
21    /// This exists so that bugs and fatal errors can both result in `!` (an abort) when emitted,
22    /// but have different aborting behaviour.
23    type EmitResult;
24
25    /// Implementation of `DiagBuilder::emit`, fully controlled by each `impl` of
26    /// `EmissionGuarantee`, to make it impossible to create a value of `Self::EmitResult` without
27    /// actually performing the emission.
28    #[track_caller]
29    fn emit_producing_guarantee(db: &mut DiagBuilder<'_, Self>) -> Self::EmitResult;
30}
31
32impl EmissionGuarantee for ErrorGuaranteed {
33    type EmitResult = Self;
34
35    fn emit_producing_guarantee(db: &mut DiagBuilder<'_, Self>) -> Self::EmitResult {
36        let guar = db.emit_producing_error_guaranteed();
37
38        // Only allow a guarantee if the `level` wasn't switched to a
39        // non-error - the field isn't `pub`, but the whole `Diag`
40        // can be overwritten with a new one, thanks to `DerefMut`.
41        assert!(
42            db.diagnostic.is_error(),
43            "emitted non-error ({:?}) diagnostic from `DiagBuilder<ErrorGuaranteed>`",
44            db.diagnostic.level,
45        );
46
47        guar.unwrap_err()
48    }
49}
50
51impl EmissionGuarantee for () {
52    type EmitResult = Self;
53
54    fn emit_producing_guarantee(db: &mut DiagBuilder<'_, Self>) -> Self::EmitResult {
55        db.emit_producing_nothing();
56    }
57}
58
59impl EmissionGuarantee for BugAbort {
60    type EmitResult = Never;
61
62    fn emit_producing_guarantee(db: &mut DiagBuilder<'_, Self>) -> Self::EmitResult {
63        db.emit_producing_nothing();
64        std::panic::panic_any(ExplicitBug);
65    }
66}
67
68impl EmissionGuarantee for FatalAbort {
69    type EmitResult = Never;
70
71    fn emit_producing_guarantee(db: &mut DiagBuilder<'_, Self>) -> Self::EmitResult {
72        db.emit_producing_nothing();
73        std::panic::panic_any(Self);
74    }
75}
76
77/// Used for emitting structured error messages and other diagnostic information.
78///
79/// **Note:** Incorrect usage of this type results in a panic when dropped.
80/// This is to ensure that all errors are either emitted or cancelled.
81#[must_use = "diagnostics must be emitted or cancelled"]
82pub struct DiagBuilder<'a, G: EmissionGuarantee> {
83    dcx: &'a DiagCtxt,
84
85    /// `Diag` is a large type, and `DiagBuilder` is often used as a
86    /// return value, especially within the frequently-used `PResult` type.
87    /// In theory, return value optimization (RVO) should avoid unnecessary
88    /// copying. In practice, it does not (at the time of writing).
89    diagnostic: Box<Diag>,
90
91    _marker: PhantomData<G>,
92}
93
94impl<G: EmissionGuarantee> Clone for DiagBuilder<'_, G> {
95    #[inline]
96    fn clone(&self) -> Self {
97        Self { dcx: self.dcx, diagnostic: self.diagnostic.clone(), _marker: PhantomData }
98    }
99}
100
101impl<G: EmissionGuarantee> fmt::Debug for DiagBuilder<'_, G> {
102    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103        self.diagnostic.fmt(f)
104    }
105}
106
107impl<G: EmissionGuarantee> Deref for DiagBuilder<'_, G> {
108    type Target = Diag;
109
110    #[inline]
111    fn deref(&self) -> &Self::Target {
112        &self.diagnostic
113    }
114}
115
116impl<G: EmissionGuarantee> DerefMut for DiagBuilder<'_, G> {
117    #[inline]
118    fn deref_mut(&mut self) -> &mut Self::Target {
119        &mut self.diagnostic
120    }
121}
122
123impl<G: EmissionGuarantee> Drop for DiagBuilder<'_, G> {
124    #[track_caller]
125    fn drop(&mut self) {
126        if std::thread::panicking() {
127            return;
128        }
129
130        let _ = self.dcx.emit_diagnostic(Diag::new(
131            Level::Bug,
132            "the following error was constructed but not emitted",
133        ));
134        let _ = self.dcx.emit_diagnostic_without_consuming(&mut self.diagnostic);
135        panic!("error was constructed but not emitted");
136    }
137}
138
139impl<'a, G: EmissionGuarantee> DiagBuilder<'a, G> {
140    /// Creates a new `DiagBuilder`.
141    #[inline(never)]
142    #[track_caller]
143    pub fn new<M: Into<DiagMsg>>(dcx: &'a DiagCtxt, level: Level, msg: M) -> Self {
144        Self { dcx, diagnostic: Box::new(Diag::new(level, msg)), _marker: PhantomData }
145    }
146
147    /// Returns the [`DiagCtxt`].
148    #[inline]
149    pub fn dcx(&self) -> &DiagCtxt {
150        self.dcx
151    }
152
153    /// Emits the diagnostic.
154    #[track_caller]
155    #[inline(never)]
156    pub fn emit(mut self) -> G::EmitResult {
157        if self.dcx.track_diagnostics() {
158            self.diagnostic.locations_note(Location::caller());
159        }
160        self.consume_no_panic(G::emit_producing_guarantee)
161    }
162
163    fn emit_producing_nothing(&mut self) {
164        let _ = self.emit_producing_error_guaranteed();
165    }
166
167    fn emit_producing_error_guaranteed(&mut self) -> Result<(), ErrorGuaranteed> {
168        self.dcx.emit_diagnostic_without_consuming(&mut self.diagnostic)
169    }
170
171    /// Cancel the diagnostic (a structured diagnostic must either be emitted or cancelled or it
172    /// will panic when dropped).
173    #[inline]
174    pub fn cancel(self) {
175        self.consume_no_panic(|_| {});
176    }
177
178    fn consume_no_panic<R>(self, f: impl FnOnce(&mut Self) -> R) -> R {
179        let mut this = ManuallyDrop::new(self);
180        let r = f(&mut *this);
181        unsafe { std::ptr::drop_in_place(&mut this.diagnostic) };
182        r
183    }
184}
185
186/// Forwards methods to [`Diag`].
187macro_rules! forward {
188    (
189        $(
190            $(#[$attrs:meta])*
191            $vis:vis fn $n:ident($($name:ident: $ty:ty),* $(,)?);
192        )*
193    ) => {
194        $(
195            $(#[$attrs])*
196            #[doc = concat!("See [`Diag::", stringify!($n), "()`].")]
197            #[inline(never)]
198            $vis fn $n(mut self, $($name: $ty),*) -> Self {
199                self.diagnostic.$n($($name),*);
200                self
201            }
202        )*
203    };
204}
205
206/// Forwarded methods to [`Diag`].
207impl<G: EmissionGuarantee> DiagBuilder<'_, G> {
208    forward! {
209        pub fn span(span: impl Into<MultiSpan>);
210        pub fn code(code: impl Into<DiagId>);
211
212        pub fn span_label(span: Span, label: impl Into<DiagMsg>);
213        pub fn span_labels(spans: impl IntoIterator<Item = Span>, label: impl Into<DiagMsg>);
214
215        pub fn warn(msg: impl Into<DiagMsg>);
216        pub fn span_warn(span: impl Into<MultiSpan>, msg: impl Into<DiagMsg>);
217
218        pub fn note(msg: impl Into<DiagMsg>);
219        pub fn span_note(span: impl Into<MultiSpan>, msg: impl Into<DiagMsg>);
220        pub fn highlighted_note(messages: Vec<(impl Into<DiagMsg>, Style)>);
221        pub fn note_once(msg: impl Into<DiagMsg>);
222        pub fn span_note_once(span: impl Into<MultiSpan>, msg: impl Into<DiagMsg>);
223
224        pub fn help(msg: impl Into<DiagMsg>);
225        pub fn help_once(msg: impl Into<DiagMsg>);
226        pub fn highlighted_help(messages: Vec<(impl Into<DiagMsg>, Style)>);
227        pub fn span_help(span: impl Into<MultiSpan>, msg: impl Into<DiagMsg>);
228    }
229}