// Copyright 2012-2015 The Rust Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution and at // http://rust-lang.org/COPYRIGHT. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. use self::Destination::*; use syntax_pos::{DUMMY_SP, FileMap, Span, MultiSpan}; use {Level, CodeSuggestion, DiagnosticBuilder, SubDiagnostic, CodeMapper, DiagnosticId}; use snippet::{Annotation, AnnotationType, Line, MultilineAnnotation, StyledString, Style}; use styled_buffer::StyledBuffer; use std::borrow::Cow; use std::io::prelude::*; use std::io; use std::rc::Rc; use term; use std::collections::HashMap; use std::cmp::min; use unicode_width; /// Emitter trait for emitting errors. pub trait Emitter { /// Emit a structured diagnostic. fn emit(&mut self, db: &DiagnosticBuilder); } impl Emitter for EmitterWriter { fn emit(&mut self, db: &DiagnosticBuilder) { let mut primary_span = db.span.clone(); let mut children = db.children.clone(); let mut suggestions: &[_] = &[]; if let Some((sugg, rest)) = db.suggestions.split_first() { if rest.is_empty() && // don't display multi-suggestions as labels sugg.substitutions.len() == 1 && // don't display multipart suggestions as labels sugg.substitutions[0].parts.len() == 1 && // don't display long messages as labels sugg.msg.split_whitespace().count() < 10 && // don't display multiline suggestions as labels !sugg.substitutions[0].parts[0].snippet.contains('\n') { let substitution = &sugg.substitutions[0].parts[0].snippet.trim(); let msg = if substitution.len() == 0 || !sugg.show_code_when_inline { // This substitution is only removal or we explicitly don't want to show the // code inline, don't show it format!("help: {}", sugg.msg) } else { format!("help: {}: `{}`", sugg.msg, substitution) }; primary_span.push_span_label(sugg.substitutions[0].parts[0].span, msg); } else { // if there are multiple suggestions, print them all in full // to be consistent. We could try to figure out if we can // make one (or the first one) inline, but that would give // undue importance to a semi-random suggestion suggestions = &db.suggestions; } } self.fix_multispans_in_std_macros(&mut primary_span, &mut children, db.handler.flags.external_macro_backtrace); self.emit_messages_default(&db.level, &db.styled_message(), &db.code, &primary_span, &children, &suggestions); } } /// maximum number of lines we will print for each error; arbitrary. pub const MAX_HIGHLIGHT_LINES: usize = 6; /// maximum number of suggestions to be shown /// /// Arbitrary, but taken from trait import suggestion limit pub const MAX_SUGGESTIONS: usize = 4; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum ColorConfig { Auto, Always, Never, } impl ColorConfig { fn use_color(&self) -> bool { match *self { ColorConfig::Always => true, ColorConfig::Never => false, ColorConfig::Auto => stderr_isatty(), } } } pub struct EmitterWriter { dst: Destination, cm: Option>, short_message: bool, teach: bool, } struct FileWithAnnotatedLines { file: Rc, lines: Vec, multiline_depth: usize, } impl EmitterWriter { pub fn stderr(color_config: ColorConfig, code_map: Option>, short_message: bool, teach: bool) -> EmitterWriter { if color_config.use_color() { let dst = Destination::from_stderr(); EmitterWriter { dst, cm: code_map, short_message, teach, } } else { EmitterWriter { dst: Raw(Box::new(io::stderr())), cm: code_map, short_message, teach, } } } pub fn new(dst: Box, code_map: Option>, short_message: bool, teach: bool) -> EmitterWriter { EmitterWriter { dst: Raw(dst), cm: code_map, short_message, teach, } } fn preprocess_annotations(&mut self, msp: &MultiSpan) -> Vec { fn add_annotation_to_file(file_vec: &mut Vec, file: Rc, line_index: usize, ann: Annotation) { for slot in file_vec.iter_mut() { // Look through each of our files for the one we're adding to if slot.file.name == file.name { // See if we already have a line for it for line_slot in &mut slot.lines { if line_slot.line_index == line_index { line_slot.annotations.push(ann); return; } } // We don't have a line yet, create one slot.lines.push(Line { line_index, annotations: vec![ann], }); slot.lines.sort(); return; } } // This is the first time we're seeing the file file_vec.push(FileWithAnnotatedLines { file, lines: vec![Line { line_index, annotations: vec![ann], }], multiline_depth: 0, }); } let mut output = vec![]; let mut multiline_annotations = vec![]; if let Some(ref cm) = self.cm { for span_label in msp.span_labels() { if span_label.span == DUMMY_SP { continue; } let lo = cm.lookup_char_pos(span_label.span.lo()); let mut hi = cm.lookup_char_pos(span_label.span.hi()); // Watch out for "empty spans". If we get a span like 6..6, we // want to just display a `^` at 6, so convert that to // 6..7. This is degenerate input, but it's best to degrade // gracefully -- and the parser likes to supply a span like // that for EOF, in particular. if lo.col_display == hi.col_display && lo.line == hi.line { hi.col_display += 1; } let ann_type = if lo.line != hi.line { let ml = MultilineAnnotation { depth: 1, line_start: lo.line, line_end: hi.line, start_col: lo.col_display, end_col: hi.col_display, is_primary: span_label.is_primary, label: span_label.label.clone(), }; multiline_annotations.push((lo.file.clone(), ml.clone())); AnnotationType::Multiline(ml) } else { AnnotationType::Singleline }; let ann = Annotation { start_col: lo.col_display, end_col: hi.col_display, is_primary: span_label.is_primary, label: span_label.label.clone(), annotation_type: ann_type, }; if !ann.is_multiline() { add_annotation_to_file(&mut output, lo.file, lo.line, ann); } } } // Find overlapping multiline annotations, put them at different depths multiline_annotations.sort_by(|a, b| { (a.1.line_start, a.1.line_end).cmp(&(b.1.line_start, b.1.line_end)) }); for item in multiline_annotations.clone() { let ann = item.1; for item in multiline_annotations.iter_mut() { let ref mut a = item.1; // Move all other multiline annotations overlapping with this one // one level to the right. if &ann != a && num_overlap(ann.line_start, ann.line_end, a.line_start, a.line_end, true) { a.increase_depth(); } else { break; } } } let mut max_depth = 0; // max overlapping multiline spans for (file, ann) in multiline_annotations { if ann.depth > max_depth { max_depth = ann.depth; } add_annotation_to_file(&mut output, file.clone(), ann.line_start, ann.as_start()); let middle = min(ann.line_start + 4, ann.line_end); for line in ann.line_start + 1..middle { add_annotation_to_file(&mut output, file.clone(), line, ann.as_line()); } if middle < ann.line_end - 1 { for line in ann.line_end - 1..ann.line_end { add_annotation_to_file(&mut output, file.clone(), line, ann.as_line()); } } add_annotation_to_file(&mut output, file, ann.line_end, ann.as_end()); } for file_vec in output.iter_mut() { file_vec.multiline_depth = max_depth; } output } fn render_source_line(&self, buffer: &mut StyledBuffer, file: Rc, line: &Line, width_offset: usize, code_offset: usize) -> Vec<(usize, Style)> { if line.line_index == 0 { return Vec::new(); } let source_string = match file.get_line(line.line_index - 1) { Some(s) => s, None => return Vec::new(), }; let line_offset = buffer.num_lines(); // First create the source line we will highlight. buffer.puts(line_offset, code_offset, &source_string, Style::Quotation); buffer.puts(line_offset, 0, &(line.line_index.to_string()), Style::LineNumber); draw_col_separator(buffer, line_offset, width_offset - 2); // Special case when there's only one annotation involved, it is the start of a multiline // span and there's no text at the beginning of the code line. Instead of doing the whole // graph: // // 2 | fn foo() { // | _^ // 3 | | // 4 | | } // | |_^ test // // we simplify the output to: // // 2 | / fn foo() { // 3 | | // 4 | | } // | |_^ test if line.annotations.len() == 1 { if let Some(ref ann) = line.annotations.get(0) { if let AnnotationType::MultilineStart(depth) = ann.annotation_type { if source_string.chars() .take(ann.start_col) .all(|c| c.is_whitespace()) { let style = if ann.is_primary { Style::UnderlinePrimary } else { Style::UnderlineSecondary }; buffer.putc(line_offset, width_offset + depth - 1, '/', style); return vec![(depth, style)]; } } } } // We want to display like this: // // vec.push(vec.pop().unwrap()); // --- ^^^ - previous borrow ends here // | | // | error occurs here // previous borrow of `vec` occurs here // // But there are some weird edge cases to be aware of: // // vec.push(vec.pop().unwrap()); // -------- - previous borrow ends here // || // |this makes no sense // previous borrow of `vec` occurs here // // For this reason, we group the lines into "highlight lines" // and "annotations lines", where the highlight lines have the `^`. // Sort the annotations by (start, end col) // The labels are reversed, sort and then reversed again. // Consider a list of annotations (A1, A2, C1, C2, B1, B2) where // the letter signifies the span. Here we are only sorting by the // span and hence, the order of the elements with the same span will // not change. On reversing the ordering (|a, b| but b.cmp(a)), you get // (C1, C2, B1, B2, A1, A2). All the elements with the same span are // still ordered first to last, but all the elements with different // spans are ordered by their spans in last to first order. Last to // first order is important, because the jiggly lines and | are on // the left, so the rightmost span needs to be rendered first, // otherwise the lines would end up needing to go over a message. let mut annotations = line.annotations.clone(); annotations.sort_by(|a,b| b.start_col.cmp(&a.start_col)); // First, figure out where each label will be positioned. // // In the case where you have the following annotations: // // vec.push(vec.pop().unwrap()); // -------- - previous borrow ends here [C] // || // |this makes no sense [B] // previous borrow of `vec` occurs here [A] // // `annotations_position` will hold [(2, A), (1, B), (0, C)]. // // We try, when possible, to stick the rightmost annotation at the end // of the highlight line: // // vec.push(vec.pop().unwrap()); // --- --- - previous borrow ends here // // But sometimes that's not possible because one of the other // annotations overlaps it. For example, from the test // `span_overlap_label`, we have the following annotations // (written on distinct lines for clarity): // // fn foo(x: u32) { // -------------- // - // // In this case, we can't stick the rightmost-most label on // the highlight line, or we would get: // // fn foo(x: u32) { // -------- x_span // | // fn_span // // which is totally weird. Instead we want: // // fn foo(x: u32) { // -------------- // | | // | x_span // fn_span // // which is...less weird, at least. In fact, in general, if // the rightmost span overlaps with any other span, we should // use the "hang below" version, so we can at least make it // clear where the span *starts*. There's an exception for this // logic, when the labels do not have a message: // // fn foo(x: u32) { // -------------- // | // x_span // // instead of: // // fn foo(x: u32) { // -------------- // | | // | x_span // // let mut annotations_position = vec![]; let mut line_len = 0; let mut p = 0; for (i, annotation) in annotations.iter().enumerate() { for (j, next) in annotations.iter().enumerate() { if overlaps(next, annotation, 0) // This label overlaps with another one and both && annotation.has_label() // take space (they have text and are not && j > i // multiline lines). && p == 0 // We're currently on the first line, move the label one line down { // This annotation needs a new line in the output. p += 1; break; } } annotations_position.push((p, annotation)); for (j, next) in annotations.iter().enumerate() { if j > i { let l = if let Some(ref label) = next.label { label.len() + 2 } else { 0 }; if (overlaps(next, annotation, l) // Do not allow two labels to be in the same // line if they overlap including padding, to // avoid situations like: // // fn foo(x: u32) { // -------^------ // | | // fn_spanx_span // && annotation.has_label() // Both labels must have some text, otherwise && next.has_label()) // they are not overlapping. // Do not add a new line if this annotation // or the next are vertical line placeholders. || (annotation.takes_space() // If either this or the next annotation is && next.has_label()) // multiline start/end, move it to a new line || (annotation.has_label() // so as not to overlap the orizontal lines. && next.takes_space()) || (annotation.takes_space() && next.takes_space()) || (overlaps(next, annotation, l) && next.end_col <= annotation.end_col && next.has_label() && p == 0) // Avoid #42595. { // This annotation needs a new line in the output. p += 1; break; } } } if line_len < p { line_len = p; } } if line_len != 0 { line_len += 1; } // If there are no annotations or the only annotations on this line are // MultilineLine, then there's only code being shown, stop processing. if line.annotations.is_empty() || line.annotations.iter() .filter(|a| !a.is_line()).collect::>().len() == 0 { return vec![]; } // Write the colunmn separator. // // After this we will have: // // 2 | fn foo() { // | // | // | // 3 | // 4 | } // | for pos in 0..line_len + 1 { draw_col_separator(buffer, line_offset + pos + 1, width_offset - 2); buffer.putc(line_offset + pos + 1, width_offset - 2, '|', Style::LineNumber); } // Write the horizontal lines for multiline annotations // (only the first and last lines need this). // // After this we will have: // // 2 | fn foo() { // | __________ // | // | // 3 | // 4 | } // | _ for &(pos, annotation) in &annotations_position { let style = if annotation.is_primary { Style::UnderlinePrimary } else { Style::UnderlineSecondary }; let pos = pos + 1; match annotation.annotation_type { AnnotationType::MultilineStart(depth) | AnnotationType::MultilineEnd(depth) => { draw_range(buffer, '_', line_offset + pos, width_offset + depth, code_offset + annotation.start_col, style); } _ if self.teach => { buffer.set_style_range(line_offset, code_offset + annotation.start_col, code_offset + annotation.end_col, style, annotation.is_primary); } _ => {} } } // Write the vertical lines for labels that are on a different line as the underline. // // After this we will have: // // 2 | fn foo() { // | __________ // | | | // | | // 3 | // 4 | | } // | |_ for &(pos, annotation) in &annotations_position { let style = if annotation.is_primary { Style::UnderlinePrimary } else { Style::UnderlineSecondary }; let pos = pos + 1; if pos > 1 && (annotation.has_label() || annotation.takes_space()) { for p in line_offset + 1..line_offset + pos + 1 { buffer.putc(p, code_offset + annotation.start_col, '|', style); } } match annotation.annotation_type { AnnotationType::MultilineStart(depth) => { for p in line_offset + pos + 1..line_offset + line_len + 2 { buffer.putc(p, width_offset + depth - 1, '|', style); } } AnnotationType::MultilineEnd(depth) => { for p in line_offset..line_offset + pos + 1 { buffer.putc(p, width_offset + depth - 1, '|', style); } } _ => (), } } // Write the labels on the annotations that actually have a label. // // After this we will have: // // 2 | fn foo() { // | __________ // | | // | something about `foo` // 3 | // 4 | } // | _ test for &(pos, annotation) in &annotations_position { let style = if annotation.is_primary { Style::LabelPrimary } else { Style::LabelSecondary }; let (pos, col) = if pos == 0 { (pos + 1, annotation.end_col + 1) } else { (pos + 2, annotation.start_col) }; if let Some(ref label) = annotation.label { buffer.puts(line_offset + pos, code_offset + col, &label, style); } } // Sort from biggest span to smallest span so that smaller spans are // represented in the output: // // x | fn foo() // | ^^^---^^ // | | | // | | something about `foo` // | something about `fn foo()` annotations_position.sort_by(|a, b| { // Decreasing order a.1.len().cmp(&b.1.len()).reverse() }); // Write the underlines. // // After this we will have: // // 2 | fn foo() { // | ____-_____^ // | | // | something about `foo` // 3 | // 4 | } // | _^ test for &(_, annotation) in &annotations_position { let (underline, style) = if annotation.is_primary { ('^', Style::UnderlinePrimary) } else { ('-', Style::UnderlineSecondary) }; for p in annotation.start_col..annotation.end_col { buffer.putc(line_offset + 1, code_offset + p, underline, style); } } annotations_position.iter().filter_map(|&(_, annotation)| { match annotation.annotation_type { AnnotationType::MultilineStart(p) | AnnotationType::MultilineEnd(p) => { let style = if annotation.is_primary { Style::LabelPrimary } else { Style::LabelSecondary }; Some((p, style)) } _ => None } }).collect::>() } fn get_multispan_max_line_num(&mut self, msp: &MultiSpan) -> usize { let mut max = 0; if let Some(ref cm) = self.cm { for primary_span in msp.primary_spans() { if primary_span != &DUMMY_SP { let hi = cm.lookup_char_pos(primary_span.hi()); if hi.line > max { max = hi.line; } } } if !self.short_message { for span_label in msp.span_labels() { if span_label.span != DUMMY_SP { let hi = cm.lookup_char_pos(span_label.span.hi()); if hi.line > max { max = hi.line; } } } } } max } fn get_max_line_num(&mut self, span: &MultiSpan, children: &Vec) -> usize { let mut max = 0; let primary = self.get_multispan_max_line_num(span); max = if primary > max { primary } else { max }; for sub in children { let sub_result = self.get_multispan_max_line_num(&sub.span); max = if sub_result > max { primary } else { max }; } max } // This "fixes" MultiSpans that contain Spans that are pointing to locations inside of // <*macros>. Since these locations are often difficult to read, we move these Spans from // <*macros> to their corresponding use site. fn fix_multispan_in_std_macros(&mut self, span: &mut MultiSpan, always_backtrace: bool) -> bool { let mut spans_updated = false; if let Some(ref cm) = self.cm { let mut before_after: Vec<(Span, Span)> = vec![]; let mut new_labels: Vec<(Span, String)> = vec![]; // First, find all the spans in <*macros> and point instead at their use site for sp in span.primary_spans() { if *sp == DUMMY_SP { continue; } let call_sp = cm.call_span_if_macro(*sp); if call_sp != *sp && !always_backtrace { before_after.push((*sp, call_sp)); } let backtrace_len = sp.macro_backtrace().len(); for (i, trace) in sp.macro_backtrace().iter().rev().enumerate() { // Only show macro locations that are local // and display them like a span_note if let Some(def_site) = trace.def_site_span { if def_site == DUMMY_SP { continue; } if always_backtrace { new_labels.push((def_site, format!("in this expansion of `{}`{}", trace.macro_decl_name, if backtrace_len > 2 { // if backtrace_len == 1 it'll be pointed // at by "in this macro invocation" format!(" (#{})", i + 1) } else { "".to_string() }))); } // Check to make sure we're not in any <*macros> if !cm.span_to_filename(def_site).is_macros() && !trace.macro_decl_name.starts_with("desugaring of ") && !trace.macro_decl_name.starts_with("#[") || always_backtrace { new_labels.push((trace.call_site, format!("in this macro invocation{}", if backtrace_len > 2 && always_backtrace { // only specify order when the macro // backtrace is multiple levels deep format!(" (#{})", i + 1) } else { "".to_string() }))); if !always_backtrace { break; } } } } } for (label_span, label_text) in new_labels { span.push_span_label(label_span, label_text); } for sp_label in span.span_labels() { if sp_label.span == DUMMY_SP { continue; } if cm.span_to_filename(sp_label.span.clone()).is_macros() && !always_backtrace { let v = sp_label.span.macro_backtrace(); if let Some(use_site) = v.last() { before_after.push((sp_label.span.clone(), use_site.call_site.clone())); } } } // After we have them, make sure we replace these 'bad' def sites with their use sites for (before, after) in before_after { span.replace(before, after); spans_updated = true; } } spans_updated } // This does a small "fix" for multispans by looking to see if it can find any that // point directly at <*macros>. Since these are often difficult to read, this // will change the span to point at the use site. fn fix_multispans_in_std_macros(&mut self, span: &mut MultiSpan, children: &mut Vec, backtrace: bool) { let mut spans_updated = self.fix_multispan_in_std_macros(span, backtrace); for child in children.iter_mut() { spans_updated |= self.fix_multispan_in_std_macros(&mut child.span, backtrace); } if spans_updated { children.push(SubDiagnostic { level: Level::Note, message: vec![ ("this error originates in a macro outside of the current crate \ (in Nightly builds, run with -Z external-macro-backtrace \ for more info)".to_string(), Style::NoStyle), ], span: MultiSpan::new(), render_span: None, }); } } /// Add a left margin to every line but the first, given a padding length and the label being /// displayed, keeping the provided highlighting. fn msg_to_buffer(&self, buffer: &mut StyledBuffer, msg: &[(String, Style)], padding: usize, label: &str, override_style: Option