about summary refs log tree commit diff
path: root/src/libsyntax/errors
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2016-01-28 22:13:25 +0000
committerbors <bors@rust-lang.org>2016-01-28 22:13:25 +0000
commit142214d1f2232a4e88ff7bd99951b01f36052c61 (patch)
tree1d6bb4858399c46d39d9f85076cef439fb899f94 /src/libsyntax/errors
parent552bf75e7d689c42febc7798d31ae58d614418f9 (diff)
parent727f959095023d9fa749acbba49a4a904c57356b (diff)
downloadrust-142214d1f2232a4e88ff7bd99951b01f36052c61.tar.gz
rust-142214d1f2232a4e88ff7bd99951b01f36052c61.zip
Auto merge of #30411 - mitaa:multispan, r=nrc
This allows to render multiple spans on one line, or to splice multiple replacements into a code suggestion.

fixes #28124
Diffstat (limited to 'src/libsyntax/errors')
-rw-r--r--src/libsyntax/errors/emitter.rs813
-rw-r--r--src/libsyntax/errors/json.rs112
-rw-r--r--src/libsyntax/errors/mod.rs325
3 files changed, 873 insertions, 377 deletions
diff --git a/src/libsyntax/errors/emitter.rs b/src/libsyntax/errors/emitter.rs
index 51013d68930..c1239bfd66d 100644
--- a/src/libsyntax/errors/emitter.rs
+++ b/src/libsyntax/errors/emitter.rs
@@ -10,10 +10,10 @@
 
 use self::Destination::*;
 
-use codemap::{self, COMMAND_LINE_SP, COMMAND_LINE_EXPN, DUMMY_SP, Pos, Span};
+use codemap::{self, COMMAND_LINE_SP, COMMAND_LINE_EXPN, DUMMY_SP, Pos, Span, MultiSpan};
 use diagnostics;
 
-use errors::{Level, RenderSpan, DiagnosticBuilder};
+use errors::{Level, RenderSpan, CodeSuggestion, DiagnosticBuilder};
 use errors::RenderSpan::*;
 use errors::Level::*;
 
@@ -23,25 +23,27 @@ use std::io;
 use std::rc::Rc;
 use term;
 
-
 pub trait Emitter {
-    fn emit(&mut self, span: Option<Span>, msg: &str, code: Option<&str>, lvl: Level);
-    fn custom_emit(&mut self, sp: RenderSpan, msg: &str, lvl: Level);
+    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 structured diagnostic.
     fn emit_struct(&mut self, db: &DiagnosticBuilder) {
-        self.emit(db.span, &db.message, db.code.as_ref().map(|s| &**s), db.level);
+        self.emit(db.span.as_ref(), &db.message, db.code.as_ref().map(|s| &**s), db.level);
         for child in &db.children {
             match child.render_span {
-                Some(ref sp) => self.custom_emit(sp.clone(), &child.message, child.level),
-                None => self.emit(child.span, &child.message, None, child.level),
+                Some(ref sp) => self.custom_emit(sp, &child.message, child.level),
+                None => self.emit(child.span.as_ref(), &child.message, None, child.level),
             }
         }
     }
 }
 
 /// maximum number of lines we will print for each error; arbitrary.
