spade_diagnostics/emitter/
codespan_emitter.rs
1use std::io::Write;
2
3use spade_codespan_reporting as codespan_reporting;
4use spade_codespan_reporting::diagnostic::{
5 Diagnostic as CodespanDiagnostic, SpannedNote, Subdiagnostic as CodespanSubdiagnostic,
6 Suggestion, SuggestionPart,
7};
8use spade_codespan_reporting::files::Files;
9use spade_codespan_reporting::term::termcolor::{Color, ColorChoice, ColorSpec, WriteColor};
10use spade_codespan_reporting::term::{self, termcolor::Buffer};
11
12use itertools::Itertools;
13use spade_common::location_info::AsLabel;
14
15use crate::diagnostic::{DiagnosticLevel, Subdiagnostic};
16use crate::{CodeBundle, Diagnostic, Emitter};
17
18pub fn color_choice(no_color: bool) -> ColorChoice {
19 if no_color {
20 ColorChoice::Never
21 } else {
22 ColorChoice::Auto
23 }
24}
25
26pub fn codespan_config() -> codespan_reporting::term::Config {
27 let mut primary_label_error = ColorSpec::new();
28 primary_label_error
29 .set_fg(Some(Color::Red))
30 .set_intense(true);
31
32 let style = codespan_reporting::term::Styles {
33 primary_label_error,
34 ..Default::default()
35 };
36 codespan_reporting::term::Config {
37 styles: style,
38 ..Default::default()
39 }
40}
41
42pub struct CodespanEmitter;
43
44impl Emitter for CodespanEmitter {
45 fn emit_diagnostic(&mut self, diag: &Diagnostic, buffer: &mut Buffer, code: &CodeBundle) {
46 let severity = diag.level.severity();
47 let is_bug = diag.level == DiagnosticLevel::Bug;
48 let message = diag.labels.message.as_str();
49 let primary_label = if let Some(primary_label_message) = diag.labels.primary_label.as_ref()
50 {
51 diag.labels
52 .span
53 .primary_label()
54 .with_message(primary_label_message.as_str())
55 } else {
56 diag.labels.span.primary_label()
57 };
58 let mut labels = vec![primary_label];
59 labels.extend(
60 diag.labels
61 .secondary_labels
62 .iter()
63 .map(|(sp, msg)| sp.secondary_label().with_message(msg.as_str())),
64 );
65 let mut simple_notes = vec![];
66 let mut subdiagnostics = vec![];
67 for subdiag in &diag.subdiagnostics {
68 match subdiag {
69 Subdiagnostic::Note { level, message } => {
70 if message.as_str().contains('\n') {
71 let level_len = level.as_str().len() + ": ".len();
81 simple_notes.push(format!(
82 "{}: {}",
83 level.as_str(),
84 message
85 .as_str()
86 .lines()
87 .collect::<Vec<_>>()
88 .join(&("\n".to_string() + &str::repeat(" ", level_len)))
89 ));
90 } else {
91 simple_notes.push(format!("{}: {}", level.as_str(), message.as_str()));
92 }
93 }
94 Subdiagnostic::SpannedNote {
95 level,
96 labels: note_labels,
97 } => {
98 let primary_label =
99 if let Some(primary_label_message) = note_labels.primary_label.as_ref() {
100 note_labels
101 .span
102 .primary_label()
103 .with_message(primary_label_message.as_str())
104 } else {
105 note_labels.span.primary_label()
106 };
107 let mut labels = vec![primary_label];
108 labels.extend(
109 note_labels
110 .secondary_labels
111 .iter()
112 .map(|(sp, msg)| sp.secondary_label().with_message(msg.as_str())),
113 );
114 subdiagnostics.push(CodespanSubdiagnostic::SpannedNote(SpannedNote {
115 severity: level.severity(),
116 message: note_labels.message.as_str().to_string(),
117 labels,
118 }));
119 }
120 Subdiagnostic::Suggestion { parts, message } => {
121 subdiagnostics.push(CodespanSubdiagnostic::Suggestion(Suggestion {
122 file_id: parts[0].0 .1,
123 message: message.as_str().to_string(),
124 parts: parts
125 .iter()
126 .map(|((sp, _), replacement)| SuggestionPart {
127 range: (*sp).into(),
128 replacement: replacement.to_string(),
129 })
130 .collect(),
131 }))
132 }
133 Subdiagnostic::TemplateTraceback { .. } => {
134 }
136 Subdiagnostic::TypeMismatch {
137 got,
138 got_outer,
139 expected,
140 expected_outer,
141 } => {
142 let mut note = vec![];
143 note.push(format!("note: Expected: {expected}"));
144 if let Some(expected_outer) = expected_outer {
145 note.push(format!(" in: {expected_outer}"));
146 }
147 note.push(format!(" Got: {got}"));
148 if let Some(got_outer) = got_outer {
149 note.push(format!(" in: {got_outer}"));
150 }
151 simple_notes.push(note.join("\n"))
152 }
153 }
154 }
155
156 let type_tracebacks = diag
157 .subdiagnostics
158 .iter()
159 .filter_map(|d| {
160 if let Subdiagnostic::TemplateTraceback { span, message } = d {
161 let filename = code.files.name(span.1).unwrap();
162 let line = code
163 .files
164 .location(span.1, span.0.start().0 as usize)
165 .unwrap()
166 .line_number;
167 Some(format!("{filename}:{line} {}", message.as_str()))
168 } else {
169 None
170 }
171 })
172 .collect::<Vec<_>>();
173
174 if !type_tracebacks.is_empty() {
175 let message = vec!["The error is in a generic unit instantiated at".to_string()]
176 .into_iter()
177 .chain(type_tracebacks)
178 .join("\n╰ ");
179
180 simple_notes.push(message)
181 };
182
183 let diag = CodespanDiagnostic::new(severity)
184 .with_message(message)
185 .with_labels(labels)
186 .with_notes(simple_notes)
187 .with_subdiagnostics(subdiagnostics);
188
189 if buffer.supports_color() && is_bug {
190 let _ = writeln!(buffer, "{}", super::panik::PANIK);
192 }
193
194 term::emit(buffer, &codespan_config(), &code.files, &diag).unwrap();
195 }
196}