about summary refs log tree commit diff
path: root/src/libsyntax/errors
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2016-05-02 19:21:56 -0700
committerbors <bors@rust-lang.org>2016-05-02 19:21:56 -0700
commit44b3cd8c462a420ab64a44ef8f70c007001a1f44 (patch)
treeafca36813604190e06b0f5a6e01e4389ce796be1 /src/libsyntax/errors
parent9a003b0ef22090e8be5b2705a427d6b08b06eaf9 (diff)
parent9355a91224a6f715b94342c074e5bac1f9e820f3 (diff)
downloadrust-44b3cd8c462a420ab64a44ef8f70c007001a1f44.tar.gz
rust-44b3cd8c462a420ab64a44ef8f70c007001a1f44.zip
Auto merge of #32756 - nikomatsakis:borrowck-snippet, r=nrc
Overhaul borrowck error messages and compiler error formatting generally

This is a major overhaul of how the compiler reports errors. The primary goal is to be able to give many spans within the same overall context, such as this:

```
./borrow-errors.rs:73:17: 73:20: error: cannot borrow `*vec` as immutable because previous closure requires unique access [E0501]
70     let append = |e| {
                    ~~~ closure construction occurs here
71         vec.push(e)
           ~~~ previous borrow occurs due to use of `vec` in closure
72     };
73     let data = &vec[3];
                   ~~~ borrow occurs here
74 }
   ~ borrow from closure ends here
```

However, in the process we made a number of other changes:

- Removed the repetitive filenames from snippets and just give the line number.
- Color the line numbers blue so they "fade away"
- Remove the file name and line number from the error code suggestions since they don't seem to fit anymore. (This should probably happen in more places, like existing notes.)
- Newlines in between errors to help group them better.

This PR is not quite ready to land, but we thought it made sense to stop here and get some feedback from people at large. It'd be great if people can check out the branch and play with it. We'd be especially interested in hearing about cases that don't look good with the new formatting (I suspect they exist).

Here is a checklist of some pending work items for this PR. Some of them may be best left for follow-up PRs:

- [x] Accommodate multiple files in a `MultiSpan` (this should be easy)
  - In this case, we want to print filenames though.
- [x] Remove duplicate E0500 code.
- [x] Make the header message bold, rather than current hack that makes all errors/warnings bold
- [x] Update warning text color (yellow is hard to read w/ a white background)

Moved numerous follow-ups to: https://github.com/rust-lang/rust/issues/33240

Joint work with @jonathandturner.

Fixes https://github.com/rust-lang/rust/issues/3533
Diffstat (limited to 'src/libsyntax/errors')
-rw-r--r--src/libsyntax/errors/emitter.rs929
-rw-r--r--src/libsyntax/errors/json.rs204
-rw-r--r--src/libsyntax/errors/mod.rs204
-rw-r--r--src/libsyntax/errors/snippet/mod.rs821
-rw-r--r--src/libsyntax/errors/snippet/test.rs521
5 files changed, 1892 insertions, 787 deletions
diff --git a/src/libsyntax/errors/emitter.rs b/src/libsyntax/errors/emitter.rs
index 0b5234769b2..486e2ace087 100644
--- a/src/libsyntax/errors/emitter.rs
+++ b/src/libsyntax/errors/emitter.rs
@@ -13,9 +13,11 @@ use self::Destination::*;
 use codemap::{self, COMMAND_LINE_SP, DUMMY_SP, Pos, Span, MultiSpan};
 use diagnostics;
 
+use errors::check_old_skool;
 use errors::{Level, RenderSpan, CodeSuggestion, DiagnosticBuilder};
 use errors::RenderSpan::*;
 use errors::Level::*;
+use errors::snippet::{RenderedLineKind, SnippetData, Style};
 
 use std::{cmp, fmt};
 use std::io::prelude::*;
@@ -23,17 +25,73 @@ use std::io;
 use std::rc::Rc;
 use term;
 
