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}