Skip to main content

runmat_hir/
diagnostic.rs

1use crate::Span;
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
5pub enum HirDiagnosticSeverity {
6    Error,
7    Warning,
8    Information,
9    Help,
10}
11
12#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
13pub struct HirDiagnosticSpan {
14    pub span: Span,
15    pub label: Option<String>,
16}
17
18#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
19pub struct HirDiagnosticNote {
20    pub message: String,
21}
22
23#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
24pub struct HirDiagnosticSuggestion {
25    pub span: Span,
26    pub replacement: String,
27    pub message: String,
28}
29
30#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
31pub struct HirDiagnostic {
32    pub code: String,
33    pub severity: HirDiagnosticSeverity,
34    pub message: String,
35    pub primary: HirDiagnosticSpan,
36    pub secondary: Vec<HirDiagnosticSpan>,
37    pub notes: Vec<HirDiagnosticNote>,
38    pub help: Option<String>,
39    pub suggestions: Vec<HirDiagnosticSuggestion>,
40    pub category: Option<String>,
41}
42
43impl HirDiagnostic {
44    pub fn new(
45        code: &'static str,
46        severity: HirDiagnosticSeverity,
47        message: impl Into<String>,
48        span: Span,
49    ) -> Self {
50        Self {
51            code: code.to_string(),
52            severity,
53            message: message.into(),
54            primary: HirDiagnosticSpan { span, label: None },
55            secondary: Vec::new(),
56            notes: Vec::new(),
57            help: None,
58            suggestions: Vec::new(),
59            category: None,
60        }
61    }
62
63    pub fn with_primary_label(mut self, label: impl Into<String>) -> Self {
64        self.primary.label = Some(label.into());
65        self
66    }
67
68    pub fn with_secondary(mut self, span: Span, label: impl Into<String>) -> Self {
69        self.secondary.push(HirDiagnosticSpan {
70            span,
71            label: Some(label.into()),
72        });
73        self
74    }
75
76    pub fn with_note(mut self, message: impl Into<String>) -> Self {
77        self.notes.push(HirDiagnosticNote {
78            message: message.into(),
79        });
80        self
81    }
82
83    pub fn with_help(mut self, message: impl Into<String>) -> Self {
84        self.help = Some(message.into());
85        self
86    }
87
88    pub fn with_suggestion(
89        mut self,
90        span: Span,
91        replacement: impl Into<String>,
92        message: impl Into<String>,
93    ) -> Self {
94        self.suggestions.push(HirDiagnosticSuggestion {
95            span,
96            replacement: replacement.into(),
97            message: message.into(),
98        });
99        self
100    }
101
102    pub fn with_category(mut self, category: &'static str) -> Self {
103        self.category = Some(category.to_string());
104        self
105    }
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111
112    #[test]
113    fn diagnostic_builder_records_structured_context() {
114        let span = Span { start: 1, end: 4 };
115        let secondary = Span { start: 8, end: 12 };
116
117        let diagnostic = HirDiagnostic::new(
118            "RM0001",
119            HirDiagnosticSeverity::Error,
120            "undefined binding",
121            span,
122        )
123        .with_primary_label("binding used here")
124        .with_secondary(secondary, "candidate declared here")
125        .with_note("name resolution is lexical")
126        .with_help("declare the binding before use")
127        .with_suggestion(span, "x", "use an existing binding")
128        .with_category("resolution");
129
130        assert_eq!(diagnostic.code, "RM0001");
131        assert_eq!(diagnostic.primary.span, span);
132        assert_eq!(
133            diagnostic.primary.label.as_deref(),
134            Some("binding used here")
135        );
136        assert_eq!(diagnostic.secondary[0].span, secondary);
137        assert_eq!(diagnostic.notes[0].message, "name resolution is lexical");
138        assert_eq!(
139            diagnostic.help.as_deref(),
140            Some("declare the binding before use")
141        );
142        assert_eq!(diagnostic.suggestions[0].replacement, "x");
143        assert_eq!(diagnostic.category.as_deref(), Some("resolution"));
144    }
145}