-const MAX_LINES: usize = 6;
+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 {
@@ -68,18 +70,18 @@ pub struct BasicEmitter {
 
 impl Emitter for BasicEmitter {
     fn emit(&mut self,
-            sp: Option<Span>,
+            msp: Option<&MultiSpan>,
             msg: &str,
             code: Option<&str>,
             lvl: Level) {
-        assert!(sp.is_none(), "BasicEmitter can't handle spans");
+        assert!(msp.is_none(), "BasicEmitter can't handle spans");
         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) {
+    fn custom_emit(&mut self, _: &RenderSpan, _: &str, _: Level) {
         panic!("BasicEmitter can't handle custom_emit");
     }
 }
@@ -103,14 +105,16 @@ pub struct EmitterWriter {
 
 impl Emitter for EmitterWriter {
     fn emit(&mut self,
-            sp: Option<Span>,
+            msp: Option<&MultiSpan>,
             msg: &str,
             code: Option<&str>,
             lvl: Level) {
-        let error = match sp {
-            Some(COMMAND_LINE_SP) => self.emit_(FileLine(COMMAND_LINE_SP), msg, code, lvl),
-            Some(DUMMY_SP) | None => print_diagnostic(&mut self.dst, "", lvl, msg, code),
-            Some(sp) => self.emit_(FullSpan(sp), msg, code, lvl),
+        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),
         };
 
         if let Err(e) = error {
@@ -119,10 +123,10 @@ impl Emitter for EmitterWriter {
     }
 
     fn custom_emit(&mut self,
-                   sp: RenderSpan,
+                   rsp: &RenderSpan,
                    msg: &str,
                    lvl: Level) {
-        if let Err(e) = self.emit_(sp, msg, None, lvl) {
+        if let Err(e) = self.emit_(rsp, msg, None, lvl) {
             panic!("failed to print diagnostics: {:?}", e);
         }
     }
@@ -163,114 +167,93 @@ impl EmitterWriter {
     }
 
     fn emit_(&mut self,
-             rsp: RenderSpan,
+             rsp: &RenderSpan,
              msg: &str,
              code: Option<&str>,
              lvl: Level)
              -> io::Result<()> {
-        let sp = rsp.span();
+        let msp = rsp.span();
+        let bounds = msp.to_span_bounds();
 
         // We cannot check equality directly with COMMAND_LINE_SP
         // since PartialEq is manually implemented to ignore the ExpnId
-        let ss = if sp.expn_id == COMMAND_LINE_EXPN {
+        let ss = if bounds.expn_id == COMMAND_LINE_EXPN {
             "<command line option>".to_string()
-        } else if let EndSpan(_) = rsp {
-            let span_end = Span { lo: sp.hi, hi: sp.hi, expn_id: sp.expn_id};
+        } 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(sp)
+            self.cm.span_to_string(bounds)
         };
 
         try!(print_diagnostic(&mut self.dst, &ss[..], lvl, msg, code));
 
-        match rsp {
+        match *rsp {
             FullSpan(_) => {
-                let lines = self.cm.span_to_lines(sp);
-                try!(self.highlight_lines(sp, lvl, lines));
-                try!(self.print_macro_backtrace(sp));
+                try!(self.highlight_lines(msp, lvl));
+                try!(self.print_macro_backtrace(bounds));
             }
             EndSpan(_) => {
-                let lines = self.cm.span_to_lines(sp);
-                try!(self.end_highlight_lines(sp, lvl, lines));
-                try!(self.print_macro_backtrace(sp));
+                try!(self.end_highlight_lines(msp, lvl));
+                try!(self.print_macro_backtrace(bounds));
             }
-            Suggestion(_, ref suggestion) => {
-                try!(self.highlight_suggestion(sp, suggestion));
-                try!(self.print_macro_backtrace(sp));
+            Suggestion(ref suggestion) => {
+                try!(self.highlight_suggestion(suggestion));
+                try!(self.print_macro_backtrace(bounds));
             }
             FileLine(..) => {
                 // no source text in this case!
             }
         }
 
-        match code {
-            Some(code) =>
-                match self.registry.as_ref().and_then(|registry| registry.find_description(code)) {
-                    Some(_) => {
-                        try!(print_diagnostic(&mut self.dst, &ss[..], Help,
-                                              &format!("run `rustc --explain {}` to see a \
-                                                       detailed explanation", code), None));
-                    }
-                    None => ()
-                },
-            None => (),
+        if let Some(code) = code {
+            if let Some(_) = self.registry.as_ref()
+                                          .and_then(|registry| registry.find_description(code)) {
+                try!(print_diagnostic(&mut self.dst, &ss[..], Help,
+                                      &format!("run `rustc --explain {}` to see a \
+                                               detailed explanation", code), None));
+            }
         }
         Ok(())
     }
 
-    fn highlight_suggestion(&mut self,
-                            sp: Span,
-                            suggestion: &str)
-                            -> io::Result<()>
+    fn highlight_suggestion(&mut self, suggestion: &CodeSuggestion) -> io::Result<()>
     {
-        let lines = self.cm.span_to_lines(sp).unwrap();
+        let lines = self.cm.span_to_lines(suggestion.msp.to_span_bounds()).unwrap();
         assert!(!lines.lines.is_empty());
 
-        // To build up the result, we want to take the snippet from the first
-        // line that precedes the span, prepend that with the suggestion, and
-        // then append the snippet from the last line that trails the span.
-        let fm = &lines.file;
-
-        let first_line = &lines.lines[0];
-        let prefix = fm.get_line(first_line.line_index)
-                       .map(|l| &l[..first_line.start_col.0])
-                       .unwrap_or("");
+        let complete = suggestion.splice_lines(&self.cm);
+        let line_count = cmp::min(lines.lines.len(), MAX_HIGHLIGHT_LINES);
+        let display_lines = &lines.lines[..line_count];
 
-        let last_line = lines.lines.last().unwrap();
-        let suffix = fm.get_line(last_line.line_index)
-                       .map(|l| &l[last_line.end_col.0..])
-                       .unwrap_or("");
-
-        let complete = format!("{}{}{}", prefix, suggestion, suffix);
+        let fm = &*lines.file;
+        // Calculate the widest number to format evenly
+        let max_digits = line_num_max_digits(display_lines.last().unwrap());
 
         // print the suggestion without any line numbers, but leave
         // space for them. This helps with lining up with previous
         // snippets from the actual error being reported.
-        let fm = &*lines.file;
         let mut lines = complete.lines();
-        for (line, line_index) in lines.by_ref().take(MAX_LINES).zip(first_line.line_index..) {
-            let elided_line_num = format!("{}", line_index+1);
+        for line in lines.by_ref().take(MAX_HIGHLIGHT_LINES) {
             try!(write!(&mut self.dst, "{0}:{1:2$} {3}\n",
-                        fm.name, "", elided_line_num.len(), line));
+                        fm.name, "", max_digits, line));
         }
 
         // if we elided some lines, add an ellipsis
-        if lines.next().is_some() {
-            let elided_line_num = format!("{}", first_line.line_index + MAX_LINES + 1);
+        if let Some(_) = lines.next() {
             try!(write!(&mut self.dst, "{0:1$} {0:2$} ...\n",
-                        "", fm.name.len(), elided_line_num.len()));
+                        "", fm.name.len(), max_digits));
         }
 
         Ok(())
     }
 
     fn highlight_lines(&mut self,
-                       sp: Span,
-                       lvl: Level,
-                       lines: codemap::FileLinesResult)
+                       msp: &MultiSpan,
+                       lvl: Level)
                        -> io::Result<()>
     {
-        let lines = match lines {
+        let lines = match self.cm.span_to_lines(msp.to_span_bounds()) {
             Ok(lines) => lines,
             Err(_) => {
                 try!(write!(&mut self.dst, "(internal compiler error: unprintable span)\n"));
@@ -279,73 +262,111 @@ impl EmitterWriter {
         };
 
         let fm = &*lines.file;
+        if let None = fm.src {
+            return Ok(());
+        }
 
-        let line_strings: Option<Vec<&str>> =
-            lines.lines.iter()
-                       .map(|info| fm.get_line(info.line_index))
-                       .collect();
-
-        let line_strings = match line_strings {
-            None => { return Ok(()); }
-            Some(line_strings) => line_strings
-        };
-
-        // Display only the first MAX_LINES lines.
-        let all_lines = lines.lines.len();
-        let display_lines = cmp::min(all_lines, MAX_LINES);
-        let display_line_infos = &lines.lines[..display_lines];
-        let display_line_strings = &line_strings[..display_lines];
+        let display_line_infos = &lines.lines[..];
+        assert!(display_line_infos.len() > 0);
 
         // Calculate the widest number to format evenly and fix #11715
-        assert!(display_line_infos.len() > 0);
-        let mut max_line_num = display_line_infos[display_line_infos.len() - 1].line_index + 1;
-        let mut digits = 0;
-        while max_line_num > 0 {
-            max_line_num /= 10;
-            digits += 1;
-        }
+        let digits = line_num_max_digits(display_line_infos.last().unwrap());
+        let first_line_index = display_line_infos.first().unwrap().line_index;
 
-        // Print the offending lines
-        for (line_info, line) in display_line_infos.iter().zip(display_line_strings) {
-            try!(write!(&mut self.dst, "{}:{:>width$} {}\n",
-                        fm.name,
-                        line_info.line_index + 1,
-                        line,
-                        width=digits));
-        }
+        let skip = fm.name.chars().count() + digits + 2;
 
-        // If we elided something, put an ellipsis.
-        if display_lines < all_lines {
-            let last_line_index = display_line_infos.last().unwrap().line_index;
-            let s = format!("{}:{} ", fm.name, last_line_index + 1);
-            try!(write!(&mut self.dst, "{0:1$}...\n", "", s.len()));
-        }
+        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);
 
-        // FIXME (#3260)
-        // If there's one line at fault we can easily point to the problem
-        if lines.lines.len() == 1 {
-            let lo = self.cm.lookup_char_pos(sp.lo);
-            let mut digits = 0;
-            let mut num = (lines.lines[0].line_index + 1) / 10;
+        // Display at most MAX_HIGHLIGHT_LINES lines.
+        let mut remaining_err_lines = MAX_HIGHLIGHT_LINES;
 
-            // how many digits must be indent past?
-            while num > 0 { num /= 10; digits += 1; }
+        // To emit a overflowed spans code-lines *AFTER* the rendered spans
+        let mut overflowed_buf = String::new();
+        let mut overflowed = false;
 
-            let mut s = String::new();
-            // Skip is the number of characters we need to skip because they are
-            // part of the 'filename:line ' part of the previous line.
-            let skip = fm.name.chars().count() + digits + 3;
-            for _ in 0..skip {
-                s.push(' ');
+        // FIXME (#8706)
+        'l: loop {
+            if remaining_err_lines <= 0 {
+                break;
             }
-            if let Some(orig) = fm.get_line(lines.lines[0].line_index) {
-                let mut col = skip;
-                let mut lastc = ' ';
-                let mut iter = orig.chars().enumerate();
-                for (pos, ch) in iter.by_ref() {
+            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;
+                    }
+                }
+                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 previous line, we insert one on
+                    // 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.
@@ -361,8 +382,8 @@ impl EmitterWriter {
                     }
                 }
 
-                try!(write!(&mut self.dst, "{}", s));
-                let mut s = String::from("^");
+                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,
@@ -373,7 +394,13 @@ impl EmitterWriter {
 
                 let hi = self.cm.lookup_char_pos(sp.hi);
                 if hi.col != lo.col {
-                    for (pos, ch) in iter {
+                    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,
@@ -381,34 +408,63 @@ impl EmitterWriter {
                         };
                         col += count;
                         s.extend(::std::iter::repeat('~').take(count));
+
+                        chars.next();
                     }
                 }
-
-                if s.len() > 1 {
+                if (col - col_ptr) > 1 {
                     // 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 {
+                try!(write!(&mut self.dst, "{0:1$}...\n", "", skip));
+            }
+
+            // Print offending code-line
+            remaining_err_lines -= 1;
+            try!(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).
                 try!(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)
+                try!(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() {
+            try!(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 the
-    /// span (instead of the first byte). Also, when the span is too long (more
+    /// `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,
-                           sp: Span,
-                           lvl: Level,
-                           lines: codemap::FileLinesResult)
+                           msp: &MultiSpan,
+                           lvl: Level)
                           -> io::Result<()> {
-        let lines = match lines {
+        let lines = match self.cm.span_to_lines(msp.to_span_bounds()) {
             Ok(lines) => lines,
             Err(_) => {
                 try!(write!(&mut self.dst, "(internal compiler error: unprintable span)\n"));
@@ -417,52 +473,107 @@ impl EmitterWriter {
         };
 
         let fm = &*lines.file;
+        if let None = fm.src {
+            return Ok(());
+        }
 
         let lines = &lines.lines[..];
-        if lines.len() > MAX_LINES {
-            if let Some(line) = fm.get_line(lines[0].line_index) {
-                try!(write!(&mut self.dst, "{}:{} {}\n", fm.name,
-                            lines[0].line_index + 1, line));
-            }
-            try!(write!(&mut self.dst, "...\n"));
-            let last_line_index = lines[lines.len() - 1].line_index;
-            if let Some(last_line) = fm.get_line(last_line_index) {
-                try!(write!(&mut self.dst, "{}:{} {}\n", fm.name,
-                            last_line_index + 1, last_line));
+
+        // 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;
             }
-        } else {
-            for line_info in lines {
-                if let Some(line) = fm.get_line(line_info.line_index) {
-                    try!(write!(&mut self.dst, "{}:{} {}\n", fm.name,
-                                line_info.line_index + 1, line));
+            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 = (lo.line - hi.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
+                    }
                 }
-            }
-        }
-        let last_line_start = format!("{}:{} ", fm.name, lines[lines.len()-1].line_index + 1);
-        let hi = self.cm.lookup_char_pos(sp.hi);
-        let skip = last_line_start.chars().count();
-        let mut s = String::new();
-        for _ in 0..skip {
-            s.push(' ');
-        }
-        if let Some(orig) = fm.get_line(lines[0].line_index) {
-            let iter = orig.chars().enumerate();
-            for (pos, ch) in iter {
-                // 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(' '),
+                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(' '),
+                    }
                 }
+                s.push('^');
+            }
+
+            if prev_line_index != line.line_index.wrapping_sub(1) {
+                // If we elided something, put an ellipsis.
+                try!(write!(&mut self.dst, "{0:1$}...\n", "", skip));
             }
+
+            // Print offending code-lines
+            try!(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)
+                try!(println_maybe_styled!(&mut self.dst, term::Attr::ForegroundColor(lvl.color()),
+                                           "{}", s));
+            }
+            prev_line_index = line.line_index;
         }
-        s.push('^');
-        println_maybe_styled!(&mut self.dst, term::Attr::ForegroundColor(lvl.color()),
-                              "{}", s)
+        Ok(())
     }
 
     fn print_macro_backtrace(&mut self,
@@ -512,6 +623,16 @@ impl EmitterWriter {
     }
 }
 
+fn line_num_max_digits(line: &codemap::LineInfo) -> usize {
+    let mut max_line_num = line.line_index + 1;
+    let mut digits = 0;
+    while max_line_num > 0 {
+        max_line_num /= 10;
+        digits += 1;
+    }
+    digits
+}
+
 fn print_diagnostic(dst: &mut Destination,
                     topic: &str,
                     lvl: Level,
@@ -526,12 +647,9 @@ fn print_diagnostic(dst: &mut Destination,
                              "{}: ", lvl.to_string()));
     try!(print_maybe_styled!(dst, term::Attr::Bold, "{}", msg));
 
-    match code {
-        Some(code) => {
-            let style = term::Attr::ForegroundColor(term::color::BRIGHT_MAGENTA);
-            try!(print_maybe_styled!(dst, style, " [{}]", code.clone()));
-        }
-        None => ()
+    if let Some(code) = code {
+        let style = term::Attr::ForegroundColor(term::color::BRIGHT_MAGENTA);
+        try!(print_maybe_styled!(dst, style, " [{}]", code.clone()));
     }
     try!(write!(dst, "\n"));
     Ok(())
@@ -632,24 +750,36 @@ impl Write for Destination {
 
 #[cfg(test)]
 mod test {
-    use errors::Level;
+    use errors::{Level, CodeSuggestion};
     use super::EmitterWriter;
-    use codemap::{mk_sp, CodeMap};
+    use codemap::{mk_sp, CodeMap, Span, MultiSpan, BytePos, NO_EXPANSION};
     use std::sync::{Arc, Mutex};
     use std::io::{self, Write};
     use std::str::from_utf8;
     use std::rc::Rc;
 
+    struct Sink(Arc<Mutex<Vec<u8>>>);
+    impl Write for Sink {
+        fn write(&mut self, data: &[u8]) -> io::Result<usize> {
+            Write::write(&mut *self.0.lock().unwrap(), data)
+        }
+        fn flush(&mut self) -> io::Result<()> { Ok(()) }
+    }
+
+    /// Given a string like " ^~~~~~~~~~~~ ", produces a span
+    /// coverting that range. The idea is that the string has the same
+    /// length as the input, and we uncover the byte positions.  Note
+    /// 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 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 }
+    }
+
     // Diagnostic doesn't align properly in span where line number increases by one digit
     #[test]
     fn test_hilight_suggestion_issue_11715() {
-        struct Sink(Arc<Mutex<Vec<u8>>>);
-        impl Write for Sink {
-            fn write(&mut self, data: &[u8]) -> io::Result<usize> {
-                Write::write(&mut *self.0.lock().unwrap(), data)
-            }
-            fn flush(&mut self) -> io::Result<()> { Ok(()) }
-        }
         let data = Arc::new(Mutex::new(Vec::new()));
         let cm = Rc::new(CodeMap::new());
         let mut ew = EmitterWriter::new(Box::new(Sink(data.clone())), None, cm.clone());
@@ -672,10 +802,8 @@ mod test {
         let end = file.lines.borrow()[11];
         let sp = mk_sp(start, end);
         let lvl = Level::Error;
-        println!("span_to_lines");
-        let lines = cm.span_to_lines(sp);
         println!("highlight_lines");
-        ew.highlight_lines(sp, lvl, lines).unwrap();
+        ew.highlight_lines(&sp.into(), lvl).unwrap();
         println!("done");
         let vec = data.lock().unwrap().clone();
         let vec: &[u8] = &vec;
@@ -687,4 +815,287 @@ mod test {
                          dummy.txt:11         e-lä-vän\n\
                          dummy.txt:12         tolv\n");
     }
+
+    #[test]
+    fn test_single_span_splice() {
+        // 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";
+        cm.new_filemap_and_lines("blork.rs", inputtext);
+        let sp = span_from_selection(inputtext, selection);
+        let msp: MultiSpan = sp.into();
+
+        // check that we are extracting the text we thought we were extracting
+        assert_eq!(&cm.span_to_snippet(sp).unwrap(), "BB\nCCC\nDDDDD");
+
+        let substitute = "ZZZZZZ".to_owned();
+        let expected = "bbbbZZZZZZddddd";
+        let suggest = CodeSuggestion {
+            msp: msp,
+            substitutes: vec![substitute],
+        };
+        assert_eq!(suggest.splice_lines(&cm), expected);
+    }
+
+    #[test]
+    fn test_multiple_span_splice() {
+        // Test that a `MultiSpan` containing multiple spans splices substitions on
+        // several lines 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);
+        };
+        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);
+    }
+
+    #[test]
+    fn test_multispan_highlight() {
+        let data = Arc::new(Mutex::new(Vec::new()));
+        let cm = Rc::new(CodeMap::new());
+        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 span = |sp, expected| {
+            let sp = span_from_selection(inp, sp);
+            assert_eq!(&cm.span_to_snippet(sp).unwrap(), expected);
+            sp
+        };
+        cm.new_filemap_and_lines("dummy.txt", inp);
+        let sp1 = span(sp1, "aaaaaa");
+        let sp2 = span(sp2, "bbbbbb");
+        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];
+
+        let test = |expected, highlight: &mut FnMut()| {
+            data.lock().unwrap().clear();
+            highlight();
+            let vec = data.lock().unwrap().clone();
+            let actual = from_utf8(&vec[..]).unwrap();
+            assert_eq!(actual, expected);
+        };
+
+        let msp = MultiSpan { spans: vec![sp1, sp2, sp34] };
+        let msp_end = MultiSpan { spans: vec![sp1, sp2, sp3, sp4_end] };
+        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();
+            }
+        });
+    }
+
+    #[test]
+    fn test_huge_multispan_highlight() {
+        let data = Arc::new(Mutex::new(Vec::new()));
+        let cm = Rc::new(CodeMap::new());
+        let mut diag = EmitterWriter::new(Box::new(Sink(data.clone())), None, cm.clone());
+
+        let inp = "aaaaa\n\
+                   aaaaa\n\
+                   aaaaa\n\
+                   bbbbb\n\
+                   ccccc\n\
+                   xxxxx\n\
+                   yyyyy\n\
+                   _____\n\
+                   ddd__eee_\n\
+                   elided\n\
+                   _ff_gg";
+        let file = cm.new_filemap_and_lines("dummy.txt", inp);
+
+        let span = |lo, hi, (off_lo, off_hi)| {
+            let lines = file.lines.borrow();
+            let (mut lo, mut hi): (BytePos, BytePos) = (lines[lo], lines[hi]);
+            lo.0 += off_lo;
+            hi.0 += off_hi;
+            mk_sp(lo, hi)
+        };
+        let sp0 = span(4, 6, (0, 5));
+        let sp1 = span(0, 6, (0, 5));
+        let sp2 = span(8, 8, (0, 3));
+        let sp3 = span(8, 8, (5, 8));
+        let sp4 = span(10, 10, (1, 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 _ff_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 _ff_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 _ff_gg\n\
+                       \x20              ^  ^\n";
+
+        let expect0_end = "dummy.txt: 5 ccccc\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 _ff_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 _ff_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];
+
+        macro_rules! test {
+            ($expected: expr, $highlight: expr) => ({
+                data.lock().unwrap().clear();
+                $highlight();
+                let vec = data.lock().unwrap().clone();
+                let actual = from_utf8(&vec[..]).unwrap();
+                println!("actual:");
+                println!("{}", actual);
+                println!("expected:");
+                println!("{}", $expected);
+                assert_eq!(&actual[..], &$expected[..]);
+            });
+        }
+
+        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] };
+
+        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 713190ef419..5bb5f4757e0 100644
--- a/src/libsyntax/errors/json.rs
+++ b/src/libsyntax/errors/json.rs
@@ -20,9 +20,9 @@
 // FIXME spec the JSON output properly.
 
 
-use codemap::{Span, CodeMap};
+use codemap::{MultiSpan, CodeMap};
 use diagnostics::registry::Registry;
-use errors::{Level, DiagnosticBuilder, SubDiagnostic, RenderSpan};
+use errors::{Level, DiagnosticBuilder, SubDiagnostic, RenderSpan, CodeSuggestion};
 use errors::emitter::Emitter;
 
 use std::rc::Rc;
@@ -52,15 +52,15 @@ impl JsonEmitter {
 }
 
 impl Emitter for JsonEmitter {
-    fn emit(&mut self, span: Option<Span>, msg: &str, code: Option<&str>, level: Level) {
+    fn emit(&mut self, span: Option<&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);
+    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);
         }
@@ -83,7 +83,7 @@ struct Diagnostic<'a> {
     code: Option<DiagnosticCode>,
     /// "error: internal compiler error", "error", "warning", "note", "help".
     level: &'static str,
-    span: Option<DiagnosticSpan>,
+    spans: Vec<DiagnosticSpan>,
     /// Assocaited diagnostic messages.
     children: Vec<Diagnostic<'a>>,
 }
@@ -110,7 +110,7 @@ struct DiagnosticCode {
 }
 
 impl<'a> Diagnostic<'a> {
-    fn new(span: Option<Span>,
+    fn new(msp: Option<&MultiSpan>,
            msg: &'a str,
            code: Option<&str>,
            level: Level,
@@ -120,7 +120,7 @@ impl<'a> Diagnostic<'a> {
             message: msg,
             code: DiagnosticCode::map_opt_string(code.map(|c| c.to_owned()), je),
             level: level.to_str(),
-            span: span.map(|sp| DiagnosticSpan::from_span(sp, je)),
+            spans: msp.map_or(vec![], |msp| DiagnosticSpan::from_multispan(msp, je)),
             children: vec![],
         }
     }
@@ -134,7 +134,7 @@ impl<'a> Diagnostic<'a> {
             message: msg,
             code: None,
             level: level.to_str(),
-            span: Some(DiagnosticSpan::from_render_span(span, je)),
+            spans: DiagnosticSpan::from_render_span(span, je),
             children: vec![],
         }
     }
@@ -146,7 +146,7 @@ impl<'a> Diagnostic<'a> {
             message: &db.message,
             code: DiagnosticCode::map_opt_string(db.code.clone(), je),
             level: db.level.to_str(),
-            span: db.span.map(|sp| DiagnosticSpan::from_span(sp, je)),
+            spans: db.span.as_ref().map_or(vec![], |sp| DiagnosticSpan::from_multispan(sp, je)),
             children: db.children.iter().map(|c| {
                 Diagnostic::from_sub_diagnostic(c, je)
             }).collect(),
@@ -158,59 +158,67 @@ impl<'a> Diagnostic<'a> {
             message: &db.message,
             code: None,
             level: db.level.to_str(),
-            span: db.render_span.as_ref()
-                    .map(|sp| DiagnosticSpan::from_render_span(sp, je))
-                    .or_else(|| db.span.map(|sp| DiagnosticSpan::from_span(sp, je))),
+            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![]),
             children: vec![],
         }
     }
 }
 
 impl DiagnosticSpan {
-    fn from_span(span: Span, je: &JsonEmitter) -> DiagnosticSpan {
-        let start = je.cm.lookup_char_pos(span.lo);
-        let end = je.cm.lookup_char_pos(span.hi);
-        DiagnosticSpan {
-            file_name: start.file.name.clone(),
-            byte_start: span.lo.0,
-            byte_end: span.hi.0,
-            line_start: start.line,
-            line_end: end.line,
-            column_start: start.col.0 + 1,
-            column_end: end.col.0 + 1,
-        }
+    fn from_multispan(msp: &MultiSpan, je: &JsonEmitter) -> Vec<DiagnosticSpan> {
+        msp.spans.iter().map(|span| {
+            let start = je.cm.lookup_char_pos(span.lo);
+            let end = je.cm.lookup_char_pos(span.hi);
+            DiagnosticSpan {
+                file_name: start.file.name.clone(),
+                byte_start: span.lo.0,
+                byte_end: span.hi.0,
+                line_start: start.line,
+                line_end: end.line,
+                column_start: start.col.0 + 1,
+                column_end: end.col.0 + 1,
+            }
+        }).collect()
     }
 
-    fn from_render_span(span: &RenderSpan, je: &JsonEmitter) -> DiagnosticSpan {
-        match *span {
+    fn from_render_span(rsp: &RenderSpan, je: &JsonEmitter) -> Vec<DiagnosticSpan> {
+        match *rsp {
             // FIXME(#30701) handle Suggestion properly
-            RenderSpan::FullSpan(sp) | RenderSpan::Suggestion(sp, _) => {
-                DiagnosticSpan::from_span(sp, je)
+            RenderSpan::FullSpan(ref msp) |
+            RenderSpan::Suggestion(CodeSuggestion { ref msp, .. }) => {
+                DiagnosticSpan::from_multispan(msp, je)
             }
-            RenderSpan::EndSpan(span) => {
-                let end = je.cm.lookup_char_pos(span.hi);
-                DiagnosticSpan {
-                    file_name: end.file.name.clone(),
-                    byte_start: span.lo.0,
-                    byte_end: span.hi.0,
-                    line_start: 0,
-                    line_end: end.line,
-                    column_start: 0,
-                    column_end: end.col.0 + 1,
-                }
+            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.lo.0,
+                        byte_end: span.hi.0,
+                        line_start: 0,
+                        line_end: end.line,
+                        column_start: 0,
+                        column_end: end.col.0 + 1,
+                    }
+                }).collect()
             }
-            RenderSpan::FileLine(span) => {
-                let start = je.cm.lookup_char_pos(span.lo);
-                let end = je.cm.lookup_char_pos(span.hi);
-                DiagnosticSpan {
-                    file_name: start.file.name.clone(),
-                    byte_start: span.lo.0,
-                    byte_end: span.hi.0,
-                    line_start: start.line,
-                    line_end: end.line,
-                    column_start: 0,
-                    column_end: 0,
-                }
+            RenderSpan::FileLine(ref msp) => {
+                msp.spans.iter().map(|span| {
+                    let start = je.cm.lookup_char_pos(span.lo);
+                    let end = je.cm.lookup_char_pos(span.hi);
+                    DiagnosticSpan {
+                        file_name: start.file.name.clone(),
+                        byte_start: span.lo.0,
+                        byte_end: span.hi.0,
+                        line_start: start.line,
+                        line_end: end.line,
+                        column_start: 0,
+                        column_end: 0,
+                    }
+                }).collect()
             }
         }
     }
diff --git a/src/libsyntax/errors/mod.rs b/src/libsyntax/errors/mod.rs
index a7a4ddc3b2a..9e1cb60f54f 100644
--- a/src/libsyntax/errors/mod.rs
+++ b/src/libsyntax/errors/mod.rs
@@ -13,7 +13,7 @@ pub use errors::emitter::ColorConfig;
 use self::Level::*;
 use self::RenderSpan::*;
 
-use codemap::{self, Span};
+use codemap::{self, CodeMap, MultiSpan};
 use diagnostics;
 use errors::emitter::{Emitter, EmitterWriter};
 
@@ -31,35 +31,112 @@ pub enum RenderSpan {
     /// A FullSpan renders with both with an initial line for the
     /// message, prefixed by file:linenum, followed by a summary of
     /// the source code covered by the span.
-    FullSpan(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(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 the `String` is spliced
-    /// into the lines in place of the code covered by the span.
-    Suggestion(Span, String),
+    /// 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(Span),
+    FileLine(MultiSpan),
+}
+
+#[derive(Clone)]
+pub struct CodeSuggestion {
+    msp: MultiSpan,
+    substitutes: Vec<String>,
 }
 
 impl RenderSpan {
-    fn span(&self) -> Span {
+    fn span(&self) -> &MultiSpan {
         match *self {
-            FullSpan(s) |
-            Suggestion(s, _) |
-            EndSpan(s) |
-            FileLine(s) =>
-                s
+            FullSpan(ref msp) |
+            Suggestion(CodeSuggestion { ref msp, .. }) |
+            EndSpan(ref msp) |
+            FileLine(ref msp) =>
+                msp
+        }
+    }
+}
+
+impl CodeSuggestion {
+    /// Returns the assembled code suggestion.
+    pub fn splice_lines(&self, cm: &CodeMap) -> String {
+        use codemap::{CharPos, Loc, Pos};
+
+        fn push_trailing(buf: &mut String, line_opt: Option<&str>,
+                         lo: &Loc, hi_opt: Option<&Loc>) {
+            let (lo, hi_opt) = (lo.col.to_usize(), hi_opt.map(|hi|hi.col.to_usize()));
+            if let Some(line) = line_opt {
+                if line.len() > lo {
+                    buf.push_str(match hi_opt {
+                        Some(hi) => &line[lo..hi],
+                        None => &line[lo..],
+                    });
+                }
+                if let None = hi_opt {
+                    buf.push('\n');
+                }
+            }
+        }
+        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());
+
+        // To build up the result, we do this for each span:
+        // - push the line segment trailing the previous span
+        //   (at the beginning a "phantom" span pointing at the start of the line)
+        // - push lines between the previous and current span (if any)
+        // - if the previous and current span are not on the same line
+        //   push the line segment leading up to the current span
+        // - splice in the span substitution
+        //
+        // Finally push the trailing line segment of the last span
+        let fm = &lines.file;
+        let mut prev_hi = cm.lookup_char_pos(bounds.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()) {
+            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));
+            } else {
+                push_trailing(&mut buf, prev_line, &prev_hi, None);
+                // push lines between the previous and current span (if any)
+                for idx in prev_hi.line..(cur_lo.line - 1) {
+                    if let Some(line) = fm.get_line(idx) {
+                        buf.push_str(line);
+                        buf.push('\n');
+                    }
+                }
+                if let Some(cur_line) = fm.get_line(cur_lo.line - 1) {
+                    buf.push_str(&cur_line[.. cur_lo.col.to_usize()]);
+                }
+            }
+            buf.push_str(substitute);
+            prev_hi = cm.lookup_char_pos(sp.hi);
+            prev_line = fm.get_line(prev_hi.line - 1);
         }
+        push_trailing(&mut buf, prev_line, &prev_hi, None);
+        // remove trailing newline
+        buf.pop();
+        buf
     }
 }
 
@@ -106,7 +183,7 @@ pub struct DiagnosticBuilder<'a> {
     level: Level,
     message: String,
     code: Option<String>,
-    span: Option<Span>,
+    span: Option<MultiSpan>,
     children: Vec<SubDiagnostic>,
 }
 
@@ -114,7 +191,7 @@ pub struct DiagnosticBuilder<'a> {
 struct SubDiagnostic {
     level: Level,
     message: String,
-    span: Option<Span>,
+    span: Option<MultiSpan>,
     render_span: Option<RenderSpan>,
 }
 
@@ -150,81 +227,84 @@ impl<'a> DiagnosticBuilder<'a> {
         self.level == Level::Fatal
     }
 
-    pub fn note(&mut self , msg: &str) -> &mut DiagnosticBuilder<'a>  {
+    pub fn note(&mut self , msg: &str) -> &mut DiagnosticBuilder<'a> {
         self.sub(Level::Note, msg, None, None);
         self
     }
-    pub fn span_note(&mut self ,
-                     sp: Span,
-                     msg: &str)
-                     -> &mut DiagnosticBuilder<'a> {
-        self.sub(Level::Note, msg, Some(sp), None);
+    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
     }
-    pub fn warn(&mut self, msg: &str) -> &mut DiagnosticBuilder<'a>  {
+    pub fn warn(&mut self, msg: &str) -> &mut DiagnosticBuilder<'a> {
         self.sub(Level::Warning, msg, None, None);
         self
     }
-    pub fn span_warn(&mut self,
-                     sp: Span,
-                     msg: &str)
-                     -> &mut DiagnosticBuilder<'a> {
-        self.sub(Level::Warning, msg, Some(sp), None);
+    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
     }
-    pub fn help(&mut self , msg: &str) -> &mut DiagnosticBuilder<'a>  {
+    pub fn help(&mut self , msg: &str) -> &mut DiagnosticBuilder<'a> {
         self.sub(Level::Help, msg, None, None);
         self
     }
-    pub fn span_help(&mut self ,
-                     sp: Span,
-                     msg: &str)
-                     -> &mut DiagnosticBuilder<'a>  {
-        self.sub(Level::Help, msg, Some(sp), None);
+    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
     }
     /// Prints out a message with a suggested edit of the code.
     ///
     /// See `diagnostic::RenderSpan::Suggestion` for more information.
-    pub fn span_suggestion(&mut self ,
-                           sp: Span,
-                           msg: &str,
-                           suggestion: String)
-                           -> &mut DiagnosticBuilder<'a>  {
-        self.sub(Level::Help, msg, Some(sp), Some(Suggestion(sp, suggestion)));
+    pub fn span_suggestion<S: Into<MultiSpan>>(&mut self,
+                                               sp: S,
+                                               msg: &str,
+                                               suggestion: String)
+                                               -> &mut DiagnosticBuilder<'a> {
+        self.sub(Level::Help, msg, None, Some(Suggestion(CodeSuggestion {
+            msp: sp.into(),
+            substitutes: vec![suggestion],
+        })));
         self
     }
-    pub fn span_end_note(&mut self ,
-                         sp: Span,
-                         msg: &str)
-                         -> &mut DiagnosticBuilder<'a>  {
-        self.sub(Level::Note, msg, Some(sp), Some(EndSpan(sp)));
+    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(&mut self ,
-                         sp: Span,
-                         msg: &str)
-                         -> &mut DiagnosticBuilder<'a>  {
-        self.sub(Level::Warning, msg, Some(sp), Some(FileLine(sp)));
+    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(&mut self ,
-                         sp: Span,
-                         msg: &str)
-                         -> &mut DiagnosticBuilder<'a>  {
-        self.sub(Level::Note, msg, Some(sp), Some(FileLine(sp)));
+    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(&mut self ,
-                         sp: Span,
-                         msg: &str)
-                         -> &mut DiagnosticBuilder<'a>  {
-        self.sub(Level::Help, msg, Some(sp), Some(FileLine(sp)));
+    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(&mut self, sp: Span) -> &mut Self {
-        self.span = Some(sp);
+    pub fn span<S: Into<MultiSpan>>(&mut self, sp: S) -> &mut Self {
+        self.span = Some(sp.into());
         self
     }
 
@@ -237,7 +317,7 @@ impl<'a> DiagnosticBuilder<'a> {
     /// struct_* methods on Handler.
     fn new(emitter: &'a RefCell<Box<Emitter>>,
            level: Level,
-           message: &str) -> DiagnosticBuilder<'a>  {
+           message: &str) -> DiagnosticBuilder<'a> {
         DiagnosticBuilder {
             emitter: emitter,
             level: level,
@@ -253,7 +333,7 @@ impl<'a> DiagnosticBuilder<'a> {
     fn sub(&mut self,
            level: Level,
            message: &str,
-           span: Option<Span>,
+           span: Option<MultiSpan>,
            render_span: Option<RenderSpan>) {
         let sub = SubDiagnostic {
             level: level,
@@ -290,7 +370,7 @@ pub struct Handler {
     emit: RefCell<Box<Emitter>>,
     pub can_emit_warnings: bool,
     treat_err_as_bug: bool,
-    delayed_span_bug: RefCell<Option<(codemap::Span, String)>>,
+    delayed_span_bug: RefCell<Option<(MultiSpan, String)>>,
 }
 
 impl Handler {
@@ -320,10 +400,10 @@ impl Handler {
         DiagnosticBuilder::new(&self.emit, Level::Cancelled, "")
     }
 
-    pub fn struct_span_warn<'a>(&'a self,
-                                sp: Span,
-                                msg: &str)
-                                -> DiagnosticBuilder<'a> {
+    pub fn struct_span_warn<'a, S: Into<MultiSpan>>(&'a self,
+                                                    sp: S,
+                                                    msg: &str)
+                                                    -> DiagnosticBuilder<'a> {
         let mut result = DiagnosticBuilder::new(&self.emit, Level::Warning, msg);
         result.span(sp);
         if !self.can_emit_warnings {
@@ -331,11 +411,11 @@ impl Handler {
         }
         result
     }
-    pub fn struct_span_warn_with_code<'a>(&'a self,
-                                          sp: Span,
-                                          msg: &str,
-                                          code: &str)
-                                          -> DiagnosticBuilder<'a> {
+    pub fn struct_span_warn_with_code<'a, S: Into<MultiSpan>>(&'a self,
+                                                              sp: S,
+                                                              msg: &str,
+                                                              code: &str)
+                                                              -> DiagnosticBuilder<'a> {
         let mut result = DiagnosticBuilder::new(&self.emit, Level::Warning, msg);
         result.span(sp);
         result.code(code.to_owned());
@@ -351,20 +431,20 @@ impl Handler {
         }
         result
     }
-    pub fn struct_span_err<'a>(&'a self,
-                               sp: Span,
-                               msg: &str)
-                               -> DiagnosticBuilder<'a> {
+    pub fn struct_span_err<'a, S: Into<MultiSpan>>(&'a self,
+                                                   sp: S,
+                                                   msg: &str)
+                                                   -> DiagnosticBuilder<'a> {
         self.bump_err_count();
         let mut result = DiagnosticBuilder::new(&self.emit, Level::Error, msg);
         result.span(sp);
         result
     }
-    pub fn struct_span_err_with_code<'a>(&'a self,
-                                         sp: Span,
-                                         msg: &str,
-                                         code: &str)
-                                         -> DiagnosticBuilder<'a> {
+    pub fn struct_span_err_with_code<'a, S: Into<MultiSpan>>(&'a self,
+                                                             sp: S,
+                                                             msg: &str,
+                                                             code: &str)
+                                                             -> DiagnosticBuilder<'a> {
         self.bump_err_count();
         let mut result = DiagnosticBuilder::new(&self.emit, Level::Error, msg);
         result.span(sp);
@@ -375,20 +455,20 @@ impl Handler {
         self.bump_err_count();
         DiagnosticBuilder::new(&self.emit, Level::Error, msg)
     }
-    pub fn struct_span_fatal<'a>(&'a self,
-                                 sp: Span,
-                                 msg: &str)
-                                 -> DiagnosticBuilder<'a> {
+    pub fn struct_span_fatal<'a, S: Into<MultiSpan>>(&'a self,
+                                                     sp: S,
+                                                     msg: &str)
+                                                     -> DiagnosticBuilder<'a> {
         self.bump_err_count();
         let mut result = DiagnosticBuilder::new(&self.emit, Level::Fatal, msg);
         result.span(sp);
         result
     }
