about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/librustc_errors/emitter.rs484
-rw-r--r--src/librustc_errors/snippet.rs103
-rw-r--r--src/libsyntax/lib.rs3
-rw-r--r--src/libsyntax/test_snippet.rs446
-rw-r--r--src/test/ui/compare-method/region-extra-2.stderr12
-rw-r--r--src/test/ui/compare-method/traits-misc-mismatch-2.stderr12
-rw-r--r--src/test/ui/dropck/dropck-eyepatch-implies-unsafe-impl.stderr20
-rw-r--r--src/test/ui/lifetimes/consider-using-explicit-lifetime.stderr7
-rw-r--r--src/test/ui/mismatched_types/main.stderr6
-rw-r--r--src/test/ui/missing-items/m2.stderr6
-rw-r--r--src/test/ui/span/impl-wrong-item-for-trait.stderr54
-rw-r--r--src/test/ui/span/issue-23827.stderr12
-rw-r--r--src/test/ui/span/issue-24356.stderr10
-rw-r--r--src/test/ui/span/multiline-span-simple.rs30
-rw-r--r--src/test/ui/span/multiline-span-simple.stderr20
15 files changed, 1081 insertions, 144 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)]
diff --git a/src/libsyntax/lib.rs b/src/libsyntax/lib.rs
index 34280812421..0545cccabf7 100644
--- a/src/libsyntax/lib.rs
+++ b/src/libsyntax/lib.rs
@@ -144,4 +144,7 @@ pub mod ext {
     }
 }
 
+#[cfg(test)]
+mod test_snippet;
+
 // __build_diagnostic_array! { libsyntax, DIAGNOSTICS }
