1use alloc::{boxed::Box, vec::Vec};
4use core::fmt;
5
6use diag_lang::{Diagnostic, Label, Severity};
7use syntax_lang::Span;
8
9#[derive(Clone, Debug, PartialEq, Eq)]
59pub struct Error {
60 diagnostics: Vec<Diagnostic>,
61 line: u32,
62 column: u32,
63}
64
65impl Error {
66 pub(crate) fn new(diagnostics: Vec<Diagnostic>, text: &str) -> Self {
68 let offset = diagnostics
69 .first()
70 .map_or(0, |d| d.primary().span().start().to_usize());
71 let (line, column) = line_col(text, offset);
72 Self {
73 diagnostics,
74 line,
75 column,
76 }
77 }
78
79 pub(crate) fn located(diagnostics: Vec<Diagnostic>, line: u32, column: u32) -> Self {
81 Self {
82 diagnostics,
83 line,
84 column,
85 }
86 }
87
88 #[inline]
102 #[must_use]
103 pub fn diagnostics(&self) -> &[Diagnostic] {
104 &self.diagnostics
105 }
106}
107
108impl fmt::Display for Error {
109 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110 let message = self.diagnostics.first().map_or("", |d| d.message());
111 write!(f, "{}:{}: {}", self.line, self.column, message)?;
112 match self.diagnostics.len() {
113 0 | 1 => Ok(()),
114 2 => f.write_str(" (and 1 more error)"),
115 n => write!(f, " (and {} more errors)", n - 1),
116 }
117 }
118}
119
120impl core::error::Error for Error {}
121
122pub(crate) fn line_col(text: &str, offset: usize) -> (u32, u32) {
124 let mut offset = offset.min(text.len());
125 while !text.is_char_boundary(offset) {
126 offset -= 1;
127 }
128 let before = &text[..offset];
129 let line_start = before.rfind('\n').map_or(0, |i| i + 1);
130 let line = before.bytes().filter(|b| *b == b'\n').count() + 1;
131 let column = before[line_start..].chars().count() + 1;
132 (saturate(line), saturate(column))
133}
134
135fn saturate(n: usize) -> u32 {
136 u32::try_from(n).unwrap_or(u32::MAX)
137}
138
139#[derive(Debug, Default)]
141pub(crate) struct Report {
142 diagnostics: Vec<Diagnostic>,
143}
144
145impl Report {
146 pub(crate) fn error(&mut self, span: Span, message: impl Into<Box<str>>) {
148 self.diagnostics.push(Diagnostic::new(
149 Severity::Error,
150 message,
151 Label::unlabelled(span),
152 ));
153 }
154
155 pub(crate) fn error_help(
157 &mut self,
158 span: Span,
159 message: impl Into<Box<str>>,
160 help: impl Into<Box<str>>,
161 ) {
162 self.diagnostics.push(
163 Diagnostic::new(Severity::Error, message, Label::unlabelled(span)).with_help(help),
164 );
165 }
166
167 pub(crate) fn diagnostic(&mut self, diagnostic: Diagnostic) {
169 self.diagnostics.push(diagnostic);
170 }
171
172 pub(crate) fn is_clean(&self) -> bool {
174 self.diagnostics.is_empty()
175 }
176
177 pub(crate) fn into_error(mut self, text: &str) -> Error {
179 self.diagnostics
180 .sort_by_key(|d| d.primary().span().start().to_u32());
181 Error::new(self.diagnostics, text)
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use alloc::string::ToString;
188
189 use super::*;
190
191 #[test]
192 fn test_line_col_counts_characters_not_bytes() {
193 let text = "ab\nçd\n";
194 assert_eq!(line_col(text, 0), (1, 1));
195 assert_eq!(line_col(text, 3), (2, 1));
196 assert_eq!(line_col(text, 5), (2, 2));
198 assert_eq!(line_col(text, 999), (3, 1));
199 }
200
201 #[test]
202 fn test_line_col_inside_multibyte_char_does_not_panic() {
203 assert_eq!(line_col("ç", 1), (1, 1));
204 }
205
206 #[test]
207 fn test_display_counts_additional_errors() {
208 let mut report = Report::default();
209 report.error(Span::new(4, 5), "second");
210 report.error(Span::new(0, 1), "first");
211 let err = report.into_error("abcdef");
212 assert_eq!(err.to_string(), "1:1: first (and 1 more error)");
213
214 let mut report = Report::default();
215 for _ in 0..3 {
216 report.error(Span::new(0, 0), "same");
217 }
218 assert_eq!(
219 report.into_error("").to_string(),
220 "1:1: same (and 2 more errors)"
221 );
222 }
223
224 #[test]
225 fn test_report_help_is_attached() {
226 let mut report = Report::default();
227 report.error_help(Span::new(0, 1), "bad", "fix it");
228 assert!(!report.is_clean());
229 let err = report.into_error("x");
230 assert_eq!(err.diagnostics()[0].help().collect::<Vec<_>>(), ["fix it"]);
231 }
232}