-    pub fn struct_span_fatal_with_code<'a>(&'a self,
-                                           sp: Span,
-                                           msg: &str,
-                                           code: &str)
-                                           -> DiagnosticBuilder<'a> {
+    pub fn struct_span_fatal_with_code<'a, S: Into<MultiSpan>>(&'a self,
+                                                               sp: S,
+                                                               msg: &str,
+                                                               code: &str)
+                                                               -> DiagnosticBuilder<'a> {
         self.bump_err_count();
         let mut result = DiagnosticBuilder::new(&self.emit, Level::Fatal, msg);
         result.span(sp);
@@ -408,58 +488,59 @@ impl Handler {
         err.cancel();
     }
 
-    pub fn span_fatal(&self, sp: Span, msg: &str) -> FatalError {
+    pub fn span_fatal<S: Into<MultiSpan>>(&self, sp: S, msg: &str) -> FatalError {
         if self.treat_err_as_bug {
             self.span_bug(sp, msg);
         }
-        self.emit(Some(sp), msg, Fatal);
+        self.emit(Some(&sp.into()), msg, Fatal);
         self.bump_err_count();
         return FatalError;
     }
-    pub fn span_fatal_with_code(&self, sp: Span, msg: &str, code: &str) -> FatalError {
+    pub fn span_fatal_with_code<S: Into<MultiSpan>>(&self, sp: S, msg: &str, code: &str)
+    -> FatalError {
         if self.treat_err_as_bug {
             self.span_bug(sp, msg);
         }
-        self.emit_with_code(Some(sp), msg, code, Fatal);
+        self.emit_with_code(Some(&sp.into()), msg, code, Fatal);
         self.bump_err_count();
         return FatalError;
     }
-    pub fn span_err(&self, sp: Span, msg: &str) {
+    pub fn span_err<S: Into<MultiSpan>>(&self, sp: S, msg: &str) {
         if self.treat_err_as_bug {
             self.span_bug(sp, msg);
         }
-        self.emit(Some(sp), msg, Error);
+        self.emit(Some(&sp.into()), msg, Error);
         self.bump_err_count();
     }
-    pub fn span_err_with_code(&self, sp: Span, msg: &str, code: &str) {
+    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), msg, code, Error);
+        self.emit_with_code(Some(&sp.into()), msg, code, Error);
         self.bump_err_count();
     }
-    pub fn span_warn(&self, sp: Span, msg: &str) {
-        self.emit(Some(sp), msg, Warning);
+    pub fn span_warn<S: Into<MultiSpan>>(&self, sp: S, msg: &str) {
+        self.emit(Some(&sp.into()), msg, Warning);
     }
-    pub fn span_warn_with_code(&self, sp: Span, msg: &str, code: &str) {
-        self.emit_with_code(Some(sp), msg, code, 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);
     }
-    pub fn span_bug(&self, sp: Span, msg: &str) -> ! {
-        self.emit(Some(sp), msg, Bug);
+    pub fn span_bug<S: Into<MultiSpan>>(&self, sp: S, msg: &str) -> ! {
+        self.emit(Some(&sp.into()), msg, Bug);
         panic!(ExplicitBug);
     }
-    pub fn delay_span_bug(&self, sp: Span, msg: &str) {
+    pub fn delay_span_bug<S: Into<MultiSpan>>(&self, sp: S, msg: &str) {
         let mut delayed = self.delayed_span_bug.borrow_mut();
-        *delayed = Some((sp, msg.to_string()));
+        *delayed = Some((sp.into(), msg.to_string()));
     }
-    pub fn span_bug_no_panic(&self, sp: Span, msg: &str) {
-        self.emit(Some(sp), msg, Bug);
+    pub fn span_bug_no_panic<S: Into<MultiSpan>>(&self, sp: S, msg: &str) {
+        self.emit(Some(&sp.into()), msg, Bug);
         self.bump_err_count();
     }
-    pub fn span_note_without_error(&self, sp: Span, msg: &str) {
-        self.emit.borrow_mut().emit(Some(sp), msg, None, Note);
+    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);
     }
-    pub fn span_unimpl(&self, sp: Span, msg: &str) -> ! {
+    pub fn span_unimpl<S: Into<MultiSpan>>(&self, sp: S, msg: &str) -> ! {
         self.span_bug(sp, &format!("unimplemented {}", msg));
     }
     pub fn fatal(&self, msg: &str) -> FatalError {
@@ -502,15 +583,14 @@ impl Handler {
     pub fn has_errors(&self) -> bool {
         self.err_count.get() > 0
     }
-
     pub fn abort_if_errors(&self) {
         let s;
         match self.err_count.get() {
             0 => {
                 let delayed_bug = self.delayed_span_bug.borrow();
                 match *delayed_bug {
-                    Some((span, ref errmsg)) => {
-                        self.span_bug(span, errmsg);
+                    Some((ref span, ref errmsg)) => {
+                        self.span_bug(span.clone(), errmsg);
                     },
                     _ => {}
                 }
@@ -526,27 +606,24 @@ impl Handler {
 
         panic!(self.fatal(&s));
     }
-
     pub fn emit(&self,
-                sp: Option<Span>,
+                msp: Option<&MultiSpan>,
                 msg: &str,
                 lvl: Level) {
         if lvl == Warning && !self.can_emit_warnings { return }
-        self.emit.borrow_mut().emit(sp, msg, None, lvl);
+        self.emit.borrow_mut().emit(msp, msg, None, lvl);
     }
-
     pub fn emit_with_code(&self,
-                          sp: Option<Span>,
+                          msp: Option<&MultiSpan>,
                           msg: &str,
                           code: &str,
                           lvl: Level) {
         if lvl == Warning && !self.can_emit_warnings { return }
-        self.emit.borrow_mut().emit(sp, msg, Some(code), lvl);
+        self.emit.borrow_mut().emit(msp, msg, Some(code), lvl);
     }
-
-    pub fn custom_emit(&self, sp: RenderSpan, msg: &str, lvl: Level) {
+    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(sp, msg, lvl);
+        self.emit.borrow_mut().custom_emit(&rsp, msg, lvl);
     }
 }