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