diff options
| author | bors <bors@rust-lang.org> | 2016-01-28 22:13:25 +0000 |
|---|---|---|
| committer | bors <bors@rust-lang.org> | 2016-01-28 22:13:25 +0000 |
| commit | 142214d1f2232a4e88ff7bd99951b01f36052c61 (patch) | |
| tree | 1d6bb4858399c46d39d9f85076cef439fb899f94 /src/libsyntax/errors | |
| parent | 552bf75e7d689c42febc7798d31ae58d614418f9 (diff) | |
| parent | 727f959095023d9fa749acbba49a4a904c57356b (diff) | |
| download | rust-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.rs | 813 | ||||
| -rw-r--r-- | src/libsyntax/errors/json.rs | 112 | ||||
| -rw-r--r-- | src/libsyntax/errors/mod.rs | 325 |
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); } } |
