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/// Trait for types that `DiagBuilder::emit` can return as a "guarantee" (or "proof") token
16/// that the emission happened.
17pub trait EmissionGuarantee: Sized {
18    /// This exists so that bugs and fatal errors can both result in `!` (an abort) when emitted,
19    /// but have different aborting behaviour.
20    type EmitResult;
21
22    /// Implementation of `DiagBuilder::emit`, fully controlled by each `impl` of
23    /// `EmissionGuarantee`, to make it impossible to create a value of `Self::EmitResult` without
24    /// actually performing the emission.
25    #[track_caller]
26    fn emit_producing_guarantee(db: &mut DiagBuilder<'_, Self>) -> Self::EmitResult;
27}
28
29impl EmissionGuarantee for ErrorGuaranteed {
30    type EmitResult = Self;
31
32    fn emit_producing_guarantee(db: &mut DiagBuilder<'_, Self>) -> Self::EmitResult {
33        let guar = db.emit_producing_error_guaranteed();
34
35        // Only allow a guarantee if the `level` wasn't switched to a
36        // non-error - the field isn't `pub`, but the whole `Diag`
37        // can be overwritten with a new one, thanks to `DerefMut`.
38        assert!(
39            db.diagnostic.is_error(),
40            "emitted non-error ({:?}) diagnostic from `DiagBuilder<ErrorGuaranteed>`",
41            db.diagnostic.level,
42        );
43
44        guar.unwrap_err()
45    }
46}
47
48impl EmissionGuarantee for () {
49    type EmitResult = Self;
50
51    fn emit_producing_guarantee(db: &mut DiagBuilder<'_, Self>) -> Self::EmitResult {
52        db.emit_producing_nothing();
53    }
54}
55
56impl EmissionGuarantee for BugAbort {
57    type EmitResult = Never;
58
59    fn emit_producing_guarantee(db: &mut DiagBuilder<'_, Self>) -> Self::EmitResult {
60        db.emit_producing_nothing();
61        std::panic::panic_any(ExplicitBug);
62    }
63}
64
65impl EmissionGuarantee for FatalAbort {
66    type EmitResult = Never;
67
68    fn emit_producing_guarantee(db: &mut DiagBuilder<'_, Self>) -> Self::EmitResult {
69        db.emit_producing_nothing();
70        std::panic::panic_any(Self);
71    }
72}
73
74/// Used for emitting structured error messages and other diagnostic information.
75///
76/// **Note:** Incorrect usage of this type results in a panic when dropped.
77/// This is to ensure that all errors are either emitted or cancelled.
78#[must_use = "diagnostics must be emitted or cancelled"]
79pub struct DiagBuilder<'a, G: EmissionGuarantee> {
80    dcx: &'a DiagCtxt,
81
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>,
87
88    _marker: PhantomData<G>,
89}
90
91impl<G: EmissionGuarantee> Clone for DiagBuilder<'_, G> {
92    #[inline]
93    fn clone(&self) -> Self {
94        Self { dcx: self.dcx, 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.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
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
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 _ = self.dcx.emit_diagnostic(Diag::new(
128            Level::Bug,
129            "the following error was constructed but not emitted",
130        ));
131        let _ = self.dcx.emit_diagnostic_without_consuming(&mut self.diagnostic);
132        panic!("error was constructed but not emitted");
133    }
134}
135
136impl<'a, G: EmissionGuarantee> DiagBuilder<'a, G> {
137    /// Creates a new `DiagBuilder`.
138    #[track_caller]
139    pub fn new<M: Into<DiagMsg>>(dcx: &'a DiagCtxt, level: Level, msg: M) -> Self {
140        Self { dcx, diagnostic: Box::new(Diag::new(level, msg)), _marker: PhantomData }
141    }
142
143    /// Returns the [`DiagCtxt`].
144    #[inline]
145    pub fn dcx(&self) -> &DiagCtxt {
146        self.dcx
147    }
148
149    /// Emits the diagnostic.
150    #[track_caller]
151    pub fn emit(mut self) -> G::EmitResult {
152        if self.dcx.track_diagnostics() {
153            self.diagnostic.locations_note(Location::caller());
154        }
155        self.consume_no_panic(G::emit_producing_guarantee)
156    }
157
158    fn emit_producing_nothing(&mut self) {
159        let _ = self.emit_producing_error_guaranteed();
160    }
161
162    fn emit_producing_error_guaranteed(&mut self) -> Result<(), ErrorGuaranteed> {
163        self.dcx.emit_diagnostic_without_consuming(&mut self.diagnostic)
164    }
165
166    /// Cancel the diagnostic (a structured diagnostic must either be emitted or cancelled or it
167    /// will panic when dropped).
168    #[inline]
169    pub fn cancel(self) {
170        self.consume_no_panic(|_| {});
171    }
172
173    fn consume_no_panic<R>(self, f: impl FnOnce(&mut Self) -> R) -> R {
174        let mut this = ManuallyDrop::new(self);
175        let r = f(&mut *this);
176        unsafe { std::ptr::drop_in_place(&mut this.diagnostic) };
177        r
178    }
179}
180
181/// Forwards methods to [`Diag`].
182macro_rules! forward {
183    (
184        $(
185            $(#[$attrs:meta])*
186            $vis:vis fn $n:ident($($name:ident: $ty:ty),* $(,)?);
187        )*
188    ) => {
189        $(
190            $(#[$attrs])*
191            #[doc = concat!("See [`Diag::", stringify!($n), "()`].")]
192            $vis fn $n(mut self, $($name: $ty),*) -> Self {
193                self.diagnostic.$n($($name),*);
194                self
195            }
196        )*
197    };
198}
199
200/// Forwarded methods to [`Diag`].
201impl<G: EmissionGuarantee> DiagBuilder<'_, G> {
202    forward! {
203        pub fn span(span: impl Into<MultiSpan>);
204        pub fn code(code: impl Into<DiagId>);
205
206        pub fn span_label(span: Span, label: impl Into<DiagMsg>);
207        pub fn span_labels(spans: impl IntoIterator<Item = Span>, label: impl Into<DiagMsg>);
208
209        pub fn warn(msg: impl Into<DiagMsg>);
210        pub fn span_warn(span: impl Into<MultiSpan>, msg: impl Into<DiagMsg>);
211
212        pub fn note(msg: impl Into<DiagMsg>);
213        pub fn span_note(span: impl Into<MultiSpan>, msg: impl Into<DiagMsg>);
214        pub fn highlighted_note(messages: Vec<(impl Into<DiagMsg>, Style)>);
215        pub fn note_once(msg: impl Into<DiagMsg>);
216        pub fn span_note_once(span: impl Into<MultiSpan>, msg: impl Into<DiagMsg>);
217
218        pub fn help(msg: impl Into<DiagMsg>);
219        pub fn help_once(msg: impl Into<DiagMsg>);
220        pub fn highlighted_help(messages: Vec<(impl Into<DiagMsg>, Style)>);
221        pub fn span_help(span: impl Into<MultiSpan>, msg: impl Into<DiagMsg>);
222    }
223}