tensorlogic_compiler/error_recovery/
collector.rs1use std::sync::{Arc, Mutex};
29
30use super::diagnostic::{Diagnostic, Severity};
31
32#[derive(Debug, Clone, Default)]
38pub struct DiagnosticCollector {
39 inner: Arc<Mutex<Vec<Diagnostic>>>,
40}
41
42impl DiagnosticCollector {
43 pub fn new() -> Self {
45 Self::default()
46 }
47
48 pub fn with_diagnostics(initial: Vec<Diagnostic>) -> Self {
50 Self {
51 inner: Arc::new(Mutex::new(initial)),
52 }
53 }
54
55 pub fn push(&self, diag: Diagnostic) {
59 let mut guard = match self.inner.lock() {
60 Ok(g) => g,
61 Err(poisoned) => poisoned.into_inner(),
62 };
63 guard.push(diag);
64 }
65
66 pub fn extend<I: IntoIterator<Item = Diagnostic>>(&self, diags: I) {
68 let mut guard = match self.inner.lock() {
69 Ok(g) => g,
70 Err(poisoned) => poisoned.into_inner(),
71 };
72 guard.extend(diags);
73 }
74
75 pub fn len(&self) -> usize {
77 self.snapshot_raw().len()
78 }
79
80 pub fn is_empty(&self) -> bool {
82 self.len() == 0
83 }
84
85 pub fn snapshot(&self) -> Vec<Diagnostic> {
87 self.snapshot_raw()
88 }
89
90 fn snapshot_raw(&self) -> Vec<Diagnostic> {
91 match self.inner.lock() {
92 Ok(g) => g.clone(),
93 Err(poisoned) => poisoned.into_inner().clone(),
94 }
95 }
96
97 pub fn of_severity(&self, severity: Severity) -> Vec<Diagnostic> {
99 self.snapshot_raw()
100 .into_iter()
101 .filter(|d| d.severity == severity)
102 .collect()
103 }
104
105 pub fn at_least(&self, min: Severity) -> Vec<Diagnostic> {
108 self.snapshot_raw()
109 .into_iter()
110 .filter(|d| d.severity >= min)
111 .collect()
112 }
113
114 pub fn count_of(&self, severity: Severity) -> usize {
116 self.snapshot_raw()
117 .iter()
118 .filter(|d| d.severity == severity)
119 .count()
120 }
121
122 pub fn errors(&self) -> Vec<Diagnostic> {
124 self.of_severity(Severity::Error)
125 }
126
127 pub fn warnings(&self) -> Vec<Diagnostic> {
129 self.of_severity(Severity::Warning)
130 }
131
132 pub fn fatals(&self) -> Vec<Diagnostic> {
134 self.of_severity(Severity::Fatal)
135 }
136
137 pub fn infos(&self) -> Vec<Diagnostic> {
139 self.of_severity(Severity::Info)
140 }
141
142 pub fn has_blocking(&self) -> bool {
145 self.snapshot_raw().iter().any(Diagnostic::is_blocking)
146 }
147
148 pub fn has_fatal(&self) -> bool {
150 self.snapshot_raw().iter().any(Diagnostic::is_fatal)
151 }
152
153 pub fn for_expression(&self, idx: usize) -> Vec<Diagnostic> {
156 self.snapshot_raw()
157 .into_iter()
158 .filter(|d| d.expression_index == Some(idx))
159 .collect()
160 }
161
162 pub fn drain(&self) -> Vec<Diagnostic> {
165 let mut guard = match self.inner.lock() {
166 Ok(g) => g,
167 Err(poisoned) => poisoned.into_inner(),
168 };
169 std::mem::take(&mut *guard)
170 }
171
172 pub fn clear(&self) {
174 let _ = self.drain();
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use super::super::diagnostic::{Diagnostic, Severity};
181 use super::*;
182
183 #[test]
184 fn push_preserves_insertion_order() {
185 let c = DiagnosticCollector::new();
186 c.push(Diagnostic::info("a"));
187 c.push(Diagnostic::warning("b"));
188 c.push(Diagnostic::error("c"));
189 let snap = c.snapshot();
190 assert_eq!(snap.len(), 3);
191 assert_eq!(snap[0].message, "a");
192 assert_eq!(snap[1].message, "b");
193 assert_eq!(snap[2].message, "c");
194 }
195
196 #[test]
197 fn severity_filters() {
198 let c = DiagnosticCollector::new();
199 c.push(Diagnostic::info("i"));
200 c.push(Diagnostic::warning("w1"));
201 c.push(Diagnostic::warning("w2"));
202 c.push(Diagnostic::error("e"));
203 c.push(Diagnostic::fatal("f"));
204
205 assert_eq!(c.len(), 5);
206 assert_eq!(c.count_of(Severity::Warning), 2);
207 assert_eq!(c.errors().len(), 1);
208 assert_eq!(c.warnings().len(), 2);
209 assert_eq!(c.fatals().len(), 1);
210 assert_eq!(c.infos().len(), 1);
211 assert!(c.has_blocking());
212 assert!(c.has_fatal());
213 assert_eq!(c.at_least(Severity::Error).len(), 2);
214 }
215
216 #[test]
217 fn for_expression_filter() {
218 let c = DiagnosticCollector::new();
219 c.push(Diagnostic::error("e0").with_expression_index(0));
220 c.push(Diagnostic::error("e1").with_expression_index(1));
221 c.push(Diagnostic::warning("w1").with_expression_index(1));
222 c.push(Diagnostic::info("no-idx"));
223
224 assert_eq!(c.for_expression(0).len(), 1);
225 assert_eq!(c.for_expression(1).len(), 2);
226 assert_eq!(c.for_expression(99).len(), 0);
227 }
228
229 #[test]
230 fn clone_shares_underlying_buffer() {
231 let c1 = DiagnosticCollector::new();
232 let c2 = c1.clone();
233 c1.push(Diagnostic::error("shared"));
234 assert_eq!(c2.len(), 1);
235 }
236
237 #[test]
238 fn drain_empties_collector() {
239 let c = DiagnosticCollector::with_diagnostics(vec![
240 Diagnostic::error("a"),
241 Diagnostic::warning("b"),
242 ]);
243 assert_eq!(c.len(), 2);
244 let drained = c.drain();
245 assert_eq!(drained.len(), 2);
246 assert!(c.is_empty());
247 }
248
249 #[test]
250 fn extend_appends_multiple() {
251 let c = DiagnosticCollector::new();
252 c.extend(vec![
253 Diagnostic::info("x"),
254 Diagnostic::warning("y"),
255 Diagnostic::error("z"),
256 ]);
257 assert_eq!(c.len(), 3);
258 assert_eq!(c.snapshot()[2].message, "z");
259 }
260}