From eb53ca3aad616069cb8f6f8fff71c27e9ba9640c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Sun, 23 Oct 2016 17:22:06 -0700 Subject: [PATCH] Show multiline spans in full if short enough When dealing with multiline spans that span few lines, show the complete span instead of restricting to the first character of the first line. For example, instead of: ``` % ./rustc foo.rs error[E0277]: the trait bound `{integer}: std::ops::Add<()>` is not satisfied --> foo.rs:13:9 | 13 | foo(1 + bar(x, | ^ trait `{integer}: std::ops::Add<()>` not satisfied | ``` show ``` % ./rustc foo.rs error[E0277]: the trait bound `{integer}: std::ops::Add<()>` is not satisfied --> foo.rs:13:9 | 13 | foo(1 + bar(x, | ________^ starting here... 14 | | y), | |_____________^ ...ending here: trait `{integer}: std::ops::Add<()>` not satisfied | ``` --- src/librustc_errors/emitter.rs | 484 ++++++++++++++---- src/librustc_errors/snippet.rs | 103 +++- src/libsyntax/lib.rs | 3 + src/libsyntax/test_snippet.rs | 446 ++++++++++++++++ .../ui/compare-method/region-extra-2.stderr | 12 +- .../traits-misc-mismatch-2.stderr | 12 +- ...dropck-eyepatch-implies-unsafe-impl.stderr | 20 +- .../consider-using-explicit-lifetime.stderr | 7 +- src/test/ui/mismatched_types/main.stderr | 6 +- src/test/ui/missing-items/m2.stderr | 6 +- .../ui/span/impl-wrong-item-for-trait.stderr | 54 +- src/test/ui/span/issue-23827.stderr | 12 +- src/test/ui/span/issue-24356.stderr | 10 +- src/test/ui/span/multiline-span-simple.rs | 30 ++ src/test/ui/span/multiline-span-simple.stderr | 20 + 15 files changed, 1081 insertions(+), 144 deletions(-) create mode 100644 src/libsyntax/test_snippet.rs create mode 100644 src/test/ui/span/multiline-span-simple.rs create mode 100644 src/test/ui/span/multiline-span-simple.stderr diff --git a/src/librustc_errors/emitter.rs b/src/librustc_errors/emitter.rs index a307e9b696def..808a1683b8436 100644 --- a/src/librustc_errors/emitter.rs +++ b/src/librustc_errors/emitter.rs @@ -14,7 +14,7 @@ use syntax_pos::{COMMAND_LINE_SP, DUMMY_SP, FileMap, Span, MultiSpan, CharPos}; use {Level, CodeSuggestion, DiagnosticBuilder, SubDiagnostic, CodeMapper}; use RenderSpan::*; -use snippet::{StyledString, Style, Annotation, Line}; +use snippet::{Annotation, AnnotationType, Line, StyledString, Style}; use styled_buffer::StyledBuffer; use std::io::prelude::*; @@ -65,6 +65,7 @@ pub struct EmitterWriter { struct FileWithAnnotatedLines { file: Rc, lines: Vec, + multiline_depth: usize, } @@ -137,10 +138,12 @@ impl EmitterWriter { line_index: line_index, annotations: vec![ann], }], + multiline_depth: 0, }); } let mut output = vec![]; + let mut multiline_annotations = vec![]; if let Some(ref cm) = self.cm { for span_label in msp.span_labels() { @@ -151,8 +154,9 @@ impl EmitterWriter { let mut hi = cm.lookup_char_pos(span_label.span.hi); let mut is_minimized = false; - // If the span is multi-line, simplify down to the span of one character - if lo.line != hi.line { + // If the span is long multi-line, simplify down to the span of one character + let max_multiline_span_length = 8; + if lo.line != hi.line && (hi.line - lo.line) > max_multiline_span_length { hi.line = lo.line; hi.col = CharPos(lo.col.0 + 1); is_minimized = true; @@ -163,22 +167,102 @@ impl EmitterWriter { // 6..7. This is degenerate input, but it's best to degrade // gracefully -- and the parser likes to supply a span like // that for EOF, in particular. - if lo.col == hi.col { + if lo.col == hi.col && lo.line == hi.line { hi.col = CharPos(lo.col.0 + 1); } - add_annotation_to_file(&mut output, - lo.file, - lo.line, - Annotation { - start_col: lo.col.0, - end_col: hi.col.0, - is_primary: span_label.is_primary, - is_minimized: is_minimized, - label: span_label.label.clone(), - }); + let mut ann = Annotation { + start_col: lo.col.0, + end_col: hi.col.0, + is_primary: span_label.is_primary, + label: span_label.label.clone(), + annotation_type: AnnotationType::Singleline, + }; + if is_minimized { + ann.annotation_type = AnnotationType::Minimized; + } else if lo.line != hi.line { + ann.annotation_type = AnnotationType::Multiline { + depth: 1, + line_start: lo.line, + line_end: hi.line, + }; + multiline_annotations.push((lo.file.clone(), ann.clone())); + }; + + if !ann.is_multiline() { + add_annotation_to_file(&mut output, + lo.file, + lo.line, + ann); + } + } + } + + // Find overlapping multiline annotations, put them at different depths + multiline_annotations.sort_by(|a, b| { + if let AnnotationType::Multiline { + line_start: a_start, + line_end: a_end, + .. + } = a.1.annotation_type { + if let AnnotationType::Multiline { + line_start: b_start, + line_end: b_end, + .. + } = b.1.annotation_type { + (a_start, a_end).cmp(&(b_start, b_end)) + } else { + panic!("tried to sort multiline annotations, but found `{:?}`", b) + } + } else { + panic!("tried to sort multiline annotations, but found `{:?}`", a) + } + }); + for item in multiline_annotations.clone() { + let ann = item.1; + if let AnnotationType::Multiline {line_start, line_end, ..} = ann.annotation_type { + for item in multiline_annotations.iter_mut() { + let ref mut a = item.1; + if let AnnotationType::Multiline { + line_start: start, + line_end: end, + .. + } = a.annotation_type { + // Move all other multiline annotations overlapping with this one + // one level to the right. + if &ann != a && num_overlap(line_start, line_end, start, end, true) { + a.annotation_type.increase_depth(); + } else { + break; + } + } else { + panic!("tried to find depth for multiline annotation, but found `{:?}`", + ann) + }; + } + } else { + panic!("tried to find depth for multiline annotation, but found `{:?}`", ann) + }; + } + + let mut max_depth = 0; // max overlapping multiline spans + for (file, ann) in multiline_annotations { + if let AnnotationType::Multiline {line_start, line_end, depth} = ann.annotation_type { + if depth > max_depth { + max_depth = depth; + } + add_annotation_to_file(&mut output, file.clone(), line_start, ann.as_start()); + for line in line_start + 1..line_end { + add_annotation_to_file(&mut output, file.clone(), line, ann.as_line()); + } + add_annotation_to_file(&mut output, file, line_end, ann.as_end()); + } else { + panic!("non-multiline annotation `{:?}` in `multiline_annotations`!", ann); } } + for file_vec in output.iter_mut() { + file_vec.multiline_depth = max_depth; + } output } @@ -186,14 +270,20 @@ impl EmitterWriter { buffer: &mut StyledBuffer, file: Rc, line: &Line, - width_offset: usize) { + width_offset: usize, + multiline_depth: usize) { let source_string = file.get_line(line.line_index - 1) .unwrap_or(""); let line_offset = buffer.num_lines(); + let code_offset = if multiline_depth == 0 { + width_offset + } else { + width_offset + multiline_depth + 1 + }; // First create the source line we will highlight. - buffer.puts(line_offset, width_offset, &source_string, Style::Quotation); + buffer.puts(line_offset, code_offset, &source_string, Style::Quotation); buffer.puts(line_offset, 0, &(line.line_index.to_string()), @@ -201,14 +291,10 @@ impl EmitterWriter { draw_col_separator(buffer, line_offset, width_offset - 2); - if line.annotations.is_empty() { - return; - } - // We want to display like this: // // vec.push(vec.pop().unwrap()); - // --- ^^^ _ previous borrow ends here + // --- ^^^ - previous borrow ends here // | | // | error occurs here // previous borrow of `vec` occurs here @@ -227,42 +313,22 @@ impl EmitterWriter { // Sort the annotations by (start, end col) let mut annotations = line.annotations.clone(); annotations.sort(); + annotations.reverse(); - // Next, create the highlight line. - for annotation in &annotations { - for p in annotation.start_col..annotation.end_col { - if annotation.is_primary { - buffer.putc(line_offset + 1, - width_offset + p, - '^', - Style::UnderlinePrimary); - if !annotation.is_minimized { - buffer.set_style(line_offset, width_offset + p, Style::UnderlinePrimary); - } - } else { - buffer.putc(line_offset + 1, - width_offset + p, - '-', - Style::UnderlineSecondary); - if !annotation.is_minimized { - buffer.set_style(line_offset, width_offset + p, Style::UnderlineSecondary); - } - } - } - } - draw_col_separator(buffer, line_offset + 1, width_offset - 2); - - // 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; - } - // Now add the text labels. We try, when possible, to stick the rightmost - // annotation at the end of the highlight line: + // First, figure out where each label will be positioned. + // + // In the case where you have the following annotations: + // + // vec.push(vec.pop().unwrap()); + // -------- - previous borrow ends here [C] + // || + // |this makes no sense [B] + // previous borrow of `vec` occurs here [A] + // + // `annotations_position` will hold [(2, A), (1, B), (0, C)]. + // + // 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 @@ -296,66 +362,251 @@ impl EmitterWriter { // 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 { - buffer.append(line_offset + 1, &highlight_label, Style::LabelPrimary); - } else { - buffer.append(line_offset + 1, &highlight_label, Style::LabelSecondary); - } - labeled_annotations = previous; + let mut annotations_position = vec![]; + let mut line_len = 0; + let mut p = 0; + let mut ann_iter = annotations.iter().peekable(); + while let Some(annotation) = ann_iter.next() { + let is_line = if let AnnotationType::MultilineLine(_) = annotation.annotation_type { + true + } else { + false + }; + let peek = ann_iter.peek(); + if let Some(next) = peek { + let next_is_line = if let AnnotationType::MultilineLine(_) = next.annotation_type { + true + } else { + false + }; + + if overlaps(next, annotation) && !is_line && !next_is_line { + p += 1; } } + annotations_position.push((p, annotation)); + if let Some(next) = peek { + let next_is_line = if let AnnotationType::MultilineLine(_) = next.annotation_type { + true + } else { + false + }; + let l = if let Some(ref label) = next.label { + label.len() + 2 + } else { + 0 + }; + if (overlaps(next, annotation) || next.end_col + l > annotation.start_col) + && !is_line && !next_is_line + { + p += 1; + } + } + if line_len < p { + line_len = p; + } + } + if line_len != 0 { + line_len += 1; } - // If that's the last annotation, we're done - if labeled_annotations.is_empty() { + // If there are no annotations or the only annotations on this line are + // MultilineLine, then there's only code being shown, stop processing. + if line.annotations.is_empty() || line.annotations.iter() + .filter(|a| { + // Set the multiline annotation vertical lines to the left of + // the code in this line. + if let AnnotationType::MultilineLine(depth) = a.annotation_type { + buffer.putc(line_offset, + width_offset + depth - 1, + '|', + if a.is_primary { + Style::UnderlinePrimary + } else { + Style::UnderlineSecondary + }); + false + } else { + true + } + }).collect::>().len() == 0 + { return; } - 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 pos in 0..line_len + 1 { + draw_col_separator(buffer, line_offset + pos + 1, width_offset - 2); + buffer.putc(line_offset + pos + 1, + width_offset - 2, + '|', + Style::LineNumber); + } + + // Write the horizontal lines for multiline annotations + // (only the first and last lines need this). + // + // After this we will have: + // + // 2 | fn foo() { + // | __________ + // | + // | + // 3 | + // 4 | } + // | _ + for &(pos, annotation) in &annotations_position { + let style = if annotation.is_primary { + Style::UnderlinePrimary + } else { + Style::UnderlineSecondary + }; + let pos = pos + 1; + match annotation.annotation_type { + AnnotationType::MultilineStart(depth) | + AnnotationType::MultilineEnd(depth) => { + draw_range(buffer, + '_', + line_offset + pos, + width_offset + depth, + code_offset + annotation.start_col, + style); + } + _ => (), + } + } - // 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 { - buffer.putc(line_offset + index, - width_offset + annotation.start_col, + // Write the vertical lines for multiline spans and for labels that are + // on a different line as the underline. + // + // After this we will have: + // + // 2 | fn foo() { + // | __________ + // | | | + // | | + // 3 | | + // 4 | | } + // | |_ + for &(pos, annotation) in &annotations_position { + let style = if annotation.is_primary { + Style::UnderlinePrimary + } else { + Style::UnderlineSecondary + }; + let pos = pos + 1; + if pos > 1 { + for p in line_offset + 1..line_offset + pos + 1 { + buffer.putc(p, + code_offset + annotation.start_col, '|', - Style::UnderlinePrimary); + style); + } + } + match annotation.annotation_type { + AnnotationType::MultilineStart(depth) => { + for p in line_offset + pos + 1..line_offset + line_len + 2 { + buffer.putc(p, + width_offset + depth - 1, + '|', + style); + } + } + AnnotationType::MultilineEnd(depth) => { + for p in line_offset..line_offset + pos + 1 { + buffer.putc(p, + width_offset + depth - 1, + '|', + style); + } + } + AnnotationType::MultilineLine(depth) => { + // the first line will have already be filled when we checked + // wether there were any annotations for this line. + for p in line_offset + 1..line_offset + line_len + 2 { + buffer.putc(p, + width_offset + depth - 1, + '|', + style); + } + } + _ => (), + } + } + + // Write the labels on the annotations that actually have a label. + // + // After this we will have: + // + // 2 | fn foo() { + // | __________ starting here... + // | | | + // | | something about `foo` + // 3 | | + // 4 | | } + // | |_ ...ending here: test + for &(pos, annotation) in &annotations_position { + let style = if annotation.is_primary { + Style::LabelPrimary + } else { + Style::LabelSecondary + }; + let (pos, col) = if pos == 0 { + (pos + 1, annotation.end_col + 1) + } else { + (pos + 2, annotation.start_col) + }; + if let Some(ref label) = annotation.label { + buffer.puts(line_offset + pos, + code_offset + col, + &label, + style); + } + } + + // Sort from biggest span to smallest span so that smaller spans are + // represented in the output: + // + // x | fn foo() + // | ^^^---^^ + // | | | + // | | something about `foo` + // | something about `fn foo()` + annotations_position.sort_by(|a, b| { + fn len(a: Annotation) -> usize { + // Account for usize underflows + if a.end_col > a.start_col { + a.end_col - a.start_col } else { - buffer.putc(line_offset + index, - width_offset + annotation.start_col, - '|', - Style::UnderlineSecondary); + a.start_col - a.end_col } - draw_col_separator(buffer, line_offset + index, width_offset - 2); } + // Decreasing order + len(a.1).cmp(&len(b.1)).reverse() + }); - if annotation.is_primary { - buffer.puts(line_offset + blank_lines, - width_offset + annotation.start_col, - annotation.label.as_ref().unwrap(), - Style::LabelPrimary); + // Write the underlines. + // + // After this we will have: + // + // 2 | fn foo() { + // | ____-_____^ starting here... + // | | | + // | | something about `foo` + // 3 | | + // 4 | | } + // | |_^ ...ending here: test + for &(_, annotation) in &annotations_position { + let (underline, style) = if annotation.is_primary { + ('^', Style::UnderlinePrimary) } else { - buffer.puts(line_offset + blank_lines, - width_offset + annotation.start_col, - annotation.label.as_ref().unwrap(), - Style::LabelSecondary); + ('-', Style::UnderlineSecondary) + }; + for p in annotation.start_col..annotation.end_col { + buffer.putc(line_offset + 1, + code_offset + p, + underline, + style); } - draw_col_separator(buffer, line_offset + blank_lines, width_offset - 2); } } @@ -577,7 +828,8 @@ impl EmitterWriter { self.render_source_line(&mut buffer, annotated_file.file.clone(), &annotated_file.lines[line_idx], - 3 + max_line_num_len); + 3 + max_line_num_len, + annotated_file.multiline_depth); // check to see if we need to print out or elide lines that come between // this annotated line and the next one @@ -729,16 +981,38 @@ fn draw_col_separator(buffer: &mut StyledBuffer, line: usize, col: usize) { } fn draw_col_separator_no_space(buffer: &mut StyledBuffer, line: usize, col: usize) { - buffer.puts(line, col, "|", Style::LineNumber); + draw_col_separator_no_space_with_style(buffer, line, col, Style::LineNumber); +} + +fn draw_col_separator_no_space_with_style(buffer: &mut StyledBuffer, + line: usize, + col: usize, + style: Style) { + buffer.putc(line, col, '|', style); +} + +fn draw_range(buffer: &mut StyledBuffer, symbol: char, line: usize, + col_from: usize, col_to: usize, style: Style) { + for col in col_from..col_to { + buffer.putc(line, col, symbol, style); + } } fn draw_note_separator(buffer: &mut StyledBuffer, line: usize, col: usize) { buffer.puts(line, col, "= ", Style::LineNumber); } +fn num_overlap(a_start: usize, a_end: usize, b_start: usize, b_end:usize, inclusive: bool) -> bool { + let extra = if inclusive { + 1 + } else { + 0 + }; + (b_start..b_end + extra).contains(a_start) || + (a_start..a_end + extra).contains(b_start) +} fn overlaps(a1: &Annotation, a2: &Annotation) -> bool { - (a2.start_col..a2.end_col).contains(a1.start_col) || - (a1.start_col..a1.end_col).contains(a2.start_col) + num_overlap(a1.start_col, a1.end_col, a2.start_col, a2.end_col, false) } fn emit_to_destination(rendered_buffer: &Vec>, diff --git a/src/librustc_errors/snippet.rs b/src/librustc_errors/snippet.rs index abfb71c861b25..3bf428af994a2 100644 --- a/src/librustc_errors/snippet.rs +++ b/src/librustc_errors/snippet.rs @@ -41,6 +41,57 @@ pub struct Line { pub annotations: Vec, } +#[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)] +pub enum AnnotationType { + /// Annotation under a single line of code + Singleline, + + /// Annotation under the first character of a multiline span + Minimized, + + /// Annotation enclosing the first and last character of a multiline span + Multiline { + depth: usize, + line_start: usize, + line_end: usize, + }, + + // The Multiline type above is replaced with the following three in order + // to reuse the current label drawing code. + // + // Each of these corresponds to one part of the following diagram: + // + // x | foo(1 + bar(x, + // | _________^ starting here... < MultilineStart + // x | | y), < MultilineLine + // | |______________^ ...ending here: label < MultilineEnd + // x | z); + /// Annotation marking the first character of a fully shown multiline span + MultilineStart(usize), + /// Annotation marking the last character of a fully shown multiline span + MultilineEnd(usize), + /// Line at the left enclosing the lines of a fully shown multiline span + MultilineLine(usize), +} + +impl AnnotationType { + pub fn depth(&self) -> usize { + match self { + &AnnotationType::Multiline {depth, ..} | + &AnnotationType::MultilineStart(depth) | + &AnnotationType::MultilineLine(depth) | + &AnnotationType::MultilineEnd(depth) => depth, + _ => 0, + } + } + + pub fn increase_depth(&mut self) { + if let AnnotationType::Multiline {ref mut depth, ..} = *self { + *depth += 1; + } + } +} + #[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)] pub struct Annotation { /// Start column, 0-based indexing -- counting *characters*, not @@ -55,11 +106,57 @@ pub struct Annotation { /// Is this annotation derived from primary span pub is_primary: bool, - /// Is this a large span minimized down to a smaller span - pub is_minimized: bool, - /// Optional label to display adjacent to the annotation. pub label: Option, + + /// Is this a single line, multiline or multiline span minimized down to a + /// smaller span. + pub annotation_type: AnnotationType, +} + +impl Annotation { + pub fn is_minimized(&self) -> bool { + match self.annotation_type { + AnnotationType::Minimized => true, + _ => false, + } + } + + pub fn is_multiline(&self) -> bool { + match self.annotation_type { + AnnotationType::Multiline {..} | + AnnotationType::MultilineStart(_) | + AnnotationType::MultilineLine(_) | + AnnotationType::MultilineEnd(_) => true, + _ => false, + } + } + + pub fn as_start(&self) -> Annotation { + let mut a = self.clone(); + a.annotation_type = AnnotationType::MultilineStart(self.annotation_type.depth()); + a.end_col = a.start_col + 1; + a.label = Some("starting here...".to_owned()); + a + } + + pub fn as_end(&self) -> Annotation { + let mut a = self.clone(); + a.annotation_type = AnnotationType::MultilineEnd(self.annotation_type.depth()); + a.start_col = a.end_col - 1; + a.label = match a.label { + Some(l) => Some(format!("...ending here: {}", l)), + None => Some("..ending here".to_owned()), + }; + a + } + + pub fn as_line(&self) -> Annotation { + let mut a = self.clone(); + a.annotation_type = AnnotationType::MultilineLine(self.annotation_type.depth()); + a.label = None; + a + } } #[derive(Debug)] diff --git a/src/libsyntax/lib.rs b/src/libsyntax/lib.rs index 34280812421a1..0545cccabf720 100644 --- a/src/libsyntax/lib.rs +++ b/src/libsyntax/lib.rs @@ -144,4 +144,7 @@ pub mod ext { } } +#[cfg(test)] +mod test_snippet; + // __build_diagnostic_array! { libsyntax, DIAGNOSTICS } diff --git a/src/libsyntax/test_snippet.rs b/src/libsyntax/test_snippet.rs new file mode 100644 index 0000000000000..4ce51076adcf4 --- /dev/null +++ b/src/libsyntax/test_snippet.rs @@ -0,0 +1,446 @@ +// Copyright 2016 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use codemap::CodeMap; +use errors::Handler; +use errors::emitter::EmitterWriter; +use std::io; +use std::io::prelude::*; +use std::rc::Rc; +use std::str; +use std::sync::{Arc, Mutex}; +use syntax_pos::{BytePos, NO_EXPANSION, Span, MultiSpan}; + +/// Identify a position in the text by the Nth occurrence of a string. +struct Position { + string: &'static str, + count: usize, +} + +struct SpanLabel { + start: Position, + end: Position, + label: &'static str, +} + +struct Shared { + data: Arc>, +} + +impl Write for Shared { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.data.lock().unwrap().write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + self.data.lock().unwrap().flush() + } +} + +fn test_harness(file_text: &str, span_labels: Vec, expected_output: &str) { + let output = Arc::new(Mutex::new(Vec::new())); + + let code_map = Rc::new(CodeMap::new()); + code_map.new_filemap_and_lines("test.rs", None, &file_text); + + let primary_span = make_span(&file_text, &span_labels[0].start, &span_labels[0].end); + let mut msp = MultiSpan::from_span(primary_span); + for span_label in span_labels { + let span = make_span(&file_text, &span_label.start, &span_label.end); + msp.push_span_label(span, span_label.label.to_string()); + println!("span: {:?} label: {:?}", span, span_label.label); + println!("text: {:?}", code_map.span_to_snippet(span)); + } + + let emitter = EmitterWriter::new(Box::new(Shared { data: output.clone() }), + Some(code_map.clone())); + let handler = Handler::with_emitter(true, false, Box::new(emitter)); + handler.span_err(msp, "foo"); + + assert!(expected_output.chars().next() == Some('\n'), + "expected output should begin with newline"); + let expected_output = &expected_output[1..]; + + let bytes = output.lock().unwrap(); + let actual_output = str::from_utf8(&bytes).unwrap(); + println!("expected output:\n------\n{}------", expected_output); + println!("actual output:\n------\n{}------", actual_output); + + assert!(expected_output == actual_output) +} + +fn make_span(file_text: &str, start: &Position, end: &Position) -> Span { + let start = make_pos(file_text, start); + let end = make_pos(file_text, end) + end.string.len(); // just after matching thing ends + assert!(start <= end); + Span { + lo: BytePos(start as u32), + hi: BytePos(end as u32), + expn_id: NO_EXPANSION, + } +} + +fn make_pos(file_text: &str, pos: &Position) -> usize { + let mut remainder = file_text; + let mut offset = 0; + for _ in 0..pos.count { + if let Some(n) = remainder.find(&pos.string) { + offset += n; + remainder = &remainder[n + 1..]; + } else { + panic!("failed to find {} instances of {:?} in {:?}", + pos.count, + pos.string, + file_text); + } + } + offset +} + +#[test] +fn ends_on_col0() { + test_harness(r#" +fn foo() { +} +"#, + vec![ + SpanLabel { + start: Position { + string: "{", + count: 1, + }, + end: Position { + string: "}", + count: 1, + }, + label: "test", + }, + ], + r#" +error: foo + --> test.rs:2:10 + | +2 | fn foo() { + | __________^ starting here... +3 | | } + | |_^ ...ending here: test + +"#); +} + +#[test] +fn ends_on_col2() { + test_harness(r#" +fn foo() { + + + } +"#, + vec![ + SpanLabel { + start: Position { + string: "{", + count: 1, + }, + end: Position { + string: "}", + count: 1, + }, + label: "test", + }, + ], + r#" +error: foo + --> test.rs:2:10 + | +2 | fn foo() { + | __________^ starting here... +3 | | +4 | | +5 | | } + | |___^ ...ending here: test + +"#); +} +#[test] +fn non_nested() { + test_harness(r#" +fn foo() { + X0 Y0 + X1 Y1 + X2 Y2 +} +"#, + vec![ + SpanLabel { + start: Position { + string: "X0", + count: 1, + }, + end: Position { + string: "X2", + count: 1, + }, + label: "`X` is a good letter", + }, + SpanLabel { + start: Position { + string: "Y0", + count: 1, + }, + end: Position { + string: "Y2", + count: 1, + }, + label: "`Y` is a good letter too", + }, + ], + r#" +error: foo + --> test.rs:3:3 + | +3 | X0 Y0 + | ____^__- starting here... + | | ___| + | || starting here... +4 | || X1 Y1 +5 | || X2 Y2 + | ||____^__- ...ending here: `Y` is a good letter too + | |____| + | ...ending here: `X` is a good letter + +"#); +} + +#[test] +fn nested() { + test_harness(r#" +fn foo() { + X0 Y0 + Y1 X1 +} +"#, + vec![ + SpanLabel { + start: Position { + string: "X0", + count: 1, + }, + end: Position { + string: "X1", + count: 1, + }, + label: "`X` is a good letter", + }, + SpanLabel { + start: Position { + string: "Y0", + count: 1, + }, + end: Position { + string: "Y1", + count: 1, + }, + label: "`Y` is a good letter too", + }, + ], +r#" +error: foo + --> test.rs:3:3 + | +3 | X0 Y0 + | ____^__- starting here... + | | ___| + | || starting here... +4 | || Y1 X1 + | ||____-__^ ...ending here: `X` is a good letter + | |_____| + | ...ending here: `Y` is a good letter too + +"#); +} + +#[test] +fn different_overlap() { + test_harness(r#" +fn foo() { + X0 Y0 Z0 + X1 Y1 Z1 + X2 Y2 Z2 + X3 Y3 Z3 +} +"#, + vec![ + SpanLabel { + start: Position { + string: "Y0", + count: 1, + }, + end: Position { + string: "X2", + count: 1, + }, + label: "`X` is a good letter", + }, + SpanLabel { + start: Position { + string: "Z1", + count: 1, + }, + end: Position { + string: "X3", + count: 1, + }, + label: "`Y` is a good letter too", + }, + ], + r#" +error: foo + --> test.rs:3:6 + | +3 | X0 Y0 Z0 + | ______^ starting here... +4 | | X1 Y1 Z1 + | |_________- starting here... +5 | || X2 Y2 Z2 + | ||____^ ...ending here: `X` is a good letter +6 | | X3 Y3 Z3 + | |_____- ...ending here: `Y` is a good letter too + +"#); +} + +#[test] +fn triple_overlap() { + test_harness(r#" +fn foo() { + X0 Y0 Z0 + X1 Y1 Z1 + X2 Y2 Z2 +} +"#, + vec![ + SpanLabel { + start: Position { + string: "X0", + count: 1, + }, + end: Position { + string: "X2", + count: 1, + }, + label: "`X` is a good letter", + }, + SpanLabel { + start: Position { + string: "Y0", + count: 1, + }, + end: Position { + string: "Y2", + count: 1, + }, + label: "`Y` is a good letter too", + }, + SpanLabel { + start: Position { + string: "Z0", + count: 1, + }, + end: Position { + string: "Z2", + count: 1, + }, + label: "`Z` label", + }, + ], + r#" +error: foo + --> test.rs:3:3 + | +3 | X0 Y0 Z0 + | _____^__-__- starting here... + | | ____|__| + | || ___| starting here... + | ||| starting here... +4 | ||| X1 Y1 Z1 +5 | ||| X2 Y2 Z2 + | |||____^__-__- ...ending here: `Z` label + | ||____|__| + | |____| ...ending here: `Y` is a good letter too + | ...ending here: `X` is a good letter + +"#); +} + +#[test] +fn minimum_depth() { + test_harness(r#" +fn foo() { + X0 Y0 Z0 + X1 Y1 Z1 + X2 Y2 Z2 + X3 Y3 Z3 +} +"#, + vec![ + SpanLabel { + start: Position { + string: "Y0", + count: 1, + }, + end: Position { + string: "X1", + count: 1, + }, + label: "`X` is a good letter", + }, + SpanLabel { + start: Position { + string: "Y1", + count: 1, + }, + end: Position { + string: "Z2", + count: 1, + }, + label: "`Y` is a good letter too", + }, + SpanLabel { + start: Position { + string: "X2", + count: 1, + }, + end: Position { + string: "Y3", + count: 1, + }, + label: "`Z`", + }, + ], + r#" +error: foo + --> test.rs:3:6 + | +3 | X0 Y0 Z0 + | ______^ starting here... +4 | | X1 Y1 Z1 + | |____^_- starting here... + | ||____| + | | ...ending here: `X` is a good letter +5 | | X2 Y2 Z2 + | |____-______- ...ending here: `Y` is a good letter too + | ____| + | | starting here... +6 | | X3 Y3 Z3 + | |________- ...ending here: `Z` + +"#); +} diff --git a/src/test/ui/compare-method/region-extra-2.stderr b/src/test/ui/compare-method/region-extra-2.stderr index 54a551bcfed5d..12b0ecabcc720 100644 --- a/src/test/ui/compare-method/region-extra-2.stderr +++ b/src/test/ui/compare-method/region-extra-2.stderr @@ -1,11 +1,15 @@ error[E0276]: impl has stricter requirements than trait --> $DIR/region-extra-2.rs:19:5 | -15 | fn renew<'b: 'a>(self) -> &'b mut [T]; - | -------------------------------------- definition of `renew` from trait +15 | fn renew<'b: 'a>(self) -> &'b mut [T]; + | -------------------------------------- definition of `renew` from trait ... -19 | fn renew<'b: 'a>(self) -> &'b mut [T] where 'a: 'b { - | ^ impl has extra requirement `'a: 'b` +19 | fn renew<'b: 'a>(self) -> &'b mut [T] where 'a: 'b { + | _____^ starting here... +20 | | //~^ ERROR E0276 +21 | | &mut self[..] +22 | | } + | |_____^ ...ending here: impl has extra requirement `'a: 'b` error: aborting due to previous error diff --git a/src/test/ui/compare-method/traits-misc-mismatch-2.stderr b/src/test/ui/compare-method/traits-misc-mismatch-2.stderr index 5003550fd1ee3..77b056f697892 100644 --- a/src/test/ui/compare-method/traits-misc-mismatch-2.stderr +++ b/src/test/ui/compare-method/traits-misc-mismatch-2.stderr @@ -1,11 +1,15 @@ error[E0276]: impl has stricter requirements than trait --> $DIR/traits-misc-mismatch-2.rs:23:5 | -19 | fn zip>(self, other: U) -> ZipIterator; - | ------------------------------------------------------------------ definition of `zip` from trait +19 | fn zip>(self, other: U) -> ZipIterator; + | ------------------------------------------------------------------ definition of `zip` from trait ... -23 | fn zip>(self, other: U) -> ZipIterator { - | ^ impl has extra requirement `U: Iterator` +23 | fn zip>(self, other: U) -> ZipIterator { + | _____^ starting here... +24 | | //~^ ERROR E0276 +25 | | ZipIterator{a: self, b: other} +26 | | } + | |_____^ ...ending here: impl has extra requirement `U: Iterator` error: aborting due to previous error diff --git a/src/test/ui/dropck/dropck-eyepatch-implies-unsafe-impl.stderr b/src/test/ui/dropck/dropck-eyepatch-implies-unsafe-impl.stderr index c53cf020a9bc5..b3e72f28d88c8 100644 --- a/src/test/ui/dropck/dropck-eyepatch-implies-unsafe-impl.stderr +++ b/src/test/ui/dropck/dropck-eyepatch-implies-unsafe-impl.stderr @@ -1,14 +1,26 @@ error[E0569]: requires an `unsafe impl` declaration due to `#[may_dangle]` attribute --> $DIR/dropck-eyepatch-implies-unsafe-impl.rs:32:1 | -32 | impl<#[may_dangle] A, B: fmt::Debug> Drop for Pt { - | ^ +32 | impl<#[may_dangle] A, B: fmt::Debug> Drop for Pt { + | _^ starting here... +33 | | //~^ ERROR requires an `unsafe impl` declaration due to `#[may_dangle]` attribute +34 | | +35 | | // (unsafe to access self.1 due to #[may_dangle] on A) +36 | | fn drop(&mut self) { println!("drop {} {:?}", self.0, self.2); } +37 | | } + | |_^ ..ending here error[E0569]: requires an `unsafe impl` declaration due to `#[may_dangle]` attribute --> $DIR/dropck-eyepatch-implies-unsafe-impl.rs:38:1 | -38 | impl<#[may_dangle] 'a, 'b, B: fmt::Debug> Drop for Pr<'a, 'b, B> { - | ^ +38 | impl<#[may_dangle] 'a, 'b, B: fmt::Debug> Drop for Pr<'a, 'b, B> { + | _^ starting here... +39 | | //~^ ERROR requires an `unsafe impl` declaration due to `#[may_dangle]` attribute +40 | | +41 | | // (unsafe to access self.1 due to #[may_dangle] on 'a) +42 | | fn drop(&mut self) { println!("drop {} {:?}", self.0, self.2); } +43 | | } + | |_^ ..ending here error: aborting due to 2 previous errors diff --git a/src/test/ui/lifetimes/consider-using-explicit-lifetime.stderr b/src/test/ui/lifetimes/consider-using-explicit-lifetime.stderr index 353e251369a10..1d1bc58805aae 100644 --- a/src/test/ui/lifetimes/consider-using-explicit-lifetime.stderr +++ b/src/test/ui/lifetimes/consider-using-explicit-lifetime.stderr @@ -15,8 +15,11 @@ error[E0495]: cannot infer an appropriate lifetime due to conflicting requiremen help: consider using an explicit lifetime parameter as shown: fn from_str(path: &'a str) -> Result --> $DIR/consider-using-explicit-lifetime.rs:25:5 | -25 | fn from_str(path: &str) -> Result { - | ^ +25 | fn from_str(path: &str) -> Result { + | _____^ starting here... +26 | | Ok(Foo { field: path }) +27 | | } + | |_____^ ..ending here error: aborting due to 2 previous errors diff --git a/src/test/ui/mismatched_types/main.stderr b/src/test/ui/mismatched_types/main.stderr index 9e26be6fdddeb..c87b635521eab 100644 --- a/src/test/ui/mismatched_types/main.stderr +++ b/src/test/ui/mismatched_types/main.stderr @@ -1,8 +1,10 @@ error[E0308]: mismatched types --> $DIR/main.rs:12:18 | -12 | let x: u32 = ( - | ^ expected u32, found () +12 | let x: u32 = ( + | __________________^ starting here... +13 | | ); + | |_____^ ...ending here: expected u32, found () | = note: expected type `u32` = note: found type `()` diff --git a/src/test/ui/missing-items/m2.stderr b/src/test/ui/missing-items/m2.stderr index caeb9ff415cd3..3313543454469 100644 --- a/src/test/ui/missing-items/m2.stderr +++ b/src/test/ui/missing-items/m2.stderr @@ -3,8 +3,10 @@ error: main function not found error[E0046]: not all trait items implemented, missing: `CONSTANT`, `Type`, `method` --> $DIR/m2.rs:20:1 | -20 | impl m1::X for X { - | ^ missing `CONSTANT`, `Type`, `method` in implementation +20 | impl m1::X for X { + | _^ starting here... +21 | | } + | |_^ ...ending here: missing `CONSTANT`, `Type`, `method` in implementation | = note: `CONSTANT` from trait: `const CONSTANT: u32;` = note: `Type` from trait: `type Type;` diff --git a/src/test/ui/span/impl-wrong-item-for-trait.stderr b/src/test/ui/span/impl-wrong-item-for-trait.stderr index 244285e358453..5c352436c3ea0 100644 --- a/src/test/ui/span/impl-wrong-item-for-trait.stderr +++ b/src/test/ui/span/impl-wrong-item-for-trait.stderr @@ -10,11 +10,19 @@ error[E0323]: item `bar` is an associated const, which doesn't match its trait ` error[E0046]: not all trait items implemented, missing: `bar` --> $DIR/impl-wrong-item-for-trait.rs:22:1 | -16 | fn bar(&self); - | -------------- `bar` from trait +16 | fn bar(&self); + | -------------- `bar` from trait ... -22 | impl Foo for FooConstForMethod { - | ^ missing `bar` in implementation +22 | impl Foo for FooConstForMethod { + | _^ starting here... +23 | | //~^ ERROR E0046 +24 | | //~| NOTE missing `bar` in implementation +25 | | const bar: u64 = 1; +26 | | //~^ ERROR E0323 +27 | | //~| NOTE does not match trait +28 | | const MY_CONST: u32 = 1; +29 | | } + | |_^ ...ending here: missing `bar` in implementation error[E0324]: item `MY_CONST` is an associated method, which doesn't match its trait `` --> $DIR/impl-wrong-item-for-trait.rs:37:5 @@ -28,11 +36,19 @@ error[E0324]: item `MY_CONST` is an associated method, which doesn't match its t error[E0046]: not all trait items implemented, missing: `MY_CONST` --> $DIR/impl-wrong-item-for-trait.rs:33:1 | -17 | const MY_CONST: u32; - | -------------------- `MY_CONST` from trait +17 | const MY_CONST: u32; + | -------------------- `MY_CONST` from trait ... -33 | impl Foo for FooMethodForConst { - | ^ missing `MY_CONST` in implementation +33 | impl Foo for FooMethodForConst { + | _^ starting here... +34 | | //~^ ERROR E0046 +35 | | //~| NOTE missing `MY_CONST` in implementation +36 | | fn bar(&self) {} +37 | | fn MY_CONST() {} +38 | | //~^ ERROR E0324 +39 | | //~| NOTE does not match trait +40 | | } + | |_^ ...ending here: missing `MY_CONST` in implementation error[E0325]: item `bar` is an associated type, which doesn't match its trait `` --> $DIR/impl-wrong-item-for-trait.rs:47:5 @@ -46,17 +62,27 @@ error[E0325]: item `bar` is an associated type, which doesn't match its trait `< error[E0046]: not all trait items implemented, missing: `bar` --> $DIR/impl-wrong-item-for-trait.rs:44:1 | -16 | fn bar(&self); - | -------------- `bar` from trait +16 | fn bar(&self); + | -------------- `bar` from trait ... -44 | impl Foo for FooTypeForMethod { - | ^ missing `bar` in implementation +44 | impl Foo for FooTypeForMethod { + | _^ starting here... +45 | | //~^ ERROR E0046 +46 | | //~| NOTE missing `bar` in implementation +47 | | type bar = u64; +48 | | //~^ ERROR E0325 +49 | | //~| NOTE does not match trait +50 | | const MY_CONST: u32 = 1; +51 | | } + | |_^ ...ending here: missing `bar` in implementation error[E0046]: not all trait items implemented, missing: `fmt` --> $DIR/impl-wrong-item-for-trait.rs:53:1 | -53 | impl Debug for FooTypeForMethod { - | ^ missing `fmt` in implementation +53 | impl Debug for FooTypeForMethod { + | _^ starting here... +54 | | } + | |_^ ...ending here: missing `fmt` in implementation | = note: `fmt` from trait: `fn(&Self, &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error>` diff --git a/src/test/ui/span/issue-23827.stderr b/src/test/ui/span/issue-23827.stderr index 5130bb53a198b..6c1c246753011 100644 --- a/src/test/ui/span/issue-23827.stderr +++ b/src/test/ui/span/issue-23827.stderr @@ -1,8 +1,16 @@ error[E0046]: not all trait items implemented, missing: `Output` --> $DIR/issue-23827.rs:36:1 | -36 | impl FnOnce<(C,)> for Prototype { - | ^ missing `Output` in implementation +36 | impl FnOnce<(C,)> for Prototype { + | _^ starting here... +37 | | //~^ ERROR E0046 +38 | | //~| NOTE missing `Output` in implementation +39 | | //~| NOTE `Output` from trait: `type Output;` +40 | | extern "rust-call" fn call_once(self, (comp,): (C,)) -> Prototype { +41 | | Fn::call(&self, (comp,)) +42 | | } +43 | | } + | |_^ ...ending here: missing `Output` in implementation | = note: `Output` from trait: `type Output;` diff --git a/src/test/ui/span/issue-24356.stderr b/src/test/ui/span/issue-24356.stderr index 906ef25ca0e10..963f4bd9bbcd8 100644 --- a/src/test/ui/span/issue-24356.stderr +++ b/src/test/ui/span/issue-24356.stderr @@ -1,8 +1,14 @@ error[E0046]: not all trait items implemented, missing: `Target` --> $DIR/issue-24356.rs:30:9 | -30 | impl Deref for Thing { - | ^ missing `Target` in implementation +30 | impl Deref for Thing { + | _________^ starting here... +31 | | //~^ ERROR E0046 +32 | | //~| NOTE missing `Target` in implementation +33 | | //~| NOTE `Target` from trait: `type Target;` +34 | | fn deref(&self) -> i8 { self.0 } +35 | | } + | |_________^ ...ending here: missing `Target` in implementation | = note: `Target` from trait: `type Target;` diff --git a/src/test/ui/span/multiline-span-simple.rs b/src/test/ui/span/multiline-span-simple.rs new file mode 100644 index 0000000000000..16414766f398e --- /dev/null +++ b/src/test/ui/span/multiline-span-simple.rs @@ -0,0 +1,30 @@ +// Copyright 2016 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +fn foo(a: u32, b: u32) { + a + b; +} + +fn bar(a: u32, b: u32) { + a + b; +} + +fn main() { + let x = 1; + let y = 2; + let z = 3; + foo(1 + + + bar(x, + + y), + + z) +} diff --git a/src/test/ui/span/multiline-span-simple.stderr b/src/test/ui/span/multiline-span-simple.stderr new file mode 100644 index 0000000000000..26acef64c896f --- /dev/null +++ b/src/test/ui/span/multiline-span-simple.stderr @@ -0,0 +1,20 @@ +error[E0277]: the trait bound `{integer}: std::ops::Add<()>` is not satisfied + --> $DIR/multiline-span-simple.rs:23:9 + | +23 | foo(1 + + | _________^ starting here... +24 | | +25 | | bar(x, +26 | | +27 | | y), + | |______________^ ...ending here: the trait `std::ops::Add<()>` is not implemented for `{integer}` + | + = help: the following implementations were found: + = help: + = help: <&'a u32 as std::ops::Add> + = help: > + = help: <&'b u32 as std::ops::Add<&'a u32>> + = help: and 90 others + +error: aborting due to previous error +