Skip to main content

haloumi_ir/
diagnostics.rs

1//! Types and traits related to diagnostics.
2
3use std::{fmt, sync::Arc};
4
5/// In-progress validation
6#[derive(Debug, Default)]
7pub struct Validation<D> {
8    errors: Vec<D>,
9    warnings: Vec<D>,
10}
11
12impl<D> Validation<D> {
13    /// Creates a new validation.
14    pub fn new() -> Self {
15        Self {
16            errors: vec![],
17            warnings: vec![],
18        }
19    }
20
21    /// Appends a warning.
22    pub fn with_warning(&mut self, warning: D) {
23        self.warnings.push(warning)
24    }
25
26    /// Appends an error.
27    pub fn with_error(&mut self, error: D) {
28        self.errors.push(error)
29    }
30
31    /// Appends a list of warnings.
32    pub fn with_warnings(&mut self, warnings: impl IntoIterator<Item = D>) {
33        self.warnings.extend(warnings)
34    }
35
36    /// Appends a list of errors.
37    pub fn with_errors(&mut self, errors: impl IntoIterator<Item = D>) {
38        self.errors.extend(errors)
39    }
40
41    /// Appends a list of diagnostics.
42    pub fn append(&mut self, diags: impl IntoIterator<Item = D>)
43    where
44        D: Diagnostic,
45    {
46        let (errors, warnings): (Vec<_>, Vec<_>) = diags.into_iter().partition(D::is_error);
47        self.with_warnings(warnings);
48        self.with_errors(errors);
49    }
50
51    /// Appends a list of diagnostics from a result.
52    pub fn append_from_result<C>(&mut self, result: Result<Vec<D>, Vec<D>>, context: &C)
53    where
54        D: Diagnostic,
55        C: std::fmt::Display + ?Sized,
56    {
57        match result {
58            Ok(warnings) => self.append(warnings.into_iter().map(|w| w.contextualize(context))),
59            Err(errors) => self.append(errors.into_iter().map(|e| e.contextualize(context))),
60        }
61    }
62}
63
64impl<D> From<Validation<D>> for Result<Vec<D>, Vec<D>> {
65    fn from(mut value: Validation<D>) -> Self {
66        if value.errors.is_empty() {
67            Ok(value.warnings)
68        } else {
69            value.errors.extend(value.warnings);
70            Err(value.errors)
71        }
72    }
73}
74
75/// Diagnostic kinds
76#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
77pub enum DiagnosticKind {
78    /// Warnings
79    Warning,
80    /// Errors
81    Error,
82    /// Others
83    Other,
84}
85
86/// Marker trait for a diagnostic.
87pub trait Diagnostic: fmt::Display {
88    /// Gets the kind of diagnostic.
89    fn kind(&self) -> DiagnosticKind;
90
91    /// Returns true if it's an error diagnostic.
92    fn is_error(&self) -> bool {
93        self.kind() == DiagnosticKind::Error
94    }
95
96    /// Adds additional context to the diagnostic.
97    fn contextualize<C>(self, context: &C) -> Self
98    where
99        C: ToString + ?Sized,
100        Self: Sized;
101}
102
103/// Simple diagnostics implementation.
104#[derive(Debug, Clone)]
105pub struct SimpleDiagnostic {
106    kind: DiagnosticKind,
107    message: String,
108}
109
110impl SimpleDiagnostic {
111    /// Creates a warning diagnostic.
112    pub fn warn(message: impl ToString) -> Self {
113        Self {
114            kind: DiagnosticKind::Warning,
115            message: message.to_string(),
116        }
117    }
118
119    /// Creates an error diagnostic.
120    pub fn error(message: impl ToString) -> Self {
121        Self {
122            kind: DiagnosticKind::Error,
123            message: message.to_string(),
124        }
125    }
126
127    /// Creates a diagnostic of another  kind.
128    pub fn other(message: impl ToString) -> Self {
129        Self {
130            kind: DiagnosticKind::Other,
131            message: message.to_string(),
132        }
133    }
134}
135
136impl Diagnostic for SimpleDiagnostic {
137    fn kind(&self) -> DiagnosticKind {
138        self.kind
139    }
140
141    fn contextualize<C>(self, context: &C) -> Self
142    where
143        C: ToString + ?Sized,
144    {
145        let mut final_message = context.to_string();
146        if final_message.is_empty() {
147            return self;
148        }
149        final_message.reserve(2 + self.message.len());
150        final_message.push_str(": ");
151        final_message.push_str(&self.message);
152        Self {
153            kind: self.kind,
154            message: final_message,
155        }
156    }
157}
158
159impl fmt::Display for SimpleDiagnostic {
160    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
161        write!(
162            f,
163            "{}: {}",
164            match self.kind {
165                DiagnosticKind::Warning => "warning",
166                DiagnosticKind::Error => "error",
167                DiagnosticKind::Other => "other",
168            },
169            self.message
170        )
171    }
172}
173
174/// Error type for a collection of diagnostics
175pub struct DiagnosticsError {
176    diags: Vec<Arc<dyn Diagnostic + Send + Sync + 'static>>,
177}
178
179impl std::fmt::Debug for DiagnosticsError {
180    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181        f.debug_struct("DiagnosticsError")
182            .field("n_diags", &self.diags.len())
183            .finish()
184    }
185}
186
187impl std::fmt::Display for DiagnosticsError {
188    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
189        self.diags
190            .iter()
191            .map(AsRef::as_ref)
192            .try_for_each(|diag| writeln!(f, "- {diag}\n"))
193    }
194}
195
196impl<D: Diagnostic + Send + Sync + 'static> FromIterator<D> for DiagnosticsError {
197    fn from_iter<T: IntoIterator<Item = D>>(iter: T) -> Self {
198        Self {
199            diags: iter
200                .into_iter()
201                .map(|d| -> Arc<dyn Diagnostic + Send + Sync + 'static> { Arc::new(d) })
202                .collect(),
203        }
204    }
205}
206
207impl<I: IntoIterator> From<I> for DiagnosticsError
208where
209    I::Item: Diagnostic + Send + Sync + 'static,
210{
211    fn from(value: I) -> Self {
212        Self::from_iter(value)
213    }
214}