solar_interface/diagnostics/
mod.rs

1//! Diagnostics implementation.
2//!
3//! Modified from [`rustc_errors`](https://github.com/rust-lang/rust/blob/520e30be83b4ed57b609d33166c988d1512bf4f3/compiler/rustc_errors/src/diagnostic.rs).
4
5use crate::Span;
6use anstyle::{AnsiColor, Color};
7use std::{borrow::Cow, fmt, panic::Location};
8
9mod builder;
10pub use builder::{DiagnosticBuilder, EmissionGuarantee};
11
12mod context;
13pub use context::{DiagCtxt, DiagCtxtFlags};
14
15mod emitter;
16#[cfg(feature = "json")]
17pub use emitter::JsonEmitter;
18pub use emitter::{
19    DynEmitter, Emitter, HumanBufferEmitter, HumanEmitter, LocalEmitter, SilentEmitter,
20};
21
22mod message;
23pub use message::{DiagnosticMessage, MultiSpan, SpanLabel};
24
25/// Represents all the diagnostics emitted up to a certain point.
26///
27/// Returned by [`DiagCtxt::emitted_diagnostics`].
28pub struct EmittedDiagnostics(pub(crate) String);
29
30impl fmt::Debug for EmittedDiagnostics {
31    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32        f.write_str(&self.0)
33    }
34}
35
36impl fmt::Display for EmittedDiagnostics {
37    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38        f.write_str(&self.0)
39    }
40}
41
42impl std::error::Error for EmittedDiagnostics {}
43
44impl EmittedDiagnostics {
45    /// Returns `true` if no diagnostics have been emitted.
46    pub fn is_empty(&self) -> bool {
47        self.0.is_empty()
48    }
49}
50
51/// Useful type to use with [`Result`] indicate that an error has already been reported to the user,
52/// so no need to continue checking.
53#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
54pub struct ErrorGuaranteed(());
55
56impl fmt::Debug for ErrorGuaranteed {
57    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58        f.write_str("ErrorGuaranteed")
59    }
60}
61
62impl ErrorGuaranteed {
63    /// Creates a new `ErrorGuaranteed`.
64    ///
65    /// Use of this method is discouraged.
66    #[inline]
67    pub const fn new_unchecked() -> Self {
68        Self(())
69    }
70}
71
72/// Marker type which enables implementation of `create_bug` and `emit_bug` functions for
73/// bug diagnostics.
74#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
75pub struct BugAbort;
76
77/// Signifies that the compiler died with an explicit call to `.bug` rather than a failed assertion,
78/// etc.
79pub struct ExplicitBug;
80
81/// Marker type which enables implementation of fatal diagnostics.
82pub struct FatalAbort;
83
84/// Diagnostic ID.
85///
86/// Use [`error_code!`](crate::error_code) to create an error code diagnostic ID.
87///
88/// # Examples
89///
90/// ```
91/// # use solar_interface::{diagnostics::DiagnosticId, error_code};
92/// let id: DiagnosticId = error_code!(1234);
93/// ```
94#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
95pub struct DiagnosticId {
96    id: u32,
97}
98
99impl DiagnosticId {
100    /// Creates an error code diagnostic ID.
101    ///
102    /// Use [`error_code!`](crate::error_code) instead.
103    #[doc(hidden)]
104    #[track_caller]
105    pub const fn new_from_macro(id: u32) -> Self {
106        assert!(id >= 1 && id <= 9999, "error code must be in range 0001-9999");
107        Self { id }
108    }
109
110    /// Returns the string representation of the diagnostic ID.
111    pub fn as_string(&self) -> String {
112        format!("{:04}", self.id)
113    }
114}
115
116/// Used for creating an error code. The input must be exactly 4 decimal digits.
117///
118/// # Examples
119///
120/// ```
121/// # use solar_interface::{diagnostics::DiagnosticId, error_code};
122/// let code: DiagnosticId = error_code!(1234);
123/// ```
124#[macro_export]
125macro_rules! error_code {
126    ($id:literal) => {
127        const { $crate::diagnostics::DiagnosticId::new_from_macro($id) }
128    };
129}
130
131/// Diagnostic level.
132#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
133pub enum Level {
134    /// For bugs in the compiler. Manifests as an ICE (internal compiler error) panic.
135    ///
136    /// Its `EmissionGuarantee` is `BugAbort`.
137    Bug,
138
139    /// An error that causes an immediate abort. Used for things like configuration errors,
140    /// internal overflows, some file operation errors.
141    ///
142    /// Its `EmissionGuarantee` is `FatalAbort`.
143    Fatal,
144
145    /// An error in the code being compiled, which prevents compilation from finishing. This is the
146    /// most common case.
147    ///
148    /// Its `EmissionGuarantee` is `ErrorGuaranteed`.
149    Error,
150
151    /// A warning about the code being compiled. Does not prevent compilation from finishing.
152    ///
153    /// Its `EmissionGuarantee` is `()`.
154    Warning,
155
156    /// A message giving additional context. Rare, because notes are more commonly attached to
157    /// other diagnostics such as errors.
158    ///
159    /// Its `EmissionGuarantee` is `()`.
160    Note,
161
162    /// A note that is only emitted once. Rare, mostly used in circumstances relating to lints.
163    ///
164    /// Its `EmissionGuarantee` is `()`.
165    OnceNote,
166
167    /// A message suggesting how to fix something. Rare, because help messages are more commonly
168    /// attached to other diagnostics such as errors.
169    ///
170    /// Its `EmissionGuarantee` is `()`.
171    Help,
172
173    /// A help that is only emitted once. Rare.
174    ///
175    /// Its `EmissionGuarantee` is `()`.
176    OnceHelp,
177
178    /// Similar to `Note`, but used in cases where compilation has failed. Rare.
179    ///
180    /// Its `EmissionGuarantee` is `()`.
181    FailureNote,
182
183    /// Only used for lints.
184    ///
185    /// Its `EmissionGuarantee` is `()`.
186    Allow,
187}
188
189impl Level {
190    /// Returns the string representation of the level.
191    pub fn to_str(self) -> &'static str {
192        match self {
193            Self::Bug => "error: internal compiler error",
194            Self::Fatal | Self::Error => "error",
195            Self::Warning => "warning",
196            Self::Note | Self::OnceNote => "note",
197            Self::Help | Self::OnceHelp => "help",
198            Self::FailureNote => "failure-note",
199            Self::Allow
200            // | Self::Expect(_)
201            => unreachable!(),
202        }
203    }
204
205    /// Returns `true` if this level is an error.
206    #[inline]
207    pub fn is_error(self) -> bool {
208        match self {
209            Self::Bug | Self::Fatal | Self::Error | Self::FailureNote => true,
210
211            Self::Warning
212            | Self::Note
213            | Self::OnceNote
214            | Self::Help
215            | Self::OnceHelp
216            | Self::Allow => false,
217        }
218    }
219
220    /// Returns the style of this level.
221    #[inline]
222    pub const fn style(self) -> anstyle::Style {
223        anstyle::Style::new().fg_color(self.color()).bold()
224    }
225
226    /// Returns the color of this level.
227    #[inline]
228    pub const fn color(self) -> Option<Color> {
229        match self.ansi_color() {
230            Some(c) => Some(Color::Ansi(c)),
231            None => None,
232        }
233    }
234
235    /// Returns the ANSI color of this level.
236    #[inline]
237    pub const fn ansi_color(self) -> Option<AnsiColor> {
238        // https://github.com/rust-lang/rust/blob/99472c7049783605444ab888a97059d0cce93a12/compiler/rustc_errors/src/lib.rs#L1768
239        match self {
240            Self::Bug | Self::Fatal | Self::Error => Some(AnsiColor::BrightRed),
241            Self::Warning => Some(AnsiColor::BrightYellow),
242            Self::Note | Self::OnceNote => Some(AnsiColor::BrightGreen),
243            Self::Help | Self::OnceHelp => Some(AnsiColor::BrightCyan),
244            Self::FailureNote | Self::Allow => None,
245        }
246    }
247}
248
249#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
250pub enum Style {
251    MainHeaderMsg,
252    HeaderMsg,
253    LineAndColumn,
254    LineNumber,
255    Quotation,
256    UnderlinePrimary,
257    UnderlineSecondary,
258    LabelPrimary,
259    LabelSecondary,
260    NoStyle,
261    Level(Level),
262    Highlight,
263    Addition,
264    Removal,
265}
266
267impl Style {
268    /// Converts the style to an [`anstyle::Style`].
269    pub const fn to_color_spec(self, level: Level) -> anstyle::Style {
270        use AnsiColor::*;
271
272        /// On Windows, BRIGHT_BLUE is hard to read on black. Use cyan instead.
273        ///
274        /// See [rust-lang/rust#36178](https://github.com/rust-lang/rust/pull/36178).
275        const BRIGHT_BLUE: Color = Color::Ansi(if cfg!(windows) { BrightCyan } else { BrightBlue });
276        const GREEN: Color = Color::Ansi(BrightGreen);
277        const MAGENTA: Color = Color::Ansi(BrightMagenta);
278        const RED: Color = Color::Ansi(BrightRed);
279        const WHITE: Color = Color::Ansi(BrightWhite);
280
281        let s = anstyle::Style::new();
282        match self {
283            Self::Addition => s.fg_color(Some(GREEN)),
284            Self::Removal => s.fg_color(Some(RED)),
285            Self::LineAndColumn => s,
286            Self::LineNumber => s.fg_color(Some(BRIGHT_BLUE)).bold(),
287            Self::Quotation => s,
288            Self::MainHeaderMsg => if cfg!(windows) { s.fg_color(Some(WHITE)) } else { s }.bold(),
289            Self::UnderlinePrimary | Self::LabelPrimary => s.fg_color(level.color()).bold(),
290            Self::UnderlineSecondary | Self::LabelSecondary => s.fg_color(Some(BRIGHT_BLUE)).bold(),
291            Self::HeaderMsg | Self::NoStyle => s,
292            Self::Level(level2) => s.fg_color(level2.color()).bold(),
293            Self::Highlight => s.fg_color(Some(MAGENTA)).bold(),
294        }
295    }
296}
297
298/// A "sub"-diagnostic attached to a parent diagnostic.
299/// For example, a note attached to an error.
300#[derive(Clone, Debug, PartialEq, Hash)]
301pub struct SubDiagnostic {
302    pub level: Level,
303    pub messages: Vec<(DiagnosticMessage, Style)>,
304    pub span: MultiSpan,
305}
306
307impl SubDiagnostic {
308    /// Formats the diagnostic messages into a single string.
309    pub fn label(&self) -> Cow<'_, str> {
310        flatten_messages(&self.messages)
311    }
312}
313
314/// A compiler diagnostic.
315#[must_use]
316#[derive(Clone, Debug)]
317pub struct Diagnostic {
318    pub(crate) level: Level,
319
320    pub messages: Vec<(DiagnosticMessage, Style)>,
321    pub span: MultiSpan,
322    pub children: Vec<SubDiagnostic>,
323    pub code: Option<DiagnosticId>,
324
325    pub created_at: &'static Location<'static>,
326}
327
328impl PartialEq for Diagnostic {
329    fn eq(&self, other: &Self) -> bool {
330        self.keys() == other.keys()
331    }
332}
333
334impl std::hash::Hash for Diagnostic {
335    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
336        self.keys().hash(state);
337    }
338}
339
340impl Diagnostic {
341    /// Creates a new `Diagnostic` with a single message.
342    #[track_caller]
343    pub fn new<M: Into<DiagnosticMessage>>(level: Level, msg: M) -> Self {
344        Self::new_with_messages(level, vec![(msg.into(), Style::NoStyle)])
345    }
346
347    /// Creates a new `Diagnostic` with multiple messages.
348    #[track_caller]
349    pub fn new_with_messages(level: Level, messages: Vec<(DiagnosticMessage, Style)>) -> Self {
350        Self {
351            level,
352            messages,
353            code: None,
354            span: MultiSpan::new(),
355            children: vec![],
356            // suggestions: Ok(vec![]),
357            // args: Default::default(),
358            // sort_span: DUMMY_SP,
359            // is_lint: false,
360            created_at: Location::caller(),
361        }
362    }
363
364    /// Returns `true` if this diagnostic is an error.
365    #[inline]
366    pub fn is_error(&self) -> bool {
367        self.level.is_error()
368    }
369
370    /// Formats the diagnostic messages into a single string.
371    pub fn label(&self) -> Cow<'_, str> {
372        flatten_messages(&self.messages)
373    }
374
375    /// Returns the messages of this diagnostic.
376    pub fn messages(&self) -> &[(DiagnosticMessage, Style)] {
377        &self.messages
378    }
379
380    /// Returns the level of this diagnostic.
381    pub fn level(&self) -> Level {
382        self.level
383    }
384
385    /// Returns the code of this diagnostic as a string.
386    pub fn id(&self) -> Option<String> {
387        self.code.as_ref().map(|code| code.as_string())
388    }
389
390    /// Fields used for `PartialEq` and `Hash` implementations.
391    fn keys(&self) -> impl PartialEq + std::hash::Hash + '_ {
392        (
393            &self.level,
394            &self.messages,
395            // self.args().collect(),
396            &self.code,
397            &self.span,
398            // &self.suggestions,
399            // (if self.is_lint { None } else { Some(&self.children) }),
400            &self.children,
401        )
402    }
403}
404
405/// Setters.
406impl Diagnostic {
407    /// Sets the span of this diagnostic.
408    pub fn span(&mut self, span: impl Into<MultiSpan>) -> &mut Self {
409        self.span = span.into();
410        self
411    }
412
413    /// Sets the code of this diagnostic.
414    pub fn code(&mut self, code: impl Into<DiagnosticId>) -> &mut Self {
415        self.code = Some(code.into());
416        self
417    }
418
419    /// Adds a span/label to be included in the resulting snippet.
420    ///
421    /// This is pushed onto the [`MultiSpan`] that was created when the diagnostic
422    /// was first built. That means it will be shown together with the original
423    /// span/label, *not* a span added by one of the `span_{note,warn,help,suggestions}` methods.
424    ///
425    /// This span is *not* considered a ["primary span"][`MultiSpan`]; only
426    /// the `Span` supplied when creating the diagnostic is primary.
427    pub fn span_label(&mut self, span: Span, label: impl Into<DiagnosticMessage>) -> &mut Self {
428        self.span.push_span_label(span, label);
429        self
430    }
431
432    /// Labels all the given spans with the provided label.
433    /// See [`Self::span_label()`] for more information.
434    pub fn span_labels(
435        &mut self,
436        spans: impl IntoIterator<Item = Span>,
437        label: impl Into<DiagnosticMessage>,
438    ) -> &mut Self {
439        let label = label.into();
440        for span in spans {
441            self.span_label(span, label.clone());
442        }
443        self
444    }
445
446    /// Adds a note with the location where this diagnostic was created and emitted.
447    pub(crate) fn locations_note(&mut self, emitted_at: &Location<'_>) -> &mut Self {
448        let msg = format!(
449            "created at {},\n\
450             emitted at {}",
451            self.created_at, emitted_at
452        );
453        self.note(msg)
454    }
455}
456
457/// Sub-diagnostics.
458impl Diagnostic {
459    /// Add a warning attached to this diagnostic.
460    pub fn warn(&mut self, msg: impl Into<DiagnosticMessage>) -> &mut Self {
461        self.sub(Level::Warning, msg, MultiSpan::new())
462    }
463
464    /// Prints the span with a warning above it.
465    /// This is like [`Diagnostic::warn()`], but it gets its own span.
466    pub fn span_warn(
467        &mut self,
468        span: impl Into<MultiSpan>,
469        msg: impl Into<DiagnosticMessage>,
470    ) -> &mut Self {
471        self.sub(Level::Warning, msg, span)
472    }
473
474    /// Add a note to this diagnostic.
475    pub fn note(&mut self, msg: impl Into<DiagnosticMessage>) -> &mut Self {
476        self.sub(Level::Note, msg, MultiSpan::new())
477    }
478
479    /// Prints the span with a note above it.
480    /// This is like [`Diagnostic::note()`], but it gets its own span.
481    pub fn span_note(
482        &mut self,
483        span: impl Into<MultiSpan>,
484        msg: impl Into<DiagnosticMessage>,
485    ) -> &mut Self {
486        self.sub(Level::Note, msg, span)
487    }
488
489    pub fn highlighted_note(
490        &mut self,
491        messages: Vec<(impl Into<DiagnosticMessage>, Style)>,
492    ) -> &mut Self {
493        self.sub_with_highlights(Level::Note, messages, MultiSpan::new())
494    }
495
496    /// Prints the span with a note above it.
497    /// This is like [`Diagnostic::note()`], but it gets emitted only once.
498    pub fn note_once(&mut self, msg: impl Into<DiagnosticMessage>) -> &mut Self {
499        self.sub(Level::OnceNote, msg, MultiSpan::new())
500    }
501
502    /// Prints the span with a note above it.
503    /// This is like [`Diagnostic::note_once()`], but it gets its own span.
504    pub fn span_note_once(
505        &mut self,
506        span: impl Into<MultiSpan>,
507        msg: impl Into<DiagnosticMessage>,
508    ) -> &mut Self {
509        self.sub(Level::OnceNote, msg, span)
510    }
511
512    /// Add a help message attached to this diagnostic.
513    pub fn help(&mut self, msg: impl Into<DiagnosticMessage>) -> &mut Self {
514        self.sub(Level::Help, msg, MultiSpan::new())
515    }
516
517    /// Prints the span with a help above it.
518    /// This is like [`Diagnostic::help()`], but it gets its own span.
519    pub fn help_once(&mut self, msg: impl Into<DiagnosticMessage>) -> &mut Self {
520        self.sub(Level::OnceHelp, msg, MultiSpan::new())
521    }
522
523    /// Add a help message attached to this diagnostic with a customizable highlighted message.
524    pub fn highlighted_help(
525        &mut self,
526        msgs: Vec<(impl Into<DiagnosticMessage>, Style)>,
527    ) -> &mut Self {
528        self.sub_with_highlights(Level::Help, msgs, MultiSpan::new())
529    }
530
531    /// Prints the span with some help above it.
532    /// This is like [`Diagnostic::help()`], but it gets its own span.
533    pub fn span_help(
534        &mut self,
535        span: impl Into<MultiSpan>,
536        msg: impl Into<DiagnosticMessage>,
537    ) -> &mut Self {
538        self.sub(Level::Help, msg, span)
539    }
540
541    fn sub(
542        &mut self,
543        level: Level,
544        msg: impl Into<DiagnosticMessage>,
545        span: impl Into<MultiSpan>,
546    ) -> &mut Self {
547        self.children.push(SubDiagnostic {
548            level,
549            messages: vec![(msg.into(), Style::NoStyle)],
550            span: span.into(),
551        });
552        self
553    }
554
555    fn sub_with_highlights(
556        &mut self,
557        level: Level,
558        messages: Vec<(impl Into<DiagnosticMessage>, Style)>,
559        span: MultiSpan,
560    ) -> &mut Self {
561        let messages = messages.into_iter().map(|(m, s)| (m.into(), s)).collect();
562        self.children.push(SubDiagnostic { level, messages, span });
563        self
564    }
565}
566
567// TODO: Styles?
568fn flatten_messages(messages: &[(DiagnosticMessage, Style)]) -> Cow<'_, str> {
569    match messages {
570        [] => Cow::Borrowed(""),
571        [(message, _)] => Cow::Borrowed(message.as_str()),
572        messages => messages.iter().map(|(msg, _)| msg.as_str()).collect(),
573    }
574}