about summary refs log tree commit diff
path: root/src/librustc_errors
diff options
context:
space:
mode:
Diffstat (limited to 'src/librustc_errors')
-rw-r--r--src/librustc_errors/emitter.rs484
-rw-r--r--src/librustc_errors/snippet.rs103
2 files changed, 479 insertions, 108 deletions
diff --git a/src/librustc_errors/emitter.rs b/src/librustc_errors/emitter.rs
index a307e9b696d..808a1683b84 100644
--- a/src/librustc_errors/emitter.rs
+++ b/src/librustc_errors/emitter.rs
@@ -14,7 +14,7 @@ use syntax_pos::{COMMAND_LINE_SP, DUMMY_SP, FileMap, Span, MultiSpan, CharPos};
 
 use {Level, CodeSuggestion, DiagnosticBuilder, SubDiagnostic, CodeMapper};
 use RenderSpan::*;
-use snippet::{StyledString, Style, Annotation, Line};
+use snippet::{Annotation, AnnotationType, Line, StyledString, Style};
 use styled_buffer::StyledBuffer;
 
 use std::io::prelude::*;
@@ -65,6 +65,7 @@ pub struct EmitterWriter {
 struct FileWithAnnotatedLines {
     file: Rc<FileMap>,
     lines: Vec<Line>,
+    multiline_depth: usize,
 }
 
 
@@ -137,10 +138,12 @@ impl EmitterWriter {
                                 line_index: 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() {
@@ -151,8 +154,9 @@ impl EmitterWriter {
                 let mut hi = cm.lookup_char_pos(span_label.span.hi);
                 let mut is_minimized = false;
 
-                // If the span is multi-line, simplify down to the span of one character
-                if lo.line != hi.line {
+                // If the span is long multi-line, simplify down to the span of one character
+                let max_multiline_span_length = 8;
+                if lo.line != hi.line && (hi.line - lo.line) > max_multiline_span_length {
                     hi.line = lo.line;
                     hi.col = CharPos(lo.col.0 + 1);
                     is_minimized = true;
@@ -163,22 +167,102 @@ impl EmitterWriter {
                 // 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 == hi.col {
+                if lo.col == hi.col && lo.line == hi.line {
                     hi.col = CharPos(lo.col.0 + 1);
                 }
 
-                add_annotation_to_file(&mut output,
-                                       lo.file,
-                                       lo.line,
-                                       Annotation {
-                                           start_col: lo.col.0,
-                                           end_col: hi.col.0,
-                                           is_primary: span_label.is_primary,
-                                           is_minimized: is_minimized,
-                                           label: span_label.label.clone(),
-                                       });
+                let mut ann = Annotation {
+                    start_col: lo.col.0,
+                    end_col: hi.col.0,
+                    is_primary: span_label.is_primary,
+                    label: span_label.label.clone(),
+                    annotation_type: AnnotationType::Singleline,
+                };
+                if is_minimized {
+                    ann.annotation_type = AnnotationType::Minimized;
+                } else if lo.line != hi.line {
+                    ann.annotation_type = AnnotationType::Multiline {
+                        depth: 1,
+                        line_start: lo.line,
+                        line_end: hi.line,
+                    };
+                    multiline_annotations.push((lo.file.clone(), ann.clone()));
+                };
+
+                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| {
+            if let AnnotationType::Multiline {
+                line_start: a_start,
+                line_end: a_end,
+                ..
+            } = a.1.annotation_type {
+                if let AnnotationType::Multiline {
+                    line_start: b_start,
+                    line_end: b_end,
+                    ..
+                } = b.1.annotation_type {
+                    (a_start, a_end).cmp(&(b_start, b_end))
+                } else {
+                    panic!("tried to sort multiline annotations, but found `{:?}`", b)
+                }
+            } else {
+                panic!("tried to sort multiline annotations, but found `{:?}`", a)
+            }
+        });
+        for item in multiline_annotations.clone() {
+            let ann = item.1;
+            if let AnnotationType::Multiline {line_start, line_end, ..} = ann.annotation_type {
+                for item in multiline_annotations.iter_mut() {
+                    let ref mut a = item.1;
+                    if let AnnotationType::Multiline {
+                        line_start: start,
+                        line_end: end,
+                        ..
+                    } = a.annotation_type {
+                        // Move all other multiline annotations overlapping with this one
+                        // one level to the right.
+                        if &ann != a && num_overlap(line_start, line_end, start, end, true) {
+                            a.annotation_type.increase_depth();
+                        } else {
+                            break;
+                        }
+                    } else {
+                        panic!("tried to find depth for multiline annotation, but found `{:?}`",
+                               ann)
+                    };
+                }
+            } else {
+                panic!("tried to find depth for multiline annotation, but found `{:?}`", ann)
+            };
+        }
+
+        let mut max_depth = 0;  // max overlapping multiline spans
+        for (file, ann) in multiline_annotations {
+            if let AnnotationType::Multiline {line_start, line_end, depth} = ann.annotation_type {
+                if depth > max_depth {
+                    max_depth = depth;
+                }
+                add_annotation_to_file(&mut output, file.clone(), line_start, ann.as_start());
+                for line in line_start + 1..line_end {
+                    add_annotation_to_file(&mut output, file.clone(), line, ann.as_line());
+                }
+                add_annotation_to_file(&mut output, file, line_end, ann.as_end());
+            } else {
+                panic!("non-multiline annotation `{:?}` in `multiline_annotations`!", ann);
             }
         }
+        for file_vec in output.iter_mut() {
+            file_vec.multiline_depth = max_depth;
+        }
         output
     }
 
@@ -186,14 +270,20 @@ impl EmitterWriter {
                           buffer: &mut StyledBuffer,
                           file: Rc<FileMap>,
                           line: &Line,
-                          width_offset: usize) {
+                          width_offset: usize,
+                          multiline_depth: usize) {
         let source_string = file.get_line(line.line_index - 1)
             .unwrap_or("");
 
         let line_offset = buffer.num_lines();
+        let code_offset = if multiline_depth == 0 {
+            width_offset
+        } else {
+            width_offset + multiline_depth + 1
+        };
 
         // First create the source line we will highlight.
-        buffer.puts(line_offset, width_offset, &source_string, Style::Quotation);
+        buffer.puts(line_offset, code_offset, &source_string, Style::Quotation);
         buffer.puts(line_offset,
                     0,
                     &(line.line_index.to_string()),
@@ -201,14 +291,10 @@ impl EmitterWriter {
 
         draw_col_separator(buffer, line_offset, width_offset - 2);
 
-        if line.annotations.is_empty() {
-            return;
-        }
-
         // We want to display like this:
         //
         //      vec.push(vec.pop().unwrap());
-        //      ---      ^^^               _ previous borrow ends here
+        //      ---      ^^^               - previous borrow ends here
         //      |        |
         //      |        error occurs here
         //      previous borrow of `vec` occurs here
@@ -227,42 +313,22 @@ impl EmitterWriter {
         // Sort the annotations by (start, end col)
         let mut annotations = line.annotations.clone();
         annotations.sort();
+        annotations.reverse();
 
-        // Next, create the highlight line.
-        for annotation in &annotations {
-            for p in annotation.start_col..annotation.end_col {
-                if annotation.is_primary {
-                    buffer.putc(line_offset + 1,
-                                width_offset + p,
-                                '^',
-                                Style::UnderlinePrimary);
-                    if !annotation.is_minimized {
-                        buffer.set_style(line_offset, width_offset + p, Style::UnderlinePrimary);
-                    }
-                } else {
-                    buffer.putc(line_offset + 1,
-                                width_offset + p,
-                                '-',
-                                Style::UnderlineSecondary);
-                    if !annotation.is_minimized {
-                        buffer.set_style(line_offset, width_offset + p, Style::UnderlineSecondary);
-                    }
-                }
-            }
-        }
-        draw_col_separator(buffer, line_offset + 1, width_offset - 2);
-
-        // Now we are going to write labels in. To start, we'll exclude
-        // the annotations with no labels.
-        let (labeled_annotations, unlabeled_annotations): (Vec<_>, _) = annotations.into_iter()
-            .partition(|a| a.label.is_some());
-
-        // If there are no annotations that need text, we're done.
-        if labeled_annotations.is_empty() {
-            return;
-        }
-        // Now add the text labels. We try, when possible, to stick the rightmost
-        // annotation at the end of the highlight line:
+        // 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
@@ -296,66 +362,251 @@ impl EmitterWriter {
         // 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*.
-        let mut labeled_annotations = &labeled_annotations[..];
-        match labeled_annotations.split_last().unwrap() {
-            (last, previous) => {
-                if previous.iter()
-                    .chain(&unlabeled_annotations)
-                    .all(|a| !overlaps(a, last)) {
-                    // append the label afterwards; we keep it in a separate
-                    // string
-                    let highlight_label: String = format!(" {}", last.label.as_ref().unwrap());
-                    if last.is_primary {
-                        buffer.append(line_offset + 1, &highlight_label, Style::LabelPrimary);
-                    } else {
-                        buffer.append(line_offset + 1, &highlight_label, Style::LabelSecondary);
-                    }
-                    labeled_annotations = previous;
+        let mut annotations_position = vec![];
+        let mut line_len = 0;
+        let mut p = 0;
+        let mut ann_iter = annotations.iter().peekable();
+        while let Some(annotation) = ann_iter.next() {
+            let is_line = if let AnnotationType::MultilineLine(_) = annotation.annotation_type {
+                true
+            } else {
+                false
+            };
+            let peek = ann_iter.peek();
+            if let Some(next) = peek {
+                let next_is_line = if let AnnotationType::MultilineLine(_) = next.annotation_type {
+                    true
+                } else {
+                    false
+                };
+
+                if overlaps(next, annotation) && !is_line && !next_is_line {
+                    p += 1;
                 }
             }
+            annotations_position.push((p, annotation));
+            if let Some(next) = peek {
+                let next_is_line = if let AnnotationType::MultilineLine(_) = next.annotation_type {
+                    true
+                } else {
+                    false
+                };
+                let l = if let Some(ref label) = next.label {
+                    label.len() + 2
+                } else {
+                    0
+                };
+                if (overlaps(next, annotation) || next.end_col + l > annotation.start_col)
+                    && !is_line && !next_is_line
+                {
+                    p += 1;
+                }
+            }
+            if line_len < p {
+                line_len = p;
+            }
+        }
+        if line_len != 0 {
+            line_len += 1;
         }
 
-        // If that's the last annotation, we're done
-        if labeled_annotations.is_empty() {
+        // 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| {
+                // Set the multiline annotation vertical lines to the left of
+                // the code in this line.
+                if let AnnotationType::MultilineLine(depth) = a.annotation_type {
+                    buffer.putc(line_offset,
+                                width_offset + depth - 1,
+                                '|',
+                                if a.is_primary {
+                                    Style::UnderlinePrimary
+                                } else {
+                                    Style::UnderlineSecondary
+                                });
+                    false
+                } else {
+                    true
+                }
+            }).collect::<Vec<_>>().len() == 0
+        {
             return;
         }
 
-        for (index, annotation) in labeled_annotations.iter().enumerate() {
-            // Leave:
-            // - 1 extra line
-            // - One line for each thing that comes after
-            let comes_after = labeled_annotations.len() - index - 1;
-            let blank_lines = 3 + comes_after;
+        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);
+                }
+                _ => (),
+            }
+        }
 
-            // For each blank line, draw a `|` at our column. The
-            // text ought to be long enough for this.
-            for index in 2..blank_lines {
-                if annotation.is_primary {
-                    buffer.putc(line_offset + index,
-                                width_offset + annotation.start_col,
+        // Write the vertical lines for multiline spans and 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 {
+                for p in line_offset + 1..line_offset + pos + 1 {
+                    buffer.putc(p,
+                                code_offset + annotation.start_col,
                                 '|',
-                                Style::UnderlinePrimary);
+                                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);
+                    }
+                }
+                AnnotationType::MultilineLine(depth) => {
+                    // the first line will have already be filled when we checked
+                    // wether there were any annotations for this line.
+                    for p in line_offset + 1..line_offset + line_len + 2 {
+                        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() {
+        //   |  __________ starting here...
+        //   | |    |
+        //   | |    something about `foo`
+        // 3 | |
+        // 4 | | }
+        //   | |_  ...ending here: 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| {
+            fn len(a: Annotation) -> usize {
+                // Account for usize underflows
+                if a.end_col > a.start_col {
+                    a.end_col - a.start_col
                 } else {
-                    buffer.putc(line_offset + index,
-                                width_offset + annotation.start_col,
-                                '|',
-                                Style::UnderlineSecondary);
+                    a.start_col - a.end_col
                 }
-                draw_col_separator(buffer, line_offset + index, width_offset - 2);
             }
+            // Decreasing order
+            len(a.1).cmp(&len(b.1)).reverse()
+        });
 
-            if annotation.is_primary {
-                buffer.puts(line_offset + blank_lines,
-                            width_offset + annotation.start_col,
-                            annotation.label.as_ref().unwrap(),
-                            Style::LabelPrimary);
+        // Write the underlines.
+        //
+        // After this we will have:
+        //
+        // 2 |   fn foo() {
+        //   |  ____-_____^ starting here...
+        //   | |    |
+        //   | |    something about `foo`
+        // 3 | |
+        // 4 | | }
+        //   | |_^  ...ending here: test
+        for &(_, annotation) in &annotations_position {
+            let (underline, style) = if annotation.is_primary {
+                ('^', Style::UnderlinePrimary)
             } else {
-                buffer.puts(line_offset + blank_lines,
-                            width_offset + annotation.start_col,
-                            annotation.label.as_ref().unwrap(),
-                            Style::LabelSecondary);
+                ('-', Style::UnderlineSecondary)
+            };
+            for p in annotation.start_col..annotation.end_col {
+                buffer.putc(line_offset + 1,
+                            code_offset + p,
+                            underline,
+                            style);
             }
-            draw_col_separator(buffer, line_offset + blank_lines, width_offset - 2);
         }
     }
 
@@ -577,7 +828,8 @@ impl EmitterWriter {
                 self.render_source_line(&mut buffer,
                                         annotated_file.file.clone(),
                                         &annotated_file.lines[line_idx],
-                                        3 + max_line_num_len);
+                                        3 + max_line_num_len,
+                                        annotated_file.multiline_depth);
 
                 // check to see if we need to print out or elide lines that come between
                 // this annotated line and the next one
@@ -729,16 +981,38 @@ fn draw_col_separator(buffer: &mut StyledBuffer, line: usize, col: usize) {
 }
 
 fn draw_col_separator_no_space(buffer: &mut StyledBuffer, line: usize, col: usize) {
-    buffer.puts(line, col, "|", Style::LineNumber);
+    draw_col_separator_no_space_with_style(buffer, line, col, Style::LineNumber);
+}
+
+fn draw_col_separator_no_space_with_style(buffer: &mut StyledBuffer,
+                                          line: usize,
+                                          col: usize,
+                                          style: Style) {
+    buffer.putc(line, col, '|', style);
+}
+
+fn draw_range(buffer: &mut StyledBuffer, symbol: char, line: usize,
+              col_from: usize, col_to: usize, style: Style) {
+    for col in col_from..col_to {
+        buffer.putc(line, col, symbol, style);
+    }
 }
 
 fn draw_note_separator(buffer: &mut StyledBuffer, line: usize, col: usize) {
     buffer.puts(line, col, "= ", Style::LineNumber);
 }
 
+fn num_overlap(a_start: usize, a_end: usize, b_start: usize, b_end:usize, inclusive: bool) -> bool {
+    let extra = if inclusive {
+        1
+    } else {
+        0
+    };
+    (b_start..b_end + extra).contains(a_start) ||
+    (a_start..a_end + extra).contains(b_start)
+}
 fn overlaps(a1: &Annotation, a2: &Annotation) -> bool {
-    (a2.start_col..a2.end_col).contains(a1.start_col) ||
-    (a1.start_col..a1.end_col).contains(a2.start_col)
+    num_overlap(a1.start_col, a1.end_col, a2.start_col, a2.end_col, false)
 }
 
 fn emit_to_destination(rendered_buffer: &Vec<Vec<StyledString>>,
diff --git a/src/librustc_errors/snippet.rs b/src/librustc_errors/snippet.rs
index abfb71c861b..3bf428af994 100644
--- a/src/librustc_errors/snippet.rs
+++ b/src/librustc_errors/snippet.rs
@@ -42,6 +42,57 @@ pub struct Line {
 }
 
 #[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
+pub enum AnnotationType {
+    /// Annotation under a single line of code
+    Singleline,
+
+    /// Annotation under the first character of a multiline span
+    Minimized,
+
+    /// Annotation enclosing the first and last character of a multiline span
+    Multiline {
+        depth: usize,
+        line_start: usize,
+        line_end: usize,
+    },
+
+    // The Multiline type above is replaced with the following three in order
+    // to reuse the current label drawing code.
+    //
+    // Each of these corresponds to one part of the following diagram:
+    //
+    //     x |   foo(1 + bar(x,
+    //       |  _________^ starting here...           < MultilineStart
+    //     x | |             y),                      < MultilineLine
+    //       | |______________^ ...ending here: label < MultilineEnd
+    //     x |       z);
+    /// Annotation marking the first character of a fully shown multiline span
+    MultilineStart(usize),
+    /// Annotation marking the last character of a fully shown multiline span
+    MultilineEnd(usize),
+    /// Line at the left enclosing the lines of a fully shown multiline span
+    MultilineLine(usize),
+}
+
+impl AnnotationType {
+    pub fn depth(&self) -> usize {
+        match self {
+            &AnnotationType::Multiline {depth, ..} |
+                &AnnotationType::MultilineStart(depth) |
+                &AnnotationType::MultilineLine(depth) |
+                &AnnotationType::MultilineEnd(depth) => depth,
+            _ => 0,
+        }
+    }
+
+    pub fn increase_depth(&mut self) {
+        if let AnnotationType::Multiline {ref mut depth, ..} = *self {
+            *depth += 1;
+        }
+    }
+}
+
+#[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
 pub struct Annotation {
     /// Start column, 0-based indexing -- counting *characters*, not
     /// utf-8 bytes. Note that it is important that this field goes
@@ -55,11 +106,57 @@ pub struct Annotation {
     /// Is this annotation derived from primary span
     pub is_primary: bool,
 
-    /// Is this a large span minimized down to a smaller span
-    pub is_minimized: bool,
-
     /// Optional label to display adjacent to the annotation.
     pub label: Option<String>,
+
+    /// Is this a single line, multiline or multiline span minimized down to a
+    /// smaller span.
+    pub annotation_type: AnnotationType,
+}
+
+impl Annotation {
+    pub fn is_minimized(&self) -> bool {
+        match self.annotation_type {
+            AnnotationType::Minimized => true,
+            _ => false,
+        }
+    }
+
+    pub fn is_multiline(&self) -> bool {
+        match self.annotation_type {
+            AnnotationType::Multiline {..} |
+                AnnotationType::MultilineStart(_) |
+                AnnotationType::MultilineLine(_) |
+                AnnotationType::MultilineEnd(_) => true,
+            _ => false,
+        }
+    }
+
+    pub fn as_start(&self) -> Annotation {
+        let mut a = self.clone();
+        a.annotation_type = AnnotationType::MultilineStart(self.annotation_type.depth());
+        a.end_col = a.start_col + 1;
+        a.label = Some("starting here...".to_owned());
+        a
+    }
+
+    pub fn as_end(&self) -> Annotation {
+        let mut a = self.clone();
+        a.annotation_type = AnnotationType::MultilineEnd(self.annotation_type.depth());
+        a.start_col = a.end_col - 1;
+        a.label = match a.label {
+            Some(l) => Some(format!("...ending here: {}", l)),
+            None => Some("..ending here".to_owned()),
+        };
+        a
+    }
+
+    pub fn as_line(&self) -> Annotation {
+        let mut a = self.clone();
+        a.annotation_type = AnnotationType::MultilineLine(self.annotation_type.depth());
+        a.label = None;
+        a
+    }
 }
 
 #[derive(Debug)]