1//! Reporting for errors, warnings, and everything else.
2//!
3//! The code in this module is heavily inspired by [codespan-reporting] and [ariadne].
4//!
5//! [codespan-reporting]: https://crates.io/crates/codespan-reporting
6//! [ariadne]: https://crates.io/crates/ariadne
78use crate::source_tracking::SourceMap;
9use crate::source_tracking::filename::FileName;
10use crate::source_tracking::immutable_string::ImmutableString;
11use crate::source_tracking::{fragment::Fragment, source::SourceId};
12use codespan_reporting::diagnostic::Diagnostic as CRDiagnostic;
13use codespan_reporting::diagnostic::Label;
14use codespan_reporting::files::{Error as CRError, Files};
15use codespan_reporting::term::Config;
16use std::sync::Mutex;
17use termcolor::{ColorChoice, StandardStream, WriteColor};
1819#[cfg(doc)]
20use crate::source_tracking::source::Source;
2122// Publicly re-export codespan's severity type, since it's pretty much exactly what we want/use.
23pub use codespan_reporting::diagnostic::Severity;
2425/// The style/priority applied to a [Highlight] being rendered.
26pub use codespan_reporting::diagnostic::LabelStyle;
2728/// The global-static color choice for printing to the standard output.
29static STDOUT_COLOR_CHOICE: Mutex<ColorChoice> = Mutex::new(ColorChoice::Auto);
3031/// Set the [ColorChoice] to use when printing [Diagnostic]s to the standard output.
32pub fn set_stdout_color(c: ColorChoice) {
33*STDOUT_COLOR_CHOICE.lock().unwrap() = c;
34}
3536/// Get the current [ColorChoice] used when printing [Diagnostic]s to the standard output.
37pub fn get_stdout_color() -> ColorChoice {
38*STDOUT_COLOR_CHOICE.lock().unwrap()
39}
4041/// Wright's [Diagnostic] type wraps one from [codespan_reporting] to make it compatible with
42/// things like [Fragment] and [SourceId].
43#[derive(Debug)]
44pub struct Diagnostic(pub CRDiagnostic<SourceId>);
4546impl Diagnostic {
47/// Construct a new [Diagnostic] with the given [Severity].
48pub fn new(severity: Severity) -> Self {
49 Diagnostic(CRDiagnostic::new(severity))
50 }
5152/// Construct a new [Diagnostic] representing a wright compiler bug.
53#[inline]
54pub fn bug() -> Self {
55Self::new(Severity::Bug)
56 }
5758/// Construct a new [Diagnostic] representing an error.
59#[inline]
60pub fn error() -> Self {
61Self::new(Severity::Error)
62 }
6364/// Construct a new [Diagnostic] representing a warning.
65#[inline]
66pub fn warning() -> Self {
67Self::new(Severity::Warning)
68 }
6970/// Construct a new [Diagnostic] representing a note.
71#[inline]
72pub fn note() -> Self {
73Self::new(Severity::Note)
74 }
7576/// Construct a new [Diagnostic] representing a note to the user.
77#[inline]
78pub fn help() -> Self {
79Self::new(Severity::Note)
80 }
8182/// Builder style function to set an error/warning code for this [Diagnostic].
83pub fn with_code(self, code: impl Into<String>) -> Self {
84 Diagnostic(self.0.with_code(code))
85 }
8687/// Builder style function to set a message for this [Diagnostic].
88pub fn with_message(self, message: impl Into<String>) -> Self {
89 Diagnostic(self.0.with_message(message))
90 }
9192/// Add all the notes from the given [Iterator] to this [Diagnostic].
93pub fn with_notes<I: Into<String>>(mut self, notes: impl IntoIterator<Item = I>) -> Self {
94self.0.notes.extend(notes.into_iter().map(Into::into));
95self
96}
9798/// Add all the [Highlight]s from a given [Iterator] to this [Diagnostic].
99pub fn with_highlights(mut self, highlights: impl IntoIterator<Item = Highlight>) -> Self {
100self.0.labels.extend(highlights.into_iter().map(Into::into));
101self
102}
103104/// Write this [Diagnostic] to a given [WriteColor]. This will error if any of the [Highlight]s are not in
105 /// the referenced [SourceMap], or if any were constructed from invalid [Fragment]s.
106pub fn write(
107&self,
108 map: &SourceMap,
109 writer: &mut dyn WriteColor,
110 config: &Config,
111 ) -> Result<(), CRError> {
112 codespan_reporting::term::emit(writer, config, map, &self.0)
113 }
114115/// Print this [Diagnostic] to the standard output. This locks the standard output until the diagnostic is printed.
116 /// This uses the global [get_stdout_color] function to determine whether or not to use colors while printing.
117 /// This uses the [Config::default] configuration from [codespan_reporting] when printing.
118pub fn print(&self, map: &SourceMap) -> Result<(), codespan_reporting::files::Error> {
119let stream = StandardStream::stdout(get_stdout_color());
120let mut lock = stream.lock();
121self.write(map, &mut lock, &Config::default())
122 }
123}
124125/// A highlighted section of a [Source] that's used in a [Diagnostic].
126#[derive(Clone, Debug)]
127pub struct Highlight {
128/// The style/importance of this [Highlight]. [Highlight]s with [LabelStyle::Primary] are given priority
129 /// when being displayed.
130pub style: LabelStyle,
131/// The [Fragment] containing the relevant section of code.
132pub fragment: Fragment,
133/// The message attached to this [Highlight]. This can be empty, in which case the [Fragment] will
134 /// just be underlined when displayed.
135pub message: String,
136}
137138impl Highlight {
139/// Construct a new [Highlight] with [LabelStyle::Primary].
140pub fn primary(fragment: Fragment, message: impl Into<String>) -> Self {
141 Highlight {
142 style: LabelStyle::Primary,
143 fragment,
144 message: message.into(),
145 }
146 }
147148/// Construct a new [Highlight] with [LabelStyle::Secondary].
149pub fn secondary(fragment: Fragment, message: impl Into<String>) -> Self {
150 Highlight {
151 style: LabelStyle::Secondary,
152 fragment,
153 message: message.into(),
154 }
155 }
156157/// Builder style function to set the [Highlight::message].
158pub fn with_message(mut self, message: impl Into<String>) -> Self {
159self.message = message.into();
160self
161}
162}
163164impl From<Highlight> for Label<SourceId> {
165fn from(value: Highlight) -> Self {
166 Label {
167 style: value.style,
168 file_id: value.fragment.source.id,
169 range: value.fragment.range,
170 message: value.message,
171 }
172 }
173}
174175impl<'f> Files<'f> for SourceMap {
176type FileId = SourceId;
177178type Name = FileName;
179180type Source = ImmutableString;
181182fn name(&'f self, id: Self::FileId) -> Result<Self::Name, codespan_reporting::files::Error> {
183self.get(id)
184 .map(|source| source.name().clone())
185 .ok_or(CRError::FileMissing)
186 }
187188fn source(
189&'f self,
190 id: Self::FileId,
191 ) -> Result<Self::Source, codespan_reporting::files::Error> {
192self.get(id)
193 .map(|source| source.source().clone())
194 .ok_or(CRError::FileMissing)
195 }
196197fn line_index(
198&'f self,
199 id: Self::FileId,
200 byte_index: usize,
201 ) -> Result<usize, codespan_reporting::files::Error> {
202Ok(self
203.get(id)
204 .ok_or(CRError::FileMissing)?
205.line_index(byte_index))
206 }
207208fn line_range(
209&'f self,
210 id: Self::FileId,
211 line_index: usize,
212 ) -> Result<std::ops::Range<usize>, codespan_reporting::files::Error> {
213Ok(self
214.get(id)
215 .ok_or(CRError::FileMissing)?
216.get_line(line_index)
217 .range)
218 }
219}