solar_interface/diagnostics/emitter/
human.rs1use super::{io_panic, rustc::FileWithAnnotatedLines, Diag, 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: &Diag) {
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>(&mut self, diagnostic: &Diag, f: impl FnOnce(&mut Self, Message<'_>) -> R) -> R {
160 let title = OwnedMessage::from_diagnostic(diagnostic);
180
181 let owned_snippets = self
182 .source_map
183 .as_deref()
184 .map(|sm| OwnedSnippet::collect(sm, diagnostic))
185 .unwrap_or_default();
186
187 let owned_footers: Vec<_> = diagnostic
189 .children
190 .iter()
191 .filter(|sub| sub.span.is_dummy())
192 .map(OwnedMessage::from_subdiagnostic)
193 .collect();
194
195 let snippet = title
196 .as_ref()
197 .snippets(owned_snippets.iter().map(OwnedSnippet::as_ref))
198 .footers(owned_footers.iter().map(OwnedMessage::as_ref));
199 f(self, snippet)
200 }
201}
202
203pub struct HumanBufferEmitter {
205 inner: HumanEmitter,
206}
207
208impl Emitter for HumanBufferEmitter {
209 #[inline]
210 fn emit_diagnostic(&mut self, diagnostic: &Diag) {
211 self.inner.emit_diagnostic(diagnostic);
212 }
213
214 #[inline]
215 fn source_map(&self) -> Option<&Arc<SourceMap>> {
216 Emitter::source_map(&self.inner)
217 }
218
219 #[inline]
220 fn supports_color(&self) -> bool {
221 self.inner.supports_color()
222 }
223}
224
225impl HumanBufferEmitter {
226 pub fn new(mut color: ColorChoice) -> Self {
228 if color == ColorChoice::Auto {
229 color = anstream::AutoStream::choice(&std::io::stderr());
230 }
231 Self { inner: HumanEmitter::new(Vec::<u8>::new(), color) }
232 }
233
234 pub fn source_map(mut self, source_map: Option<Arc<SourceMap>>) -> Self {
236 self.inner = self.inner.source_map(source_map);
237 self
238 }
239
240 pub fn ui_testing(mut self, yes: bool) -> Self {
242 self.inner = self.inner.ui_testing(yes);
243 self
244 }
245
246 pub fn inner(&self) -> &HumanEmitter {
248 &self.inner
249 }
250
251 pub fn inner_mut(&mut self) -> &mut HumanEmitter {
253 &mut self.inner
254 }
255
256 pub fn buffer(&self) -> &str {
258 let buffer = self.inner.downcast_writer::<Vec<u8>>().unwrap();
259 debug_assert!(std::str::from_utf8(buffer).is_ok(), "HumanEmitter wrote invalid UTF-8");
260 unsafe { std::str::from_utf8_unchecked(buffer) }
262 }
263
264 pub fn buffer_mut(&mut self) -> &mut String {
266 let buffer = self.inner.downcast_writer_mut::<Vec<u8>>().unwrap();
267 debug_assert!(std::str::from_utf8(buffer).is_ok(), "HumanEmitter wrote invalid UTF-8");
268 unsafe { &mut *(buffer as *mut Vec<u8> as *mut String) }
270 }
271}
272
273#[derive(Debug)]
274struct OwnedMessage {
275 id: Option<String>,
276 label: String,
277 level: ASLevel,
278}
279
280impl OwnedMessage {
281 fn from_diagnostic(diag: &Diag) -> Self {
282 Self { id: diag.id(), label: diag.label().into_owned(), level: to_as_level(diag.level) }
283 }
284
285 fn from_subdiagnostic(sub: &SubDiagnostic) -> Self {
286 Self { id: None, label: sub.label().into_owned(), level: to_as_level(sub.level) }
287 }
288
289 fn as_ref(&self) -> Message<'_> {
290 let mut msg = self.level.title(&self.label);
291 if let Some(id) = &self.id {
292 msg = msg.id(id);
293 }
294 msg
295 }
296}
297
298#[derive(Debug)]
299struct OwnedAnnotation {
300 range: Range<usize>,
301 label: String,
302 level: ASLevel,
303}
304
305impl OwnedAnnotation {
306 fn as_ref(&self) -> Annotation<'_> {
307 self.level.span(self.range.clone()).label(&self.label)
308 }
309}
310
311#[derive(Debug)]
312struct OwnedSnippet {
313 origin: String,
314 source: String,
315 line_start: usize,
316 fold: bool,
317 annotations: Vec<OwnedAnnotation>,
318}
319
320impl OwnedSnippet {
321 fn collect(sm: &SourceMap, diagnostic: &Diag) -> Vec<Self> {
322 let mut files = Self::collect_files(sm, &diagnostic.span);
324 files.iter_mut().for_each(|file| file.set_level(diagnostic.level));
325
326 for sub in &diagnostic.children {
328 let label = sub.label();
329 for mut sub_file in Self::collect_files(sm, &sub.span) {
330 for line in &mut sub_file.lines {
331 for ann in &mut line.annotations {
332 ann.level = Some(sub.level);
333 if ann.is_primary && ann.label.is_none() {
334 ann.label = Some(label.to_string());
335 }
336 }
337 }
338
339 if let Some(main_file) =
340 files.iter_mut().find(|main_file| Arc::ptr_eq(&main_file.file, &sub_file.file))
341 {
342 main_file.add_lines(sub_file.lines);
343 } else {
344 files.push(sub_file);
345 }
346 }
347 }
348
349 files
350 .iter()
351 .map(|file| file_to_snippet(sm, &file.file, &file.lines, diagnostic.level))
352 .collect()
353 }
354
355 fn collect_files(sm: &SourceMap, msp: &MultiSpan) -> Vec<FileWithAnnotatedLines> {
356 let mut annotated_files = FileWithAnnotatedLines::collect_annotations(sm, msp);
357 if let Some(primary_span) = msp.primary_span() {
358 if !primary_span.is_dummy() && annotated_files.len() > 1 {
359 let primary_lo = sm.lookup_char_pos(primary_span.lo());
360 if let Ok(pos) =
361 annotated_files.binary_search_by(|x| x.file.name.cmp(&primary_lo.file.name))
362 {
363 annotated_files.swap(0, pos);
364 }
365 }
366 }
367 annotated_files
368 }
369
370 fn as_ref(&self) -> Snippet<'_> {
371 Snippet::source(&self.source)
372 .line_start(self.line_start)
373 .origin(&self.origin)
374 .fold(self.fold)
375 .annotations(self.annotations.iter().map(OwnedAnnotation::as_ref))
376 }
377}
378
379type MultiLine<'a> = (Option<&'a String>, usize);
380
381fn multi_line_at<'a, 'b>(mls: &'a mut Vec<MultiLine<'b>>, depth: usize) -> &'a mut MultiLine<'b> {
382 assert!(depth > 0);
383 if mls.len() < depth {
384 mls.resize_with(depth, || (None, 0));
385 }
386 &mut mls[depth - 1]
387}
388
389fn file_to_snippet(
394 sm: &SourceMap,
395 file: &SourceFile,
396 lines: &[super::rustc::Line],
397 default_level: Level,
398) -> OwnedSnippet {
399 debug_assert!(!lines.is_empty());
400
401 let first_line = lines.first().unwrap().line_index;
402 debug_assert!(first_line > 0, "line index is 1-based");
403 let last_line = lines.last().unwrap().line_index;
404 debug_assert!(last_line >= first_line);
405 debug_assert!(lines.is_sorted());
406 let snippet_base = file.line_position(first_line - 1).unwrap();
407
408 let mut snippet = OwnedSnippet {
409 origin: sm.filename_for_diagnostics(&file.name).to_string(),
410 source: file.get_lines(first_line - 1..=last_line - 1).unwrap_or_default().into(),
411 line_start: first_line,
412 fold: true,
413 annotations: Vec::new(),
414 };
415 let mut mls = Vec::new();
416 for line in lines {
417 let line_abs_pos = file.line_position(line.line_index - 1).unwrap();
418 let line_rel_pos = line_abs_pos - snippet_base;
419 let rel_pos = |c: &super::rustc::AnnotationColumn| {
422 line_rel_pos + char_to_byte_pos(&snippet.source[line_rel_pos..], c.file)
423 };
424
425 for ann in &line.annotations {
426 match ann.annotation_type {
427 super::rustc::AnnotationType::Singleline => {
428 snippet.annotations.push(OwnedAnnotation {
429 range: rel_pos(&ann.start_col)..rel_pos(&ann.end_col),
430 label: ann.label.clone().unwrap_or_default(),
431 level: to_as_level(ann.level.unwrap_or(default_level)),
432 });
433 }
434 super::rustc::AnnotationType::MultilineStart(depth) => {
435 *multi_line_at(&mut mls, depth) = (ann.label.as_ref(), rel_pos(&ann.start_col));
436 }
437 super::rustc::AnnotationType::MultilineLine(_) => {}
438 super::rustc::AnnotationType::MultilineEnd(depth) => {
439 let (label, multiline_start_idx) = *multi_line_at(&mut mls, depth);
440 let end_idx = rel_pos(&ann.end_col);
441 debug_assert!(end_idx >= multiline_start_idx);
442 snippet.annotations.push(OwnedAnnotation {
443 range: multiline_start_idx..end_idx,
444 label: label.or(ann.label.as_ref()).cloned().unwrap_or_default(),
445 level: to_as_level(ann.level.unwrap_or(default_level)),
446 });
447 }
448 }
449 }
450 }
451 snippet
452}
453
454fn to_as_level(level: Level) -> ASLevel {
455 match level {
456 Level::Bug | Level::Fatal | Level::Error => ASLevel::Error,
457 Level::Warning => ASLevel::Warning,
458 Level::Note | Level::OnceNote | Level::FailureNote => ASLevel::Note,
459 Level::Help | Level::OnceHelp => ASLevel::Help,
460 Level::Allow => ASLevel::Info,
461 }
462}
463
464fn char_to_byte_pos(s: &str, char_pos: usize) -> usize {
465 s.chars().take(char_pos).map(char::len_utf8).sum()
466}