solar_interface/diagnostics/emitter/
mod.rs

1use super::{Diag, Level, MultiSpan, SuggestionStyle};
2use crate::{SourceMap, diagnostics::Suggestions};
3use std::{any::Any, borrow::Cow, sync::Arc};
4
5mod human;
6pub use human::{HumanBufferEmitter, HumanEmitter};
7
8#[cfg(feature = "json")]
9mod json;
10#[cfg(feature = "json")]
11pub use json::JsonEmitter;
12
13mod mem;
14pub use mem::InMemoryEmitter;
15
16mod rustc;
17
18/// Dynamic diagnostic emitter. See [`Emitter`].
19pub type DynEmitter = dyn Emitter + Send;
20
21/// Diagnostic emitter.
22pub trait Emitter: Any {
23    /// Emits a diagnostic.
24    fn emit_diagnostic(&mut self, diagnostic: &mut Diag);
25
26    /// Returns a reference to the source map, if any.
27    #[inline]
28    fn source_map(&self) -> Option<&Arc<SourceMap>> {
29        None
30    }
31
32    /// Returns `true` if we can use colors in the current output stream.
33    #[inline]
34    fn supports_color(&self) -> bool {
35        false
36    }
37
38    /// Formats the substitutions of the primary_span
39    ///
40    /// There are a lot of conditions to this method, but in short:
41    ///
42    /// * If the current `DiagInner` has only one visible `CodeSuggestion`, we format the `help`
43    ///   suggestion depending on the content of the substitutions. In that case, we modify the span
44    ///   and clear the suggestions.
45    ///
46    /// * If the current `DiagInner` has multiple suggestions, we leave `primary_span` and the
47    ///   suggestions untouched.
48    fn primary_span_formatted<'a>(
49        &self,
50        primary_span: &mut Cow<'a, MultiSpan>,
51        suggestions: &mut Suggestions,
52    ) {
53        if let Some((sugg, rest)) = &suggestions.split_first()
54            // if there are multiple suggestions, print them all in full
55            // to be consistent.
56            && rest.is_empty()
57            // don't display multi-suggestions as labels
58            && let [substitution] = sugg.substitutions.as_slice()
59            // don't display multipart suggestions as labels
60            && let [part] = substitution.parts.as_slice()
61            // don't display long messages as labels
62            && sugg.msg.as_str().split_whitespace().count() < 10
63            // don't display multiline suggestions as labels
64            && !part.snippet.contains('\n')
65            && ![
66                // when this style is set we want the suggestion to be a message, not inline
67                SuggestionStyle::HideCodeAlways,
68                // trivial suggestion for tooling's sake, never shown
69                SuggestionStyle::CompletelyHidden,
70                // subtle suggestion, never shown inline
71                SuggestionStyle::ShowAlways,
72            ].contains(&sugg.style)
73        {
74            let snippet = part.snippet.trim();
75            let msg = if snippet.is_empty() || sugg.style.hide_inline() {
76                // This substitution is only removal OR we explicitly don't want to show the
77                // code inline (`hide_inline`). Therefore, we don't show the substitution.
78                format!("help: {}", sugg.msg.as_str())
79            } else {
80                format!("help: {}: `{}`", sugg.msg.as_str(), snippet)
81            };
82            primary_span.to_mut().push_span_label(part.span, msg);
83
84            // Since we only return the modified primary_span, we disable suggestions.
85            *suggestions = Suggestions::Disabled;
86        } else {
87            // Do nothing.
88        }
89    }
90}
91
92impl DynEmitter {
93    pub(crate) fn local_buffer(&self) -> Option<&str> {
94        (self as &dyn Any).downcast_ref::<HumanBufferEmitter>().map(HumanBufferEmitter::buffer)
95    }
96}
97
98/// Diagnostic emitter.
99///
100/// Emits fatal diagnostics by default, with `note` if set.
101pub struct SilentEmitter {
102    fatal_emitter: Option<Box<DynEmitter>>,
103    note: Option<String>,
104}
105
106impl SilentEmitter {
107    /// Creates a new `SilentEmitter`. Emits fatal diagnostics with `fatal_emitter`.
108    pub fn new(fatal_emitter: impl Emitter + Send) -> Self {
109        Self::new_boxed(Some(Box::new(fatal_emitter)))
110    }
111
112    /// Creates a new `SilentEmitter`. Emits fatal diagnostics with `fatal_emitter` if `Some`.
113    pub fn new_boxed(fatal_emitter: Option<Box<DynEmitter>>) -> Self {
114        Self { fatal_emitter, note: None }
115    }
116
117    /// Creates a new `SilentEmitter` that does not emit any diagnostics at all.
118    ///
119    /// Same as `new_boxed(None)`.
120    pub fn new_silent() -> Self {
121        Self::new_boxed(None)
122    }
123
124    /// Sets the note to be emitted for fatal diagnostics.
125    pub fn with_note(mut self, note: Option<String>) -> Self {
126        self.note = note;
127        self
128    }
129}
130
131impl Emitter for SilentEmitter {
132    fn emit_diagnostic(&mut self, diagnostic: &mut Diag) {
133        let Some(fatal_emitter) = self.fatal_emitter.as_deref_mut() else { return };
134        if diagnostic.level != Level::Fatal {
135            return;
136        }
137
138        if let Some(note) = &self.note {
139            let mut diagnostic = diagnostic.clone();
140            diagnostic.note(note.clone());
141            fatal_emitter.emit_diagnostic(&mut diagnostic);
142        } else {
143            fatal_emitter.emit_diagnostic(diagnostic);
144        }
145    }
146}
147
148/// Diagnostic emitter that only stores emitted diagnostics.
149#[derive(Clone, Debug)]
150pub struct LocalEmitter {
151    diagnostics: Vec<Diag>,
152}
153
154impl Default for LocalEmitter {
155    fn default() -> Self {
156        Self::new()
157    }
158}
159
160impl LocalEmitter {
161    /// Creates a new `LocalEmitter`.
162    pub fn new() -> Self {
163        Self { diagnostics: Vec::new() }
164    }
165
166    /// Returns a reference to the emitted diagnostics.
167    pub fn diagnostics(&self) -> &[Diag] {
168        &self.diagnostics
169    }
170
171    /// Consumes the emitter and returns the emitted diagnostics.
172    pub fn into_diagnostics(self) -> Vec<Diag> {
173        self.diagnostics
174    }
175}
176
177impl Emitter for LocalEmitter {
178    fn emit_diagnostic(&mut self, diagnostic: &mut Diag) {
179        self.diagnostics.push(diagnostic.clone());
180    }
181}
182
183#[cold]
184#[inline(never)]
185fn io_panic(error: std::io::Error) -> ! {
186    panic!("failed to emit diagnostic: {error}");
187}