Skip to content

Commit

Permalink
Rollup merge of rust-lang#72026 - botika:master, r=estebank
Browse files Browse the repository at this point in the history
Update annotate-snippets-rs to 0.8.0

rust-lang#59346
I made major changes to this library. In the previous version we worked with owned while in the current one with borrowed.

I have adapted it without changing the behavior.
I have modified the coverage since the previous one did not return correctly the index of the character in the line.
  • Loading branch information
RalfJung authored Jun 8, 2020
2 parents 7355816 + 8fc6202 commit 8484b99
Show file tree
Hide file tree
Showing 7 changed files with 210 additions and 128 deletions.
12 changes: 9 additions & 3 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ dependencies = [
"ansi_term",
]

[[package]]
name = "annotate-snippets"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d78ea013094e5ea606b1c05fe35f1dd7ea1eb1ea259908d040b25bd5ec677ee5"

[[package]]
name = "ansi_term"
version = "0.11.0"
Expand Down Expand Up @@ -3316,7 +3322,7 @@ version = "659.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c374e89b3c9714869ef86076942155383804ba6778c26be2169d324563c31f9"
dependencies = [
"annotate-snippets",
"annotate-snippets 0.6.1",
"atty",
"log",
"rustc-ap-rustc_data_structures",
Expand Down Expand Up @@ -3810,7 +3816,7 @@ version = "0.0.0"
name = "rustc_errors"
version = "0.0.0"
dependencies = [
"annotate-snippets",
"annotate-snippets 0.8.0",
"atty",
"log",
"rustc_data_structures",
Expand Down Expand Up @@ -4477,7 +4483,7 @@ dependencies = [
name = "rustfmt-nightly"
version = "1.4.15"
dependencies = [
"annotate-snippets",
"annotate-snippets 0.6.1",
"bytecount",
"cargo_metadata 0.8.0",
"derive-new",
Expand Down
2 changes: 1 addition & 1 deletion src/librustc_errors/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ rustc_data_structures = { path = "../librustc_data_structures" }
unicode-width = "0.1.4"
atty = "0.2"
termcolor = "1.0"
annotate-snippets = "0.6.1"
annotate-snippets = "0.8.0"
termize = "0.1.1"

[target.'cfg(windows)'.dependencies]
Expand Down
215 changes: 92 additions & 123 deletions src/librustc_errors/annotate_snippet_emitter_writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,11 @@
use crate::emitter::FileWithAnnotatedLines;
use crate::snippet::Line;
use crate::{CodeSuggestion, Diagnostic, DiagnosticId, Emitter, Level, SubDiagnostic};
use annotate_snippets::display_list::DisplayList;
use annotate_snippets::formatter::DisplayListFormatter;
use annotate_snippets::display_list::{DisplayList, FormatOptions};
use annotate_snippets::snippet::*;
use rustc_data_structures::sync::Lrc;
use rustc_span::source_map::SourceMap;
use rustc_span::{Loc, MultiSpan, SourceFile};
use rustc_span::{MultiSpan, SourceFile};

/// Generates diagnostics using annotate-snippet
pub struct AnnotateSnippetEmitterWriter {
Expand Down Expand Up @@ -59,112 +58,20 @@ impl Emitter for AnnotateSnippetEmitterWriter {
}
}

/// Collects all the data needed to generate the data structures needed for the
/// `annotate-snippets` library.
struct DiagnosticConverter<'a> {
source_map: Option<Lrc<SourceMap>>,
level: Level,
message: String,
code: Option<DiagnosticId>,
msp: MultiSpan,
#[allow(dead_code)]
children: &'a [SubDiagnostic],
#[allow(dead_code)]
suggestions: &'a [CodeSuggestion],
/// Provides the source string for the given `line` of `file`
fn source_string(file: Lrc<SourceFile>, line: &Line) -> String {
file.get_line(line.line_index - 1).map(|a| a.to_string()).unwrap_or_default()
}

impl<'a> DiagnosticConverter<'a> {
/// Turns rustc Diagnostic information into a `annotate_snippets::snippet::Snippet`.
fn to_annotation_snippet(&self) -> Option<Snippet> {
if let Some(source_map) = &self.source_map {
// Make sure our primary file comes first
let primary_lo = if let Some(ref primary_span) = self.msp.primary_span().as_ref() {
source_map.lookup_char_pos(primary_span.lo())
} else {
// FIXME(#59346): Not sure when this is the case and what
// should be done if it happens
return None;
};
let annotated_files =
FileWithAnnotatedLines::collect_annotations(&self.msp, &self.source_map);
let slices = self.slices_for_files(annotated_files, primary_lo);

Some(Snippet {
title: Some(Annotation {
label: Some(self.message.to_string()),
id: self.code.clone().map(|c| match c {
DiagnosticId::Error(val) | DiagnosticId::Lint(val) => val,
}),
annotation_type: Self::annotation_type_for_level(self.level),
}),
footer: vec![],
slices,
})
} else {
// FIXME(#59346): Is it ok to return None if there's no source_map?
None
}
}

fn slices_for_files(
&self,
annotated_files: Vec<FileWithAnnotatedLines>,
primary_lo: Loc,
) -> Vec<Slice> {
// FIXME(#64205): Provide a test case where `annotated_files` is > 1
annotated_files
.iter()
.flat_map(|annotated_file| {
annotated_file
.lines
.iter()
.map(|line| {
let line_source = Self::source_string(annotated_file.file.clone(), &line);
Slice {
source: line_source,
line_start: line.line_index,
origin: Some(primary_lo.file.name.to_string()),
// FIXME(#59346): Not really sure when `fold` should be true or false
fold: false,
annotations: line
.annotations
.iter()
.map(|a| self.annotation_to_source_annotation(a.clone()))
.collect(),
}
})
.collect::<Vec<Slice>>()
})
.collect::<Vec<Slice>>()
}

/// Turns a `crate::snippet::Annotation` into a `SourceAnnotation`
fn annotation_to_source_annotation(
&self,
annotation: crate::snippet::Annotation,
) -> SourceAnnotation {
SourceAnnotation {
range: (annotation.start_col, annotation.end_col),
label: annotation.label.unwrap_or("".to_string()),
annotation_type: Self::annotation_type_for_level(self.level),
}
}

/// Provides the source string for the given `line` of `file`
fn source_string(file: Lrc<SourceFile>, line: &Line) -> String {
file.get_line(line.line_index - 1).map(|a| a.to_string()).unwrap_or(String::new())
}

/// Maps `Diagnostic::Level` to `snippet::AnnotationType`
fn annotation_type_for_level(level: Level) -> AnnotationType {
match level {
Level::Bug | Level::Fatal | Level::Error => AnnotationType::Error,
Level::Warning => AnnotationType::Warning,
Level::Note => AnnotationType::Note,
Level::Help => AnnotationType::Help,
// FIXME(#59346): Not sure how to map these two levels
Level::Cancelled | Level::FailureNote => AnnotationType::Error,
}
/// Maps `Diagnostic::Level` to `snippet::AnnotationType`
fn annotation_type_for_level(level: Level) -> AnnotationType {
match level {
Level::Bug | Level::Fatal | Level::Error => AnnotationType::Error,
Level::Warning => AnnotationType::Warning,
Level::Note => AnnotationType::Note,
Level::Help => AnnotationType::Help,
// FIXME(#59346): Not sure how to map these two levels
Level::Cancelled | Level::FailureNote => AnnotationType::Error,
}
}

Expand All @@ -191,25 +98,87 @@ impl AnnotateSnippetEmitterWriter {
message: String,
code: &Option<DiagnosticId>,
msp: &MultiSpan,
children: &[SubDiagnostic],
suggestions: &[CodeSuggestion],
_children: &[SubDiagnostic],
_suggestions: &[CodeSuggestion],
) {
let converter = DiagnosticConverter {
source_map: self.source_map.clone(),
level: *level,
message,
code: code.clone(),
msp: msp.clone(),
children,
suggestions,
};
if let Some(snippet) = converter.to_annotation_snippet() {
let dl = DisplayList::from(snippet);
let dlf = DisplayListFormatter::new(true, self.ui_testing);
if let Some(source_map) = &self.source_map {
// Make sure our primary file comes first
let primary_lo = if let Some(ref primary_span) = msp.primary_span().as_ref() {
if primary_span.is_dummy() {
// FIXME(#59346): Not sure when this is the case and what
// should be done if it happens
return;
} else {
source_map.lookup_char_pos(primary_span.lo())
}
} else {
// FIXME(#59346): Not sure when this is the case and what
// should be done if it happens
return;
};
let mut annotated_files =
FileWithAnnotatedLines::collect_annotations(msp, &self.source_map);
if let Ok(pos) =
annotated_files.binary_search_by(|x| x.file.name.cmp(&primary_lo.file.name))
{
annotated_files.swap(0, pos);
}
// owned: line source, line index, annotations
type Owned = (String, usize, Vec<crate::snippet::Annotation>);
let origin = primary_lo.file.name.to_string();
let annotated_files: Vec<Owned> = annotated_files
.into_iter()
.flat_map(|annotated_file| {
let file = annotated_file.file;
annotated_file
.lines
.into_iter()
.map(|line| {
(source_string(file.clone(), &line), line.line_index, line.annotations)
})
.collect::<Vec<Owned>>()
})
.collect();
let snippet = Snippet {
title: Some(Annotation {
label: Some(&message),
id: code.as_ref().map(|c| match c {
DiagnosticId::Error(val) | DiagnosticId::Lint(val) => val.as_str(),
}),
annotation_type: annotation_type_for_level(*level),
}),
footer: vec![],
opt: FormatOptions { color: true, anonymized_line_numbers: self.ui_testing },
slices: annotated_files
.iter()
.map(|(source, line_index, annotations)| {
Slice {
source,
line_start: *line_index,
origin: Some(&origin),
// FIXME(#59346): Not really sure when `fold` should be true or false
fold: false,
annotations: annotations
.into_iter()
.map(|annotation| SourceAnnotation {
range: (annotation.start_col, annotation.end_col),
label: annotation
.label
.as_ref()
.map(|s| s.as_str())
.unwrap_or_default(),
annotation_type: annotation_type_for_level(*level),
})
.collect(),
}
})
.collect(),
};
// FIXME(#59346): Figure out if we can _always_ print to stderr or not.
// `emitter.rs` has the `Destination` enum that lists various possible output
// destinations.
eprintln!("{}", dlf.format(&dl));
};
eprintln!("{}", DisplayList::from(snippet))
}
// FIXME(#59346): Is it ok to return None if there's no source_map?
}
}
37 changes: 37 additions & 0 deletions src/test/ui/annotate-snippet/auxiliary/multispan.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// force-host
// no-prefer-dynamic

#![crate_type = "proc-macro"]
#![feature(proc_macro_diagnostic, proc_macro_span, proc_macro_def_site)]

extern crate proc_macro;

use proc_macro::{TokenStream, TokenTree, Span, Diagnostic};

fn parse(input: TokenStream) -> Result<(), Diagnostic> {
let mut hi_spans = vec![];
for tree in input {
if let TokenTree::Ident(ref ident) = tree {
if ident.to_string() == "hi" {
hi_spans.push(ident.span());
}
}
}

if !hi_spans.is_empty() {
return Err(Span::def_site()
.error("hello to you, too!")
.span_note(hi_spans, "found these 'hi's"));
}

Ok(())
}

#[proc_macro]
pub fn hello(input: TokenStream) -> TokenStream {
if let Err(diag) = parse(input) {
diag.emit();
}

TokenStream::new()
}
2 changes: 1 addition & 1 deletion src/test/ui/annotate-snippet/missing-type.stderr
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
error[E0412]: cannot find type `Iter` in this scope
--> $DIR/missing-type.rs:4:11
--> $DIR/missing-type.rs:4:12
|
LL | let x: Iter;
| ^^^^ not found in this scope
Expand Down
28 changes: 28 additions & 0 deletions src/test/ui/annotate-snippet/multispan.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// aux-build:multispan.rs
// compile-flags: --error-format human-annotate-rs

#![feature(proc_macro_hygiene)]

extern crate multispan;

use multispan::hello;

fn main() {
// This one emits no error.
hello!();

// Exactly one 'hi'.
hello!(hi); //~ ERROR hello to you, too!

// Now two, back to back.
hello!(hi hi); //~ ERROR hello to you, too!

// Now three, back to back.
hello!(hi hi hi); //~ ERROR hello to you, too!

// Now several, with spacing.
hello!(hi hey hi yo hi beep beep hi hi); //~ ERROR hello to you, too!
hello!(hi there, hi how are you? hi... hi.); //~ ERROR hello to you, too!
hello!(whoah. hi di hi di ho); //~ ERROR hello to you, too!
hello!(hi good hi and good bye); //~ ERROR hello to you, too!
}
Loading

0 comments on commit 8484b99

Please sign in to comment.