revamp MultiSpan and introduce new snippet code
MultiSpan model is now: - set of primary spans - set of span+label pairs Primary spans render with `^^^`, secondary spans with `---`. Labels are placed next to the `^^^` or `---` marker as appropriate.
This commit is contained in:
parent
e1a575cb07
commit
a20ee76b56
3 changed files with 1433 additions and 140 deletions
|
@ -32,8 +32,6 @@ use serialize::{Encodable, Decodable, Encoder, Decoder};
|
|||
|
||||
use ast::Name;
|
||||
|
||||
use errors::emitter::MAX_HIGHLIGHT_LINES;
|
||||
|
||||
// _____________________________________________________________________________
|
||||
// Pos, BytePos, CharPos
|
||||
//
|
||||
|
@ -51,7 +49,7 @@ pub struct BytePos(pub u32);
|
|||
/// A character offset. Because of multibyte utf8 characters, a byte offset
|
||||
/// is not equivalent to a character offset. The CodeMap will convert BytePos
|
||||
/// values to CharPos values as necessary.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Debug)]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
|
||||
pub struct CharPos(pub usize);
|
||||
|
||||
// FIXME: Lots of boilerplate in these impls, but so far my attempts to fix
|
||||
|
@ -132,13 +130,29 @@ 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.
|
||||
/// A collection of spans. Spans have two orthogonal attributes:
|
||||
///
|
||||
/// - they can be *primary spans*. In this case they are the locus of
|
||||
/// the error, and would be rendered with `^^^`.
|
||||
/// - they can have a *label*. In this case, the label is written next
|
||||
/// to the mark in the snippet when we render.
|
||||
#[derive(Clone)]
|
||||
pub struct MultiSpan {
|
||||
pub spans: Vec<Span>
|
||||
primary_spans: Vec<Span>,
|
||||
span_labels: Vec<(Span, String)>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SpanLabel {
|
||||
/// the span we are going to include in the final snippet
|
||||
pub span: Span,
|
||||
|
||||
/// is this a primary span? This is the "locus" of the message,
|
||||
/// and is indicated with a `^^^^` underline, versus `----`
|
||||
pub is_primary: bool,
|
||||
|
||||
/// what label should we attach to this span (if any)?
|
||||
pub label: Option<String>,
|
||||
}
|
||||
|
||||
pub const DUMMY_SP: Span = Span { lo: BytePos(0), hi: BytePos(0), expn_id: NO_EXPANSION };
|
||||
|
@ -276,97 +290,76 @@ 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);
|
||||
MultiSpan {
|
||||
primary_spans: vec![],
|
||||
span_labels: vec![]
|
||||
}
|
||||
}
|
||||
|
||||
/// 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));
|
||||
pub fn from_span(primary_span: Span) -> MultiSpan {
|
||||
MultiSpan {
|
||||
primary_spans: vec![primary_span],
|
||||
span_labels: vec![]
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
pub fn from_spans(vec: Vec<Span>) -> MultiSpan {
|
||||
MultiSpan {
|
||||
primary_spans: vec,
|
||||
span_labels: vec![]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push_primary_span(&mut self, span: Span) {
|
||||
self.primary_spans.push(span);
|
||||
}
|
||||
|
||||
pub fn push_span_label(&mut self, span: Span, label: String) {
|
||||
self.span_labels.push((span, label));
|
||||
}
|
||||
|
||||
/// Selects the first primary span (if any)
|
||||
pub fn primary_span(&self) -> Option<Span> {
|
||||
self.primary_spans.first().cloned()
|
||||
}
|
||||
|
||||
/// Returns all primary spans.
|
||||
pub fn primary_spans(&self) -> &[Span] {
|
||||
&self.primary_spans
|
||||
}
|
||||
|
||||
/// Returns the strings to highlight. If we have an explicit set,
|
||||
/// return those, otherwise just give back an (unlabeled) version
|
||||
/// of the primary span.
|
||||
pub fn span_labels(&self) -> Vec<SpanLabel> {
|
||||
let is_primary = |span| self.primary_spans.contains(&span);
|
||||
let mut span_labels = vec![];
|
||||
|
||||
for &(span, ref label) in &self.span_labels {
|
||||
span_labels.push(SpanLabel {
|
||||
span: span,
|
||||
is_primary: is_primary(span),
|
||||
label: Some(label.clone())
|
||||
});
|
||||
}
|
||||
|
||||
for &span in &self.primary_spans {
|
||||
if !span_labels.iter().any(|sl| sl.span == span) {
|
||||
span_labels.push(SpanLabel {
|
||||
span: span,
|
||||
is_primary: true,
|
||||
label: None
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for idx in 0.. {
|
||||
if let Some(sp_trim) = sp.trim_start(prev) {
|
||||
// Implies `sp.hi > prev.hi`
|
||||
let cur = match self.spans.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);
|
||||
span_labels
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Span> for MultiSpan {
|
||||
fn from(span: Span) -> MultiSpan {
|
||||
MultiSpan { spans: vec![span] }
|
||||
MultiSpan::from_span(span)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -929,6 +922,10 @@ impl CodeMap {
|
|||
}
|
||||
|
||||
pub fn span_to_string(&self, sp: Span) -> String {
|
||||
if sp == COMMAND_LINE_SP {
|
||||
return "<command line option>".to_string();
|
||||
}
|
||||
|
||||
if self.files.borrow().is_empty() && sp.source_equal(&DUMMY_SP) {
|
||||
return "no-location".to_string();
|
||||
}
|
||||
|
@ -1099,12 +1096,16 @@ impl CodeMap {
|
|||
}
|
||||
|
||||
pub fn span_to_lines(&self, sp: Span) -> FileLinesResult {
|
||||
debug!("span_to_lines(sp={:?})", sp);
|
||||
|
||||
if sp.lo > sp.hi {
|
||||
return Err(SpanLinesError::IllFormedSpan(sp));
|
||||
}
|
||||
|
||||
let lo = self.lookup_char_pos(sp.lo);
|
||||
debug!("span_to_lines: lo={:?}", lo);
|
||||
let hi = self.lookup_char_pos(sp.hi);
|
||||
debug!("span_to_lines: hi={:?}", hi);
|
||||
|
||||
if lo.file.start_pos != hi.file.start_pos {
|
||||
return Err(SpanLinesError::DistinctSources(DistinctSources {
|
||||
|
@ -1184,59 +1185,6 @@ 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<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 {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue