1 // Copyright 2012-2015 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
11 use self::Destination::*;
13 use syntax_pos::{COMMAND_LINE_SP, DUMMY_SP, FileMap, Span, MultiSpan, CharPos};
15 use {Level, CodeSuggestion, DiagnosticBuilder, SubDiagnostic, CodeMapper};
17 use snippet::{StyledString, Style, Annotation, Line};
18 use styled_buffer::StyledBuffer;
20 use std::io::prelude::*;
25 /// Emitter trait for emitting errors.
27 /// Emit a structured diagnostic.
28 fn emit(&mut self, db: &DiagnosticBuilder);
31 impl Emitter for EmitterWriter {
32 fn emit(&mut self, db: &DiagnosticBuilder) {
33 let mut primary_span = db.span.clone();
34 let mut children = db.children.clone();
35 self.fix_multispans_in_std_macros(&mut primary_span, &mut children);
36 self.emit_messages_default(&db.level, &db.message, &db.code, &primary_span, &children);
40 /// maximum number of lines we will print for each error; arbitrary.
41 pub const MAX_HIGHLIGHT_LINES: usize = 6;
43 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
44 pub enum ColorConfig {
51 fn use_color(&self) -> bool {
53 ColorConfig::Always => true,
54 ColorConfig::Never => false,
55 ColorConfig::Auto => stderr_isatty(),
60 pub struct EmitterWriter {
62 cm: Option<Rc<CodeMapper>>,
65 struct FileWithAnnotatedLines {
71 /// Do not use this for messages that end in `\n` – use `println_maybe_styled` instead. See
72 /// `EmitterWriter::print_maybe_styled` for details.
73 macro_rules! print_maybe_styled {
74 ($dst: expr, $style: expr, $($arg: tt)*) => {
75 $dst.print_maybe_styled(format_args!($($arg)*), $style, false)
79 macro_rules! println_maybe_styled {
80 ($dst: expr, $style: expr, $($arg: tt)*) => {
81 $dst.print_maybe_styled(format_args!($($arg)*), $style, true)
86 pub fn stderr(color_config: ColorConfig,
87 code_map: Option<Rc<CodeMapper>>)
89 if color_config.use_color() {
90 let dst = Destination::from_stderr();
91 EmitterWriter { dst: dst,
94 EmitterWriter { dst: Raw(Box::new(io::stderr())),
99 pub fn new(dst: Box<Write + Send>,
100 code_map: Option<Rc<CodeMapper>>)
102 EmitterWriter { dst: Raw(dst),
106 fn preprocess_annotations(&self, msp: &MultiSpan) -> Vec<FileWithAnnotatedLines> {
107 fn add_annotation_to_file(file_vec: &mut Vec<FileWithAnnotatedLines>,
112 for slot in file_vec.iter_mut() {
113 // Look through each of our files for the one we're adding to
114 if slot.file.name == file.name {
115 // See if we already have a line for it
116 for line_slot in &mut slot.lines {
117 if line_slot.line_index == line_index {
118 line_slot.annotations.push(ann);
122 // We don't have a line yet, create one
123 slot.lines.push(Line {
124 line_index: line_index,
125 annotations: vec![ann],
131 // This is the first time we're seeing the file
132 file_vec.push(FileWithAnnotatedLines {
135 line_index: line_index,
136 annotations: vec![ann],
141 let mut output = vec![];
143 if let Some(ref cm) = self.cm {
144 for span_label in msp.span_labels() {
145 if span_label.span == DUMMY_SP || span_label.span == COMMAND_LINE_SP {
148 let lo = cm.lookup_char_pos(span_label.span.lo);
149 let mut hi = cm.lookup_char_pos(span_label.span.hi);
150 let mut is_minimized = false;
152 // If the span is multi-line, simplify down to the span of one character
153 if lo.line != hi.line {
155 hi.col = CharPos(lo.col.0 + 1);
159 // Watch out for "empty spans". If we get a span like 6..6, we
160 // want to just display a `^` at 6, so convert that to
161 // 6..7. This is degenerate input, but it's best to degrade
162 // gracefully -- and the parser likes to supply a span like
163 // that for EOF, in particular.
164 if lo.col == hi.col {
165 hi.col = CharPos(lo.col.0 + 1);
168 add_annotation_to_file(&mut output,
174 is_primary: span_label.is_primary,
175 is_minimized: is_minimized,
176 label: span_label.label.clone(),
183 fn render_source_line(&self,
184 buffer: &mut StyledBuffer,
187 width_offset: usize) {
188 let source_string = file.get_line(line.line_index - 1)
191 let line_offset = buffer.num_lines();
193 // First create the source line we will highlight.
194 buffer.puts(line_offset, width_offset, &source_string, Style::Quotation);
195 buffer.puts(line_offset,
197 &(line.line_index.to_string()),
200 draw_col_separator(buffer, line_offset, width_offset - 2);
202 if line.annotations.is_empty() {
206 // We want to display like this:
208 // vec.push(vec.pop().unwrap());
209 // --- ^^^ _ previous borrow ends here
211 // | error occurs here
212 // previous borrow of `vec` occurs here
214 // But there are some weird edge cases to be aware of:
216 // vec.push(vec.pop().unwrap());
217 // -------- - previous borrow ends here
219 // |this makes no sense
220 // previous borrow of `vec` occurs here
222 // For this reason, we group the lines into "highlight lines"
223 // and "annotations lines", where the highlight lines have the `~`.
225 // Sort the annotations by (start, end col)
226 let mut annotations = line.annotations.clone();
229 // Next, create the highlight line.
230 for annotation in &annotations {
231 for p in annotation.start_col..annotation.end_col {
232 if annotation.is_primary {
233 buffer.putc(line_offset + 1,
236 Style::UnderlinePrimary);
237 if !annotation.is_minimized {
238 buffer.set_style(line_offset,
240 Style::UnderlinePrimary);
243 buffer.putc(line_offset + 1,
246 Style::UnderlineSecondary);
247 if !annotation.is_minimized {
248 buffer.set_style(line_offset,
250 Style::UnderlineSecondary);
255 draw_col_separator(buffer, line_offset + 1, width_offset - 2);
257 // Now we are going to write labels in. To start, we'll exclude
258 // the annotations with no labels.
259 let (labeled_annotations, unlabeled_annotations): (Vec<_>, _) = annotations.into_iter()
260 .partition(|a| a.label.is_some());
262 // If there are no annotations that need text, we're done.
263 if labeled_annotations.is_empty() {
266 // Now add the text labels. We try, when possible, to stick the rightmost
267 // annotation at the end of the highlight line:
269 // vec.push(vec.pop().unwrap());
270 // --- --- - previous borrow ends here
272 // But sometimes that's not possible because one of the other
273 // annotations overlaps it. For example, from the test
274 // `span_overlap_label`, we have the following annotations
275 // (written on distinct lines for clarity):
281 // In this case, we can't stick the rightmost-most label on
282 // the highlight line, or we would get:
289 // which is totally weird. Instead we want:
297 // which is...less weird, at least. In fact, in general, if
298 // the rightmost span overlaps with any other span, we should
299 // use the "hang below" version, so we can at least make it
300 // clear where the span *starts*.
301 let mut labeled_annotations = &labeled_annotations[..];
302 match labeled_annotations.split_last().unwrap() {
303 (last, previous) => {
305 .chain(&unlabeled_annotations)
306 .all(|a| !overlaps(a, last)) {
307 // append the label afterwards; we keep it in a separate
309 let highlight_label: String = format!(" {}", last.label.as_ref().unwrap());
311 buffer.append(line_offset + 1, &highlight_label, Style::LabelPrimary);
313 buffer.append(line_offset + 1, &highlight_label, Style::LabelSecondary);
315 labeled_annotations = previous;
320 // If that's the last annotation, we're done
321 if labeled_annotations.is_empty() {
325 for (index, annotation) in labeled_annotations.iter().enumerate() {
328 // - One line for each thing that comes after
329 let comes_after = labeled_annotations.len() - index - 1;
330 let blank_lines = 3 + comes_after;
332 // For each blank line, draw a `|` at our column. The
333 // text ought to be long enough for this.
334 for index in 2..blank_lines {
335 if annotation.is_primary {
336 buffer.putc(line_offset + index,
337 width_offset + annotation.start_col,
339 Style::UnderlinePrimary);
341 buffer.putc(line_offset + index,
342 width_offset + annotation.start_col,
344 Style::UnderlineSecondary);
346 draw_col_separator(buffer, line_offset + index, width_offset - 2);
349 if annotation.is_primary {
350 buffer.puts(line_offset + blank_lines,
351 width_offset + annotation.start_col,
352 annotation.label.as_ref().unwrap(),
353 Style::LabelPrimary);
355 buffer.puts(line_offset + blank_lines,
356 width_offset + annotation.start_col,
357 annotation.label.as_ref().unwrap(),
358 Style::LabelSecondary);
360 draw_col_separator(buffer, line_offset + blank_lines, width_offset - 2);
364 fn get_multispan_max_line_num(&mut self, msp: &MultiSpan) -> usize {
366 if let Some(ref cm) = self.cm {
367 for primary_span in msp.primary_spans() {
368 if primary_span != &DUMMY_SP && primary_span != &COMMAND_LINE_SP {
369 let hi = cm.lookup_char_pos(primary_span.hi);
375 for span_label in msp.span_labels() {
376 if span_label.span != DUMMY_SP && span_label.span != COMMAND_LINE_SP {
377 let hi = cm.lookup_char_pos(span_label.span.hi);
387 fn get_max_line_num(&mut self, span: &MultiSpan, children: &Vec<SubDiagnostic>) -> usize {
390 let primary = self.get_multispan_max_line_num(span);
391 max = if primary > max { primary } else { max };
393 for sub in children {
394 let sub_result = self.get_multispan_max_line_num(&sub.span);
395 max = if sub_result > max { primary } else { max };
400 // This "fixes" MultiSpans that contain Spans that are pointing to locations inside of
401 // <*macros>. Since these locations are often difficult to read, we move these Spans from
402 // <*macros> to their corresponding use site.
403 fn fix_multispan_in_std_macros(&mut self, span: &mut MultiSpan) -> bool {
404 let mut spans_updated = false;
406 if let Some(ref cm) = self.cm {
407 let mut before_after: Vec<(Span, Span)> = vec![];
408 let mut new_labels: Vec<(Span, String)> = vec![];
410 // First, find all the spans in <*macros> and point instead at their use site
411 for sp in span.primary_spans() {
412 if (*sp == COMMAND_LINE_SP) || (*sp == DUMMY_SP) {
415 if cm.span_to_filename(sp.clone()).contains("macros>") {
416 let v = cm.macro_backtrace(sp.clone());
417 if let Some(use_site) = v.last() {
418 before_after.push((sp.clone(), use_site.call_site.clone()));
421 for trace in cm.macro_backtrace(sp.clone()).iter().rev() {
422 // Only show macro locations that are local
423 // and display them like a span_note
424 if let Some(def_site) = trace.def_site_span {
425 if (def_site == COMMAND_LINE_SP) || (def_site == DUMMY_SP) {
428 // Check to make sure we're not in any <*macros>
429 if !cm.span_to_filename(def_site).contains("macros>") {
430 new_labels.push((trace.call_site,
431 "in this macro invocation".to_string()));
437 for (label_span, label_text) in new_labels {
438 span.push_span_label(label_span, label_text);
440 for sp_label in span.span_labels() {
441 if (sp_label.span == COMMAND_LINE_SP) || (sp_label.span == DUMMY_SP) {
444 if cm.span_to_filename(sp_label.span.clone()).contains("macros>") {
445 let v = cm.macro_backtrace(sp_label.span.clone());
446 if let Some(use_site) = v.last() {
447 before_after.push((sp_label.span.clone(), use_site.call_site.clone()));
451 // After we have them, make sure we replace these 'bad' def sites with their use sites
452 for (before, after) in before_after {
453 span.replace(before, after);
454 spans_updated = true;
461 // This does a small "fix" for multispans by looking to see if it can find any that
462 // point directly at <*macros>. Since these are often difficult to read, this
463 // will change the span to point at the use site.
464 fn fix_multispans_in_std_macros(&mut self,
465 span: &mut MultiSpan,
466 children: &mut Vec<SubDiagnostic>) {
467 let mut spans_updated = self.fix_multispan_in_std_macros(span);
468 for child in children.iter_mut() {
469 spans_updated |= self.fix_multispan_in_std_macros(&mut child.span);
472 children.push(SubDiagnostic {
474 message: "this error originates in a macro from the standard library".to_string(),
475 span: MultiSpan::new(),
481 fn emit_message_default(&mut self,
484 code: &Option<String>,
486 max_line_num_len: usize,
489 let mut buffer = StyledBuffer::new();
491 if msp.primary_spans().is_empty() && msp.span_labels().is_empty() && is_secondary {
492 // This is a secondary message with no span info
493 for _ in 0..max_line_num_len {
494 buffer.prepend(0, " ", Style::NoStyle);
496 draw_note_separator(&mut buffer, 0, max_line_num_len + 1);
497 buffer.append(0, &level.to_string(), Style::HeaderMsg);
498 buffer.append(0, ": ", Style::NoStyle);
499 buffer.append(0, msg, Style::NoStyle);
502 buffer.append(0, &level.to_string(), Style::Level(level.clone()));
505 buffer.append(0, "[", Style::Level(level.clone()));
506 buffer.append(0, &code, Style::Level(level.clone()));
507 buffer.append(0, "]", Style::Level(level.clone()));
511 buffer.append(0, ": ", Style::HeaderMsg);
512 buffer.append(0, msg, Style::HeaderMsg);
515 // Preprocess all the annotations so that they are grouped by file and by line number
516 // This helps us quickly iterate over the whole message (including secondary file spans)
517 let mut annotated_files = self.preprocess_annotations(msp);
519 // Make sure our primary file comes first
521 if let (Some(ref cm), Some(ref primary_span)) = (self.cm.as_ref(),
522 msp.primary_span().as_ref()) {
523 if primary_span != &&DUMMY_SP && primary_span != &&COMMAND_LINE_SP {
524 cm.lookup_char_pos(primary_span.lo)
527 emit_to_destination(&buffer.render(), level, &mut self.dst)?;
531 // If we don't have span information, emit and exit
532 emit_to_destination(&buffer.render(), level, &mut self.dst)?;
536 annotated_files.binary_search_by(|x| x.file.name.cmp(&primary_lo.file.name)) {
537 annotated_files.swap(0, pos);
540 // Print out the annotate source lines that correspond with the error
541 for annotated_file in annotated_files {
542 // print out the span location and spacer before we print the annotated source
543 // to do this, we need to know if this span will be primary
544 let is_primary = primary_lo.file.name == annotated_file.file.name;
546 // remember where we are in the output buffer for easy reference
547 let buffer_msg_line_offset = buffer.num_lines();
549 buffer.prepend(buffer_msg_line_offset, "--> ", Style::LineNumber);
550 let loc = primary_lo.clone();
551 buffer.append(buffer_msg_line_offset,
552 &format!("{}:{}:{}", loc.file.name, loc.line, loc.col.0 + 1),
553 Style::LineAndColumn);
554 for _ in 0..max_line_num_len {
555 buffer.prepend(buffer_msg_line_offset, " ", Style::NoStyle);
558 // remember where we are in the output buffer for easy reference
559 let buffer_msg_line_offset = buffer.num_lines();
562 draw_col_separator(&mut buffer, buffer_msg_line_offset, max_line_num_len + 1);
564 // Then, the secondary file indicator
565 buffer.prepend(buffer_msg_line_offset + 1, "::: ", Style::LineNumber);
566 buffer.append(buffer_msg_line_offset + 1,
567 &annotated_file.file.name,
568 Style::LineAndColumn);
569 for _ in 0..max_line_num_len {
570 buffer.prepend(buffer_msg_line_offset + 1, " ", Style::NoStyle);
574 // Put in the spacer between the location and annotated source
575 let buffer_msg_line_offset = buffer.num_lines();
576 draw_col_separator_no_space(&mut buffer, buffer_msg_line_offset, max_line_num_len + 1);
578 // Next, output the annotate source for this file
579 for line_idx in 0..annotated_file.lines.len() {
580 self.render_source_line(&mut buffer,
581 annotated_file.file.clone(),
582 &annotated_file.lines[line_idx],
583 3 + max_line_num_len);
585 // check to see if we need to print out or elide lines that come between
586 // this annotated line and the next one
587 if line_idx < (annotated_file.lines.len() - 1) {
588 let line_idx_delta = annotated_file.lines[line_idx + 1].line_index -
589 annotated_file.lines[line_idx].line_index;
590 if line_idx_delta > 2 {
591 let last_buffer_line_num = buffer.num_lines();
592 buffer.puts(last_buffer_line_num, 0, "...", Style::LineNumber);
593 } else if line_idx_delta == 2 {
594 let unannotated_line = annotated_file.file
595 .get_line(annotated_file.lines[line_idx].line_index)
598 let last_buffer_line_num = buffer.num_lines();
600 buffer.puts(last_buffer_line_num,
602 &(annotated_file.lines[line_idx + 1].line_index - 1)
605 draw_col_separator(&mut buffer, last_buffer_line_num, 1 + max_line_num_len);
606 buffer.puts(last_buffer_line_num,
607 3 + max_line_num_len,
615 // final step: take our styled buffer, render it, then output it
616 emit_to_destination(&buffer.render(), level, &mut self.dst)?;
620 fn emit_suggestion_default(&mut self,
621 suggestion: &CodeSuggestion,
624 max_line_num_len: usize)
626 use std::borrow::Borrow;
628 let primary_span = suggestion.msp.primary_span().unwrap();
629 if let Some(ref cm) = self.cm {
630 let mut buffer = StyledBuffer::new();
632 buffer.append(0, &level.to_string(), Style::Level(level.clone()));
633 buffer.append(0, ": ", Style::HeaderMsg);
634 buffer.append(0, msg, Style::HeaderMsg);
636 let lines = cm.span_to_lines(primary_span).unwrap();
638 assert!(!lines.lines.is_empty());
640 let complete = suggestion.splice_lines(cm.borrow());
642 // print the suggestion without any line numbers, but leave
643 // space for them. This helps with lining up with previous
644 // snippets from the actual error being reported.
645 let mut lines = complete.lines();
647 for line in lines.by_ref().take(MAX_HIGHLIGHT_LINES) {
648 draw_col_separator(&mut buffer, row_num, max_line_num_len + 1);
649 buffer.append(row_num, line, Style::NoStyle);
653 // if we elided some lines, add an ellipsis
654 if let Some(_) = lines.next() {
655 buffer.append(row_num, "...", Style::NoStyle);
657 emit_to_destination(&buffer.render(), level, &mut self.dst)?;
661 fn emit_messages_default(&mut self,
664 code: &Option<String>,
666 children: &Vec<SubDiagnostic>) {
667 let max_line_num = self.get_max_line_num(span, children);
668 let max_line_num_len = max_line_num.to_string().len();
670 match self.emit_message_default(span,
677 if !children.is_empty() {
678 let mut buffer = StyledBuffer::new();
679 draw_col_separator_no_space(&mut buffer, 0, max_line_num_len + 1);
680 match emit_to_destination(&buffer.render(), level, &mut self.dst) {
682 Err(e) => panic!("failed to emit error: {}", e)
685 for child in children {
686 match child.render_span {
687 Some(FullSpan(ref msp)) => {
688 match self.emit_message_default(msp,
694 Err(e) => panic!("failed to emit error: {}", e),
698 Some(Suggestion(ref cs)) => {
699 match self.emit_suggestion_default(cs,
703 Err(e) => panic!("failed to emit error: {}", e),
708 match self.emit_message_default(&child.span,
714 Err(e) => panic!("failed to emit error: {}", e),
721 Err(e) => panic!("failed to emit error: {}", e)
723 match write!(&mut self.dst, "\n") {
724 Err(e) => panic!("failed to emit error: {}", e),
730 fn draw_col_separator(buffer: &mut StyledBuffer, line: usize, col: usize) {
731 buffer.puts(line, col, "| ", Style::LineNumber);
734 fn draw_col_separator_no_space(buffer: &mut StyledBuffer, line: usize, col: usize) {
735 buffer.puts(line, col, "|", Style::LineNumber);
738 fn draw_note_separator(buffer: &mut StyledBuffer, line: usize, col: usize) {
739 buffer.puts(line, col, "= ", Style::LineNumber);
742 fn overlaps(a1: &Annotation, a2: &Annotation) -> bool {
743 (a2.start_col..a2.end_col).contains(a1.start_col) ||
744 (a1.start_col..a1.end_col).contains(a2.start_col)
747 fn emit_to_destination(rendered_buffer: &Vec<Vec<StyledString>>,
749 dst: &mut Destination) -> io::Result<()> {
750 for line in rendered_buffer {
752 dst.apply_style(lvl.clone(), part.style)?;
753 write!(dst, "{}", part.text)?;
762 fn stderr_isatty() -> bool {
764 unsafe { libc::isatty(libc::STDERR_FILENO) != 0 }
767 fn stderr_isatty() -> bool {
770 type HANDLE = *mut u8;
771 const STD_ERROR_HANDLE: DWORD = -12i32 as DWORD;
773 fn GetStdHandle(which: DWORD) -> HANDLE;
774 fn GetConsoleMode(hConsoleHandle: HANDLE,
775 lpMode: *mut DWORD) -> BOOL;
778 let handle = GetStdHandle(STD_ERROR_HANDLE);
780 GetConsoleMode(handle, &mut out) != 0
784 pub enum Destination {
785 Terminal(Box<term::StderrTerminal>),
786 Raw(Box<Write + Send>),
790 fn from_stderr() -> Destination {
791 match term::stderr() {
792 Some(t) => Terminal(t),
793 None => Raw(Box::new(io::stderr())),
797 fn apply_style(&mut self,
802 Style::FileNameStyle | Style::LineAndColumn => {}
803 Style::LineNumber => {
804 try!(self.start_attr(term::Attr::Bold));
805 try!(self.start_attr(term::Attr::ForegroundColor(term::color::BRIGHT_BLUE)));
807 Style::ErrorCode => {
808 try!(self.start_attr(term::Attr::Bold));
809 try!(self.start_attr(term::Attr::ForegroundColor(term::color::BRIGHT_MAGENTA)));
811 Style::Quotation => {}
812 Style::OldSchoolNote => {
813 try!(self.start_attr(term::Attr::Bold));
814 try!(self.start_attr(term::Attr::ForegroundColor(term::color::BRIGHT_GREEN)));
816 Style::OldSchoolNoteText | Style::HeaderMsg => {
817 try!(self.start_attr(term::Attr::Bold));
819 Style::UnderlinePrimary | Style::LabelPrimary => {
820 try!(self.start_attr(term::Attr::Bold));
821 try!(self.start_attr(term::Attr::ForegroundColor(lvl.color())));
823 Style::UnderlineSecondary |
824 Style::LabelSecondary => {
825 try!(self.start_attr(term::Attr::Bold));
826 try!(self.start_attr(term::Attr::ForegroundColor(term::color::BRIGHT_BLUE)));
830 try!(self.start_attr(term::Attr::Bold));
831 try!(self.start_attr(term::Attr::ForegroundColor(l.color())));
837 fn start_attr(&mut self, attr: term::Attr) -> io::Result<()> {
839 Terminal(ref mut t) => { t.attr(attr)?; }
845 fn reset_attrs(&mut self) -> io::Result<()> {
847 Terminal(ref mut t) => { t.reset()?; }
854 impl Write for Destination {
855 fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
857 Terminal(ref mut t) => t.write(bytes),
858 Raw(ref mut w) => w.write(bytes),
861 fn flush(&mut self) -> io::Result<()> {
863 Terminal(ref mut t) => t.flush(),
864 Raw(ref mut w) => w.flush(),