refactor to use new snippet code and model

Major changes:
- Remove old snippet rendering code and use the new stuff.
- Introduce `span_label` method to add a label
- Remove EndSpan mode and replace with a fn to get the last
  character of a span.
- Stop using `Option<MultiSpan>` and just use an empty `MultiSpan`
- and probably a bunch of other stuff :)
This commit is contained in:
Niko Matsakis 2016-04-20 14:56:01 -04:00
parent 5b150cf0ca
commit 11dc974a38
7 changed files with 351 additions and 738 deletions

View File

@ -567,7 +567,7 @@ pub fn early_error(output: config::ErrorOutputType, msg: &str) -> ! {
} }
config::ErrorOutputType::Json => Box::new(JsonEmitter::basic()), config::ErrorOutputType::Json => Box::new(JsonEmitter::basic()),
}; };
emitter.emit(None, msg, None, errors::Level::Fatal); emitter.emit(&MultiSpan::new(), msg, None, errors::Level::Fatal);
panic!(errors::FatalError); panic!(errors::FatalError);
} }
@ -578,7 +578,7 @@ pub fn early_warn(output: config::ErrorOutputType, msg: &str) {
} }
config::ErrorOutputType::Json => Box::new(JsonEmitter::basic()), config::ErrorOutputType::Json => Box::new(JsonEmitter::basic()),
}; };
emitter.emit(None, msg, None, errors::Level::Warning); emitter.emit(&MultiSpan::new(), msg, None, errors::Level::Warning);
} }
// Err(0) means compilation was stopped, but no errors were found. // Err(0) means compilation was stopped, but no errors were found.

View File

