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 warning(span: impl Into<FullSpan>, message: impl Into<Message>) -> Self {
215 Self::new(DiagnosticLevel::Warning, span, message)
216 }
217
218 pub fn level(mut self, level: DiagnosticLevel) -> Self {
219 self.level = level;
220 self
221 }
222 pub fn message(mut self, message: impl Into<Message>) -> Self {
223 self.labels.message = message.into();
224 self
225 }
226
227 pub fn primary_label(mut self, primary_label: impl Into<Message>) -> Self {
229 self.labels.primary_label = Some(primary_label.into());
230 self
231 }
232
233 pub fn secondary_label(
235 mut self,
236 span: impl Into<FullSpan>,
237 message: impl Into<Message>,
238 ) -> Self {
239 self.labels
240 .secondary_labels
241 .push((span.into(), message.into()));
242 self
243 }
244
245 pub fn note(mut self, message: impl Into<Message>) -> Self {
247 self.add_note(message);
248 self
249 }
250
251 pub fn add_note(&mut self, message: impl Into<Message>) -> &mut Self {
255 self.subdiagnostics.push(Subdiagnostic::Note {
256 level: SubdiagnosticLevel::Note,
257 message: message.into(),
258 });
259 self
260 }
261
262 pub fn help(mut self, message: impl Into<Message>) -> Self {
266 self.add_help(message);
267 self
268 }
269
270 pub fn add_help(&mut self, message: impl Into<Message>) -> &mut Self {
274 self.subdiagnostics.push(Subdiagnostic::Note {
275 level: SubdiagnosticLevel::Help,
276 message: message.into(),
277 });
278 self
279 }
280
281 pub fn subdiagnostic(mut self, subdiagnostic: Subdiagnostic) -> Self {
290 self.subdiagnostics.push(subdiagnostic);
291 self
292 }
293
294 pub fn push_subdiagnostic(&mut self, subdiagnostic: Subdiagnostic) -> &mut Self {
296 self.subdiagnostics.push(subdiagnostic);
297 self
298 }
299
300 pub fn span_suggest(
301 self,
302 message: impl Into<Message>,
303 span: impl Into<FullSpan>,
304 code: impl Into<String>,
305 ) -> Self {
306 self.subdiagnostic(Subdiagnostic::Suggestion {
307 parts: vec![(span.into(), code.into())],
308 message: message.into(),
309 })
310 }
311
312 pub fn span_suggest_insert_before(
318 self,
319 message: impl Into<Message>,
320 span: impl Into<FullSpan>,
321 code: impl Into<String>,
322 ) -> Self {
323 let (span, file) = span.into();
324 let code = code.into();
325
326 assert!(!code.is_empty());
327
328 self.span_suggest(message, (Span::new(span.start(), span.start()), file), code)
329 }
330
331 pub fn span_suggest_insert_after(
337 self,
338 message: impl Into<Message>,
339 span: impl Into<FullSpan>,
340 code: impl Into<String>,
341 ) -> Self {
342 let (span, file) = span.into();
343 let code = code.into();
344
345 assert!(!code.is_empty());
346
347 self.span_suggest(message, (Span::new(span.end(), span.end()), file), code)
348 }
349
350 pub fn span_suggest_replace(
352 self,
353 message: impl Into<Message>,
354 span: impl Into<FullSpan>,
355 code: impl Into<String>,
356 ) -> Self {
357 let (span, file) = span.into();
358 let code = code.into();
359
360 assert!(span.start() != span.end());
361 assert!(!code.is_empty());
362
363 self.span_suggest(message, (span, file), code)
364 }
365
366 pub fn span_suggest_remove(
368 self,
369 message: impl Into<Message>,
370 span: impl Into<FullSpan>,
371 ) -> Self {
372 let (span, file) = span.into();
373
374 assert!(span.start() != span.end());
375
376 self.span_suggest(message, (span, file), "")
377 }
378
379 pub fn span_suggest_multipart(
381 mut self,
382 message: impl Into<Message>,
383 parts: SuggestionParts,
384 ) -> Self {
385 self.push_span_suggest_multipart(message, parts);
386 self
387 }
388
389 pub fn push_span_suggest_multipart(
391 &mut self,
392 message: impl Into<Message>,
393 SuggestionParts(parts): SuggestionParts,
394 ) -> &mut Self {
395 self.subdiagnostics.push(Subdiagnostic::Suggestion {
396 parts,
397 message: message.into(),
398 });
399 self
400 }
401
402 pub fn type_error(
403 mut self,
404 expected: String,
405 expected_outer: Option<String>,
406 got: String,
407 got_outer: Option<String>,
408 ) -> Self {
409 self.push_subdiagnostic(Subdiagnostic::TypeMismatch {
410 got,
411 got_outer,
412 expected,
413 expected_outer,
414 });
415 self
416 }
417}
418
419#[macro_export]
422macro_rules! diag_assert {
423 ($span:expr, $condition:expr) => {
424 diag_assert!($span, $condition, "Assertion {} failed", stringify!($condition))
425 };
426 ($span:expr, $condition:expr, $($rest:tt)*) => {
427 if !$condition {
428 return Err(Diagnostic::bug(
429 $span,
430 format!($($rest)*),
431 )
432 .into());
433 }
434 };
435}
436
437#[macro_export]
439macro_rules! diag_anyhow {
440 ($span:expr, $($arg:tt)*) => {
441 {
442 let bt = std::backtrace::Backtrace::capture();
443 let bt = if bt.status() == std::backtrace::BacktraceStatus::Captured {
444 format!("{bt}")
445 } else {
446 format!("Rerun with RUST_BACKTRACE=1 to capture a backtrace")
447 };
448 spade_diagnostics::Diagnostic::bug($span, format!($($arg)*))
449 .note(format!("Triggered at {}:{}\n{bt}", file!(), line!()))
450 }
451 }
452}
453
454#[macro_export]
456macro_rules! diag_bail {
457 ($span:expr, $($arg:tt)*) => {
458 return Err(spade_diagnostics::diag_anyhow!($span, $($arg)*).into())
459 }
460}