about summary refs log tree commit diff
path: root/src/libsyntax/errors
diff options
context:
space:
mode:
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..]);
+}