solar_interface/diagnostics/emitter/
human.rs1use super::{io_panic, rustc::FileWithAnnotatedLines, Diagnostic, Emitter};
2use crate::{
3 diagnostics::{Level, MultiSpan, Style, SubDiagnostic},
4 source_map::SourceFile,
5 SourceMap,
6};
7use annotate_snippets::{Annotation, Level as ASLevel, Message, Renderer, Snippet};
8use anstream::{AutoStream, ColorChoice};
9use std::{
10 any::Any,
11 io::{self, Write},
12 ops::Range,
13 sync::Arc,
14};
15
16type Writer = dyn Write + Send + 'static;
19
20const DEFAULT_RENDERER: Renderer = Renderer::plain()
21 .error(Level::Error.style())
22 .warning(Level::Warning.style())
23 .info(Level::Note.style())
24 .note(Level::Note.style())
25 .help(Level::Help.style())
26 .line_no(Style::LineNumber.to_color_spec(Level::Note))
27 .emphasis(anstyle::Style::new().bold())
28 .none(anstyle::Style::new());
29
30pub struct HumanEmitter {
32 writer_type_id: std::any::TypeId,
33 real_writer: *mut Writer,
34 writer: AutoStream<Box<Writer>>,
35 source_map: Option<Arc<SourceMap>>,
36 renderer: Renderer,
37}
38
39unsafe impl Send for HumanEmitter {}
41
42impl Emitter for HumanEmitter {
43 fn emit_diagnostic(&mut self, diagnostic: &Diagnostic) {
44 self.snippet(diagnostic, |this, snippet| {
45 writeln!(this.writer, "{}\n", this.renderer.render(snippet))?;
46 this.writer.flush()
47 })
48 .unwrap_or_else(|e| io_panic(e));
49 }
50
51 fn source_map(&self) -> Option<&Arc<SourceMap>> {
52 self.source_map.as_ref()
53 }
54
55 fn supports_color(&self) -> bool {
56 match self.writer.current_choice() {
57 ColorChoice::AlwaysAnsi | ColorChoice::Always => true,
58 ColorChoice::Auto | ColorChoice::Never => false,
59 }
60 }
61}
62
63impl HumanEmitter {
64 pub fn new<W: Write + Send + 'static>(writer: W, color: ColorChoice) -> Self {
70 let writer_type_id = writer.type_id();
72 let mut real_writer = Box::new(writer) as Box<Writer>;
73 Self {
74 writer_type_id,
75 real_writer: &mut *real_writer,
76 writer: AutoStream::new(real_writer, color),
77 source_map: None,
78 renderer: DEFAULT_RENDERER,
79 }
80 }
81
82 pub fn test() -> Self {
84 struct TestWriter(io::Stderr);
85
86 impl Write for TestWriter {
87 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
88 eprint!("{}", String::from_utf8_lossy(buf));
91 Ok(buf.len())
92 }
93
94 fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
95 self.write(buf).map(drop)
96 }
97
98 fn flush(&mut self) -> io::Result<()> {
99 self.0.flush()
100 }
101 }
102
103 Self::new(TestWriter(io::stderr()), ColorChoice::Always)
104 }
105
106 pub fn stderr(mut color_choice: ColorChoice) -> Self {
108 let stderr = io::stderr();
109 if color_choice == ColorChoice::Auto {
111 color_choice = AutoStream::choice(&stderr);
112 }
113 Self::new(io::BufWriter::new(stderr), color_choice)
115 }
116
117 pub fn source_map(mut self, source_map: Option<Arc<SourceMap>>) -> Self {
119 self.set_source_map(source_map);
120 self
121 }
122
123 pub fn set_source_map(&mut self, source_map: Option<Arc<SourceMap>>) {
125 self.source_map = source_map;
126 }
127
128 pub fn ui_testing(mut self, yes: bool) -> Self {
130 self.renderer = self.renderer.anonymized_line_numbers(yes);
131 self
132 }
133
134 pub fn set_ui_testing(&mut self, yes: bool) {
136 self.renderer =
137 std::mem::replace(&mut self.renderer, DEFAULT_RENDERER).anonymized_line_numbers(yes);
138 }
139
140 fn downcast_writer<T: Any>(&self) -> Option<&T> {
142 if self.writer_type_id == std::any::TypeId::of::<T>() {
143 Some(unsafe { &*(self.real_writer as *const T) })
144 } else {
145 None
146 }
147 }
148
149 fn downcast_writer_mut<T: Any>(&mut self) -> Option<&mut T> {
151 if self.writer_type_id == std::any::TypeId::of::<T>() {
152 Some(unsafe { &mut *(self.real_writer as *mut T) })
153 } else {
154 None
155 }
156 }
157
158 fn snippet<R>(
160 &mut self,
161 diagnostic: &Diagnostic,
162 f: impl FnOnce(&mut Self, Message<'_>) -> R,
163 ) -> R {
164 let title = OwnedMessage::from_diagnostic(diagnostic);
184
185 let owned_snippets = self
186 .source_map
187 .as_deref()
188 .map(|sm| OwnedSnippet::collect(sm, diagnostic))
189 .unwrap_or_default();
190
191 let owned_footers: Vec<_> = diagnostic
193 .children
194 .iter()
195 .filter(|sub| sub.span.is_dummy())
196 .map(OwnedMessage::from_subdiagnostic)
197 .collect();
198
199 let snippet = title
200 .as_ref()
201 .snippets(owned_snippets.iter().map(OwnedSnippet::as_ref))
202 .footers(owned_footers.iter().map(OwnedMessage::as_ref));
203 f(self, snippet)
204 }
205}
206
207pub struct HumanBufferEmitter {
209 inner: HumanEmitter,
210}
211
212impl Emitter for HumanBufferEmitter {
213 #[inline]
214 fn emit_diagnostic(&mut self, diagnostic: &Diagnostic) {
215 self.inner.emit_diagnostic(diagnostic);
216 }
217
218 #[inline]
219 fn source_map(&self) -> Option<&Arc<SourceMap>> {
220 Emitter::source_map(&self.inner)
221 }
222
223 #[inline]
224 fn supports_color(&self) -> bool {
225 self.inner.supports_color()
226 }
227}
228
229impl HumanBufferEmitter {
230 pub fn new(mut color: ColorChoice) -> Self {
232 if color == ColorChoice::Auto {
233 color = anstream::AutoStream::choice(&std::io::stderr());
234 }
235 Self { inner: HumanEmitter::new(Vec::<u8>::new(), color) }
236 }
237
238 pub fn source_map(mut self, source_map: Option<Arc<SourceMap>>) -> Self {
240 self.inner = self.inner.source_map(source_map);
241 self
242 }
243
244 pub fn ui_testing(mut self, yes: bool) -> Self {
246 self.inner = self.inner.ui_testing(yes);
247 self
248 }
249
250 pub fn inner(&self) -> &HumanEmitter {
252 &self.inner
253 }
254
255 pub fn inner_mut(&mut self) -> &mut HumanEmitter {
257 &mut self.inner
258 }
259
260 pub fn buffer(&self) -> &str {
262 let buffer = self.inner.downcast_writer::<Vec<u8>>().unwrap();
263 debug_assert!(std::str::from_utf8(buffer).is_ok(), "HumanEmitter wrote invalid UTF-8");
264 unsafe { std::str::from_utf8_unchecked(buffer) }
266 }
267
268 pub fn buffer_mut(&mut self) -> &mut String {
270 let buffer = self.inner.downcast_writer_mut::<Vec<u8>>().unwrap();
271 debug_assert!(std::str::from_utf8(buffer).is_ok(), "HumanEmitter wrote invalid UTF-8");
272 unsafe { &mut *(buffer as *mut Vec<u8> as *mut String) }
274 }
275}
276
277#[derive(Debug)]
278struct OwnedMessage {
279 id: Option<String>,
280 label: String,
281 level: ASLevel,
282}
283
284impl OwnedMessage {
285 fn from_diagnostic(diag: &Diagnostic) -> Self {
286 Self { id: diag.id(), label: diag.label().into_owned(), level: to_as_level(diag.level) }
287 }
288
289 fn from_subdiagnostic(sub: &SubDiagnostic) -> Self {
290 Self { id: None, label: sub.label().into_owned(), level: to_as_level(sub.level) }
291 }
292
293 fn as_ref(&self) -> Message<'_> {
294 let mut msg = self.level.title(&self.label);
295 if let Some(id) = &self.id {
296 msg = msg.id(id);
297 }
298 msg
299 }
300}
301
302#[derive(Debug)]
303struct OwnedAnnotation {
304 range: Range<usize>,
305 label: String,
306 level: ASLevel,
307}
308
309impl OwnedAnnotation {
310 fn as_ref(&self) -> Annotation<'_> {
311 self.level.span(self.range.clone()).label(&self.label)
312 }
313}
314
315#[derive(Debug)]
316struct OwnedSnippet {
317 origin: String,
318 source: String,
319 line_start: usize,
320 fold: bool,
321 annotations: Vec<OwnedAnnotation>,
322}
323
324impl OwnedSnippet {
325 fn collect(sm: &SourceMap, diagnostic: &Diagnostic) -> Vec<Self> {
326 let mut files = Self::collect_files(sm, &diagnostic.span);
328 files.iter_mut().for_each(|file| file.set_level(diagnostic.level));
329
330 for sub in &diagnostic.children {
332 let label = sub.label();
333 for mut sub_file in Self::collect_files(sm, &sub.span) {
334 for line in &mut sub_file.lines {
335 for ann in &mut line.annotations {
336 ann.level = Some(sub.level);
337 if ann.is_primary && ann.label.is_none() {
338 ann.label = Some(label.to_string());
339 }
340 }
341 }
342
343 if let Some(main_file) =
344 files.iter_mut().find(|main_file| Arc::ptr_eq(&main_file.file, &sub_file.file))
345 {
346 main_file.add_lines(sub_file.lines);
347 } else {
348 files.push(sub_file);
349 }
350 }
351 }
352
353 files
354 .iter()
355 .map(|file| file_to_snippet(sm, &file.file, &file.lines, diagnostic.level))
356 .collect()
357 }
358
359 fn collect_files(sm: &SourceMap, msp: &MultiSpan) -> Vec<FileWithAnnotatedLines> {
360 let mut annotated_files = FileWithAnnotatedLines::collect_annotations(sm, msp);
361 if let Some(primary_span) = msp.primary_span() {
362 if !primary_span.is_dummy() && annotated_files.len() > 1 {
363 let primary_lo = sm.lookup_char_pos(primary_span.lo());
364 if let Ok(pos) =
365 annotated_files.binary_search_by(|x| x.file.name.cmp(&primary_lo.file.name))
366 {
367 annotated_files.swap(0, pos);
368 }
369 }
370 }
371 annotated_files
372 }
373
374 fn as_ref(&self) -> Snippet<'_> {
375 Snippet::source(&self.source)
376 .line_start(self.line_start)
377 .origin(&self.origin)
378 .fold(self.fold)
379 .annotations(self.annotations.iter().map(OwnedAnnotation::as_ref))
380 }
381}
382
383fn file_to_snippet(
388 sm: &SourceMap,
389 file: &SourceFile,
390 lines: &[super::rustc::Line],
391 default_level: Level,
392) -> OwnedSnippet {
393 debug_assert!(!lines.is_empty());
394
395 let first_line = lines.first().unwrap().line_index;
396 debug_assert!(first_line > 0, "line index is 1-based");
397 let last_line = lines.last().unwrap().line_index;
398 debug_assert!(last_line >= first_line);
399 debug_assert!(lines.is_sorted());
400 let snippet_base = file.line_position(first_line - 1).unwrap();
401
402 let mut snippet = OwnedSnippet {
403 origin: sm.filename_for_diagnostics(&file.name).to_string(),
404 source: file.get_lines(first_line - 1..=last_line - 1).unwrap_or_default().into(),
405 line_start: first_line,
406 fold: true,
407 annotations: Vec::new(),
408 };
409 let mut multiline_start = None;
410 for line in lines {
411 let line_abs_pos = file.line_position(line.line_index - 1).unwrap();
412 let line_rel_pos = line_abs_pos - snippet_base;
413 let rel_pos = |c: &super::rustc::AnnotationColumn| {
416 line_rel_pos + char_to_byte_pos(&snippet.source[line_rel_pos..], c.file)
417 };
418
419 for ann in &line.annotations {
420 match ann.annotation_type {
421 super::rustc::AnnotationType::Singleline => {
422 snippet.annotations.push(OwnedAnnotation {
423 range: rel_pos(&ann.start_col)..rel_pos(&ann.end_col),
424 label: ann.label.clone().unwrap_or_default(),
425 level: to_as_level(ann.level.unwrap_or(default_level)),
426 });
427 }
428 super::rustc::AnnotationType::MultilineStart(_) => {
429 debug_assert!(multiline_start.is_none());
430 multiline_start = Some((ann.label.as_ref(), rel_pos(&ann.start_col)));
431 }
432 super::rustc::AnnotationType::MultilineLine(_) => {}
433 super::rustc::AnnotationType::MultilineEnd(_) => {
434 let (label, multiline_start_idx) = multiline_start.take().unwrap();
435 let end_idx = rel_pos(&ann.end_col);
436 debug_assert!(end_idx >= multiline_start_idx);
437 snippet.annotations.push(OwnedAnnotation {
438 range: multiline_start_idx..end_idx,
439 label: label.or(ann.label.as_ref()).cloned().unwrap_or_default(),
440 level: to_as_level(ann.level.unwrap_or(default_level)),
441 });
442 }
443 }
444 }
445 }
446 snippet
447}
448
449fn to_as_level(level: Level) -> ASLevel {
450 match level {
451 Level::Bug | Level::Fatal | Level::Error => ASLevel::Error,
452 Level::Warning => ASLevel::Warning,
453 Level::Note | Level::OnceNote | Level::FailureNote => ASLevel::Note,
454 Level::Help | Level::OnceHelp => ASLevel::Help,
455 Level::Allow => ASLevel::Info,
456 }
457}
458
459fn char_to_byte_pos(s: &str, char_pos: usize) -> usize {
460 s.chars().take(char_pos).map(char::len_utf8).sum()
461}