@ -91,8 +91,9 @@ use std::thread;
use rustc::session::early_error; use rustc::session::early_error;
use syntax::{ast, errors, diagnostics}; use syntax::{ast, errors, diagnostic};
use syntax::codemap::{CodeMap, FileLoader, RealFileLoader}; use syntax::codemap::MultiSpan;
use syntax::parse::{self, PResult};
use syntax::errors::emitter::Emitter; use syntax::errors::emitter::Emitter;
use syntax::feature_gate::{GatedCfg, UnstableFeatures}; use syntax::feature_gate::{GatedCfg, UnstableFeatures};
use syntax::parse::{self, PResult, token}; use syntax::parse::{self, PResult, token};
@ -136,7 +137,8 @@ pub fn run(args: Vec<String>) -> isize {
None => { None => {
let mut emitter = let mut emitter =
errors::emitter::BasicEmitter::stderr(errors::ColorConfig::Auto); errors::emitter::BasicEmitter::stderr(errors::ColorConfig::Auto);
emitter.emit(None, &abort_msg(err_count), None, errors::Level::Fatal); emitter.emit(&MultiSpan::new(), &abort_msg(err_count), None,
errors::Level::Fatal);
exit_on_err(); exit_on_err();
} }
} }
@ -379,7 +381,7 @@ fn check_cfg(sopts: &config::Options,
match item.node { match item.node {
ast::MetaItemKind::List(ref pred, _) => { ast::MetaItemKind::List(ref pred, _) => {
saw_invalid_predicate = true; saw_invalid_predicate = true;
emitter.emit(None, emitter.emit(&MultiSpan::new(),
&format!("invalid predicate in --cfg command line argument: `{}`", &format!("invalid predicate in --cfg command line argument: `{}`",
pred), pred),
None, None,
@ -1028,19 +1030,19 @@ pub fn monitor<F: FnOnce() + Send + 'static>(f: F) {
// a .span_bug or .bug call has already printed what // a .span_bug or .bug call has already printed what
// it wants to print. // it wants to print.
if !value.is::<errors::ExplicitBug>() { if !value.is::<errors::ExplicitBug>() {
emitter.emit(None, "unexpected panic", None, errors::Level::Bug); emitter.emit(&MultiSpan::new(), "unexpected panic", None, errors::Level::Bug);
} }
let xs = ["the compiler unexpectedly panicked. this is a bug.".to_string(), let xs = ["the compiler unexpectedly panicked. this is a bug.".to_string(),
format!("we would appreciate a bug report: {}", BUG_REPORT_URL)]; format!("we would appreciate a bug report: {}", BUG_REPORT_URL)];
for note in &xs { for note in &xs {
emitter.emit(None, &note[..], None, errors::Level::Note) emitter.emit(&MultiSpan::new(), &note[..], None, errors::Level::Note)
} }
if match env::var_os("RUST_BACKTRACE") { if match env::var_os("RUST_BACKTRACE") {
Some(val) => &val != "0", Some(val) => &val != "0",
None => false, None => false,
} { } {
emitter.emit(None, emitter.emit(&MultiSpan::new(),
"run with `RUST_BACKTRACE=1` for a backtrace", "run with `RUST_BACKTRACE=1` for a backtrace",
None, None,
errors::Level::Note); errors::Level::Note);

View File

@ -86,10 +86,6 @@ impl Emitter for ExpectErrorEmitter {
lvl: Level) { lvl: Level) {
remove_message(self, msg, lvl); remove_message(self, msg, lvl);
} }
fn custom_emit(&mut self, _sp: &RenderSpan, msg: &str, lvl: Level) {
remove_message(self, msg, lvl);
}
} }
fn errors(msgs: &[&str]) -> (Box<Emitter + Send>, usize) { fn errors(msgs: &[&str]) -> (Box<Emitter + Send>, usize) {

View File

@ -19,7 +19,7 @@ use llvm::SMDiagnosticRef;
use {CrateTranslation, ModuleTranslation}; use {CrateTranslation, ModuleTranslation};
use util::common::time; use util::common::time;
use util::common::path2cstr; use util::common::path2cstr;
use syntax::codemap; use syntax::codemap::{self, MultiSpan};
use syntax::errors::{self, Handler, Level}; use syntax::errors::{self, Handler, Level};
use syntax::errors::emitter::Emitter; use syntax::errors::emitter::Emitter;
@ -84,13 +84,13 @@ impl SharedEmitter {
for diag in &*buffer { for diag in &*buffer {
match diag.code { match diag.code {
Some(ref code) => { Some(ref code) => {
handler.emit_with_code(None, handler.emit_with_code(&MultiSpan::new(),
&diag.msg, &diag.msg,
&code[..], &code[..],
diag.lvl); diag.lvl);
}, },
None => { None => {
handler.emit(None, handler.emit(&MultiSpan::new(),
&diag.msg, &diag.msg,
diag.lvl); diag.lvl);
}, },
@ -101,9 +101,12 @@ impl SharedEmitter {
} }
impl Emitter for SharedEmitter { impl Emitter for SharedEmitter {
fn emit(&mut self, sp: Option<&codemap::MultiSpan>, fn emit(&mut self,
msg: &str, code: Option<&str>, lvl: Level) { sp: &codemap::MultiSpan,
assert!(sp.is_none(), "SharedEmitter doesn't support spans"); msg: &str,
code: Option<&str>,
lvl: Level) {
assert!(sp.primary_span().is_none(), "SharedEmitter doesn't support spans");
self.buffer.lock().unwrap().push(Diagnostic { self.buffer.lock().unwrap().push(Diagnostic {
msg: msg.to_string(), msg: msg.to_string(),
@ -112,8 +115,8 @@ impl Emitter for SharedEmitter {
}); });
} }
fn custom_emit(&mut self, _sp: &errors::RenderSpan, _msg: &str, _lvl: Level) { fn emit_struct(&mut self, _db: &errors::DiagnosticBuilder) {
bug!("SharedEmitter doesn't support custom_emit"); bug!("SharedEmitter doesn't support emit_struct");
} }
} }

View File

@ -163,6 +163,12 @@ pub const COMMAND_LINE_SP: Span = Span { lo: BytePos(0),
expn_id: COMMAND_LINE_EXPN }; expn_id: COMMAND_LINE_EXPN };
impl Span { impl Span {
/// Returns a new span representing just the end-point of this span
pub fn end_point(self) -> Span {
let lo = cmp::max(self.hi.0 - 1, self.lo.0);
Span { lo: BytePos(lo), hi: self.hi, expn_id: self.expn_id}
}
/// Returns `self` if `self` is not the dummy span, and `other` otherwise. /// Returns `self` if `self` is not the dummy span, and `other` otherwise.
pub fn substitute_dummy(self, other: Span) -> Span { pub fn substitute_dummy(self, other: Span) -> Span {
if self.source_equal(&DUMMY_SP) { other } else { self } if self.source_equal(&DUMMY_SP) { other } else { self }
@ -794,7 +800,7 @@ impl CodeMap {
/// Creates a new filemap and sets its line information. /// Creates a new filemap and sets its line information.
pub fn new_filemap_and_lines(&self, filename: &str, src: &str) -> Rc<FileMap> { pub fn new_filemap_and_lines(&self, filename: &str, src: &str) -> Rc<FileMap> {
let fm = self.new_filemap(filename.to_string(), src.to_owned()); let fm = self.new_filemap(filename.to_string(), src.to_owned());
let mut byte_pos: u32 = 0; let mut byte_pos: u32 = fm.start_pos.0;
for line in src.lines() { for line in src.lines() {
// register the start of this line // register the start of this line
fm.next_line(BytePos(byte_pos)); fm.next_line(BytePos(byte_pos));
@ -1126,7 +1132,9 @@ impl CodeMap {
// numbers in Loc are 1-based, so we subtract 1 to get 0-based // numbers in Loc are 1-based, so we subtract 1 to get 0-based
// lines. // lines.
for line_index in lo.line-1 .. hi.line-1 { for line_index in lo.line-1 .. hi.line-1 {
let line_len = lo.file.get_line(line_index).map(|s| s.len()).unwrap_or(0); let line_len = lo.file.get_line(line_index)
.map(|s| s.chars().count())
.unwrap_or(0);
lines.push(LineInfo { line_index: line_index, lines.push(LineInfo { line_index: line_index,
start_col: start_col, start_col: start_col,
end_col: CharPos::from_usize(line_len) }); end_col: CharPos::from_usize(line_len) });
@ -1584,13 +1592,13 @@ mod tests {
assert_eq!(file_lines.lines[0].line_index, 1); assert_eq!(file_lines.lines[0].line_index, 1);
} }
/// Given a string like " ^~~~~~~~~~~~ ", produces a span /// Given a string like " ~~~~~~~~~~~~ ", produces a span
/// coverting that range. The idea is that the string has the same /// coverting that range. The idea is that the string has the same
/// length as the input, and we uncover the byte positions. Note /// length as the input, and we uncover the byte positions. Note
/// that this can span lines and so on. /// that this can span lines and so on.
fn span_from_selection(input: &str, selection: &str) -> Span { fn span_from_selection(input: &str, selection: &str) -> Span {
assert_eq!(input.len(), selection.len()); assert_eq!(input.len(), selection.len());
let left_index = selection.find('^').unwrap() as u32; let left_index = selection.find('~').unwrap() as u32;
let right_index = selection.rfind('~').map(|x|x as u32).unwrap_or(left_index); 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 } Span { lo: BytePos(left_index), hi: BytePos(right_index + 1), expn_id: NO_EXPANSION }
} }
@ -1601,7 +1609,7 @@ mod tests {
fn span_to_snippet_and_lines_spanning_multiple_lines() { fn span_to_snippet_and_lines_spanning_multiple_lines() {
let cm = CodeMap::new(); let cm = CodeMap::new();
let inputtext = "aaaaa\nbbbbBB\nCCC\nDDDDDddddd\neee\n"; let inputtext = "aaaaa\nbbbbBB\nCCC\nDDDDDddddd\neee\n";
let selection = " \n ^~\n~~~\n~~~~~ \n \n"; let selection = " \n ~~\n~~~\n~~~~~ \n \n";
cm.new_filemap_and_lines("blork.rs", inputtext); cm.new_filemap_and_lines("blork.rs", inputtext);
let span = span_from_selection(inputtext, selection); let span = span_from_selection(inputtext, selection);
@ -1751,73 +1759,4 @@ r"blork2.rs:2:1: 2:12
"; ";
assert_eq!(sstr, res_str); assert_eq!(sstr, res_str);
} }
#[test]
fn t13() {
// Test that collecting multiple spans into line-groups works correctly
let cm = CodeMap::new();
let inp = "_aaaaa__bbb\nvv\nw\nx\ny\nz\ncccccc__ddddee__";
let sp1 = " ^~~~~ \n \n \n \n \n \n ";
let sp2 = " \n \n \n \n \n^\n ";
let sp3 = " ^~~\n~~\n \n \n \n \n ";
let sp4 = " \n \n \n \n \n \n^~~~~~ ";
let sp5 = " \n \n \n \n \n \n ^~~~ ";
let sp6 = " \n \n \n \n \n \n ^~~~ ";
let sp_trim = " \n \n \n \n \n \n ^~ ";
let sp_merge = " \n \n \n \n \n \n ^~~~~~ ";
let sp7 = " \n ^\n \n \n \n \n ";
let sp8 = " \n \n^\n \n \n \n ";
let sp9 = " \n \n \n^\n \n \n ";
let sp10 = " \n \n \n \n^\n \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("blork.rs", inp);
let sp1 = span(sp1, "aaaaa");
let sp2 = span(sp2, "z");
let sp3 = span(sp3, "bbb\nvv");
let sp4 = span(sp4, "cccccc");
let sp5 = span(sp5, "dddd");
let sp6 = span(sp6, "ddee");
let sp7 = span(sp7, "v");
let sp8 = span(sp8, "w");
let sp9 = span(sp9, "x");
let sp10 = span(sp10, "y");
let sp_trim = span(sp_trim, "ee");
let sp_merge = span(sp_merge, "ddddee");
let spans = vec![sp5, sp2, sp4, sp9, sp10, sp7, sp3, sp8, sp1, sp6];
macro_rules! check_next {
($groups: expr, $expected: expr) => ({
let actual = $groups.next().map(|g|&g.spans[..]);
let expected = $expected;
println!("actual:\n{:?}\n", actual);
println!("expected:\n{:?}\n", expected);
assert_eq!(actual, expected.as_ref().map(|x|&x[..]));
});
}
let _groups = cm.group_spans(spans.clone());
let it = &mut _groups.iter();
check_next!(it, Some([sp1, sp7, sp8, sp9, sp10, sp2]));
// New group because we're exceeding MAX_HIGHLIGHT_LINES
check_next!(it, Some([sp4, sp_merge]));
check_next!(it, Some([sp3]));
check_next!(it, None::<[Span; 0]>);
let _groups = cm.end_group_spans(spans);
let it = &mut _groups.iter();
check_next!(it, Some([sp1, sp7, sp8, sp9, sp10, sp2]));
// New group because we're exceeding MAX_HIGHLIGHT_LINES
check_next!(it, Some([sp4, sp5, sp_trim]));
check_next!(it, Some([sp3]));
check_next!(it, None::<[Span; 0]>);
}
} }

View File

@ -16,6 +16,7 @@ use diagnostics;
use errors::{Level, RenderSpan, CodeSuggestion, DiagnosticBuilder}; use errors::{Level, RenderSpan, CodeSuggestion, DiagnosticBuilder};
use errors::RenderSpan::*; use errors::RenderSpan::*;
use errors::Level::*; use errors::Level::*;
use errors::snippet::{RenderedLineKind, SnippetData, Style};
use std::{cmp, fmt}; use std::{cmp, fmt};
use std::io::prelude::*; use std::io::prelude::*;
@ -24,27 +25,15 @@ use std::rc::Rc;
use term; use term;
pub trait Emitter { pub trait Emitter {
fn emit(&mut self, span: Option<&MultiSpan>, msg: &str, code: Option<&str>, lvl: Level); fn emit(&mut self, span: &MultiSpan, msg: &str, code: Option<&str>, lvl: Level);
fn custom_emit(&mut self, sp: &RenderSpan, msg: &str, lvl: Level);
/// Emit a structured diagnostic. /// Emit a structured diagnostic.
fn emit_struct(&mut self, db: &DiagnosticBuilder) { fn emit_struct(&mut self, db: &DiagnosticBuilder);
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, &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. /// maximum number of lines we will print for each error; arbitrary.
pub const MAX_HIGHLIGHT_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)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ColorConfig { pub enum ColorConfig {
Auto, Auto,
@ -70,19 +59,23 @@ pub struct BasicEmitter {
impl Emitter for BasicEmitter { impl Emitter for BasicEmitter {
fn emit(&mut self, fn emit(&mut self,
msp: Option<&MultiSpan>, msp: &MultiSpan,
msg: &str, msg: &str,
code: Option<&str>, code: Option<&str>,
lvl: Level) { lvl: Level) {
assert!(msp.is_none(), "BasicEmitter can't handle spans"); assert!(msp.primary_span().is_none(), "BasicEmitter can't handle spans");
if let Err(e) = print_diagnostic(&mut self.dst, "", lvl, msg, code) { if let Err(e) = print_diagnostic(&mut self.dst, "", lvl, msg, code) {
panic!("failed to print diagnostics: {:?}", e); panic!("failed to print diagnostics: {:?}", e);
} }
} }
fn custom_emit(&mut self, _: &RenderSpan, _: &str, _: Level) { fn emit_struct(&mut self, db: &DiagnosticBuilder) {
panic!("BasicEmitter can't handle custom_emit"); self.emit(&db.span, &db.message, db.code.as_ref().map(|s| &**s), db.level);
for child in &db.children {
assert!(child.render_span.is_none(), "BasicEmitter can't handle spans");
self.emit(&child.span, &child.message, None, child.level);
}
} }
} }
@ -101,33 +94,31 @@ pub struct EmitterWriter {
dst: Destination, dst: Destination,
registry: Option<diagnostics::registry::Registry>, registry: Option<diagnostics::registry::Registry>,
cm: Rc<codemap::CodeMap>, cm: Rc<codemap::CodeMap>,
first: bool,
} }
impl Emitter for EmitterWriter { impl Emitter for EmitterWriter {
fn emit(&mut self, fn emit(&mut self,
msp: Option<&MultiSpan>, msp: &MultiSpan,
msg: &str, msg: &str,
code: Option<&str>, code: Option<&str>,
lvl: Level) { lvl: Level) {
let error = match msp.map(|s|(s.to_span_bounds(), s)) { self.emit_multispan(msp, msg, code, lvl, true);
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 {
panic!("failed to print diagnostics: {:?}", e);
}
} }
fn custom_emit(&mut self, fn emit_struct(&mut self, db: &DiagnosticBuilder) {
rsp: &RenderSpan, self.emit_multispan(&db.span, &db.message,
msg: &str, db.code.as_ref().map(|s| &**s), db.level, true);
lvl: Level) {
if let Err(e) = self.emit_(rsp, msg, None, lvl) { for child in &db.children {
panic!("failed to print diagnostics: {:?}", e); match child.render_span {
Some(ref sp) =>
self.emit_renderspan(sp, &child.message,
child.level),
None =>
self.emit_multispan(&child.span,
&child.message, None, child.level, false),
}
} }
} }
} }
@ -153,9 +144,10 @@ impl EmitterWriter {
-> EmitterWriter { -> EmitterWriter {
if color_config.use_color() { if color_config.use_color() {
let dst = Destination::from_stderr(); let dst = Destination::from_stderr();
EmitterWriter { dst: dst, registry: registry, cm: code_map } EmitterWriter { dst: dst, registry: registry, cm: code_map, first: true }
} else { } else {
EmitterWriter { dst: Raw(Box::new(io::stderr())), registry: registry, cm: code_map } EmitterWriter { dst: Raw(Box::new(io::stderr())),
registry: registry, cm: code_map, first: true }
} }
} }
@ -163,7 +155,49 @@ impl EmitterWriter {
registry: Option<diagnostics::registry::Registry>, registry: Option<diagnostics::registry::Registry>,
code_map: Rc<codemap::CodeMap>) code_map: Rc<codemap::CodeMap>)
-> EmitterWriter { -> EmitterWriter {
EmitterWriter { dst: Raw(dst), registry: registry, cm: code_map } EmitterWriter { dst: Raw(dst), registry: registry, cm: code_map, first: true }
}
fn emit_multispan(&mut self,
span: &MultiSpan,
msg: &str,
code: Option<&str>,
lvl: Level,
is_header: bool) {
if is_header {
if self.first {
self.first = false;
} else {
match write!(self.dst, "\n") {
Ok(_) => { }
Err(e) => {
panic!("failed to print diagnostics: {:?}", e)
}
}
}
}
let error = match span.primary_span() {
Some(COMMAND_LINE_SP) => {
self.emit_(&FileLine(span.clone()), msg, code, lvl)
}
Some(DUMMY_SP) | None => {
print_diagnostic(&mut self.dst, "", lvl, msg, code)
}
Some(_) => {
self.emit_(&FullSpan(span.clone()), msg, code, lvl)
}
};
if let Err(e) = error {
panic!("failed to print diagnostics: {:?}", e);
}
}
fn emit_renderspan(&mut self, sp: &RenderSpan, msg: &str, lvl: Level) {
if let Err(e) = self.emit_(sp, msg, None, lvl) {
panic!("failed to print diagnostics: {:?}", e);
}
} }
fn emit_(&mut self, fn emit_(&mut self,
@ -173,51 +207,43 @@ impl EmitterWriter {
lvl: Level) lvl: Level)
-> io::Result<()> { -> io::Result<()> {
let msp = rsp.span(); let msp = rsp.span();
let bounds = msp.to_span_bounds(); let primary_span = msp.primary_span();
let ss = if bounds == COMMAND_LINE_SP { match code {
"<command line option>".to_string() Some(code) if self.registry.as_ref()
} else if let EndSpan(_) = *rsp { .and_then(|registry| registry.find_description(code)).is_some() =>
let span_end = Span { lo: bounds.hi, hi: bounds.hi, expn_id: bounds.expn_id}; {
self.cm.span_to_string(span_end) let code_with_explain = String::from("--explain ") + code;
} else { print_diagnostic(&mut self.dst, "", lvl, msg, Some(&code_with_explain))?
self.cm.span_to_string(bounds) }
}; _ => print_diagnostic(&mut self.dst, "", lvl, msg, code)?
}
print_diagnostic(&mut self.dst, &ss[..], lvl, msg, code)?;
match *rsp { match *rsp {
FullSpan(_) => { FullSpan(_) => {
self.highlight_lines(msp, lvl)?; self.highlight_lines(msp, lvl)?;
self.print_macro_backtrace(bounds)?; if let Some(primary_span) = primary_span {
self.print_macro_backtrace(primary_span)?;
} }
EndSpan(_) => {
self.end_highlight_lines(msp, lvl)?;
self.print_macro_backtrace(bounds)?;
} }
Suggestion(ref suggestion) => { Suggestion(ref suggestion) => {
self.highlight_suggestion(suggestion)?; self.highlight_suggestion(suggestion)?;
self.print_macro_backtrace(bounds)?; if let Some(primary_span) = primary_span {
self.print_macro_backtrace(primary_span)?;
}
} }
FileLine(..) => { FileLine(..) => {
// no source text in this case! // no source text in this case!
} }
} }
if let Some(code) = code {
if let Some(_) = self.registry.as_ref()
.and_then(|registry| registry.find_description(code)) {
print_diagnostic(&mut self.dst, &ss[..], Help,
&format!("run `rustc --explain {}` to see a \
detailed explanation", code), None)?;
}
}
Ok(()) Ok(())
} }
fn highlight_suggestion(&mut self, suggestion: &CodeSuggestion) -> io::Result<()> fn highlight_suggestion(&mut self, suggestion: &CodeSuggestion) -> io::Result<()>
{ {
let lines = self.cm.span_to_lines(suggestion.msp.to_span_bounds()).unwrap(); let primary_span = suggestion.msp.primary_span().unwrap();
let lines = self.cm.span_to_lines(primary_span).unwrap();
assert!(!lines.lines.is_empty()); assert!(!lines.lines.is_empty());
let complete = suggestion.splice_lines(&self.cm); let complete = suggestion.splice_lines(&self.cm);
@ -251,325 +277,21 @@ impl EmitterWriter {
lvl: Level) lvl: Level)
-> io::Result<()> -> io::Result<()>
{ {
let lines = match self.cm.span_to_lines(msp.to_span_bounds()) { let mut snippet_data = SnippetData::new(self.cm.clone(),
Ok(lines) => lines, msp.primary_span());
Err(_) => { for span_label in msp.span_labels() {
write!(&mut self.dst, "(internal compiler error: unprintable span)\n")?; snippet_data.push(span_label.span,
return Ok(()); span_label.is_primary,
span_label.label);
} }
}; let rendered_lines = snippet_data.render_lines();
for rendered_line in &rendered_lines {
let fm = &*lines.file; for styled_string in &rendered_line.text {
if let None = fm.src { self.dst.apply_style(lvl, &rendered_line.kind, styled_string.style)?;
return Ok(()); write!(&mut self.dst, "{}", styled_string.text)?;
self.dst.reset_attrs()?;
} }
write!(&mut self.dst, "\n")?;
let display_line_infos = &lines.lines[..];
assert!(display_line_infos.len() > 0);
// Calculate the widest number to format evenly and fix #11715
let digits = line_num_max_digits(display_line_infos.last().unwrap());
let first_line_index = display_line_infos.first().unwrap().line_index;
let skip = fm.name.chars().count() + digits + 2;
let mut spans = msp.spans.iter().peekable();
let mut lines = display_line_infos.iter();
let mut prev_line_index = first_line_index.wrapping_sub(1);
// Display at most MAX_HIGHLIGHT_LINES lines.
let mut remaining_err_lines = MAX_HIGHLIGHT_LINES;
// To emit a overflowed spans code-lines *AFTER* the rendered spans
let mut overflowed_buf = String::new();
let mut overflowed = false;
// FIXME (#8706)
'l: loop {
if remaining_err_lines <= 0 {
break;
}
let line = match lines.next() {
Some(l) => l,
None => break,
};
// Skip is the number of characters we need to skip because they are
// part of the 'filename:line ' part of the code line.
let mut s: String = ::std::iter::repeat(' ').take(skip).collect();
let mut col = skip;
let mut lastc = ' ';
let cur_line_str = fm.get_line(line.line_index).unwrap();
let mut line_chars = cur_line_str.chars().enumerate().peekable();
let mut line_spans = 0;
// Assemble spans for this line
loop {
// Peek here to preserve the span if it doesn't belong to this line
let sp = match spans.peek() {
Some(sp) => **sp,
None => break,
};
let lo = self.cm.lookup_char_pos(sp.lo);
let hi = self.cm.lookup_char_pos(sp.hi);
let line_num = line.line_index + 1;
if !(lo.line <= line_num && hi.line >= line_num) {
// This line is not contained in the span
if overflowed {
// Never elide the final line of an overflowed span
prev_line_index = line.line_index - 1;
overflowed = false;
break;
}
if line_spans == 0 {
continue 'l;
} else {
// This line is finished, now render the spans we've assembled
break;
}
}
spans.next();
line_spans += 1;
if lo.line != hi.line {
// Assemble extra code lines to be emitted after this lines spans
// (substract `2` because the first and last line are rendered normally)
let max_lines = cmp::min(remaining_err_lines, MAX_SP_LINES) - 2;
prev_line_index = line.line_index;
let count = cmp::min((hi.line - lo.line - 1), max_lines);
for _ in 0..count {
let line = match lines.next() {
Some(l) => l,
None => break,
};
let line_str = fm.get_line(line.line_index).unwrap();
overflowed_buf.push_str(&format!("{}:{:>width$} {}\n",
fm.name,
line.line_index + 1,
line_str,
width=digits));
remaining_err_lines -= 1;
prev_line_index += 1
}
// Remember that the span overflowed to ensure
// that we emit its last line exactly once
// (other spans may, or may not, start on it)
overflowed = true;
break;
}
for (pos, ch) in line_chars.by_ref() {
lastc = ch;
if pos >= lo.col.to_usize() { break; }
// Whenever a tab occurs on the code line, we insert one on
// the error-point-squiggly-line as well (instead of a space).
// That way the squiggly line will usually appear in the correct
// position.
match ch {
'\t' => {
col += 8 - col%8;
s.push('\t');
},
_ => {
col += 1;
s.push(' ');
},
}
}
s.push('^');
let col_ptr = col;
let count = match lastc {
// Most terminals have a tab stop every eight columns by default
'\t' => 8 - col%8,
_ => 1,
};
col += count;
s.extend(::std::iter::repeat('~').take(count));
let hi = self.cm.lookup_char_pos(sp.hi);
if hi.col != lo.col {
let mut chars = line_chars.by_ref();
loop {
// We peek here to preserve the value for the next span
let (pos, ch) = match chars.peek() {
Some(elem) => *elem,
None => break,
};
if pos >= hi.col.to_usize() { break; }
let count = match ch {
'\t' => 8 - col%8,
_ => 1,
};
col += count;
s.extend(::std::iter::repeat('~').take(count));
chars.next();
}
}
if (col - col_ptr) > 0 {
// One extra squiggly is replaced by a "^"
s.pop();
}
}
// If we elided something put an ellipsis.
if prev_line_index != line.line_index.wrapping_sub(1) && !overflowed {
write!(&mut self.dst, "{0:1$}...\n", "", skip)?;
}
// Print offending code-line
remaining_err_lines -= 1;
write!(&mut self.dst, "{}:{:>width$} {}\n",
fm.name,
line.line_index + 1,
cur_line_str,
width=digits)?;
if s.len() > skip {
// Render the spans we assembled previously (if any).
println_maybe_styled!(&mut self.dst, term::Attr::ForegroundColor(lvl.color()),
"{}", s)?;
}
if !overflowed_buf.is_empty() {
// Print code-lines trailing the rendered spans (when a span overflows)
write!(&mut self.dst, "{}", &overflowed_buf)?;
overflowed_buf.clear();
} else {
prev_line_index = line.line_index;
}
}
// If we elided something, put an ellipsis.
if lines.next().is_some() {
write!(&mut self.dst, "{0:1$}...\n", "", skip)?;
}
Ok(())
}
/// Here are the differences between this and the normal `highlight_lines`:
/// `end_highlight_lines` will always put arrow on the last byte of each
/// span (instead of the first byte). Also, when a span is too long (more
/// than 6 lines), `end_highlight_lines` will print the first line, then
/// dot dot dot, then last line, whereas `highlight_lines` prints the first
/// six lines.
#[allow(deprecated)]
fn end_highlight_lines(&mut self,
msp: &MultiSpan,
lvl: Level)
-> io::Result<()> {
let lines = match self.cm.span_to_lines(msp.to_span_bounds()) {
Ok(lines) => lines,
Err(_) => {
write!(&mut self.dst, "(internal compiler error: unprintable span)\n")?;
return Ok(());
}
};
let fm = &*lines.file;
if let None = fm.src {
return Ok(());
}
let lines = &lines.lines[..];
// Calculate the widest number to format evenly
let first_line = lines.first().unwrap();
let last_line = lines.last().unwrap();
let digits = line_num_max_digits(last_line);
let skip = fm.name.chars().count() + digits + 2;
let mut spans = msp.spans.iter().peekable();
let mut lines = lines.iter();
let mut prev_line_index = first_line.line_index.wrapping_sub(1);
// Display at most MAX_HIGHLIGHT_LINES lines.
let mut remaining_err_lines = MAX_HIGHLIGHT_LINES;
'l: loop {
if remaining_err_lines <= 0 {
break;
}
let line = match lines.next() {
Some(line) => line,
None => break,
};
// Skip is the number of characters we need to skip because they are
// part of the 'filename:line ' part of the previous line.
let mut s: String = ::std::iter::repeat(' ').take(skip).collect();
let line_str = fm.get_line(line.line_index).unwrap();
let mut line_chars = line_str.chars().enumerate();
let mut line_spans = 0;
loop {
// Peek here to preserve the span if it doesn't belong to this line
let sp = match spans.peek() {
Some(sp) => **sp,
None => break,
};
let lo = self.cm.lookup_char_pos(sp.lo);
let hi = self.cm.lookup_char_pos(sp.hi);
let elide_sp = (hi.line - lo.line) >= MAX_SP_LINES;
let line_num = line.line_index + 1;
if !(lo.line <= line_num && hi.line >= line_num) {
// This line is not contained in the span
if line_spans == 0 {
continue 'l;
} else {
// This line is finished, now render the spans we've assembled
break
}
} else if hi.line > line_num {
if elide_sp && lo.line < line_num {
// This line is inbetween the first and last line of the span,
// so we may want to elide it.
continue 'l;
} else {
break
}
}
line_spans += 1;
spans.next();
for (pos, ch) in line_chars.by_ref() {
// Span seems to use half-opened interval, so subtract 1
if pos >= hi.col.to_usize() - 1 { break; }
// Whenever a tab occurs on the previous line, we insert one on
// the error-point-squiggly-line as well (instead of a space).
// That way the squiggly line will usually appear in the correct
// position.
match ch {
'\t' => s.push('\t'),
_ => s.push(' '),
}
}
s.push('^');
}
if prev_line_index != line.line_index.wrapping_sub(1) {
// If we elided something, put an ellipsis.
write!(&mut self.dst, "{0:1$}...\n", "", skip)?;
}
// Print offending code-lines
write!(&mut self.dst, "{}:{:>width$} {}\n", fm.name,
line.line_index + 1, line_str, width=digits)?;
remaining_err_lines -= 1;
if s.len() > skip {
// Render the spans we assembled previously (if any)
println_maybe_styled!(&mut self.dst, term::Attr::ForegroundColor(lvl.color()),
"{}", s)?;
}
prev_line_index = line.line_index;
} }
Ok(()) Ok(())
} }
@ -602,6 +324,7 @@ fn line_num_max_digits(line: &codemap::LineInfo) -> usize {
digits digits
} }
fn print_diagnostic(dst: &mut Destination, fn print_diagnostic(dst: &mut Destination,
topic: &str, topic: &str,
lvl: Level, lvl: Level,
@ -609,17 +332,22 @@ fn print_diagnostic(dst: &mut Destination,
code: Option<&str>) code: Option<&str>)
-> io::Result<()> { -> io::Result<()> {
if !topic.is_empty() { if !topic.is_empty() {
write!(dst, "{} ", topic)?; dst.start_attr(term::Attr::ForegroundColor(lvl.color()))?;
write!(dst, "{}: ", topic)?;
dst.reset_attrs()?;
} }
dst.start_attr(term::Attr::Bold)?;
print_maybe_styled!(dst, term::Attr::ForegroundColor(lvl.color()), dst.start_attr(term::Attr::ForegroundColor(lvl.color()))?;
"{}: ", lvl.to_string())?; write!(dst, "{}", lvl.to_string())?;
print_maybe_styled!(dst, term::Attr::Bold, "{}", msg)?; dst.reset_attrs()?;
write!(dst, ": ")?;
dst.start_attr(term::Attr::Bold)?;
write!(dst, "{}", msg)?;
if let Some(code) = code { if let Some(code) = code {
let style = term::Attr::ForegroundColor(term::color::BRIGHT_MAGENTA); let style = term::Attr::ForegroundColor(term::color::BRIGHT_MAGENTA);
print_maybe_styled!(dst, style, " [{}]", code.clone())?; print_maybe_styled!(dst, style, " [{}]", code.clone())?;
} }
dst.reset_attrs()?;
write!(dst, "\n")?; write!(dst, "\n")?;
Ok(()) Ok(())
} }
@ -660,6 +388,52 @@ impl Destination {
} }
} }
fn apply_style(&mut self,
lvl: Level,
_kind: &RenderedLineKind,
style: Style)
-> io::Result<()> {
match style {
Style::FileNameStyle => {
}
Style::LineAndColumn => {
}
Style::LineNumber => {
self.start_attr(term::Attr::Bold)?;
self.start_attr(term::Attr::ForegroundColor(term::color::BRIGHT_BLUE))?;
}
Style::Quotation => {
}
Style::UnderlinePrimary | Style::LabelPrimary => {
self.start_attr(term::Attr::Bold)?;
self.start_attr(term::Attr::ForegroundColor(lvl.color()))?;
}
Style::UnderlineSecondary | Style::LabelSecondary => {
self.start_attr(term::Attr::Bold)?;
self.start_attr(term::Attr::ForegroundColor(term::color::BRIGHT_BLUE))?;
}
Style::NoStyle => {
}
}
Ok(())
}
fn start_attr(&mut self, attr: term::Attr) -> io::Result<()> {
match *self {
Terminal(ref mut t) => { t.attr(attr)?; }
Raw(_) => { }
}
Ok(())
}
fn reset_attrs(&mut self) -> io::Result<()> {
match *self {
Terminal(ref mut t) => { t.reset()?; }
Raw(_) => { }
}
Ok(())
}
fn print_maybe_styled(&mut self, fn print_maybe_styled(&mut self,
args: fmt::Arguments, args: fmt::Arguments,
color: term::Attr, color: term::Attr,
@ -741,7 +515,7 @@ mod test {
/// that this can span lines and so on. /// that this can span lines and so on.
fn span_from_selection(input: &str, selection: &str) -> Span { fn span_from_selection(input: &str, selection: &str) -> Span {
assert_eq!(input.len(), selection.len()); assert_eq!(input.len(), selection.len());
let left_index = selection.find('^').unwrap() as u32; let left_index = selection.find('~').unwrap() as u32;
let right_index = selection.rfind('~').map(|x|x as u32).unwrap_or(left_index); 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 } Span { lo: BytePos(left_index), hi: BytePos(right_index + 1), expn_id: NO_EXPANSION }
} }
@ -777,12 +551,15 @@ mod test {
let vec = data.lock().unwrap().clone(); let vec = data.lock().unwrap().clone();
let vec: &[u8] = &vec; let vec: &[u8] = &vec;
let str = from_utf8(vec).unwrap(); let str = from_utf8(vec).unwrap();
println!("{}", str); println!("r#\"\n{}\"#", str);
assert_eq!(str, "dummy.txt: 8 line8\n\ assert_eq!(str, &r#"
dummy.txt: 9 line9\n\ --> dummy.txt:8:1
dummy.txt:10 line10\n\ 8 |> line8
dummy.txt:11 e--vän\n\ |> ^^^^^^^^^^^^^
dummy.txt:12 tolv\n"); ...
11 |> e--vän
|> ^^^^^^^^^^^^^^^^
"#[1..]);
} }
#[test] #[test]
@ -790,7 +567,7 @@ mod test {
// Test that a `MultiSpan` containing a single span splices a substition correctly // Test that a `MultiSpan` containing a single span splices a substition correctly
let cm = CodeMap::new(); let cm = CodeMap::new();
let inputtext = "aaaaa\nbbbbBB\nCCC\nDDDDDddddd\neee\n"; let inputtext = "aaaaa\nbbbbBB\nCCC\nDDDDDddddd\neee\n";
let selection = " \n ^~\n~~~\n~~~~~ \n \n"; let selection = " \n ~~\n~~~\n~~~~~ \n \n";
cm.new_filemap_and_lines("blork.rs", inputtext); cm.new_filemap_and_lines("blork.rs", inputtext);
let sp = span_from_selection(inputtext, selection); let sp = span_from_selection(inputtext, selection);
let msp: MultiSpan = sp.into(); let msp: MultiSpan = sp.into();
@ -808,51 +585,25 @@ mod test {
} }
#[test] #[test]
fn test_multiple_span_splice() { fn test_multi_span_splice() {
// Test that a `MultiSpan` containing multiple spans splices substitions on // Test that a `MultiSpan` containing multiple spans splices a substition correctly
// several lines correctly
let cm = CodeMap::new(); let cm = CodeMap::new();
let inp = "aaaaabbbbBB\nZZ\nZZ\nCCCDDDDDdddddeee"; let inputtext = "aaaaa\nbbbbBB\nCCC\nDDDDDddddd\neee\n";
let sp1 = " ^~~~~~\n \n \n "; let selection1 = " \n \n \n \n ~ \n"; // intentionally out of order
let sp2 = " \n \n \n^~~~~~ "; let selection2 = " \n ~~\n~~~\n~~~~~ \n \n";
let sp3 = " \n \n \n ^~~ "; cm.new_filemap_and_lines("blork.rs", inputtext);
let sp4 = " \n \n \n ^~~~ "; let sp1 = span_from_selection(inputtext, selection1);
let sp2 = span_from_selection(inputtext, selection2);
let msp: MultiSpan = MultiSpan::from_spans(vec![sp1, sp2]);
let span_eq = |sp, eq| assert_eq!(&cm.span_to_snippet(sp).unwrap(), eq); let expected = "bbbbZZZZZZddddd\neXYZe";
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 { let suggest = CodeSuggestion {
msp: msp, msp: msp,
substitutes: substitutes.clone(), substitutes: vec!["ZZZZZZ".to_owned(),
"XYZ".to_owned()]
}; };
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` assert_eq!(suggest.splice_lines(&cm), expected);
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] #[test]
@ -862,17 +613,17 @@ mod test {
let mut diag = EmitterWriter::new(Box::new(Sink(data.clone())), None, cm.clone()); let mut diag = EmitterWriter::new(Box::new(Sink(data.clone())), None, cm.clone());
let inp = "_____aaaaaa____bbbbbb__cccccdd_"; let inp = "_____aaaaaa____bbbbbb__cccccdd_";
let sp1 = " ^~~~~~ "; let sp1 = " ~~~~~~ ";
let sp2 = " ^~~~~~ "; let sp2 = " ~~~~~~ ";
let sp3 = " ^~~~~ "; let sp3 = " ~~~~~ ";
let sp4 = " ^~~~ "; let sp4 = " ~~~~ ";
let sp34 = " ^~~~~~~ "; let sp34 = " ~~~~~~~ ";
let sp4_end = " ^~ ";
let expect_start = "dummy.txt:1 _____aaaaaa____bbbbbb__cccccdd_\n\ let expect_start = &r#"
\x20 ^~~~~~ ^~~~~~ ^~~~~~~\n"; --> dummy.txt:1:6
let expect_end = "dummy.txt:1 _____aaaaaa____bbbbbb__cccccdd_\n\ 1 |> _____aaaaaa____bbbbbb__cccccdd_
\x20 ^ ^ ^ ^\n"; |> ^^^^^^ ^^^^^^ ^^^^^^^
"#[1..];
let span = |sp, expected| { let span = |sp, expected| {
let sp = span_from_selection(inp, sp); let sp = span_from_selection(inp, sp);
@ -885,7 +636,6 @@ mod test {
let sp3 = span(sp3, "ccccc"); let sp3 = span(sp3, "ccccc");
let sp4 = span(sp4, "ccdd"); let sp4 = span(sp4, "ccdd");
let sp34 = span(sp34, "cccccdd"); let sp34 = span(sp34, "cccccdd");
let sp4_end = span(sp4_end, "dd");
let spans = vec![sp1, sp2, sp3, sp4]; let spans = vec![sp1, sp2, sp3, sp4];
@ -894,26 +644,17 @@ mod test {
highlight(); highlight();
let vec = data.lock().unwrap().clone(); let vec = data.lock().unwrap().clone();
let actual = from_utf8(&vec[..]).unwrap(); let actual = from_utf8(&vec[..]).unwrap();
println!("actual=\n{}", actual);
assert_eq!(actual, expected); assert_eq!(actual, expected);
}; };
let msp = MultiSpan { spans: vec![sp1, sp2, sp34] }; let msp = MultiSpan::from_spans(vec![sp1, sp2, sp34]);
let msp_end = MultiSpan { spans: vec![sp1, sp2, sp3, sp4_end] };
test(expect_start, &mut || { test(expect_start, &mut || {
diag.highlight_lines(&msp, Level::Error).unwrap(); diag.highlight_lines(&msp, Level::Error).unwrap();
}); });
test(expect_end, &mut || {
diag.end_highlight_lines(&msp_end, Level::Error).unwrap();
});
test(expect_start, &mut || { test(expect_start, &mut || {
for msp in cm.group_spans(spans.clone()) { let msp = MultiSpan::from_spans(spans.clone());
diag.highlight_lines(&msp, Level::Error).unwrap(); 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();
}
}); });
} }
@ -950,75 +691,31 @@ mod test {
let sp4 = span(10, 10, (2, 3)); let sp4 = span(10, 10, (2, 3));
let sp5 = span(10, 10, (4, 6)); let sp5 = span(10, 10, (4, 6));
let expect0 = "dummy.txt: 5 ccccc\n\ let expect0 = &r#"
dummy.txt: 6 xxxxx\n\ --> dummy.txt:5:1
dummy.txt: 7 yyyyy\n\ 5 |> ccccc
\x20 ...\n\ |> ^^^^^
dummy.txt: 9 ddd__eee_\n\ ...
\x20 ^~~ ^~~\n\ 8 |> _____
\x20 ...\n\ 9 |> ddd__eee_
dummy.txt:11 __f_gg\n\ |> ^^^ ^^^
\x20 ^ ^~\n"; 10 |> elided
11 |> __f_gg
|> ^ ^^
"#[1..];
let expect = "dummy.txt: 1 aaaaa\n\ let expect = &r#"
dummy.txt: 2 aaaaa\n\ --> dummy.txt:1:1
dummy.txt: 3 aaaaa\n\ 1 |> aaaaa
dummy.txt: 4 bbbbb\n\ |> ^^^^^
dummy.txt: 5 ccccc\n\ ...
dummy.txt: 6 xxxxx\n\ 8 |> _____
\x20 ...\n"; 9 |> ddd__eee_
|> ^^^ ^^^
let expect_g1 = "dummy.txt:1 aaaaa\n\ 10 |> elided
dummy.txt:2 aaaaa\n\ 11 |> __f_gg
dummy.txt:3 aaaaa\n\ |> ^ ^^
dummy.txt:4 bbbbb\n\ "#[1..];
dummy.txt:5 ccccc\n\
dummy.txt:6 xxxxx\n\
\x20 ...\n";
let expect2 = "dummy.txt: 9 ddd__eee_\n\
\x20 ^~~ ^~~\n\
\x20 ...\n\
dummy.txt:11 __f_gg\n\
\x20 ^ ^~\n";
let expect_end = "dummy.txt: 1 aaaaa\n\
\x20 ...\n\
dummy.txt: 7 yyyyy\n\
\x20 ^\n\
\x20 ...\n\
dummy.txt: 9 ddd__eee_\n\
\x20 ^ ^\n\
\x20 ...\n\
dummy.txt:11 __f_gg\n\
\x20 ^ ^\n";
let expect0_end = "dummy.txt: 5 ccccc\n\
dummy.txt: 6 xxxxx\n\
dummy.txt: 7 yyyyy\n\
\x20 ^\n\
\x20 ...\n\
dummy.txt: 9 ddd__eee_\n\
\x20 ^ ^\n\
\x20 ...\n\
dummy.txt:11 __f_gg\n\
\x20 ^ ^\n";
let expect_end_g1 = "dummy.txt:1 aaaaa\n\
\x20 ...\n\
dummy.txt:7 yyyyy\n\
\x20 ^\n";
let expect2_end = "dummy.txt: 9 ddd__eee_\n\
\x20 ^ ^\n\
\x20 ...\n\
dummy.txt:11 __f_gg\n\
\x20 ^ ^\n";
let expect_groups = [expect2, expect_g1];
let expect_end_groups = [expect2_end, expect_end_g1];
let spans = vec![sp3, sp1, sp4, sp2, sp5];
macro_rules! test { macro_rules! test {
($expected: expr, $highlight: expr) => ({ ($expected: expr, $highlight: expr) => ({
@ -1034,37 +731,14 @@ mod test {
}); });
} }
let msp0 = MultiSpan { spans: vec![sp0, sp2, sp3, sp4, sp5] }; let msp0 = MultiSpan::from_spans(vec![sp0, sp2, sp3, sp4, sp5]);
let msp = MultiSpan { spans: vec![sp1, sp2, sp3, sp4, sp5] }; let msp = MultiSpan::from_spans(vec![sp1, sp2, sp3, sp4, sp5]);
let msp2 = MultiSpan { spans: vec![sp2, sp3, sp4, sp5] };
test!(expect0, || { test!(expect0, || {
diag.highlight_lines(&msp0, Level::Error).unwrap(); diag.highlight_lines(&msp0, Level::Error).unwrap();
}); });
test!(expect0_end, || {
diag.end_highlight_lines(&msp0, Level::Error).unwrap();
});
test!(expect, || { test!(expect, || {
diag.highlight_lines(&msp, Level::Error).unwrap(); 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();
});
}
} }
} }

View File

@ -13,7 +13,7 @@ pub use errors::emitter::ColorConfig;
use self::Level::*; use self::Level::*;
use self::RenderSpan::*; use self::RenderSpan::*;
use codemap::{self, CodeMap, MultiSpan}; use codemap::{self, CodeMap, MultiSpan, NO_EXPANSION, Span};
use diagnostics; use diagnostics;
use errors::emitter::{Emitter, EmitterWriter}; use errors::emitter::{Emitter, EmitterWriter};
@ -24,6 +24,7 @@ use term;
pub mod emitter; pub mod emitter;
pub mod json; pub mod json;
pub mod snippet;
#[derive(Clone)] #[derive(Clone)]
pub enum RenderSpan { pub enum RenderSpan {
@ -32,13 +33,6 @@ pub enum RenderSpan {
/// the source code covered by the span. /// the source code covered by the span.
FullSpan(MultiSpan), FullSpan(MultiSpan),
/// Similar to a FullSpan, but the cited position is the end of
/// the span, instead of the start. Used, at least, for telling
/// compiletest/runtest to look at the last line of the span
/// (since `end_highlight_lines` displays an arrow to the end
/// of the span).
EndSpan(MultiSpan),
/// A suggestion renders with both with an initial line for the /// A suggestion renders with both with an initial line for the
/// message, prefixed by file:linenum, followed by a summary /// message, prefixed by file:linenum, followed by a summary
/// of hypothetical source code, where each `String` is spliced /// of hypothetical source code, where each `String` is spliced
@ -61,7 +55,6 @@ impl RenderSpan {
match *self { match *self {
FullSpan(ref msp) | FullSpan(ref msp) |
Suggestion(CodeSuggestion { ref msp, .. }) | Suggestion(CodeSuggestion { ref msp, .. }) |
EndSpan(ref msp) |
FileLine(ref msp) => FileLine(ref msp) =>
msp msp
} }
@ -88,12 +81,24 @@ impl CodeSuggestion {
} }
} }
} }
let bounds = self.msp.to_span_bounds();
let lines = cm.span_to_lines(bounds).unwrap();
assert!(!lines.lines.is_empty());
// This isn't strictly necessary, but would in all likelyhood be an error let mut primary_spans = self.msp.primary_spans().to_owned();
assert_eq!(self.msp.spans.len(), self.substitutes.len());
assert_eq!(primary_spans.len(), self.substitutes.len());
if primary_spans.is_empty() {
return format!("");
}
// Assumption: all spans are in the same file, and all spans
// are disjoint. Sort in ascending order.
primary_spans.sort_by_key(|sp| sp.lo);
// Find the bounding span.
let lo = primary_spans.iter().map(|sp| sp.lo).min().unwrap();
let hi = primary_spans.iter().map(|sp| sp.hi).min().unwrap();
let bounding_span = Span { lo: lo, hi: hi, expn_id: NO_EXPANSION };
let lines = cm.span_to_lines(bounding_span).unwrap();
assert!(!lines.lines.is_empty());
// To build up the result, we do this for each span: // To build up the result, we do this for each span:
// - push the line segment trailing the previous span // - push the line segment trailing the previous span
@ -105,13 +110,13 @@ impl CodeSuggestion {
// //
// Finally push the trailing line segment of the last span // Finally push the trailing line segment of the last span
let fm = &lines.file; let fm = &lines.file;
let mut prev_hi = cm.lookup_char_pos(bounds.lo); let mut prev_hi = cm.lookup_char_pos(bounding_span.lo);
prev_hi.col = CharPos::from_usize(0); prev_hi.col = CharPos::from_usize(0);
let mut prev_line = fm.get_line(lines.lines[0].line_index); let mut prev_line = fm.get_line(lines.lines[0].line_index);
let mut buf = String::new(); let mut buf = String::new();
for (sp, substitute) in self.msp.spans.iter().zip(self.substitutes.iter()) { for (sp, substitute) in primary_spans.iter().zip(self.substitutes.iter()) {
let cur_lo = cm.lookup_char_pos(sp.lo); let cur_lo = cm.lookup_char_pos(sp.lo);
if prev_hi.line == cur_lo.line { if prev_hi.line == cur_lo.line {
push_trailing(&mut buf, prev_line, &prev_hi, Some(&cur_lo)); push_trailing(&mut buf, prev_line, &prev_hi, Some(&cur_lo));
@ -183,7 +188,7 @@ pub struct DiagnosticBuilder<'a> {
level: Level, level: Level,
message: String, message: String,
code: Option<String>, code: Option<String>,
span: Option<MultiSpan>, span: MultiSpan,
children: Vec<SubDiagnostic>, children: Vec<SubDiagnostic>,
} }
@ -192,7 +197,7 @@ pub struct DiagnosticBuilder<'a> {
struct SubDiagnostic { struct SubDiagnostic {
level: Level, level: Level,
message: String, message: String,
span: Option<MultiSpan>, span: MultiSpan,
render_span: Option<RenderSpan>, render_span: Option<RenderSpan>,
} }
@ -228,37 +233,61 @@ impl<'a> DiagnosticBuilder<'a> {
self.level == Level::Fatal self.level == Level::Fatal
} }
pub fn note(&mut self , msg: &str) -> &mut DiagnosticBuilder<'a> { /// Add a span/label to be included in the resulting snippet.
self.sub(Level::Note, msg, None, None); /// This is pushed onto the `MultiSpan` that was created when the
/// diagnostic was first built. If you don't call this function at
/// all, and you just supplied a `Span` to create the diagnostic,
/// then the snippet will just include that `Span`, which is
/// called the primary span.
pub fn span_label(mut self, span: Span, label: &fmt::Display)
-> DiagnosticBuilder<'a> {
self.span.push_span_label(span, format!("{}", label));
self
}
pub fn note_expected_found(mut self,
label: &fmt::Display,
expected: &fmt::Display,
found: &fmt::Display)
-> DiagnosticBuilder<'a>
{
// For now, just attach these as notes
self.note(&format!("expected {} `{}`", label, expected));
self.note(&format!(" found {} `{}`", label, found));
self
}
pub fn note(&mut self, msg: &str) -> &mut DiagnosticBuilder<'a> {
self.sub(Level::Note, msg, MultiSpan::new(), None);
self self
} }
pub fn span_note<S: Into<MultiSpan>>(&mut self, pub fn span_note<S: Into<MultiSpan>>(&mut self,
sp: S, sp: S,
msg: &str) msg: &str)
-> &mut DiagnosticBuilder<'a> { -> &mut DiagnosticBuilder<'a> {
self.sub(Level::Note, msg, Some(sp.into()), None); self.sub(Level::Note, msg, sp.into(), None);
self 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.sub(Level::Warning, msg, MultiSpan::new(), None);
self self
} }
pub fn span_warn<S: Into<MultiSpan>>(&mut self, pub fn span_warn<S: Into<MultiSpan>>(&mut self,
sp: S, sp: S,
msg: &str) msg: &str)
-> &mut DiagnosticBuilder<'a> { -> &mut DiagnosticBuilder<'a> {
self.sub(Level::Warning, msg, Some(sp.into()), None); self.sub(Level::Warning, msg, sp.into(), None);
self 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.sub(Level::Help, msg, MultiSpan::new(), None);
self self
} }
pub fn span_help<S: Into<MultiSpan>>(&mut self, pub fn span_help<S: Into<MultiSpan>>(&mut self,
sp: S, sp: S,
msg: &str) msg: &str)
-> &mut DiagnosticBuilder<'a> { -> &mut DiagnosticBuilder<'a> {
self.sub(Level::Help, msg, Some(sp.into()), None); self.sub(Level::Help, msg, sp.into(), None);
self self
} }
/// Prints out a message with a suggested edit of the code. /// Prints out a message with a suggested edit of the code.
@ -269,43 +298,15 @@ impl<'a> DiagnosticBuilder<'a> {
msg: &str, msg: &str,
suggestion: String) suggestion: String)
-> &mut DiagnosticBuilder<'a> { -> &mut DiagnosticBuilder<'a> {
self.sub(Level::Help, msg, None, Some(Suggestion(CodeSuggestion { self.sub(Level::Help, msg, MultiSpan::new(), Some(Suggestion(CodeSuggestion {
msp: sp.into(), msp: sp.into(),
substitutes: vec![suggestion], substitutes: vec![suggestion],
}))); })));
self self
} }
pub fn span_end_note<S: Into<MultiSpan>>(&mut self,
sp: S,
msg: &str)
-> &mut DiagnosticBuilder<'a> {
self.sub(Level::Note, msg, None, Some(EndSpan(sp.into())));
self
}
pub fn fileline_warn<S: Into<MultiSpan>>(&mut self,
sp: S,
msg: &str)
-> &mut DiagnosticBuilder<'a> {
self.sub(Level::Warning, msg, None, Some(FileLine(sp.into())));
self
}
pub fn fileline_note<S: Into<MultiSpan>>(&mut self,
sp: S,
msg: &str)
-> &mut DiagnosticBuilder<'a> {
self.sub(Level::Note, msg, None, Some(FileLine(sp.into())));
self
}
pub fn fileline_help<S: Into<MultiSpan>>(&mut self,
sp: S,
msg: &str)
-> &mut DiagnosticBuilder<'a> {
self.sub(Level::Help, msg, None, Some(FileLine(sp.into())));
self
}
pub fn span<S: Into<MultiSpan>>(&mut self, sp: S) -> &mut Self { pub fn set_span<S: Into<MultiSpan>>(&mut self, sp: S) -> &mut Self {
self.span = Some(sp.into()); self.span = sp.into();
self self
} }
@ -324,7 +325,7 @@ impl<'a> DiagnosticBuilder<'a> {
level: level, level: level,
message: message.to_owned(), message: message.to_owned(),
code: None, code: None,
span: None, span: MultiSpan::new(),
children: vec![], children: vec![],
} }
} }
@ -334,7 +335,7 @@ impl<'a> DiagnosticBuilder<'a> {
fn sub(&mut self, fn sub(&mut self,
level: Level, level: Level,
message: &str, message: &str,
span: Option<MultiSpan>, span: MultiSpan,
render_span: Option<RenderSpan>) { render_span: Option<RenderSpan>) {
let sub = SubDiagnostic { let sub = SubDiagnostic {
level: level, level: level,
@ -357,7 +358,10 @@ impl<'a> fmt::Debug for DiagnosticBuilder<'a> {
impl<'a> Drop for DiagnosticBuilder<'a> { impl<'a> Drop for DiagnosticBuilder<'a> {
fn drop(&mut self) { fn drop(&mut self) {
if !self.cancelled() { if !self.cancelled() {
self.emitter.borrow_mut().emit(None, "Error constructed but not emitted", None, Bug); self.emitter.borrow_mut().emit(&MultiSpan::new(),
"Error constructed but not emitted",
None,
Bug);
panic!(); panic!();
} }
} }
@ -412,7 +416,7 @@ impl Handler {
msg: &str) msg: &str)
-> DiagnosticBuilder<'a> { -> DiagnosticBuilder<'a> {
let mut result = DiagnosticBuilder::new(&self.emit, Level::Warning, msg); let mut result = DiagnosticBuilder::new(&self.emit, Level::Warning, msg);
result.span(sp); result.set_span(sp);
if !self.can_emit_warnings { if !self.can_emit_warnings {
result.cancel(); result.cancel();
} }
@ -424,7 +428,7 @@ impl Handler {
code: &str) code: &str)
-> DiagnosticBuilder<'a> { -> DiagnosticBuilder<'a> {
let mut result = DiagnosticBuilder::new(&self.emit, Level::Warning, msg); let mut result = DiagnosticBuilder::new(&self.emit, Level::Warning, msg);
result.span(sp); result.set_span(sp);
result.code(code.to_owned()); result.code(code.to_owned());
if !self.can_emit_warnings { if !self.can_emit_warnings {
result.cancel(); result.cancel();
@ -444,7 +448,7 @@ impl Handler {
-> DiagnosticBuilder<'a> { -> DiagnosticBuilder<'a> {
self.bump_err_count(); self.bump_err_count();
let mut result = DiagnosticBuilder::new(&self.emit, Level::Error, msg); let mut result = DiagnosticBuilder::new(&self.emit, Level::Error, msg);
result.span(sp); result.set_span(sp);
result result
} }
pub fn struct_span_err_with_code<'a, S: Into<MultiSpan>>(&'a self, pub fn struct_span_err_with_code<'a, S: Into<MultiSpan>>(&'a self,
@ -454,7 +458,7 @@ impl Handler {
-> DiagnosticBuilder<'a> { -> DiagnosticBuilder<'a> {
self.bump_err_count(); self.bump_err_count();
let mut result = DiagnosticBuilder::new(&self.emit, Level::Error, msg); let mut result = DiagnosticBuilder::new(&self.emit, Level::Error, msg);
result.span(sp); result.set_span(sp);
result.code(code.to_owned()); result.code(code.to_owned());
result result
} }
@ -468,7 +472,7 @@ impl Handler {
-> DiagnosticBuilder<'a> { -> DiagnosticBuilder<'a> {
self.bump_err_count(); self.bump_err_count();
let mut result = DiagnosticBuilder::new(&self.emit, Level::Fatal, msg); let mut result = DiagnosticBuilder::new(&self.emit, Level::Fatal, msg);
result.span(sp); result.set_span(sp);
result result
} }
pub fn struct_span_fatal_with_code<'a, S: Into<MultiSpan>>(&'a self, pub fn struct_span_fatal_with_code<'a, S: Into<MultiSpan>>(&'a self,
@ -478,7 +482,7 @@ impl Handler {
-> DiagnosticBuilder<'a> { -> DiagnosticBuilder<'a> {
self.bump_err_count(); self.bump_err_count();
let mut result = DiagnosticBuilder::new(&self.emit, Level::Fatal, msg); let mut result = DiagnosticBuilder::new(&self.emit, Level::Fatal, msg);
result.span(sp); result.set_span(sp);
result.code(code.to_owned()); result.code(code.to_owned());
result result
} }
@ -499,7 +503,7 @@ impl Handler {
if self.treat_err_as_bug { if self.treat_err_as_bug {
self.span_bug(sp, msg); self.span_bug(sp, msg);
} }
self.emit(Some(&sp.into()), msg, Fatal); self.emit(&sp.into(), msg, Fatal);
self.bump_err_count(); self.bump_err_count();
return FatalError; return FatalError;
} }
@ -508,7 +512,7 @@ impl Handler {
if self.treat_err_as_bug { if self.treat_err_as_bug {
self.span_bug(sp, msg); self.span_bug(sp, msg);
} }
self.emit_with_code(Some(&sp.into()), msg, code, Fatal); self.emit_with_code(&sp.into(), msg, code, Fatal);
self.bump_err_count(); self.bump_err_count();
return FatalError; return FatalError;
} }
@ -516,24 +520,24 @@ impl Handler {
if self.treat_err_as_bug { if self.treat_err_as_bug {
self.span_bug(sp, msg); self.span_bug(sp, msg);
} }
self.emit(Some(&sp.into()), msg, Error); self.emit(&sp.into(), msg, Error);
self.bump_err_count(); self.bump_err_count();
} }
pub fn span_err_with_code<S: Into<MultiSpan>>(&self, sp: S, 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 { if self.treat_err_as_bug {
self.span_bug(sp, msg); self.span_bug(sp, msg);
} }
self.emit_with_code(Some(&sp.into()), msg, code, Error); self.emit_with_code(&sp.into(), msg, code, Error);
self.bump_err_count(); self.bump_err_count();
} }
pub fn span_warn<S: Into<MultiSpan>>(&self, sp: S, msg: &str) { pub fn span_warn<S: Into<MultiSpan>>(&self, sp: S, msg: &str) {
self.emit(Some(&sp.into()), msg, Warning); self.emit(&sp.into(), msg, Warning);
} }
pub fn span_warn_with_code<S: Into<MultiSpan>>(&self, sp: S, msg: &str, code: &str) { pub fn span_warn_with_code<S: Into<MultiSpan>>(&self, sp: S, msg: &str, code: &str) {
self.emit_with_code(Some(&sp.into()), msg, code, Warning); self.emit_with_code(&sp.into(), msg, code, Warning);
} }
pub fn span_bug<S: Into<MultiSpan>>(&self, sp: S, msg: &str) -> ! { pub fn span_bug<S: Into<MultiSpan>>(&self, sp: S, msg: &str) -> ! {
self.emit(Some(&sp.into()), msg, Bug); self.emit(&sp.into(), msg, Bug);
panic!(ExplicitBug); panic!(ExplicitBug);
} }
pub fn delay_span_bug<S: Into<MultiSpan>>(&self, sp: S, msg: &str) { pub fn delay_span_bug<S: Into<MultiSpan>>(&self, sp: S, msg: &str) {
@ -541,11 +545,11 @@ impl Handler {
*delayed = Some((sp.into(), msg.to_string())); *delayed = Some((sp.into(), msg.to_string()));
} }
pub fn span_bug_no_panic<S: Into<MultiSpan>>(&self, sp: S, msg: &str) { pub fn span_bug_no_panic<S: Into<MultiSpan>>(&self, sp: S, msg: &str) {
self.emit(Some(&sp.into()), msg, Bug); self.emit(&sp.into(), msg, Bug);
self.bump_err_count(); self.bump_err_count();
} }
pub fn span_note_without_error<S: Into<MultiSpan>>(&self, sp: S, msg: &str) { pub fn span_note_without_error<S: Into<MultiSpan>>(&self, sp: S, msg: &str) {
self.emit.borrow_mut().emit(Some(&sp.into()), msg, None, Note); self.emit.borrow_mut().emit(&sp.into(), msg, None, Note);
} }
pub fn span_unimpl<S: Into<MultiSpan>>(&self, sp: S, msg: &str) -> ! { pub fn span_unimpl<S: Into<MultiSpan>>(&self, sp: S, msg: &str) -> ! {
self.span_bug(sp, &format!("unimplemented {}", msg)); self.span_bug(sp, &format!("unimplemented {}", msg));
@ -554,7 +558,7 @@ impl Handler {
if self.treat_err_as_bug { if self.treat_err_as_bug {
self.bug(msg); self.bug(msg);
} }
self.emit.borrow_mut().emit(None, msg, None, Fatal); self.emit.borrow_mut().emit(&MultiSpan::new(), msg, None, Fatal);
self.bump_err_count(); self.bump_err_count();
FatalError FatalError
} }
@ -562,17 +566,17 @@ impl Handler {
if self.treat_err_as_bug { if self.treat_err_as_bug {
self.bug(msg); self.bug(msg);
} }
self.emit.borrow_mut().emit(None, msg, None, Error); self.emit.borrow_mut().emit(&MultiSpan::new(), msg, None, Error);
self.bump_err_count(); self.bump_err_count();
} }
pub fn warn(&self, msg: &str) { pub fn warn(&self, msg: &str) {
self.emit.borrow_mut().emit(None, msg, None, Warning); self.emit.borrow_mut().emit(&MultiSpan::new(), msg, None, Warning);
} }
pub fn note_without_error(&self, msg: &str) { pub fn note_without_error(&self, msg: &str) {
self.emit.borrow_mut().emit(None, msg, None, Note); self.emit.borrow_mut().emit(&MultiSpan::new(), msg, None, Note);
} }
pub fn bug(&self, msg: &str) -> ! { pub fn bug(&self, msg: &str) -> ! {
self.emit.borrow_mut().emit(None, msg, None, Bug); self.emit.borrow_mut().emit(&MultiSpan::new(), msg, None, Bug);
panic!(ExplicitBug); panic!(ExplicitBug);
} }
pub fn unimpl(&self, msg: &str) -> ! { pub fn unimpl(&self, msg: &str) -> ! {
@ -614,25 +618,20 @@ impl Handler {
panic!(self.fatal(&s)); panic!(self.fatal(&s));
} }
pub fn emit(&self, pub fn emit(&self,
msp: Option<&MultiSpan>, msp: &MultiSpan,
msg: &str, msg: &str,
lvl: Level) { lvl: Level) {
if lvl == Warning && !self.can_emit_warnings { return } if lvl == Warning && !self.can_emit_warnings { return }
self.emit.borrow_mut().emit(msp, msg, None, lvl); self.emit.borrow_mut().emit(&msp, msg, None, lvl);
if !self.continue_after_error.get() { self.abort_if_errors(); } if !self.continue_after_error.get() { self.abort_if_errors(); }
} }
pub fn emit_with_code(&self, pub fn emit_with_code(&self,
msp: Option<&MultiSpan>, msp: &MultiSpan,
msg: &str, msg: &str,
code: &str, code: &str,
lvl: Level) { lvl: Level) {
if lvl == Warning && !self.can_emit_warnings { return } if lvl == Warning && !self.can_emit_warnings { return }
self.emit.borrow_mut().emit(msp, msg, Some(code), lvl); self.emit.borrow_mut().emit(&msp, msg, Some(code), lvl);
if !self.continue_after_error.get() { self.abort_if_errors(); }
}
pub fn custom_emit(&self, rsp: RenderSpan, msg: &str, lvl: Level) {
if lvl == Warning && !self.can_emit_warnings { return }
self.emit.borrow_mut().custom_emit(&rsp, msg, lvl);
if !self.continue_after_error.get() { self.abort_if_errors(); } if !self.continue_after_error.get() { self.abort_if_errors(); }
} }
} }