diff --git a/src/libsyntax/test_snippet.rs b/src/libsyntax/test_snippet.rs
new file mode 100644
index 00000000000..4ce51076adc
--- /dev/null
+++ b/src/libsyntax/test_snippet.rs
@@ -0,0 +1,446 @@
+// Copyright 2016 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use codemap::CodeMap;
+use errors::Handler;
+use errors::emitter::EmitterWriter;
+use std::io;
+use std::io::prelude::*;
+use std::rc::Rc;
+use std::str;
+use std::sync::{Arc, Mutex};
+use syntax_pos::{BytePos, NO_EXPANSION, Span, MultiSpan};
+
+/// Identify a position in the text by the Nth occurrence of a string.
+struct Position {
+    string: &'static str,
+    count: usize,
+}
+
+struct SpanLabel {
+    start: Position,
+    end: Position,
+    label: &'static str,
+}
+
+struct Shared<T: Write> {
+    data: Arc<Mutex<T>>,
+}
+
+impl<T: Write> Write for Shared<T> {
+    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+        self.data.lock().unwrap().write(buf)
+    }
+
+    fn flush(&mut self) -> io::Result<()> {
+        self.data.lock().unwrap().flush()
+    }
+}
+
+fn test_harness(file_text: &str, span_labels: Vec<SpanLabel>, expected_output: &str) {
+    let output = Arc::new(Mutex::new(Vec::new()));
+
+    let code_map = Rc::new(CodeMap::new());
+    code_map.new_filemap_and_lines("test.rs", None, &file_text);
+
+    let primary_span = make_span(&file_text, &span_labels[0].start, &span_labels[0].end);
+    let mut msp = MultiSpan::from_span(primary_span);
+    for span_label in span_labels {
+        let span = make_span(&file_text, &span_label.start, &span_label.end);
+        msp.push_span_label(span, span_label.label.to_string());
+        println!("span: {:?} label: {:?}", span, span_label.label);
+        println!("text: {:?}", code_map.span_to_snippet(span));
+    }
+
+    let emitter = EmitterWriter::new(Box::new(Shared { data: output.clone() }),
+                                     Some(code_map.clone()));
+    let handler = Handler::with_emitter(true, false, Box::new(emitter));
+    handler.span_err(msp, "foo");
+
+    assert!(expected_output.chars().next() == Some('\n'),
+            "expected output should begin with newline");
+    let expected_output = &expected_output[1..];
+
+    let bytes = output.lock().unwrap();
+    let actual_output = str::from_utf8(&bytes).unwrap();
+    println!("expected output:\n------\n{}------", expected_output);
+    println!("actual output:\n------\n{}------", actual_output);
+
+    assert!(expected_output == actual_output)
+}
+
+fn make_span(file_text: &str, start: &Position, end: &Position) -> Span {
+    let start = make_pos(file_text, start);
+    let end = make_pos(file_text, end) + end.string.len(); // just after matching thing ends
+    assert!(start <= end);
+    Span {
+        lo: BytePos(start as u32),
+        hi: BytePos(end as u32),
+        expn_id: NO_EXPANSION,
+    }
+}
+
+fn make_pos(file_text: &str, pos: &Position) -> usize {
+    let mut remainder = file_text;
+    let mut offset = 0;
+    for _ in 0..pos.count {
+        if let Some(n) = remainder.find(&pos.string) {
+            offset += n;
+            remainder = &remainder[n + 1..];
+        } else {
+            panic!("failed to find {} instances of {:?} in {:?}",
+                   pos.count,
+                   pos.string,
+                   file_text);
+        }
+    }
+    offset
+}
+
+#[test]
+fn ends_on_col0() {
+    test_harness(r#"
+fn foo() {
+}
+"#,
+    vec![
+        SpanLabel {
+           start: Position {
+               string: "{",
+               count: 1,
+           },
+           end: Position {
+               string: "}",
+               count: 1,
+           },
+           label: "test",
+       },
+    ],
+    r#"
+error: foo
+ --> test.rs:2:10
+  |
+2 |   fn foo() {
+  |  __________^ starting here...
+3 | | }
+  | |_^ ...ending here: test
+
+"#);
+}
+
+#[test]
+fn ends_on_col2() {
+    test_harness(r#"
+fn foo() {
+
+
+  }
+"#,
+     vec![
+        SpanLabel {
+            start: Position {
+                string: "{",
+                count: 1,
+            },
+            end: Position {
+                string: "}",
+                count: 1,
+            },
+            label: "test",
+        },
+     ],
+     r#"
+error: foo
+ --> test.rs:2:10
+  |
+2 |   fn foo() {
+  |  __________^ starting here...
+3 | |
+4 | |
+5 | |   }
+  | |___^ ...ending here: test
+
+"#);
+}
+#[test]
+fn non_nested() {
+    test_harness(r#"
+fn foo() {
+  X0 Y0
+  X1 Y1
+  X2 Y2
+}
+"#,
+    vec![
+        SpanLabel {
+            start: Position {
+                string: "X0",
+                count: 1,
+            },
+            end: Position {
+                string: "X2",
+                count: 1,
+            },
+            label: "`X` is a good letter",
+        },
+        SpanLabel {
+            start: Position {
+                string: "Y0",
+                count: 1,
+            },
+            end: Position {
+                string: "Y2",
+                count: 1,
+            },
+            label: "`Y` is a good letter too",
+        },
+    ],
+    r#"
+error: foo
+ --> test.rs:3:3
+  |
+3 |      X0 Y0
+  |  ____^__- starting here...
+  | | ___|
+  | ||   starting here...
+4 | ||   X1 Y1
+5 | ||   X2 Y2
+  | ||____^__- ...ending here: `Y` is a good letter too
+  |  |____|
+  |       ...ending here: `X` is a good letter
+
+"#);
+}
+
+#[test]
+fn nested() {
+    test_harness(r#"
+fn foo() {
+  X0 Y0
+  Y1 X1
+}
+"#,
+    vec![
+        SpanLabel {
+            start: Position {
+                string: "X0",
+                count: 1,
+            },
+            end: Position {
+                string: "X1",
+                count: 1,
+            },
+            label: "`X` is a good letter",
+        },
+        SpanLabel {
+            start: Position {
+                string: "Y0",
+                count: 1,
+            },
+            end: Position {
+                string: "Y1",
+                count: 1,
+            },
+            label: "`Y` is a good letter too",
+        },
+    ],
+r#"
+error: foo
+ --> test.rs:3:3
+  |
+3 |      X0 Y0
+  |  ____^__- starting here...
+  | | ___|
+  | ||   starting here...
+4 | ||   Y1 X1
+  | ||____-__^ ...ending here: `X` is a good letter
+  | |_____|
+  |       ...ending here: `Y` is a good letter too
+
+"#);
+}
+
+#[test]
+fn different_overlap() {
+    test_harness(r#"
+fn foo() {
+  X0 Y0 Z0
+  X1 Y1 Z1
+  X2 Y2 Z2
+  X3 Y3 Z3
+}
+"#,
+    vec![
+        SpanLabel {
+            start: Position {
+                string: "Y0",
+                count: 1,
+            },
+            end: Position {
+                string: "X2",
+                count: 1,
+            },
+            label: "`X` is a good letter",
+        },
+        SpanLabel {
+            start: Position {
+                string: "Z1",
+                count: 1,
+            },
+            end: Position {
+                string: "X3",
+                count: 1,
+            },
+            label: "`Y` is a good letter too",
+        },
+    ],
+    r#"
+error: foo
+ --> test.rs:3:6
+  |
+3 |      X0 Y0 Z0
+  |   ______^ starting here...
+4 |  |   X1 Y1 Z1
+  |  |_________- starting here...
+5 | ||   X2 Y2 Z2
+  | ||____^ ...ending here: `X` is a good letter
+6 | |    X3 Y3 Z3
+  | |_____- ...ending here: `Y` is a good letter too
+
+"#);
+}
+
+#[test]
+fn triple_overlap() {
+    test_harness(r#"
+fn foo() {
+  X0 Y0 Z0
+  X1 Y1 Z1
+  X2 Y2 Z2
+}
+"#,
+    vec![
+        SpanLabel {
+            start: Position {
+                string: "X0",
+                count: 1,
+            },
+            end: Position {
+                string: "X2",
+                count: 1,
+            },
+            label: "`X` is a good letter",
+        },
+        SpanLabel {
+            start: Position {
+                string: "Y0",
+                count: 1,
+            },
+            end: Position {
+                string: "Y2",
+                count: 1,
+            },
+            label: "`Y` is a good letter too",
+        },
+        SpanLabel {
+            start: Position {
+                string: "Z0",
+                count: 1,
+            },
+            end: Position {
+                string: "Z2",
+                count: 1,
+            },
+            label: "`Z` label",
+        },
+    ],
+    r#"
+error: foo
+ --> test.rs:3:3
+  |
+3 |       X0 Y0 Z0
+  |  _____^__-__- starting here...
+  | | ____|__|
+  | || ___|  starting here...
+  | |||   starting here...
+4 | |||   X1 Y1 Z1
+5 | |||   X2 Y2 Z2
+  | |||____^__-__- ...ending here: `Z` label
+  |  ||____|__|
+  |   |____|  ...ending here: `Y` is a good letter too
+  |        ...ending here: `X` is a good letter
+
+"#);
+}
+
+#[test]
+fn minimum_depth() {
+    test_harness(r#"
+fn foo() {
+  X0 Y0 Z0
+  X1 Y1 Z1
+  X2 Y2 Z2
+  X3 Y3 Z3
+}
+"#,
+    vec![
+        SpanLabel {
+            start: Position {
+                string: "Y0",
+                count: 1,
+            },
+            end: Position {
+                string: "X1",
+                count: 1,
+            },
+            label: "`X` is a good letter",
+        },
+        SpanLabel {
+            start: Position {
+                string: "Y1",
+                count: 1,
+            },
+            end: Position {
+                string: "Z2",
+                count: 1,
+            },
+            label: "`Y` is a good letter too",
+        },
+        SpanLabel {
+            start: Position {
+                string: "X2",
+                count: 1,
+            },
+            end: Position {
+                string: "Y3",
+                count: 1,
+            },
+            label: "`Z`",
+        },
+    ],
+    r#"
+error: foo
+ --> test.rs:3:6
+  |
+3 |      X0 Y0 Z0
+  |   ______^ starting here...
+4 |  |   X1 Y1 Z1
+  |  |____^_- starting here...
+  | ||____|
+  | |     ...ending here: `X` is a good letter
+5 | |    X2 Y2 Z2
+  | |____-______- ...ending here: `Y` is a good letter too
+  |  ____|
+  | |    starting here...
+6 | |    X3 Y3 Z3
+  | |________- ...ending here: `Z`
+
+"#);
+}
diff --git a/src/test/ui/compare-method/region-extra-2.stderr b/src/test/ui/compare-method/region-extra-2.stderr
index 54a551bcfed..12b0ecabcc7 100644
--- a/src/test/ui/compare-method/region-extra-2.stderr
+++ b/src/test/ui/compare-method/region-extra-2.stderr
@@ -1,11 +1,15 @@
 error[E0276]: impl has stricter requirements than trait
   --> $DIR/region-extra-2.rs:19:5
    |
-15 |     fn renew<'b: 'a>(self) -> &'b mut [T];
-   |     -------------------------------------- definition of `renew` from trait
+15 |       fn renew<'b: 'a>(self) -> &'b mut [T];
+   |       -------------------------------------- definition of `renew` from trait
 ...
-19 |     fn renew<'b: 'a>(self) -> &'b mut [T] where 'a: 'b {
-   |     ^ impl has extra requirement `'a: 'b`
+19 |       fn renew<'b: 'a>(self) -> &'b mut [T] where 'a: 'b {
+   |  _____^ starting here...
+20 | |         //~^ ERROR E0276
+21 | |         &mut self[..]
+22 | |     }
+   | |_____^ ...ending here: impl has extra requirement `'a: 'b`
 
 error: aborting due to previous error
 
diff --git a/src/test/ui/compare-method/traits-misc-mismatch-2.stderr b/src/test/ui/compare-method/traits-misc-mismatch-2.stderr
index 5003550fd1e..77b056f6978 100644
--- a/src/test/ui/compare-method/traits-misc-mismatch-2.stderr
+++ b/src/test/ui/compare-method/traits-misc-mismatch-2.stderr
@@ -1,11 +1,15 @@
 error[E0276]: impl has stricter requirements than trait
   --> $DIR/traits-misc-mismatch-2.rs:23:5
    |
-19 |     fn zip<B, U: Iterator<U>>(self, other: U) -> ZipIterator<Self, U>;
-   |     ------------------------------------------------------------------ definition of `zip` from trait
+19 |       fn zip<B, U: Iterator<U>>(self, other: U) -> ZipIterator<Self, U>;
+   |       ------------------------------------------------------------------ definition of `zip` from trait
 ...
-23 |     fn zip<B, U: Iterator<B>>(self, other: U) -> ZipIterator<T, U> {
-   |     ^ impl has extra requirement `U: Iterator<B>`
+23 |       fn zip<B, U: Iterator<B>>(self, other: U) -> ZipIterator<T, U> {
+   |  _____^ starting here...
+24 | |     //~^ ERROR E0276
+25 | |         ZipIterator{a: self, b: other}
+26 | |     }
+   | |_____^ ...ending here: impl has extra requirement `U: Iterator<B>`
 
 error: aborting due to previous error
 
diff --git a/src/test/ui/dropck/dropck-eyepatch-implies-unsafe-impl.stderr b/src/test/ui/dropck/dropck-eyepatch-implies-unsafe-impl.stderr
index c53cf020a9b..b3e72f28d88 100644
--- a/src/test/ui/dropck/dropck-eyepatch-implies-unsafe-impl.stderr
+++ b/src/test/ui/dropck/dropck-eyepatch-implies-unsafe-impl.stderr
@@ -1,14 +1,26 @@
 error[E0569]: requires an `unsafe impl` declaration due to `#[may_dangle]` attribute
   --> $DIR/dropck-eyepatch-implies-unsafe-impl.rs:32:1
    |
-32 | impl<#[may_dangle] A, B: fmt::Debug> Drop for Pt<A, B> {
-   | ^
+32 |   impl<#[may_dangle] A, B: fmt::Debug> Drop for Pt<A, B> {
+   |  _^ starting here...
+33 | |     //~^ ERROR requires an `unsafe impl` declaration due to `#[may_dangle]` attribute
+34 | |
+35 | |     // (unsafe to access self.1  due to #[may_dangle] on A)
+36 | |     fn drop(&mut self) { println!("drop {} {:?}", self.0, self.2); }
+37 | | }
+   | |_^ ..ending here
 
 error[E0569]: requires an `unsafe impl` declaration due to `#[may_dangle]` attribute
   --> $DIR/dropck-eyepatch-implies-unsafe-impl.rs:38:1
    |
-38 | impl<#[may_dangle] 'a, 'b, B: fmt::Debug> Drop for Pr<'a, 'b, B> {
-   | ^
+38 |   impl<#[may_dangle] 'a, 'b, B: fmt::Debug> Drop for Pr<'a, 'b, B> {
+   |  _^ starting here...
+39 | |     //~^ ERROR requires an `unsafe impl` declaration due to `#[may_dangle]` attribute
+40 | |
+41 | |     // (unsafe to access self.1 due to #[may_dangle] on 'a)
+42 | |     fn drop(&mut self) { println!("drop {} {:?}", self.0, self.2); }
+43 | | }
+   | |_^ ..ending here
 
 error: aborting due to 2 previous errors
 
diff --git a/src/test/ui/lifetimes/consider-using-explicit-lifetime.stderr b/src/test/ui/lifetimes/consider-using-explicit-lifetime.stderr
index 353e251369a..1d1bc58805a 100644
--- a/src/test/ui/lifetimes/consider-using-explicit-lifetime.stderr
+++ b/src/test/ui/lifetimes/consider-using-explicit-lifetime.stderr
@@ -15,8 +15,11 @@ error[E0495]: cannot infer an appropriate lifetime due to conflicting requiremen
 help: consider using an explicit lifetime parameter as shown: fn from_str(path: &'a str) -> Result<Self, ()>
   --> $DIR/consider-using-explicit-lifetime.rs:25:5
    |
-25 |     fn from_str(path: &str) -> Result<Self, ()> {
-   |     ^
+25 |       fn from_str(path: &str) -> Result<Self, ()> {
+   |  _____^ starting here...
+26 | |         Ok(Foo { field: path })
+27 | |     }
+   | |_____^ ..ending here
 
 error: aborting due to 2 previous errors
 
diff --git a/src/test/ui/mismatched_types/main.stderr b/src/test/ui/mismatched_types/main.stderr
index 9e26be6fddd..c87b635521e 100644
--- a/src/test/ui/mismatched_types/main.stderr
+++ b/src/test/ui/mismatched_types/main.stderr
@@ -1,8 +1,10 @@
 error[E0308]: mismatched types
   --> $DIR/main.rs:12:18
    |
-12 |     let x: u32 = (
-   |                  ^ expected u32, found ()
+12 |       let x: u32 = (
+   |  __________________^ starting here...
+13 | |     );
+   | |_____^ ...ending here: expected u32, found ()
    |
    = note: expected type `u32`
    = note:    found type `()`
diff --git a/src/test/ui/missing-items/m2.stderr b/src/test/ui/missing-items/m2.stderr
index caeb9ff415c..33135434544 100644
--- a/src/test/ui/missing-items/m2.stderr
+++ b/src/test/ui/missing-items/m2.stderr
@@ -3,8 +3,10 @@ error: main function not found
 error[E0046]: not all trait items implemented, missing: `CONSTANT`, `Type`, `method`
   --> $DIR/m2.rs:20:1
    |
-20 | impl m1::X for X {
-   | ^ missing `CONSTANT`, `Type`, `method` in implementation
+20 |   impl m1::X for X {
+   |  _^ starting here...
+21 | | }
+   | |_^ ...ending here: missing `CONSTANT`, `Type`, `method` in implementation
    |
    = note: `CONSTANT` from trait: `const CONSTANT: u32;`
    = note: `Type` from trait: `type Type;`
diff --git a/src/test/ui/span/impl-wrong-item-for-trait.stderr b/src/test/ui/span/impl-wrong-item-for-trait.stderr
index 244285e3584..5c352436c3e 100644
--- a/src/test/ui/span/impl-wrong-item-for-trait.stderr
+++ b/src/test/ui/span/impl-wrong-item-for-trait.stderr
@@ -10,11 +10,19 @@ error[E0323]: item `bar` is an associated const, which doesn't match its trait `
 error[E0046]: not all trait items implemented, missing: `bar`
   --> $DIR/impl-wrong-item-for-trait.rs:22:1
    |
-16 |     fn bar(&self);
-   |     -------------- `bar` from trait
+16 |       fn bar(&self);
+   |       -------------- `bar` from trait
 ...
-22 | impl Foo for FooConstForMethod {
-   | ^ missing `bar` in implementation
+22 |   impl Foo for FooConstForMethod {
+   |  _^ starting here...
+23 | |     //~^ ERROR E0046
+24 | |     //~| NOTE missing `bar` in implementation
+25 | |     const bar: u64 = 1;
+26 | |     //~^ ERROR E0323
+27 | |     //~| NOTE does not match trait
+28 | |     const MY_CONST: u32 = 1;
+29 | | }
+   | |_^ ...ending here: missing `bar` in implementation
 
 error[E0324]: item `MY_CONST` is an associated method, which doesn't match its trait `<FooMethodForConst as Foo>`
   --> $DIR/impl-wrong-item-for-trait.rs:37:5
@@ -28,11 +36,19 @@ error[E0324]: item `MY_CONST` is an associated method, which doesn't match its t
 error[E0046]: not all trait items implemented, missing: `MY_CONST`
   --> $DIR/impl-wrong-item-for-trait.rs:33:1
    |
-17 |     const MY_CONST: u32;
-   |     -------------------- `MY_CONST` from trait
+17 |       const MY_CONST: u32;
+   |       -------------------- `MY_CONST` from trait
 ...
-33 | impl Foo for FooMethodForConst {
-   | ^ missing `MY_CONST` in implementation
+33 |   impl Foo for FooMethodForConst {
+   |  _^ starting here...
+34 | |     //~^ ERROR E0046
+35 | |     //~| NOTE missing `MY_CONST` in implementation
+36 | |     fn bar(&self) {}
+37 | |     fn MY_CONST() {}
+38 | |     //~^ ERROR E0324
+39 | |     //~| NOTE does not match trait
+40 | | }
+   | |_^ ...ending here: missing `MY_CONST` in implementation
 
 error[E0325]: item `bar` is an associated type, which doesn't match its trait `<FooTypeForMethod as Foo>`
   --> $DIR/impl-wrong-item-for-trait.rs:47:5
@@ -46,17 +62,27 @@ error[E0325]: item `bar` is an associated type, which doesn't match its trait `<
 error[E0046]: not all trait items implemented, missing: `bar`
   --> $DIR/impl-wrong-item-for-trait.rs:44:1
    |
-16 |     fn bar(&self);
-   |     -------------- `bar` from trait
+16 |       fn bar(&self);
+   |       -------------- `bar` from trait
 ...
-44 | impl Foo for FooTypeForMethod {
-   | ^ missing `bar` in implementation
+44 |   impl Foo for FooTypeForMethod {
+   |  _^ starting here...
+45 | |     //~^ ERROR E0046
+46 | |     //~| NOTE missing `bar` in implementation
+47 | |     type bar = u64;
+48 | |     //~^ ERROR E0325
+49 | |     //~| NOTE does not match trait
+50 | |     const MY_CONST: u32 = 1;
+51 | | }
+   | |_^ ...ending here: missing `bar` in implementation
 
 error[E0046]: not all trait items implemented, missing: `fmt`
   --> $DIR/impl-wrong-item-for-trait.rs:53:1
    |
-53 | impl Debug for FooTypeForMethod {
-   | ^ missing `fmt` in implementation
+53 |   impl Debug for FooTypeForMethod {
+   |  _^ starting here...
+54 | | }
+   | |_^ ...ending here: missing `fmt` in implementation
    |
    = note: `fmt` from trait: `fn(&Self, &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error>`
 
diff --git a/src/test/ui/span/issue-23827.stderr b/src/test/ui/span/issue-23827.stderr
index 5130bb53a19..6c1c2467530 100644
--- a/src/test/ui/span/issue-23827.stderr
+++ b/src/test/ui/span/issue-23827.stderr
@@ -1,8 +1,16 @@
 error[E0046]: not all trait items implemented, missing: `Output`
   --> $DIR/issue-23827.rs:36:1
    |
-36 | impl<C: Component> FnOnce<(C,)> for Prototype {
-   | ^ missing `Output` in implementation
+36 |   impl<C: Component> FnOnce<(C,)> for Prototype {
+   |  _^ starting here...
+37 | |     //~^ ERROR E0046
+38 | |     //~| NOTE missing `Output` in implementation
+39 | |     //~| NOTE `Output` from trait: `type Output;`
+40 | |     extern "rust-call" fn call_once(self, (comp,): (C,)) -> Prototype {
+41 | |         Fn::call(&self, (comp,))
+42 | |     }
+43 | | }
+   | |_^ ...ending here: missing `Output` in implementation
    |
    = note: `Output` from trait: `type Output;`
 
diff --git a/src/test/ui/span/issue-24356.stderr b/src/test/ui/span/issue-24356.stderr
index 906ef25ca0e..963f4bd9bbc 100644
--- a/src/test/ui/span/issue-24356.stderr
+++ b/src/test/ui/span/issue-24356.stderr
@@ -1,8 +1,14 @@
 error[E0046]: not all trait items implemented, missing: `Target`
   --> $DIR/issue-24356.rs:30:9
    |
-30 |         impl Deref for Thing {
-   |         ^ missing `Target` in implementation
+30 |           impl Deref for Thing {
+   |  _________^ starting here...
+31 | |             //~^ ERROR E0046
+32 | |             //~| NOTE missing `Target` in implementation
+33 | |             //~| NOTE `Target` from trait: `type Target;`
+34 | |             fn deref(&self) -> i8 { self.0 }
+35 | |         }
+   | |_________^ ...ending here: missing `Target` in implementation
    |
    = note: `Target` from trait: `type Target;`
 
diff --git a/src/test/ui/span/multiline-span-simple.rs b/src/test/ui/span/multiline-span-simple.rs
new file mode 100644
index 00000000000..16414766f39
--- /dev/null
+++ b/src/test/ui/span/multiline-span-simple.rs
@@ -0,0 +1,30 @@
+// Copyright 2016 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+fn foo(a: u32, b: u32) {
+    a + b;
+}
+
+fn bar(a: u32, b: u32) {
+    a + b;
+}
+
+fn main() {
+    let x = 1;
+    let y = 2;
+    let z = 3;
+    foo(1 +
+
+        bar(x,
+
+            y),
+
+        z)
+}
diff --git a/src/test/ui/span/multiline-span-simple.stderr b/src/test/ui/span/multiline-span-simple.stderr
new file mode 100644
index 00000000000..26acef64c89
--- /dev/null
+++ b/src/test/ui/span/multiline-span-simple.stderr
@@ -0,0 +1,20 @@
+error[E0277]: the trait bound `{integer}: std::ops::Add<()>` is not satisfied
+  --> $DIR/multiline-span-simple.rs:23:9
+   |
+23 |       foo(1 +
+   |  _________^ starting here...
+24 | |
+25 | |         bar(x,
+26 | |
+27 | |             y),
+   | |______________^ ...ending here: the trait `std::ops::Add<()>` is not implemented for `{integer}`
+   |
+   = help: the following implementations were found:
+   = help:   <u32 as std::ops::Add>
+   = help:   <&'a u32 as std::ops::Add<u32>>
+   = help:   <u32 as std::ops::Add<&'a u32>>
+   = help:   <&'b u32 as std::ops::Add<&'a u32>>
+   = help: and 90 others
+
+error: aborting due to previous error
+