+/// Emitter trait for emitting errors. Do not implement this directly:
+/// implement `CoreEmitter` instead.
 pub trait Emitter {
-    fn emit(&mut self, span: Option<&MultiSpan>, msg: &str, code: Option<&str>, lvl: Level);
-    fn custom_emit(&mut self, sp: &RenderSpan, msg: &str, lvl: Level);
+    /// Emit a standalone diagnostic message.
+    fn emit(&mut self, span: &MultiSpan, msg: &str, code: Option<&str>, lvl: Level);
 
     /// Emit a structured diagnostic.
+    fn emit_struct(&mut self, db: &DiagnosticBuilder);
+}
+
+pub trait CoreEmitter {
+    fn emit_message(&mut self,
+                    rsp: &RenderSpan,
+                    msg: &str,
+                    code: Option<&str>,
+                    lvl: Level,
+                    is_header: bool,
+                    show_snippet: bool);
+}
+
+impl<T: CoreEmitter> Emitter for T {
+    fn emit(&mut self,
+            msp: &MultiSpan,
+            msg: &str,
+            code: Option<&str>,
+            lvl: Level) {
+        self.emit_message(&FullSpan(msp.clone()),
+                          msg,
+                          code,
+                          lvl,
+                          true,
+                          true);
+    }
+
     fn emit_struct(&mut self, db: &DiagnosticBuilder) {
-        self.emit(db.span.as_ref(), &db.message, db.code.as_ref().map(|s| &**s), db.level);
+        let old_school = check_old_skool();
+        let db_span = FullSpan(db.span.clone());
+        self.emit_message(&FullSpan(db.span.clone()),
+                          &db.message,
+                          db.code.as_ref().map(|s| &**s),
+                          db.level,
+                          true,
+                          true);
         for child in &db.children {
-            match child.render_span {
-                Some(ref sp) => self.custom_emit(sp, &child.message, child.level),
-                None => self.emit(child.span.as_ref(), &child.message, None, child.level),
+            let render_span = child.render_span
+                                   .clone()
+                                   .unwrap_or_else(
+                                       || FullSpan(child.span.clone()));
+
+            if !old_school {
+                self.emit_message(&render_span,
+                                    &child.message,
+                                    None,
+                                    child.level,
+                                    false,
+                                    true);
+            } else {
+                let (render_span, show_snippet) = match render_span.span().primary_span() {
+                    None => (db_span.clone(), false),
+                    _ => (render_span, true)
+                };
+                self.emit_message(&render_span,
+                                    &child.message,
+                                    None,
+                                    child.level,
+                                    false,
+                                    show_snippet);
             }
         }
     }
@@ -42,9 +100,6 @@ pub trait Emitter {
 /// maximum number of lines we will print for each error; arbitrary.
 pub const MAX_HIGHLIGHT_LINES: usize = 6;
 
-/// maximum number of lines we will print for each span; arbitrary.
-const MAX_SP_LINES: usize = 6;
-
 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
 pub enum ColorConfig {
     Auto,
@@ -68,21 +123,18 @@ pub struct BasicEmitter {
     dst: Destination,
 }
 
-impl Emitter for BasicEmitter {
-    fn emit(&mut self,
-            msp: Option<&MultiSpan>,
-            msg: &str,
-            code: Option<&str>,
-            lvl: Level) {
-        assert!(msp.is_none(), "BasicEmitter can't handle spans");
+impl CoreEmitter for BasicEmitter {
+    fn emit_message(&mut self,
+                    _rsp: &RenderSpan,
+                    msg: &str,
+                    code: Option<&str>,
+                    lvl: Level,
+                    _is_header: bool,
+                    _show_snippet: bool) {
+        // we ignore the span as we have no access to a codemap at this point
         if let Err(e) = print_diagnostic(&mut self.dst, "", lvl, msg, code) {
             panic!("failed to print diagnostics: {:?}", e);
         }
-
-    }
-
-    fn custom_emit(&mut self, _: &RenderSpan, _: &str, _: Level) {
-        panic!("BasicEmitter can't handle custom_emit");
     }
 }
 
@@ -101,33 +153,26 @@ pub struct EmitterWriter {
     dst: Destination,
     registry: Option<diagnostics::registry::Registry>,
     cm: Rc<codemap::CodeMap>,
-}
 
-impl Emitter for EmitterWriter {
-    fn emit(&mut self,
-            msp: Option<&MultiSpan>,
-            msg: &str,
-            code: Option<&str>,
-            lvl: Level) {
-        let error = match msp.map(|s|(s.to_span_bounds(), s)) {
-            Some((COMMAND_LINE_SP, msp)) => {
-                self.emit_(&FileLine(msp.clone()), msg, code, lvl)
-            },
-            Some((DUMMY_SP, _)) | None => print_diagnostic(&mut self.dst, "", lvl, msg, code),
-            Some((_, msp)) => self.emit_(&FullSpan(msp.clone()), msg, code, lvl),
-        };
+    /// Is this the first error emitted thus far? If not, we emit a
+    /// `\n` before the top-level errors.
+    first: bool,
 
-        if let Err(e) = error {
-            panic!("failed to print diagnostics: {:?}", e);
-        }
-    }
+    // For now, allow an old-school mode while we transition
+    old_school: bool,
+}
 
-    fn custom_emit(&mut self,
-                   rsp: &RenderSpan,
-                   msg: &str,
-                   lvl: Level) {
-        if let Err(e) = self.emit_(rsp, msg, None, lvl) {
-            panic!("failed to print diagnostics: {:?}", e);
+impl CoreEmitter for EmitterWriter {
+    fn emit_message(&mut self,
+                    rsp: &RenderSpan,
+                    msg: &str,
+                    code: Option<&str>,
+                    lvl: Level,
+                    is_header: bool,
+                    show_snippet: bool) {
+        match self.emit_message_(rsp, msg, code, lvl, is_header, show_snippet) {
+            Ok(()) => { }
+            Err(e) => panic!("failed to emit error: {}", e)
         }
     }
 }
@@ -151,11 +196,20 @@ impl EmitterWriter {
                   registry: Option<diagnostics::registry::Registry>,
                   code_map: Rc<codemap::CodeMap>)
                   -> EmitterWriter {
+        let old_school = check_old_skool();
         if color_config.use_color() {
             let dst = Destination::from_stderr();
-            EmitterWriter { dst: dst, registry: registry, cm: code_map }
+            EmitterWriter { dst: dst,
+                            registry: registry,
+                            cm: code_map,
+                            first: true,
+                            old_school: old_school }
         } else {
-            EmitterWriter { dst: Raw(Box::new(io::stderr())), registry: registry, cm: code_map }
+            EmitterWriter { dst: Raw(Box::new(io::stderr())),
+                            registry: registry,
+                            cm: code_map,
+                            first: true,
+                            old_school: old_school }
         }
     }
 
@@ -163,53 +217,108 @@ impl EmitterWriter {
                registry: Option<diagnostics::registry::Registry>,
                code_map: Rc<codemap::CodeMap>)
                -> EmitterWriter {
-        EmitterWriter { dst: Raw(dst), registry: registry, cm: code_map }
-    }
+        let old_school = check_old_skool();
+        EmitterWriter { dst: Raw(dst),
+                        registry: registry,
+                        cm: code_map,
+                        first: true,
+                        old_school: old_school }
+    }
+
+    fn emit_message_(&mut self,
+                     rsp: &RenderSpan,
+                     msg: &str,
+                     code: Option<&str>,
+                     lvl: Level,
+                     is_header: bool,
+                     show_snippet: bool)
+                     -> io::Result<()> {
+        if is_header {
+            if self.first {
+                self.first = false;
+            } else {
+                if !self.old_school {
+                    write!(self.dst, "\n")?;
+                }
+            }
+        }
 
-    fn emit_(&mut self,
-             rsp: &RenderSpan,
-             msg: &str,
-             code: Option<&str>,
-             lvl: Level)
-             -> io::Result<()> {
-        let msp = rsp.span();
-        let bounds = msp.to_span_bounds();
-
-        let ss = if bounds == COMMAND_LINE_SP {
-            "<command line option>".to_string()
-        } else if let EndSpan(_) = *rsp {
-            let span_end = Span { lo: bounds.hi, hi: bounds.hi, expn_id: bounds.expn_id};
-            self.cm.span_to_string(span_end)
-        } else {
-            self.cm.span_to_string(bounds)
-        };
+        match code {
+            Some(code) if self.registry.as_ref()
+                                       .and_then(|registry| registry.find_description(code))
+                                       .is_some() => {
+                let code_with_explain = String::from("--explain ") + code;
+                if self.old_school {
+                    let loc = match rsp.span().primary_span() {
+                        Some(COMMAND_LINE_SP) | Some(DUMMY_SP) => "".to_string(),
+                        Some(ps) => self.cm.span_to_string(ps),
+                        None => "".to_string()
+                    };
+                    print_diagnostic(&mut self.dst, &loc, lvl, msg, Some(code))?
+                }
+                else {
+                    print_diagnostic(&mut self.dst, "", lvl, msg, Some(&code_with_explain))?
+                }
+            }
+            _ => {
+                if self.old_school {
+                    let loc = match rsp.span().primary_span() {
+                        Some(COMMAND_LINE_SP) | Some(DUMMY_SP) => "".to_string(),
+                        Some(ps) => self.cm.span_to_string(ps),
+                        None => "".to_string()
+                    };
+                    print_diagnostic(&mut self.dst, &loc, lvl, msg, code)?
+                }
+                else {
+                    print_diagnostic(&mut self.dst, "", lvl, msg, code)?
+                }
+            }
+        }
 
-        print_diagnostic(&mut self.dst, &ss[..], lvl, msg, code)?;
+        if !show_snippet {
+            return Ok(());
+        }
 
+        // Watch out for various nasty special spans; don't try to
+        // print any filename or anything for those.
+        match rsp.span().primary_span() {
+            Some(COMMAND_LINE_SP) | Some(DUMMY_SP) => {
+                return Ok(());
+            }
+            _ => { }
+        }
+
+        // Otherwise, print out the snippet etc as needed.
         match *rsp {
-            FullSpan(_) => {
+            FullSpan(ref msp) => {
                 self.highlight_lines(msp, lvl)?;
-                self.print_macro_backtrace(bounds)?;
-            }
-            EndSpan(_) => {
-                self.end_highlight_lines(msp, lvl)?;
-                self.print_macro_backtrace(bounds)?;
+                if let Some(primary_span) = msp.primary_span() {
+                    self.print_macro_backtrace(primary_span)?;
+                }
             }
             Suggestion(ref suggestion) => {
                 self.highlight_suggestion(suggestion)?;
-                self.print_macro_backtrace(bounds)?;
-            }
-            FileLine(..) => {
-                // no source text in this case!
+                if let Some(primary_span) = rsp.span().primary_span() {
+                    self.print_macro_backtrace(primary_span)?;
+                }
             }
         }
-
-        if let Some(code) = code {
-            if let Some(_) = self.registry.as_ref()
-                                          .and_then(|registry| registry.find_description(code)) {
-                print_diagnostic(&mut self.dst, &ss[..], Help,
-                                 &format!("run `rustc --explain {}` to see a \
-                                           detailed explanation", code), None)?;
+        if self.old_school {
+            match code {
+                Some(code) if self.registry.as_ref()
+                                        .and_then(|registry| registry.find_description(code))
+                                        .is_some() => {
+                    let loc = match rsp.span().primary_span() {
+                        Some(COMMAND_LINE_SP) | Some(DUMMY_SP) => "".to_string(),
+                        Some(ps) => self.cm.span_to_string(ps),
+                        None => "".to_string()
+                    };
+                    let msg = "run `rustc --explain ".to_string() + &code.to_string() +
+                        "` to see a detailed explanation";
+                    print_diagnostic(&mut self.dst, &loc, Level::Help, &msg,
+                        None)?
+                }
+                _ => ()
             }
         }
         Ok(())
@@ -217,7 +326,8 @@ impl EmitterWriter {
 
     fn highlight_suggestion(&mut self, suggestion: &CodeSuggestion) -> io::Result<()>
     {
-        let lines = self.cm.span_to_lines(suggestion.msp.to_span_bounds()).unwrap();
+        let primary_span = suggestion.msp.primary_span().unwrap();
+        let lines = self.cm.span_to_lines(primary_span).unwrap();
         assert!(!lines.lines.is_empty());
 
         let complete = suggestion.splice_lines(&self.cm);
@@ -251,325 +361,52 @@ impl EmitterWriter {
                        lvl: Level)
                        -> io::Result<()>
     {
-        let lines = match self.cm.span_to_lines(msp.to_span_bounds()) {
-            Ok(lines) => lines,
-            Err(_) => {
-                write!(&mut self.dst, "(internal compiler error: unprintable span)\n")?;
-                return Ok(());
-            }
-        };
-
-        let fm = &*lines.file;
-        if let None = fm.src {
-            return Ok(());
-        }
-
-        let display_line_infos = &lines.lines[..];
-        assert!(display_line_infos.len() > 0);
-
-        // Calculate the widest number to format evenly and fix #11715
-        let digits = line_num_max_digits(display_line_infos.last().unwrap());
-        let first_line_index = display_line_infos.first().unwrap().line_index;
-
-        let skip = fm.name.chars().count() + digits + 2;
-
-        let mut spans = msp.spans.iter().peekable();
-        let mut lines = display_line_infos.iter();
-        let mut prev_line_index = first_line_index.wrapping_sub(1);
-
-        // Display at most MAX_HIGHLIGHT_LINES lines.
-        let mut remaining_err_lines = MAX_HIGHLIGHT_LINES;
-
-        // To emit a overflowed spans code-lines *AFTER* the rendered spans
-        let mut overflowed_buf = String::new();
-        let mut overflowed = false;
-
-        // FIXME (#8706)
-        'l: loop {
-            if remaining_err_lines <= 0 {
-                break;
-            }
-            let line = match lines.next() {
-                Some(l) => l,
-                None => break,
-            };
-
-            // Skip is the number of characters we need to skip because they are
-            // part of the 'filename:line ' part of the code line.
-            let mut s: String = ::std::iter::repeat(' ').take(skip).collect();
-            let mut col = skip;
-            let mut lastc = ' ';
-
-            let cur_line_str = fm.get_line(line.line_index).unwrap();
-            let mut line_chars = cur_line_str.chars().enumerate().peekable();
-            let mut line_spans = 0;
-
-            // Assemble spans for this line
-            loop {
-                // Peek here to preserve the span if it doesn't belong to this line
-                let sp = match spans.peek() {
-                    Some(sp) => **sp,
-                    None => break,
-                };
-                let lo = self.cm.lookup_char_pos(sp.lo);
-                let hi = self.cm.lookup_char_pos(sp.hi);
-                let line_num = line.line_index + 1;
-
-                if !(lo.line <= line_num && hi.line >= line_num) {
-                    // This line is not contained in the span
-                    if overflowed {
-                        // Never elide the final line of an overflowed span
-                        prev_line_index = line.line_index - 1;
-                        overflowed = false;
-                        break;
-                    }
-
-                    if line_spans == 0 {
-                        continue 'l;
-                    } else {
-                        // This line is finished, now render the spans we've assembled
-                        break;
-                    }
+        let mut snippet_data = SnippetData::new(self.cm.clone(),
+                                                msp.primary_span());
+        if self.old_school {
+            let mut output_vec = vec![];
+
+            for span_label in msp.span_labels() {
+                let mut snippet_data = snippet_data.clone();
+
+                snippet_data.push(span_label.span,
+                                  span_label.is_primary,
+                                  span_label.label);
+                if span_label.is_primary {
+                    output_vec.insert(0, snippet_data);
                 }
-                spans.next();
-                line_spans += 1;
-
-                if lo.line != hi.line {
-                    // Assemble extra code lines to be emitted after this lines spans
-                    // (substract `2` because the first and last line are rendered normally)
-                    let max_lines = cmp::min(remaining_err_lines, MAX_SP_LINES) - 2;
-                    prev_line_index = line.line_index;
-                    let count = cmp::min((hi.line - lo.line - 1), max_lines);
-                    for _ in 0..count {
-                        let line = match lines.next() {
-                            Some(l) => l,
-                            None => break,
-                        };
-                        let line_str = fm.get_line(line.line_index).unwrap();
-                        overflowed_buf.push_str(&format!("{}:{:>width$} {}\n",
-                                                       fm.name,
-                                                       line.line_index + 1,
-                                                       line_str,
-                                                       width=digits));
-                        remaining_err_lines -= 1;
-                        prev_line_index += 1
-                    }
-                    // Remember that the span overflowed to ensure
-                    // that we emit its last line exactly once
-                    // (other spans may, or may not, start on it)
-                    overflowed = true;
-                    break;
-                }
-
-                for (pos, ch) in line_chars.by_ref() {
-                    lastc = ch;
-                    if pos >= lo.col.to_usize() { break; }
-                    // Whenever a tab occurs on the code line, we insert one on
-                    // the error-point-squiggly-line as well (instead of a space).
-                    // That way the squiggly line will usually appear in the correct
-                    // position.
-                    match ch {
-                        '\t' => {
-                            col += 8 - col%8;
-                            s.push('\t');
-                        },
-                        _ => {
-                            col += 1;
-                            s.push(' ');
-                        },
-                    }
+                else {
+                    output_vec.push(snippet_data);
                 }
+            }
 
-                s.push('^');
-                let col_ptr = col;
-                let count = match lastc {
-                    // Most terminals have a tab stop every eight columns by default
-                    '\t' => 8 - col%8,
-                    _ => 1,
-                };
-                col += count;
-                s.extend(::std::iter::repeat('~').take(count));
-
-                let hi = self.cm.lookup_char_pos(sp.hi);
-                if hi.col != lo.col {
-                    let mut chars = line_chars.by_ref();
-                    loop {
-                        // We peek here to preserve the value for the next span
-                        let (pos, ch) = match chars.peek() {
-                            Some(elem) => *elem,
-                            None => break,
-                        };
-                        if pos >= hi.col.to_usize() { break; }
-                        let count = match ch {
-                            '\t' => 8 - col%8,
-                            _ => 1,
-                        };
-                        col += count;
-                        s.extend(::std::iter::repeat('~').take(count));
-
-                        chars.next();
+            for snippet_data in output_vec.iter() {
+                let rendered_lines = snippet_data.render_lines();
+                for rendered_line in &rendered_lines {
+                    for styled_string in &rendered_line.text {
+                        self.dst.apply_style(lvl, &rendered_line.kind, styled_string.style)?;
+                        write!(&mut self.dst, "{}", styled_string.text)?;
+                        self.dst.reset_attrs()?;
                     }
+                    write!(&mut self.dst, "\n")?;
                 }
-                if (col - col_ptr) > 0 {
-                    // One extra squiggly is replaced by a "^"
-                    s.pop();
-                }
-            }
-
-            // If we elided something put an ellipsis.
-            if prev_line_index != line.line_index.wrapping_sub(1) && !overflowed {
-                write!(&mut self.dst, "{0:1$}...\n", "", skip)?;
-            }
-
-            // Print offending code-line
-            remaining_err_lines -= 1;
-            write!(&mut self.dst, "{}:{:>width$} {}\n",
-                   fm.name,
-                   line.line_index + 1,
-                   cur_line_str,
-                   width=digits)?;
-
-            if s.len() > skip {
-                // Render the spans we assembled previously (if any).
-                println_maybe_styled!(&mut self.dst, term::Attr::ForegroundColor(lvl.color()),
-                                      "{}", s)?;
-            }
-
-            if !overflowed_buf.is_empty() {
-                // Print code-lines trailing the rendered spans (when a span overflows)
-                write!(&mut self.dst, "{}", &overflowed_buf)?;
-                overflowed_buf.clear();
-            } else {
-                prev_line_index = line.line_index;
             }
         }
-
-        // If we elided something, put an ellipsis.
-        if lines.next().is_some() {
-            write!(&mut self.dst, "{0:1$}...\n", "", skip)?;
-        }
-        Ok(())
-    }
-
-    /// Here are the differences between this and the normal `highlight_lines`:
-    /// `end_highlight_lines` will always put arrow on the last byte of each
-    /// span (instead of the first byte). Also, when a span is too long (more
-    /// than 6 lines), `end_highlight_lines` will print the first line, then
-    /// dot dot dot, then last line, whereas `highlight_lines` prints the first
-    /// six lines.
-    #[allow(deprecated)]
-    fn end_highlight_lines(&mut self,
-                           msp: &MultiSpan,
-                           lvl: Level)
-                          -> io::Result<()> {
-        let lines = match self.cm.span_to_lines(msp.to_span_bounds()) {
-            Ok(lines) => lines,
-            Err(_) => {
-                write!(&mut self.dst, "(internal compiler error: unprintable span)\n")?;
-                return Ok(());
+        else {
+            for span_label in msp.span_labels() {
+                snippet_data.push(span_label.span,
+                                  span_label.is_primary,
+                                  span_label.label);
             }
-        };
-
-        let fm = &*lines.file;
-        if let None = fm.src {
-            return Ok(());
-        }
-
-        let lines = &lines.lines[..];
-
-        // Calculate the widest number to format evenly
-        let first_line = lines.first().unwrap();
-        let last_line = lines.last().unwrap();
-        let digits = line_num_max_digits(last_line);
-
-        let skip = fm.name.chars().count() + digits + 2;
-
-        let mut spans = msp.spans.iter().peekable();
-        let mut lines = lines.iter();
-        let mut prev_line_index = first_line.line_index.wrapping_sub(1);
-
-        // Display at most MAX_HIGHLIGHT_LINES lines.
-        let mut remaining_err_lines = MAX_HIGHLIGHT_LINES;
-
-        'l: loop {
-            if remaining_err_lines <= 0 {
-                break;
-            }
-            let line = match lines.next() {
-                Some(line) => line,
-                None => break,
-            };
-
-            // Skip is the number of characters we need to skip because they are
-            // part of the 'filename:line ' part of the previous line.
-            let mut s: String = ::std::iter::repeat(' ').take(skip).collect();
-
-            let line_str = fm.get_line(line.line_index).unwrap();
-            let mut line_chars = line_str.chars().enumerate();
-            let mut line_spans = 0;
-
-            loop {
-                // Peek here to preserve the span if it doesn't belong to this line
-                let sp = match spans.peek() {
-                    Some(sp) => **sp,
-                    None => break,
-                };
-                let lo = self.cm.lookup_char_pos(sp.lo);
-                let hi = self.cm.lookup_char_pos(sp.hi);
-                let elide_sp = (hi.line - lo.line) >= MAX_SP_LINES;
-
-                let line_num = line.line_index + 1;
-                if !(lo.line <= line_num && hi.line >= line_num) {
-                    // This line is not contained in the span
-                    if line_spans == 0 {
-                        continue 'l;
-                    } else {
-                        // This line is finished, now render the spans we've assembled
-                        break
-                    }
-                } else if hi.line > line_num {
-                    if elide_sp && lo.line < line_num {
-                        // This line is inbetween the first and last line of the span,
-                        // so we may want to elide it.
-                        continue 'l;
-                    } else {
-                        break
-                    }
-                }
-                line_spans += 1;
-                spans.next();
-
-                for (pos, ch) in line_chars.by_ref() {
-                    // Span seems to use half-opened interval, so subtract 1
-                    if pos >= hi.col.to_usize() - 1 { break; }
-                    // Whenever a tab occurs on the previous line, we insert one on
-                    // the error-point-squiggly-line as well (instead of a space).
-                    // That way the squiggly line will usually appear in the correct
-                    // position.
-                    match ch {
-                        '\t' => s.push('\t'),
-                        _ => s.push(' '),
-                    }
+            let rendered_lines = snippet_data.render_lines();
+            for rendered_line in &rendered_lines {
+                for styled_string in &rendered_line.text {
+                    self.dst.apply_style(lvl, &rendered_line.kind, styled_string.style)?;
+                    write!(&mut self.dst, "{}", styled_string.text)?;
+                    self.dst.reset_attrs()?;
                 }
-                s.push('^');
-            }
-
-            if prev_line_index != line.line_index.wrapping_sub(1) {
-                // If we elided something, put an ellipsis.
-                write!(&mut self.dst, "{0:1$}...\n", "", skip)?;
+                write!(&mut self.dst, "\n")?;
             }
-
-            // Print offending code-lines
-            write!(&mut self.dst, "{}:{:>width$} {}\n", fm.name,
-                   line.line_index + 1, line_str, width=digits)?;
-            remaining_err_lines -= 1;
-
-            if s.len() > skip {
-                // Render the spans we assembled previously (if any)
-                println_maybe_styled!(&mut self.dst, term::Attr::ForegroundColor(lvl.color()),
-                                      "{}", s)?;
-            }
-            prev_line_index = line.line_index;
         }
         Ok(())
     }
@@ -609,17 +446,29 @@ fn print_diagnostic(dst: &mut Destination,
                     code: Option<&str>)
                     -> io::Result<()> {
     if !topic.is_empty() {
-        write!(dst, "{} ", topic)?;
+        let old_school = check_old_skool();
+        if !old_school {
+            write!(dst, "{}: ", topic)?;
+        }
+        else {
+            write!(dst, "{} ", topic)?;
+        }
+        dst.reset_attrs()?;
     }
-
-    print_maybe_styled!(dst, term::Attr::ForegroundColor(lvl.color()),
-                        "{}: ", lvl.to_string())?;
-    print_maybe_styled!(dst, term::Attr::Bold, "{}", msg)?;
+    dst.start_attr(term::Attr::Bold)?;
+    dst.start_attr(term::Attr::ForegroundColor(lvl.color()))?;
+    write!(dst, "{}", lvl.to_string())?;
+    dst.reset_attrs()?;
+    write!(dst, ": ")?;
+    dst.start_attr(term::Attr::Bold)?;
+    write!(dst, "{}", msg)?;
 
     if let Some(code) = code {
         let style = term::Attr::ForegroundColor(term::color::BRIGHT_MAGENTA);
         print_maybe_styled!(dst, style, " [{}]", code.clone())?;
     }
+
+    dst.reset_attrs()?;
     write!(dst, "\n")?;
     Ok(())
 }
@@ -660,6 +509,51 @@ impl Destination {
         }
     }
 
+    fn apply_style(&mut self,
+                   lvl: Level,
+                   _kind: &RenderedLineKind,
+                   style: Style)
+                   -> io::Result<()> {
+        match style {
+            Style::FileNameStyle |
+            Style::LineAndColumn => {
+            }
+            Style::LineNumber => {
+                self.start_attr(term::Attr::Bold)?;
+                self.start_attr(term::Attr::ForegroundColor(term::color::BRIGHT_BLUE))?;
+            }
+            Style::Quotation => {
+            }
+            Style::UnderlinePrimary | Style::LabelPrimary => {
+                self.start_attr(term::Attr::Bold)?;
+                self.start_attr(term::Attr::ForegroundColor(lvl.color()))?;
+            }
+            Style::UnderlineSecondary | Style::LabelSecondary => {
+                self.start_attr(term::Attr::Bold)?;
+                self.start_attr(term::Attr::ForegroundColor(term::color::BRIGHT_BLUE))?;
+            }
+            Style::NoStyle => {
+            }
+        }
+        Ok(())
+    }
+
+    fn start_attr(&mut self, attr: term::Attr) -> io::Result<()> {
+        match *self {
+            Terminal(ref mut t) => { t.attr(attr)?; }
+            Raw(_) => { }
+        }
+        Ok(())
+    }
+
+    fn reset_attrs(&mut self) -> io::Result<()> {
+        match *self {
+            Terminal(ref mut t) => { t.reset()?; }
+            Raw(_) => { }
+        }
+        Ok(())
+    }
+
     fn print_maybe_styled(&mut self,
                           args: fmt::Arguments,
                           color: term::Attr,
@@ -741,7 +635,7 @@ mod test {
     /// that this can span lines and so on.
     fn span_from_selection(input: &str, selection: &str) -> Span {
         assert_eq!(input.len(), selection.len());
-        let left_index = selection.find('^').unwrap() as u32;
+        let left_index = selection.find('~').unwrap() as u32;
         let right_index = selection.rfind('~').map(|x|x as u32).unwrap_or(left_index);
         Span { lo: BytePos(left_index), hi: BytePos(right_index + 1), expn_id: NO_EXPANSION }
     }
@@ -767,7 +661,7 @@ mod test {
         dreizehn
         ";
         let file = cm.new_filemap_and_lines("dummy.txt", content);
-        let start = file.lines.borrow()[7];
+        let start = file.lines.borrow()[10];
         let end = file.lines.borrow()[11];
         let sp = mk_sp(start, end);
         let lvl = Level::Error;
@@ -777,12 +671,12 @@ mod test {
         let vec = data.lock().unwrap().clone();
         let vec: &[u8] = &vec;
         let str = from_utf8(vec).unwrap();
-        println!("{}", str);
-        assert_eq!(str, "dummy.txt: 8         line8\n\
-                         dummy.txt: 9         line9\n\
-                         dummy.txt:10         line10\n\
-                         dummy.txt:11         e-lä-vän\n\
-                         dummy.txt:12         tolv\n");
+        println!("r#\"\n{}\"#", str);
+        assert_eq!(str, &r#"
+  --> dummy.txt:11:1
+11 |>         e-lä-vän
+   |> ^
+"#[1..]);
     }
 
     #[test]
@@ -790,7 +684,7 @@ mod test {
         // Test that a `MultiSpan` containing a single span splices a substition correctly
         let cm = CodeMap::new();
         let inputtext = "aaaaa\nbbbbBB\nCCC\nDDDDDddddd\neee\n";
-        let selection = "     \n    ^~\n~~~\n~~~~~     \n   \n";
+        let selection = "     \n    ~~\n~~~\n~~~~~     \n   \n";
         cm.new_filemap_and_lines("blork.rs", inputtext);
         let sp = span_from_selection(inputtext, selection);
         let msp: MultiSpan = sp.into();
@@ -808,51 +702,25 @@ mod test {
     }
 
     #[test]
-    fn test_multiple_span_splice() {
-        // Test that a `MultiSpan` containing multiple spans splices substitions on
-        // several lines correctly
+    fn test_multi_span_splice() {
+        // Test that a `MultiSpan` containing multiple spans splices a substition correctly
         let cm = CodeMap::new();
-        let inp = "aaaaabbbbBB\nZZ\nZZ\nCCCDDDDDdddddeee";
-        let sp1 = "     ^~~~~~\n  \n  \n                ";
-        let sp2 = "           \n  \n  \n^~~~~~          ";
-        let sp3 = "           \n  \n  \n        ^~~     ";
-        let sp4 = "           \n  \n  \n           ^~~~ ";
-
-        let span_eq = |sp, eq| assert_eq!(&cm.span_to_snippet(sp).unwrap(), eq);
-
-        cm.new_filemap_and_lines("blork.rs", inp);
-        let sp1 = span_from_selection(inp, sp1);
-        let sp2 = span_from_selection(inp, sp2);
-        let sp3 = span_from_selection(inp, sp3);
-        let sp4 = span_from_selection(inp, sp4);
-        span_eq(sp1, "bbbbBB");
-        span_eq(sp2, "CCCDDD");
-        span_eq(sp3, "ddd");
-        span_eq(sp4, "ddee");
-
-        let substitutes: Vec<String> = ["1", "2", "3", "4"].iter().map(|x|x.to_string()).collect();
-        let expected = "aaaaa1\nZZ\nZZ\n2DD34e";
-
-        let test = |msp| {
-            let suggest = CodeSuggestion {
-                msp: msp,
-                substitutes: substitutes.clone(),
-            };
-            let actual = suggest.splice_lines(&cm);
-            assert_eq!(actual, expected);
+        let inputtext  = "aaaaa\nbbbbBB\nCCC\nDDDDDddddd\neee\n";
+        let selection1 = "     \n      \n   \n          \n ~ \n"; // intentionally out of order
+        let selection2 = "     \n    ~~\n~~~\n~~~~~     \n   \n";
+        cm.new_filemap_and_lines("blork.rs", inputtext);
+        let sp1 = span_from_selection(inputtext, selection1);
+        let sp2 = span_from_selection(inputtext, selection2);
+        let msp: MultiSpan = MultiSpan::from_spans(vec![sp1, sp2]);
+
+        let expected = "bbbbZZZZZZddddd\neXYZe";
+        let suggest = CodeSuggestion {
+            msp: msp,
+            substitutes: vec!["ZZZZZZ".to_owned(),
+                              "XYZ".to_owned()]
         };
-        test(MultiSpan { spans: vec![sp1, sp2, sp3, sp4] });
-
-        // Test ordering and merging by `MultiSpan::push`
-        let mut msp = MultiSpan::new();
-        msp.push_merge(sp2);
-        msp.push_merge(sp1);
-        assert_eq!(&msp.spans, &[sp1, sp2]);
-        msp.push_merge(sp4);
-        assert_eq!(&msp.spans, &[sp1, sp2, sp4]);
-        msp.push_merge(sp3);
-        assert_eq!(&msp.spans, &[sp1, sp2, sp3, sp4]);
-        test(msp);
+
+        assert_eq!(suggest.splice_lines(&cm), expected);
     }
 
     #[test]
@@ -862,17 +730,17 @@ mod test {
         let mut diag = EmitterWriter::new(Box::new(Sink(data.clone())), None, cm.clone());
 
         let inp =       "_____aaaaaa____bbbbbb__cccccdd_";
-        let sp1 =       "     ^~~~~~                    ";
-        let sp2 =       "               ^~~~~~          ";
-        let sp3 =       "                       ^~~~~   ";
-        let sp4 =       "                          ^~~~ ";
-        let sp34 =      "                       ^~~~~~~ ";
-        let sp4_end =   "                            ^~ ";
-
-        let expect_start = "dummy.txt:1 _____aaaaaa____bbbbbb__cccccdd_\n\
-                         \x20                ^~~~~~    ^~~~~~  ^~~~~~~\n";
-        let expect_end =   "dummy.txt:1 _____aaaaaa____bbbbbb__cccccdd_\n\
-                         \x20                     ^         ^      ^ ^\n";
+        let sp1 =       "     ~~~~~~                    ";
+        let sp2 =       "               ~~~~~~          ";
+        let sp3 =       "                       ~~~~~   ";
+        let sp4 =       "                          ~~~~ ";
+        let sp34 =      "                       ~~~~~~~ ";
+
+        let expect_start = &r#"
+ --> dummy.txt:1:6
+1 |> _____aaaaaa____bbbbbb__cccccdd_
+  |>      ^^^^^^    ^^^^^^  ^^^^^^^
+"#[1..];
 
         let span = |sp, expected| {
             let sp = span_from_selection(inp, sp);
@@ -885,7 +753,6 @@ mod test {
         let sp3 = span(sp3, "ccccc");
         let sp4 = span(sp4, "ccdd");
         let sp34 = span(sp34, "cccccdd");
-        let sp4_end = span(sp4_end, "dd");
 
         let spans = vec![sp1, sp2, sp3, sp4];
 
@@ -894,26 +761,17 @@ mod test {
             highlight();
             let vec = data.lock().unwrap().clone();
             let actual = from_utf8(&vec[..]).unwrap();
+            println!("actual=\n{}", actual);
             assert_eq!(actual, expected);
         };
 
-        let msp = MultiSpan { spans: vec![sp1, sp2, sp34] };
-        let msp_end = MultiSpan { spans: vec![sp1, sp2, sp3, sp4_end] };
+        let msp = MultiSpan::from_spans(vec![sp1, sp2, sp34]);
         test(expect_start, &mut || {
             diag.highlight_lines(&msp, Level::Error).unwrap();
         });
-        test(expect_end, &mut || {
-            diag.end_highlight_lines(&msp_end, Level::Error).unwrap();
-        });
         test(expect_start, &mut || {
-            for msp in cm.group_spans(spans.clone()) {
-                diag.highlight_lines(&msp, Level::Error).unwrap();
-            }
-        });
-        test(expect_end, &mut || {
-            for msp in cm.end_group_spans(spans.clone()) {
-                diag.end_highlight_lines(&msp, Level::Error).unwrap();
-            }
+            let msp = MultiSpan::from_spans(spans.clone());
+            diag.highlight_lines(&msp, Level::Error).unwrap();
         });
     }
 
@@ -950,75 +808,29 @@ mod test {
         let sp4 = span(10, 10, (2, 3));
         let sp5 = span(10, 10, (4, 6));
 
-        let expect0 = "dummy.txt: 5 ccccc\n\
-                       dummy.txt: 6 xxxxx\n\
-                       dummy.txt: 7 yyyyy\n\
-                    \x20            ...\n\
-                       dummy.txt: 9 ddd__eee_\n\
-                    \x20            ^~~  ^~~\n\
-                    \x20            ...\n\
-                       dummy.txt:11 __f_gg\n\
-                    \x20              ^ ^~\n";
-
-        let expect = "dummy.txt: 1 aaaaa\n\
-                      dummy.txt: 2 aaaaa\n\
-                      dummy.txt: 3 aaaaa\n\
-                      dummy.txt: 4 bbbbb\n\
-                      dummy.txt: 5 ccccc\n\
-                      dummy.txt: 6 xxxxx\n\
-                   \x20            ...\n";
-
-        let expect_g1 = "dummy.txt:1 aaaaa\n\
-                         dummy.txt:2 aaaaa\n\
-                         dummy.txt:3 aaaaa\n\
-                         dummy.txt:4 bbbbb\n\
-                         dummy.txt:5 ccccc\n\
-                         dummy.txt:6 xxxxx\n\
-                      \x20           ...\n";
-
-        let expect2 = "dummy.txt: 9 ddd__eee_\n\
-                    \x20            ^~~  ^~~\n\
-                    \x20            ...\n\
-                       dummy.txt:11 __f_gg\n\
-                    \x20              ^ ^~\n";
-
-
-        let expect_end = "dummy.txt: 1 aaaaa\n\
-                       \x20            ...\n\
-                          dummy.txt: 7 yyyyy\n\
-                       \x20                ^\n\
-                       \x20            ...\n\
-                          dummy.txt: 9 ddd__eee_\n\
-                       \x20              ^    ^\n\
-                       \x20            ...\n\
-                          dummy.txt:11 __f_gg\n\
-                       \x20              ^  ^\n";
-
-        let expect0_end = "dummy.txt: 5 ccccc\n\
-                           dummy.txt: 6 xxxxx\n\
-                           dummy.txt: 7 yyyyy\n\
-                        \x20                ^\n\
-                        \x20            ...\n\
-                           dummy.txt: 9 ddd__eee_\n\
-                        \x20              ^    ^\n\
-                        \x20            ...\n\
-                           dummy.txt:11 __f_gg\n\
-                        \x20              ^  ^\n";
-
-        let expect_end_g1 = "dummy.txt:1 aaaaa\n\
-                          \x20           ...\n\
-                             dummy.txt:7 yyyyy\n\
-                          \x20               ^\n";
-
-        let expect2_end = "dummy.txt: 9 ddd__eee_\n\
-                        \x20              ^    ^\n\
-                        \x20            ...\n\
-                           dummy.txt:11 __f_gg\n\
-                        \x20              ^  ^\n";
-
-        let expect_groups = [expect2, expect_g1];
-        let expect_end_groups = [expect2_end, expect_end_g1];
-        let spans = vec![sp3, sp1, sp4, sp2, sp5];
+        let expect0 = &r#"
+   --> dummy.txt:5:1
+5   |> ccccc
+    |> ^
+...
+9   |> ddd__eee_
+    |> ^^^  ^^^
+10  |> elided
+11  |> __f_gg
+    |>   ^ ^^
+"#[1..];
+
+        let expect = &r#"
+   --> dummy.txt:1:1
+1   |> aaaaa
+    |> ^
+...
+9   |> ddd__eee_
+    |> ^^^  ^^^
+10  |> elided
+11  |> __f_gg
+    |>   ^ ^^
+"#[1..];
 
         macro_rules! test {
             ($expected: expr, $highlight: expr) => ({
@@ -1034,37 +846,14 @@ mod test {
             });
         }
 
-        let msp0 = MultiSpan { spans: vec![sp0, sp2, sp3, sp4, sp5] };
-        let msp = MultiSpan { spans: vec![sp1, sp2, sp3, sp4, sp5] };
-        let msp2 = MultiSpan { spans: vec![sp2, sp3, sp4, sp5] };
+        let msp0 = MultiSpan::from_spans(vec![sp0, sp2, sp3, sp4, sp5]);
+        let msp = MultiSpan::from_spans(vec![sp1, sp2, sp3, sp4, sp5]);
 
         test!(expect0, || {
             diag.highlight_lines(&msp0, Level::Error).unwrap();
         });
-        test!(expect0_end, || {
-            diag.end_highlight_lines(&msp0, Level::Error).unwrap();
-        });
         test!(expect, || {
             diag.highlight_lines(&msp, Level::Error).unwrap();
         });
-        test!(expect_end, || {
-            diag.end_highlight_lines(&msp, Level::Error).unwrap();
-        });
-        test!(expect2, || {
-            diag.highlight_lines(&msp2, Level::Error).unwrap();
-        });
-        test!(expect2_end, || {
-            diag.end_highlight_lines(&msp2, Level::Error).unwrap();
-        });
-        for (msp, expect) in cm.group_spans(spans.clone()).iter().zip(expect_groups.iter()) {
-            test!(expect, || {
-                diag.highlight_lines(&msp, Level::Error).unwrap();
-            });
-        }
-        for (msp, expect) in cm.group_spans(spans.clone()).iter().zip(expect_end_groups.iter()) {
-            test!(expect, || {
-                diag.end_highlight_lines(&msp, Level::Error).unwrap();
-            });
-        }
     }
 }
diff --git a/src/libsyntax/errors/json.rs b/src/libsyntax/errors/json.rs
index 821617bfe89..93c6268ccae 100644
--- a/src/libsyntax/errors/json.rs
+++ b/src/libsyntax/errors/json.rs
@@ -20,7 +20,7 @@
 // FIXME spec the JSON output properly.
 
 
-use codemap::{self, Span, MacroBacktrace, MultiSpan, CodeMap};
+use codemap::{self, MacroBacktrace, Span, SpanLabel, MultiSpan, CodeMap};
 use diagnostics::registry::Registry;
 use errors::{Level, DiagnosticBuilder, SubDiagnostic, RenderSpan, CodeSuggestion};
 use errors::emitter::Emitter;
@@ -53,20 +53,13 @@ impl JsonEmitter {
 }
 
 impl Emitter for JsonEmitter {
-    fn emit(&mut self, span: Option<&MultiSpan>, msg: &str, code: Option<&str>, level: Level) {
+    fn emit(&mut self, span: &MultiSpan, msg: &str, code: Option<&str>, level: Level) {
         let data = Diagnostic::new(span, msg, code, level, self);
         if let Err(e) = writeln!(&mut self.dst, "{}", as_json(&data)) {
             panic!("failed to print diagnostics: {:?}", e);
         }
     }
 
-    fn custom_emit(&mut self, sp: &RenderSpan, msg: &str, level: Level) {
-        let data = Diagnostic::from_render_span(sp, msg, level, self);
-        if let Err(e) = writeln!(&mut self.dst, "{}", as_json(&data)) {
-            panic!("failed to print diagnostics: {:?}", e);
-        }
-    }
-
     fn emit_struct(&mut self, db: &DiagnosticBuilder) {
         let data = Diagnostic::from_diagnostic_builder(db, self);
         if let Err(e) = writeln!(&mut self.dst, "{}", as_json(&data)) {
@@ -104,8 +97,13 @@ struct DiagnosticSpan {
     /// 1-based, character offset.
     column_start: usize,
     column_end: usize,
+    /// Is this a "primary" span -- meaning the point, or one of the points,
+    /// where the error occurred?
+    is_primary: bool,
     /// Source text from the start of line_start to the end of line_end.
     text: Vec<DiagnosticSpanLine>,
+    /// Label that should be placed at this location (if any)
+    label: Option<String>,
     /// If we are suggesting a replacement, this will contain text
     /// that should be sliced in atop this span. You may prefer to
     /// load the fully rendered version from the parent `Diagnostic`,
@@ -148,7 +146,7 @@ struct DiagnosticCode {
 }
 
 impl<'a> Diagnostic<'a> {
-    fn new(msp: Option<&MultiSpan>,
+    fn new(msp: &MultiSpan,
            msg: &'a str,
            code: Option<&str>,
            level: Level,
@@ -158,27 +156,12 @@ impl<'a> Diagnostic<'a> {
             message: msg,
             code: DiagnosticCode::map_opt_string(code.map(|c| c.to_owned()), je),
             level: level.to_str(),
-            spans: msp.map_or(vec![], |msp| DiagnosticSpan::from_multispan(msp, je)),
+            spans: DiagnosticSpan::from_multispan(msp, je),
             children: vec![],
             rendered: None,
         }
     }
 
-    fn from_render_span(span: &RenderSpan,
-                        msg: &'a str,
-                        level: Level,
-                        je: &JsonEmitter)
-                        -> Diagnostic<'a> {
-        Diagnostic {
-            message: msg,
-            code: None,
-            level: level.to_str(),
-            spans: DiagnosticSpan::from_render_span(span, je),
-            children: vec![],
-            rendered: je.render(span),
-        }
-    }
-
     fn from_diagnostic_builder<'c>(db: &'c DiagnosticBuilder,
                                    je: &JsonEmitter)
                                    -> Diagnostic<'c> {
@@ -186,7 +169,7 @@ impl<'a> Diagnostic<'a> {
             message: &db.message,
             code: DiagnosticCode::map_opt_string(db.code.clone(), je),
             level: db.level.to_str(),
-            spans: db.span.as_ref().map_or(vec![], |sp| DiagnosticSpan::from_multispan(sp, je)),
+            spans: DiagnosticSpan::from_multispan(&db.span, je),
             children: db.children.iter().map(|c| {
                 Diagnostic::from_sub_diagnostic(c, je)
             }).collect(),
@@ -201,8 +184,7 @@ impl<'a> Diagnostic<'a> {
             level: db.level.to_str(),
             spans: db.render_span.as_ref()
                      .map(|sp| DiagnosticSpan::from_render_span(sp, je))
-                     .or_else(|| db.span.as_ref().map(|s| DiagnosticSpan::from_multispan(s, je)))
-                     .unwrap_or(vec![]),
+                     .unwrap_or_else(|| DiagnosticSpan::from_multispan(&db.span, je)),
             children: vec![],
             rendered: db.render_span.as_ref()
                                     .and_then(|rsp| je.render(rsp)),
@@ -211,44 +193,68 @@ impl<'a> Diagnostic<'a> {
 }
 
 impl DiagnosticSpan {
-    fn from_span(span: Span, suggestion: Option<&String>, je: &JsonEmitter)
-                 -> DiagnosticSpan {
+    fn from_span_label(span: SpanLabel,
+                       suggestion: Option<&String>,
+                       je: &JsonEmitter)
+                       -> DiagnosticSpan {
+        Self::from_span_etc(span.span,
+                            span.is_primary,
+                            span.label,
+                            suggestion,
+                            je)
+    }
+
+    fn from_span_etc(span: Span,
+                     is_primary: bool,
+                     label: Option<String>,
+                     suggestion: Option<&String>,
+                     je: &JsonEmitter)
+                     -> DiagnosticSpan {
         // obtain the full backtrace from the `macro_backtrace`
         // helper; in some ways, it'd be better to expand the
         // backtrace ourselves, but the `macro_backtrace` helper makes
         // some decision, such as dropping some frames, and I don't
         // want to duplicate that logic here.
         let backtrace = je.cm.macro_backtrace(span).into_iter();
-        DiagnosticSpan::from_span_and_backtrace(span, suggestion, backtrace, je)
+        DiagnosticSpan::from_span_full(span,
+                                       is_primary,
+                                       label,
+                                       suggestion,
+                                       backtrace,
+                                       je)
     }
 
-    fn from_span_and_backtrace(span: Span,
-                               suggestion: Option<&String>,
-                               mut backtrace: vec::IntoIter<MacroBacktrace>,
-                               je: &JsonEmitter)
-                               -> DiagnosticSpan {
+    fn from_span_full(span: Span,
+                      is_primary: bool,
+                      label: Option<String>,
+                      suggestion: Option<&String>,
+                      mut backtrace: vec::IntoIter<MacroBacktrace>,
+                      je: &JsonEmitter)
+                      -> DiagnosticSpan {
         let start = je.cm.lookup_char_pos(span.lo);
         let end = je.cm.lookup_char_pos(span.hi);
-        let backtrace_step =
-            backtrace.next()
-                     .map(|bt| {
-                         let call_site =
-                             Self::from_span_and_backtrace(bt.call_site,
-                                                           None,
-                                                           backtrace,
-                                                           je);
-                         let def_site_span = bt.def_site_span.map(|sp| {
-                             Self::from_span_and_backtrace(sp,
-                                                           None,
-                                                           vec![].into_iter(),
-                                                           je)
-                         });
-                         Box::new(DiagnosticSpanMacroExpansion {
-                             span: call_site,
-                             macro_decl_name: bt.macro_decl_name,
-                             def_site_span: def_site_span,
-                         })
-                     });
+        let backtrace_step = backtrace.next().map(|bt| {
+            let call_site =
+                Self::from_span_full(bt.call_site,
+                                     false,
+                                     None,
+                                     None,
+                                     backtrace,
+                                     je);
+            let def_site_span = bt.def_site_span.map(|sp| {
+                Self::from_span_full(sp,
+                                     false,
+                                     None,
+                                     None,
+                                     vec![].into_iter(),
+                                     je)
+            });
+            Box::new(DiagnosticSpanMacroExpansion {
+                span: call_site,
+                macro_decl_name: bt.macro_decl_name,
+                def_site_span: def_site_span,
+            })
+        });
         DiagnosticSpan {
             file_name: start.file.name.clone(),
             byte_start: span.lo.0,
@@ -257,53 +263,41 @@ impl DiagnosticSpan {
             line_end: end.line,
             column_start: start.col.0 + 1,
             column_end: end.col.0 + 1,
+            is_primary: is_primary,
             text: DiagnosticSpanLine::from_span(span, je),
             suggested_replacement: suggestion.cloned(),
             expansion: backtrace_step,
+            label: label,
         }
     }
 
     fn from_multispan(msp: &MultiSpan, je: &JsonEmitter) -> Vec<DiagnosticSpan> {
-        msp.spans.iter().map(|&span| Self::from_span(span, None, je)).collect()
+        msp.span_labels()
+           .into_iter()
+           .map(|span_str| Self::from_span_label(span_str, None, je))
+           .collect()
     }
 
     fn from_suggestion(suggestion: &CodeSuggestion, je: &JsonEmitter)
                        -> Vec<DiagnosticSpan> {
-        assert_eq!(suggestion.msp.spans.len(), suggestion.substitutes.len());
-        suggestion.msp.spans.iter()
-                            .zip(&suggestion.substitutes)
-                            .map(|(&span, suggestion)| {
-                                DiagnosticSpan::from_span(span, Some(suggestion), je)
-                            })
-                            .collect()
+        assert_eq!(suggestion.msp.span_labels().len(), suggestion.substitutes.len());
+        suggestion.msp.span_labels()
+                      .into_iter()
+                      .zip(&suggestion.substitutes)
+                      .map(|(span_label, suggestion)| {
+                          DiagnosticSpan::from_span_label(span_label,
+                                                          Some(suggestion),
+                                                          je)
+                      })
+                      .collect()
     }
 
     fn from_render_span(rsp: &RenderSpan, je: &JsonEmitter) -> Vec<DiagnosticSpan> {
         match *rsp {
-            RenderSpan::FileLine(ref msp) |
-            RenderSpan::FullSpan(ref msp) => {
-                DiagnosticSpan::from_multispan(msp, je)
-            }
-            RenderSpan::Suggestion(ref suggestion) => {
-                DiagnosticSpan::from_suggestion(suggestion, je)
-            }
-            RenderSpan::EndSpan(ref msp) => {
-                msp.spans.iter().map(|&span| {
-                    let end = je.cm.lookup_char_pos(span.hi);
-                    DiagnosticSpan {
-                        file_name: end.file.name.clone(),
-                        byte_start: span.hi.0,
-                        byte_end: span.hi.0,
-                        line_start: end.line,
-                        line_end: end.line,
-                        column_start: end.col.0 + 1,
-                        column_end: end.col.0 + 1,
-                        text: DiagnosticSpanLine::from_span_end(span, je),
-                        suggested_replacement: None,
-                        expansion: None,
-                    }
-                }).collect()
-            }
+            RenderSpan::FullSpan(ref msp) =>
+                DiagnosticSpan::from_multispan(msp, je),
+            RenderSpan::Suggestion(ref suggestion) =>
+                DiagnosticSpan::from_suggestion(suggestion, je),
         }
     }
 }
@@ -340,34 +334,6 @@ impl DiagnosticSpanLine {
              })
             .unwrap_or(vec![])
     }
-
-    /// Create a list of DiagnosticSpanLines from span - the result covers all
-    /// of `span`, but the highlight is zero-length and at the end of `span`.
-    fn from_span_end(span: Span, je: &JsonEmitter) -> Vec<DiagnosticSpanLine> {
-        je.cm.span_to_lines(span)
-             .map(|lines| {
-                 let fm = &*lines.file;
-                 lines.lines.iter()
-                            .enumerate()
-                            .map(|(i, line)| {
-                                // Invariant - CodeMap::span_to_lines
-                                // will not return extra context lines
-                                // - the last line returned is the last
-                                // line of `span`.
-                                let highlight = if i == lines.lines.len() - 1 {
-                                    (line.end_col.0 + 1, line.end_col.0 + 1)
-                                } else {
-                                    (0, 0)
-                                };
-                                DiagnosticSpanLine::line_from_filemap(fm,
-                                                                      line.line_index,
-                                                                      highlight.0,
-                                                                      highlight.1)
-                            })
-                            .collect()
-             })
-            .unwrap_or(vec![])
-    }
 }
 
 impl DiagnosticCode {
@@ -389,16 +355,12 @@ impl DiagnosticCode {
 impl JsonEmitter {
     fn render(&self, render_span: &RenderSpan) -> Option<String> {
         match *render_span {
-            RenderSpan::FileLine(_) |
             RenderSpan::FullSpan(_) => {
                 None
             }
             RenderSpan::Suggestion(ref suggestion) => {
                 Some(suggestion.splice_lines(&self.cm))
             }
-            RenderSpan::EndSpan(_) => {
-                None
-            }
         }
     }
 }
diff --git a/src/libsyntax/errors/mod.rs b/src/libsyntax/errors/mod.rs
index 792828b3054..f0c665bcb3c 100644
--- a/src/libsyntax/errors/mod.rs
+++ b/src/libsyntax/errors/mod.rs
@@ -13,17 +13,19 @@ pub use errors::emitter::ColorConfig;
 use self::Level::*;
 use self::RenderSpan::*;
 
-use codemap::{self, CodeMap, MultiSpan};
+use codemap::{self, CodeMap, MultiSpan, NO_EXPANSION, Span};
 use diagnostics;
 use errors::emitter::{Emitter, EmitterWriter};
 
 use std::cell::{RefCell, Cell};
 use std::{error, fmt};
 use std::rc::Rc;
+use std::thread::panicking;
 use term;
 
 pub mod emitter;
 pub mod json;
+pub mod snippet;
 
 #[derive(Clone)]
 pub enum RenderSpan {
@@ -32,22 +34,11 @@ pub enum RenderSpan {
     /// the source code covered by the span.
     FullSpan(MultiSpan),
 
-    /// Similar to a FullSpan, but the cited position is the end of
-    /// the span, instead of the start. Used, at least, for telling
-    /// compiletest/runtest to look at the last line of the span
-    /// (since `end_highlight_lines` displays an arrow to the end
-    /// of the span).
-    EndSpan(MultiSpan),
-
     /// A suggestion renders with both with an initial line for the
     /// message, prefixed by file:linenum, followed by a summary
     /// of hypothetical source code, where each `String` is spliced
     /// into the lines in place of the code covered by each span.
     Suggestion(CodeSuggestion),
-
-    /// A FileLine renders with just a line for the message prefixed
-    /// by file:linenum.
-    FileLine(MultiSpan),
 }
 
 #[derive(Clone)]
@@ -60,9 +51,7 @@ impl RenderSpan {
     fn span(&self) -> &MultiSpan {
         match *self {
             FullSpan(ref msp) |
-            Suggestion(CodeSuggestion { ref msp, .. }) |
-            EndSpan(ref msp) |
-            FileLine(ref msp) =>
+            Suggestion(CodeSuggestion { ref msp, .. }) =>
                 msp
         }
     }
@@ -88,12 +77,24 @@ impl CodeSuggestion {
                 }
             }
         }
-        let bounds = self.msp.to_span_bounds();
-        let lines = cm.span_to_lines(bounds).unwrap();
-        assert!(!lines.lines.is_empty());
 
-        // This isn't strictly necessary, but would in all likelyhood be an error
-        assert_eq!(self.msp.spans.len(), self.substitutes.len());
+        let mut primary_spans = self.msp.primary_spans().to_owned();
+
+        assert_eq!(primary_spans.len(), self.substitutes.len());
+        if primary_spans.is_empty() {
+            return format!("");
+        }
+
+        // Assumption: all spans are in the same file, and all spans
+        // are disjoint. Sort in ascending order.
+        primary_spans.sort_by_key(|sp| sp.lo);
+
+        // Find the bounding span.
+        let lo = primary_spans.iter().map(|sp| sp.lo).min().unwrap();
+        let hi = primary_spans.iter().map(|sp| sp.hi).min().unwrap();
+        let bounding_span = Span { lo: lo, hi: hi, expn_id: NO_EXPANSION };
+        let lines = cm.span_to_lines(bounding_span).unwrap();
+        assert!(!lines.lines.is_empty());
 
         // To build up the result, we do this for each span:
         // - push the line segment trailing the previous span
@@ -105,13 +106,13 @@ impl CodeSuggestion {
         //
         // Finally push the trailing line segment of the last span
         let fm = &lines.file;
-        let mut prev_hi = cm.lookup_char_pos(bounds.lo);
+        let mut prev_hi = cm.lookup_char_pos(bounding_span.lo);
         prev_hi.col = CharPos::from_usize(0);
 
         let mut prev_line = fm.get_line(lines.lines[0].line_index);
         let mut buf = String::new();
 
-        for (sp, substitute) in self.msp.spans.iter().zip(self.substitutes.iter()) {
+        for (sp, substitute) in primary_spans.iter().zip(self.substitutes.iter()) {
             let cur_lo = cm.lookup_char_pos(sp.lo);
             if prev_hi.line == cur_lo.line {
                 push_trailing(&mut buf, prev_line, &prev_hi, Some(&cur_lo));
@@ -183,7 +184,7 @@ pub struct DiagnosticBuilder<'a> {
     level: Level,
     message: String,
     code: Option<String>,
-    span: Option<MultiSpan>,
+    span: MultiSpan,
     children: Vec<SubDiagnostic>,
 }
 
@@ -192,7 +193,7 @@ pub struct DiagnosticBuilder<'a> {
 struct SubDiagnostic {
     level: Level,
     message: String,
-    span: Option<MultiSpan>,
+    span: MultiSpan,
     render_span: Option<RenderSpan>,
 }
 
@@ -228,37 +229,61 @@ impl<'a> DiagnosticBuilder<'a> {
         self.level == Level::Fatal
     }
 
-    pub fn note(&mut self , msg: &str) -> &mut DiagnosticBuilder<'a> {
-        self.sub(Level::Note, msg, None, None);
+    /// Add a span/label to be included in the resulting snippet.
+    /// This is pushed onto the `MultiSpan` that was created when the
+    /// diagnostic was first built. If you don't call this function at
+    /// all, and you just supplied a `Span` to create the diagnostic,
+    /// then the snippet will just include that `Span`, which is
+    /// called the primary span.
+    pub fn span_label(mut self, span: Span, label: &fmt::Display)
+                      -> DiagnosticBuilder<'a> {
+        self.span.push_span_label(span, format!("{}", label));
+        self
+    }
+
+    pub fn note_expected_found(mut self,
+                               label: &fmt::Display,
+                               expected: &fmt::Display,
+                               found: &fmt::Display)
+                               -> DiagnosticBuilder<'a>
+    {
+        // For now, just attach these as notes
+        self.note(&format!("expected {} `{}`", label, expected));
+        self.note(&format!("   found {} `{}`", label, found));
+        self
+    }
+
+    pub fn note(&mut self, msg: &str) -> &mut DiagnosticBuilder<'a> {
+        self.sub(Level::Note, msg, MultiSpan::new(), None);
         self
     }
     pub fn span_note<S: Into<MultiSpan>>(&mut self,
                                          sp: S,
                                          msg: &str)
                                          -> &mut DiagnosticBuilder<'a> {
-        self.sub(Level::Note, msg, Some(sp.into()), None);
+        self.sub(Level::Note, msg, sp.into(), None);
         self
     }
     pub fn warn(&mut self, msg: &str) -> &mut DiagnosticBuilder<'a> {
-        self.sub(Level::Warning, msg, None, None);
+        self.sub(Level::Warning, msg, MultiSpan::new(), None);
         self
     }
     pub fn span_warn<S: Into<MultiSpan>>(&mut self,
                                          sp: S,
                                          msg: &str)
                                          -> &mut DiagnosticBuilder<'a> {
-        self.sub(Level::Warning, msg, Some(sp.into()), None);
+        self.sub(Level::Warning, msg, sp.into(), None);
         self
     }
     pub fn help(&mut self , msg: &str) -> &mut DiagnosticBuilder<'a> {
-        self.sub(Level::Help, msg, None, None);
+        self.sub(Level::Help, msg, MultiSpan::new(), None);
         self
     }
     pub fn span_help<S: Into<MultiSpan>>(&mut self,
                                          sp: S,
                                          msg: &str)
                                          -> &mut DiagnosticBuilder<'a> {
-        self.sub(Level::Help, msg, Some(sp.into()), None);
+        self.sub(Level::Help, msg, sp.into(), None);
         self
     }
     /// Prints out a message with a suggested edit of the code.
@@ -269,43 +294,15 @@ impl<'a> DiagnosticBuilder<'a> {
                                                msg: &str,
                                                suggestion: String)
                                                -> &mut DiagnosticBuilder<'a> {
-        self.sub(Level::Help, msg, None, Some(Suggestion(CodeSuggestion {
+        self.sub(Level::Help, msg, MultiSpan::new(), Some(Suggestion(CodeSuggestion {
             msp: sp.into(),
             substitutes: vec![suggestion],
         })));
         self
     }
-    pub fn span_end_note<S: Into<MultiSpan>>(&mut self,
-                                             sp: S,
-                                             msg: &str)
-                                             -> &mut DiagnosticBuilder<'a> {
-        self.sub(Level::Note, msg, None, Some(EndSpan(sp.into())));
-        self
-    }
-    pub fn fileline_warn<S: Into<MultiSpan>>(&mut self,
-                                             sp: S,
-                                             msg: &str)
-                                             -> &mut DiagnosticBuilder<'a> {
-        self.sub(Level::Warning, msg, None, Some(FileLine(sp.into())));
-        self
-    }
-    pub fn fileline_note<S: Into<MultiSpan>>(&mut self,
-                                             sp: S,
-                                             msg: &str)
-                                             -> &mut DiagnosticBuilder<'a> {
-        self.sub(Level::Note, msg, None, Some(FileLine(sp.into())));
-        self
-    }
-    pub fn fileline_help<S: Into<MultiSpan>>(&mut self,
-                                             sp: S,
-                                             msg: &str)
-                                             -> &mut DiagnosticBuilder<'a> {
-        self.sub(Level::Help, msg, None, Some(FileLine(sp.into())));
-        self
-    }
 
-    pub fn span<S: Into<MultiSpan>>(&mut self, sp: S) -> &mut Self {
-        self.span = Some(sp.into());
+    pub fn set_span<S: Into<MultiSpan>>(&mut self, sp: S) -> &mut Self {
+        self.span = sp.into();
         self
     }
 
@@ -324,7 +321,7 @@ impl<'a> DiagnosticBuilder<'a> {
             level: level,
             message: message.to_owned(),
             code: None,
-            span: None,
+            span: MultiSpan::new(),
             children: vec![],
         }
     }
@@ -334,7 +331,7 @@ impl<'a> DiagnosticBuilder<'a> {
     fn sub(&mut self,
            level: Level,
            message: &str,
-           span: Option<MultiSpan>,
+           span: MultiSpan,
            render_span: Option<RenderSpan>) {
         let sub = SubDiagnostic {
             level: level,
@@ -356,8 +353,11 @@ impl<'a> fmt::Debug for DiagnosticBuilder<'a> {
 /// we emit a bug.
 impl<'a> Drop for DiagnosticBuilder<'a> {
     fn drop(&mut self) {
-        if !self.cancelled() {
-            self.emitter.borrow_mut().emit(None, "Error constructed but not emitted", None, Bug);
+        if !panicking() && !self.cancelled() {
+            self.emitter.borrow_mut().emit(&MultiSpan::new(),
+                                           "Error constructed but not emitted",
+                                           None,
+                                           Bug);
             panic!();
         }
     }
@@ -412,7 +412,7 @@ impl Handler {
                                                     msg: &str)
                                                     -> DiagnosticBuilder<'a> {
         let mut result = DiagnosticBuilder::new(&self.emit, Level::Warning, msg);
-        result.span(sp);
+        result.set_span(sp);
         if !self.can_emit_warnings {
             result.cancel();
         }
@@ -424,7 +424,7 @@ impl Handler {
                                                               code: &str)
                                                               -> DiagnosticBuilder<'a> {
         let mut result = DiagnosticBuilder::new(&self.emit, Level::Warning, msg);
-        result.span(sp);
+        result.set_span(sp);
         result.code(code.to_owned());
         if !self.can_emit_warnings {
             result.cancel();
@@ -444,7 +444,7 @@ impl Handler {
                                                    -> DiagnosticBuilder<'a> {
         self.bump_err_count();
         let mut result = DiagnosticBuilder::new(&self.emit, Level::Error, msg);
-        result.span(sp);
+        result.set_span(sp);
         result
     }
     pub fn struct_span_err_with_code<'a, S: Into<MultiSpan>>(&'a self,
@@ -454,7 +454,7 @@ impl Handler {
                                                              -> DiagnosticBuilder<'a> {
         self.bump_err_count();
         let mut result = DiagnosticBuilder::new(&self.emit, Level::Error, msg);
-        result.span(sp);
+        result.set_span(sp);
         result.code(code.to_owned());
         result
     }
@@ -468,7 +468,7 @@ impl Handler {
                                                      -> DiagnosticBuilder<'a> {
         self.bump_err_count();
         let mut result = DiagnosticBuilder::new(&self.emit, Level::Fatal, msg);
-        result.span(sp);
+        result.set_span(sp);
         result
     }
     pub fn struct_span_fatal_with_code<'a, S: Into<MultiSpan>>(&'a self,
@@ -478,7 +478,7 @@ impl Handler {
                                                                -> DiagnosticBuilder<'a> {
         self.bump_err_count();
         let mut result = DiagnosticBuilder::new(&self.emit, Level::Fatal, msg);
-        result.span(sp);
+        result.set_span(sp);
         result.code(code.to_owned());
         result
     }
@@ -499,7 +499,7 @@ impl Handler {
         if self.treat_err_as_bug {
             self.span_bug(sp, msg);
         }
-        self.emit(Some(&sp.into()), msg, Fatal);
+        self.emit(&sp.into(), msg, Fatal);
         self.bump_err_count();
         return FatalError;
     }
@@ -508,7 +508,7 @@ impl Handler {
         if self.treat_err_as_bug {
             self.span_bug(sp, msg);
         }
-        self.emit_with_code(Some(&sp.into()), msg, code, Fatal);
+        self.emit_with_code(&sp.into(), msg, code, Fatal);
         self.bump_err_count();
         return FatalError;
     }
@@ -516,24 +516,24 @@ impl Handler {
         if self.treat_err_as_bug {
             self.span_bug(sp, msg);
         }
-        self.emit(Some(&sp.into()), msg, Error);
+        self.emit(&sp.into(), msg, Error);
         self.bump_err_count();
     }
     pub fn span_err_with_code<S: Into<MultiSpan>>(&self, sp: S, msg: &str, code: &str) {
         if self.treat_err_as_bug {
             self.span_bug(sp, msg);
         }
-        self.emit_with_code(Some(&sp.into()), msg, code, Error);
+        self.emit_with_code(&sp.into(), msg, code, Error);
         self.bump_err_count();
     }
     pub fn span_warn<S: Into<MultiSpan>>(&self, sp: S, msg: &str) {
-        self.emit(Some(&sp.into()), msg, Warning);
+        self.emit(&sp.into(), msg, Warning);
     }
     pub fn span_warn_with_code<S: Into<MultiSpan>>(&self, sp: S, msg: &str, code: &str) {
-        self.emit_with_code(Some(&sp.into()), msg, code, Warning);
+        self.emit_with_code(&sp.into(), msg, code, Warning);
     }
     pub fn span_bug<S: Into<MultiSpan>>(&self, sp: S, msg: &str) -> ! {
-        self.emit(Some(&sp.into()), msg, Bug);
+        self.emit(&sp.into(), msg, Bug);
         panic!(ExplicitBug);
     }
     pub fn delay_span_bug<S: Into<MultiSpan>>(&self, sp: S, msg: &str) {
@@ -541,11 +541,11 @@ impl Handler {
         *delayed = Some((sp.into(), msg.to_string()));
     }
     pub fn span_bug_no_panic<S: Into<MultiSpan>>(&self, sp: S, msg: &str) {
-        self.emit(Some(&sp.into()), msg, Bug);
+        self.emit(&sp.into(), msg, Bug);
         self.bump_err_count();
     }
     pub fn span_note_without_error<S: Into<MultiSpan>>(&self, sp: S, msg: &str) {
-        self.emit.borrow_mut().emit(Some(&sp.into()), msg, None, Note);
+        self.emit.borrow_mut().emit(&sp.into(), msg, None, Note);
     }
     pub fn span_unimpl<S: Into<MultiSpan>>(&self, sp: S, msg: &str) -> ! {
         self.span_bug(sp, &format!("unimplemented {}", msg));
@@ -554,7 +554,7 @@ impl Handler {
         if self.treat_err_as_bug {
             self.bug(msg);
         }
-        self.emit.borrow_mut().emit(None, msg, None, Fatal);
+        self.emit.borrow_mut().emit(&MultiSpan::new(), msg, None, Fatal);
         self.bump_err_count();
         FatalError
     }
@@ -562,17 +562,17 @@ impl Handler {
         if self.treat_err_as_bug {
             self.bug(msg);
         }
-        self.emit.borrow_mut().emit(None, msg, None, Error);
+        self.emit.borrow_mut().emit(&MultiSpan::new(), msg, None, Error);
         self.bump_err_count();
     }
     pub fn warn(&self, msg: &str) {
-        self.emit.borrow_mut().emit(None, msg, None, Warning);
+        self.emit.borrow_mut().emit(&MultiSpan::new(), msg, None, Warning);
     }
     pub fn note_without_error(&self, msg: &str) {
-        self.emit.borrow_mut().emit(None, msg, None, Note);
+        self.emit.borrow_mut().emit(&MultiSpan::new(), msg, None, Note);
     }
     pub fn bug(&self, msg: &str) -> ! {
-        self.emit.borrow_mut().emit(None, msg, None, Bug);
+        self.emit.borrow_mut().emit(&MultiSpan::new(), msg, None, Bug);
         panic!(ExplicitBug);
     }
     pub fn unimpl(&self, msg: &str) -> ! {
@@ -614,25 +614,20 @@ impl Handler {
         panic!(self.fatal(&s));
     }
     pub fn emit(&self,
-                msp: Option<&MultiSpan>,
+                msp: &MultiSpan,
                 msg: &str,
                 lvl: Level) {
         if lvl == Warning && !self.can_emit_warnings { return }
-        self.emit.borrow_mut().emit(msp, msg, None, lvl);
+        self.emit.borrow_mut().emit(&msp, msg, None, lvl);
         if !self.continue_after_error.get() { self.abort_if_errors(); }
     }
     pub fn emit_with_code(&self,
-                          msp: Option<&MultiSpan>,
+                          msp: &MultiSpan,
                           msg: &str,
                           code: &str,
                           lvl: Level) {
         if lvl == Warning && !self.can_emit_warnings { return }
-        self.emit.borrow_mut().emit(msp, msg, Some(code), lvl);
-        if !self.continue_after_error.get() { self.abort_if_errors(); }
-    }
-    pub fn custom_emit(&self, rsp: RenderSpan, msg: &str, lvl: Level) {
-        if lvl == Warning && !self.can_emit_warnings { return }
-        self.emit.borrow_mut().custom_emit(&rsp, msg, lvl);
+        self.emit.borrow_mut().emit(&msp, msg, Some(code), lvl);
         if !self.continue_after_error.get() { self.abort_if_errors(); }
     }
 }
@@ -662,7 +657,7 @@ impl Level {
     fn color(self) -> term::color::Color {
         match self {
             Bug | Fatal | PhaseFatal | Error => term::color::BRIGHT_RED,
-            Warning => term::color::BRIGHT_YELLOW,
+            Warning => term::color::YELLOW,
             Note => term::color::BRIGHT_GREEN,
             Help => term::color::BRIGHT_CYAN,
             Cancelled => unreachable!(),
@@ -689,3 +684,20 @@ pub fn expect<T, M>(diag: &Handler, opt: Option<T>, msg: M) -> T where
         None => diag.bug(&msg()),
     }
 }
+
+/// True if we should use the old-skool error format style. This is
+/// the default setting until the new errors are deemed stable enough
+/// for general use.
+///
+/// FIXME(#33240)
+#[cfg(not(test))]
+fn check_old_skool() -> bool {
+    use std::env;
+    env::var("RUST_NEW_ERROR_FORMAT").is_err()
+}
+
+/// For unit tests, use the new format.
+#[cfg(test)]
+fn check_old_skool() -> bool {
+    false
+}
diff --git a/src/libsyntax/errors/snippet/mod.rs b/src/libsyntax/errors/snippet/mod.rs
new file mode 100644
index 00000000000..e213f623ab8
--- /dev/null
+++ b/src/libsyntax/errors/snippet/mod.rs
@@ -0,0 +1,821 @@
+// 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 <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.
+
+// Code for annotating snippets.
+
+use codemap::{CharPos, CodeMap, FileMap, LineInfo, Span};
+use errors::check_old_skool;
+use std::cmp;
+use std::rc::Rc;
+use std::mem;
+
+mod test;
+
+#[derive(Clone)]
+pub struct SnippetData {
+    codemap: Rc<CodeMap>,
+    files: Vec<FileInfo>,
+}
+
+#[derive(Clone)]
+pub struct FileInfo {
+    file: Rc<FileMap>,
+
+    /// The "primary file", if any, gets a `-->` marker instead of
+    /// `>>>`, and has a line-number/column printed and not just a
+    /// filename.  It appears first in the listing. It is known to
+    /// contain at least one primary span, though primary spans (which
+    /// are designated with `^^^`) may also occur in other files.
+    primary_span: Option<Span>,
+
+    lines: Vec<Line>,
+}
+
+#[derive(Clone, Debug)]
+struct Line {
+    line_index: usize,
+    annotations: Vec<Annotation>,
+}
+
+#[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
+struct Annotation {
+    /// Start column, 0-based indexing -- counting *characters*, not
+    /// utf-8 bytes. Note that it is important that this field goes
+    /// first, so that when we sort, we sort orderings by start
+    /// column.
+    start_col: usize,
+
+    /// End column within the line (exclusive)
+    end_col: usize,
+
+    /// Is this annotation derived from primary span
+    is_primary: bool,
+
+    /// Optional label to display adjacent to the annotation.
+    label: Option<String>,
+}
+
+#[derive(Debug)]
+pub struct RenderedLine {
+    pub text: Vec<StyledString>,
+    pub kind: RenderedLineKind,
+}
+
+#[derive(Debug)]
+pub struct StyledString {
+    pub text: String,
+    pub style: Style,
+}
+
+#[derive(Debug)]
+pub struct StyledBuffer {
+    text: Vec<Vec<char>>,
+    styles: Vec<Vec<Style>>
+}
+
+#[derive(Copy, Clone, Debug, PartialEq)]
+pub enum Style {
+    FileNameStyle,
+    LineAndColumn,
+    LineNumber,
+    Quotation,
+    UnderlinePrimary,
+    UnderlineSecondary,
+    LabelPrimary,
+    LabelSecondary,
+    NoStyle,
+}
+
+#[derive(Debug, Clone)]
+pub enum RenderedLineKind {
+    PrimaryFileName,
+    OtherFileName,
+    SourceText {
+        file: Rc<FileMap>,
+        line_index: usize,
+    },
+    Annotations,
+    Elision,
+}
+
+impl SnippetData {
+    pub fn new(codemap: Rc<CodeMap>,
+               primary_span: Option<Span>) // (*)
+               -> Self {
+        // (*) The primary span indicates the file that must appear
+        // first, and which will have a line number etc in its
+        // name. Outside of tests, this is always `Some`, but for many
+        // tests it's not relevant to test this portion of the logic,
+        // and it's tedious to pick a primary span (read: tedious to
+        // port older tests that predate the existence of a primary
+        // span).
+
+        debug!("SnippetData::new(primary_span={:?})", primary_span);
+
+        let mut data = SnippetData {
+            codemap: codemap.clone(),
+            files: vec![]
+        };
+        if let Some(primary_span) = primary_span {
+            let lo = codemap.lookup_char_pos(primary_span.lo);
+            data.files.push(
+                FileInfo {
+                    file: lo.file,
+                    primary_span: Some(primary_span),
+                    lines: vec![],
+                });
+        }
+        data
+    }
+
+    pub fn push(&mut self, span: Span, is_primary: bool, label: Option<String>) {
+        debug!("SnippetData::push(span={:?}, is_primary={}, label={:?})",
+               span, is_primary, label);
+
+        let file_lines = match self.codemap.span_to_lines(span) {
+            Ok(file_lines) => file_lines,
+            Err(_) => {
+                // ignore unprintable spans completely.
+                return;
+            }
+        };
+
+        self.file(&file_lines.file)
+            .push_lines(&file_lines.lines, is_primary, label);
+    }
+
+    fn file(&mut self, file_map: &Rc<FileMap>) -> &mut FileInfo {
+        let index = self.files.iter().position(|f| f.file.name == file_map.name);
+        if let Some(index) = index {
+            return &mut self.files[index];
+        }
+
+        self.files.push(
+            FileInfo {
+                file: file_map.clone(),
+                lines: vec![],
+                primary_span: None,
+            });
+        self.files.last_mut().unwrap()
+    }
+
+    pub fn render_lines(&self) -> Vec<RenderedLine> {
+        debug!("SnippetData::render_lines()");
+
+        let mut rendered_lines: Vec<_> =
+            self.files.iter()
+                      .flat_map(|f| f.render_file_lines(&self.codemap))
+                      .collect();
+        prepend_prefixes(&mut rendered_lines);
+        trim_lines(&mut rendered_lines);
+        rendered_lines
+    }
+}
+
+pub trait StringSource {
+    fn make_string(self) -> String;
+}
+
+impl StringSource for String {
+    fn make_string(self) -> String {
+        self
+    }
+}
+
+impl StringSource for Vec<char> {
+    fn make_string(self) -> String {
+        self.into_iter().collect()
+    }
+}
+
+impl<S> From<(S, Style, RenderedLineKind)> for RenderedLine
+    where S: StringSource
+{
+    fn from((text, style, kind): (S, Style, RenderedLineKind)) -> Self {
+        RenderedLine {
+            text: vec![StyledString {
+                text: text.make_string(),
+                style: style,
+            }],
+            kind: kind,
+        }
+    }
+}
+
+impl<S1,S2> From<(S1, Style, S2, Style, RenderedLineKind)> for RenderedLine
+    where S1: StringSource, S2: StringSource
+{
+    fn from(tuple: (S1, Style, S2, Style, RenderedLineKind)) -> Self {
+        let (text1, style1, text2, style2, kind) = tuple;
+        RenderedLine {
+            text: vec![
+                StyledString {
+                    text: text1.make_string(),
+                    style: style1,
+                },
+                StyledString {
+                    text: text2.make_string(),
+                    style: style2,
+                }
+            ],
+            kind: kind,
+        }
+    }
+}
+
+impl RenderedLine {
+    fn trim_last(&mut self) {
+        if let Some(last_text) = self.text.last_mut() {
+            let len = last_text.text.trim_right().len();
+            last_text.text.truncate(len);
+        }
+    }
+}
+
+impl RenderedLineKind {
+    fn prefix(&self) -> StyledString {
+        match *self {
+            RenderedLineKind::SourceText { file: _, line_index } =>
+                StyledString {
+                    text: format!("{}", line_index + 1),
+                    style: Style::LineNumber,
+                },
+            RenderedLineKind::Elision =>
+                StyledString {
+                    text: String::from("..."),
+                    style: Style::LineNumber,
+                },
+            RenderedLineKind::PrimaryFileName |
+            RenderedLineKind::OtherFileName |
+            RenderedLineKind::Annotations =>
+                StyledString {
+                    text: String::from(""),
+                    style: Style::LineNumber,
+                },
+        }
+    }
+}
+
+impl StyledBuffer {
+    fn new() -> StyledBuffer {
+        StyledBuffer { text: vec![], styles: vec![] }
+    }
+
+    fn render(&self, source_kind: RenderedLineKind) -> Vec<RenderedLine> {
+        let mut output: Vec<RenderedLine> = vec![];
+        let mut styled_vec: Vec<StyledString> = vec![];
+
+        for (row, row_style) in self.text.iter().zip(&self.styles) {
+            let mut current_style = Style::NoStyle;
+            let mut current_text = String::new();
+
+            for (&c, &s) in row.iter().zip(row_style) {
+                if s != current_style {
+                    if !current_text.is_empty() {
+                        styled_vec.push(StyledString { text: current_text, style: current_style });
+                    }
+                    current_style = s;
+                    current_text = String::new();
+                }
+                current_text.push(c);
+            }
+            if !current_text.is_empty() {
+                styled_vec.push(StyledString { text: current_text, style: current_style });
+            }
+
+            if output.is_empty() {
+                //We know our first output line is source and the rest are highlights and labels
+                output.push(RenderedLine { text: styled_vec, kind: source_kind.clone() });
+            } else {
+                output.push(RenderedLine { text: styled_vec, kind: RenderedLineKind::Annotations });
+            }
+            styled_vec = vec![];
+        }
+
+        output
+    }
+
+    fn putc(&mut self, line: usize, col: usize, chr: char, style: Style) {
+        while line >= self.text.len() {
+            self.text.push(vec![]);
+            self.styles.push(vec![]);
+        }
+
+        if col < self.text[line].len() {
+            self.text[line][col] = chr;
+            self.styles[line][col] = style;
+        } else {
+            while self.text[line].len() < col {
+                self.text[line].push(' ');
+                self.styles[line].push(Style::NoStyle);
+            }
+            self.text[line].push(chr);
+            self.styles[line].push(style);
+        }
+    }
+
+    fn puts(&mut self, line: usize, col: usize, string: &str, style: Style) {
+        let mut n = col;
+        for c in string.chars() {
+            self.putc(line, n, c, style);
+            n += 1;
+        }
+    }
+
+    fn set_style(&mut self, line: usize, col: usize, style: Style) {
+        if self.styles.len() > line && self.styles[line].len() > col {
+            self.styles[line][col] = style;
+        }
+    }
+
+    fn append(&mut self, line: usize, string: &str, style: Style) {
+        if line >= self.text.len() {
+            self.puts(line, 0, string, style);
+        } else {
+            let col = self.text[line].len();
+            self.puts(line, col, string, style);
+        }
+    }
+}
+
+impl FileInfo {
+    fn push_lines(&mut self,
+                  lines: &[LineInfo],
+                  is_primary: bool,
+                  label: Option<String>) {
+        assert!(lines.len() > 0);
+
+        // If a span covers multiple lines, we reduce it to a single
+        // point at the start of the span. This means that instead
+        // of producing output like this:
+        //
+        // ```
+        // --> foo.rs:2:1
+        // 2   |> fn conflicting_items<'grammar>(state: &LR0State<'grammar>)
+        //     |> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+        // 3   |>                               -> Set<LR0Item<'grammar>>
+        //     |> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+        // (and so on)
+        // ```
+        //
+        // we produce:
+        //
+        // ```
+        // --> foo.rs:2:1
+        // 2   |> fn conflicting_items<'grammar>(state: &LR0State<'grammar>)
+        //        ^
+        // ```
+        //
+        // Basically, although this loses information, multi-line spans just
+        // never look good.
+
+        let (line, start_col, end_col) = if lines.len() == 1 {
+            (lines[0].line_index, lines[0].start_col, lines[0].end_col)
+        } else {
+            (lines[0].line_index, lines[0].start_col, CharPos(lines[0].start_col.0 + 1))
+        };
+        let index = self.ensure_source_line(line);
+        self.lines[index].push_annotation(start_col,
+                                          end_col,
+                                          is_primary,
+                                          label);
+    }
+
+    /// Ensure that we have a `Line` struct corresponding to
+    /// `line_index` in the file. If we already have some other lines,
+    /// then this will add the intervening lines to ensure that we
+    /// have a complete snippet. (Note that when we finally display,
+    /// some of those lines may be elided.)
+    fn ensure_source_line(&mut self, line_index: usize) -> usize {
+        if self.lines.is_empty() {
+            self.lines.push(Line::new(line_index));
+            return 0;
+        }
+
+        // Find the range of lines we have thus far.
+        let first_line_index = self.lines.first().unwrap().line_index;
+        let last_line_index = self.lines.last().unwrap().line_index;
+        assert!(first_line_index <= last_line_index);
+
+        // If the new line is lower than all the lines we have thus
+        // far, then insert the new line and any intervening lines at
+        // the front. In a silly attempt at micro-optimization, we
+        // don't just call `insert` repeatedly, but instead make a new
+        // (empty) vector, pushing the new lines onto it, and then
+        // appending the old vector.
+        if line_index < first_line_index {
+            let lines = mem::replace(&mut self.lines, vec![]);
+            self.lines.extend(
+                (line_index .. first_line_index)
+                    .map(|line| Line::new(line))
+                    .chain(lines));
+            return 0;
+        }
+
+        // If the new line comes after the ones we have so far, insert
+        // lines for it.
+        if line_index > last_line_index {
+            self.lines.extend(
+                (last_line_index+1 .. line_index+1)
+                    .map(|line| Line::new(line)));
+            return self.lines.len() - 1;
+        }
+
+        // Otherwise it should already exist.
+        return line_index - first_line_index;
+    }
+
+    fn render_file_lines(&self, codemap: &Rc<CodeMap>) -> Vec<RenderedLine> {
+        let old_school = check_old_skool();
+
+        // As a first step, we elide any instance of more than one
+        // continuous unannotated line.
+
+        let mut lines_iter = self.lines.iter();
+        let mut output = vec![];
+
+        // First insert the name of the file.
+        if !old_school {
+            match self.primary_span {
+                Some(span) => {
+                    let lo = codemap.lookup_char_pos(span.lo);
+                    output.push(RenderedLine {
+                        text: vec![StyledString {
+                            text: lo.file.name.clone(),
+                            style: Style::FileNameStyle,
+                        }, StyledString {
+                            text: format!(":{}:{}", lo.line, lo.col.0 + 1),
+                            style: Style::LineAndColumn,
+                        }],
+                        kind: RenderedLineKind::PrimaryFileName,
+                    });
+                }
+                None => {
+                    output.push(RenderedLine {
+                        text: vec![StyledString {
+                            text: self.file.name.clone(),
+                            style: Style::FileNameStyle,
+                        }],
+                        kind: RenderedLineKind::OtherFileName,
+                    });
+                }
+            }
+        }
+
+        let mut next_line = lines_iter.next();
+        while next_line.is_some() {
+            // Consume lines with annotations.
+            while let Some(line) = next_line {
+                if line.annotations.is_empty() { break; }
+
+                let mut rendered_lines = self.render_line(line);
+                assert!(!rendered_lines.is_empty());
+                if old_school {
+                    match self.primary_span {
+                        Some(span) => {
+                            let lo = codemap.lookup_char_pos(span.lo);
+                            rendered_lines[0].text.insert(0, StyledString {
+                                text: format!(":{} ", lo.line),
+                                style: Style::LineAndColumn,
+                            });
+                            rendered_lines[0].text.insert(0, StyledString {
+                                text: lo.file.name.clone(),
+                                style: Style::FileNameStyle,
+                            });
+                            let gap_amount =
+                                rendered_lines[0].text[0].text.len() +
+                                rendered_lines[0].text[1].text.len();
+                            assert!(rendered_lines.len() >= 2,
+                                    "no annotations resulted from: {:?}",
+                                    line);
+                            for i in 1..rendered_lines.len() {
+                                rendered_lines[i].text.insert(0, StyledString {
+                                    text: vec![" "; gap_amount].join(""),
+                                    style: Style::NoStyle
+                                });
+                            }
+                        }
+                        _ =>()
+                    }
+                }
+                output.append(&mut rendered_lines);
+                next_line = lines_iter.next();
+            }
+
+            // Emit lines without annotations, but only if they are
+            // followed by a line with an annotation.
+            let unannotated_line = next_line;
+            let mut unannotated_lines = 0;
+            while let Some(line) = next_line {
+                if !line.annotations.is_empty() { break; }
+                unannotated_lines += 1;
+                next_line = lines_iter.next();
+            }
+            if unannotated_lines > 1 {
+                output.push(RenderedLine::from((String::new(),
+                                                Style::NoStyle,
+                                                RenderedLineKind::Elision)));
+            } else if let Some(line) = unannotated_line {
+                output.append(&mut self.render_line(line));
+            }
+        }
+
+        output
+    }
+
+    fn render_line(&self, line: &Line) -> Vec<RenderedLine> {
+        let old_school = check_old_skool();
+        let source_string = self.file.get_line(line.line_index)
+                                     .unwrap_or("");
+        let source_kind = RenderedLineKind::SourceText {
+            file: self.file.clone(),
+            line_index: line.line_index,
+        };
+
+        let mut styled_buffer = StyledBuffer::new();
+
+        // First create the source line we will highlight.
+        styled_buffer.append(0, &source_string, Style::Quotation);
+
+        if line.annotations.is_empty() {
+            return styled_buffer.render(source_kind);
+        }
+
+        // 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 `~`.
+
+        //let mut highlight_line = Self::whitespace(&source_string);
+
+        // Sort the annotations by (start, end col)
+        let mut annotations = line.annotations.clone();
+        annotations.sort();
+
+        // Next, create the highlight line.
+        for annotation in &annotations {
+            if old_school {
+                for p in annotation.start_col .. annotation.end_col {
+                    if p == annotation.start_col {
+                        styled_buffer.putc(1, p, '^',
+                            if annotation.is_primary {
+                                Style::UnderlinePrimary
+                            } else {
+                                Style::UnderlineSecondary
+                            });
+                    }
+                    else {
+                        styled_buffer.putc(1, p, '~',
+                            if annotation.is_primary {
+                                Style::UnderlinePrimary
+                            } else {
+                                Style::UnderlineSecondary
+                            });
+                    }
+                }
+            }
+            else {
+                for p in annotation.start_col .. annotation.end_col {
+                    if annotation.is_primary {
+                        styled_buffer.putc(1, p, '^', Style::UnderlinePrimary);
+                        styled_buffer.set_style(0, p, Style::UnderlinePrimary);
+                    } else {
+                        styled_buffer.putc(1, p, '-', Style::UnderlineSecondary);
+                    }
+                }
+            }
+        }
+
+        // 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 styled_buffer.render(source_kind);
+        }
+        if old_school {
+            return styled_buffer.render(source_kind);
+        }
+
+        // Now add the text labels. 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*.
+        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 {
+                        styled_buffer.append(1, &highlight_label, Style::LabelPrimary);
+                    } else {
+                        styled_buffer.append(1, &highlight_label, Style::LabelSecondary);
+                    }
+                    labeled_annotations = previous;
+                }
+            }
+        }
+
+        // If that's the last annotation, we're done
+        if labeled_annotations.is_empty() {
+            return styled_buffer.render(source_kind);
+        }
+
+        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 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 {
+                    styled_buffer.putc(index, annotation.start_col, '|', Style::UnderlinePrimary);
+                } else {
+                    styled_buffer.putc(index, annotation.start_col, '|', Style::UnderlineSecondary);
+                }
+            }
+
+            if annotation.is_primary {
+                styled_buffer.puts(blank_lines, annotation.start_col,
+                    annotation.label.as_ref().unwrap(), Style::LabelPrimary);
+            } else {
+                styled_buffer.puts(blank_lines, annotation.start_col,
+                    annotation.label.as_ref().unwrap(), Style::LabelSecondary);
+            }
+        }
+
+        styled_buffer.render(source_kind)
+    }
+}
+
+fn prepend_prefixes(rendered_lines: &mut [RenderedLine]) {
+    let old_school = check_old_skool();
+    if old_school {
+        return;
+    }
+
+    let prefixes: Vec<_> =
+        rendered_lines.iter()
+                      .map(|rl| rl.kind.prefix())
+                      .collect();
+
+    // find the max amount of spacing we need; add 1 to
+    // p.text.len() to leave space between the prefix and the
+    // source text
+    let padding_len =
+        prefixes.iter()
+                .map(|p| if p.text.len() == 0 { 0 } else { p.text.len() + 1 })
+                .max()
+                .unwrap_or(0);
+
+    // Ensure we insert at least one character of padding, so that the
+    // `-->` arrows can fit etc.
+    let padding_len = cmp::max(padding_len, 1);
+
+    for (mut prefix, line) in prefixes.into_iter().zip(rendered_lines) {
+        let extra_spaces = (prefix.text.len() .. padding_len).map(|_| ' ');
+        prefix.text.extend(extra_spaces);
+        match line.kind {
+            RenderedLineKind::Elision => {
+                line.text.insert(0, prefix);
+            }
+            RenderedLineKind::PrimaryFileName => {
+                //   --> filename
+                // 22 |>
+                //   ^
+                //   padding_len
+                let dashes = (0..padding_len - 1).map(|_| ' ')
+                                                 .chain(Some('-'))
+                                                 .chain(Some('-'))
+                                                 .chain(Some('>'))
+                                                 .chain(Some(' '));
+                line.text.insert(0, StyledString {text: dashes.collect(),
+                                                  style: Style::LineNumber})
+            }
+            RenderedLineKind::OtherFileName => {
+                //   ::: filename
+                // 22 |>
+                //   ^
+                //   padding_len
+                let dashes = (0..padding_len - 1).map(|_| ' ')
+                                                 .chain(Some(':'))
+                                                 .chain(Some(':'))
+                                                 .chain(Some(':'))
+                                                 .chain(Some(' '));
+                line.text.insert(0, StyledString {text: dashes.collect(),
+                                                  style: Style::LineNumber})
+            }
+            _ => {
+                line.text.insert(0, prefix);
+                line.text.insert(1, StyledString {text: String::from("|> "),
+                                                  style: Style::LineNumber})
+            }
+        }
+    }
+}
+
+fn trim_lines(rendered_lines: &mut [RenderedLine]) {
+    for line in rendered_lines {
+        while !line.text.is_empty() {
+            line.trim_last();
+            if line.text.last().unwrap().text.is_empty() {
+                line.text.pop();
+            } else {
+                break;
+            }
+        }
+    }
+}
+
+impl Line {
+    fn new(line_index: usize) -> Line {
+        Line {
+            line_index: line_index,
+            annotations: vec![]
+        }
+    }
+
+    fn push_annotation(&mut self,
+                       start: CharPos,
+                       end: CharPos,
+                       is_primary: bool,
+                       label: Option<String>) {
+        self.annotations.push(Annotation {
+            start_col: start.0,
+            end_col: end.0,
+            is_primary: is_primary,
+            label: label,
+        });
+    }
+}
+
+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)
+}
diff --git a/src/libsyntax/errors/snippet/test.rs b/src/libsyntax/errors/snippet/test.rs
new file mode 100644
index 00000000000..569d1119919
--- /dev/null
+++ b/src/libsyntax/errors/snippet/test.rs
@@ -0,0 +1,521 @@
+// 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.
+
+// Code for testing annotated snippets.
+
+#![cfg(test)]
+
+use codemap::{BytePos, CodeMap, FileMap, NO_EXPANSION, Span};
+use std::rc::Rc;
+use super::{RenderedLine, SnippetData};
+
+/// Returns the span corresponding to the `n`th occurrence of
+/// `substring` in `source_text`.
+trait CodeMapExtension {
+    fn span_substr(&self,
+                   file: &Rc<FileMap>,
+                   source_text: &str,
+                   substring: &str,
+                   n: usize)
+                   -> Span;
+}
+
+impl CodeMapExtension for CodeMap {
+    fn span_substr(&self,
+                   file: &Rc<FileMap>,
+                   source_text: &str,
+                   substring: &str,
+                   n: usize)
+                   -> Span
+    {
+        println!("span_substr(file={:?}/{:?}, substring={:?}, n={})",
+                 file.name, file.start_pos, substring, n);
+        let mut i = 0;
+        let mut hi = 0;
+        loop {
+            let offset = source_text[hi..].find(substring).unwrap_or_else(|| {
+                panic!("source_text `{}` does not have {} occurrences of `{}`, only {}",
+                       source_text, n, substring, i);
+            });
+            let lo = hi + offset;
+            hi = lo + substring.len();
+            if i == n {
+                let span = Span {
+                    lo: BytePos(lo as u32 + file.start_pos.0),
+                    hi: BytePos(hi as u32 + file.start_pos.0),
+                    expn_id: NO_EXPANSION,
+                };
+                assert_eq!(&self.span_to_snippet(span).unwrap()[..],
+                           substring);
+                return span;
+            }
+            i += 1;
+        }
+    }
+}
+
+fn splice(start: Span, end: Span) -> Span {
+    Span {
+        lo: start.lo,
+        hi: end.hi,
+        expn_id: NO_EXPANSION,
+    }
+}
+
+fn make_string(lines: &[RenderedLine]) -> String {
+    lines.iter()
+         .flat_map(|rl| {
+             rl.text.iter()
+                    .map(|s| &s.text[..])
+                    .chain(Some("\n"))
+         })
+         .collect()
+}
+
+#[test]
+fn one_line() {
+    let file_text = r#"
+fn foo() {
+    vec.push(vec.pop().unwrap());
+}
+"#;
+
+    let cm = Rc::new(CodeMap::new());
+    let foo = cm.new_filemap_and_lines("foo.rs", file_text);
+    let span_vec0 = cm.span_substr(&foo, file_text, "vec", 0);
+    let span_vec1 = cm.span_substr(&foo, file_text, "vec", 1);
+    let span_semi = cm.span_substr(&foo, file_text, ";", 0);
+
+    let mut snippet = SnippetData::new(cm, None);
+    snippet.push(span_vec0, false, Some(format!("previous borrow of `vec` occurs here")));
+    snippet.push(span_vec1, false, Some(format!("error occurs here")));
+    snippet.push(span_semi, false, Some(format!("previous borrow ends here")));
+
+    let lines = snippet.render_lines();
+    println!("{:#?}", lines);
+
+    let text: String = make_string(&lines);
+
+    println!("text=\n{}", text);
+    assert_eq!(&text[..], &r#"
+ ::: foo.rs
+3 |>     vec.push(vec.pop().unwrap());
+  |>     ---      ---                - previous borrow ends here
+  |>     |        |
+  |>     |        error occurs here
+  |>     previous borrow of `vec` occurs here
+"#[1..]);
+}
+
+#[test]
+fn two_files() {
+    let file_text_foo = r#"
+fn foo() {
+    vec.push(vec.pop().unwrap());
+}
+"#;
+
+    let file_text_bar = r#"
+fn bar() {
+    // these blank links here
+    // serve to ensure that the line numbers
+    // from bar.rs
+    // require more digits
+
+
+
+
+
+
+
+
+
+
+    vec.push();
+
+    // this line will get elided
+
+    vec.pop().unwrap());
+}
+"#;
+
+    let cm = Rc::new(CodeMap::new());
+    let foo_map = cm.new_filemap_and_lines("foo.rs", file_text_foo);
+    let span_foo_vec0 = cm.span_substr(&foo_map, file_text_foo, "vec", 0);
+    let span_foo_vec1 = cm.span_substr(&foo_map, file_text_foo, "vec", 1);
+    let span_foo_semi = cm.span_substr(&foo_map, file_text_foo, ";", 0);
+
+    let bar_map = cm.new_filemap_and_lines("bar.rs", file_text_bar);
+    let span_bar_vec0 = cm.span_substr(&bar_map, file_text_bar, "vec", 0);
+    let span_bar_vec1 = cm.span_substr(&bar_map, file_text_bar, "vec", 1);
+    let span_bar_semi = cm.span_substr(&bar_map, file_text_bar, ";", 0);
+
+    let mut snippet = SnippetData::new(cm, Some(span_foo_vec1));
+    snippet.push(span_foo_vec0, false, Some(format!("a")));
+    snippet.push(span_foo_vec1, true, Some(format!("b")));
+    snippet.push(span_foo_semi, false, Some(format!("c")));
+    snippet.push(span_bar_vec0, false, Some(format!("d")));
+    snippet.push(span_bar_vec1, false, Some(format!("e")));
+    snippet.push(span_bar_semi, false, Some(format!("f")));
+
+    let lines = snippet.render_lines();
+    println!("{:#?}", lines);
+
+    let text: String = make_string(&lines);
+
+    println!("text=\n{}", text);
+
+    // Note that the `|>` remain aligned across both files:
+    assert_eq!(&text[..], &r#"
+   --> foo.rs:3:14
+3   |>     vec.push(vec.pop().unwrap());
+    |>     ---      ^^^                - c
+    |>     |        |
+    |>     |        b
+    |>     a
+   ::: bar.rs
+17  |>     vec.push();
+    |>     ---       - f
+    |>     |
+    |>     d
+...
+21  |>     vec.pop().unwrap());
+    |>     --- e
+"#[1..]);
+}
+
+#[test]
+fn multi_line() {
+    let file_text = r#"
+fn foo() {
+    let name = find_id(&data, 22).unwrap();
+
+    // Add one more item we forgot to the vector. Silly us.
+    data.push(Data { name: format!("Hera"), id: 66 });
+
+    // Print everything out.
+    println!("Name: {:?}", name);
+    println!("Data: {:?}", data);
+}
+"#;
+
+    let cm = Rc::new(CodeMap::new());
+    let foo = cm.new_filemap_and_lines("foo.rs", file_text);
+    let span_data0 = cm.span_substr(&foo, file_text, "data", 0);
+    let span_data1 = cm.span_substr(&foo, file_text, "data", 1);
+    let span_rbrace = cm.span_substr(&foo, file_text, "}", 3);
+
+    let mut snippet = SnippetData::new(cm, None);
+    snippet.push(span_data0, false, Some(format!("immutable borrow begins here")));
+    snippet.push(span_data1, false, Some(format!("mutable borrow occurs here")));
+    snippet.push(span_rbrace, false, Some(format!("immutable borrow ends here")));
+
+    let lines = snippet.render_lines();
+    println!("{:#?}", lines);
+
+    let text: String = make_string(&lines);
+
+    println!("text=\n{}", text);
+    assert_eq!(&text[..], &r#"
+   ::: foo.rs
+3   |>     let name = find_id(&data, 22).unwrap();
+    |>                         ---- immutable borrow begins here
+...
+6   |>     data.push(Data { name: format!("Hera"), id: 66 });
+    |>     ---- mutable borrow occurs here
+...
+11  |> }
+    |> - immutable borrow ends here
+"#[1..]);
+}
+
+#[test]
+fn overlapping() {
+    let file_text = r#"
+fn foo() {
+    vec.push(vec.pop().unwrap());
+}
+"#;
+
+    let cm = Rc::new(CodeMap::new());
+    let foo = cm.new_filemap_and_lines("foo.rs", file_text);
+    let span0 = cm.span_substr(&foo, file_text, "vec.push", 0);
+    let span1 = cm.span_substr(&foo, file_text, "vec", 0);
+    let span2 = cm.span_substr(&foo, file_text, "ec.push", 0);
+    let span3 = cm.span_substr(&foo, file_text, "unwrap", 0);
+
+    let mut snippet = SnippetData::new(cm, None);
+    snippet.push(span0, false, Some(format!("A")));
+    snippet.push(span1, false, Some(format!("B")));
+    snippet.push(span2, false, Some(format!("C")));
+    snippet.push(span3, false, Some(format!("D")));
+
+    let lines = snippet.render_lines();
+    println!("{:#?}", lines);
+    let text: String = make_string(&lines);
+
+    println!("text=r#\"\n{}\".trim_left()", text);
+    assert_eq!(&text[..], &r#"
+ ::: foo.rs
+3 |>     vec.push(vec.pop().unwrap());
+  |>     --------           ------ D
+  |>     ||
+  |>     |C
+  |>     A
+  |>     B
+"#[1..]);
+}
+
+#[test]
+fn one_line_out_of_order() {
+    let file_text = r#"
+fn foo() {
+    vec.push(vec.pop().unwrap());
+}
+"#;
+
+    let cm = Rc::new(CodeMap::new());
+    let foo = cm.new_filemap_and_lines("foo.rs", file_text);
+    let span_vec0 = cm.span_substr(&foo, file_text, "vec", 0);
+    let span_vec1 = cm.span_substr(&foo, file_text, "vec", 1);
+    let span_semi = cm.span_substr(&foo, file_text, ";", 0);
+
+    // intentionally don't push the snippets left to right
+    let mut snippet = SnippetData::new(cm, None);
+    snippet.push(span_vec1, false, Some(format!("error occurs here")));
+    snippet.push(span_vec0, false, Some(format!("previous borrow of `vec` occurs here")));
+    snippet.push(span_semi, false, Some(format!("previous borrow ends here")));
+
+    let lines = snippet.render_lines();
+    println!("{:#?}", lines);
+    let text: String = make_string(&lines);
+
+    println!("text=r#\"\n{}\".trim_left()", text);
+    assert_eq!(&text[..], &r#"
+ ::: foo.rs
+3 |>     vec.push(vec.pop().unwrap());
+  |>     ---      ---                - previous borrow ends here
+  |>     |        |
+  |>     |        error occurs here
+  |>     previous borrow of `vec` occurs here
+"#[1..]);
+}
+
+#[test]
+fn elide_unnecessary_lines() {
+    let file_text = r#"
+fn foo() {
+    let mut vec = vec![0, 1, 2];
+    let mut vec2 = vec;
+    vec2.push(3);
+    vec2.push(4);
+    vec2.push(5);
+    vec2.push(6);
+    vec.push(7);
+}
+"#;
+
+    let cm = Rc::new(CodeMap::new());
+    let foo = cm.new_filemap_and_lines("foo.rs", file_text);
+    let span_vec0 = cm.span_substr(&foo, file_text, "vec", 3);
+    let span_vec1 = cm.span_substr(&foo, file_text, "vec", 8);
+
+    let mut snippet = SnippetData::new(cm, None);
+    snippet.push(span_vec0, false, Some(format!("`vec` moved here because it \
+        has type `collections::vec::Vec<i32>`")));
+    snippet.push(span_vec1, false, Some(format!("use of moved value: `vec`")));
+
+    let lines = snippet.render_lines();
+    println!("{:#?}", lines);
+    let text: String = make_string(&lines);
+    println!("text=r#\"\n{}\".trim_left()", text);
+    assert_eq!(&text[..], &r#"
+   ::: foo.rs
+4   |>     let mut vec2 = vec;
+    |>                    --- `vec` moved here because it has type `collections::vec::Vec<i32>`
+...
+9   |>     vec.push(7);
+    |>     --- use of moved value: `vec`
+"#[1..]);
+}
+
+#[test]
+fn spans_without_labels() {
+    let file_text = r#"
+fn foo() {
+    let mut vec = vec![0, 1, 2];
+    let mut vec2 = vec;
+    vec2.push(3);
+    vec2.push(4);
+    vec2.push(5);
+    vec2.push(6);
+    vec.push(7);
+}
+"#;
+
+    let cm = Rc::new(CodeMap::new());
+    let foo = cm.new_filemap_and_lines("foo.rs", file_text);
+
+    let mut snippet = SnippetData::new(cm.clone(), None);
+    for i in 0..4 {
+        let span_veci = cm.span_substr(&foo, file_text, "vec", i);
+        snippet.push(span_veci, false, None);
+    }
+
+    let lines = snippet.render_lines();
+    let text: String = make_string(&lines);
+    println!("text=&r#\"\n{}\n\"#[1..]", text);
+    assert_eq!(text, &r#"
+ ::: foo.rs
+3 |>     let mut vec = vec![0, 1, 2];
+  |>             ---   ---
+4 |>     let mut vec2 = vec;
+  |>             ---    ---
+"#[1..]);
+}
+
+#[test]
+fn span_long_selection() {
+    let file_text = r#"
+impl SomeTrait for () {
+    fn foo(x: u32) {
+        // impl 1
+        // impl 2
+        // impl 3
+    }
+}
+"#;
+
+    let cm = Rc::new(CodeMap::new());
+    let foo = cm.new_filemap_and_lines("foo.rs", file_text);
+
+    let mut snippet = SnippetData::new(cm.clone(), None);
+    let fn_span = cm.span_substr(&foo, file_text, "fn", 0);
+    let rbrace_span = cm.span_substr(&foo, file_text, "}", 0);
+    snippet.push(splice(fn_span, rbrace_span), false, None);
+    let lines = snippet.render_lines();
+    let text: String = make_string(&lines);
+    println!("r#\"\n{}\"", text);
+    assert_eq!(text, &r#"
+ ::: foo.rs
+3 |>     fn foo(x: u32) {
+  |>     -
+"#[1..]);
+}
+
+#[test]
+fn span_overlap_label() {
+    // Test that we don't put `x_span` to the right of its highlight,
+    // since there is another highlight that overlaps it.
+
+    let file_text = r#"
+    fn foo(x: u32) {
+    }
+}
+"#;
+
+    let cm = Rc::new(CodeMap::new());
+    let foo = cm.new_filemap_and_lines("foo.rs", file_text);
+
+    let mut snippet = SnippetData::new(cm.clone(), None);
+    let fn_span = cm.span_substr(&foo, file_text, "fn foo(x: u32)", 0);
+    let x_span = cm.span_substr(&foo, file_text, "x", 0);
+    snippet.push(fn_span, false, Some(format!("fn_span")));
+    snippet.push(x_span, false, Some(format!("x_span")));
+    let lines = snippet.render_lines();
+    let text: String = make_string(&lines);
+    println!("r#\"\n{}\"", text);
+    assert_eq!(text, &r#"
+ ::: foo.rs
+2 |>     fn foo(x: u32) {
+  |>     --------------
+  |>     |      |
+  |>     |      x_span
+  |>     fn_span
+"#[1..]);
+}
+
+#[test]
+fn span_overlap_label2() {
+    // Test that we don't put `x_span` to the right of its highlight,
+    // since there is another highlight that overlaps it. In this
+    // case, the overlap is only at the beginning, but it's still
+    // better to show the beginning more clearly.
+
+    let file_text = r#"
+    fn foo(x: u32) {
+    }
+}
+"#;
+
+    let cm = Rc::new(CodeMap::new());
+    let foo = cm.new_filemap_and_lines("foo.rs", file_text);
+
+    let mut snippet = SnippetData::new(cm.clone(), None);
+    let fn_span = cm.span_substr(&foo, file_text, "fn foo(x", 0);
+    let x_span = cm.span_substr(&foo, file_text, "x: u32)", 0);
+    snippet.push(fn_span, false, Some(format!("fn_span")));
+    snippet.push(x_span, false, Some(format!("x_span")));
+    let lines = snippet.render_lines();
+    let text: String = make_string(&lines);
+    println!("r#\"\n{}\"", text);
+    assert_eq!(text, &r#"
+ ::: foo.rs
+2 |>     fn foo(x: u32) {
+  |>     --------------
+  |>     |      |
+  |>     |      x_span
+  |>     fn_span
+"#[1..]);
+}
+
+#[test]
+fn span_overlap_label3() {
+    // Test that we don't put `x_span` to the right of its highlight,
+    // since there is another highlight that overlaps it. In this
+    // case, the overlap is only at the beginning, but it's still
+    // better to show the beginning more clearly.
+
+    let file_text = r#"
+    fn foo() {
+       let closure = || {
+           inner
+       };
+    }
+}
+"#;
+
+    let cm = Rc::new(CodeMap::new());
+    let foo = cm.new_filemap_and_lines("foo.rs", file_text);
+
+    let mut snippet = SnippetData::new(cm.clone(), None);
+
+    let closure_span = {
+        let closure_start_span = cm.span_substr(&foo, file_text, "||", 0);
+        let closure_end_span = cm.span_substr(&foo, file_text, "}", 0);
+        splice(closure_start_span, closure_end_span)
+    };
+
+    let inner_span = cm.span_substr(&foo, file_text, "inner", 0);
+
+    snippet.push(closure_span, false, Some(format!("foo")));
+    snippet.push(inner_span, false, Some(format!("bar")));
+
+    let lines = snippet.render_lines();
+    let text: String = make_string(&lines);
+    println!("r#\"\n{}\"", text);
+    assert_eq!(text, &r#"
+ ::: foo.rs
+3 |>        let closure = || {
+  |>                      - foo
+4 |>            inner
+  |>            ----- bar
+"#[1..]);
+}