From a20ee76b56c46a593238ce7ac9b9f70a99c43ff4 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Wed, 20 Apr 2016 14:52:31 -0400 Subject: 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. --- src/libsyntax/errors/snippet/mod.rs | 821 +++++++++++++++++++++++++++++++++++ src/libsyntax/errors/snippet/test.rs | 524 ++++++++++++++++++++++ 2 files changed, 1345 insertions(+) create mode 100644 src/libsyntax/errors/snippet/mod.rs create mode 100644 src/libsyntax/errors/snippet/test.rs (limited to 'src/libsyntax/errors') diff --git a/src/libsyntax/errors/snippet/mod.rs b/src/libsyntax/errors/snippet/mod.rs new file mode 100644 index 00000000000..cd8f705ab2e --- /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 or the MIT license +// , 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 std::cmp; +use std::rc::Rc; +use std::mem; +use std::ops::Range; + +#[cfg(test)] +mod test; + +pub struct SnippetData { + codemap: Rc, + files: Vec, +} + +pub struct FileInfo { + file: Rc, + + /// 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, + + lines: Vec, +} + +struct Line { + line_index: usize, + annotations: Vec, +} + +#[derive(Clone, 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. + end_col: usize, + + /// Is this annotation derived from primary span + is_primary: bool, + + /// Optional label to display adjacent to the annotation. + label: Option, +} + +#[derive(Debug)] +pub struct RenderedLine { + pub text: Vec, + pub kind: RenderedLineKind, +} + +#[derive(Debug)] +pub struct StyledString { + pub text: String, + pub style: Style, +} + +#[derive(Debug)] +pub struct StyledBuffer { + text: Vec>, + styles: Vec> +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum Style { + FileNameStyle, + LineAndColumn, + LineNumber, + Quotation, + UnderlinePrimary, + UnderlineSecondary, + LabelPrimary, + LabelSecondary, + NoStyle, +} +use self::Style::*; + +#[derive(Debug, Clone)] +pub enum RenderedLineKind { + PrimaryFileName, + OtherFileName, + SourceText { + file: Rc, + line_index: usize, + }, + Annotations, + Elision, +} +use self::RenderedLineKind::*; + +impl SnippetData { + pub fn new(codemap: Rc, + primary_span: Option) // (*) + -> 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) { + 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) -> &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 { + 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 { + fn make_string(self) -> String { + self.into_iter().collect() + } +} + +impl 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 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 !self.text.is_empty() { + let last_text = &mut self.text.last_mut().unwrap().text; + let len = last_text.trim_right().len(); + last_text.truncate(len); + } + } +} + +impl RenderedLineKind { + fn prefix(&self) -> StyledString { + match *self { + SourceText { file: _, line_index } => + StyledString { + text: format!("{}", line_index + 1), + style: LineNumber, + }, + Elision => + StyledString { + text: String::from("..."), + style: LineNumber, + }, + PrimaryFileName | + OtherFileName | + Annotations => + StyledString { + text: String::from(""), + style: LineNumber, + }, + } + } +} + +impl StyledBuffer { + fn new() -> StyledBuffer { + StyledBuffer { text: vec![], styles: vec![] } + } + + fn render(&self, source_kind: RenderedLineKind) -> Vec { + let mut output: Vec = vec![]; + let mut styled_vec: Vec = vec![]; + + for (row, row_style) in self.text.iter().zip(&self.styles) { + let mut current_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: 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(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) { + assert!(lines.len() > 0); + + // If a span covers multiple lines, just put the label on the + // first one. This is a sort of arbitrary choice and not + // obviously correct. + let (line0, remaining_lines) = lines.split_first().unwrap(); + let index = self.ensure_source_line(line0.line_index); + self.lines[index].push_annotation(line0.start_col, + line0.end_col, + is_primary, + label); + for line in remaining_lines { + if line.end_col > line.start_col { + let index = self.ensure_source_line(line.line_index); + self.lines[index].push_annotation(line.start_col, + line.end_col, + is_primary, + None); + } + } + } + + /// 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) -> Vec { + // Group our lines by those with annotations and those without + let mut lines_iter = self.lines.iter().peekable(); + + let mut line_groups = vec![]; + + loop { + match lines_iter.next() { + None => break, + Some(line) if line.annotations.is_empty() => { + // Collect unannotated group + let mut unannotated_group : Vec<&Line> = vec![]; + + unannotated_group.push(line); + + loop { + let next_line = + match lines_iter.peek() { + None => break, + Some(x) if !x.annotations.is_empty() => break, + Some(x) => x.clone() + }; + + unannotated_group.push(next_line); + lines_iter.next(); + } + + line_groups.push((false, unannotated_group)); + } + Some(line) => { + // Collect annotated group + let mut annotated_group : Vec<&Line> = vec![]; + + annotated_group.push(line); + + loop { + let next_line = + match lines_iter.peek() { + None => break, + Some(x) if x.annotations.is_empty() => break, + Some(x) => x.clone() + }; + + annotated_group.push(next_line); + lines_iter.next(); + } + + line_groups.push((true, annotated_group)); + } + } + } + + let mut output = vec![]; + + // First insert the name of the file. + 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: FileNameStyle, + }, StyledString { + text: format!(":{}:{}", lo.line, lo.col.0 + 1), + style: LineAndColumn, + }], + kind: PrimaryFileName, + }); + } + None => { + output.push(RenderedLine { + text: vec![StyledString { + text: self.file.name.clone(), + style: FileNameStyle, + }], + kind: OtherFileName, + }); + } + } + + for &(is_annotated, ref group) in line_groups.iter() { + if is_annotated { + let mut annotation_ends_at_eol = false; + let mut prev_ends_at_eol = false; + let mut elide_unlabeled_region = false; + + for group_line in group.iter() { + let source_string_len = + self.file.get_line(group_line.line_index) + .map(|s| s.len()) + .unwrap_or(0); + + for annotation in &group_line.annotations { + if annotation.end_col == source_string_len { + annotation_ends_at_eol = true; + } + } + + let is_single_unlabeled_annotated_line = + if group_line.annotations.len() == 1 { + if let Some(annotation) = group_line.annotations.first() { + match annotation.label { + Some(_) => false, + None => annotation.start_col == 0 && + annotation.end_col == source_string_len + } + } else { + false + } + } else { + false + }; + + if prev_ends_at_eol && is_single_unlabeled_annotated_line { + if !elide_unlabeled_region { + output.push(RenderedLine::from((String::new(), + NoStyle, Elision))); + elide_unlabeled_region = true; + prev_ends_at_eol = true; + } + continue; + } + + let mut v = self.render_line(group_line); + output.append(&mut v); + + prev_ends_at_eol = annotation_ends_at_eol; + } + } else { + if group.len() > 1 { + output.push(RenderedLine::from((String::new(), NoStyle, Elision))); + } else { + let mut v: Vec = + group.iter().flat_map(|line| self.render_line(line)).collect(); + output.append(&mut v); + } + } + } + + output + } + + fn render_line(&self, line: &Line) -> Vec { + let source_string = self.file.get_line(line.line_index) + .unwrap_or(""); + let source_kind = 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, 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 { + for p in annotation.start_col .. annotation.end_col { + if annotation.is_primary { + styled_buffer.putc(1, p, '^', UnderlinePrimary); + styled_buffer.set_style(0, p, UnderlinePrimary); + } else { + styled_buffer.putc(1, p, '-', 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); + } + + // 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, LabelPrimary); + } else { + styled_buffer.append(1, &highlight_label, 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, '|', UnderlinePrimary); + } else { + styled_buffer.putc(index, annotation.start_col, '|', UnderlineSecondary); + } + } + + if annotation.is_primary { + styled_buffer.puts(blank_lines, annotation.start_col, + annotation.label.as_ref().unwrap(), LabelPrimary); + } else { + styled_buffer.puts(blank_lines, annotation.start_col, + annotation.label.as_ref().unwrap(), LabelSecondary); + } + } + + styled_buffer.render(source_kind) + } +} + +fn prepend_prefixes(rendered_lines: &mut [RenderedLine]) { + 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: LineNumber}) + } + RenderedLineKind::OtherFileName => { + // >>>>> filename + // 22 |> + // ^ + // padding_len + let dashes = (0..padding_len + 2).map(|_| '>') + .chain(Some(' ')); + line.text.insert(0, StyledString {text: dashes.collect(), + style: LineNumber}) + } + _ => { + line.text.insert(0, prefix); + line.text.insert(1, StyledString {text: String::from("|> "), + 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) { + 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 +{ + between(a1.start_col, a2.start_col .. a2.end_col) || + between(a2.start_col, a1.start_col .. a1.end_col) +} + +fn between(v: usize, range: Range) -> bool { + v >= range.start && v < range.end +} diff --git a/src/libsyntax/errors/snippet/test.rs b/src/libsyntax/errors/snippet/test.rs new file mode 100644 index 00000000000..44ece285b1b --- /dev/null +++ b/src/libsyntax/errors/snippet/test.rs @@ -0,0 +1,524 @@ +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// Code for testing annotated snippets. + +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, + source_text: &str, + substring: &str, + n: usize) + -> Span; +} + +impl CodeMapExtension for CodeMap { + fn span_substr(&self, + file: &Rc, + 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`"))); + 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` +... +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 +5 |> }; + |> -------- +"#[1..]); +} -- cgit 1.4.1-3-g733a5 From e7c7a18d94cf672d6a031455d091e0bebe1a6b7c Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Wed, 20 Apr 2016 14:57:20 -0400 Subject: adapt JSON to new model Each Span now carries a `is_primary` boolean along with an optional label. If there are multiple labels for a span, it will appear multiple times. --- src/libsyntax/errors/json.rs | 202 ++++++++++++++++---------------------- src/tools/compiletest/src/json.rs | 47 ++++++--- 2 files changed, 119 insertions(+), 130 deletions(-) (limited to 'src/libsyntax/errors') diff --git a/src/libsyntax/errors/json.rs b/src/libsyntax/errors/json.rs index 821617bfe89..b343c3f3fbb 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, + /// Label that should be placed at this location (if any) + label: Option, /// 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, + 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, - je: &JsonEmitter) - -> DiagnosticSpan { + fn from_span_full(span: Span, + is_primary: bool, + label: Option, + suggestion: Option<&String>, + mut backtrace: vec::IntoIter, + 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,42 @@ 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 { - 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 { - 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 { 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 +335,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 { - 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 { @@ -396,9 +363,6 @@ impl JsonEmitter { RenderSpan::Suggestion(ref suggestion) => { Some(suggestion.splice_lines(&self.cm)) } - RenderSpan::EndSpan(_) => { - None - } } } } diff --git a/src/tools/compiletest/src/json.rs b/src/tools/compiletest/src/json.rs index 073b5e57cc7..3501b335205 100644 --- a/src/tools/compiletest/src/json.rs +++ b/src/tools/compiletest/src/json.rs @@ -33,6 +33,8 @@ struct DiagnosticSpan { line_end: usize, column_start: usize, column_end: usize, + is_primary: bool, + label: Option, expansion: Option>, } @@ -66,7 +68,7 @@ fn parse_line(file_name: &str, line: &str) -> Vec { match json::decode::(line) { Ok(diagnostic) => { let mut expected_errors = vec![]; - push_expected_errors(&mut expected_errors, &diagnostic, file_name); + push_expected_errors(&mut expected_errors, &diagnostic, &[], file_name); expected_errors } Err(error) => { @@ -80,12 +82,24 @@ fn parse_line(file_name: &str, line: &str) -> Vec { fn push_expected_errors(expected_errors: &mut Vec, diagnostic: &Diagnostic, + default_spans: &[&DiagnosticSpan], file_name: &str) { - // We only consider messages pertaining to the current file. - let matching_spans = || { - diagnostic.spans.iter().filter(|span| { - Path::new(&span.file_name) == Path::new(&file_name) - }) + let spans_in_this_file: Vec<_> = + diagnostic.spans.iter() + .filter(|span| Path::new(&span.file_name) == Path::new(&file_name)) + .collect(); + + let primary_spans: Vec<_> = + spans_in_this_file.iter() + .cloned() + .filter(|span| span.is_primary) + .collect(); + let primary_spans = if primary_spans.is_empty() { + // subdiagnostics often don't have a span of their own; + // inherit the span from the parent in that case + default_spans + } else { + &primary_spans }; // We break the output into multiple lines, and then append the @@ -124,7 +138,7 @@ fn push_expected_errors(expected_errors: &mut Vec, // more structured shortly anyhow. let mut message_lines = diagnostic.message.lines(); if let Some(first_line) = message_lines.next() { - for span in matching_spans() { + for span in primary_spans { let msg = with_code(span, first_line); let kind = ErrorKind::from_str(&diagnostic.level).ok(); expected_errors.push( @@ -137,7 +151,7 @@ fn push_expected_errors(expected_errors: &mut Vec, } } for next_line in message_lines { - for span in matching_spans() { + for span in primary_spans { expected_errors.push( Error { line_num: span.line_start, @@ -150,7 +164,7 @@ fn push_expected_errors(expected_errors: &mut Vec, // If the message has a suggestion, register that. if let Some(ref rendered) = diagnostic.rendered { - let start_line = matching_spans().map(|s| s.line_start).min().expect("\ + let start_line = primary_spans.iter().map(|s| s.line_start).min().expect("\ every suggestion should have at least one span"); for (index, line) in rendered.lines().enumerate() { expected_errors.push( @@ -164,7 +178,7 @@ fn push_expected_errors(expected_errors: &mut Vec, } // Add notes for the backtrace - for span in matching_spans() { + for span in primary_spans { for frame in &span.expansion { push_backtrace(expected_errors, frame, @@ -172,9 +186,20 @@ fn push_expected_errors(expected_errors: &mut Vec, } } + // Add notes for any labels that appear in the message. + for span in spans_in_this_file.iter() + .filter(|span| span.label.is_some()) + { + expected_errors.push(Error { + line_num: span.line_start, + kind: Some(ErrorKind::Note), + msg: span.label.clone().unwrap() + }); + } + // Flatten out the children. for child in &diagnostic.children { - push_expected_errors(expected_errors, child, file_name); + push_expected_errors(expected_errors, child, primary_spans, file_name); } } -- cgit 1.4.1-3-g733a5 From 11dc974a38fd533aa692cea213305056cd3a6902 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Wed, 20 Apr 2016 14:56:01 -0400 Subject: refactor to use new snippet code and model Major changes: - Remove old snippet rendering code and use the new stuff. - Introduce `span_label` method to add a label - Remove EndSpan mode and replace with a fn to get the last character of a span. - Stop using `Option` and just use an empty `MultiSpan` - and probably a bunch of other stuff :) --- src/librustc/session/mod.rs | 4 +- src/librustc_driver/lib.rs | 16 +- src/librustc_driver/test.rs | 4 - src/librustc_trans/back/write.rs | 19 +- src/libsyntax/codemap.rs | 87 +---- src/libsyntax/errors/emitter.rs | 796 ++++++++++++--------------------------- src/libsyntax/errors/mod.rs | 175 +++++---- 7 files changed, 357 insertions(+), 744 deletions(-) (limited to 'src/libsyntax/errors') diff --git a/src/librustc/session/mod.rs b/src/librustc/session/mod.rs index 5b78e4de18b..edb1c4530c2 100644 --- a/src/librustc/session/mod.rs +++ b/src/librustc/session/mod.rs @@ -567,7 +567,7 @@ pub fn early_error(output: config::ErrorOutputType, msg: &str) -> ! { } config::ErrorOutputType::Json => Box::new(JsonEmitter::basic()), }; - emitter.emit(None, msg, None, errors::Level::Fatal); + emitter.emit(&MultiSpan::new(), msg, None, errors::Level::Fatal); panic!(errors::FatalError); } @@ -578,7 +578,7 @@ pub fn early_warn(output: config::ErrorOutputType, msg: &str) { } config::ErrorOutputType::Json => Box::new(JsonEmitter::basic()), }; - emitter.emit(None, msg, None, errors::Level::Warning); + emitter.emit(&MultiSpan::new(), msg, None, errors::Level::Warning); } // Err(0) means compilation was stopped, but no errors were found. diff --git a/src/librustc_driver/lib.rs b/src/librustc_driver/lib.rs index 2d3363507d0..52306e388e2 100644 --- a/src/librustc_driver/lib.rs +++ b/src/librustc_driver/lib.rs @@ -91,8 +91,9 @@ use std::thread; use rustc::session::early_error; -use syntax::{ast, errors, diagnostics}; -use syntax::codemap::{CodeMap, FileLoader, RealFileLoader}; +use syntax::{ast, errors, diagnostic}; +use syntax::codemap::MultiSpan; +use syntax::parse::{self, PResult}; use syntax::errors::emitter::Emitter; use syntax::feature_gate::{GatedCfg, UnstableFeatures}; use syntax::parse::{self, PResult, token}; @@ -136,7 +137,8 @@ pub fn run(args: Vec) -> isize { None => { let mut emitter = errors::emitter::BasicEmitter::stderr(errors::ColorConfig::Auto); - emitter.emit(None, &abort_msg(err_count), None, errors::Level::Fatal); + emitter.emit(&MultiSpan::new(), &abort_msg(err_count), None, + errors::Level::Fatal); exit_on_err(); } } @@ -379,7 +381,7 @@ fn check_cfg(sopts: &config::Options, match item.node { ast::MetaItemKind::List(ref pred, _) => { saw_invalid_predicate = true; - emitter.emit(None, + emitter.emit(&MultiSpan::new(), &format!("invalid predicate in --cfg command line argument: `{}`", pred), None, @@ -1028,19 +1030,19 @@ pub fn monitor(f: F) { // a .span_bug or .bug call has already printed what // it wants to print. if !value.is::() { - emitter.emit(None, "unexpected panic", None, errors::Level::Bug); + emitter.emit(&MultiSpan::new(), "unexpected panic", None, errors::Level::Bug); } let xs = ["the compiler unexpectedly panicked. this is a bug.".to_string(), format!("we would appreciate a bug report: {}", BUG_REPORT_URL)]; for note in &xs { - emitter.emit(None, ¬e[..], None, errors::Level::Note) + emitter.emit(&MultiSpan::new(), ¬e[..], None, errors::Level::Note) } if match env::var_os("RUST_BACKTRACE") { Some(val) => &val != "0", None => false, } { - emitter.emit(None, + emitter.emit(&MultiSpan::new(), "run with `RUST_BACKTRACE=1` for a backtrace", None, errors::Level::Note); diff --git a/src/librustc_driver/test.rs b/src/librustc_driver/test.rs index ce92dd158c9..60f4ab1c95f 100644 --- a/src/librustc_driver/test.rs +++ b/src/librustc_driver/test.rs @@ -86,10 +86,6 @@ impl Emitter for ExpectErrorEmitter { lvl: Level) { remove_message(self, msg, lvl); } - - fn custom_emit(&mut self, _sp: &RenderSpan, msg: &str, lvl: Level) { - remove_message(self, msg, lvl); - } } fn errors(msgs: &[&str]) -> (Box, usize) { diff --git a/src/librustc_trans/back/write.rs b/src/librustc_trans/back/write.rs index 8a915f04405..50fd0392762 100644 --- a/src/librustc_trans/back/write.rs +++ b/src/librustc_trans/back/write.rs @@ -19,7 +19,7 @@ use llvm::SMDiagnosticRef; use {CrateTranslation, ModuleTranslation}; use util::common::time; use util::common::path2cstr; -use syntax::codemap; +use syntax::codemap::{self, MultiSpan}; use syntax::errors::{self, Handler, Level}; use syntax::errors::emitter::Emitter; @@ -84,13 +84,13 @@ impl SharedEmitter { for diag in &*buffer { match diag.code { Some(ref code) => { - handler.emit_with_code(None, + handler.emit_with_code(&MultiSpan::new(), &diag.msg, &code[..], diag.lvl); }, None => { - handler.emit(None, + handler.emit(&MultiSpan::new(), &diag.msg, diag.lvl); }, @@ -101,9 +101,12 @@ impl SharedEmitter { } impl Emitter for SharedEmitter { - fn emit(&mut self, sp: Option<&codemap::MultiSpan>, - msg: &str, code: Option<&str>, lvl: Level) { - assert!(sp.is_none(), "SharedEmitter doesn't support spans"); + fn emit(&mut self, + sp: &codemap::MultiSpan, + msg: &str, + code: Option<&str>, + lvl: Level) { + assert!(sp.primary_span().is_none(), "SharedEmitter doesn't support spans"); self.buffer.lock().unwrap().push(Diagnostic { msg: msg.to_string(), @@ -112,8 +115,8 @@ impl Emitter for SharedEmitter { }); } - fn custom_emit(&mut self, _sp: &errors::RenderSpan, _msg: &str, _lvl: Level) { - bug!("SharedEmitter doesn't support custom_emit"); + fn emit_struct(&mut self, _db: &errors::DiagnosticBuilder) { + bug!("SharedEmitter doesn't support emit_struct"); } } diff --git a/src/libsyntax/codemap.rs b/src/libsyntax/codemap.rs index 228af27f4b1..5862538de2e 100644 --- a/src/libsyntax/codemap.rs +++ b/src/libsyntax/codemap.rs @@ -163,6 +163,12 @@ pub const COMMAND_LINE_SP: Span = Span { lo: BytePos(0), expn_id: COMMAND_LINE_EXPN }; impl Span { + /// Returns a new span representing just the end-point of this span + pub fn end_point(self) -> Span { + let lo = cmp::max(self.hi.0 - 1, self.lo.0); + Span { lo: BytePos(lo), hi: self.hi, expn_id: self.expn_id} + } + /// Returns `self` if `self` is not the dummy span, and `other` otherwise. pub fn substitute_dummy(self, other: Span) -> Span { if self.source_equal(&DUMMY_SP) { other } else { self } @@ -794,7 +800,7 @@ impl CodeMap { /// Creates a new filemap and sets its line information. pub fn new_filemap_and_lines(&self, filename: &str, src: &str) -> Rc { let fm = self.new_filemap(filename.to_string(), src.to_owned()); - let mut byte_pos: u32 = 0; + let mut byte_pos: u32 = fm.start_pos.0; for line in src.lines() { // register the start of this line fm.next_line(BytePos(byte_pos)); @@ -1126,7 +1132,9 @@ impl CodeMap { // numbers in Loc are 1-based, so we subtract 1 to get 0-based // lines. for line_index in lo.line-1 .. hi.line-1 { - let line_len = lo.file.get_line(line_index).map(|s| s.len()).unwrap_or(0); + let line_len = lo.file.get_line(line_index) + .map(|s| s.chars().count()) + .unwrap_or(0); lines.push(LineInfo { line_index: line_index, start_col: start_col, end_col: CharPos::from_usize(line_len) }); @@ -1584,13 +1592,13 @@ mod tests { assert_eq!(file_lines.lines[0].line_index, 1); } - /// Given a string like " ^~~~~~~~~~~~ ", produces a span + /// Given a string like " ~~~~~~~~~~~~ ", produces a span /// coverting that range. The idea is that the string has the same /// length as the input, and we uncover the byte positions. Note /// that this can span lines and so on. fn span_from_selection(input: &str, selection: &str) -> Span { assert_eq!(input.len(), selection.len()); - let left_index = selection.find('^').unwrap() as u32; + let 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 } } @@ -1601,7 +1609,7 @@ mod tests { fn span_to_snippet_and_lines_spanning_multiple_lines() { 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 span = span_from_selection(inputtext, selection); @@ -1751,73 +1759,4 @@ r"blork2.rs:2:1: 2:12 "; assert_eq!(sstr, res_str); } - - #[test] - fn t13() { - // Test that collecting multiple spans into line-groups works correctly - let cm = CodeMap::new(); - let inp = "_aaaaa__bbb\nvv\nw\nx\ny\nz\ncccccc__ddddee__"; - let sp1 = " ^~~~~ \n \n \n \n \n \n "; - let sp2 = " \n \n \n \n \n^\n "; - let sp3 = " ^~~\n~~\n \n \n \n \n "; - let sp4 = " \n \n \n \n \n \n^~~~~~ "; - let sp5 = " \n \n \n \n \n \n ^~~~ "; - let sp6 = " \n \n \n \n \n \n ^~~~ "; - let sp_trim = " \n \n \n \n \n \n ^~ "; - let sp_merge = " \n \n \n \n \n \n ^~~~~~ "; - let sp7 = " \n ^\n \n \n \n \n "; - let sp8 = " \n \n^\n \n \n \n "; - let sp9 = " \n \n \n^\n \n \n "; - let sp10 = " \n \n \n \n^\n \n "; - - let span = |sp, expected| { - let sp = span_from_selection(inp, sp); - assert_eq!(&cm.span_to_snippet(sp).unwrap(), expected); - sp - }; - - cm.new_filemap_and_lines("blork.rs", inp); - let sp1 = span(sp1, "aaaaa"); - let sp2 = span(sp2, "z"); - let sp3 = span(sp3, "bbb\nvv"); - let sp4 = span(sp4, "cccccc"); - let sp5 = span(sp5, "dddd"); - let sp6 = span(sp6, "ddee"); - let sp7 = span(sp7, "v"); - let sp8 = span(sp8, "w"); - let sp9 = span(sp9, "x"); - let sp10 = span(sp10, "y"); - let sp_trim = span(sp_trim, "ee"); - let sp_merge = span(sp_merge, "ddddee"); - - let spans = vec![sp5, sp2, sp4, sp9, sp10, sp7, sp3, sp8, sp1, sp6]; - - macro_rules! check_next { - ($groups: expr, $expected: expr) => ({ - let actual = $groups.next().map(|g|&g.spans[..]); - let expected = $expected; - println!("actual:\n{:?}\n", actual); - println!("expected:\n{:?}\n", expected); - assert_eq!(actual, expected.as_ref().map(|x|&x[..])); - }); - } - - let _groups = cm.group_spans(spans.clone()); - let it = &mut _groups.iter(); - - check_next!(it, Some([sp1, sp7, sp8, sp9, sp10, sp2])); - // New group because we're exceeding MAX_HIGHLIGHT_LINES - check_next!(it, Some([sp4, sp_merge])); - check_next!(it, Some([sp3])); - check_next!(it, None::<[Span; 0]>); - - let _groups = cm.end_group_spans(spans); - let it = &mut _groups.iter(); - - check_next!(it, Some([sp1, sp7, sp8, sp9, sp10, sp2])); - // New group because we're exceeding MAX_HIGHLIGHT_LINES - check_next!(it, Some([sp4, sp5, sp_trim])); - check_next!(it, Some([sp3])); - check_next!(it, None::<[Span; 0]>); - } } diff --git a/src/libsyntax/errors/emitter.rs b/src/libsyntax/errors/emitter.rs index 0b5234769b2..e963a5f794c 100644 --- a/src/libsyntax/errors/emitter.rs +++ b/src/libsyntax/errors/emitter.rs @@ -16,6 +16,7 @@ use diagnostics; 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::*; @@ -24,27 +25,15 @@ use std::rc::Rc; use term; 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); + fn emit(&mut self, span: &MultiSpan, msg: &str, code: Option<&str>, lvl: Level); /// Emit a structured diagnostic. - fn emit_struct(&mut self, db: &DiagnosticBuilder) { - self.emit(db.span.as_ref(), &db.message, db.code.as_ref().map(|s| &**s), db.level); - for child in &db.children { - match child.render_span { - Some(ref sp) => self.custom_emit(sp, &child.message, child.level), - None => self.emit(child.span.as_ref(), &child.message, None, child.level), - } - } - } + fn emit_struct(&mut self, db: &DiagnosticBuilder); } /// 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, @@ -70,19 +59,23 @@ pub struct BasicEmitter { impl Emitter for BasicEmitter { fn emit(&mut self, - msp: Option<&MultiSpan>, + msp: &MultiSpan, msg: &str, code: Option<&str>, lvl: Level) { - assert!(msp.is_none(), "BasicEmitter can't handle spans"); + assert!(msp.primary_span().is_none(), "BasicEmitter can't handle spans"); + if let Err(e) = print_diagnostic(&mut self.dst, "", lvl, msg, code) { panic!("failed to print diagnostics: {:?}", e); } - } - fn custom_emit(&mut self, _: &RenderSpan, _: &str, _: Level) { - panic!("BasicEmitter can't handle custom_emit"); + fn emit_struct(&mut self, db: &DiagnosticBuilder) { + self.emit(&db.span, &db.message, db.code.as_ref().map(|s| &**s), db.level); + for child in &db.children { + assert!(child.render_span.is_none(), "BasicEmitter can't handle spans"); + self.emit(&child.span, &child.message, None, child.level); + } } } @@ -101,33 +94,31 @@ pub struct EmitterWriter { dst: Destination, registry: Option, cm: Rc, + first: bool, } impl Emitter for EmitterWriter { fn emit(&mut self, - msp: Option<&MultiSpan>, + msp: &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), - }; - - if let Err(e) = error { - panic!("failed to print diagnostics: {:?}", e); - } + self.emit_multispan(msp, msg, code, lvl, true); } - 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); + fn emit_struct(&mut self, db: &DiagnosticBuilder) { + self.emit_multispan(&db.span, &db.message, + db.code.as_ref().map(|s| &**s), db.level, true); + + for child in &db.children { + match child.render_span { + Some(ref sp) => + self.emit_renderspan(sp, &child.message, + child.level), + None => + self.emit_multispan(&child.span, + &child.message, None, child.level, false), + } } } } @@ -153,9 +144,10 @@ impl EmitterWriter { -> EmitterWriter { 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 } } else { - EmitterWriter { dst: Raw(Box::new(io::stderr())), registry: registry, cm: code_map } + EmitterWriter { dst: Raw(Box::new(io::stderr())), + registry: registry, cm: code_map, first: true } } } @@ -163,7 +155,49 @@ impl EmitterWriter { registry: Option, code_map: Rc) -> EmitterWriter { - EmitterWriter { dst: Raw(dst), registry: registry, cm: code_map } + EmitterWriter { dst: Raw(dst), registry: registry, cm: code_map, first: true } + } + + fn emit_multispan(&mut self, + span: &MultiSpan, + msg: &str, + code: Option<&str>, + lvl: Level, + is_header: bool) { + if is_header { + if self.first { + self.first = false; + } else { + match write!(self.dst, "\n") { + Ok(_) => { } + Err(e) => { + panic!("failed to print diagnostics: {:?}", e) + } + } + } + } + + let error = match span.primary_span() { + Some(COMMAND_LINE_SP) => { + self.emit_(&FileLine(span.clone()), msg, code, lvl) + } + Some(DUMMY_SP) | None => { + print_diagnostic(&mut self.dst, "", lvl, msg, code) + } + Some(_) => { + self.emit_(&FullSpan(span.clone()), msg, code, lvl) + } + }; + + if let Err(e) = error { + panic!("failed to print diagnostics: {:?}", e); + } + } + + fn emit_renderspan(&mut self, sp: &RenderSpan, msg: &str, lvl: Level) { + if let Err(e) = self.emit_(sp, msg, None, lvl) { + panic!("failed to print diagnostics: {:?}", e); + } } fn emit_(&mut self, @@ -173,51 +207,43 @@ impl EmitterWriter { lvl: Level) -> io::Result<()> { let msp = rsp.span(); - let bounds = msp.to_span_bounds(); - - let ss = if bounds == COMMAND_LINE_SP { - "".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) - }; - - print_diagnostic(&mut self.dst, &ss[..], lvl, msg, code)?; + let primary_span = msp.primary_span(); + + 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; + print_diagnostic(&mut self.dst, "", lvl, msg, Some(&code_with_explain))? + } + _ => print_diagnostic(&mut self.dst, "", lvl, msg, code)? + } match *rsp { FullSpan(_) => { 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) = primary_span { + self.print_macro_backtrace(primary_span)?; + } } Suggestion(ref suggestion) => { self.highlight_suggestion(suggestion)?; - self.print_macro_backtrace(bounds)?; + if let Some(primary_span) = primary_span { + self.print_macro_backtrace(primary_span)?; + } } FileLine(..) => { // no source text in this case! } } - if let Some(code) = code { - if let Some(_) = self.registry.as_ref() - .and_then(|registry| registry.find_description(code)) { - print_diagnostic(&mut self.dst, &ss[..], Help, - &format!("run `rustc --explain {}` to see a \ - detailed explanation", code), None)?; - } - } Ok(()) } 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 +277,21 @@ 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; - } - } - spans.next(); - line_spans += 1; - - if lo.line != hi.line { - // Assemble extra code lines to be emitted after this lines spans - // (substract `2` because the first and last line are rendered normally) - let max_lines = cmp::min(remaining_err_lines, MAX_SP_LINES) - 2; - prev_line_index = line.line_index; - let count = cmp::min((hi.line - lo.line - 1), max_lines); - for _ in 0..count { - let line = match lines.next() { - Some(l) => l, - None => break, - }; - let line_str = fm.get_line(line.line_index).unwrap(); - overflowed_buf.push_str(&format!("{}:{:>width$} {}\n", - fm.name, - line.line_index + 1, - line_str, - width=digits)); - remaining_err_lines -= 1; - prev_line_index += 1 - } - // Remember that the span overflowed to ensure - // that we emit its last line exactly once - // (other spans may, or may not, start on it) - overflowed = true; - break; - } - - for (pos, ch) in line_chars.by_ref() { - lastc = ch; - if pos >= lo.col.to_usize() { break; } - // Whenever a tab occurs on the code line, we insert one on - // the error-point-squiggly-line as well (instead of a space). - // That way the squiggly line will usually appear in the correct - // position. - match ch { - '\t' => { - col += 8 - col%8; - s.push('\t'); - }, - _ => { - col += 1; - s.push(' '); - }, - } - } - - s.push('^'); - let col_ptr = col; - let count = match lastc { - // Most terminals have a tab stop every eight columns by default - '\t' => 8 - col%8, - _ => 1, - }; - col += count; - s.extend(::std::iter::repeat('~').take(count)); - - let hi = self.cm.lookup_char_pos(sp.hi); - if hi.col != lo.col { - let mut chars = line_chars.by_ref(); - loop { - // We peek here to preserve the value for the next span - let (pos, ch) = match chars.peek() { - Some(elem) => *elem, - None => break, - }; - if pos >= hi.col.to_usize() { break; } - let count = match ch { - '\t' => 8 - col%8, - _ => 1, - }; - col += count; - s.extend(::std::iter::repeat('~').take(count)); - - chars.next(); - } - } - if (col - col_ptr) > 0 { - // One extra squiggly is replaced by a "^" - s.pop(); - } - } - - // If we elided something put an ellipsis. - if prev_line_index != line.line_index.wrapping_sub(1) && !overflowed { - write!(&mut self.dst, "{0:1$}...\n", "", skip)?; - } - - // Print offending code-line - remaining_err_lines -= 1; - write!(&mut self.dst, "{}:{:>width$} {}\n", - fm.name, - line.line_index + 1, - cur_line_str, - width=digits)?; - - if s.len() > skip { - // Render the spans we assembled previously (if any). - println_maybe_styled!(&mut self.dst, term::Attr::ForegroundColor(lvl.color()), - "{}", s)?; - } - - if !overflowed_buf.is_empty() { - // Print code-lines trailing the rendered spans (when a span overflows) - write!(&mut self.dst, "{}", &overflowed_buf)?; - overflowed_buf.clear(); - } else { - prev_line_index = line.line_index; - } - } - - // If we elided something, put an ellipsis. - if lines.next().is_some() { - write!(&mut self.dst, "{0:1$}...\n", "", skip)?; - } - Ok(()) - } - - /// Here are the differences between this and the normal `highlight_lines`: - /// `end_highlight_lines` will always put arrow on the last byte of each - /// span (instead of the first byte). Also, when a span is too long (more - /// than 6 lines), `end_highlight_lines` will print the first line, then - /// dot dot dot, then last line, whereas `highlight_lines` prints the first - /// six lines. - #[allow(deprecated)] - fn end_highlight_lines(&mut self, - msp: &MultiSpan, - lvl: Level) - -> io::Result<()> { - let lines = match self.cm.span_to_lines(msp.to_span_bounds()) { - Ok(lines) => lines, - Err(_) => { - write!(&mut self.dst, "(internal compiler error: unprintable span)\n")?; - return Ok(()); - } - }; - - let fm = &*lines.file; - if let None = fm.src { - return Ok(()); + let mut snippet_data = SnippetData::new(self.cm.clone(), + msp.primary_span()); + for span_label in msp.span_labels() { + snippet_data.push(span_label.span, + span_label.is_primary, + span_label.label); } - - let lines = &lines.lines[..]; - - // Calculate the widest number to format evenly - let first_line = lines.first().unwrap(); - let last_line = lines.last().unwrap(); - let digits = line_num_max_digits(last_line); - - let skip = fm.name.chars().count() + digits + 2; - - let mut spans = msp.spans.iter().peekable(); - let mut lines = lines.iter(); - let mut prev_line_index = first_line.line_index.wrapping_sub(1); - - // Display at most MAX_HIGHLIGHT_LINES lines. - let mut remaining_err_lines = MAX_HIGHLIGHT_LINES; - - 'l: loop { - if remaining_err_lines <= 0 { - break; - } - let line = match lines.next() { - Some(line) => line, - None => break, - }; - - // Skip is the number of characters we need to skip because they are - // part of the 'filename:line ' part of the previous line. - let mut s: String = ::std::iter::repeat(' ').take(skip).collect(); - - let line_str = fm.get_line(line.line_index).unwrap(); - let mut line_chars = line_str.chars().enumerate(); - let mut line_spans = 0; - - loop { - // Peek here to preserve the span if it doesn't belong to this line - let sp = match spans.peek() { - Some(sp) => **sp, - None => break, - }; - let lo = self.cm.lookup_char_pos(sp.lo); - let hi = self.cm.lookup_char_pos(sp.hi); - let elide_sp = (hi.line - lo.line) >= MAX_SP_LINES; - - let line_num = line.line_index + 1; - if !(lo.line <= line_num && hi.line >= line_num) { - // This line is not contained in the span - if line_spans == 0 { - continue 'l; - } else { - // This line is finished, now render the spans we've assembled - break - } - } else if hi.line > line_num { - if elide_sp && lo.line < line_num { - // This line is inbetween the first and last line of the span, - // so we may want to elide it. - continue 'l; - } else { - break - } - } - line_spans += 1; - spans.next(); - - for (pos, ch) in line_chars.by_ref() { - // Span seems to use half-opened interval, so subtract 1 - if pos >= hi.col.to_usize() - 1 { break; } - // Whenever a tab occurs on the previous line, we insert one on - // the error-point-squiggly-line as well (instead of a space). - // That way the squiggly line will usually appear in the correct - // position. - match ch { - '\t' => s.push('\t'), - _ => s.push(' '), - } - } - s.push('^'); + 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()?; } - - if prev_line_index != line.line_index.wrapping_sub(1) { - // If we elided something, put an ellipsis. - write!(&mut self.dst, "{0:1$}...\n", "", skip)?; - } - - // Print offending code-lines - write!(&mut self.dst, "{}:{:>width$} {}\n", fm.name, - line.line_index + 1, line_str, width=digits)?; - remaining_err_lines -= 1; - - if s.len() > skip { - // Render the spans we assembled previously (if any) - println_maybe_styled!(&mut self.dst, term::Attr::ForegroundColor(lvl.color()), - "{}", s)?; - } - prev_line_index = line.line_index; + write!(&mut self.dst, "\n")?; } Ok(()) } @@ -602,6 +324,7 @@ fn line_num_max_digits(line: &codemap::LineInfo) -> usize { digits } + fn print_diagnostic(dst: &mut Destination, topic: &str, lvl: Level, @@ -609,17 +332,22 @@ fn print_diagnostic(dst: &mut Destination, code: Option<&str>) -> io::Result<()> { if !topic.is_empty() { - write!(dst, "{} ", topic)?; - } - - 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::ForegroundColor(lvl.color()))?; + write!(dst, "{}: ", topic)?; + dst.reset_attrs()?; + } + 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 +388,52 @@ impl Destination { } } + fn apply_style(&mut self, + lvl: Level, + _kind: &RenderedLineKind, + style: Style) + -> io::Result<()> { + match style { + Style::FileNameStyle => { + } + Style::LineAndColumn => { + } + Style::LineNumber => { + self.start_attr(term::Attr::Bold)?; + self.start_attr(term::Attr::ForegroundColor(term::color::BRIGHT_BLUE))?; + } + Style::Quotation => { + } + Style::UnderlinePrimary | Style::LabelPrimary => { + self.start_attr(term::Attr::Bold)?; + self.start_attr(term::Attr::ForegroundColor(lvl.color()))?; + } + Style::UnderlineSecondary | Style::LabelSecondary => { + self.start_attr(term::Attr::Bold)?; + self.start_attr(term::Attr::ForegroundColor(term::color::BRIGHT_BLUE))?; + } + Style::NoStyle => { + } + } + Ok(()) + } + + fn start_attr(&mut self, attr: term::Attr) -> io::Result<()> { + match *self { + Terminal(ref mut t) => { t.attr(attr)?; } + Raw(_) => { } + } + Ok(()) + } + + fn reset_attrs(&mut self) -> io::Result<()> { + match *self { + Terminal(ref mut t) => { t.reset()?; } + Raw(_) => { } + } + Ok(()) + } + fn print_maybe_styled(&mut self, args: fmt::Arguments, color: term::Attr, @@ -741,7 +515,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 } } @@ -777,12 +551,15 @@ 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:8:1 +8 |> line8 + |> ^^^^^^^^^^^^^ +... +11 |> e-lä-vän + |> ^^^^^^^^^^^^^^^^ +"#[1..]); } #[test] @@ -790,7 +567,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 +585,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 = ["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 +613,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 +636,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 +644,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 +691,31 @@ 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 + |> ^^^^^ +... +8 |> _____ +9 |> ddd__eee_ + |> ^^^ ^^^ +10 |> elided +11 |> __f_gg + |> ^ ^^ +"#[1..]; + + let expect = &r#" + --> dummy.txt:1:1 +1 |> aaaaa + |> ^^^^^ +... +8 |> _____ +9 |> ddd__eee_ + |> ^^^ ^^^ +10 |> elided +11 |> __f_gg + |> ^ ^^ +"#[1..]; macro_rules! test { ($expected: expr, $highlight: expr) => ({ @@ -1034,37 +731,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/mod.rs b/src/libsyntax/errors/mod.rs index 792828b3054..abbc4eef7bf 100644 --- a/src/libsyntax/errors/mod.rs +++ b/src/libsyntax/errors/mod.rs @@ -13,7 +13,7 @@ pub use errors::emitter::ColorConfig; use self::Level::*; use self::RenderSpan::*; -use codemap::{self, CodeMap, MultiSpan}; +use codemap::{self, CodeMap, MultiSpan, NO_EXPANSION, Span}; use diagnostics; use errors::emitter::{Emitter, EmitterWriter}; @@ -24,6 +24,7 @@ use term; pub mod emitter; pub mod json; +pub mod snippet; #[derive(Clone)] pub enum RenderSpan { @@ -32,13 +33,6 @@ 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 @@ -61,7 +55,6 @@ impl RenderSpan { match *self { FullSpan(ref msp) | Suggestion(CodeSuggestion { ref msp, .. }) | - EndSpan(ref msp) | FileLine(ref msp) => msp } @@ -88,12 +81,24 @@ impl CodeSuggestion { } } } - let bounds = self.msp.to_span_bounds(); - let lines = cm.span_to_lines(bounds).unwrap(); - assert!(!lines.lines.is_empty()); - // This isn't strictly necessary, but would in all likelyhood be an error - 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 +110,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 +188,7 @@ pub struct DiagnosticBuilder<'a> { level: Level, message: String, code: Option, - span: Option, + span: MultiSpan, children: Vec, } @@ -192,7 +197,7 @@ pub struct DiagnosticBuilder<'a> { struct SubDiagnostic { level: Level, message: String, - span: Option, + span: MultiSpan, render_span: Option, } @@ -228,37 +233,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>(&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>(&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>(&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 +298,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>(&mut self, - sp: S, - msg: &str) - -> &mut DiagnosticBuilder<'a> { - self.sub(Level::Note, msg, None, Some(EndSpan(sp.into()))); - self - } - pub fn fileline_warn>(&mut self, - sp: S, - msg: &str) - -> &mut DiagnosticBuilder<'a> { - self.sub(Level::Warning, msg, None, Some(FileLine(sp.into()))); - self - } - pub fn fileline_note>(&mut self, - sp: S, - msg: &str) - -> &mut DiagnosticBuilder<'a> { - self.sub(Level::Note, msg, None, Some(FileLine(sp.into()))); - self - } - pub fn fileline_help>(&mut self, - sp: S, - msg: &str) - -> &mut DiagnosticBuilder<'a> { - self.sub(Level::Help, msg, None, Some(FileLine(sp.into()))); - self - } - pub fn span>(&mut self, sp: S) -> &mut Self { - self.span = Some(sp.into()); + pub fn set_span>(&mut self, sp: S) -> &mut Self { + self.span = sp.into(); self } @@ -324,7 +325,7 @@ impl<'a> DiagnosticBuilder<'a> { level: level, message: message.to_owned(), code: None, - span: None, + span: MultiSpan::new(), children: vec![], } } @@ -334,7 +335,7 @@ impl<'a> DiagnosticBuilder<'a> { fn sub(&mut self, level: Level, message: &str, - span: Option, + span: MultiSpan, render_span: Option) { let sub = SubDiagnostic { level: level, @@ -357,7 +358,10 @@ impl<'a> fmt::Debug for DiagnosticBuilder<'a> { 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); + self.emitter.borrow_mut().emit(&MultiSpan::new(), + "Error constructed but not emitted", + None, + Bug); panic!(); } } @@ -412,7 +416,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 +428,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 +448,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>(&'a self, @@ -454,7 +458,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 +472,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>(&'a self, @@ -478,7 +482,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 +503,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 +512,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 +520,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>(&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>(&self, sp: S, msg: &str) { - self.emit(Some(&sp.into()), msg, Warning); + self.emit(&sp.into(), msg, Warning); } pub fn span_warn_with_code>(&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>(&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>(&self, sp: S, msg: &str) { @@ -541,11 +545,11 @@ impl Handler { *delayed = Some((sp.into(), msg.to_string())); } pub fn span_bug_no_panic>(&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>(&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>(&self, sp: S, msg: &str) -> ! { self.span_bug(sp, &format!("unimplemented {}", msg)); @@ -554,7 +558,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 +566,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 +618,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(); } } } -- cgit 1.4.1-3-g733a5 From 41a652e0948d6cbcffa89a219b37a1e39ae619d4 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Wed, 20 Apr 2016 15:52:52 -0400 Subject: WIP factor out RudimentaryEmitter --- src/librustc_trans/back/write.rs | 21 ++++++------------ src/libsyntax/errors/emitter.rs | 47 ++++++++++++++++++++++++++-------------- 2 files changed, 38 insertions(+), 30 deletions(-) (limited to 'src/libsyntax/errors') diff --git a/src/librustc_trans/back/write.rs b/src/librustc_trans/back/write.rs index 50fd0392762..ffd8c261b60 100644 --- a/src/librustc_trans/back/write.rs +++ b/src/librustc_trans/back/write.rs @@ -19,9 +19,9 @@ use llvm::SMDiagnosticRef; use {CrateTranslation, ModuleTranslation}; use util::common::time; use util::common::path2cstr; -use syntax::codemap::{self, MultiSpan}; +use syntax::codemap::MultiSpan; use syntax::errors::{self, Handler, Level}; -use syntax::errors::emitter::Emitter; +use syntax::errors::emitter::RudimentaryEmitter; use std::collections::HashMap; use std::ffi::{CStr, CString}; @@ -100,24 +100,17 @@ impl SharedEmitter { } } -impl Emitter for SharedEmitter { - fn emit(&mut self, - sp: &codemap::MultiSpan, - msg: &str, - code: Option<&str>, - lvl: Level) { - assert!(sp.primary_span().is_none(), "SharedEmitter doesn't support spans"); - +impl RudimentaryEmitter for SharedEmitter { + fn emit_rudimentary(&mut self, + msg: &str, + code: Option<&str>, + lvl: Level) { self.buffer.lock().unwrap().push(Diagnostic { msg: msg.to_string(), code: code.map(|s| s.to_string()), lvl: lvl, }); } - - fn emit_struct(&mut self, _db: &errors::DiagnosticBuilder) { - bug!("SharedEmitter doesn't support emit_struct"); - } } diff --git a/src/libsyntax/errors/emitter.rs b/src/libsyntax/errors/emitter.rs index e963a5f794c..f851937d82b 100644 --- a/src/libsyntax/errors/emitter.rs +++ b/src/libsyntax/errors/emitter.rs @@ -25,12 +25,38 @@ use std::rc::Rc; use term; pub trait Emitter { + /// 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); } +/// A core trait that can only handle very simple messages: those +/// without spans or any real structure. Used only in specific contexts. +pub trait RudimentaryEmitter { + fn emit_rudimentary(&mut self, msg: &str, code: Option<&str>, lvl: Level); +} + +impl Emitter for T { + fn emit(&mut self, + msp: &MultiSpan, + msg: &str, + code: Option<&str>, + lvl: Level) { + assert!(msp.primary_span().is_none(), "Rudimenatry emitters can't handle spans"); + self.emit_rudimentary(msg, code, lvl); + } + + fn emit_struct(&mut self, db: &DiagnosticBuilder) { + self.emit(&db.span, &db.message, db.code.as_ref().map(|s| &**s), db.level); + for child in &db.children { + assert!(child.render_span.is_none(), "Rudimentary emitters can't handle render spans"); + self.emit(&child.span, &child.message, None, child.level); + } + } +} + /// maximum number of lines we will print for each error; arbitrary. pub const MAX_HIGHLIGHT_LINES: usize = 6; @@ -57,26 +83,15 @@ pub struct BasicEmitter { dst: Destination, } -impl Emitter for BasicEmitter { - fn emit(&mut self, - msp: &MultiSpan, - msg: &str, - code: Option<&str>, - lvl: Level) { - assert!(msp.primary_span().is_none(), "BasicEmitter can't handle spans"); - +impl RudimentaryEmitter for BasicEmitter { + fn emit_rudimentary(&mut self, + msg: &str, + code: Option<&str>, + lvl: Level) { if let Err(e) = print_diagnostic(&mut self.dst, "", lvl, msg, code) { panic!("failed to print diagnostics: {:?}", e); } } - - fn emit_struct(&mut self, db: &DiagnosticBuilder) { - self.emit(&db.span, &db.message, db.code.as_ref().map(|s| &**s), db.level); - for child in &db.children { - assert!(child.render_span.is_none(), "BasicEmitter can't handle spans"); - self.emit(&child.span, &child.message, None, child.level); - } - } } impl BasicEmitter { -- cgit 1.4.1-3-g733a5 From 71c6f813098a4e51344b7968022bbd946bad37be Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Wed, 20 Apr 2016 16:08:51 -0400 Subject: change errors from Yellow to Magenta The Yellow text is very hard to read with a white background. --- src/libsyntax/errors/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/libsyntax/errors') diff --git a/src/libsyntax/errors/mod.rs b/src/libsyntax/errors/mod.rs index abbc4eef7bf..0de2e067802 100644 --- a/src/libsyntax/errors/mod.rs +++ b/src/libsyntax/errors/mod.rs @@ -661,7 +661,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::BRIGHT_MAGENTA, Note => term::color::BRIGHT_GREEN, Help => term::color::BRIGHT_CYAN, Cancelled => unreachable!(), -- cgit 1.4.1-3-g733a5 From 1067850e6a8664eaabd59c3893aa5a762bdf2339 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Wed, 20 Apr 2016 20:00:25 -0400 Subject: refactor the Emitter trait There is now a CoreEmitter that everything desugars to, but without losing any information. Also remove RenderSpan::FileLine. This lets the rustc_driver tests build. --- src/librustc_driver/test.rs | 17 ++-- src/librustc_trans/back/write.rs | 16 ++-- src/libsyntax/errors/emitter.rs | 163 ++++++++++++++++++--------------------- src/libsyntax/errors/json.rs | 2 - src/libsyntax/errors/mod.rs | 7 +- 5 files changed, 92 insertions(+), 113 deletions(-) (limited to 'src/libsyntax/errors') diff --git a/src/librustc_driver/test.rs b/src/librustc_driver/test.rs index 60f4ab1c95f..f2448d50b22 100644 --- a/src/librustc_driver/test.rs +++ b/src/librustc_driver/test.rs @@ -34,9 +34,9 @@ use std::cell::RefCell; use std::rc::Rc; use syntax::ast; use syntax::abi::Abi; -use syntax::codemap::{MultiSpan, CodeMap, DUMMY_SP}; +use syntax::codemap::{CodeMap, DUMMY_SP}; use syntax::errors; -use syntax::errors::emitter::Emitter; +use syntax::errors::emitter::{CoreEmitter, Emitter}; use syntax::errors::{Level, RenderSpan}; use syntax::parse::token; use syntax::feature_gate::UnstableFeatures; @@ -78,12 +78,13 @@ fn remove_message(e: &mut ExpectErrorEmitter, msg: &str, lvl: Level) { } } -impl Emitter for ExpectErrorEmitter { - fn emit(&mut self, - _sp: Option<&MultiSpan>, - msg: &str, - _: Option<&str>, - lvl: Level) { +impl CoreEmitter for ExpectErrorEmitter { + fn emit_message(&mut self, + _sp: &RenderSpan, + msg: &str, + _: Option<&str>, + lvl: Level, + _is_header: bool) { remove_message(self, msg, lvl); } } diff --git a/src/librustc_trans/back/write.rs b/src/librustc_trans/back/write.rs index ffd8c261b60..a35048f89c1 100644 --- a/src/librustc_trans/back/write.rs +++ b/src/librustc_trans/back/write.rs @@ -20,8 +20,8 @@ use {CrateTranslation, ModuleTranslation}; use util::common::time; use util::common::path2cstr; use syntax::codemap::MultiSpan; -use syntax::errors::{self, Handler, Level}; -use syntax::errors::emitter::RudimentaryEmitter; +use syntax::errors::{self, Handler, Level, RenderSpan}; +use syntax::errors::emitter::CoreEmitter; use std::collections::HashMap; use std::ffi::{CStr, CString}; @@ -100,11 +100,13 @@ impl SharedEmitter { } } -impl RudimentaryEmitter for SharedEmitter { - fn emit_rudimentary(&mut self, - msg: &str, - code: Option<&str>, - lvl: Level) { +impl CoreEmitter for SharedEmitter { + fn emit_message(&mut self, + _rsp: &RenderSpan, + msg: &str, + code: Option<&str>, + lvl: Level, + _is_header: bool) { self.buffer.lock().unwrap().push(Diagnostic { msg: msg.to_string(), code: code.map(|s| s.to_string()), diff --git a/src/libsyntax/errors/emitter.rs b/src/libsyntax/errors/emitter.rs index f851937d82b..2b29de7fd71 100644 --- a/src/libsyntax/errors/emitter.rs +++ b/src/libsyntax/errors/emitter.rs @@ -24,6 +24,8 @@ 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 { /// Emit a standalone diagnostic message. fn emit(&mut self, span: &MultiSpan, msg: &str, code: Option<&str>, lvl: Level); @@ -32,27 +34,44 @@ pub trait Emitter { fn emit_struct(&mut self, db: &DiagnosticBuilder); } -/// A core trait that can only handle very simple messages: those -/// without spans or any real structure. Used only in specific contexts. -pub trait RudimentaryEmitter { - fn emit_rudimentary(&mut self, msg: &str, code: Option<&str>, lvl: Level); +pub trait CoreEmitter { + fn emit_message(&mut self, + rsp: &RenderSpan, + msg: &str, + code: Option<&str>, + lvl: Level, + is_header: bool); } -impl Emitter for T { +impl Emitter for T { fn emit(&mut self, msp: &MultiSpan, msg: &str, code: Option<&str>, lvl: Level) { - assert!(msp.primary_span().is_none(), "Rudimenatry emitters can't handle spans"); - self.emit_rudimentary(msg, code, lvl); + self.emit_message(&FullSpan(msp.clone()), + msg, + code, + lvl, + true); } fn emit_struct(&mut self, db: &DiagnosticBuilder) { - self.emit(&db.span, &db.message, db.code.as_ref().map(|s| &**s), db.level); + self.emit_message(&FullSpan(db.span.clone()), + &db.message, + db.code.as_ref().map(|s| &**s), + db.level, + true); for child in &db.children { - assert!(child.render_span.is_none(), "Rudimentary emitters can't handle render spans"); - self.emit(&child.span, &child.message, None, child.level); + let render_span = child.render_span + .clone() + .unwrap_or_else( + || FullSpan(child.span.clone())); + self.emit_message(&render_span, + &child.message, + None, + child.level, + false); } } } @@ -83,11 +102,14 @@ pub struct BasicEmitter { dst: Destination, } -impl RudimentaryEmitter for BasicEmitter { - fn emit_rudimentary(&mut self, - msg: &str, - code: Option<&str>, - lvl: Level) { +impl CoreEmitter for BasicEmitter { + fn emit_message(&mut self, + _rsp: &RenderSpan, + msg: &str, + code: Option<&str>, + lvl: Level, + _is_header: 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); } @@ -112,28 +134,16 @@ pub struct EmitterWriter { first: bool, } -impl Emitter for EmitterWriter { - fn emit(&mut self, - msp: &MultiSpan, - msg: &str, - code: Option<&str>, - lvl: Level) { - self.emit_multispan(msp, msg, code, lvl, true); - } - - fn emit_struct(&mut self, db: &DiagnosticBuilder) { - self.emit_multispan(&db.span, &db.message, - db.code.as_ref().map(|s| &**s), db.level, true); - - for child in &db.children { - match child.render_span { - Some(ref sp) => - self.emit_renderspan(sp, &child.message, - child.level), - None => - self.emit_multispan(&child.span, - &child.message, None, child.level, false), - } +impl CoreEmitter for EmitterWriter { + fn emit_message(&mut self, + rsp: &RenderSpan, + msg: &str, + code: Option<&str>, + lvl: Level, + is_header: bool) { + match self.emit_message_(rsp, msg, code, lvl, is_header) { + Ok(()) => { } + Err(e) => panic!("failed to emit error: {}", e) } } } @@ -173,83 +183,56 @@ impl EmitterWriter { EmitterWriter { dst: Raw(dst), registry: registry, cm: code_map, first: true } } - fn emit_multispan(&mut self, - span: &MultiSpan, - msg: &str, - code: Option<&str>, - lvl: Level, - is_header: bool) { + fn emit_message_(&mut self, + rsp: &RenderSpan, + msg: &str, + code: Option<&str>, + lvl: Level, + is_header: bool) + -> io::Result<()> { if is_header { if self.first { self.first = false; } else { - match write!(self.dst, "\n") { - Ok(_) => { } - Err(e) => { - panic!("failed to print diagnostics: {:?}", e) - } - } + write!(self.dst, "\n")?; } } - let error = match span.primary_span() { - Some(COMMAND_LINE_SP) => { - self.emit_(&FileLine(span.clone()), msg, code, lvl) - } - Some(DUMMY_SP) | None => { - print_diagnostic(&mut self.dst, "", lvl, msg, code) - } - Some(_) => { - self.emit_(&FullSpan(span.clone()), msg, code, lvl) - } - }; - - if let Err(e) = error { - panic!("failed to print diagnostics: {:?}", e); - } - } - - fn emit_renderspan(&mut self, sp: &RenderSpan, msg: &str, lvl: Level) { - if let Err(e) = self.emit_(sp, msg, None, lvl) { - panic!("failed to print diagnostics: {:?}", e); - } - } - - fn emit_(&mut self, - rsp: &RenderSpan, - msg: &str, - code: Option<&str>, - lvl: Level) - -> io::Result<()> { - let msp = rsp.span(); - let primary_span = msp.primary_span(); - match code { Some(code) if self.registry.as_ref() - .and_then(|registry| registry.find_description(code)).is_some() => - { + .and_then(|registry| registry.find_description(code)) + .is_some() => { let code_with_explain = String::from("--explain ") + code; print_diagnostic(&mut self.dst, "", lvl, msg, Some(&code_with_explain))? } - _ => print_diagnostic(&mut self.dst, "", lvl, msg, code)? + _ => { + print_diagnostic(&mut self.dst, "", lvl, msg, code)? + } } + // 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)?; - if let Some(primary_span) = primary_span { + if let Some(primary_span) = msp.primary_span() { self.print_macro_backtrace(primary_span)?; } } Suggestion(ref suggestion) => { self.highlight_suggestion(suggestion)?; - if let Some(primary_span) = primary_span { + if let Some(primary_span) = rsp.span().primary_span() { self.print_macro_backtrace(primary_span)?; } } - FileLine(..) => { - // no source text in this case! - } } Ok(()) diff --git a/src/libsyntax/errors/json.rs b/src/libsyntax/errors/json.rs index b343c3f3fbb..93c6268ccae 100644 --- a/src/libsyntax/errors/json.rs +++ b/src/libsyntax/errors/json.rs @@ -294,7 +294,6 @@ impl DiagnosticSpan { fn from_render_span(rsp: &RenderSpan, je: &JsonEmitter) -> Vec { match *rsp { - RenderSpan::FileLine(ref msp) | RenderSpan::FullSpan(ref msp) => DiagnosticSpan::from_multispan(msp, je), RenderSpan::Suggestion(ref suggestion) => @@ -356,7 +355,6 @@ impl DiagnosticCode { impl JsonEmitter { fn render(&self, render_span: &RenderSpan) -> Option { match *render_span { - RenderSpan::FileLine(_) | RenderSpan::FullSpan(_) => { None } diff --git a/src/libsyntax/errors/mod.rs b/src/libsyntax/errors/mod.rs index 0de2e067802..d533ffb981a 100644 --- a/src/libsyntax/errors/mod.rs +++ b/src/libsyntax/errors/mod.rs @@ -38,10 +38,6 @@ pub enum RenderSpan { /// 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)] @@ -54,8 +50,7 @@ impl RenderSpan { fn span(&self) -> &MultiSpan { match *self { FullSpan(ref msp) | - Suggestion(CodeSuggestion { ref msp, .. }) | - FileLine(ref msp) => + Suggestion(CodeSuggestion { ref msp, .. }) => msp } } -- cgit 1.4.1-3-g733a5 From e56121c584893d8b46af5e4cd5d580d30f221d9f Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 26 Apr 2016 12:49:10 -0400 Subject: Do not import variants from RenderedLineKind --- src/libsyntax/errors/snippet/mod.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) (limited to 'src/libsyntax/errors') diff --git a/src/libsyntax/errors/snippet/mod.rs b/src/libsyntax/errors/snippet/mod.rs index cd8f705ab2e..0018667e67b 100644 --- a/src/libsyntax/errors/snippet/mod.rs +++ b/src/libsyntax/errors/snippet/mod.rs @@ -103,7 +103,7 @@ pub enum RenderedLineKind { Annotations, Elision, } -use self::RenderedLineKind::*; +use self::RenderedLineKind as RLK; impl SnippetData { pub fn new(codemap: Rc, @@ -244,19 +244,19 @@ impl RenderedLine { impl RenderedLineKind { fn prefix(&self) -> StyledString { match *self { - SourceText { file: _, line_index } => + RLK::SourceText { file: _, line_index } => StyledString { text: format!("{}", line_index + 1), style: LineNumber, }, - Elision => + RLK::Elision => StyledString { text: String::from("..."), style: LineNumber, }, - PrimaryFileName | - OtherFileName | - Annotations => + RLK::PrimaryFileName | + RLK::OtherFileName | + RLK::Annotations => StyledString { text: String::from(""), style: LineNumber, @@ -296,7 +296,7 @@ impl StyledBuffer { //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: Annotations }); + output.push(RenderedLine { text: styled_vec, kind: RLK::Annotations }); } styled_vec = vec![]; } @@ -484,7 +484,7 @@ impl FileInfo { text: format!(":{}:{}", lo.line, lo.col.0 + 1), style: LineAndColumn, }], - kind: PrimaryFileName, + kind: RLK::PrimaryFileName, }); } None => { @@ -493,7 +493,7 @@ impl FileInfo { text: self.file.name.clone(), style: FileNameStyle, }], - kind: OtherFileName, + kind: RLK::OtherFileName, }); } } @@ -534,7 +534,7 @@ impl FileInfo { if prev_ends_at_eol && is_single_unlabeled_annotated_line { if !elide_unlabeled_region { output.push(RenderedLine::from((String::new(), - NoStyle, Elision))); + NoStyle, RLK::Elision))); elide_unlabeled_region = true; prev_ends_at_eol = true; } @@ -548,7 +548,7 @@ impl FileInfo { } } else { if group.len() > 1 { - output.push(RenderedLine::from((String::new(), NoStyle, Elision))); + output.push(RenderedLine::from((String::new(), NoStyle, RLK::Elision))); } else { let mut v: Vec = group.iter().flat_map(|line| self.render_line(line)).collect(); @@ -563,7 +563,7 @@ impl FileInfo { fn render_line(&self, line: &Line) -> Vec { let source_string = self.file.get_line(line.line_index) .unwrap_or(""); - let source_kind = SourceText { + let source_kind = RLK::SourceText { file: self.file.clone(), line_index: line.line_index, }; -- cgit 1.4.1-3-g733a5 From d58a4becf3943c02b9815f3d3875fe8817e41c7b Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 26 Apr 2016 12:52:28 -0400 Subject: Nit: do not import variants from Style --- src/libsyntax/errors/snippet/mod.rs | 47 ++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 24 deletions(-) (limited to 'src/libsyntax/errors') diff --git a/src/libsyntax/errors/snippet/mod.rs b/src/libsyntax/errors/snippet/mod.rs index 0018667e67b..f86d4bdb147 100644 --- a/src/libsyntax/errors/snippet/mod.rs +++ b/src/libsyntax/errors/snippet/mod.rs @@ -90,7 +90,6 @@ pub enum Style { LabelSecondary, NoStyle, } -use self::Style::*; #[derive(Debug, Clone)] pub enum RenderedLineKind { @@ -247,19 +246,19 @@ impl RenderedLineKind { RLK::SourceText { file: _, line_index } => StyledString { text: format!("{}", line_index + 1), - style: LineNumber, + style: Style::LineNumber, }, RLK::Elision => StyledString { text: String::from("..."), - style: LineNumber, + style: Style::LineNumber, }, RLK::PrimaryFileName | RLK::OtherFileName | RLK::Annotations => StyledString { text: String::from(""), - style: LineNumber, + style: Style::LineNumber, }, } } @@ -275,7 +274,7 @@ impl StyledBuffer { let mut styled_vec: Vec = vec![]; for (row, row_style) in self.text.iter().zip(&self.styles) { - let mut current_style = NoStyle; + let mut current_style = Style::NoStyle; let mut current_text = String::new(); for (&c, &s) in row.iter().zip(row_style) { @@ -316,7 +315,7 @@ impl StyledBuffer { } else { while self.text[line].len() < col { self.text[line].push(' '); - self.styles[line].push(NoStyle); + self.styles[line].push(Style::NoStyle); } self.text[line].push(chr); self.styles[line].push(style); @@ -479,10 +478,10 @@ impl FileInfo { output.push(RenderedLine { text: vec![StyledString { text: lo.file.name.clone(), - style: FileNameStyle, + style: Style::FileNameStyle, }, StyledString { text: format!(":{}:{}", lo.line, lo.col.0 + 1), - style: LineAndColumn, + style: Style::LineAndColumn, }], kind: RLK::PrimaryFileName, }); @@ -491,7 +490,7 @@ impl FileInfo { output.push(RenderedLine { text: vec![StyledString { text: self.file.name.clone(), - style: FileNameStyle, + style: Style::FileNameStyle, }], kind: RLK::OtherFileName, }); @@ -534,7 +533,7 @@ impl FileInfo { if prev_ends_at_eol && is_single_unlabeled_annotated_line { if !elide_unlabeled_region { output.push(RenderedLine::from((String::new(), - NoStyle, RLK::Elision))); + Style::NoStyle, RLK::Elision))); elide_unlabeled_region = true; prev_ends_at_eol = true; } @@ -548,7 +547,7 @@ impl FileInfo { } } else { if group.len() > 1 { - output.push(RenderedLine::from((String::new(), NoStyle, RLK::Elision))); + output.push(RenderedLine::from((String::new(), Style::NoStyle, RLK::Elision))); } else { let mut v: Vec = group.iter().flat_map(|line| self.render_line(line)).collect(); @@ -571,7 +570,7 @@ impl FileInfo { let mut styled_buffer = StyledBuffer::new(); // First create the source line we will highlight. - styled_buffer.append(0, &source_string, Quotation); + styled_buffer.append(0, &source_string, Style::Quotation); if line.annotations.is_empty() { return styled_buffer.render(source_kind); @@ -606,10 +605,10 @@ impl FileInfo { for annotation in &annotations { for p in annotation.start_col .. annotation.end_col { if annotation.is_primary { - styled_buffer.putc(1, p, '^', UnderlinePrimary); - styled_buffer.set_style(0, p, UnderlinePrimary); + styled_buffer.putc(1, p, '^', Style::UnderlinePrimary); + styled_buffer.set_style(0, p, Style::UnderlinePrimary); } else { - styled_buffer.putc(1, p, '-', UnderlineSecondary); + styled_buffer.putc(1, p, '-', Style::UnderlineSecondary); } } } @@ -671,9 +670,9 @@ impl FileInfo { // string let highlight_label: String = format!(" {}", last.label.as_ref().unwrap()); if last.is_primary { - styled_buffer.append(1, &highlight_label, LabelPrimary); + styled_buffer.append(1, &highlight_label, Style::LabelPrimary); } else { - styled_buffer.append(1, &highlight_label, LabelSecondary); + styled_buffer.append(1, &highlight_label, Style::LabelSecondary); } labeled_annotations = previous; } @@ -696,18 +695,18 @@ impl FileInfo { // 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, '|', UnderlinePrimary); + styled_buffer.putc(index, annotation.start_col, '|', Style::UnderlinePrimary); } else { - styled_buffer.putc(index, annotation.start_col, '|', UnderlineSecondary); + 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(), LabelPrimary); + annotation.label.as_ref().unwrap(), Style::LabelPrimary); } else { styled_buffer.puts(blank_lines, annotation.start_col, - annotation.label.as_ref().unwrap(), LabelSecondary); + annotation.label.as_ref().unwrap(), Style::LabelSecondary); } } @@ -752,7 +751,7 @@ fn prepend_prefixes(rendered_lines: &mut [RenderedLine]) { .chain(Some('>')) .chain(Some(' ')); line.text.insert(0, StyledString {text: dashes.collect(), - style: LineNumber}) + style: Style::LineNumber}) } RenderedLineKind::OtherFileName => { // >>>>> filename @@ -762,12 +761,12 @@ fn prepend_prefixes(rendered_lines: &mut [RenderedLine]) { let dashes = (0..padding_len + 2).map(|_| '>') .chain(Some(' ')); line.text.insert(0, StyledString {text: dashes.collect(), - style: LineNumber}) + style: Style::LineNumber}) } _ => { line.text.insert(0, prefix); line.text.insert(1, StyledString {text: String::from("|> "), - style: LineNumber}) + style: Style::LineNumber}) } } } -- cgit 1.4.1-3-g733a5 From d5529f000da43fe2e9f2aad03f747b0144da9354 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 26 Apr 2016 12:53:20 -0400 Subject: Nit: do not use RLK --- src/libsyntax/errors/snippet/mod.rs | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) (limited to 'src/libsyntax/errors') diff --git a/src/libsyntax/errors/snippet/mod.rs b/src/libsyntax/errors/snippet/mod.rs index f86d4bdb147..4d9a3eb2486 100644 --- a/src/libsyntax/errors/snippet/mod.rs +++ b/src/libsyntax/errors/snippet/mod.rs @@ -102,7 +102,6 @@ pub enum RenderedLineKind { Annotations, Elision, } -use self::RenderedLineKind as RLK; impl SnippetData { pub fn new(codemap: Rc, @@ -243,19 +242,19 @@ impl RenderedLine { impl RenderedLineKind { fn prefix(&self) -> StyledString { match *self { - RLK::SourceText { file: _, line_index } => + RenderedLineKind::SourceText { file: _, line_index } => StyledString { text: format!("{}", line_index + 1), style: Style::LineNumber, }, - RLK::Elision => + RenderedLineKind::Elision => StyledString { text: String::from("..."), style: Style::LineNumber, }, - RLK::PrimaryFileName | - RLK::OtherFileName | - RLK::Annotations => + RenderedLineKind::PrimaryFileName | + RenderedLineKind::OtherFileName | + RenderedLineKind::Annotations => StyledString { text: String::from(""), style: Style::LineNumber, @@ -295,7 +294,7 @@ impl StyledBuffer { //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: RLK::Annotations }); + output.push(RenderedLine { text: styled_vec, kind: RenderedLineKind::Annotations }); } styled_vec = vec![]; } @@ -483,7 +482,7 @@ impl FileInfo { text: format!(":{}:{}", lo.line, lo.col.0 + 1), style: Style::LineAndColumn, }], - kind: RLK::PrimaryFileName, + kind: RenderedLineKind::PrimaryFileName, }); } None => { @@ -492,7 +491,7 @@ impl FileInfo { text: self.file.name.clone(), style: Style::FileNameStyle, }], - kind: RLK::OtherFileName, + kind: RenderedLineKind::OtherFileName, }); } } @@ -533,7 +532,8 @@ impl FileInfo { if prev_ends_at_eol && is_single_unlabeled_annotated_line { if !elide_unlabeled_region { output.push(RenderedLine::from((String::new(), - Style::NoStyle, RLK::Elision))); + Style::NoStyle, + RenderedLineKind::Elision))); elide_unlabeled_region = true; prev_ends_at_eol = true; } @@ -547,7 +547,9 @@ impl FileInfo { } } else { if group.len() > 1 { - output.push(RenderedLine::from((String::new(), Style::NoStyle, RLK::Elision))); + output.push(RenderedLine::from((String::new(), + Style::NoStyle, + RenderedLineKind::Elision))); } else { let mut v: Vec = group.iter().flat_map(|line| self.render_line(line)).collect(); @@ -562,7 +564,7 @@ impl FileInfo { fn render_line(&self, line: &Line) -> Vec { let source_string = self.file.get_line(line.line_index) .unwrap_or(""); - let source_kind = RLK::SourceText { + let source_kind = RenderedLineKind::SourceText { file: self.file.clone(), line_index: line.line_index, }; -- cgit 1.4.1-3-g733a5 From f6496cd3700a9a4e3dc1a6d3245287066f4b99e4 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 26 Apr 2016 13:06:28 -0400 Subject: Nit: address various style nits --- src/libsyntax/errors/emitter.rs | 7 ++++++- src/libsyntax/errors/snippet/mod.rs | 4 +--- src/libsyntax/errors/snippet/test.rs | 4 +++- 3 files changed, 10 insertions(+), 5 deletions(-) (limited to 'src/libsyntax/errors') diff --git a/src/libsyntax/errors/emitter.rs b/src/libsyntax/errors/emitter.rs index 2b29de7fd71..7cad1714625 100644 --- a/src/libsyntax/errors/emitter.rs +++ b/src/libsyntax/errors/emitter.rs @@ -131,6 +131,9 @@ pub struct EmitterWriter { dst: Destination, registry: Option, cm: Rc, + + /// Is this the first error emitted thus far? If not, we emit a + /// `\n` before the top-level errors. first: bool, } @@ -172,7 +175,9 @@ impl EmitterWriter { EmitterWriter { dst: dst, registry: registry, cm: code_map, first: true } } else { EmitterWriter { dst: Raw(Box::new(io::stderr())), - registry: registry, cm: code_map, first: true } + registry: registry, + cm: code_map, + first: true } } } diff --git a/src/libsyntax/errors/snippet/mod.rs b/src/libsyntax/errors/snippet/mod.rs index 4d9a3eb2486..ada336b29a4 100644 --- a/src/libsyntax/errors/snippet/mod.rs +++ b/src/libsyntax/errors/snippet/mod.rs @@ -16,7 +16,6 @@ use std::rc::Rc; use std::mem; use std::ops::Range; -#[cfg(test)] mod test; pub struct SnippetData { @@ -210,8 +209,7 @@ impl From<(S, Style, RenderedLineKind)> for RenderedLine impl From<(S1, Style, S2, Style, RenderedLineKind)> for RenderedLine where S1: StringSource, S2: StringSource { - fn from(tuple: (S1, Style, S2, Style, RenderedLineKind)) - -> Self { + fn from(tuple: (S1, Style, S2, Style, RenderedLineKind)) -> Self { let (text1, style1, text2, style2, kind) = tuple; RenderedLine { text: vec![ diff --git a/src/libsyntax/errors/snippet/test.rs b/src/libsyntax/errors/snippet/test.rs index 44ece285b1b..ccf50536adb 100644 --- a/src/libsyntax/errors/snippet/test.rs +++ b/src/libsyntax/errors/snippet/test.rs @@ -1,4 +1,4 @@ -// Copyright 2012-2015 The Rust Project Developers. See the COPYRIGHT +// 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. // @@ -10,6 +10,8 @@ // Code for testing annotated snippets. +#![cfg(test)] + use codemap::{BytePos, CodeMap, FileMap, NO_EXPANSION, Span}; use std::rc::Rc; use super::{RenderedLine, SnippetData}; -- cgit 1.4.1-3-g733a5 From 94841bea7b5753ba29655ce60a99f329e8eb8f24 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 26 Apr 2016 13:36:19 -0400 Subject: Nit: in emitter.rs --- src/libsyntax/errors/emitter.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src/libsyntax/errors') diff --git a/src/libsyntax/errors/emitter.rs b/src/libsyntax/errors/emitter.rs index 7cad1714625..07dafb0b7df 100644 --- a/src/libsyntax/errors/emitter.rs +++ b/src/libsyntax/errors/emitter.rs @@ -397,8 +397,7 @@ impl Destination { style: Style) -> io::Result<()> { match style { - Style::FileNameStyle => { - } + Style::FileNameStyle | Style::LineAndColumn => { } Style::LineNumber => { -- cgit 1.4.1-3-g733a5 From 24f4b151b11b22b66ac0128f76c1e12cca45b178 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 26 Apr 2016 13:36:30 -0400 Subject: Nit: use last_mut better --- src/libsyntax/errors/snippet/mod.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'src/libsyntax/errors') diff --git a/src/libsyntax/errors/snippet/mod.rs b/src/libsyntax/errors/snippet/mod.rs index ada336b29a4..0c8b4f2046a 100644 --- a/src/libsyntax/errors/snippet/mod.rs +++ b/src/libsyntax/errors/snippet/mod.rs @@ -229,10 +229,9 @@ impl From<(S1, Style, S2, Style, RenderedLineKind)> for RenderedLine impl RenderedLine { fn trim_last(&mut self) { - if !self.text.is_empty() { - let last_text = &mut self.text.last_mut().unwrap().text; - let len = last_text.trim_right().len(); - last_text.truncate(len); + if let Some(last_text) = self.text.last_mut() { + let len = last_text.text.trim_right().len(); + last_text.text.truncate(len); } } } -- cgit 1.4.1-3-g733a5 From 1fdbfcdbd0a6a63317872fef24222533bbc8cfaf Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 26 Apr 2016 09:33:38 -0700 Subject: only emit `^` at the start of a multi-line error as a result, simplify elision code --- src/libsyntax/errors/emitter.rs | 15 +-- src/libsyntax/errors/snippet/mod.rs | 184 +++++++++++------------------------ src/libsyntax/errors/snippet/test.rs | 11 +-- 3 files changed, 67 insertions(+), 143 deletions(-) (limited to 'src/libsyntax/errors') diff --git a/src/libsyntax/errors/emitter.rs b/src/libsyntax/errors/emitter.rs index 07dafb0b7df..eaa973db2b8 100644 --- a/src/libsyntax/errors/emitter.rs +++ b/src/libsyntax/errors/emitter.rs @@ -543,7 +543,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; @@ -555,12 +555,9 @@ mod test { let str = from_utf8(vec).unwrap(); println!("r#\"\n{}\"#", str); assert_eq!(str, &r#" - --> dummy.txt:8:1 -8 |> line8 - |> ^^^^^^^^^^^^^ -... + --> dummy.txt:11:1 11 |> e-lä-vän - |> ^^^^^^^^^^^^^^^^ + |> ^ "#[1..]); } @@ -696,9 +693,8 @@ mod test { let expect0 = &r#" --> dummy.txt:5:1 5 |> ccccc - |> ^^^^^ + |> ^ ... -8 |> _____ 9 |> ddd__eee_ |> ^^^ ^^^ 10 |> elided @@ -709,9 +705,8 @@ mod test { let expect = &r#" --> dummy.txt:1:1 1 |> aaaaa - |> ^^^^^ + |> ^ ... -8 |> _____ 9 |> ddd__eee_ |> ^^^ ^^^ 10 |> elided diff --git a/src/libsyntax/errors/snippet/mod.rs b/src/libsyntax/errors/snippet/mod.rs index 0c8b4f2046a..643b5c3c5f2 100644 --- a/src/libsyntax/errors/snippet/mod.rs +++ b/src/libsyntax/errors/snippet/mod.rs @@ -49,7 +49,7 @@ struct Annotation { /// column. start_col: usize, - /// End column within the line. + /// End column within the line (exclusive) end_col: usize, /// Is this annotation derived from primary span @@ -349,24 +349,40 @@ impl FileInfo { label: Option) { assert!(lines.len() > 0); - // If a span covers multiple lines, just put the label on the - // first one. This is a sort of arbitrary choice and not - // obviously correct. - let (line0, remaining_lines) = lines.split_first().unwrap(); - let index = self.ensure_source_line(line0.line_index); - self.lines[index].push_annotation(line0.start_col, - line0.end_col, + // 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> + // |> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + // (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); - for line in remaining_lines { - if line.end_col > line.start_col { - let index = self.ensure_source_line(line.line_index); - self.lines[index].push_annotation(line.start_col, - line.end_col, - is_primary, - None); - } - } } /// Ensure that we have a `Line` struct corresponding to @@ -414,57 +430,10 @@ impl FileInfo { } fn render_file_lines(&self, codemap: &Rc) -> Vec { - // Group our lines by those with annotations and those without - let mut lines_iter = self.lines.iter().peekable(); - - let mut line_groups = vec![]; - - loop { - match lines_iter.next() { - None => break, - Some(line) if line.annotations.is_empty() => { - // Collect unannotated group - let mut unannotated_group : Vec<&Line> = vec![]; - - unannotated_group.push(line); - - loop { - let next_line = - match lines_iter.peek() { - None => break, - Some(x) if !x.annotations.is_empty() => break, - Some(x) => x.clone() - }; - - unannotated_group.push(next_line); - lines_iter.next(); - } - - line_groups.push((false, unannotated_group)); - } - Some(line) => { - // Collect annotated group - let mut annotated_group : Vec<&Line> = vec![]; - - annotated_group.push(line); - - loop { - let next_line = - match lines_iter.peek() { - None => break, - Some(x) if x.annotations.is_empty() => break, - Some(x) => x.clone() - }; - - annotated_group.push(next_line); - lines_iter.next(); - } - - line_groups.push((true, annotated_group)); - } - } - } + // 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. @@ -493,65 +462,30 @@ impl FileInfo { } } - for &(is_annotated, ref group) in line_groups.iter() { - if is_annotated { - let mut annotation_ends_at_eol = false; - let mut prev_ends_at_eol = false; - let mut elide_unlabeled_region = false; - - for group_line in group.iter() { - let source_string_len = - self.file.get_line(group_line.line_index) - .map(|s| s.len()) - .unwrap_or(0); - - for annotation in &group_line.annotations { - if annotation.end_col == source_string_len { - annotation_ends_at_eol = true; - } - } - - let is_single_unlabeled_annotated_line = - if group_line.annotations.len() == 1 { - if let Some(annotation) = group_line.annotations.first() { - match annotation.label { - Some(_) => false, - None => annotation.start_col == 0 && - annotation.end_col == source_string_len - } - } else { - false - } - } else { - false - }; - - if prev_ends_at_eol && is_single_unlabeled_annotated_line { - if !elide_unlabeled_region { - output.push(RenderedLine::from((String::new(), - Style::NoStyle, - RenderedLineKind::Elision))); - elide_unlabeled_region = true; - prev_ends_at_eol = true; - } - continue; - } - - let mut v = self.render_line(group_line); - output.append(&mut v); + 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; } + output.append(&mut self.render_line(line)); + next_line = lines_iter.next(); + } - prev_ends_at_eol = annotation_ends_at_eol; - } - } else { - if group.len() > 1 { - output.push(RenderedLine::from((String::new(), - Style::NoStyle, - RenderedLineKind::Elision))); - } else { - let mut v: Vec = - group.iter().flat_map(|line| self.render_line(line)).collect(); - output.append(&mut v); - } + // 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)); } } diff --git a/src/libsyntax/errors/snippet/test.rs b/src/libsyntax/errors/snippet/test.rs index ccf50536adb..d995d828bc7 100644 --- a/src/libsyntax/errors/snippet/test.rs +++ b/src/libsyntax/errors/snippet/test.rs @@ -406,8 +406,7 @@ impl SomeTrait for () { assert_eq!(text, &r#" >>>>>> foo.rs 3 |> fn foo(x: u32) { - |> ---------------- -... + |> - "#[1..]); } @@ -515,12 +514,8 @@ fn span_overlap_label3() { assert_eq!(text, &r#" >>>> foo.rs 3 |> let closure = || { - |> ---- foo + |> - foo 4 |> inner - |> ---------------- - |> | - |> bar -5 |> }; - |> -------- + |> ---- bar "#[1..]); } -- cgit 1.4.1-3-g733a5 From ba12ed06edf119c1d543158b8d0ac7d7ec503d82 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 26 Apr 2016 22:52:56 -0400 Subject: fix tests better --- src/libsyntax/errors/emitter.rs | 4 ++-- src/libsyntax/errors/snippet/test.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'src/libsyntax/errors') diff --git a/src/libsyntax/errors/emitter.rs b/src/libsyntax/errors/emitter.rs index eaa973db2b8..cea6dbb75d9 100644 --- a/src/libsyntax/errors/emitter.rs +++ b/src/libsyntax/errors/emitter.rs @@ -556,8 +556,8 @@ mod test { println!("r#\"\n{}\"#", str); assert_eq!(str, &r#" --> dummy.txt:11:1 -11 |> e-lä-vän - |> ^ +11 |> e-lä-vän + |> ^ "#[1..]); } diff --git a/src/libsyntax/errors/snippet/test.rs b/src/libsyntax/errors/snippet/test.rs index d995d828bc7..286a3e3d407 100644 --- a/src/libsyntax/errors/snippet/test.rs +++ b/src/libsyntax/errors/snippet/test.rs @@ -405,8 +405,8 @@ impl SomeTrait for () { println!("r#\"\n{}\"", text); assert_eq!(text, &r#" >>>>>> foo.rs -3 |> fn foo(x: u32) { - |> - +3 |> fn foo(x: u32) { + |> - "#[1..]); } @@ -516,6 +516,6 @@ fn span_overlap_label3() { 3 |> let closure = || { |> - foo 4 |> inner - |> ---- bar + |> ----- bar "#[1..]); } -- cgit 1.4.1-3-g733a5 From 8a9ad72c1d67261049aac1d067529da48adcc644 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 26 Apr 2016 22:59:15 -0400 Subject: Nit: use Range::contains --- src/libsyntax/errors/snippet/mod.rs | 9 ++------- src/libsyntax/lib.rs | 1 + 2 files changed, 3 insertions(+), 7 deletions(-) (limited to 'src/libsyntax/errors') diff --git a/src/libsyntax/errors/snippet/mod.rs b/src/libsyntax/errors/snippet/mod.rs index 643b5c3c5f2..feaf48352db 100644 --- a/src/libsyntax/errors/snippet/mod.rs +++ b/src/libsyntax/errors/snippet/mod.rs @@ -14,7 +14,6 @@ use codemap::{CharPos, CodeMap, FileMap, LineInfo, Span}; use std::cmp; use std::rc::Rc; use std::mem; -use std::ops::Range; mod test; @@ -744,10 +743,6 @@ fn overlaps(a1: &Annotation, a2: &Annotation) -> bool { - between(a1.start_col, a2.start_col .. a2.end_col) || - between(a2.start_col, a1.start_col .. a1.end_col) -} - -fn between(v: usize, range: Range) -> bool { - v >= range.start && v < range.end + (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/lib.rs b/src/libsyntax/lib.rs index 6cfa1e9847b..420a41e03b9 100644 --- a/src/libsyntax/lib.rs +++ b/src/libsyntax/lib.rs @@ -33,6 +33,7 @@ #![feature(str_escape)] #![feature(unicode)] #![feature(question_mark)] +#![feature(range_contains)] extern crate serialize; extern crate term; -- cgit 1.4.1-3-g733a5 From 790043b44e0c078c0f80b16cd03d1aeac6ef242b Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Wed, 27 Apr 2016 10:45:35 -0400 Subject: fix snippet tests MORE! --- src/libsyntax/errors/emitter.rs | 2 +- src/libsyntax/errors/snippet/test.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src/libsyntax/errors') diff --git a/src/libsyntax/errors/emitter.rs b/src/libsyntax/errors/emitter.rs index cea6dbb75d9..b5be0fa16dd 100644 --- a/src/libsyntax/errors/emitter.rs +++ b/src/libsyntax/errors/emitter.rs @@ -555,7 +555,7 @@ mod test { let str = from_utf8(vec).unwrap(); println!("r#\"\n{}\"#", str); assert_eq!(str, &r#" - --> dummy.txt:11:1 + --> dummy.txt:11:1 11 |> e-lä-vän |> ^ "#[1..]); diff --git a/src/libsyntax/errors/snippet/test.rs b/src/libsyntax/errors/snippet/test.rs index 286a3e3d407..56c891daa12 100644 --- a/src/libsyntax/errors/snippet/test.rs +++ b/src/libsyntax/errors/snippet/test.rs @@ -404,7 +404,7 @@ impl SomeTrait for () { let text: String = make_string(&lines); println!("r#\"\n{}\"", text); assert_eq!(text, &r#" ->>>>>> foo.rs +>>>> foo.rs 3 |> fn foo(x: u32) { |> - "#[1..]); -- cgit 1.4.1-3-g733a5 From 89d086be74d5ddf21d67f2a3c27a29cca2631bba Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Wed, 27 Apr 2016 19:51:12 -0400 Subject: change color of warning to YELLOW --- src/libsyntax/errors/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/libsyntax/errors') diff --git a/src/libsyntax/errors/mod.rs b/src/libsyntax/errors/mod.rs index d533ffb981a..feac8aadc1e 100644 --- a/src/libsyntax/errors/mod.rs +++ b/src/libsyntax/errors/mod.rs @@ -656,7 +656,7 @@ impl Level { fn color(self) -> term::color::Color { match self { Bug | Fatal | PhaseFatal | Error => term::color::BRIGHT_RED, - Warning => term::color::BRIGHT_MAGENTA, + Warning => term::color::YELLOW, Note => term::color::BRIGHT_GREEN, Help => term::color::BRIGHT_CYAN, Cancelled => unreachable!(), -- cgit 1.4.1-3-g733a5 From 84cb56f8ee11ba89914462e478f06e9c1e8e7971 Mon Sep 17 00:00:00 2001 From: Jonathan Turner Date: Thu, 28 Apr 2016 16:39:59 -0700 Subject: Add back in a 'old school' error format --- src/libsyntax/errors/emitter.rs | 116 ++++++++++++++++++++++++++----- src/libsyntax/errors/snippet/mod.rs | 135 +++++++++++++++++++++++++++--------- 2 files changed, 201 insertions(+), 50 deletions(-) (limited to 'src/libsyntax/errors') diff --git a/src/libsyntax/errors/emitter.rs b/src/libsyntax/errors/emitter.rs index b5be0fa16dd..7f4d1a9dc34 100644 --- a/src/libsyntax/errors/emitter.rs +++ b/src/libsyntax/errors/emitter.rs @@ -135,6 +135,9 @@ pub struct EmitterWriter { /// Is this the first error emitted thus far? If not, we emit a /// `\n` before the top-level errors. first: bool, + + // For now, allow an old-school mode while we transition + old_school: bool, } impl CoreEmitter for EmitterWriter { @@ -170,14 +173,23 @@ impl EmitterWriter { registry: Option, code_map: Rc) -> EmitterWriter { + let old_school = match ::std::env::var("RUST_NEW_ERROR_FORMAT") { + Ok(_) => false, + Err(_) => true, + }; if color_config.use_color() { let dst = Destination::from_stderr(); - EmitterWriter { dst: dst, registry: registry, cm: code_map, first: true } + 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, - first: true } + first: true, + old_school: old_school } } } @@ -185,7 +197,15 @@ impl EmitterWriter { registry: Option, code_map: Rc) -> EmitterWriter { - EmitterWriter { dst: Raw(dst), registry: registry, cm: code_map, first: true } + let old_school = match ::std::env::var("RUST_NEW_ERROR_FORMAT") { + Ok(_) => false, + Err(_) => true, + }; + EmitterWriter { dst: Raw(dst), + registry: registry, + cm: code_map, + first: true, + old_school: old_school } } fn emit_message_(&mut self, @@ -199,7 +219,9 @@ impl EmitterWriter { if self.first { self.first = false; } else { - write!(self.dst, "\n")?; + if !self.old_school { + write!(self.dst, "\n")?; + } } } @@ -208,7 +230,17 @@ impl EmitterWriter { .and_then(|registry| registry.find_description(code)) .is_some() => { let code_with_explain = String::from("--explain ") + code; - 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, Some(code))? + } + else { + print_diagnostic(&mut self.dst, "", lvl, msg, Some(&code_with_explain))? + } } _ => { print_diagnostic(&mut self.dst, "", lvl, msg, code)? @@ -239,7 +271,24 @@ impl EmitterWriter { } } } - + 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(()) } @@ -282,19 +331,48 @@ impl EmitterWriter { { let mut snippet_data = SnippetData::new(self.cm.clone(), msp.primary_span()); - for span_label in msp.span_labels() { - snippet_data.push(span_label.span, - span_label.is_primary, - span_label.label); + 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); + } + else { + output_vec.push(snippet_data); + } + } + + 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")?; + } + } } - 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()?; + else { + for span_label in msp.span_labels() { + snippet_data.push(span_label.span, + span_label.is_primary, + span_label.label); + } + 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")?; } - write!(&mut self.dst, "\n")?; } Ok(()) } @@ -327,7 +405,6 @@ fn line_num_max_digits(line: &codemap::LineInfo) -> usize { digits } - fn print_diagnostic(dst: &mut Destination, topic: &str, lvl: Level, @@ -335,7 +412,6 @@ fn print_diagnostic(dst: &mut Destination, code: Option<&str>) -> io::Result<()> { if !topic.is_empty() { - dst.start_attr(term::Attr::ForegroundColor(lvl.color()))?; write!(dst, "{}: ", topic)?; dst.reset_attrs()?; } @@ -346,10 +422,12 @@ fn print_diagnostic(dst: &mut Destination, 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(()) diff --git a/src/libsyntax/errors/snippet/mod.rs b/src/libsyntax/errors/snippet/mod.rs index feaf48352db..18a64cc399c 100644 --- a/src/libsyntax/errors/snippet/mod.rs +++ b/src/libsyntax/errors/snippet/mod.rs @@ -17,11 +17,13 @@ use std::mem; mod test; +#[derive(Clone)] pub struct SnippetData { codemap: Rc, files: Vec, } +#[derive(Clone)] pub struct FileInfo { file: Rc, @@ -35,6 +37,7 @@ pub struct FileInfo { lines: Vec, } +#[derive(Clone)] struct Line { line_index: usize, annotations: Vec, @@ -429,6 +432,10 @@ impl FileInfo { } fn render_file_lines(&self, codemap: &Rc) -> Vec { + let old_school = match ::std::env::var("RUST_NEW_ERROR_FORMAT") { + Ok(_) => false, + Err(_) => true, + }; // As a first step, we elide any instance of more than one // continuous unannotated line. @@ -436,28 +443,30 @@ impl FileInfo { let mut output = vec![]; // First insert the name of the file. - 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, - }); + 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, + }); + } } } @@ -466,7 +475,31 @@ impl FileInfo { // Consume lines with annotations. while let Some(line) = next_line { if line.annotations.is_empty() { break; } - output.append(&mut self.render_line(line)); + + let mut rendered_line = self.render_line(line); + if old_school { + match self.primary_span { + Some(span) => { + let lo = codemap.lookup_char_pos(span.lo); + rendered_line[0].text.insert(0, StyledString { + text: format!(":{} ", lo.line), + style: Style::LineAndColumn, + }); + rendered_line[0].text.insert(0, StyledString { + text: lo.file.name.clone(), + style: Style::FileNameStyle, + }); + let gap_amount = rendered_line[0].text[0].text.len() + + rendered_line[0].text[1].text.len(); + rendered_line[1].text.insert(0, StyledString { + text: vec![" "; gap_amount].join(""), + style: Style::NoStyle + }); + } + _ =>() + } + } + output.append(&mut rendered_line); next_line = lines_iter.next(); } @@ -492,6 +525,10 @@ impl FileInfo { } fn render_line(&self, line: &Line) -> Vec { + let old_school = match ::std::env::var("RUST_NEW_ERROR_FORMAT") { + Ok(_) => false, + Err(_) => true, + }; let source_string = self.file.get_line(line.line_index) .unwrap_or(""); let source_kind = RenderedLineKind::SourceText { @@ -535,12 +572,34 @@ impl FileInfo { // Next, create the highlight line. for annotation in &annotations { - 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); + 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); + } } } } @@ -555,6 +614,9 @@ impl FileInfo { 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: @@ -647,6 +709,14 @@ impl FileInfo { } fn prepend_prefixes(rendered_lines: &mut [RenderedLine]) { + let old_school = match ::std::env::var("RUST_NEW_ERROR_FORMAT") { + Ok(_) => false, + Err(_) => true, + }; + if old_school { + return; + } + let prefixes: Vec<_> = rendered_lines.iter() .map(|rl| rl.kind.prefix()) @@ -686,11 +756,14 @@ fn prepend_prefixes(rendered_lines: &mut [RenderedLine]) { style: Style::LineNumber}) } RenderedLineKind::OtherFileName => { - // >>>>> filename + // ::: filename // 22 |> // ^ // padding_len - let dashes = (0..padding_len + 2).map(|_| '>') + 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}) -- cgit 1.4.1-3-g733a5 From 79f61a45328e534e52cf705452b33ae4b8ae474d Mon Sep 17 00:00:00 2001 From: Jonathan Turner Date: Fri, 29 Apr 2016 13:27:40 -0700 Subject: Finish up with 'old school' error mode --- src/librustc_trans/back/write.rs | 3 +- src/libsyntax/errors/emitter.rs | 75 +++++++++++++++++++++++++++++++++------- 2 files changed, 65 insertions(+), 13 deletions(-) (limited to 'src/libsyntax/errors') diff --git a/src/librustc_trans/back/write.rs b/src/librustc_trans/back/write.rs index a35048f89c1..10bcf83d755 100644 --- a/src/librustc_trans/back/write.rs +++ b/src/librustc_trans/back/write.rs @@ -106,7 +106,8 @@ impl CoreEmitter for SharedEmitter { msg: &str, code: Option<&str>, lvl: Level, - _is_header: bool) { + _is_header: bool, + _show_snippet: bool) { self.buffer.lock().unwrap().push(Diagnostic { msg: msg.to_string(), code: code.map(|s| s.to_string()), diff --git a/src/libsyntax/errors/emitter.rs b/src/libsyntax/errors/emitter.rs index 7f4d1a9dc34..769f6b05397 100644 --- a/src/libsyntax/errors/emitter.rs +++ b/src/libsyntax/errors/emitter.rs @@ -40,7 +40,8 @@ pub trait CoreEmitter { msg: &str, code: Option<&str>, lvl: Level, - is_header: bool); + is_header: bool, + show_snippet: bool); } impl Emitter for T { @@ -53,25 +54,47 @@ impl Emitter for T { msg, code, lvl, + true, true); } fn emit_struct(&mut self, db: &DiagnosticBuilder) { + let old_school = match ::std::env::var("RUST_NEW_ERROR_FORMAT") { + Ok(_) => false, + Err(_) => true, + }; + 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 { let render_span = child.render_span .clone() .unwrap_or_else( || FullSpan(child.span.clone())); - self.emit_message(&render_span, - &child.message, - None, - child.level, - false); + + 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); + } } } } @@ -108,7 +131,8 @@ impl CoreEmitter for BasicEmitter { msg: &str, code: Option<&str>, lvl: Level, - _is_header: bool) { + _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); @@ -146,8 +170,9 @@ impl CoreEmitter for EmitterWriter { msg: &str, code: Option<&str>, lvl: Level, - is_header: bool) { - match self.emit_message_(rsp, msg, code, lvl, is_header) { + 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) } @@ -213,7 +238,8 @@ impl EmitterWriter { msg: &str, code: Option<&str>, lvl: Level, - is_header: bool) + is_header: bool, + show_snippet: bool) -> io::Result<()> { if is_header { if self.first { @@ -243,10 +269,24 @@ impl EmitterWriter { } } _ => { - print_diagnostic(&mut self.dst, "", lvl, msg, 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, code)? + } + else { + print_diagnostic(&mut self.dst, "", 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() { @@ -333,8 +373,10 @@ impl EmitterWriter { 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); @@ -412,7 +454,16 @@ fn print_diagnostic(dst: &mut Destination, code: Option<&str>) -> io::Result<()> { if !topic.is_empty() { - write!(dst, "{}: ", topic)?; + let old_school = match ::std::env::var("RUST_NEW_ERROR_FORMAT") { + Ok(_) => false, + Err(_) => true, + }; + if !old_school { + write!(dst, "{}: ", topic)?; + } + else { + write!(dst, "{} ", topic)?; + } dst.reset_attrs()?; } dst.start_attr(term::Attr::Bold)?; -- cgit 1.4.1-3-g733a5 From 95576b8ec40538fc311029dd838d2a22c0e9af7f Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Fri, 29 Apr 2016 21:13:42 -0400 Subject: update unit tests --- src/libsyntax/errors/emitter.rs | 21 +++++---------------- src/libsyntax/errors/mod.rs | 17 +++++++++++++++++ src/libsyntax/errors/snippet/mod.rs | 17 +++++------------ src/libsyntax/errors/snippet/test.rs | 22 +++++++++++----------- 4 files changed, 38 insertions(+), 39 deletions(-) (limited to 'src/libsyntax/errors') diff --git a/src/libsyntax/errors/emitter.rs b/src/libsyntax/errors/emitter.rs index 769f6b05397..486e2ace087 100644 --- a/src/libsyntax/errors/emitter.rs +++ b/src/libsyntax/errors/emitter.rs @@ -13,6 +13,7 @@ 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::*; @@ -59,10 +60,7 @@ impl Emitter for T { } fn emit_struct(&mut self, db: &DiagnosticBuilder) { - let old_school = match ::std::env::var("RUST_NEW_ERROR_FORMAT") { - Ok(_) => false, - Err(_) => true, - }; + let old_school = check_old_skool(); let db_span = FullSpan(db.span.clone()); self.emit_message(&FullSpan(db.span.clone()), &db.message, @@ -198,10 +196,7 @@ impl EmitterWriter { registry: Option, code_map: Rc) -> EmitterWriter { - let old_school = match ::std::env::var("RUST_NEW_ERROR_FORMAT") { - Ok(_) => false, - Err(_) => true, - }; + let old_school = check_old_skool(); if color_config.use_color() { let dst = Destination::from_stderr(); EmitterWriter { dst: dst, @@ -222,10 +217,7 @@ impl EmitterWriter { registry: Option, code_map: Rc) -> EmitterWriter { - let old_school = match ::std::env::var("RUST_NEW_ERROR_FORMAT") { - Ok(_) => false, - Err(_) => true, - }; + let old_school = check_old_skool(); EmitterWriter { dst: Raw(dst), registry: registry, cm: code_map, @@ -454,10 +446,7 @@ fn print_diagnostic(dst: &mut Destination, code: Option<&str>) -> io::Result<()> { if !topic.is_empty() { - let old_school = match ::std::env::var("RUST_NEW_ERROR_FORMAT") { - Ok(_) => false, - Err(_) => true, - }; + let old_school = check_old_skool(); if !old_school { write!(dst, "{}: ", topic)?; } diff --git a/src/libsyntax/errors/mod.rs b/src/libsyntax/errors/mod.rs index feac8aadc1e..4ade537c8ce 100644 --- a/src/libsyntax/errors/mod.rs +++ b/src/libsyntax/errors/mod.rs @@ -683,3 +683,20 @@ pub fn expect(diag: &Handler, opt: Option, 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 index 18a64cc399c..6c90bfd0818 100644 --- a/src/libsyntax/errors/snippet/mod.rs +++ b/src/libsyntax/errors/snippet/mod.rs @@ -11,6 +11,7 @@ // 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; @@ -432,10 +433,8 @@ impl FileInfo { } fn render_file_lines(&self, codemap: &Rc) -> Vec { - let old_school = match ::std::env::var("RUST_NEW_ERROR_FORMAT") { - Ok(_) => false, - Err(_) => true, - }; + let old_school = check_old_skool(); + // As a first step, we elide any instance of more than one // continuous unannotated line. @@ -525,10 +524,7 @@ impl FileInfo { } fn render_line(&self, line: &Line) -> Vec { - let old_school = match ::std::env::var("RUST_NEW_ERROR_FORMAT") { - Ok(_) => false, - Err(_) => true, - }; + let old_school = check_old_skool(); let source_string = self.file.get_line(line.line_index) .unwrap_or(""); let source_kind = RenderedLineKind::SourceText { @@ -709,10 +705,7 @@ impl FileInfo { } fn prepend_prefixes(rendered_lines: &mut [RenderedLine]) { - let old_school = match ::std::env::var("RUST_NEW_ERROR_FORMAT") { - Ok(_) => false, - Err(_) => true, - }; + let old_school = check_old_skool(); if old_school { return; } diff --git a/src/libsyntax/errors/snippet/test.rs b/src/libsyntax/errors/snippet/test.rs index 56c891daa12..569d1119919 100644 --- a/src/libsyntax/errors/snippet/test.rs +++ b/src/libsyntax/errors/snippet/test.rs @@ -105,7 +105,7 @@ fn foo() { println!("text=\n{}", text); assert_eq!(&text[..], &r#" ->>>> foo.rs + ::: foo.rs 3 |> vec.push(vec.pop().unwrap()); |> --- --- - previous borrow ends here |> | | @@ -180,7 +180,7 @@ fn bar() { |> | | |> | b |> a ->>>>>> bar.rs + ::: bar.rs 17 |> vec.push(); |> --- - f |> | @@ -224,7 +224,7 @@ fn foo() { println!("text=\n{}", text); assert_eq!(&text[..], &r#" ->>>>>> foo.rs + ::: foo.rs 3 |> let name = find_id(&data, 22).unwrap(); |> ---- immutable borrow begins here ... @@ -263,7 +263,7 @@ fn foo() { println!("text=r#\"\n{}\".trim_left()", text); assert_eq!(&text[..], &r#" ->>>> foo.rs + ::: foo.rs 3 |> vec.push(vec.pop().unwrap()); |> -------- ------ D |> || @@ -299,7 +299,7 @@ fn foo() { println!("text=r#\"\n{}\".trim_left()", text); assert_eq!(&text[..], &r#" ->>>> foo.rs + ::: foo.rs 3 |> vec.push(vec.pop().unwrap()); |> --- --- - previous borrow ends here |> | | @@ -337,7 +337,7 @@ fn foo() { let text: String = make_string(&lines); println!("text=r#\"\n{}\".trim_left()", text); assert_eq!(&text[..], &r#" ->>>>>> foo.rs + ::: foo.rs 4 |> let mut vec2 = vec; |> --- `vec` moved here because it has type `collections::vec::Vec` ... @@ -373,7 +373,7 @@ fn foo() { let text: String = make_string(&lines); println!("text=&r#\"\n{}\n\"#[1..]", text); assert_eq!(text, &r#" ->>>> foo.rs + ::: foo.rs 3 |> let mut vec = vec![0, 1, 2]; |> --- --- 4 |> let mut vec2 = vec; @@ -404,7 +404,7 @@ impl SomeTrait for () { let text: String = make_string(&lines); println!("r#\"\n{}\"", text); assert_eq!(text, &r#" ->>>> foo.rs + ::: foo.rs 3 |> fn foo(x: u32) { |> - "#[1..]); @@ -433,7 +433,7 @@ fn span_overlap_label() { let text: String = make_string(&lines); println!("r#\"\n{}\"", text); assert_eq!(text, &r#" ->>>> foo.rs + ::: foo.rs 2 |> fn foo(x: u32) { |> -------------- |> | | @@ -467,7 +467,7 @@ fn span_overlap_label2() { let text: String = make_string(&lines); println!("r#\"\n{}\"", text); assert_eq!(text, &r#" ->>>> foo.rs + ::: foo.rs 2 |> fn foo(x: u32) { |> -------------- |> | | @@ -512,7 +512,7 @@ fn span_overlap_label3() { let text: String = make_string(&lines); println!("r#\"\n{}\"", text); assert_eq!(text, &r#" ->>>> foo.rs + ::: foo.rs 3 |> let closure = || { |> - foo 4 |> inner -- cgit 1.4.1-3-g733a5 From 9d151a71c032b655ca457521730044237c9e130e Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Mon, 2 May 2016 11:37:59 -0400 Subject: do not fail if len(rendered_lines) is == 1 also handle more rendered-lines --- src/libsyntax/errors/snippet/mod.rs | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) (limited to 'src/libsyntax/errors') diff --git a/src/libsyntax/errors/snippet/mod.rs b/src/libsyntax/errors/snippet/mod.rs index 6c90bfd0818..1ec4a015742 100644 --- a/src/libsyntax/errors/snippet/mod.rs +++ b/src/libsyntax/errors/snippet/mod.rs @@ -475,30 +475,34 @@ impl FileInfo { while let Some(line) = next_line { if line.annotations.is_empty() { break; } - let mut rendered_line = self.render_line(line); + 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_line[0].text.insert(0, StyledString { + rendered_lines[0].text.insert(0, StyledString { text: format!(":{} ", lo.line), style: Style::LineAndColumn, }); - rendered_line[0].text.insert(0, StyledString { + rendered_lines[0].text.insert(0, StyledString { text: lo.file.name.clone(), style: Style::FileNameStyle, }); - let gap_amount = rendered_line[0].text[0].text.len() + - rendered_line[0].text[1].text.len(); - rendered_line[1].text.insert(0, StyledString { - text: vec![" "; gap_amount].join(""), - style: Style::NoStyle - }); + let gap_amount = + rendered_lines[0].text[0].text.len() + + rendered_lines[0].text[1].text.len(); + 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_line); + output.append(&mut rendered_lines); next_line = lines_iter.next(); } -- cgit 1.4.1-3-g733a5 From db8a9a92b3dafcd5a8d7207096c8cbb90db0b013 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Mon, 2 May 2016 11:44:25 -0400 Subject: avoid double panic --- src/libsyntax/errors/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/libsyntax/errors') diff --git a/src/libsyntax/errors/mod.rs b/src/libsyntax/errors/mod.rs index 4ade537c8ce..f0c665bcb3c 100644 --- a/src/libsyntax/errors/mod.rs +++ b/src/libsyntax/errors/mod.rs @@ -20,6 +20,7 @@ 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; @@ -352,7 +353,7 @@ impl<'a> fmt::Debug for DiagnosticBuilder<'a> { /// we emit a bug. impl<'a> Drop for DiagnosticBuilder<'a> { fn drop(&mut self) { - if !self.cancelled() { + if !panicking() && !self.cancelled() { self.emitter.borrow_mut().emit(&MultiSpan::new(), "Error constructed but not emitted", None, -- cgit 1.4.1-3-g733a5 From 9355a91224a6f715b94342c074e5bac1f9e820f3 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Mon, 2 May 2016 13:05:14 -0400 Subject: assert we get at least two rendered lines back --- src/libsyntax/errors/snippet/mod.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'src/libsyntax/errors') diff --git a/src/libsyntax/errors/snippet/mod.rs b/src/libsyntax/errors/snippet/mod.rs index 1ec4a015742..e213f623ab8 100644 --- a/src/libsyntax/errors/snippet/mod.rs +++ b/src/libsyntax/errors/snippet/mod.rs @@ -38,13 +38,13 @@ pub struct FileInfo { lines: Vec, } -#[derive(Clone)] +#[derive(Clone, Debug)] struct Line { line_index: usize, annotations: Vec, } -#[derive(Clone, PartialOrd, Ord, PartialEq, Eq)] +#[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 @@ -492,6 +492,9 @@ impl FileInfo { 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(""), -- cgit 1.4.1-3-g733a5