tensorlogic_compiler/error_recovery/
diagnostic.rs1use std::fmt;
31
32#[derive(Debug, Clone, PartialEq, Eq, Hash)]
40pub struct SourceSpan {
41 pub start: usize,
43 pub end: usize,
45 pub source: Option<String>,
47}
48
49impl SourceSpan {
50 pub fn new(start: usize, end: usize) -> Self {
52 Self {
53 start,
54 end,
55 source: None,
56 }
57 }
58
59 pub fn with_source(start: usize, end: usize, source: impl Into<String>) -> Self {
61 Self {
62 start,
63 end,
64 source: Some(source.into()),
65 }
66 }
67
68 pub fn len(&self) -> usize {
70 self.end.saturating_sub(self.start)
71 }
72
73 pub fn is_empty(&self) -> bool {
75 self.end <= self.start
76 }
77}
78
79impl fmt::Display for SourceSpan {
80 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81 match &self.source {
82 Some(src) => write!(f, "{}:{}..{}", src, self.start, self.end),
83 None => write!(f, "{}..{}", self.start, self.end),
84 }
85 }
86}
87
88#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
90pub enum Severity {
91 Info,
93 Warning,
95 Error,
97 Fatal,
99}
100
101impl Severity {
102 pub fn is_blocking(self) -> bool {
105 matches!(self, Severity::Error | Severity::Fatal)
106 }
107
108 pub fn label(self) -> &'static str {
110 match self {
111 Severity::Info => "info",
112 Severity::Warning => "warning",
113 Severity::Error => "error",
114 Severity::Fatal => "fatal",
115 }
116 }
117}
118
119impl fmt::Display for Severity {
120 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121 f.write_str(self.label())
122 }
123}
124
125#[derive(Debug, Clone, PartialEq, Eq)]
127pub struct Diagnostic {
128 pub severity: Severity,
130 pub message: String,
132 pub location: Option<SourceSpan>,
134 pub expression_index: Option<usize>,
137}
138
139impl Diagnostic {
140 pub fn new(severity: Severity, message: impl Into<String>) -> Self {
142 Self {
143 severity,
144 message: message.into(),
145 location: None,
146 expression_index: None,
147 }
148 }
149
150 pub fn info(message: impl Into<String>) -> Self {
152 Self::new(Severity::Info, message)
153 }
154
155 pub fn warning(message: impl Into<String>) -> Self {
157 Self::new(Severity::Warning, message)
158 }
159
160 pub fn error(message: impl Into<String>) -> Self {
162 Self::new(Severity::Error, message)
163 }
164
165 pub fn fatal(message: impl Into<String>) -> Self {
167 Self::new(Severity::Fatal, message)
168 }
169
170 pub fn with_expression_index(mut self, idx: usize) -> Self {
172 self.expression_index = Some(idx);
173 self
174 }
175
176 pub fn with_location(mut self, span: SourceSpan) -> Self {
178 self.location = Some(span);
179 self
180 }
181
182 pub fn is_blocking(&self) -> bool {
185 self.severity.is_blocking()
186 }
187
188 pub fn is_fatal(&self) -> bool {
191 self.severity == Severity::Fatal
192 }
193}
194
195impl fmt::Display for Diagnostic {
196 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
197 write!(f, "[{}]", self.severity)?;
198 if let Some(idx) = self.expression_index {
199 write!(f, "[expr#{}]", idx)?;
200 }
201 if let Some(span) = &self.location {
202 write!(f, " ({})", span)?;
203 }
204 write!(f, " {}", self.message)
205 }
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211
212 #[test]
213 fn severity_ordering() {
214 assert!(Severity::Info < Severity::Warning);
215 assert!(Severity::Warning < Severity::Error);
216 assert!(Severity::Error < Severity::Fatal);
217 }
218
219 #[test]
220 fn severity_is_blocking() {
221 assert!(!Severity::Info.is_blocking());
222 assert!(!Severity::Warning.is_blocking());
223 assert!(Severity::Error.is_blocking());
224 assert!(Severity::Fatal.is_blocking());
225 }
226
227 #[test]
228 fn diagnostic_builders() {
229 let d = Diagnostic::error("boom").with_expression_index(7);
230 assert_eq!(d.severity, Severity::Error);
231 assert_eq!(d.message, "boom");
232 assert_eq!(d.expression_index, Some(7));
233 assert!(d.is_blocking());
234 assert!(!d.is_fatal());
235 }
236
237 #[test]
238 fn diagnostic_display_includes_index_and_span() {
239 let d = Diagnostic::warning("possible issue")
240 .with_expression_index(3)
241 .with_location(SourceSpan::with_source(10, 20, "main.tl"));
242 let s = format!("{}", d);
243 assert!(s.contains("warning"));
244 assert!(s.contains("expr#3"));
245 assert!(s.contains("main.tl:10..20"));
246 assert!(s.contains("possible issue"));
247 }
248
249 #[test]
250 fn source_span_basics() {
251 let sp = SourceSpan::new(3, 7);
252 assert_eq!(sp.len(), 4);
253 assert!(!sp.is_empty());
254 let empty = SourceSpan::new(5, 5);
255 assert!(empty.is_empty());
256 }
257}