1#![doc(html_root_url = "https://docs.rs/rucc-diag/0.2.21")]
20
21mod errors;
22mod source;
23
24pub use crate::errors::{DEFAULT_ERROR_LIMIT, Errors};
25pub use crate::source::{
26 FileId, Loc, PresumedLoc, SourceBytes, SourceFile, SourceMap, SourceMapFull,
27};
28
29use std::fmt;
30
31pub type BytePos = u32;
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
41pub struct Span {
42 pub lo: BytePos,
44 pub hi: BytePos,
46}
47
48impl Span {
49 #[inline]
55 pub const fn new(lo: BytePos, hi: BytePos) -> Self {
56 assert!(lo <= hi, "reversed span");
57 Self { lo, hi }
58 }
59
60 #[inline]
63 pub const fn empty_at(at: BytePos) -> Self {
64 Self { lo: at, hi: at }
65 }
66
67 pub const DUMMY: Self = Self { lo: BytePos::MAX, hi: BytePos::MAX };
73
74 #[inline]
76 pub const fn is_dummy(self) -> bool {
77 self.lo == BytePos::MAX
78 }
79
80 #[inline]
82 pub const fn len(self) -> u32 {
83 self.hi - self.lo
84 }
85
86 #[inline]
88 pub const fn is_empty(self) -> bool {
89 self.lo == self.hi
90 }
91
92 #[inline]
98 pub fn to(self, other: Self) -> Self {
99 if self.is_dummy() {
100 return other;
101 }
102 if other.is_dummy() {
103 return self;
104 }
105 Self { lo: self.lo.min(other.lo), hi: self.hi.max(other.hi) }
106 }
107
108 #[inline]
110 pub const fn contains(self, pos: BytePos) -> bool {
111 !self.is_dummy() && self.lo <= pos && pos < self.hi
112 }
113}
114
115#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
120pub enum Severity {
121 Note,
123 Help,
125 Warning,
128 Error,
131 Ice,
135}
136
137impl Severity {
138 #[inline]
140 pub const fn is_fatal(self) -> bool {
141 matches!(self, Severity::Error | Severity::Ice)
142 }
143
144 pub const fn as_str(self) -> &'static str {
146 match self {
147 Severity::Note => "note",
148 Severity::Help => "help",
149 Severity::Warning => "warning",
150 Severity::Error => "error",
151 Severity::Ice => "internal compiler error",
152 }
153 }
154}
155
156impl fmt::Display for Severity {
157 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158 f.write_str(self.as_str())
159 }
160}
161
162#[derive(Debug, Clone, PartialEq, Eq)]
164pub struct Diagnostic {
165 pub severity: Severity,
167 pub code: Option<&'static str>,
170 pub message: String,
173 pub span: Span,
175 pub children: Vec<Diagnostic>,
177}
178
179impl Diagnostic {
180 pub fn new(severity: Severity, message: impl Into<String>, span: Span) -> Self {
182 Self { severity, code: None, message: message.into(), span, children: Vec::new() }
183 }
184
185 pub fn error(message: impl Into<String>, span: Span) -> Self {
187 Self::new(Severity::Error, message, span)
188 }
189
190 pub fn warning(message: impl Into<String>, span: Span) -> Self {
192 Self::new(Severity::Warning, message, span)
193 }
194
195 #[must_use]
197 pub fn with_code(mut self, code: &'static str) -> Self {
198 self.code = Some(code);
199 self
200 }
201
202 #[must_use]
204 pub fn note(mut self, message: impl Into<String>, span: Span) -> Self {
205 self.children.push(Self::new(Severity::Note, message, span));
206 self
207 }
208
209 #[must_use]
211 pub fn help(mut self, message: impl Into<String>, span: Span) -> Self {
212 self.children.push(Self::new(Severity::Help, message, span));
213 self
214 }
215}
216
217#[cfg(test)]
218mod tests {
219 use super::*;
220
221 #[test]
222 fn joining_spans_covers_both() {
223 let a = Span::new(4, 9);
224 let b = Span::new(20, 22);
225 assert_eq!(a.to(b), Span::new(4, 22));
226 assert_eq!(b.to(a), Span::new(4, 22));
227 }
228
229 #[test]
230 fn joining_with_a_dummy_keeps_the_real_one() {
231 let a = Span::new(4, 9);
232 assert_eq!(a.to(Span::DUMMY), a);
233 assert_eq!(Span::DUMMY.to(a), a);
234 }
235
236 #[test]
237 fn a_dummy_span_contains_nothing() {
238 assert!(!Span::DUMMY.contains(0));
239 assert!(!Span::DUMMY.contains(BytePos::MAX));
240 }
241
242 #[test]
243 fn an_empty_span_is_not_a_dummy_span() {
244 let e = Span::empty_at(0);
245 assert!(e.is_empty());
246 assert!(!e.is_dummy());
247 }
248
249 #[test]
250 fn severity_orders_by_how_bad_it_is() {
251 assert!(Severity::Error > Severity::Warning);
252 assert!(Severity::Ice > Severity::Error);
253 assert!(Severity::Warning > Severity::Note);
254 }
255
256 #[test]
257 fn only_errors_and_ices_suppress_output() {
258 assert!(Severity::Error.is_fatal());
259 assert!(Severity::Ice.is_fatal());
260 assert!(!Severity::Warning.is_fatal());
261 }
262
263 #[test]
264 fn a_diagnostic_carries_its_children() {
265 let d = Diagnostic::error("expected an expression", Span::new(1, 2))
266 .with_code("E0001")
267 .note("in this macro expansion", Span::new(0, 8))
268 .help("did you mean a compound literal", Span::DUMMY);
269 assert_eq!(d.code, Some("E0001"));
270 assert_eq!(d.children.len(), 2);
271 assert_eq!(d.children[0].severity, Severity::Note);
272 assert_eq!(d.children[1].severity, Severity::Help);
273 }
274
275 #[test]
276 #[should_panic(expected = "reversed span")]
277 fn a_reversed_span_is_rejected() {
278 let _ = Span::new(9, 4);
279 }
280}