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};
use ast::Name;
+use errors::emitter::MAX_HIGHLIGHT_LINES;
+
// _____________________________________________________________________________
// Pos, BytePos, CharPos
//
/// 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
}
// _____________________________________________________________________________
-// Span, Spanned
+// Span, MultiSpan, Spanned
//
/// Spans represent a region of code, used for error reporting. Positions in spans
/// able to use many of the functions on spans in codemap and you cannot assume
/// that the length of the span = hi - lo; there may be space in the BytePos
/// range between files.
-#[derive(Clone, Copy, Hash)]
+#[derive(Clone, Copy, Hash, PartialEq, Eq)]
pub struct Span {
pub lo: BytePos,
pub hi: BytePos,
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<Span>
+}
+
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
impl Span {
/// Returns `self` if `self` is not the dummy span, and `other` otherwise.
pub fn substitute_dummy(self, other: Span) -> Span {
- if self == DUMMY_SP { other } else { self }
+ if self.source_equal(&DUMMY_SP) { other } else { self }
}
pub fn contains(self, other: Span) -> bool {
self.lo <= other.lo && other.hi <= self.hi
}
+
+ /// Return true if the spans are equal with regards to the source text.
+ ///
+ /// Use this instead of `==` when either span could be generated code,
+ /// and you only care that they point to the same bytes of source text.
+ pub fn source_equal(&self, other: &Span) -> bool {
+ self.lo == other.lo && self.hi == other.hi
+ }
+
+ /// Returns `Some(span)`, a union of `self` and `other`, on overlap.
+ pub fn merge(self, other: Span) -> Option<Span> {
+ 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<Span> {
+ 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)]
pub span: Span,
}
-impl PartialEq for Span {
- fn eq(&self, other: &Span) -> bool {
- return (*self).lo == (*other).lo && (*self).hi == (*other).hi;
- }
- fn ne(&self, other: &Span) -> bool { !(*self).eq(other) }
-}
-
-impl Eq for Span {}
-
impl Encodable for Span {
fn encode<S: Encoder>(&self, s: &mut S) -> Result<(), S::Error> {
s.emit_struct("Span", 2, |s| {
}
}
+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<Span> for MultiSpan {
+ fn from(span: Span) -> MultiSpan {
+ MultiSpan { spans: vec![span] }
+ }
+}
+
// _____________________________________________________________________________
// Loc, LocWithOpt, FileMapAndLine, FileMapAndBytePos
//
}
pub fn span_to_string(&self, sp: Span) -> String {
- if self.files.borrow().is_empty() && sp == DUMMY_SP {
+ if self.files.borrow().is_empty() && sp.source_equal(&DUMMY_SP) {
return "no-location".to_string();
}
/// the macro callsite that expanded to it.
pub fn source_callsite(&self, sp: Span) -> Span {
let mut span = sp;
+ // Special case - if a macro is parsed as an argument to another macro, the source
+ // callsite is the first callsite, which is also source-equivalent to the span.
+ let mut first = true;
while span.expn_id != NO_EXPANSION && span.expn_id != COMMAND_LINE_EXPN {
if let Some(callsite) = self.with_expn_info(span.expn_id,
|ei| ei.map(|ei| ei.call_site.clone())) {
+ if first && span.source_equal(&callsite) {
+ if self.lookup_char_pos(span.lo).file.is_real_file() {
+ return Span { expn_id: NO_EXPANSION, .. span };
+ }
+ }
+ first = false;
span = callsite;
}
else {
span
}
+ /// Return the source callee.
+ ///
+ /// Returns None if the supplied span has no expansion trace,
+ /// else returns the NameAndSpan for the macro definition
+ /// corresponding to the source callsite.
+ pub fn source_callee(&self, sp: Span) -> Option<NameAndSpan> {
+ let mut span = sp;
+ // Special case - if a macro is parsed as an argument to another macro, the source
+ // callsite is source-equivalent to the span, and the source callee is the first callee.
+ let mut first = true;
+ while let Some(callsite) = self.with_expn_info(span.expn_id,
+ |ei| ei.map(|ei| ei.call_site.clone())) {
+ if first && span.source_equal(&callsite) {
+ if self.lookup_char_pos(span.lo).file.is_real_file() {
+ return self.with_expn_info(span.expn_id,
+ |ei| ei.map(|ei| ei.callee.clone()));
+ }
+ }
+ first = false;
+ if let Some(_) = self.with_expn_info(callsite.expn_id,
+ |ei| ei.map(|ei| ei.call_site.clone())) {
+ span = callsite;
+ }
+ else {
+ return self.with_expn_info(span.expn_id,
+ |ei| ei.map(|ei| ei.callee.clone()));
+ }
+ }
+ None
+ }
+
pub fn span_to_filename(&self, sp: Span) -> FileName {
self.lookup_char_pos(sp.lo).file.name.to_string()
}
}
}
+ /// 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<F>(&self, mut spans: Vec<Span>, push: F) -> Vec<MultiSpan>
+ where F: Fn(&mut MultiSpan, Span)
+ {
+ spans.sort_by(|a, b| a.lo.cmp(&b.lo));
+ let mut groups = Vec::<MultiSpan>::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<Span>) -> Vec<MultiSpan> {
+ 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<Span>) -> Vec<MultiSpan> {
+ self.custom_group_spans(spans, |msp, sp| msp.push_trim(sp))
+ }
+
pub fn get_filemap(&self, filename: &str) -> Rc<FileMap> {
for fm in self.files.borrow().iter() {
if filename == fm.name {
expninfo.map_or(/* hit the top level */ true, |info| {
let span_comes_from_this_expansion =
- info.callee.span.map_or(span == info.call_site, |mac_span| {
+ info.callee.span.map_or(span.source_equal(&info.call_site), |mac_span| {
mac_span.contains(span)
});
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 }
}
";
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]>);
+ }
}