diff --git a/src/librustc/session/mod.rs b/src/librustc/session/mod.rs index 975ec0e709b7d..6bee4718c77cc 100644 --- a/src/librustc/session/mod.rs +++ b/src/librustc/session/mod.rs @@ -15,7 +15,7 @@ use session::search_paths::PathKind; use util::nodemap::{NodeMap, FnvHashMap}; use syntax::ast::{NodeId, NodeIdAssigner, Name}; -use syntax::codemap::Span; +use syntax::codemap::{Span, MultiSpan}; use syntax::errors::{self, DiagnosticBuilder}; use syntax::errors::emitter::{Emitter, BasicEmitter, EmitterWriter}; use syntax::errors::json::JsonEmitter; @@ -47,7 +47,7 @@ pub struct Session { pub cstore: Rc CrateStore<'a>>, pub parse_sess: ParseSess, // For a library crate, this is always none - pub entry_fn: RefCell>, + pub entry_fn: RefCell>, pub entry_type: Cell>, pub plugin_registrar_fn: Cell>, pub default_sysroot: Option, @@ -57,7 +57,7 @@ pub struct Session { pub local_crate_source_file: Option, pub working_dir: PathBuf, pub lint_store: RefCell, - pub lints: RefCell>>, + pub lints: RefCell>>, pub plugin_llvm_passes: RefCell>, pub plugin_attributes: RefCell>, pub crate_types: RefCell>, @@ -81,36 +81,36 @@ pub struct Session { } impl Session { - pub fn struct_span_warn<'a>(&'a self, - sp: Span, - msg: &str) - -> DiagnosticBuilder<'a> { + pub fn struct_span_warn<'a, S: Into>(&'a self, + sp: S, + msg: &str) + -> DiagnosticBuilder<'a> { self.diagnostic().struct_span_warn(sp, msg) } - 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>(&'a self, + sp: S, + msg: &str, + code: &str) + -> DiagnosticBuilder<'a> { self.diagnostic().struct_span_warn_with_code(sp, msg, code) } pub fn struct_warn<'a>(&'a self, msg: &str) -> DiagnosticBuilder<'a> { self.diagnostic().struct_warn(msg) } - pub fn struct_span_err<'a>(&'a self, - sp: Span, - msg: &str) - -> DiagnosticBuilder<'a> { + pub fn struct_span_err<'a, S: Into>(&'a self, + sp: S, + msg: &str) + -> DiagnosticBuilder<'a> { match split_msg_into_multilines(msg) { Some(ref msg) => self.diagnostic().struct_span_err(sp, msg), None => self.diagnostic().struct_span_err(sp, msg), } } - 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>(&'a self, + sp: S, + msg: &str, + code: &str) + -> DiagnosticBuilder<'a> { match split_msg_into_multilines(msg) { Some(ref msg) => self.diagnostic().struct_span_err_with_code(sp, msg, code), None => self.diagnostic().struct_span_err_with_code(sp, msg, code), @@ -119,46 +119,46 @@ impl Session { pub fn struct_err<'a>(&'a self, msg: &str) -> DiagnosticBuilder<'a> { self.diagnostic().struct_err(msg) } - pub fn struct_span_fatal<'a>(&'a self, - sp: Span, - msg: &str) - -> DiagnosticBuilder<'a> { + pub fn struct_span_fatal<'a, S: Into>(&'a self, + sp: S, + msg: &str) + -> DiagnosticBuilder<'a> { self.diagnostic().struct_span_fatal(sp, msg) } - 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>(&'a self, + sp: S, + msg: &str, + code: &str) + -> DiagnosticBuilder<'a> { self.diagnostic().struct_span_fatal_with_code(sp, msg, code) } pub fn struct_fatal<'a>(&'a self, msg: &str) -> DiagnosticBuilder<'a> { self.diagnostic().struct_fatal(msg) } - pub fn span_fatal(&self, sp: Span, msg: &str) -> ! { + pub fn span_fatal>(&self, sp: S, msg: &str) -> ! { panic!(self.diagnostic().span_fatal(sp, msg)) } - pub fn span_fatal_with_code(&self, sp: Span, msg: &str, code: &str) -> ! { + pub fn span_fatal_with_code>(&self, sp: S, msg: &str, code: &str) -> ! { panic!(self.diagnostic().span_fatal_with_code(sp, msg, code)) } pub fn fatal(&self, msg: &str) -> ! { panic!(self.diagnostic().fatal(msg)) } - pub fn span_err_or_warn(&self, is_warning: bool, sp: Span, msg: &str) { + pub fn span_err_or_warn>(&self, is_warning: bool, sp: S, msg: &str) { if is_warning { self.span_warn(sp, msg); } else { self.span_err(sp, msg); } } - pub fn span_err(&self, sp: Span, msg: &str) { + pub fn span_err>(&self, sp: S, msg: &str) { match split_msg_into_multilines(msg) { Some(msg) => self.diagnostic().span_err(sp, &msg), None => self.diagnostic().span_err(sp, msg) } } - pub fn span_err_with_code(&self, sp: Span, msg: &str, code: &str) { + pub fn span_err_with_code>(&self, sp: S, msg: &str, code: &str) { match split_msg_into_multilines(msg) { Some(msg) => self.diagnostic().span_err_with_code(sp, &msg, code), None => self.diagnostic().span_err_with_code(sp, msg, code) @@ -186,32 +186,32 @@ impl Session { } result } - pub fn span_warn(&self, sp: Span, msg: &str) { + pub fn span_warn>(&self, sp: S, msg: &str) { self.diagnostic().span_warn(sp, msg) } - pub fn span_warn_with_code(&self, sp: Span, msg: &str, code: &str) { + pub fn span_warn_with_code>(&self, sp: S, msg: &str, code: &str) { self.diagnostic().span_warn_with_code(sp, msg, code) } pub fn warn(&self, msg: &str) { self.diagnostic().warn(msg) } - pub fn opt_span_warn(&self, opt_sp: Option, msg: &str) { + pub fn opt_span_warn>(&self, opt_sp: Option, msg: &str) { match opt_sp { Some(sp) => self.span_warn(sp, msg), None => self.warn(msg), } } - pub fn opt_span_bug(&self, opt_sp: Option, msg: &str) -> ! { + pub fn opt_span_bug>(&self, opt_sp: Option, msg: &str) -> ! { match opt_sp { Some(sp) => self.span_bug(sp, msg), None => self.bug(msg), } } /// Delay a span_bug() call until abort_if_errors() - pub fn delay_span_bug(&self, sp: Span, msg: &str) { + pub fn delay_span_bug>(&self, sp: S, msg: &str) { self.diagnostic().delay_span_bug(sp, msg) } - pub fn span_bug(&self, sp: Span, msg: &str) -> ! { + pub fn span_bug>(&self, sp: S, msg: &str) -> ! { self.diagnostic().span_bug(sp, msg) } pub fn bug(&self, msg: &str) -> ! { @@ -220,10 +220,10 @@ impl Session { pub fn note_without_error(&self, msg: &str) { self.diagnostic().note_without_error(msg) } - pub fn span_note_without_error(&self, sp: Span, msg: &str) { + pub fn span_note_without_error>(&self, sp: S, msg: &str) { self.diagnostic().span_note_without_error(sp, msg) } - pub fn span_unimpl(&self, sp: Span, msg: &str) -> ! { + pub fn span_unimpl>(&self, sp: S, msg: &str) -> ! { self.diagnostic().span_unimpl(sp, msg) } pub fn unimpl(&self, msg: &str) -> ! { @@ -260,7 +260,7 @@ impl Session { } // This exists to help with refactoring to eliminate impossible // cases later on - pub fn impossible_case(&self, sp: Span, msg: &str) -> ! { + pub fn impossible_case>(&self, sp: S, msg: &str) -> ! { self.span_bug(sp, &format!("impossible case reached: {}", msg)); } pub fn verbose(&self) -> bool { self.opts.debugging_opts.verbose } diff --git a/src/librustc_borrowck/borrowck/mod.rs b/src/librustc_borrowck/borrowck/mod.rs index 631149e69d77e..3ca9a17686485 100644 --- a/src/librustc_borrowck/borrowck/mod.rs +++ b/src/librustc_borrowck/borrowck/mod.rs @@ -991,7 +991,7 @@ impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> { &format!("to force the closure to take ownership of {} \ (and any other referenced variables), \ use the `move` keyword, as shown:", - cmt_path_or_string), + cmt_path_or_string), suggestion) .emit(); } diff --git a/src/librustc_driver/test.rs b/src/librustc_driver/test.rs index b19628baa88be..a662081ac2123 100644 --- a/src/librustc_driver/test.rs +++ b/src/librustc_driver/test.rs @@ -32,7 +32,7 @@ use rustc::front::map as hir_map; use rustc::session::{self, config}; use std::rc::Rc; use syntax::{abi, ast}; -use syntax::codemap::{Span, CodeMap, DUMMY_SP}; +use syntax::codemap::{MultiSpan, CodeMap, DUMMY_SP}; use syntax::errors; use syntax::errors::emitter::Emitter; use syntax::errors::{Level, RenderSpan}; @@ -78,14 +78,14 @@ fn remove_message(e: &mut ExpectErrorEmitter, msg: &str, lvl: Level) { impl Emitter for ExpectErrorEmitter { fn emit(&mut self, - _sp: Option, + _sp: Option<&MultiSpan>, msg: &str, _: Option<&str>, lvl: Level) { remove_message(self, msg, lvl); } - fn custom_emit(&mut self, _sp: RenderSpan, msg: &str, lvl: Level) { + fn custom_emit(&mut self, _sp: &RenderSpan, msg: &str, lvl: Level) { remove_message(self, msg, lvl); } } diff --git a/src/librustc_trans/back/write.rs b/src/librustc_trans/back/write.rs index bc007da7174a7..1068bca55d61b 100644 --- a/src/librustc_trans/back/write.rs +++ b/src/librustc_trans/back/write.rs @@ -109,7 +109,7 @@ impl SharedEmitter { } impl Emitter for SharedEmitter { - fn emit(&mut self, sp: Option, + fn emit(&mut self, sp: Option<&codemap::MultiSpan>, msg: &str, code: Option<&str>, lvl: Level) { assert!(sp.is_none(), "SharedEmitter doesn't support spans"); @@ -120,7 +120,7 @@ impl Emitter for SharedEmitter { }); } - fn custom_emit(&mut self, _sp: errors::RenderSpan, _msg: &str, _lvl: Level) { + fn custom_emit(&mut self, _sp: &errors::RenderSpan, _msg: &str, _lvl: Level) { panic!("SharedEmitter doesn't support custom_emit"); } } diff --git a/src/librustc_typeck/check/mod.rs b/src/librustc_typeck/check/mod.rs index a8697f45d9156..af343e0b0790f 100644 --- a/src/librustc_typeck/check/mod.rs +++ b/src/librustc_typeck/check/mod.rs @@ -1072,8 +1072,8 @@ fn report_cast_to_unsized_type<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>, match fcx.tcx().sess.codemap().span_to_snippet(t_span) { Ok(s) => { err.span_suggestion(t_span, - "try casting to a `Box` instead:", - format!("Box<{}>", s)); + "try casting to a `Box` instead:", + format!("Box<{}>", s)); }, Err(_) => span_help!(err, t_span, "did you mean `Box<{}>`?", tstr), diff --git a/src/libsyntax/codemap.rs b/src/libsyntax/codemap.rs index 432c1688536bb..ca01623fef90f 100644 --- a/src/libsyntax/codemap.rs +++ b/src/libsyntax/codemap.rs @@ -23,6 +23,7 @@ use std::cell::{Cell, RefCell}; use std::ops::{Add, Sub}; use std::path::Path; use std::rc::Rc; +use std::cmp; use std::{fmt, fs}; use std::io::{self, Read}; @@ -31,6 +32,8 @@ use serialize::{Encodable, Decodable, Encoder, Decoder}; use ast::Name; +use errors::emitter::MAX_HIGHLIGHT_LINES; + // _____________________________________________________________________________ // Pos, BytePos, CharPos // @@ -42,7 +45,7 @@ pub trait Pos { /// A byte offset. Keep this small (currently 32-bits), as AST contains /// a lot of them. -#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] pub struct BytePos(pub u32); /// A character offset. Because of multibyte utf8 characters, a byte offset @@ -109,7 +112,7 @@ impl Sub for CharPos { } // _____________________________________________________________________________ -// Span, Spanned +// Span, MultiSpan, Spanned // /// Spans represent a region of code, used for error reporting. Positions in spans @@ -129,6 +132,15 @@ pub struct Span { pub expn_id: ExpnId } +/// Spans are converted to MultiSpans just before error reporting, either automatically, +/// generated by line grouping, or manually constructed. +/// In the latter case care should be taken to ensure that spans are ordered, disjoint, +/// and point into the same FileMap. +#[derive(Clone)] +pub struct MultiSpan { + pub spans: Vec +} + pub const DUMMY_SP: Span = Span { lo: BytePos(0), hi: BytePos(0), expn_id: NO_EXPANSION }; // Generic span to be used for code originating from the command line @@ -145,6 +157,33 @@ impl Span { pub fn contains(self, other: Span) -> bool { self.lo <= other.lo && other.hi <= self.hi } + + /// Returns `Some(span)`, a union of `self` and `other`, on overlap. + pub fn merge(self, other: Span) -> Option { + if self.expn_id != other.expn_id { + return None; + } + + if (self.lo <= other.lo && self.hi > other.lo) || + (self.lo >= other.lo && self.lo < other.hi) { + Some(Span { + lo: cmp::min(self.lo, other.lo), + hi: cmp::max(self.hi, other.hi), + expn_id: self.expn_id, + }) + } else { + None + } + } + + /// Returns `Some(span)`, where the start is trimmed by the end of `other` + pub fn trim_start(self, other: Span) -> Option { + if self.hi > other.hi { + Some(Span { lo: cmp::max(self.lo, other.hi), .. self }) + } else { + None + } + } } #[derive(Clone, PartialEq, Eq, RustcEncodable, RustcDecodable, Hash, Debug, Copy)] @@ -236,6 +275,102 @@ pub fn original_sp(cm: &CodeMap, sp: Span, enclosing_sp: Span) -> Span { } } +impl MultiSpan { + pub fn new() -> MultiSpan { + MultiSpan { spans: Vec::new() } + } + + pub fn to_span_bounds(&self) -> Span { + assert!(!self.spans.is_empty()); + let Span { lo, expn_id, .. } = *self.spans.first().unwrap(); + let Span { hi, .. } = *self.spans.last().unwrap(); + Span { lo: lo, hi: hi, expn_id: expn_id } + } + + /// Merges or inserts the given span into itself. + pub fn push_merge(&mut self, mut sp: Span) { + let mut idx_merged = None; + + for idx in 0.. { + let cur = match self.spans.get(idx) { + Some(s) => *s, + None => break, + }; + // Try to merge with a contained Span + if let Some(union) = cur.merge(sp) { + self.spans[idx] = union; + sp = union; + idx_merged = Some(idx); + break; + } + // Or insert into the first sorted position + if sp.hi <= cur.lo { + self.spans.insert(idx, sp); + idx_merged = Some(idx); + break; + } + } + if let Some(idx) = idx_merged { + // Merge with spans trailing the insertion/merging position + while (idx + 1) < self.spans.len() { + if let Some(union) = self.spans[idx + 1].merge(sp) { + self.spans[idx] = union; + self.spans.remove(idx + 1); + } else { + break; + } + } + } else { + self.spans.push(sp); + } + } + + /// Inserts the given span into itself, for use with `end_highlight_lines`. + pub fn push_trim(&mut self, mut sp: Span) { + let mut prev = mk_sp(BytePos(0), BytePos(0)); + + if let Some(first) = self.spans.get_mut(0) { + if first.lo > sp.lo { + // Prevent us here from spanning fewer lines + // because of trimming the start of the span + // (this should not be visible, because this method ought + // to not be used in conjunction with `highlight_lines`) + first.lo = sp.lo; + } + } + + for idx in 0.. { + if let Some(sp_trim) = sp.trim_start(prev) { + // Implies `sp.hi > prev.hi` + let cur = match self.spans.as_slice().get(idx) { + Some(s) => *s, + None => { + sp = sp_trim; + break; + } + }; + // `cur` may overlap with `sp_trim` + if let Some(cur_trim) = cur.trim_start(sp_trim) { + // Implies `sp.hi < cur.hi` + self.spans.insert(idx, sp_trim); + self.spans[idx + 1] = cur_trim; + return; + } else if sp.hi == cur.hi { + return; + } + prev = cur; + } + } + self.spans.push(sp); + } +} + +impl From for MultiSpan { + fn from(span: Span) -> MultiSpan { + MultiSpan { spans: vec![span] } + } +} + // _____________________________________________________________________________ // Loc, LocWithOpt, FileMapAndLine, FileMapAndBytePos // @@ -1020,6 +1155,59 @@ impl CodeMap { } } + /// Groups and sorts spans by lines into `MultiSpan`s, where `push` adds them to their group, + /// specifying the unification behaviour for overlapping spans. + /// Spans overflowing a line are put into their own one-element-group. + pub fn custom_group_spans(&self, mut spans: Vec, push: F) -> Vec + where F: Fn(&mut MultiSpan, Span) + { + spans.sort_by(|a, b| a.lo.cmp(&b.lo)); + let mut groups = Vec::::new(); + let mut overflowing = vec![]; + let mut prev_expn = ExpnId(!2u32); + let mut prev_file = !0usize; + let mut prev_line = !0usize; + let mut err_size = 0; + + for sp in spans { + let line = self.lookup_char_pos(sp.lo).line; + let line_hi = self.lookup_char_pos(sp.hi).line; + if line != line_hi { + overflowing.push(sp.into()); + continue + } + let file = self.lookup_filemap_idx(sp.lo); + + if err_size < MAX_HIGHLIGHT_LINES && sp.expn_id == prev_expn && file == prev_file { + // `push` takes care of sorting, trimming, and merging + push(&mut groups.last_mut().unwrap(), sp); + if line != prev_line { + err_size += 1; + } + } else { + groups.push(sp.into()); + err_size = 1; + } + prev_expn = sp.expn_id; + prev_file = file; + prev_line = line; + } + groups.extend(overflowing); + groups + } + + /// Groups and sorts spans by lines into `MultiSpan`s, merging overlapping spans. + /// Spans overflowing a line are put into their own one-element-group. + pub fn group_spans(&self, spans: Vec) -> Vec { + self.custom_group_spans(spans, |msp, sp| msp.push_merge(sp)) + } + + /// Like `group_spans`, but trims overlapping spans instead of + /// merging them (for use with `end_highlight_lines`) + pub fn end_group_spans(&self, spans: Vec) -> Vec { + self.custom_group_spans(spans, |msp, sp| msp.push_trim(sp)) + } + pub fn get_filemap(&self, filename: &str) -> Rc { for fm in self.files.borrow().iter() { if filename == fm.name { @@ -1351,7 +1539,7 @@ mod tests { 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('~').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 } } @@ -1511,4 +1699,73 @@ r"blork2.rs:2:1: 2:12 "; 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]>); + } } diff --git a/src/libsyntax/errors/emitter.rs b/src/libsyntax/errors/emitter.rs index 51013d68930ea..c1239bfd66db8 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, 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, + 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, + 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 { "".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> = - 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>>); + impl Write for Sink { + fn write(&mut self, data: &[u8]) -> io::Result { + 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>>); - impl Write for Sink { - fn write(&mut self, data: &[u8]) -> io::Result { - 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 = ["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 713190ef419d6..5bb5f4757e013 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, 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, /// "error: internal compiler error", "error", "warning", "note", "help". level: &'static str, - span: Option, + spans: Vec, /// Assocaited diagnostic messages. children: Vec>, } @@ -110,7 +110,7 @@ struct DiagnosticCode { } impl<'a> Diagnostic<'a> { - fn new(span: Option, + 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 { + 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 { + 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 a7a4ddc3b2a63..9e1cb60f54f67 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, } 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, - span: Option, + span: Option, children: Vec, } @@ -114,7 +191,7 @@ pub struct DiagnosticBuilder<'a> { struct SubDiagnostic { level: Level, message: String, - span: Option, + span: Option, render_span: Option, } @@ -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>(&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>(&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>(&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>(&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>(&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>(&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>(&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>(&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>(&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>, 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: Option, render_span: Option) { let sub = SubDiagnostic { level: level, @@ -290,7 +370,7 @@ pub struct Handler { emit: RefCell>, pub can_emit_warnings: bool, treat_err_as_bug: bool, - delayed_span_bug: RefCell>, + delayed_span_bug: RefCell>, } 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>(&'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>(&'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>(&'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>(&'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>(&'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>(&'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>(&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>(&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>(&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>(&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>(&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>(&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>(&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>(&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>(&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>(&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>(&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, + 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, + 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); } } diff --git a/src/test/run-make/json-errors/Makefile b/src/test/run-make/json-errors/Makefile index 2467e08300c18..e6701224ccb6b 100644 --- a/src/test/run-make/json-errors/Makefile +++ b/src/test/run-make/json-errors/Makefile @@ -4,5 +4,5 @@ all: cp foo.rs $(TMPDIR) cd $(TMPDIR) -$(RUSTC) -Z unstable-options --error-format=json foo.rs 2>foo.log - grep -q '{"message":"unresolved name `y`","code":{"code":"E0425","explanation":"\\nAn unresolved name was used. Example of erroneous codes.*"},"level":"error","span":{"file_name":"foo.rs","byte_start":496,"byte_end":497,"line_start":12,"line_end":12,"column_start":18,"column_end":19},"children":\[\]}' foo.log - grep -q '{"message":".*","code":{"code":"E0277","explanation":"\\nYou tried.*"},"level":"error","span":{.*},"children":\[{"message":"the .*","code":null,"level":"help","span":{"file_name":"foo.rs","byte_start":504,"byte_end":516,"line_start":14,"line_end":14,"column_start":0,"column_end":0},"children":\[\]},{"message":" ","code":null,"level":"help",' foo.log + grep -q '{"message":"unresolved name `y`","code":{"code":"E0425","explanation":"\\nAn unresolved name was used. Example of erroneous codes.*"},"level":"error","spans":\[{"file_name":"foo.rs","byte_start":496,"byte_end":497,"line_start":12,"line_end":12,"column_start":18,"column_end":19}\],"children":\[\]}' foo.log + grep -q '{"message":".*","code":{"code":"E0277","explanation":"\\nYou tried.*"},"level":"error","spans":\[{.*}\],"children":\[{"message":"the .*","code":null,"level":"help","spans":\[{"file_name":"foo.rs","byte_start":504,"byte_end":516,"line_start":14,"line_end":14,"column_start":0,"column_end":0}\],"children":\[\]},{"message":" ","code":null,"level":"help",' foo.log