1use spade_codespan::Span;
2use spade_codespan_reporting::diagnostic::Severity;
3
4use spade_common::location_info::FullSpan;
5
6const INTERNAL_BUG_NOTE: &str = r#"This is an internal bug in the compiler.
7We would appreciate if you opened an issue in the repository:
8https://gitlab.com/spade-lang/spade/-/issues/new?issuable_template=Internal%20bug"#;
9
10#[derive(Debug, Clone, PartialEq)]
11pub enum Message {
12 Str(String),
13 }
15
16impl Message {
17 pub fn as_str(&self) -> &str {
18 match self {
19 Message::Str(s) => s,
20 }
21 }
22}
23
24impl From<String> for Message {
25 fn from(other: String) -> Message {
26 Message::Str(other)
27 }
28}
29
30impl From<&str> for Message {
31 fn from(other: &str) -> Message {
32 Message::from(other.to_string())
33 }
34}
35
36#[derive(Debug, Clone, PartialEq)]
37pub enum DiagnosticLevel {
38 Bug,
40 Error,
41 Warning,
42}
43
44#[derive(Debug, Clone, PartialEq)]
45pub enum SubdiagnosticLevel {
46 Help,
47 Note,
48}
49
50impl DiagnosticLevel {
51 pub fn as_str(&self) -> &'static str {
52 match self {
53 DiagnosticLevel::Bug => "internal bug",
54 DiagnosticLevel::Error => "error",
55 DiagnosticLevel::Warning => "warning",
56 }
57 }
58
59 pub fn severity(&self) -> Severity {
60 match self {
61 DiagnosticLevel::Bug => Severity::Bug,
62 DiagnosticLevel::Error => Severity::Error,
63 DiagnosticLevel::Warning => Severity::Warning,
64 }
65 }
66}
67
68impl SubdiagnosticLevel {
69 pub fn as_str(&self) -> &'static str {
70 match self {
71 SubdiagnosticLevel::Help => "help",
72 SubdiagnosticLevel::Note => "note",
73 }
74 }
75
76 pub fn severity(&self) -> Severity {
77 match self {
78 SubdiagnosticLevel::Help => Severity::Help,
79 SubdiagnosticLevel::Note => Severity::Note,
80 }
81 }
82}
83
84#[derive(Debug, Clone, PartialEq)]
85pub struct Labels {
86 pub message: Message,
87 pub span: FullSpan,
89 pub primary_label: Option<Message>,
91 pub secondary_labels: Vec<(FullSpan, Message)>,
93}
94
95#[must_use]
97#[derive(Debug, Clone, PartialEq)]
98pub struct Diagnostic {
99 pub level: DiagnosticLevel,
100 pub labels: Labels,
101 pub subdiagnostics: Vec<Subdiagnostic>,
103}
104
105#[derive(Debug, Clone, PartialEq)]
107pub enum Subdiagnostic {
108 Note {
110 level: SubdiagnosticLevel,
111 message: Message,
112 },
113 TypeMismatch {
114 got: String,
115 got_outer: Option<String>,
116 expected: String,
117 expected_outer: Option<String>,
118 },
119 SpannedNote {
121 level: SubdiagnosticLevel,
122 labels: Labels,
123 },
124 TemplateTraceback {
125 span: FullSpan,
126 message: Message,
127 },
128 Suggestion {
130 parts: Vec<(FullSpan, String)>,
146 message: Message,
147 },
148}
149
150impl Subdiagnostic {
151 pub fn span_note(span: impl Into<FullSpan>, message: impl Into<Message>) -> Self {
152 Subdiagnostic::SpannedNote {
153 level: SubdiagnosticLevel::Note,
154 labels: Labels {
155 message: message.into(),
156 span: span.into(),
157 primary_label: None,
158 secondary_labels: Vec::new(),
159 },
160 }
161 }
162}
163
164pub struct SuggestionParts(Vec<(FullSpan, String)>);
166
167impl Default for SuggestionParts {
168 fn default() -> Self {
169 Self::new()
170 }
171}
172
173impl SuggestionParts {
174 pub fn new() -> Self {
175 Self(Vec::new())
176 }
177
178 pub fn part(mut self, span: impl Into<FullSpan>, code: impl Into<String>) -> Self {
179 self.0.push((span.into(), code.into()));
180 self
181 }
182
183 pub fn push_part(&mut self, span: impl Into<FullSpan>, code: impl Into<String>) {
184 self.0.push((span.into(), code.into()));
185 }
186}
187
188impl Diagnostic {
189 fn new(level: DiagnosticLevel, span: impl Into<FullSpan>, message: impl Into<Message>) -> Self {
190 Self {
191 level,
192 labels: Labels {
193 message: message.into(),
194 span: span.into(),
195 primary_label: None,
196 secondary_labels: Vec::new(),
197 },
198 subdiagnostics: Vec::new(),
199 }
200 }
201
202 pub fn bug(span: impl Into<FullSpan>, message: impl Into<Message>) -> Self {
206 Self::new(DiagnosticLevel::Bug, span, message).note(INTERNAL_BUG_NOTE)
207 }
208
209 pub fn error(span: impl Into<FullSpan>, message: impl Into<Message>) -> Self {
211 Self::new(DiagnosticLevel::Error, span, message)
212 }
213
214 pub fn level(mut self, level: DiagnosticLevel) -> Self {
215 self.level = level;
216 self
217 }
218 pub fn message(mut self, message: impl Into<Message>) -> Self {
219 self.labels.message = message.into();
220 self
221 }
222
223 pub fn primary_label(mut self, primary_label: impl Into<Message>) -> Self {
225 self.labels.primary_label = Some(primary_label.into());
226 self
227 }
228
229 pub fn secondary_label(
231 mut self,
232 span: impl Into<FullSpan>,
233 message: impl Into<Message>,
234 ) -> Self {
235 self.labels
236 .secondary_labels
237 .push((span.into(), message.into()));
238 self
239 }
240
241 pub fn note(mut self, message: impl Into<Message>) -> Self {
243 self.add_note(message);
244 self
245 }
246
247 pub fn add_note(&mut self, message: impl Into<Message>) -> &mut Self {
251 self.subdiagnostics.push(Subdiagnostic::Note {
252 level: SubdiagnosticLevel::Note,
253 message: message.into(),
254 });
255 self
256 }
257
258 pub fn help(mut self, message: impl Into<Message>) -> Self {
262 self.add_help(message);
263 self
264 }
265
266 pub fn add_help(&mut self, message: impl Into<Message>) -> &mut Self {
270 self.subdiagnostics.push(Subdiagnostic::Note {
271 level: SubdiagnosticLevel::Help,
272 message: message.into(),
273 });
274 self
275 }
276
277 pub fn subdiagnostic(mut self, subdiagnostic: Subdiagnostic) -> Self {
286 self.subdiagnostics.push(subdiagnostic);
287 self
288 }
289
290 pub fn push_subdiagnostic(&mut self, subdiagnostic: Subdiagnostic) -> &mut Self {
292 self.subdiagnostics.push(subdiagnostic);
293 self
294 }
295
296 pub fn span_suggest(
297 self,
298 message: impl Into<Message>,
299 span: impl Into<FullSpan>,
300 code: impl Into<String>,
301 ) -> Self {
302 self.subdiagnostic(Subdiagnostic::Suggestion {
303 parts: vec![(span.into(), code.into())],
304 message: message.into(),
305 })
306 }
307
308 pub fn span_suggest_insert_before(
314 self,
315 message: impl Into<Message>,
316 span: impl Into<FullSpan>,
317 code: impl Into<String>,
318 ) -> Self {
319 let (span, file) = span.into();
320 let code = code.into();
321
322 assert!(!code.is_empty());
323
324 self.span_suggest(message, (Span::new(span.start(), span.start()), file), code)
325 }
326
327 pub fn span_suggest_insert_after(
333 self,
334 message: impl Into<Message>,
335 span: impl Into<FullSpan>,
336 code: impl Into<String>,
337 ) -> Self {
338 let (span, file) = span.into();
339 let code = code.into();
340
341 assert!(!code.is_empty());
342
343 self.span_suggest(message, (Span::new(span.end(), span.end()), file), code)
344 }
345
346 pub fn span_suggest_replace(
348 self,
349 message: impl Into<Message>,
350 span: impl Into<FullSpan>,
351 code: impl Into<String>,
352 ) -> Self {
353 let (span, file) = span.into();
354 let code = code.into();
355
356 assert!(span.start() != span.end());
357 assert!(!code.is_empty());
358
359 self.span_suggest(message, (span, file), code)
360 }
361
362 pub fn span_suggest_remove(
364 self,
365 message: impl Into<Message>,
366 span: impl Into<FullSpan>,
367 ) -> Self {
368 let (span, file) = span.into();
369
370 assert!(span.start() != span.end());
371
372 self.span_suggest(message, (span, file), "")
373 }
374
375 pub fn span_suggest_multipart(
377 mut self,
378 message: impl Into<Message>,
379 parts: SuggestionParts,
380 ) -> Self {
381 self.push_span_suggest_multipart(message, parts);
382 self
383 }
384
385 pub fn push_span_suggest_multipart(
387 &mut self,
388 message: impl Into<Message>,
389 SuggestionParts(parts): SuggestionParts,
390 ) -> &mut Self {
391 self.subdiagnostics.push(Subdiagnostic::Suggestion {
392 parts,
393 message: message.into(),
394 });
395 self
396 }
397
398 pub fn type_error(
399 mut self,
400 expected: String,
401 expected_outer: Option<String>,
402 got: String,
403 got_outer: Option<String>,
404 ) -> Self {
405 self.push_subdiagnostic(Subdiagnostic::TypeMismatch {
406 got,
407 got_outer,
408 expected,
409 expected_outer,
410 });
411 self
412 }
413}
414
415#[macro_export]
418macro_rules! diag_assert {
419 ($span:expr, $condition:expr) => {
420 diag_assert!($span, $condition, "Assertion {} failed", stringify!($condition))
421 };
422 ($span:expr, $condition:expr, $($rest:tt)*) => {
423 if !$condition {
424 return Err(Diagnostic::bug(
425 $span,
426 format!($($rest)*),
427 )
428 .into());
429 }
430 };
431}
432
433#[macro_export]
435macro_rules! diag_anyhow {
436 ($span:expr, $($arg:tt)*) => {
437 spade_diagnostics::Diagnostic::bug($span, format!($($arg)*))
438 .note(format!("Triggered at {}:{}", file!(), line!()))
439 }
440}
441
442#[macro_export]
444macro_rules! diag_bail {
445 ($span:expr, $($arg:tt)*) => {
446 return Err(spade_diagnostics::diag_anyhow!($span, $($arg)*).into())
447 }
448}