1use crate::{
3 parse::{FileId, SourceList},
4 ParseTree,
5};
6use std::{convert::TryInto, ops::Range, sync::Arc};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub struct Span {
11 start: u32,
12 end: u32,
13}
14
15#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
17#[repr(u8)]
18pub enum Level {
19 Error,
21 Warning,
23 Info,
25}
26
27#[derive(Clone, Debug, PartialEq, Eq)]
29pub struct Message {
30 pub text: String,
31 pub file: FileId,
32 pub span: Span,
33}
34
35#[derive(Clone, Debug, PartialEq, Eq)]
39pub struct Diagnostic {
40 pub message: Message,
42 pub level: Level,
44}
45
46#[derive(Clone)]
48pub struct DiagnosticSet {
49 pub(crate) messages: Vec<Diagnostic>,
50 pub(crate) sources: Arc<SourceList>,
51 pub(crate) max_to_print: usize,
52}
53
54impl Span {
55 pub fn range(&self) -> Range<usize> {
57 self.start as usize..self.end as usize
58 }
59}
60
61impl Diagnostic {
62 pub fn new(
64 level: Level,
65 file: FileId,
66 range: Range<usize>,
67 message: impl Into<String>,
68 ) -> Self {
69 Diagnostic {
70 message: Message {
71 text: message.into(),
72 span: Span {
73 start: range.start.try_into().unwrap(),
74 end: range.end.try_into().unwrap(),
75 },
76 file,
77 },
78 level,
79 }
80 }
81
82 pub fn error(file: FileId, span: Range<usize>, message: impl Into<String>) -> Self {
84 Diagnostic::new(Level::Error, file, span, message)
85 }
86
87 pub fn warning(file: FileId, span: Range<usize>, message: impl Into<String>) -> Self {
89 Diagnostic::new(Level::Warning, file, span, message)
90 }
91
92 pub fn text(&self) -> &str {
94 &self.message.text
95 }
96
97 pub fn span(&self) -> Range<usize> {
99 self.message.span.range()
100 }
101
102 pub fn is_error(&self) -> bool {
104 matches!(self.level, Level::Error)
105 }
106}
107
108struct DiagnosticDisplayer<'a>(&'a DiagnosticSet);
112
113impl DiagnosticSet {
114 pub fn new(messages: Vec<Diagnostic>, tree: &ParseTree, max_to_print: usize) -> Self {
116 Self {
117 messages,
118 sources: tree.sources.clone(),
119 max_to_print,
120 }
121 }
122 pub fn len(&self) -> usize {
124 self.messages.len()
125 }
126
127 pub fn is_empty(&self) -> bool {
129 self.messages.is_empty()
130 }
131
132 pub fn has_errors(&self) -> bool {
134 self.messages.iter().any(|msg| msg.is_error())
135 }
136
137 pub fn set_max_to_print(&mut self, max_to_print: usize) {
139 self.max_to_print = max_to_print;
140 }
141
142 pub fn discard_warnings(&mut self) {
144 self.messages.retain(|x| x.is_error());
145 }
146
147 pub fn split_off_warnings(&mut self) -> Option<DiagnosticSet> {
149 self.messages.sort_unstable_by_key(|d| d.level);
150 let split_at = self.messages.iter().position(|x| !x.is_error())?;
151 let warnings = self.messages.split_off(split_at);
152 Some(Self {
153 messages: warnings,
154 sources: self.sources.clone(),
155 max_to_print: self.max_to_print,
156 })
157 }
158
159 pub fn diagnostics(&self) -> &[Diagnostic] {
161 &self.messages
162 }
163
164 pub fn display(&self) -> impl std::fmt::Display + '_ {
166 DiagnosticDisplayer(self)
167 }
168
169 pub(crate) fn write(&self, f: &mut impl std::fmt::Write, colorize: bool) -> std::fmt::Result {
170 let mut first = true;
171 for err in self.messages.iter().take(self.max_to_print) {
172 if !first {
173 writeln!(f)?;
174 }
175 write!(f, "{}", self.sources.format_diagnostic(err, colorize))?;
176 first = false;
177 }
178 if let Some(overflow) = self.messages.len().checked_sub(self.max_to_print) {
179 writeln!(f, "... and {overflow} more errors")?;
180 }
181 Ok(())
182 }
183
184 #[cfg(any(test, feature = "test"))]
185 pub(crate) fn to_string(&self, colorize: bool) -> String {
186 let mut out = String::new();
187 self.write(&mut out, colorize).unwrap();
188 out
189 }
190}
191
192impl std::fmt::Display for DiagnosticDisplayer<'_> {
193 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
194 use std::io::IsTerminal;
195 let colorize = std::io::stderr().is_terminal();
196 self.0.write(f, colorize)
197 }
198}
199
200impl std::fmt::Debug for DiagnosticSet {
201 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
202 f.debug_struct("DiagnosticSet")
203 .field("messages", &self.messages)
204 .field("tree", &"ParseTree")
205 .finish()
206 }
207}