diff options
Diffstat (limited to 'src/libsyntax/errors')
| -rw-r--r-- | src/libsyntax/errors/emitter.rs | 929 | ||||
| -rw-r--r-- | src/libsyntax/errors/json.rs | 204 | ||||
| -rw-r--r-- | src/libsyntax/errors/mod.rs | 204 | ||||
| -rw-r--r-- | src/libsyntax/errors/snippet/mod.rs | 821 | ||||
| -rw-r--r-- | src/libsyntax/errors/snippet/test.rs | 521 |
5 files changed, 1892 insertions, 787 deletions
diff --git a/src/libsyntax/errors/emitter.rs b/src/libsyntax/errors/emitter.rs index 0b5234769b2..486e2ace087 100644 --- a/src/libsyntax/errors/emitter.rs +++ b/src/libsyntax/errors/emitter.rs @@ -13,9 +13,11 @@ use self::Destination::*; use codemap::{self, COMMAND_LINE_SP, DUMMY_SP, Pos, Span, MultiSpan}; use diagnostics; +use errors::check_old_skool; use errors::{Level, RenderSpan, CodeSuggestion, DiagnosticBuilder}; use errors::RenderSpan::*; use errors::Level::*; +use errors::snippet::{RenderedLineKind, SnippetData, Style}; use std::{cmp, fmt}; use std::io::prelude::*; @@ -23,17 +25,73 @@ use std::io; use std::rc::Rc; use term; +/// Emitter trait for emitting errors. Do not implement this directly: +/// implement `CoreEmitter` instead. pub trait Emitter { - 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 standalone diagnostic message. + fn emit(&mut self, span: &MultiSpan, msg: &str, code: Option<&str>, lvl: Level); /// Emit a structured diagnostic. + fn emit_struct(&mut self, db: &DiagnosticBuilder); +} + +pub trait CoreEmitter { + fn emit_message(&mut self, + rsp: &RenderSpan, + msg: &str, + code: Option<&str>, + lvl: Level, + is_header: bool, + show_snippet: bool); +} + +impl<T: CoreEmitter> Emitter for T { + fn emit(&mut self, + msp: &MultiSpan, + msg: &str, + code: Option<&str>, + lvl: Level) { + self.emit_message(&FullSpan(msp.clone()), + msg, + code, + lvl, + true, + true); + } + fn emit_struct(&mut self, db: &DiagnosticBuilder) { - self.emit(db.span.as_ref(), &db.message, db.code.as_ref().map(|s| &**s), db.level); + let old_school = check_old_skool(); + let db_span = FullSpan(db.span.clone()); + self.emit_message(&FullSpan(db.span.clone()), + &db.message, + db.code.as_ref().map(|s| &**s), + db.level, + true, + true); for child in &db.children { - match child.render_span { - Some(ref sp) => self.custom_emit(sp, &child.message, child.level), - None => self.emit(child.span.as_ref(), &child.message, None, child.level), + let render_span = child.render_span + .clone() + .unwrap_or_else( + || FullSpan(child.span.clone())); + + if !old_school { + self.emit_message(&render_span, + &child.message, + None, + child.level, + false, + true); + } else { + let (render_span, show_snippet) = match render_span.span().primary_span() { + None => (db_span.clone(), false), + _ => (render_span, true) + }; + self.emit_message(&render_span, + &child.message, + None, + child.level, + false, + show_snippet); } } } @@ -42,9 +100,6 @@ pub trait Emitter { /// maximum number of lines we will print for each error; arbitrary. 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 { Auto, @@ -68,21 +123,18 @@ pub struct BasicEmitter { dst: Destination, } -impl Emitter for BasicEmitter { - fn emit(&mut self, - msp: Option<&MultiSpan>, - msg: &str, - code: Option<&str>, - lvl: Level) { - assert!(msp.is_none(), "BasicEmitter can't handle spans"); +impl CoreEmitter for BasicEmitter { + fn emit_message(&mut self, + _rsp: &RenderSpan, + msg: &str, + code: Option<&str>, + lvl: Level, + _is_header: bool, + _show_snippet: bool) { + // we ignore the span as we have no access to a codemap at this point 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) { - panic!("BasicEmitter can't handle custom_emit"); } } @@ -101,33 +153,26 @@ pub struct EmitterWriter { dst: Destination, registry: Option<diagnostics::registry::Registry>, cm: Rc<codemap::CodeMap>, -} -impl Emitter for EmitterWriter { - fn emit(&mut self, - msp: Option<&MultiSpan>, - msg: &str, - code: Option<&str>, - lvl: Level) { - 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), - }; + /// Is this the first error emitted thus far? If not, we emit a + /// `\n` before the top-level errors. + first: bool, - if let Err(e) = error { - panic!("failed to print diagnostics: {:?}", e); - } - } + // For now, allow an old-school mode while we transition + old_school: bool, +} - fn custom_emit(&mut self, - rsp: &RenderSpan, - msg: &str, - lvl: Level) { - if let Err(e) = self.emit_(rsp, msg, None, lvl) { - panic!("failed to print diagnostics: {:?}", e); +impl CoreEmitter for EmitterWriter { + fn emit_message(&mut self, + rsp: &RenderSpan, + msg: &str, + code: Option<&str>, + lvl: Level, + is_header: bool, + show_snippet: bool) { + match self.emit_message_(rsp, msg, code, lvl, is_header, show_snippet) { + Ok(()) => { } + Err(e) => panic!("failed to emit error: {}", e) } } } @@ -151,11 +196,20 @@ impl EmitterWriter { registry: Option<diagnostics::registry::Registry>, code_map: Rc<codemap::CodeMap>) -> EmitterWriter { + let old_school = check_old_skool(); if color_config.use_color() { let dst = Destination::from_stderr(); - EmitterWriter { dst: dst, registry: registry, cm: code_map } + EmitterWriter { dst: dst, + registry: registry, + cm: code_map, + first: true, + old_school: old_school } } else { - EmitterWriter { dst: Raw(Box::new(io::stderr())), registry: registry, cm: code_map } + EmitterWriter { dst: Raw(Box::new(io::stderr())), + registry: registry, + cm: code_map, + first: true, + old_school: old_school } } } @@ -163,53 +217,108 @@ impl EmitterWriter { registry: Option<diagnostics::registry::Registry>, code_map: Rc<codemap::CodeMap>) -> EmitterWriter { - EmitterWriter { dst: Raw(dst), registry: registry, cm: code_map } - } + let old_school = check_old_skool(); + EmitterWriter { dst: Raw(dst), + registry: registry, + cm: code_map, + first: true, + old_school: old_school } + } + + fn emit_message_(&mut self, + rsp: &RenderSpan, + msg: &str, + code: Option<&str>, + lvl: Level, + is_header: bool, + show_snippet: bool) + -> io::Result<()> { + if is_header { + if self.first { + self.first = false; + } else { + if !self.old_school { + write!(self.dst, "\n")?; + } + } + } - fn emit_(&mut self, - rsp: &RenderSpan, - msg: &str, - code: Option<&str>, - lvl: Level) - -> io::Result<()> { - let msp = rsp.span(); - let bounds = msp.to_span_bounds(); - - let ss = if bounds == COMMAND_LINE_SP { - "<command line option>".to_string() - } 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(bounds) - }; + match code { + Some(code) if self.registry.as_ref() + .and_then(|registry| registry.find_description(code)) + .is_some() => { + let code_with_explain = String::from("--explain ") + code; + if self.old_school { + let loc = match rsp.span().primary_span() { + Some(COMMAND_LINE_SP) | Some(DUMMY_SP) => "".to_string(), + Some(ps) => self.cm.span_to_string(ps), + None => "".to_string() + }; + print_diagnostic(&mut self.dst, &loc, lvl, msg, Some(code))? + } + else { + print_diagnostic(&mut self.dst, "", lvl, msg, Some(&code_with_explain))? + } + } + _ => { + if self.old_school { + let loc = match rsp.span().primary_span() { + Some(COMMAND_LINE_SP) | Some(DUMMY_SP) => "".to_string(), + Some(ps) => self.cm.span_to_string(ps), + None => "".to_string() + }; + print_diagnostic(&mut self.dst, &loc, lvl, msg, code)? + } + else { + print_diagnostic(&mut self.dst, "", lvl, msg, code)? + } + } + } - print_diagnostic(&mut self.dst, &ss[..], lvl, msg, code)?; + if !show_snippet { + return Ok(()); + } + // Watch out for various nasty special spans; don't try to + // print any filename or anything for those. + match rsp.span().primary_span() { + Some(COMMAND_LINE_SP) | Some(DUMMY_SP) => { + return Ok(()); + } + _ => { } + } + + // Otherwise, print out the snippet etc as needed. match *rsp { - FullSpan(_) => { + FullSpan(ref msp) => { self.highlight_lines(msp, lvl)?; - self.print_macro_backtrace(bounds)?; - } - EndSpan(_) => { - self.end_highlight_lines(msp, lvl)?; - self.print_macro_backtrace(bounds)?; + if let Some(primary_span) = msp.primary_span() { + self.print_macro_backtrace(primary_span)?; + } } Suggestion(ref suggestion) => { self.highlight_suggestion(suggestion)?; - self.print_macro_backtrace(bounds)?; - } - FileLine(..) => { - // no source text in this case! + if let Some(primary_span) = rsp.span().primary_span() { + self.print_macro_backtrace(primary_span)?; + } } } - - if let Some(code) = code { - if let Some(_) = self.registry.as_ref() - .and_then(|registry| registry.find_description(code)) { - print_diagnostic(&mut self.dst, &ss[..], Help, - &format!("run `rustc --explain {}` to see a \ - detailed explanation", code), None)?; + if self.old_school { + match code { + Some(code) if self.registry.as_ref() + .and_then(|registry| registry.find_description(code)) + .is_some() => { + let loc = match rsp.span().primary_span() { + Some(COMMAND_LINE_SP) | Some(DUMMY_SP) => "".to_string(), + Some(ps) => self.cm.span_to_string(ps), + None => "".to_string() + }; + let msg = "run `rustc --explain ".to_string() + &code.to_string() + + "` to see a detailed explanation"; + print_diagnostic(&mut self.dst, &loc, Level::Help, &msg, + None)? + } + _ => () } } Ok(()) @@ -217,7 +326,8 @@ impl EmitterWriter { fn highlight_suggestion(&mut self, suggestion: &CodeSuggestion) -> io::Result<()> { - let lines = self.cm.span_to_lines(suggestion.msp.to_span_bounds()).unwrap(); + let primary_span = suggestion.msp.primary_span().unwrap(); + let lines = self.cm.span_to_lines(primary_span).unwrap(); assert!(!lines.lines.is_empty()); let complete = suggestion.splice_lines(&self.cm); @@ -251,325 +361,52 @@ impl EmitterWriter { lvl: Level) -> io::Result<()> { - let lines = match self.cm.span_to_lines(msp.to_span_bounds()) { - Ok(lines) => lines, - Err(_) => { - write!(&mut self.dst, "(internal compiler error: unprintable span)\n")?; - return Ok(()); - } - }; - - let fm = &*lines.file; - if let None = fm.src { - return Ok(()); - } - - let display_line_infos = &lines.lines[..]; - assert!(display_line_infos.len() > 0); - - // Calculate the widest number to format evenly and fix #11715 - let digits = line_num_max_digits(display_line_infos.last().unwrap()); - let first_line_index = display_line_infos.first().unwrap().line_index; - - let skip = fm.name.chars().count() + digits + 2; - - let mut spans = msp.spans.iter().peekable(); - let mut lines = display_line_infos.iter(); - let mut prev_line_index = first_line_index.wrapping_sub(1); - - // Display at most MAX_HIGHLIGHT_LINES lines. - let mut remaining_err_lines = MAX_HIGHLIGHT_LINES; - - // To emit a overflowed spans code-lines *AFTER* the rendered spans - let mut overflowed_buf = String::new(); - let mut overflowed = false; - - // FIXME (#8706) - 'l: loop { - if remaining_err_lines <= 0 { - break; - } - let line = match lines.next() { - Some(l) => l, - None => break, - }; - - // Skip is the number of characters we need to skip because they are - // part of the 'filename:line ' part of the code line. - let mut s: String = ::std::iter::repeat(' ').take(skip).collect(); - let mut col = skip; - let mut lastc = ' '; - - let cur_line_str = fm.get_line(line.line_index).unwrap(); - let mut line_chars = cur_line_str.chars().enumerate().peekable(); - let mut line_spans = 0; - - // Assemble spans for this line - loop { - // Peek here to preserve the span if it doesn't belong to this line - let sp = match spans.peek() { - Some(sp) => **sp, - None => break, - }; - let lo = self.cm.lookup_char_pos(sp.lo); - let hi = self.cm.lookup_char_pos(sp.hi); - let line_num = line.line_index + 1; - - if !(lo.line <= line_num && hi.line >= line_num) { - // This line is not contained in the span - if overflowed { - // Never elide the final line of an overflowed span - prev_line_index = line.line_index - 1; - overflowed = false; - break; - } - - if line_spans == 0 { - continue 'l; - } else { - // This line is finished, now render the spans we've assembled - break; - } + let mut snippet_data = SnippetData::new(self.cm.clone(), + msp.primary_span()); + if self.old_school { + let mut output_vec = vec![]; + + for span_label in msp.span_labels() { + let mut snippet_data = snippet_data.clone(); + + snippet_data.push(span_label.span, + span_label.is_primary, + span_label.label); + if span_label.is_primary { + output_vec.insert(0, snippet_data); } - spans.next(); - line_spans += 1; - - if lo.line != hi.line { - // Assemble extra code lines to be emitted after this lines spans - // (substract `2` because the first and last line are rendered normally) - let max_lines = cmp::min(remaining_err_lines, MAX_SP_LINES) - 2; - prev_line_index = line.line_index; - let count = cmp::min((hi.line - lo.line - 1), max_lines); - for _ in 0..count { - let line = match lines.next() { - Some(l) => l, - None => break, - }; - let line_str = fm.get_line(line.line_index).unwrap(); - overflowed_buf.push_str(&format!("{}:{:>width$} {}\n", - fm.name, - line.line_index + 1, - line_str, - width=digits)); - remaining_err_lines -= 1; - prev_line_index += 1 - } - // Remember that the span overflowed to ensure - // that we emit its last line exactly once - // (other spans may, or may not, start on it) - overflowed = true; - break; - } - - for (pos, ch) in line_chars.by_ref() { - lastc = ch; - if pos >= lo.col.to_usize() { break; } - // Whenever a tab occurs on the code line, we insert one on - // the error-point-squiggly-line as well (instead of a space). - // That way the squiggly line will usually appear in the correct - // position. - match ch { - '\t' => { - col += 8 - col%8; - s.push('\t'); - }, - _ => { - col += 1; - s.push(' '); - }, - } + else { + output_vec.push(snippet_data); } + } - s.push('^'); - let col_ptr = col; - let count = match lastc { - // Most terminals have a tab stop every eight columns by default - '\t' => 8 - col%8, - _ => 1, - }; - col += count; - s.extend(::std::iter::repeat('~').take(count)); - - let hi = self.cm.lookup_char_pos(sp.hi); - if hi.col != lo.col { - let mut chars = line_chars.by_ref(); - loop { - // We peek here to preserve the value for the next span - let (pos, ch) = match chars.peek() { - Some(elem) => *elem, - None => break, - }; - if pos >= hi.col.to_usize() { break; } - let count = match ch { - '\t' => 8 - col%8, - _ => 1, - }; - col += count; - s.extend(::std::iter::repeat('~').take(count)); - - chars.next(); + for snippet_data in output_vec.iter() { + let rendered_lines = snippet_data.render_lines(); + for rendered_line in &rendered_lines { + for styled_string in &rendered_line.text { + self.dst.apply_style(lvl, &rendered_line.kind, styled_string.style)?; + write!(&mut self.dst, "{}", styled_string.text)?; + self.dst.reset_attrs()?; } + write!(&mut self.dst, "\n")?; } - if (col - col_ptr) > 0 { - // One extra squiggly is replaced by a "^" - s.pop(); - } - } - - // If we elided something put an ellipsis. - if prev_line_index != line.line_index.wrapping_sub(1) && !overflowed { - write!(&mut self.dst, "{0:1$}...\n", "", skip)?; - } - - // Print offending code-line - remaining_err_lines -= 1; - write!(&mut self.dst, "{}:{:>width$} {}\n", - fm.name, - line.line_index + 1, - cur_line_str, - width=digits)?; - - if s.len() > skip { - // Render the spans we assembled previously (if any). - println_maybe_styled!(&mut self.dst, term::Attr::ForegroundColor(lvl.color()), - "{}", s)?; - } - - if !overflowed_buf.is_empty() { - // Print code-lines trailing the rendered spans (when a span overflows) - write!(&mut self.dst, "{}", &overflowed_buf)?; - overflowed_buf.clear(); - } else { - prev_line_index = line.line_index; } } - - // If we elided something, put an ellipsis. - if lines.next().is_some() { - write!(&mut self.dst, "{0:1$}...\n", "", skip)?; - } - Ok(()) - } - - /// Here are the differences between this and the normal `highlight_lines`: - /// `end_highlight_lines` will always put arrow on the last byte of each - /// span (instead of the first byte). Also, when a span is too long (more - /// than 6 lines), `end_highlight_lines` will print the first line, then - /// dot dot dot, then last line, whereas `highlight_lines` prints the first - /// six lines. - #[allow(deprecated)] - fn end_highlight_lines(&mut self, - msp: &MultiSpan, - lvl: Level) - -> io::Result<()> { - let lines = match self.cm.span_to_lines(msp.to_span_bounds()) { - Ok(lines) => lines, - Err(_) => { - write!(&mut self.dst, "(internal compiler error: unprintable span)\n")?; - return Ok(()); + else { + for span_label in msp.span_labels() { + snippet_data.push(span_label.span, + span_label.is_primary, + span_label.label); } - }; - - let fm = &*lines.file; - if let None = fm.src { - return Ok(()); - } - - let lines = &lines.lines[..]; - - // Calculate the widest number to format evenly - let first_line = lines.first().unwrap(); - let last_line = lines.last().unwrap(); - let digits = line_num_max_digits(last_line); - - let skip = fm.name.chars().count() + digits + 2; - - let mut spans = msp.spans.iter().peekable(); - let mut lines = lines.iter(); - let mut prev_line_index = first_line.line_index.wrapping_sub(1); - - // Display at most MAX_HIGHLIGHT_LINES lines. - let mut remaining_err_lines = MAX_HIGHLIGHT_LINES; - - 'l: loop { - if remaining_err_lines <= 0 { - break; - } - let line = match lines.next() { - Some(line) => line, - None => break, - }; - - // Skip is the number of characters we need to skip because they are - // part of the 'filename:line ' part of the previous line. - let mut s: String = ::std::iter::repeat(' ').take(skip).collect(); - - let line_str = fm.get_line(line.line_index).unwrap(); - let mut line_chars = line_str.chars().enumerate(); - let mut line_spans = 0; - - loop { - // Peek here to preserve the span if it doesn't belong to this line - let sp = match spans.peek() { - Some(sp) => **sp, - None => break, - }; - let lo = self.cm.lookup_char_pos(sp.lo); - let hi = self.cm.lookup_char_pos(sp.hi); - let elide_sp = (hi.line - lo.line) >= MAX_SP_LINES; - - let line_num = line.line_index + 1; - if !(lo.line <= line_num && hi.line >= line_num) { - // This line is not contained in the span - if line_spans == 0 { - continue 'l; - } else { - // This line is finished, now render the spans we've assembled - break - } - } else if hi.line > line_num { - if elide_sp && lo.line < line_num { - // This line is inbetween the first and last line of the span, - // so we may want to elide it. - continue 'l; - } else { - break - } - } - line_spans += 1; - spans.next(); - - for (pos, ch) in line_chars.by_ref() { - // Span seems to use half-opened interval, so subtract 1 - if pos >= hi.col.to_usize() - 1 { break; } - // Whenever a tab occurs on the previous line, we insert one on - // the error-point-squiggly-line as well (instead of a space). - // That way the squiggly line will usually appear in the correct - // position. - match ch { - '\t' => s.push('\t'), - _ => s.push(' '), - } + let rendered_lines = snippet_data.render_lines(); + for rendered_line in &rendered_lines { + for styled_string in &rendered_line.text { + self.dst.apply_style(lvl, &rendered_line.kind, styled_string.style)?; + write!(&mut self.dst, "{}", styled_string.text)?; + self.dst.reset_attrs()?; } - s.push('^'); - } - - if prev_line_index != line.line_index.wrapping_sub(1) { - // If we elided something, put an ellipsis. - write!(&mut self.dst, "{0:1$}...\n", "", skip)?; + write!(&mut self.dst, "\n")?; } - - // Print offending code-lines - write!(&mut self.dst, "{}:{:>width$} {}\n", fm.name, - line.line_index + 1, line_str, width=digits)?; - remaining_err_lines -= 1; - - if s.len() > skip { - // Render the spans we assembled previously (if any) - println_maybe_styled!(&mut self.dst, term::Attr::ForegroundColor(lvl.color()), - "{}", s)?; - } - prev_line_index = line.line_index; } Ok(()) } @@ -609,17 +446,29 @@ fn print_diagnostic(dst: &mut Destination, code: Option<&str>) -> io::Result<()> { if !topic.is_empty() { - write!(dst, "{} ", topic)?; + let old_school = check_old_skool(); + if !old_school { + write!(dst, "{}: ", topic)?; + } + else { + write!(dst, "{} ", topic)?; + } + dst.reset_attrs()?; } - - print_maybe_styled!(dst, term::Attr::ForegroundColor(lvl.color()), - "{}: ", lvl.to_string())?; - print_maybe_styled!(dst, term::Attr::Bold, "{}", msg)?; + dst.start_attr(term::Attr::Bold)?; + dst.start_attr(term::Attr::ForegroundColor(lvl.color()))?; + write!(dst, "{}", lvl.to_string())?; + dst.reset_attrs()?; + write!(dst, ": ")?; + dst.start_attr(term::Attr::Bold)?; + write!(dst, "{}", msg)?; if let Some(code) = code { let style = term::Attr::ForegroundColor(term::color::BRIGHT_MAGENTA); print_maybe_styled!(dst, style, " [{}]", code.clone())?; } + + dst.reset_attrs()?; write!(dst, "\n")?; Ok(()) } @@ -660,6 +509,51 @@ impl Destination { } } + fn apply_style(&mut self, + lvl: Level, + _kind: &RenderedLineKind, + style: Style) + -> io::Result<()> { + match style { + Style::FileNameStyle | + Style::LineAndColumn => { + } + Style::LineNumber => { + self.start_attr(term::Attr::Bold)?; + self.start_attr(term::Attr::ForegroundColor(term::color::BRIGHT_BLUE))?; + } + Style::Quotation => { + } + Style::UnderlinePrimary | Style::LabelPrimary => { + self.start_attr(term::Attr::Bold)?; + self.start_attr(term::Attr::ForegroundColor(lvl.color()))?; + } + Style::UnderlineSecondary | Style::LabelSecondary => { + self.start_attr(term::Attr::Bold)?; + self.start_attr(term::Attr::ForegroundColor(term::color::BRIGHT_BLUE))?; + } + Style::NoStyle => { + } + } + Ok(()) + } + + fn start_attr(&mut self, attr: term::Attr) -> io::Result<()> { + match *self { + Terminal(ref mut t) => { t.attr(attr)?; } + Raw(_) => { } + } + Ok(()) + } + + fn reset_attrs(&mut self) -> io::Result<()> { + match *self { + Terminal(ref mut t) => { t.reset()?; } + Raw(_) => { } + } + Ok(()) + } + fn print_maybe_styled(&mut self, args: fmt::Arguments, color: term::Attr, @@ -741,7 +635,7 @@ mod test { /// 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 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 } } @@ -767,7 +661,7 @@ mod test { dreizehn "; let file = cm.new_filemap_and_lines("dummy.txt", content); - let start = file.lines.borrow()[7]; + let start = file.lines.borrow()[10]; let end = file.lines.borrow()[11]; let sp = mk_sp(start, end); let lvl = Level::Error; @@ -777,12 +671,12 @@ mod test { let vec = data.lock().unwrap().clone(); let vec: &[u8] = &vec; let str = from_utf8(vec).unwrap(); - println!("{}", str); - assert_eq!(str, "dummy.txt: 8 line8\n\ - dummy.txt: 9 line9\n\ - dummy.txt:10 line10\n\ - dummy.txt:11 e-lä-vän\n\ - dummy.txt:12 tolv\n"); + println!("r#\"\n{}\"#", str); + assert_eq!(str, &r#" + --> dummy.txt:11:1 +11 |> e-lä-vän + |> ^ +"#[1..]); } #[test] @@ -790,7 +684,7 @@ mod test { // 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"; + 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(); @@ -808,51 +702,25 @@ mod test { } #[test] - fn test_multiple_span_splice() { - // Test that a `MultiSpan` containing multiple spans splices substitions on - // several lines correctly + fn test_multi_span_splice() { + // Test that a `MultiSpan` containing multiple spans splices a substition correctly let cm = CodeMap::new(); - let inp = "aaaaabbbbBB\nZZ\nZZ\nCCCDDDDDdddddeee"; - let sp1 = " ^~~~~~\n \n \n "; - let sp2 = " \n \n \n^~~~~~ "; - let sp3 = " \n \n \n ^~~ "; - let sp4 = " \n \n \n ^~~~ "; - - let span_eq = |sp, eq| assert_eq!(&cm.span_to_snippet(sp).unwrap(), eq); - - cm.new_filemap_and_lines("blork.rs", inp); - let sp1 = span_from_selection(inp, sp1); - let sp2 = span_from_selection(inp, sp2); - let sp3 = span_from_selection(inp, sp3); - let sp4 = span_from_selection(inp, sp4); - span_eq(sp1, "bbbbBB"); - span_eq(sp2, "CCCDDD"); - span_eq(sp3, "ddd"); - span_eq(sp4, "ddee"); - - let substitutes: Vec<String> = ["1", "2", "3", "4"].iter().map(|x|x.to_string()).collect(); - let expected = "aaaaa1\nZZ\nZZ\n2DD34e"; - - let test = |msp| { - let suggest = CodeSuggestion { - msp: msp, - substitutes: substitutes.clone(), - }; - let actual = suggest.splice_lines(&cm); - assert_eq!(actual, expected); + let inputtext = "aaaaa\nbbbbBB\nCCC\nDDDDDddddd\neee\n"; + let selection1 = " \n \n \n \n ~ \n"; // intentionally out of order + let selection2 = " \n ~~\n~~~\n~~~~~ \n \n"; + cm.new_filemap_and_lines("blork.rs", inputtext); + let sp1 = span_from_selection(inputtext, selection1); + let sp2 = span_from_selection(inputtext, selection2); + let msp: MultiSpan = MultiSpan::from_spans(vec![sp1, sp2]); + + let expected = "bbbbZZZZZZddddd\neXYZe"; + let suggest = CodeSuggestion { + msp: msp, + substitutes: vec!["ZZZZZZ".to_owned(), + "XYZ".to_owned()] }; - 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); + + assert_eq!(suggest.splice_lines(&cm), expected); } #[test] @@ -862,17 +730,17 @@ mod test { 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 sp1 = " ~~~~~~ "; + let sp2 = " ~~~~~~ "; + let sp3 = " ~~~~~ "; + let sp4 = " ~~~~ "; + let sp34 = " ~~~~~~~ "; + + let expect_start = &r#" + --> dummy.txt:1:6 +1 |> _____aaaaaa____bbbbbb__cccccdd_ + |> ^^^^^^ ^^^^^^ ^^^^^^^ +"#[1..]; let span = |sp, expected| { let sp = span_from_selection(inp, sp); @@ -885,7 +753,6 @@ mod test { 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]; @@ -894,26 +761,17 @@ mod test { highlight(); let vec = data.lock().unwrap().clone(); let actual = from_utf8(&vec[..]).unwrap(); + println!("actual=\n{}", actual); assert_eq!(actual, expected); }; - let msp = MultiSpan { spans: vec![sp1, sp2, sp34] }; - let msp_end = MultiSpan { spans: vec![sp1, sp2, sp3, sp4_end] }; + let msp = MultiSpan::from_spans(vec![sp1, sp2, sp34]); 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(); - } + let msp = MultiSpan::from_spans(spans.clone()); + diag.highlight_lines(&msp, Level::Error).unwrap(); }); } @@ -950,75 +808,29 @@ mod test { let sp4 = span(10, 10, (2, 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 __f_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 __f_gg\n\ - \x20 ^ ^~\n"; - - - let expect_end = "dummy.txt: 1 aaaaa\n\ - \x20 ...\n\ - dummy.txt: 7 yyyyy\n\ - \x20 ^\n\ - \x20 ...\n\ - dummy.txt: 9 ddd__eee_\n\ - \x20 ^ ^\n\ - \x20 ...\n\ - dummy.txt:11 __f_gg\n\ - \x20 ^ ^\n"; - - let expect0_end = "dummy.txt: 5 ccccc\n\ - dummy.txt: 6 xxxxx\n\ - dummy.txt: 7 yyyyy\n\ - \x20 ^\n\ - \x20 ...\n\ - dummy.txt: 9 ddd__eee_\n\ - \x20 ^ ^\n\ - \x20 ...\n\ - dummy.txt:11 __f_gg\n\ - \x20 ^ ^\n"; - - let expect_end_g1 = "dummy.txt:1 aaaaa\n\ - \x20 ...\n\ - dummy.txt:7 yyyyy\n\ - \x20 ^\n"; - - let expect2_end = "dummy.txt: 9 ddd__eee_\n\ - \x20 ^ ^\n\ - \x20 ...\n\ - dummy.txt:11 __f_gg\n\ - \x20 ^ ^\n"; - - let expect_groups = [expect2, expect_g1]; - let expect_end_groups = [expect2_end, expect_end_g1]; - let spans = vec![sp3, sp1, sp4, sp2, sp5]; + let expect0 = &r#" + --> dummy.txt:5:1 +5 |> ccccc + |> ^ +... +9 |> ddd__eee_ + |> ^^^ ^^^ +10 |> elided +11 |> __f_gg + |> ^ ^^ +"#[1..]; + + let expect = &r#" + --> dummy.txt:1:1 +1 |> aaaaa + |> ^ +... +9 |> ddd__eee_ + |> ^^^ ^^^ +10 |> elided +11 |> __f_gg + |> ^ ^^ +"#[1..]; macro_rules! test { ($expected: expr, $highlight: expr) => ({ @@ -1034,37 +846,14 @@ mod test { }); } - 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] }; + let msp0 = MultiSpan::from_spans(vec![sp0, sp2, sp3, sp4, sp5]); + let msp = MultiSpan::from_spans(vec![sp1, 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 821617bfe89..93c6268ccae 100644 --- a/src/libsyntax/errors/json.rs +++ b/src/libsyntax/errors/json.rs @@ -20,7 +20,7 @@ // FIXME spec the JSON output properly. -use codemap::{self, Span, MacroBacktrace, MultiSpan, CodeMap}; +use codemap::{self, MacroBacktrace, Span, SpanLabel, MultiSpan, CodeMap}; use diagnostics::registry::Registry; use errors::{Level, DiagnosticBuilder, SubDiagnostic, RenderSpan, CodeSuggestion}; use errors::emitter::Emitter; @@ -53,20 +53,13 @@ impl JsonEmitter { } impl Emitter for JsonEmitter { - fn emit(&mut self, span: Option<&MultiSpan>, msg: &str, code: Option<&str>, level: Level) { + fn emit(&mut self, span: &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); - if let Err(e) = writeln!(&mut self.dst, "{}", as_json(&data)) { - panic!("failed to print diagnostics: {:?}", e); - } - } - fn emit_struct(&mut self, db: &DiagnosticBuilder) { let data = Diagnostic::from_diagnostic_builder(db, self); if let Err(e) = writeln!(&mut self.dst, "{}", as_json(&data)) { @@ -104,8 +97,13 @@ struct DiagnosticSpan { /// 1-based, character offset. column_start: usize, column_end: usize, + /// Is this a "primary" span -- meaning the point, or one of the points, + /// where the error occurred? + is_primary: bool, /// Source text from the start of line_start to the end of line_end. text: Vec<DiagnosticSpanLine>, + /// Label that should be placed at this location (if any) + label: Option<String>, /// If we are suggesting a replacement, this will contain text /// that should be sliced in atop this span. You may prefer to /// load the fully rendered version from the parent `Diagnostic`, @@ -148,7 +146,7 @@ struct DiagnosticCode { } impl<'a> Diagnostic<'a> { - fn new(msp: Option<&MultiSpan>, + fn new(msp: &MultiSpan, msg: &'a str, code: Option<&str>, level: Level, @@ -158,27 +156,12 @@ impl<'a> Diagnostic<'a> { message: msg, code: DiagnosticCode::map_opt_string(code.map(|c| c.to_owned()), je), level: level.to_str(), - spans: msp.map_or(vec![], |msp| DiagnosticSpan::from_multispan(msp, je)), + spans: DiagnosticSpan::from_multispan(msp, je), children: vec![], rendered: None, } } - fn from_render_span(span: &RenderSpan, - msg: &'a str, - level: Level, - je: &JsonEmitter) - -> Diagnostic<'a> { - Diagnostic { - message: msg, - code: None, - level: level.to_str(), - spans: DiagnosticSpan::from_render_span(span, je), - children: vec![], - rendered: je.render(span), - } - } - fn from_diagnostic_builder<'c>(db: &'c DiagnosticBuilder, je: &JsonEmitter) -> Diagnostic<'c> { @@ -186,7 +169,7 @@ impl<'a> Diagnostic<'a> { message: &db.message, code: DiagnosticCode::map_opt_string(db.code.clone(), je), level: db.level.to_str(), - spans: db.span.as_ref().map_or(vec![], |sp| DiagnosticSpan::from_multispan(sp, je)), + spans: DiagnosticSpan::from_multispan(&db.span, je), children: db.children.iter().map(|c| { Diagnostic::from_sub_diagnostic(c, je) }).collect(), @@ -201,8 +184,7 @@ impl<'a> Diagnostic<'a> { level: db.level.to_str(), 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![]), + .unwrap_or_else(|| DiagnosticSpan::from_multispan(&db.span, je)), children: vec![], rendered: db.render_span.as_ref() .and_then(|rsp| je.render(rsp)), @@ -211,44 +193,68 @@ impl<'a> Diagnostic<'a> { } impl DiagnosticSpan { - fn from_span(span: Span, suggestion: Option<&String>, je: &JsonEmitter) - -> DiagnosticSpan { + fn from_span_label(span: SpanLabel, + suggestion: Option<&String>, + je: &JsonEmitter) + -> DiagnosticSpan { + Self::from_span_etc(span.span, + span.is_primary, + span.label, + suggestion, + je) + } + + fn from_span_etc(span: Span, + is_primary: bool, + label: Option<String>, + suggestion: Option<&String>, + je: &JsonEmitter) + -> DiagnosticSpan { // obtain the full backtrace from the `macro_backtrace` // helper; in some ways, it'd be better to expand the // backtrace ourselves, but the `macro_backtrace` helper makes // some decision, such as dropping some frames, and I don't // want to duplicate that logic here. let backtrace = je.cm.macro_backtrace(span).into_iter(); - DiagnosticSpan::from_span_and_backtrace(span, suggestion, backtrace, je) + DiagnosticSpan::from_span_full(span, + is_primary, + label, + suggestion, + backtrace, + je) } - fn from_span_and_backtrace(span: Span, - suggestion: Option<&String>, - mut backtrace: vec::IntoIter<MacroBacktrace>, - je: &JsonEmitter) - -> DiagnosticSpan { + fn from_span_full(span: Span, + is_primary: bool, + label: Option<String>, + suggestion: Option<&String>, + mut backtrace: vec::IntoIter<MacroBacktrace>, + je: &JsonEmitter) + -> DiagnosticSpan { let start = je.cm.lookup_char_pos(span.lo); let end = je.cm.lookup_char_pos(span.hi); - let backtrace_step = - backtrace.next() - .map(|bt| { - let call_site = - Self::from_span_and_backtrace(bt.call_site, - None, - backtrace, - je); - let def_site_span = bt.def_site_span.map(|sp| { - Self::from_span_and_backtrace(sp, - None, - vec![].into_iter(), - je) - }); - Box::new(DiagnosticSpanMacroExpansion { - span: call_site, - macro_decl_name: bt.macro_decl_name, - def_site_span: def_site_span, - }) - }); + let backtrace_step = backtrace.next().map(|bt| { + let call_site = + Self::from_span_full(bt.call_site, + false, + None, + None, + backtrace, + je); + let def_site_span = bt.def_site_span.map(|sp| { + Self::from_span_full(sp, + false, + None, + None, + vec![].into_iter(), + je) + }); + Box::new(DiagnosticSpanMacroExpansion { + span: call_site, + macro_decl_name: bt.macro_decl_name, + def_site_span: def_site_span, + }) + }); DiagnosticSpan { file_name: start.file.name.clone(), byte_start: span.lo.0, @@ -257,53 +263,41 @@ impl DiagnosticSpan { line_end: end.line, column_start: start.col.0 + 1, column_end: end.col.0 + 1, + is_primary: is_primary, text: DiagnosticSpanLine::from_span(span, je), suggested_replacement: suggestion.cloned(), expansion: backtrace_step, + label: label, } } fn from_multispan(msp: &MultiSpan, je: &JsonEmitter) -> Vec<DiagnosticSpan> { - msp.spans.iter().map(|&span| Self::from_span(span, None, je)).collect() + msp.span_labels() + .into_iter() + .map(|span_str| Self::from_span_label(span_str, None, je)) + .collect() } fn from_suggestion(suggestion: &CodeSuggestion, je: &JsonEmitter) -> Vec<DiagnosticSpan> { - assert_eq!(suggestion.msp.spans.len(), suggestion.substitutes.len()); - suggestion.msp.spans.iter() - .zip(&suggestion.substitutes) - .map(|(&span, suggestion)| { - DiagnosticSpan::from_span(span, Some(suggestion), je) - }) - .collect() + assert_eq!(suggestion.msp.span_labels().len(), suggestion.substitutes.len()); + suggestion.msp.span_labels() + .into_iter() + .zip(&suggestion.substitutes) + .map(|(span_label, suggestion)| { + DiagnosticSpan::from_span_label(span_label, + Some(suggestion), + je) + }) + .collect() } fn from_render_span(rsp: &RenderSpan, je: &JsonEmitter) -> Vec<DiagnosticSpan> { match *rsp { - RenderSpan::FileLine(ref msp) | - RenderSpan::FullSpan(ref msp) => { - DiagnosticSpan::from_multispan(msp, je) - } - RenderSpan::Suggestion(ref suggestion) => { - DiagnosticSpan::from_suggestion(suggestion, je) - } - 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.hi.0, - byte_end: span.hi.0, - line_start: end.line, - line_end: end.line, - column_start: end.col.0 + 1, - column_end: end.col.0 + 1, - text: DiagnosticSpanLine::from_span_end(span, je), - suggested_replacement: None, - expansion: None, - } - }).collect() - } + RenderSpan::FullSpan(ref msp) => + DiagnosticSpan::from_multispan(msp, je), + RenderSpan::Suggestion(ref suggestion) => + DiagnosticSpan::from_suggestion(suggestion, je), } } } @@ -340,34 +334,6 @@ impl DiagnosticSpanLine { }) .unwrap_or(vec![]) } - - /// Create a list of DiagnosticSpanLines from span - the result covers all - /// of `span`, but the highlight is zero-length and at the end of `span`. - fn from_span_end(span: Span, je: &JsonEmitter) -> Vec<DiagnosticSpanLine> { - je.cm.span_to_lines(span) - .map(|lines| { - let fm = &*lines.file; - lines.lines.iter() - .enumerate() - .map(|(i, line)| { - // Invariant - CodeMap::span_to_lines - // will not return extra context lines - // - the last line returned is the last - // line of `span`. - let highlight = if i == lines.lines.len() - 1 { - (line.end_col.0 + 1, line.end_col.0 + 1) - } else { - (0, 0) - }; - DiagnosticSpanLine::line_from_filemap(fm, - line.line_index, - highlight.0, - highlight.1) - }) - .collect() - }) - .unwrap_or(vec![]) - } } impl DiagnosticCode { @@ -389,16 +355,12 @@ impl DiagnosticCode { impl JsonEmitter { fn render(&self, render_span: &RenderSpan) -> Option<String> { match *render_span { - RenderSpan::FileLine(_) | RenderSpan::FullSpan(_) => { None } RenderSpan::Suggestion(ref suggestion) => { Some(suggestion.splice_lines(&self.cm)) } - RenderSpan::EndSpan(_) => { - None - } } } } diff --git a/src/libsyntax/errors/mod.rs b/src/libsyntax/errors/mod.rs index 792828b3054..f0c665bcb3c 100644 --- a/src/libsyntax/errors/mod.rs +++ b/src/libsyntax/errors/mod.rs @@ -13,17 +13,19 @@ pub use errors::emitter::ColorConfig; use self::Level::*; use self::RenderSpan::*; -use codemap::{self, CodeMap, MultiSpan}; +use codemap::{self, CodeMap, MultiSpan, NO_EXPANSION, Span}; use diagnostics; use errors::emitter::{Emitter, EmitterWriter}; use std::cell::{RefCell, Cell}; use std::{error, fmt}; use std::rc::Rc; +use std::thread::panicking; use term; pub mod emitter; pub mod json; +pub mod snippet; #[derive(Clone)] pub enum RenderSpan { @@ -32,22 +34,11 @@ pub enum RenderSpan { /// the source code covered by the 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(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 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(MultiSpan), } #[derive(Clone)] @@ -60,9 +51,7 @@ impl RenderSpan { fn span(&self) -> &MultiSpan { match *self { FullSpan(ref msp) | - Suggestion(CodeSuggestion { ref msp, .. }) | - EndSpan(ref msp) | - FileLine(ref msp) => + Suggestion(CodeSuggestion { ref msp, .. }) => msp } } @@ -88,12 +77,24 @@ impl CodeSuggestion { } } } - let bounds = self.msp.to_span_bounds(); - let lines = cm.span_to_lines(bounds).unwrap(); - assert!(!lines.lines.is_empty()); - // This isn't strictly necessary, but would in all likelyhood be an error - assert_eq!(self.msp.spans.len(), self.substitutes.len()); + let mut primary_spans = self.msp.primary_spans().to_owned(); + + assert_eq!(primary_spans.len(), self.substitutes.len()); + if primary_spans.is_empty() { + return format!(""); + } + + // Assumption: all spans are in the same file, and all spans + // are disjoint. Sort in ascending order. + primary_spans.sort_by_key(|sp| sp.lo); + + // Find the bounding span. + let lo = primary_spans.iter().map(|sp| sp.lo).min().unwrap(); + let hi = primary_spans.iter().map(|sp| sp.hi).min().unwrap(); + let bounding_span = Span { lo: lo, hi: hi, expn_id: NO_EXPANSION }; + let lines = cm.span_to_lines(bounding_span).unwrap(); + assert!(!lines.lines.is_empty()); // To build up the result, we do this for each span: // - push the line segment trailing the previous span @@ -105,13 +106,13 @@ impl CodeSuggestion { // // Finally push the trailing line segment of the last span let fm = &lines.file; - let mut prev_hi = cm.lookup_char_pos(bounds.lo); + let mut prev_hi = cm.lookup_char_pos(bounding_span.lo); prev_hi.col = CharPos::from_usize(0); 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()) { + for (sp, substitute) in primary_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)); @@ -183,7 +184,7 @@ pub struct DiagnosticBuilder<'a> { level: Level, message: String, code: Option<String>, - span: Option<MultiSpan>, + span: MultiSpan, children: Vec<SubDiagnostic>, } @@ -192,7 +193,7 @@ pub struct DiagnosticBuilder<'a> { struct SubDiagnostic { level: Level, message: String, - span: Option<MultiSpan>, + span: MultiSpan, render_span: Option<RenderSpan>, } @@ -228,37 +229,61 @@ impl<'a> DiagnosticBuilder<'a> { self.level == Level::Fatal } - pub fn note(&mut self , msg: &str) -> &mut DiagnosticBuilder<'a> { - self.sub(Level::Note, msg, None, None); + /// Add a span/label to be included in the resulting snippet. + /// This is pushed onto the `MultiSpan` that was created when the + /// diagnostic was first built. If you don't call this function at + /// all, and you just supplied a `Span` to create the diagnostic, + /// then the snippet will just include that `Span`, which is + /// called the primary span. + pub fn span_label(mut self, span: Span, label: &fmt::Display) + -> DiagnosticBuilder<'a> { + self.span.push_span_label(span, format!("{}", label)); + self + } + + pub fn note_expected_found(mut self, + label: &fmt::Display, + expected: &fmt::Display, + found: &fmt::Display) + -> DiagnosticBuilder<'a> + { + // For now, just attach these as notes + self.note(&format!("expected {} `{}`", label, expected)); + self.note(&format!(" found {} `{}`", label, found)); + self + } + + pub fn note(&mut self, msg: &str) -> &mut DiagnosticBuilder<'a> { + self.sub(Level::Note, msg, MultiSpan::new(), None); self } pub fn span_note<S: Into<MultiSpan>>(&mut self, sp: S, msg: &str) -> &mut DiagnosticBuilder<'a> { - self.sub(Level::Note, msg, Some(sp.into()), None); + self.sub(Level::Note, msg, sp.into(), None); self } pub fn warn(&mut self, msg: &str) -> &mut DiagnosticBuilder<'a> { - self.sub(Level::Warning, msg, None, None); + self.sub(Level::Warning, msg, MultiSpan::new(), None); self } pub fn span_warn<S: Into<MultiSpan>>(&mut self, sp: S, msg: &str) -> &mut DiagnosticBuilder<'a> { - self.sub(Level::Warning, msg, Some(sp.into()), None); + self.sub(Level::Warning, msg, sp.into(), None); self } pub fn help(&mut self , msg: &str) -> &mut DiagnosticBuilder<'a> { - self.sub(Level::Help, msg, None, None); + self.sub(Level::Help, msg, MultiSpan::new(), None); self } pub fn span_help<S: Into<MultiSpan>>(&mut self, sp: S, msg: &str) -> &mut DiagnosticBuilder<'a> { - self.sub(Level::Help, msg, Some(sp.into()), None); + self.sub(Level::Help, msg, sp.into(), None); self } /// Prints out a message with a suggested edit of the code. @@ -269,43 +294,15 @@ impl<'a> DiagnosticBuilder<'a> { msg: &str, suggestion: String) -> &mut DiagnosticBuilder<'a> { - self.sub(Level::Help, msg, None, Some(Suggestion(CodeSuggestion { + self.sub(Level::Help, msg, MultiSpan::new(), Some(Suggestion(CodeSuggestion { msp: sp.into(), substitutes: vec![suggestion], }))); self } - pub fn span_end_note<S: Into<MultiSpan>>(&mut self, - sp: S, - msg: &str) - -> &mut DiagnosticBuilder<'a> { - self.sub(Level::Note, msg, None, Some(EndSpan(sp.into()))); - self - } - pub fn fileline_warn<S: Into<MultiSpan>>(&mut self, - sp: S, - msg: &str) - -> &mut DiagnosticBuilder<'a> { - self.sub(Level::Warning, msg, None, Some(FileLine(sp.into()))); - self - } - pub fn fileline_note<S: Into<MultiSpan>>(&mut self, - sp: S, - msg: &str) - -> &mut DiagnosticBuilder<'a> { - self.sub(Level::Note, msg, None, Some(FileLine(sp.into()))); - self - } - pub fn fileline_help<S: Into<MultiSpan>>(&mut self, - sp: S, - msg: &str) - -> &mut DiagnosticBuilder<'a> { - self.sub(Level::Help, msg, None, Some(FileLine(sp.into()))); - self - } - pub fn span<S: Into<MultiSpan>>(&mut self, sp: S) -> &mut Self { - self.span = Some(sp.into()); + pub fn set_span<S: Into<MultiSpan>>(&mut self, sp: S) -> &mut Self { + self.span = sp.into(); self } @@ -324,7 +321,7 @@ impl<'a> DiagnosticBuilder<'a> { level: level, message: message.to_owned(), code: None, - span: None, + span: MultiSpan::new(), children: vec![], } } @@ -334,7 +331,7 @@ impl<'a> DiagnosticBuilder<'a> { fn sub(&mut self, level: Level, message: &str, - span: Option<MultiSpan>, + span: MultiSpan, render_span: Option<RenderSpan>) { let sub = SubDiagnostic { level: level, @@ -356,8 +353,11 @@ impl<'a> fmt::Debug for DiagnosticBuilder<'a> { /// we emit a bug. impl<'a> Drop for DiagnosticBuilder<'a> { fn drop(&mut self) { - if !self.cancelled() { - self.emitter.borrow_mut().emit(None, "Error constructed but not emitted", None, Bug); + if !panicking() && !self.cancelled() { + self.emitter.borrow_mut().emit(&MultiSpan::new(), + "Error constructed but not emitted", + None, + Bug); panic!(); } } @@ -412,7 +412,7 @@ impl Handler { msg: &str) -> DiagnosticBuilder<'a> { let mut result = DiagnosticBuilder::new(&self.emit, Level::Warning, msg); - result.span(sp); + result.set_span(sp); if !self.can_emit_warnings { result.cancel(); } @@ -424,7 +424,7 @@ impl Handler { code: &str) -> DiagnosticBuilder<'a> { let mut result = DiagnosticBuilder::new(&self.emit, Level::Warning, msg); - result.span(sp); + result.set_span(sp); result.code(code.to_owned()); if !self.can_emit_warnings { result.cancel(); @@ -444,7 +444,7 @@ impl Handler { -> DiagnosticBuilder<'a> { self.bump_err_count(); let mut result = DiagnosticBuilder::new(&self.emit, Level::Error, msg); - result.span(sp); + result.set_span(sp); result } pub fn struct_span_err_with_code<'a, S: Into<MultiSpan>>(&'a self, @@ -454,7 +454,7 @@ impl Handler { -> DiagnosticBuilder<'a> { self.bump_err_count(); let mut result = DiagnosticBuilder::new(&self.emit, Level::Error, msg); - result.span(sp); + result.set_span(sp); result.code(code.to_owned()); result } @@ -468,7 +468,7 @@ impl Handler { -> DiagnosticBuilder<'a> { self.bump_err_count(); let mut result = DiagnosticBuilder::new(&self.emit, Level::Fatal, msg); - result.span(sp); + result.set_span(sp); result } pub fn struct_span_fatal_with_code<'a, S: Into<MultiSpan>>(&'a self, @@ -478,7 +478,7 @@ impl Handler { -> DiagnosticBuilder<'a> { self.bump_err_count(); let mut result = DiagnosticBuilder::new(&self.emit, Level::Fatal, msg); - result.span(sp); + result.set_span(sp); result.code(code.to_owned()); result } @@ -499,7 +499,7 @@ impl Handler { if self.treat_err_as_bug { self.span_bug(sp, msg); } - self.emit(Some(&sp.into()), msg, Fatal); + self.emit(&sp.into(), msg, Fatal); self.bump_err_count(); return FatalError; } @@ -508,7 +508,7 @@ impl Handler { if self.treat_err_as_bug { self.span_bug(sp, msg); } - self.emit_with_code(Some(&sp.into()), msg, code, Fatal); + self.emit_with_code(&sp.into(), msg, code, Fatal); self.bump_err_count(); return FatalError; } @@ -516,24 +516,24 @@ impl Handler { if self.treat_err_as_bug { self.span_bug(sp, msg); } - self.emit(Some(&sp.into()), msg, Error); + self.emit(&sp.into(), msg, Error); self.bump_err_count(); } pub fn span_err_with_code<S: Into<MultiSpan>>(&self, sp: S, msg: &str, code: &str) { if self.treat_err_as_bug { self.span_bug(sp, msg); } - self.emit_with_code(Some(&sp.into()), msg, code, Error); + self.emit_with_code(&sp.into(), msg, code, Error); self.bump_err_count(); } pub fn span_warn<S: Into<MultiSpan>>(&self, sp: S, msg: &str) { - self.emit(Some(&sp.into()), msg, Warning); + self.emit(&sp.into(), msg, Warning); } pub fn span_warn_with_code<S: Into<MultiSpan>>(&self, sp: S, msg: &str, code: &str) { - self.emit_with_code(Some(&sp.into()), msg, code, Warning); + self.emit_with_code(&sp.into(), msg, code, Warning); } pub fn span_bug<S: Into<MultiSpan>>(&self, sp: S, msg: &str) -> ! { - self.emit(Some(&sp.into()), msg, Bug); + self.emit(&sp.into(), msg, Bug); panic!(ExplicitBug); } pub fn delay_span_bug<S: Into<MultiSpan>>(&self, sp: S, msg: &str) { @@ -541,11 +541,11 @@ impl Handler { *delayed = Some((sp.into(), msg.to_string())); } pub fn span_bug_no_panic<S: Into<MultiSpan>>(&self, sp: S, msg: &str) { - self.emit(Some(&sp.into()), msg, Bug); + self.emit(&sp.into(), msg, Bug); self.bump_err_count(); } pub fn span_note_without_error<S: Into<MultiSpan>>(&self, sp: S, msg: &str) { - self.emit.borrow_mut().emit(Some(&sp.into()), msg, None, Note); + self.emit.borrow_mut().emit(&sp.into(), msg, None, Note); } pub fn span_unimpl<S: Into<MultiSpan>>(&self, sp: S, msg: &str) -> ! { self.span_bug(sp, &format!("unimplemented {}", msg)); @@ -554,7 +554,7 @@ impl Handler { if self.treat_err_as_bug { self.bug(msg); } - self.emit.borrow_mut().emit(None, msg, None, Fatal); + self.emit.borrow_mut().emit(&MultiSpan::new(), msg, None, Fatal); self.bump_err_count(); FatalError } @@ -562,17 +562,17 @@ impl Handler { if self.treat_err_as_bug { self.bug(msg); } - self.emit.borrow_mut().emit(None, msg, None, Error); + self.emit.borrow_mut().emit(&MultiSpan::new(), msg, None, Error); self.bump_err_count(); } pub fn warn(&self, msg: &str) { - self.emit.borrow_mut().emit(None, msg, None, Warning); + self.emit.borrow_mut().emit(&MultiSpan::new(), msg, None, Warning); } pub fn note_without_error(&self, msg: &str) { - self.emit.borrow_mut().emit(None, msg, None, Note); + self.emit.borrow_mut().emit(&MultiSpan::new(), msg, None, Note); } pub fn bug(&self, msg: &str) -> ! { - self.emit.borrow_mut().emit(None, msg, None, Bug); + self.emit.borrow_mut().emit(&MultiSpan::new(), msg, None, Bug); panic!(ExplicitBug); } pub fn unimpl(&self, msg: &str) -> ! { @@ -614,25 +614,20 @@ impl Handler { panic!(self.fatal(&s)); } pub fn emit(&self, - msp: Option<&MultiSpan>, + msp: &MultiSpan, msg: &str, lvl: Level) { if lvl == Warning && !self.can_emit_warnings { return } - self.emit.borrow_mut().emit(msp, msg, None, lvl); + self.emit.borrow_mut().emit(&msp, msg, None, lvl); if !self.continue_after_error.get() { self.abort_if_errors(); } } pub fn emit_with_code(&self, - msp: Option<&MultiSpan>, + msp: &MultiSpan, msg: &str, code: &str, lvl: Level) { if lvl == Warning && !self.can_emit_warnings { return } - self.emit.borrow_mut().emit(msp, msg, Some(code), lvl); - if !self.continue_after_error.get() { self.abort_if_errors(); } - } - pub fn custom_emit(&self, rsp: RenderSpan, msg: &str, lvl: Level) { - if lvl == Warning && !self.can_emit_warnings { return } - self.emit.borrow_mut().custom_emit(&rsp, msg, lvl); + self.emit.borrow_mut().emit(&msp, msg, Some(code), lvl); if !self.continue_after_error.get() { self.abort_if_errors(); } } } @@ -662,7 +657,7 @@ impl Level { fn color(self) -> term::color::Color { match self { Bug | Fatal | PhaseFatal | Error => term::color::BRIGHT_RED, - Warning => term::color::BRIGHT_YELLOW, + Warning => term::color::YELLOW, Note => term::color::BRIGHT_GREEN, Help => term::color::BRIGHT_CYAN, Cancelled => unreachable!(), @@ -689,3 +684,20 @@ pub fn expect<T, M>(diag: &Handler, opt: Option<T>, msg: M) -> T where None => diag.bug(&msg()), } } + +/// True if we should use the old-skool error format style. This is +/// the default setting until the new errors are deemed stable enough +/// for general use. +/// +/// FIXME(#33240) +#[cfg(not(test))] +fn check_old_skool() -> bool { + use std::env; + env::var("RUST_NEW_ERROR_FORMAT").is_err() +} + +/// For unit tests, use the new format. +#[cfg(test)] +fn check_old_skool() -> bool { + false +} diff --git a/src/libsyntax/errors/snippet/mod.rs b/src/libsyntax/errors/snippet/mod.rs new file mode 100644 index 00000000000..e213f623ab8 --- /dev/null +++ b/src/libsyntax/errors/snippet/mod.rs @@ -0,0 +1,821 @@ +// Copyright 2012-2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// Code for annotating snippets. + +use codemap::{CharPos, CodeMap, FileMap, LineInfo, Span}; +use errors::check_old_skool; +use std::cmp; +use std::rc::Rc; +use std::mem; + +mod test; + +#[derive(Clone)] +pub struct SnippetData { + codemap: Rc<CodeMap>, + files: Vec<FileInfo>, +} + +#[derive(Clone)] +pub struct FileInfo { + file: Rc<FileMap>, + + /// The "primary file", if any, gets a `-->` marker instead of + /// `>>>`, and has a line-number/column printed and not just a + /// filename. It appears first in the listing. It is known to + /// contain at least one primary span, though primary spans (which + /// are designated with `^^^`) may also occur in other files. + primary_span: Option<Span>, + + lines: Vec<Line>, +} + +#[derive(Clone, Debug)] +struct Line { + line_index: usize, + annotations: Vec<Annotation>, +} + +#[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)] +struct Annotation { + /// Start column, 0-based indexing -- counting *characters*, not + /// utf-8 bytes. Note that it is important that this field goes + /// first, so that when we sort, we sort orderings by start + /// column. + start_col: usize, + + /// End column within the line (exclusive) + end_col: usize, + + /// Is this annotation derived from primary span + is_primary: bool, + + /// Optional label to display adjacent to the annotation. + label: Option<String>, +} + +#[derive(Debug)] +pub struct RenderedLine { + pub text: Vec<StyledString>, + pub kind: RenderedLineKind, +} + +#[derive(Debug)] +pub struct StyledString { + pub text: String, + pub style: Style, +} + +#[derive(Debug)] +pub struct StyledBuffer { + text: Vec<Vec<char>>, + styles: Vec<Vec<Style>> +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum Style { + FileNameStyle, + LineAndColumn, + LineNumber, + Quotation, + UnderlinePrimary, + UnderlineSecondary, + LabelPrimary, + LabelSecondary, + NoStyle, +} + +#[derive(Debug, Clone)] +pub enum RenderedLineKind { + PrimaryFileName, + OtherFileName, + SourceText { + file: Rc<FileMap>, + line_index: usize, + }, + Annotations, + Elision, +} + +impl SnippetData { + pub fn new(codemap: Rc<CodeMap>, + primary_span: Option<Span>) // (*) + -> Self { + // (*) The primary span indicates the file that must appear + // first, and which will have a line number etc in its + // name. Outside of tests, this is always `Some`, but for many + // tests it's not relevant to test this portion of the logic, + // and it's tedious to pick a primary span (read: tedious to + // port older tests that predate the existence of a primary + // span). + + debug!("SnippetData::new(primary_span={:?})", primary_span); + + let mut data = SnippetData { + codemap: codemap.clone(), + files: vec![] + }; + if let Some(primary_span) = primary_span { + let lo = codemap.lookup_char_pos(primary_span.lo); + data.files.push( + FileInfo { + file: lo.file, + primary_span: Some(primary_span), + lines: vec![], + }); + } + data + } + + pub fn push(&mut self, span: Span, is_primary: bool, label: Option<String>) { + debug!("SnippetData::push(span={:?}, is_primary={}, label={:?})", + span, is_primary, label); + + let file_lines = match self.codemap.span_to_lines(span) { + Ok(file_lines) => file_lines, + Err(_) => { + // ignore unprintable spans completely. + return; + } + }; + + self.file(&file_lines.file) + .push_lines(&file_lines.lines, is_primary, label); + } + + fn file(&mut self, file_map: &Rc<FileMap>) -> &mut FileInfo { + let index = self.files.iter().position(|f| f.file.name == file_map.name); + if let Some(index) = index { + return &mut self.files[index]; + } + + self.files.push( + FileInfo { + file: file_map.clone(), + lines: vec![], + primary_span: None, + }); + self.files.last_mut().unwrap() + } + + pub fn render_lines(&self) -> Vec<RenderedLine> { + debug!("SnippetData::render_lines()"); + + let mut rendered_lines: Vec<_> = + self.files.iter() + .flat_map(|f| f.render_file_lines(&self.codemap)) + .collect(); + prepend_prefixes(&mut rendered_lines); + trim_lines(&mut rendered_lines); + rendered_lines + } +} + +pub trait StringSource { + fn make_string(self) -> String; +} + +impl StringSource for String { + fn make_string(self) -> String { + self + } +} + +impl StringSource for Vec<char> { + fn make_string(self) -> String { + self.into_iter().collect() + } +} + +impl<S> From<(S, Style, RenderedLineKind)> for RenderedLine + where S: StringSource +{ + fn from((text, style, kind): (S, Style, RenderedLineKind)) -> Self { + RenderedLine { + text: vec![StyledString { + text: text.make_string(), + style: style, + }], + kind: kind, + } + } +} + +impl<S1,S2> From<(S1, Style, S2, Style, RenderedLineKind)> for RenderedLine + where S1: StringSource, S2: StringSource +{ + fn from(tuple: (S1, Style, S2, Style, RenderedLineKind)) -> Self { + let (text1, style1, text2, style2, kind) = tuple; + RenderedLine { + text: vec![ + StyledString { + text: text1.make_string(), + style: style1, + }, + StyledString { + text: text2.make_string(), + style: style2, + } + ], + kind: kind, + } + } +} + +impl RenderedLine { + fn trim_last(&mut self) { + if let Some(last_text) = self.text.last_mut() { + let len = last_text.text.trim_right().len(); + last_text.text.truncate(len); + } + } +} + +impl RenderedLineKind { + fn prefix(&self) -> StyledString { + match *self { + RenderedLineKind::SourceText { file: _, line_index } => + StyledString { + text: format!("{}", line_index + 1), + style: Style::LineNumber, + }, + RenderedLineKind::Elision => + StyledString { + text: String::from("..."), + style: Style::LineNumber, + }, + RenderedLineKind::PrimaryFileName | + RenderedLineKind::OtherFileName | + RenderedLineKind::Annotations => + StyledString { + text: String::from(""), + style: Style::LineNumber, + }, + } + } +} + +impl StyledBuffer { + fn new() -> StyledBuffer { + StyledBuffer { text: vec![], styles: vec![] } + } + + fn render(&self, source_kind: RenderedLineKind) -> Vec<RenderedLine> { + let mut output: Vec<RenderedLine> = vec![]; + let mut styled_vec: Vec<StyledString> = vec![]; + + for (row, row_style) in self.text.iter().zip(&self.styles) { + let mut current_style = Style::NoStyle; + let mut current_text = String::new(); + + for (&c, &s) in row.iter().zip(row_style) { + if s != current_style { + if !current_text.is_empty() { + styled_vec.push(StyledString { text: current_text, style: current_style }); + } + current_style = s; + current_text = String::new(); + } + current_text.push(c); + } + if !current_text.is_empty() { + styled_vec.push(StyledString { text: current_text, style: current_style }); + } + + if output.is_empty() { + //We know our first output line is source and the rest are highlights and labels + output.push(RenderedLine { text: styled_vec, kind: source_kind.clone() }); + } else { + output.push(RenderedLine { text: styled_vec, kind: RenderedLineKind::Annotations }); + } + styled_vec = vec![]; + } + + output + } + + fn putc(&mut self, line: usize, col: usize, chr: char, style: Style) { + while line >= self.text.len() { + self.text.push(vec![]); + self.styles.push(vec![]); + } + + if col < self.text[line].len() { + self.text[line][col] = chr; + self.styles[line][col] = style; + } else { + while self.text[line].len() < col { + self.text[line].push(' '); + self.styles[line].push(Style::NoStyle); + } + self.text[line].push(chr); + self.styles[line].push(style); + } + } + + fn puts(&mut self, line: usize, col: usize, string: &str, style: Style) { + let mut n = col; + for c in string.chars() { + self.putc(line, n, c, style); + n += 1; + } + } + + fn set_style(&mut self, line: usize, col: usize, style: Style) { + if self.styles.len() > line && self.styles[line].len() > col { + self.styles[line][col] = style; + } + } + + fn append(&mut self, line: usize, string: &str, style: Style) { + if line >= self.text.len() { + self.puts(line, 0, string, style); + } else { + let col = self.text[line].len(); + self.puts(line, col, string, style); + } + } +} + +impl FileInfo { + fn push_lines(&mut self, + lines: &[LineInfo], + is_primary: bool, + label: Option<String>) { + assert!(lines.len() > 0); + + // If a span covers multiple lines, we reduce it to a single + // point at the start of the span. This means that instead + // of producing output like this: + // + // ``` + // --> foo.rs:2:1 + // 2 |> fn conflicting_items<'grammar>(state: &LR0State<'grammar>) + // |> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + // 3 |> -> Set<LR0Item<'grammar>> + // |> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + // (and so on) + // ``` + // + // we produce: + // + // ``` + // --> foo.rs:2:1 + // 2 |> fn conflicting_items<'grammar>(state: &LR0State<'grammar>) + // ^ + // ``` + // + // Basically, although this loses information, multi-line spans just + // never look good. + + let (line, start_col, end_col) = if lines.len() == 1 { + (lines[0].line_index, lines[0].start_col, lines[0].end_col) + } else { + (lines[0].line_index, lines[0].start_col, CharPos(lines[0].start_col.0 + 1)) + }; + let index = self.ensure_source_line(line); + self.lines[index].push_annotation(start_col, + end_col, + is_primary, + label); + } + + /// Ensure that we have a `Line` struct corresponding to + /// `line_index` in the file. If we already have some other lines, + /// then this will add the intervening lines to ensure that we + /// have a complete snippet. (Note that when we finally display, + /// some of those lines may be elided.) + fn ensure_source_line(&mut self, line_index: usize) -> usize { + if self.lines.is_empty() { + self.lines.push(Line::new(line_index)); + return 0; + } + + // Find the range of lines we have thus far. + let first_line_index = self.lines.first().unwrap().line_index; + let last_line_index = self.lines.last().unwrap().line_index; + assert!(first_line_index <= last_line_index); + + // If the new line is lower than all the lines we have thus + // far, then insert the new line and any intervening lines at + // the front. In a silly attempt at micro-optimization, we + // don't just call `insert` repeatedly, but instead make a new + // (empty) vector, pushing the new lines onto it, and then + // appending the old vector. + if line_index < first_line_index { + let lines = mem::replace(&mut self.lines, vec![]); + self.lines.extend( + (line_index .. first_line_index) + .map(|line| Line::new(line)) + .chain(lines)); + return 0; + } + + // If the new line comes after the ones we have so far, insert + // lines for it. + if line_index > last_line_index { + self.lines.extend( + (last_line_index+1 .. line_index+1) + .map(|line| Line::new(line))); + return self.lines.len() - 1; + } + + // Otherwise it should already exist. + return line_index - first_line_index; + } + + fn render_file_lines(&self, codemap: &Rc<CodeMap>) -> Vec<RenderedLine> { + let old_school = check_old_skool(); + + // As a first step, we elide any instance of more than one + // continuous unannotated line. + + let mut lines_iter = self.lines.iter(); + let mut output = vec![]; + + // First insert the name of the file. + if !old_school { + match self.primary_span { + Some(span) => { + let lo = codemap.lookup_char_pos(span.lo); + output.push(RenderedLine { + text: vec![StyledString { + text: lo.file.name.clone(), + style: Style::FileNameStyle, + }, StyledString { + text: format!(":{}:{}", lo.line, lo.col.0 + 1), + style: Style::LineAndColumn, + }], + kind: RenderedLineKind::PrimaryFileName, + }); + } + None => { + output.push(RenderedLine { + text: vec![StyledString { + text: self.file.name.clone(), + style: Style::FileNameStyle, + }], + kind: RenderedLineKind::OtherFileName, + }); + } + } + } + + let mut next_line = lines_iter.next(); + while next_line.is_some() { + // Consume lines with annotations. + while let Some(line) = next_line { + if line.annotations.is_empty() { break; } + + let mut rendered_lines = self.render_line(line); + assert!(!rendered_lines.is_empty()); + if old_school { + match self.primary_span { + Some(span) => { + let lo = codemap.lookup_char_pos(span.lo); + rendered_lines[0].text.insert(0, StyledString { + text: format!(":{} ", lo.line), + style: Style::LineAndColumn, + }); + rendered_lines[0].text.insert(0, StyledString { + text: lo.file.name.clone(), + style: Style::FileNameStyle, + }); + let gap_amount = + rendered_lines[0].text[0].text.len() + + rendered_lines[0].text[1].text.len(); + assert!(rendered_lines.len() >= 2, + "no annotations resulted from: {:?}", + line); + for i in 1..rendered_lines.len() { + rendered_lines[i].text.insert(0, StyledString { + text: vec![" "; gap_amount].join(""), + style: Style::NoStyle + }); + } + } + _ =>() + } + } + output.append(&mut rendered_lines); + next_line = lines_iter.next(); + } + + // Emit lines without annotations, but only if they are + // followed by a line with an annotation. + let unannotated_line = next_line; + let mut unannotated_lines = 0; + while let Some(line) = next_line { + if !line.annotations.is_empty() { break; } + unannotated_lines += 1; + next_line = lines_iter.next(); + } + if unannotated_lines > 1 { + output.push(RenderedLine::from((String::new(), + Style::NoStyle, + RenderedLineKind::Elision))); + } else if let Some(line) = unannotated_line { + output.append(&mut self.render_line(line)); + } + } + + output + } + + fn render_line(&self, line: &Line) -> Vec<RenderedLine> { + let old_school = check_old_skool(); + let source_string = self.file.get_line(line.line_index) + .unwrap_or(""); + let source_kind = RenderedLineKind::SourceText { + file: self.file.clone(), + line_index: line.line_index, + }; + + let mut styled_buffer = StyledBuffer::new(); + + // First create the source line we will highlight. + styled_buffer.append(0, &source_string, Style::Quotation); + + if line.annotations.is_empty() { + return styled_buffer.render(source_kind); + } + + // We want to display like this: + // + // vec.push(vec.pop().unwrap()); + // --- ^^^ _ previous borrow ends here + // | | + // | error occurs here + // previous borrow of `vec` occurs here + // + // But there are some weird edge cases to be aware of: + // + // vec.push(vec.pop().unwrap()); + // -------- - previous borrow ends here + // || + // |this makes no sense + // previous borrow of `vec` occurs here + // + // For this reason, we group the lines into "highlight lines" + // and "annotations lines", where the highlight lines have the `~`. + + //let mut highlight_line = Self::whitespace(&source_string); + + // Sort the annotations by (start, end col) + let mut annotations = line.annotations.clone(); + annotations.sort(); + + // Next, create the highlight line. + for annotation in &annotations { + if old_school { + for p in annotation.start_col .. annotation.end_col { + if p == annotation.start_col { + styled_buffer.putc(1, p, '^', + if annotation.is_primary { + Style::UnderlinePrimary + } else { + Style::UnderlineSecondary + }); + } + else { + styled_buffer.putc(1, p, '~', + if annotation.is_primary { + Style::UnderlinePrimary + } else { + Style::UnderlineSecondary + }); + } + } + } + else { + for p in annotation.start_col .. annotation.end_col { + if annotation.is_primary { + styled_buffer.putc(1, p, '^', Style::UnderlinePrimary); + styled_buffer.set_style(0, p, Style::UnderlinePrimary); + } else { + styled_buffer.putc(1, p, '-', Style::UnderlineSecondary); + } + } + } + } + + // Now we are going to write labels in. To start, we'll exclude + // the annotations with no labels. + let (labeled_annotations, unlabeled_annotations): (Vec<_>, _) = + annotations.into_iter() + .partition(|a| a.label.is_some()); + + // If there are no annotations that need text, we're done. + if labeled_annotations.is_empty() { + return styled_buffer.render(source_kind); + } + if old_school { + return styled_buffer.render(source_kind); + } + + // Now add the text labels. We try, when possible, to stick the rightmost + // annotation at the end of the highlight line: + // + // vec.push(vec.pop().unwrap()); + // --- --- - previous borrow ends here + // + // But sometimes that's not possible because one of the other + // annotations overlaps it. For example, from the test + // `span_overlap_label`, we have the following annotations + // (written on distinct lines for clarity): + // + // fn foo(x: u32) { + // -------------- + // - + // + // In this case, we can't stick the rightmost-most label on + // the highlight line, or we would get: + // + // fn foo(x: u32) { + // -------- x_span + // | + // fn_span + // + // which is totally weird. Instead we want: + // + // fn foo(x: u32) { + // -------------- + // | | + // | x_span + // fn_span + // + // which is...less weird, at least. In fact, in general, if + // the rightmost span overlaps with any other span, we should + // use the "hang below" version, so we can at least make it + // clear where the span *starts*. + let mut labeled_annotations = &labeled_annotations[..]; + match labeled_annotations.split_last().unwrap() { + (last, previous) => { + if previous.iter() + .chain(&unlabeled_annotations) + .all(|a| !overlaps(a, last)) + { + // append the label afterwards; we keep it in a separate + // string + let highlight_label: String = format!(" {}", last.label.as_ref().unwrap()); + if last.is_primary { + styled_buffer.append(1, &highlight_label, Style::LabelPrimary); + } else { + styled_buffer.append(1, &highlight_label, Style::LabelSecondary); + } + labeled_annotations = previous; + } + } + } + + // If that's the last annotation, we're done + if labeled_annotations.is_empty() { + return styled_buffer.render(source_kind); + } + + for (index, annotation) in labeled_annotations.iter().enumerate() { + // Leave: + // - 1 extra line + // - One line for each thing that comes after + let comes_after = labeled_annotations.len() - index - 1; + let blank_lines = 3 + comes_after; + + // For each blank line, draw a `|` at our column. The + // text ought to be long enough for this. + for index in 2..blank_lines { + if annotation.is_primary { + styled_buffer.putc(index, annotation.start_col, '|', Style::UnderlinePrimary); + } else { + styled_buffer.putc(index, annotation.start_col, '|', Style::UnderlineSecondary); + } + } + + if annotation.is_primary { + styled_buffer.puts(blank_lines, annotation.start_col, + annotation.label.as_ref().unwrap(), Style::LabelPrimary); + } else { + styled_buffer.puts(blank_lines, annotation.start_col, + annotation.label.as_ref().unwrap(), Style::LabelSecondary); + } + } + + styled_buffer.render(source_kind) + } +} + +fn prepend_prefixes(rendered_lines: &mut [RenderedLine]) { + let old_school = check_old_skool(); + if old_school { + return; + } + + let prefixes: Vec<_> = + rendered_lines.iter() + .map(|rl| rl.kind.prefix()) + .collect(); + + // find the max amount of spacing we need; add 1 to + // p.text.len() to leave space between the prefix and the + // source text + let padding_len = + prefixes.iter() + .map(|p| if p.text.len() == 0 { 0 } else { p.text.len() + 1 }) + .max() + .unwrap_or(0); + + // Ensure we insert at least one character of padding, so that the + // `-->` arrows can fit etc. + let padding_len = cmp::max(padding_len, 1); + + for (mut prefix, line) in prefixes.into_iter().zip(rendered_lines) { + let extra_spaces = (prefix.text.len() .. padding_len).map(|_| ' '); + prefix.text.extend(extra_spaces); + match line.kind { + RenderedLineKind::Elision => { + line.text.insert(0, prefix); + } + RenderedLineKind::PrimaryFileName => { + // --> filename + // 22 |> + // ^ + // padding_len + let dashes = (0..padding_len - 1).map(|_| ' ') + .chain(Some('-')) + .chain(Some('-')) + .chain(Some('>')) + .chain(Some(' ')); + line.text.insert(0, StyledString {text: dashes.collect(), + style: Style::LineNumber}) + } + RenderedLineKind::OtherFileName => { + // ::: filename + // 22 |> + // ^ + // padding_len + let dashes = (0..padding_len - 1).map(|_| ' ') + .chain(Some(':')) + .chain(Some(':')) + .chain(Some(':')) + .chain(Some(' ')); + line.text.insert(0, StyledString {text: dashes.collect(), + style: Style::LineNumber}) + } + _ => { + line.text.insert(0, prefix); + line.text.insert(1, StyledString {text: String::from("|> "), + style: Style::LineNumber}) + } + } + } +} + +fn trim_lines(rendered_lines: &mut [RenderedLine]) { + for line in rendered_lines { + while !line.text.is_empty() { + line.trim_last(); + if line.text.last().unwrap().text.is_empty() { + line.text.pop(); + } else { + break; + } + } + } +} + +impl Line { + fn new(line_index: usize) -> Line { + Line { + line_index: line_index, + annotations: vec![] + } + } + + fn push_annotation(&mut self, + start: CharPos, + end: CharPos, + is_primary: bool, + label: Option<String>) { + self.annotations.push(Annotation { + start_col: start.0, + end_col: end.0, + is_primary: is_primary, + label: label, + }); + } +} + +fn overlaps(a1: &Annotation, + a2: &Annotation) + -> bool +{ + (a2.start_col .. a2.end_col).contains(a1.start_col) || + (a1.start_col .. a1.end_col).contains(a2.start_col) +} diff --git a/src/libsyntax/errors/snippet/test.rs b/src/libsyntax/errors/snippet/test.rs new file mode 100644 index 00000000000..569d1119919 --- /dev/null +++ b/src/libsyntax/errors/snippet/test.rs @@ -0,0 +1,521 @@ +// Copyright 2016 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// Code for testing annotated snippets. + +#![cfg(test)] + +use codemap::{BytePos, CodeMap, FileMap, NO_EXPANSION, Span}; +use std::rc::Rc; +use super::{RenderedLine, SnippetData}; + +/// Returns the span corresponding to the `n`th occurrence of +/// `substring` in `source_text`. +trait CodeMapExtension { + fn span_substr(&self, + file: &Rc<FileMap>, + source_text: &str, + substring: &str, + n: usize) + -> Span; +} + +impl CodeMapExtension for CodeMap { + fn span_substr(&self, + file: &Rc<FileMap>, + source_text: &str, + substring: &str, + n: usize) + -> Span + { + println!("span_substr(file={:?}/{:?}, substring={:?}, n={})", + file.name, file.start_pos, substring, n); + let mut i = 0; + let mut hi = 0; + loop { + let offset = source_text[hi..].find(substring).unwrap_or_else(|| { + panic!("source_text `{}` does not have {} occurrences of `{}`, only {}", + source_text, n, substring, i); + }); + let lo = hi + offset; + hi = lo + substring.len(); + if i == n { + let span = Span { + lo: BytePos(lo as u32 + file.start_pos.0), + hi: BytePos(hi as u32 + file.start_pos.0), + expn_id: NO_EXPANSION, + }; + assert_eq!(&self.span_to_snippet(span).unwrap()[..], + substring); + return span; + } + i += 1; + } + } +} + +fn splice(start: Span, end: Span) -> Span { + Span { + lo: start.lo, + hi: end.hi, + expn_id: NO_EXPANSION, + } +} + +fn make_string(lines: &[RenderedLine]) -> String { + lines.iter() + .flat_map(|rl| { + rl.text.iter() + .map(|s| &s.text[..]) + .chain(Some("\n")) + }) + .collect() +} + +#[test] +fn one_line() { + let file_text = r#" +fn foo() { + vec.push(vec.pop().unwrap()); +} +"#; + + let cm = Rc::new(CodeMap::new()); + let foo = cm.new_filemap_and_lines("foo.rs", file_text); + let span_vec0 = cm.span_substr(&foo, file_text, "vec", 0); + let span_vec1 = cm.span_substr(&foo, file_text, "vec", 1); + let span_semi = cm.span_substr(&foo, file_text, ";", 0); + + let mut snippet = SnippetData::new(cm, None); + snippet.push(span_vec0, false, Some(format!("previous borrow of `vec` occurs here"))); + snippet.push(span_vec1, false, Some(format!("error occurs here"))); + snippet.push(span_semi, false, Some(format!("previous borrow ends here"))); + + let lines = snippet.render_lines(); + println!("{:#?}", lines); + + let text: String = make_string(&lines); + + println!("text=\n{}", text); + assert_eq!(&text[..], &r#" + ::: foo.rs +3 |> vec.push(vec.pop().unwrap()); + |> --- --- - previous borrow ends here + |> | | + |> | error occurs here + |> previous borrow of `vec` occurs here +"#[1..]); +} + +#[test] +fn two_files() { + let file_text_foo = r#" +fn foo() { + vec.push(vec.pop().unwrap()); +} +"#; + + let file_text_bar = r#" +fn bar() { + // these blank links here + // serve to ensure that the line numbers + // from bar.rs + // require more digits + + + + + + + + + + + vec.push(); + + // this line will get elided + + vec.pop().unwrap()); +} +"#; + + let cm = Rc::new(CodeMap::new()); + let foo_map = cm.new_filemap_and_lines("foo.rs", file_text_foo); + let span_foo_vec0 = cm.span_substr(&foo_map, file_text_foo, "vec", 0); + let span_foo_vec1 = cm.span_substr(&foo_map, file_text_foo, "vec", 1); + let span_foo_semi = cm.span_substr(&foo_map, file_text_foo, ";", 0); + + let bar_map = cm.new_filemap_and_lines("bar.rs", file_text_bar); + let span_bar_vec0 = cm.span_substr(&bar_map, file_text_bar, "vec", 0); + let span_bar_vec1 = cm.span_substr(&bar_map, file_text_bar, "vec", 1); + let span_bar_semi = cm.span_substr(&bar_map, file_text_bar, ";", 0); + + let mut snippet = SnippetData::new(cm, Some(span_foo_vec1)); + snippet.push(span_foo_vec0, false, Some(format!("a"))); + snippet.push(span_foo_vec1, true, Some(format!("b"))); + snippet.push(span_foo_semi, false, Some(format!("c"))); + snippet.push(span_bar_vec0, false, Some(format!("d"))); + snippet.push(span_bar_vec1, false, Some(format!("e"))); + snippet.push(span_bar_semi, false, Some(format!("f"))); + + let lines = snippet.render_lines(); + println!("{:#?}", lines); + + let text: String = make_string(&lines); + + println!("text=\n{}", text); + + // Note that the `|>` remain aligned across both files: + assert_eq!(&text[..], &r#" + --> foo.rs:3:14 +3 |> vec.push(vec.pop().unwrap()); + |> --- ^^^ - c + |> | | + |> | b + |> a + ::: bar.rs +17 |> vec.push(); + |> --- - f + |> | + |> d +... +21 |> vec.pop().unwrap()); + |> --- e +"#[1..]); +} + +#[test] +fn multi_line() { + let file_text = r#" +fn foo() { + let name = find_id(&data, 22).unwrap(); + + // Add one more item we forgot to the vector. Silly us. + data.push(Data { name: format!("Hera"), id: 66 }); + + // Print everything out. + println!("Name: {:?}", name); + println!("Data: {:?}", data); +} +"#; + + let cm = Rc::new(CodeMap::new()); + let foo = cm.new_filemap_and_lines("foo.rs", file_text); + let span_data0 = cm.span_substr(&foo, file_text, "data", 0); + let span_data1 = cm.span_substr(&foo, file_text, "data", 1); + let span_rbrace = cm.span_substr(&foo, file_text, "}", 3); + + let mut snippet = SnippetData::new(cm, None); + snippet.push(span_data0, false, Some(format!("immutable borrow begins here"))); + snippet.push(span_data1, false, Some(format!("mutable borrow occurs here"))); + snippet.push(span_rbrace, false, Some(format!("immutable borrow ends here"))); + + let lines = snippet.render_lines(); + println!("{:#?}", lines); + + let text: String = make_string(&lines); + + println!("text=\n{}", text); + assert_eq!(&text[..], &r#" + ::: foo.rs +3 |> let name = find_id(&data, 22).unwrap(); + |> ---- immutable borrow begins here +... +6 |> data.push(Data { name: format!("Hera"), id: 66 }); + |> ---- mutable borrow occurs here +... +11 |> } + |> - immutable borrow ends here +"#[1..]); +} + +#[test] +fn overlapping() { + let file_text = r#" +fn foo() { + vec.push(vec.pop().unwrap()); +} +"#; + + let cm = Rc::new(CodeMap::new()); + let foo = cm.new_filemap_and_lines("foo.rs", file_text); + let span0 = cm.span_substr(&foo, file_text, "vec.push", 0); + let span1 = cm.span_substr(&foo, file_text, "vec", 0); + let span2 = cm.span_substr(&foo, file_text, "ec.push", 0); + let span3 = cm.span_substr(&foo, file_text, "unwrap", 0); + + let mut snippet = SnippetData::new(cm, None); + snippet.push(span0, false, Some(format!("A"))); + snippet.push(span1, false, Some(format!("B"))); + snippet.push(span2, false, Some(format!("C"))); + snippet.push(span3, false, Some(format!("D"))); + + let lines = snippet.render_lines(); + println!("{:#?}", lines); + let text: String = make_string(&lines); + + println!("text=r#\"\n{}\".trim_left()", text); + assert_eq!(&text[..], &r#" + ::: foo.rs +3 |> vec.push(vec.pop().unwrap()); + |> -------- ------ D + |> || + |> |C + |> A + |> B +"#[1..]); +} + +#[test] +fn one_line_out_of_order() { + let file_text = r#" +fn foo() { + vec.push(vec.pop().unwrap()); +} +"#; + + let cm = Rc::new(CodeMap::new()); + let foo = cm.new_filemap_and_lines("foo.rs", file_text); + let span_vec0 = cm.span_substr(&foo, file_text, "vec", 0); + let span_vec1 = cm.span_substr(&foo, file_text, "vec", 1); + let span_semi = cm.span_substr(&foo, file_text, ";", 0); + + // intentionally don't push the snippets left to right + let mut snippet = SnippetData::new(cm, None); + snippet.push(span_vec1, false, Some(format!("error occurs here"))); + snippet.push(span_vec0, false, Some(format!("previous borrow of `vec` occurs here"))); + snippet.push(span_semi, false, Some(format!("previous borrow ends here"))); + + let lines = snippet.render_lines(); + println!("{:#?}", lines); + let text: String = make_string(&lines); + + println!("text=r#\"\n{}\".trim_left()", text); + assert_eq!(&text[..], &r#" + ::: foo.rs +3 |> vec.push(vec.pop().unwrap()); + |> --- --- - previous borrow ends here + |> | | + |> | error occurs here + |> previous borrow of `vec` occurs here +"#[1..]); +} + +#[test] +fn elide_unnecessary_lines() { + let file_text = r#" +fn foo() { + let mut vec = vec![0, 1, 2]; + let mut vec2 = vec; + vec2.push(3); + vec2.push(4); + vec2.push(5); + vec2.push(6); + vec.push(7); +} +"#; + + let cm = Rc::new(CodeMap::new()); + let foo = cm.new_filemap_and_lines("foo.rs", file_text); + let span_vec0 = cm.span_substr(&foo, file_text, "vec", 3); + let span_vec1 = cm.span_substr(&foo, file_text, "vec", 8); + + let mut snippet = SnippetData::new(cm, None); + snippet.push(span_vec0, false, Some(format!("`vec` moved here because it \ + has type `collections::vec::Vec<i32>`"))); + snippet.push(span_vec1, false, Some(format!("use of moved value: `vec`"))); + + let lines = snippet.render_lines(); + println!("{:#?}", lines); + let text: String = make_string(&lines); + println!("text=r#\"\n{}\".trim_left()", text); + assert_eq!(&text[..], &r#" + ::: foo.rs +4 |> let mut vec2 = vec; + |> --- `vec` moved here because it has type `collections::vec::Vec<i32>` +... +9 |> vec.push(7); + |> --- use of moved value: `vec` +"#[1..]); +} + +#[test] +fn spans_without_labels() { + let file_text = r#" +fn foo() { + let mut vec = vec![0, 1, 2]; + let mut vec2 = vec; + vec2.push(3); + vec2.push(4); + vec2.push(5); + vec2.push(6); + vec.push(7); +} +"#; + + let cm = Rc::new(CodeMap::new()); + let foo = cm.new_filemap_and_lines("foo.rs", file_text); + + let mut snippet = SnippetData::new(cm.clone(), None); + for i in 0..4 { + let span_veci = cm.span_substr(&foo, file_text, "vec", i); + snippet.push(span_veci, false, None); + } + + let lines = snippet.render_lines(); + let text: String = make_string(&lines); + println!("text=&r#\"\n{}\n\"#[1..]", text); + assert_eq!(text, &r#" + ::: foo.rs +3 |> let mut vec = vec![0, 1, 2]; + |> --- --- +4 |> let mut vec2 = vec; + |> --- --- +"#[1..]); +} + +#[test] +fn span_long_selection() { + let file_text = r#" +impl SomeTrait for () { + fn foo(x: u32) { + // impl 1 + // impl 2 + // impl 3 + } +} +"#; + + let cm = Rc::new(CodeMap::new()); + let foo = cm.new_filemap_and_lines("foo.rs", file_text); + + let mut snippet = SnippetData::new(cm.clone(), None); + let fn_span = cm.span_substr(&foo, file_text, "fn", 0); + let rbrace_span = cm.span_substr(&foo, file_text, "}", 0); + snippet.push(splice(fn_span, rbrace_span), false, None); + let lines = snippet.render_lines(); + let text: String = make_string(&lines); + println!("r#\"\n{}\"", text); + assert_eq!(text, &r#" + ::: foo.rs +3 |> fn foo(x: u32) { + |> - +"#[1..]); +} + +#[test] +fn span_overlap_label() { + // Test that we don't put `x_span` to the right of its highlight, + // since there is another highlight that overlaps it. + + let file_text = r#" + fn foo(x: u32) { + } +} +"#; + + let cm = Rc::new(CodeMap::new()); + let foo = cm.new_filemap_and_lines("foo.rs", file_text); + + let mut snippet = SnippetData::new(cm.clone(), None); + let fn_span = cm.span_substr(&foo, file_text, "fn foo(x: u32)", 0); + let x_span = cm.span_substr(&foo, file_text, "x", 0); + snippet.push(fn_span, false, Some(format!("fn_span"))); + snippet.push(x_span, false, Some(format!("x_span"))); + let lines = snippet.render_lines(); + let text: String = make_string(&lines); + println!("r#\"\n{}\"", text); + assert_eq!(text, &r#" + ::: foo.rs +2 |> fn foo(x: u32) { + |> -------------- + |> | | + |> | x_span + |> fn_span +"#[1..]); +} + +#[test] +fn span_overlap_label2() { + // Test that we don't put `x_span` to the right of its highlight, + // since there is another highlight that overlaps it. In this + // case, the overlap is only at the beginning, but it's still + // better to show the beginning more clearly. + + let file_text = r#" + fn foo(x: u32) { + } +} +"#; + + let cm = Rc::new(CodeMap::new()); + let foo = cm.new_filemap_and_lines("foo.rs", file_text); + + let mut snippet = SnippetData::new(cm.clone(), None); + let fn_span = cm.span_substr(&foo, file_text, "fn foo(x", 0); + let x_span = cm.span_substr(&foo, file_text, "x: u32)", 0); + snippet.push(fn_span, false, Some(format!("fn_span"))); + snippet.push(x_span, false, Some(format!("x_span"))); + let lines = snippet.render_lines(); + let text: String = make_string(&lines); + println!("r#\"\n{}\"", text); + assert_eq!(text, &r#" + ::: foo.rs +2 |> fn foo(x: u32) { + |> -------------- + |> | | + |> | x_span + |> fn_span +"#[1..]); +} + +#[test] +fn span_overlap_label3() { + // Test that we don't put `x_span` to the right of its highlight, + // since there is another highlight that overlaps it. In this + // case, the overlap is only at the beginning, but it's still + // better to show the beginning more clearly. + + let file_text = r#" + fn foo() { + let closure = || { + inner + }; + } +} +"#; + + let cm = Rc::new(CodeMap::new()); + let foo = cm.new_filemap_and_lines("foo.rs", file_text); + + let mut snippet = SnippetData::new(cm.clone(), None); + + let closure_span = { + let closure_start_span = cm.span_substr(&foo, file_text, "||", 0); + let closure_end_span = cm.span_substr(&foo, file_text, "}", 0); + splice(closure_start_span, closure_end_span) + }; + + let inner_span = cm.span_substr(&foo, file_text, "inner", 0); + + snippet.push(closure_span, false, Some(format!("foo"))); + snippet.push(inner_span, false, Some(format!("bar"))); + + let lines = snippet.render_lines(); + let text: String = make_string(&lines); + println!("r#\"\n{}\"", text); + assert_eq!(text, &r#" + ::: foo.rs +3 |> let closure = || { + |> - foo +4 |> inner + |> ----- bar +"#[1..]); +} |
