solar_interface/diagnostics/
builder.rs

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