1
Fork 0

mv compiler to compiler/

This commit is contained in:
mark 2020-08-27 22:58:48 -05:00 committed by Vadim Petrochenkov
parent db534b3ac2
commit 9e5f7d5631
1686 changed files with 941 additions and 1051 deletions

View file

@ -0,0 +1,180 @@
//! Emit diagnostics using the `annotate-snippets` library
//!
//! This is the equivalent of `./emitter.rs` but making use of the
//! [`annotate-snippets`][annotate_snippets] library instead of building the output ourselves.
//!
//! [annotate_snippets]: https://docs.rs/crate/annotate-snippets/
use crate::emitter::FileWithAnnotatedLines;
use crate::snippet::Line;
use crate::{CodeSuggestion, Diagnostic, DiagnosticId, Emitter, Level, SubDiagnostic};
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::{MultiSpan, SourceFile};
/// Generates diagnostics using annotate-snippet
pub struct AnnotateSnippetEmitterWriter {
source_map: Option<Lrc<SourceMap>>,
/// If true, hides the longer explanation text
short_message: bool,
/// If true, will normalize line numbers with `LL` to prevent noise in UI test diffs.
ui_testing: bool,
macro_backtrace: bool,
}
impl Emitter for AnnotateSnippetEmitterWriter {
/// The entry point for the diagnostics generation
fn emit_diagnostic(&mut self, diag: &Diagnostic) {
let mut children = diag.children.clone();
let (mut primary_span, suggestions) = self.primary_span_formatted(&diag);
self.fix_multispans_in_extern_macros_and_render_macro_backtrace(
&self.source_map,
&mut primary_span,
&mut children,
&diag.level,
self.macro_backtrace,
);
self.emit_messages_default(
&diag.level,
diag.message(),
&diag.code,
&primary_span,
&children,
&suggestions,
);
}
fn source_map(&self) -> Option<&Lrc<SourceMap>> {
self.source_map.as_ref()
}
fn should_show_explain(&self) -> bool {
!self.short_message
}
}
/// 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()
}
/// 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,
}
}
impl AnnotateSnippetEmitterWriter {
pub fn new(
source_map: Option<Lrc<SourceMap>>,
short_message: bool,
macro_backtrace: bool,
) -> Self {
Self { source_map, short_message, ui_testing: false, macro_backtrace }
}
/// Allows to modify `Self` to enable or disable the `ui_testing` flag.
///
/// If this is set to true, line numbers will be normalized as `LL` in the output.
pub fn ui_testing(mut self, ui_testing: bool) -> Self {
self.ui_testing = ui_testing;
self
}
fn emit_messages_default(
&mut self,
level: &Level,
message: String,
code: &Option<DiagnosticId>,
msp: &MultiSpan,
_children: &[SubDiagnostic],
_suggestions: &[CodeSuggestion],
) {
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
.iter()
.map(|annotation| SourceAnnotation {
range: (annotation.start_col, annotation.end_col),
label: annotation.label.as_deref().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!("{}", DisplayList::from(snippet))
}
// FIXME(#59346): Is it ok to return None if there's no source_map?
}
}

View file

@ -0,0 +1,586 @@
use crate::snippet::Style;
use crate::Applicability;
use crate::CodeSuggestion;
use crate::Level;
use crate::Substitution;
use crate::SubstitutionPart;
use crate::SuggestionStyle;
use rustc_span::{MultiSpan, Span, DUMMY_SP};
use std::fmt;
#[must_use]
#[derive(Clone, Debug, PartialEq, Hash, Encodable, Decodable)]
pub struct Diagnostic {
pub level: Level,
pub message: Vec<(String, Style)>,
pub code: Option<DiagnosticId>,
pub span: MultiSpan,
pub children: Vec<SubDiagnostic>,
pub suggestions: Vec<CodeSuggestion>,
/// This is not used for highlighting or rendering any error message. Rather, it can be used
/// as a sort key to sort a buffer of diagnostics. By default, it is the primary span of
/// `span` if there is one. Otherwise, it is `DUMMY_SP`.
pub sort_span: Span,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Encodable, Decodable)]
pub enum DiagnosticId {
Error(String),
Lint(String),
}
/// For example a note attached to an error.
#[derive(Clone, Debug, PartialEq, Hash, Encodable, Decodable)]
pub struct SubDiagnostic {
pub level: Level,
pub message: Vec<(String, Style)>,
pub span: MultiSpan,
pub render_span: Option<MultiSpan>,
}
#[derive(Debug, PartialEq, Eq)]
pub struct DiagnosticStyledString(pub Vec<StringPart>);
impl DiagnosticStyledString {
pub fn new() -> DiagnosticStyledString {
DiagnosticStyledString(vec![])
}
pub fn push_normal<S: Into<String>>(&mut self, t: S) {
self.0.push(StringPart::Normal(t.into()));
}
pub fn push_highlighted<S: Into<String>>(&mut self, t: S) {
self.0.push(StringPart::Highlighted(t.into()));
}
pub fn push<S: Into<String>>(&mut self, t: S, highlight: bool) {
if highlight {
self.push_highlighted(t);
} else {
self.push_normal(t);
}
}
pub fn normal<S: Into<String>>(t: S) -> DiagnosticStyledString {
DiagnosticStyledString(vec![StringPart::Normal(t.into())])
}
pub fn highlighted<S: Into<String>>(t: S) -> DiagnosticStyledString {
DiagnosticStyledString(vec![StringPart::Highlighted(t.into())])
}
pub fn content(&self) -> String {
self.0.iter().map(|x| x.content()).collect::<String>()
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum StringPart {
Normal(String),
Highlighted(String),
}
impl StringPart {
pub fn content(&self) -> &str {
match self {
&StringPart::Normal(ref s) | &StringPart::Highlighted(ref s) => s,
}
}
}
impl Diagnostic {
pub fn new(level: Level, message: &str) -> Self {
Diagnostic::new_with_code(level, None, message)
}
pub fn new_with_code(level: Level, code: Option<DiagnosticId>, message: &str) -> Self {
Diagnostic {
level,
message: vec![(message.to_owned(), Style::NoStyle)],
code,
span: MultiSpan::new(),
children: vec![],
suggestions: vec![],
sort_span: DUMMY_SP,
}
}
pub fn is_error(&self) -> bool {
match self.level {
Level::Bug | Level::Fatal | Level::Error | Level::FailureNote => true,
Level::Warning | Level::Note | Level::Help | Level::Cancelled => false,
}
}
/// Cancel the diagnostic (a structured diagnostic must either be emitted or
/// canceled or it will panic when dropped).
pub fn cancel(&mut self) {
self.level = Level::Cancelled;
}
pub fn cancelled(&self) -> bool {
self.level == Level::Cancelled
}
/// Set the sorting span.
pub fn set_sort_span(&mut self, sp: Span) {
self.sort_span = sp;
}
/// Adds 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. That means it will be shown together with the original
/// span/label, *not* a span added by one of the `span_{note,warn,help,suggestions}` methods.
///
/// This span is *not* considered a ["primary span"][`MultiSpan`]; only
/// the `Span` supplied when creating the diagnostic is primary.
///
/// [`MultiSpan`]: ../rustc_span/struct.MultiSpan.html
pub fn span_label<T: Into<String>>(&mut self, span: Span, label: T) -> &mut Self {
self.span.push_span_label(span, label.into());
self
}
pub fn replace_span_with(&mut self, after: Span) -> &mut Self {
let before = self.span.clone();
self.set_span(after);
for span_label in before.span_labels() {
if let Some(label) = span_label.label {
self.span_label(after, label);
}
}
self
}
pub fn note_expected_found(
&mut self,
expected_label: &dyn fmt::Display,
expected: DiagnosticStyledString,
found_label: &dyn fmt::Display,
found: DiagnosticStyledString,
) -> &mut Self {
self.note_expected_found_extra(expected_label, expected, found_label, found, &"", &"")
}
pub fn note_unsuccessfull_coercion(
&mut self,
expected: DiagnosticStyledString,
found: DiagnosticStyledString,
) -> &mut Self {
let mut msg: Vec<_> =
vec![("required when trying to coerce from type `".to_string(), Style::NoStyle)];
msg.extend(expected.0.iter().map(|x| match *x {
StringPart::Normal(ref s) => (s.to_owned(), Style::NoStyle),
StringPart::Highlighted(ref s) => (s.to_owned(), Style::Highlight),
}));
msg.push(("` to type '".to_string(), Style::NoStyle));
msg.extend(found.0.iter().map(|x| match *x {
StringPart::Normal(ref s) => (s.to_owned(), Style::NoStyle),
StringPart::Highlighted(ref s) => (s.to_owned(), Style::Highlight),
}));
msg.push(("`".to_string(), Style::NoStyle));
// For now, just attach these as notes
self.highlighted_note(msg);
self
}
pub fn note_expected_found_extra(
&mut self,
expected_label: &dyn fmt::Display,
expected: DiagnosticStyledString,
found_label: &dyn fmt::Display,
found: DiagnosticStyledString,
expected_extra: &dyn fmt::Display,
found_extra: &dyn fmt::Display,
) -> &mut Self {
let expected_label = expected_label.to_string();
let expected_label = if expected_label.is_empty() {
"expected".to_string()
} else {
format!("expected {}", expected_label)
};
let found_label = found_label.to_string();
let found_label = if found_label.is_empty() {
"found".to_string()
} else {
format!("found {}", found_label)
};
let (found_padding, expected_padding) = if expected_label.len() > found_label.len() {
(expected_label.len() - found_label.len(), 0)
} else {
(0, found_label.len() - expected_label.len())
};
let mut msg: Vec<_> =
vec![(format!("{}{} `", " ".repeat(expected_padding), expected_label), Style::NoStyle)];
msg.extend(expected.0.iter().map(|x| match *x {
StringPart::Normal(ref s) => (s.to_owned(), Style::NoStyle),
StringPart::Highlighted(ref s) => (s.to_owned(), Style::Highlight),
}));
msg.push((format!("`{}\n", expected_extra), Style::NoStyle));
msg.push((format!("{}{} `", " ".repeat(found_padding), found_label), Style::NoStyle));
msg.extend(found.0.iter().map(|x| match *x {
StringPart::Normal(ref s) => (s.to_owned(), Style::NoStyle),
StringPart::Highlighted(ref s) => (s.to_owned(), Style::Highlight),
}));
msg.push((format!("`{}", found_extra), Style::NoStyle));
// For now, just attach these as notes.
self.highlighted_note(msg);
self
}
pub fn note_trait_signature(&mut self, name: String, signature: String) -> &mut Self {
self.highlighted_note(vec![
(format!("`{}` from trait: `", name), Style::NoStyle),
(signature, Style::Highlight),
("`".to_string(), Style::NoStyle),
]);
self
}
pub fn note(&mut self, msg: &str) -> &mut Self {
self.sub(Level::Note, msg, MultiSpan::new(), None);
self
}
pub fn highlighted_note(&mut self, msg: Vec<(String, Style)>) -> &mut Self {
self.sub_with_highlights(Level::Note, msg, MultiSpan::new(), None);
self
}
/// Prints the span with a note above it.
pub fn span_note<S: Into<MultiSpan>>(&mut self, sp: S, msg: &str) -> &mut Self {
self.sub(Level::Note, msg, sp.into(), None);
self
}
pub fn warn(&mut self, msg: &str) -> &mut Self {
self.sub(Level::Warning, msg, MultiSpan::new(), None);
self
}
/// Prints the span with a warn above it.
pub fn span_warn<S: Into<MultiSpan>>(&mut self, sp: S, msg: &str) -> &mut Self {
self.sub(Level::Warning, msg, sp.into(), None);
self
}
pub fn help(&mut self, msg: &str) -> &mut Self {
self.sub(Level::Help, msg, MultiSpan::new(), None);
self
}
/// Prints the span with some help above it.
pub fn span_help<S: Into<MultiSpan>>(&mut self, sp: S, msg: &str) -> &mut Self {
self.sub(Level::Help, msg, sp.into(), None);
self
}
pub fn multipart_suggestion(
&mut self,
msg: &str,
suggestion: Vec<(Span, String)>,
applicability: Applicability,
) -> &mut Self {
self.suggestions.push(CodeSuggestion {
substitutions: vec![Substitution {
parts: suggestion
.into_iter()
.map(|(span, snippet)| SubstitutionPart { snippet, span })
.collect(),
}],
msg: msg.to_owned(),
style: SuggestionStyle::ShowCode,
applicability,
});
self
}
pub fn multipart_suggestions(
&mut self,
msg: &str,
suggestions: Vec<Vec<(Span, String)>>,
applicability: Applicability,
) -> &mut Self {
self.suggestions.push(CodeSuggestion {
substitutions: suggestions
.into_iter()
.map(|suggestion| Substitution {
parts: suggestion
.into_iter()
.map(|(span, snippet)| SubstitutionPart { snippet, span })
.collect(),
})
.collect(),
msg: msg.to_owned(),
style: SuggestionStyle::ShowCode,
applicability,
});
self
}
/// Prints out a message with for a multipart suggestion without showing the suggested code.
///
/// This is intended to be used for suggestions that are obvious in what the changes need to
/// be from the message, showing the span label inline would be visually unpleasant
/// (marginally overlapping spans or multiline spans) and showing the snippet window wouldn't
/// improve understandability.
pub fn tool_only_multipart_suggestion(
&mut self,
msg: &str,
suggestion: Vec<(Span, String)>,
applicability: Applicability,
) -> &mut Self {
self.suggestions.push(CodeSuggestion {
substitutions: vec![Substitution {
parts: suggestion
.into_iter()
.map(|(span, snippet)| SubstitutionPart { snippet, span })
.collect(),
}],
msg: msg.to_owned(),
style: SuggestionStyle::CompletelyHidden,
applicability,
});
self
}
/// Prints out a message with a suggested edit of the code.
///
/// In case of short messages and a simple suggestion, rustc displays it as a label:
///
/// ```text
/// try adding parentheses: `(tup.0).1`
/// ```
///
/// The message
///
/// * should not end in any punctuation (a `:` is added automatically)
/// * should not be a question (avoid language like "did you mean")
/// * should not contain any phrases like "the following", "as shown", etc.
/// * may look like "to do xyz, use" or "to do xyz, use abc"
/// * may contain a name of a function, variable, or type, but not whole expressions
///
/// See `CodeSuggestion` for more information.
pub fn span_suggestion(
&mut self,
sp: Span,
msg: &str,
suggestion: String,
applicability: Applicability,
) -> &mut Self {
self.span_suggestion_with_style(
sp,
msg,
suggestion,
applicability,
SuggestionStyle::ShowCode,
);
self
}
pub fn span_suggestion_with_style(
&mut self,
sp: Span,
msg: &str,
suggestion: String,
applicability: Applicability,
style: SuggestionStyle,
) -> &mut Self {
self.suggestions.push(CodeSuggestion {
substitutions: vec![Substitution {
parts: vec![SubstitutionPart { snippet: suggestion, span: sp }],
}],
msg: msg.to_owned(),
style,
applicability,
});
self
}
pub fn span_suggestion_verbose(
&mut self,
sp: Span,
msg: &str,
suggestion: String,
applicability: Applicability,
) -> &mut Self {
self.span_suggestion_with_style(
sp,
msg,
suggestion,
applicability,
SuggestionStyle::ShowAlways,
);
self
}
/// Prints out a message with multiple suggested edits of the code.
pub fn span_suggestions(
&mut self,
sp: Span,
msg: &str,
suggestions: impl Iterator<Item = String>,
applicability: Applicability,
) -> &mut Self {
self.suggestions.push(CodeSuggestion {
substitutions: suggestions
.map(|snippet| Substitution { parts: vec![SubstitutionPart { snippet, span: sp }] })
.collect(),
msg: msg.to_owned(),
style: SuggestionStyle::ShowCode,
applicability,
});
self
}
/// Prints out a message with a suggested edit of the code. If the suggestion is presented
/// inline, it will only show the message and not the suggestion.
///
/// See `CodeSuggestion` for more information.
pub fn span_suggestion_short(
&mut self,
sp: Span,
msg: &str,
suggestion: String,
applicability: Applicability,
) -> &mut Self {
self.span_suggestion_with_style(
sp,
msg,
suggestion,
applicability,
SuggestionStyle::HideCodeInline,
);
self
}
/// Prints out a message with for a suggestion without showing the suggested code.
///
/// This is intended to be used for suggestions that are obvious in what the changes need to
/// be from the message, showing the span label inline would be visually unpleasant
/// (marginally overlapping spans or multiline spans) and showing the snippet window wouldn't
/// improve understandability.
pub fn span_suggestion_hidden(
&mut self,
sp: Span,
msg: &str,
suggestion: String,
applicability: Applicability,
) -> &mut Self {
self.span_suggestion_with_style(
sp,
msg,
suggestion,
applicability,
SuggestionStyle::HideCodeAlways,
);
self
}
/// Adds a suggestion to the json output, but otherwise remains silent/undisplayed in the cli.
///
/// This is intended to be used for suggestions that are *very* obvious in what the changes
/// need to be from the message, but we still want other tools to be able to apply them.
pub fn tool_only_span_suggestion(
&mut self,
sp: Span,
msg: &str,
suggestion: String,
applicability: Applicability,
) -> &mut Self {
self.span_suggestion_with_style(
sp,
msg,
suggestion,
applicability,
SuggestionStyle::CompletelyHidden,
);
self
}
pub fn set_span<S: Into<MultiSpan>>(&mut self, sp: S) -> &mut Self {
self.span = sp.into();
if let Some(span) = self.span.primary_span() {
self.sort_span = span;
}
self
}
pub fn code(&mut self, s: DiagnosticId) -> &mut Self {
self.code = Some(s);
self
}
pub fn clear_code(&mut self) -> &mut Self {
self.code = None;
self
}
pub fn get_code(&self) -> Option<DiagnosticId> {
self.code.clone()
}
pub fn set_primary_message<M: Into<String>>(&mut self, msg: M) -> &mut Self {
self.message[0] = (msg.into(), Style::NoStyle);
self
}
pub fn message(&self) -> String {
self.message.iter().map(|i| i.0.as_str()).collect::<String>()
}
pub fn styled_message(&self) -> &Vec<(String, Style)> {
&self.message
}
/// Used by a lint. Copies over all details *but* the "main
/// message".
pub fn copy_details_not_message(&mut self, from: &Diagnostic) {
self.span = from.span.clone();
self.code = from.code.clone();
self.children.extend(from.children.iter().cloned())
}
/// Convenience function for internal use, clients should use one of the
/// public methods above.
pub fn sub(
&mut self,
level: Level,
message: &str,
span: MultiSpan,
render_span: Option<MultiSpan>,
) {
let sub = SubDiagnostic {
level,
message: vec![(message.to_owned(), Style::NoStyle)],
span,
render_span,
};
self.children.push(sub);
}
/// Convenience function for internal use, clients should use one of the
/// public methods above.
fn sub_with_highlights(
&mut self,
level: Level,
message: Vec<(String, Style)>,
span: MultiSpan,
render_span: Option<MultiSpan>,
) {
let sub = SubDiagnostic { level, message, span, render_span };
self.children.push(sub);
}
}
impl SubDiagnostic {
pub fn message(&self) -> String {
self.message.iter().map(|i| i.0.as_str()).collect::<String>()
}
pub fn styled_message(&self) -> &Vec<(String, Style)> {
&self.message
}
}

View file

@ -0,0 +1,452 @@
use crate::{Applicability, Handler, Level, StashKey};
use crate::{Diagnostic, DiagnosticId, DiagnosticStyledString};
use rustc_span::{MultiSpan, Span};
use std::fmt::{self, Debug};
use std::ops::{Deref, DerefMut};
use std::thread::panicking;
use tracing::debug;
/// Used for emitting structured error messages and other diagnostic information.
///
/// If there is some state in a downstream crate you would like to
/// access in the methods of `DiagnosticBuilder` here, consider
/// extending `HandlerFlags`, accessed via `self.handler.flags`.
#[must_use]
#[derive(Clone)]
pub struct DiagnosticBuilder<'a>(Box<DiagnosticBuilderInner<'a>>);
/// This is a large type, and often used as a return value, especially within
/// the frequently-used `PResult` type. In theory, return value optimization
/// (RVO) should avoid unnecessary copying. In practice, it does not (at the
/// time of writing). The split between `DiagnosticBuilder` and
/// `DiagnosticBuilderInner` exists to avoid many `memcpy` calls.
#[must_use]
#[derive(Clone)]
struct DiagnosticBuilderInner<'a> {
handler: &'a Handler,
diagnostic: Diagnostic,
allow_suggestions: bool,
}
/// In general, the `DiagnosticBuilder` uses deref to allow access to
/// the fields and methods of the embedded `diagnostic` in a
/// transparent way. *However,* many of the methods are intended to
/// be used in a chained way, and hence ought to return `self`. In
/// that case, we can't just naively forward to the method on the
/// `diagnostic`, because the return type would be a `&Diagnostic`
/// instead of a `&DiagnosticBuilder<'a>`. This `forward!` macro makes
/// it easy to declare such methods on the builder.
macro_rules! forward {
// Forward pattern for &self -> &Self
(
$(#[$attrs:meta])*
pub fn $n:ident(&self, $($name:ident: $ty:ty),* $(,)?) -> &Self
) => {
$(#[$attrs])*
pub fn $n(&self, $($name: $ty),*) -> &Self {
self.diagnostic.$n($($name),*);
self
}
};
// Forward pattern for &mut self -> &mut Self
(
$(#[$attrs:meta])*
pub fn $n:ident(&mut self, $($name:ident: $ty:ty),* $(,)?) -> &mut Self
) => {
$(#[$attrs])*
pub fn $n(&mut self, $($name: $ty),*) -> &mut Self {
self.0.diagnostic.$n($($name),*);
self
}
};
// Forward pattern for &mut self -> &mut Self, with S: Into<MultiSpan>
// type parameter. No obvious way to make this more generic.
(
$(#[$attrs:meta])*
pub fn $n:ident<S: Into<MultiSpan>>(
&mut self,
$($name:ident: $ty:ty),*
$(,)?
) -> &mut Self
) => {
$(#[$attrs])*
pub fn $n<S: Into<MultiSpan>>(&mut self, $($name: $ty),*) -> &mut Self {
self.0.diagnostic.$n($($name),*);
self
}
};
}
impl<'a> Deref for DiagnosticBuilder<'a> {
type Target = Diagnostic;
fn deref(&self) -> &Diagnostic {
&self.0.diagnostic
}
}
impl<'a> DerefMut for DiagnosticBuilder<'a> {
fn deref_mut(&mut self) -> &mut Diagnostic {
&mut self.0.diagnostic
}
}
impl<'a> DiagnosticBuilder<'a> {
/// Emit the diagnostic.
pub fn emit(&mut self) {
self.0.handler.emit_diagnostic(&self);
self.cancel();
}
/// Emit the diagnostic unless `delay` is true,
/// in which case the emission will be delayed as a bug.
///
/// See `emit` and `delay_as_bug` for details.
pub fn emit_unless(&mut self, delay: bool) {
if delay {
self.delay_as_bug();
} else {
self.emit();
}
}
/// Stashes diagnostic for possible later improvement in a different,
/// later stage of the compiler. The diagnostic can be accessed with
/// the provided `span` and `key` through `.steal_diagnostic` on `Handler`.
///
/// As with `buffer`, this is unless the handler has disabled such buffering.
pub fn stash(self, span: Span, key: StashKey) {
if let Some((diag, handler)) = self.into_diagnostic() {
handler.stash_diagnostic(span, key, diag);
}
}
/// Converts the builder to a `Diagnostic` for later emission,
/// unless handler has disabled such buffering.
pub fn into_diagnostic(mut self) -> Option<(Diagnostic, &'a Handler)> {
if self.0.handler.flags.dont_buffer_diagnostics
|| self.0.handler.flags.treat_err_as_bug.is_some()
{
self.emit();
return None;
}
let handler = self.0.handler;
// We must use `Level::Cancelled` for `dummy` to avoid an ICE about an
// unused diagnostic.
let dummy = Diagnostic::new(Level::Cancelled, "");
let diagnostic = std::mem::replace(&mut self.0.diagnostic, dummy);
// Logging here is useful to help track down where in logs an error was
// actually emitted.
debug!("buffer: diagnostic={:?}", diagnostic);
Some((diagnostic, handler))
}
/// Buffers the diagnostic for later emission,
/// unless handler has disabled such buffering.
pub fn buffer(self, buffered_diagnostics: &mut Vec<Diagnostic>) {
buffered_diagnostics.extend(self.into_diagnostic().map(|(diag, _)| diag));
}
/// Convenience function for internal use, clients should use one of the
/// span_* methods instead.
pub fn sub<S: Into<MultiSpan>>(
&mut self,
level: Level,
message: &str,
span: Option<S>,
) -> &mut Self {
let span = span.map(|s| s.into()).unwrap_or_else(MultiSpan::new);
self.0.diagnostic.sub(level, message, span, None);
self
}
/// Delay emission of this diagnostic as a bug.
///
/// This can be useful in contexts where an error indicates a bug but
/// typically this only happens when other compilation errors have already
/// happened. In those cases this can be used to defer emission of this
/// diagnostic as a bug in the compiler only if no other errors have been
/// emitted.
///
/// In the meantime, though, callsites are required to deal with the "bug"
/// locally in whichever way makes the most sense.
pub fn delay_as_bug(&mut self) {
self.level = Level::Bug;
self.0.handler.delay_as_bug(self.0.diagnostic.clone());
self.cancel();
}
/// Adds 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. That means it will be shown together with the original
/// span/label, *not* a span added by one of the `span_{note,warn,help,suggestions}` methods.
///
/// This span is *not* considered a ["primary span"][`MultiSpan`]; only
/// the `Span` supplied when creating the diagnostic is primary.
///
/// [`MultiSpan`]: ../rustc_span/struct.MultiSpan.html
pub fn span_label(&mut self, span: Span, label: impl Into<String>) -> &mut Self {
self.0.diagnostic.span_label(span, label);
self
}
/// Labels all the given spans with the provided label.
/// See `span_label` for more information.
pub fn span_labels(
&mut self,
spans: impl IntoIterator<Item = Span>,
label: impl AsRef<str>,
) -> &mut Self {
let label = label.as_ref();
for span in spans {
self.0.diagnostic.span_label(span, label);
}
self
}
forward!(pub fn note_expected_found(
&mut self,
expected_label: &dyn fmt::Display,
expected: DiagnosticStyledString,
found_label: &dyn fmt::Display,
found: DiagnosticStyledString,
) -> &mut Self);
forward!(pub fn note_expected_found_extra(
&mut self,
expected_label: &dyn fmt::Display,
expected: DiagnosticStyledString,
found_label: &dyn fmt::Display,
found: DiagnosticStyledString,
expected_extra: &dyn fmt::Display,
found_extra: &dyn fmt::Display,
) -> &mut Self);
forward!(pub fn note_unsuccessfull_coercion(
&mut self,
expected: DiagnosticStyledString,
found: DiagnosticStyledString,
) -> &mut Self);
forward!(pub fn note(&mut self, msg: &str) -> &mut Self);
forward!(pub fn span_note<S: Into<MultiSpan>>(
&mut self,
sp: S,
msg: &str,
) -> &mut Self);
forward!(pub fn warn(&mut self, msg: &str) -> &mut Self);
forward!(pub fn span_warn<S: Into<MultiSpan>>(&mut self, sp: S, msg: &str) -> &mut Self);
forward!(pub fn help(&mut self, msg: &str) -> &mut Self);
forward!(pub fn span_help<S: Into<MultiSpan>>(
&mut self,
sp: S,
msg: &str,
) -> &mut Self);
pub fn multipart_suggestion(
&mut self,
msg: &str,
suggestion: Vec<(Span, String)>,
applicability: Applicability,
) -> &mut Self {
if !self.0.allow_suggestions {
return self;
}
self.0.diagnostic.multipart_suggestion(msg, suggestion, applicability);
self
}
pub fn multipart_suggestions(
&mut self,
msg: &str,
suggestions: Vec<Vec<(Span, String)>>,
applicability: Applicability,
) -> &mut Self {
if !self.0.allow_suggestions {
return self;
}
self.0.diagnostic.multipart_suggestions(msg, suggestions, applicability);
self
}
pub fn tool_only_multipart_suggestion(
&mut self,
msg: &str,
suggestion: Vec<(Span, String)>,
applicability: Applicability,
) -> &mut Self {
if !self.0.allow_suggestions {
return self;
}
self.0.diagnostic.tool_only_multipart_suggestion(msg, suggestion, applicability);
self
}
pub fn span_suggestion(
&mut self,
sp: Span,
msg: &str,
suggestion: String,
applicability: Applicability,
) -> &mut Self {
if !self.0.allow_suggestions {
return self;
}
self.0.diagnostic.span_suggestion(sp, msg, suggestion, applicability);
self
}
pub fn span_suggestions(
&mut self,
sp: Span,
msg: &str,
suggestions: impl Iterator<Item = String>,
applicability: Applicability,
) -> &mut Self {
if !self.0.allow_suggestions {
return self;
}
self.0.diagnostic.span_suggestions(sp, msg, suggestions, applicability);
self
}
pub fn span_suggestion_short(
&mut self,
sp: Span,
msg: &str,
suggestion: String,
applicability: Applicability,
) -> &mut Self {
if !self.0.allow_suggestions {
return self;
}
self.0.diagnostic.span_suggestion_short(sp, msg, suggestion, applicability);
self
}
pub fn span_suggestion_verbose(
&mut self,
sp: Span,
msg: &str,
suggestion: String,
applicability: Applicability,
) -> &mut Self {
if !self.0.allow_suggestions {
return self;
}
self.0.diagnostic.span_suggestion_verbose(sp, msg, suggestion, applicability);
self
}
pub fn span_suggestion_hidden(
&mut self,
sp: Span,
msg: &str,
suggestion: String,
applicability: Applicability,
) -> &mut Self {
if !self.0.allow_suggestions {
return self;
}
self.0.diagnostic.span_suggestion_hidden(sp, msg, suggestion, applicability);
self
}
pub fn tool_only_span_suggestion(
&mut self,
sp: Span,
msg: &str,
suggestion: String,
applicability: Applicability,
) -> &mut Self {
if !self.0.allow_suggestions {
return self;
}
self.0.diagnostic.tool_only_span_suggestion(sp, msg, suggestion, applicability);
self
}
forward!(pub fn set_span<S: Into<MultiSpan>>(&mut self, sp: S) -> &mut Self);
forward!(pub fn code(&mut self, s: DiagnosticId) -> &mut Self);
pub fn allow_suggestions(&mut self, allow: bool) -> &mut Self {
self.0.allow_suggestions = allow;
self
}
/// Convenience function for internal use, clients should use one of the
/// struct_* methods on Handler.
crate fn new(handler: &'a Handler, level: Level, message: &str) -> DiagnosticBuilder<'a> {
DiagnosticBuilder::new_with_code(handler, level, None, message)
}
/// Convenience function for internal use, clients should use one of the
/// struct_* methods on Handler.
crate fn new_with_code(
handler: &'a Handler,
level: Level,
code: Option<DiagnosticId>,
message: &str,
) -> DiagnosticBuilder<'a> {
let diagnostic = Diagnostic::new_with_code(level, code, message);
DiagnosticBuilder::new_diagnostic(handler, diagnostic)
}
/// Creates a new `DiagnosticBuilder` with an already constructed
/// diagnostic.
crate fn new_diagnostic(handler: &'a Handler, diagnostic: Diagnostic) -> DiagnosticBuilder<'a> {
debug!("Created new diagnostic");
DiagnosticBuilder(Box::new(DiagnosticBuilderInner {
handler,
diagnostic,
allow_suggestions: true,
}))
}
}
impl<'a> Debug for DiagnosticBuilder<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.diagnostic.fmt(f)
}
}
/// Destructor bomb - a `DiagnosticBuilder` must be either emitted or canceled
/// or we emit a bug.
impl<'a> Drop for DiagnosticBuilder<'a> {
fn drop(&mut self) {
if !panicking() && !self.cancelled() {
let mut db = DiagnosticBuilder::new(
self.0.handler,
Level::Bug,
"the following error was constructed but not emitted",
);
db.emit();
self.emit();
panic!();
}
}
}
#[macro_export]
macro_rules! struct_span_err {
($session:expr, $span:expr, $code:ident, $($message:tt)*) => ({
$session.struct_span_err_with_code(
$span,
&format!($($message)*),
$crate::error_code!($code),
)
})
}
#[macro_export]
macro_rules! error_code {
($code:ident) => {{ $crate::DiagnosticId::Error(stringify!($code).to_owned()) }};
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,446 @@
//! A JSON emitter for errors.
//!
//! This works by converting errors to a simplified structural format (see the
//! structs at the start of the file) and then serializing them. These should
//! contain as much information about the error as possible.
//!
//! The format of the JSON output should be considered *unstable*. For now the
//! structs at the end of this file (Diagnostic*) specify the error format.
// FIXME: spec the JSON output properly.
use rustc_span::source_map::{FilePathMapping, SourceMap};
use crate::emitter::{Emitter, HumanReadableErrorType};
use crate::registry::Registry;
use crate::{Applicability, DiagnosticId};
use crate::{CodeSuggestion, SubDiagnostic};
use rustc_data_structures::sync::Lrc;
use rustc_span::hygiene::ExpnData;
use rustc_span::{MultiSpan, Span, SpanLabel};
use std::io::{self, Write};
use std::path::Path;
use std::sync::{Arc, Mutex};
use std::vec;
use rustc_serialize::json::{as_json, as_pretty_json};
#[cfg(test)]
mod tests;
pub struct JsonEmitter {
dst: Box<dyn Write + Send>,
registry: Option<Registry>,
sm: Lrc<SourceMap>,
pretty: bool,
ui_testing: bool,
json_rendered: HumanReadableErrorType,
terminal_width: Option<usize>,
macro_backtrace: bool,
}
impl JsonEmitter {
pub fn stderr(
registry: Option<Registry>,
source_map: Lrc<SourceMap>,
pretty: bool,
json_rendered: HumanReadableErrorType,
terminal_width: Option<usize>,
macro_backtrace: bool,
) -> JsonEmitter {
JsonEmitter {
dst: Box::new(io::BufWriter::new(io::stderr())),
registry,
sm: source_map,
pretty,
ui_testing: false,
json_rendered,
terminal_width,
macro_backtrace,
}
}
pub fn basic(
pretty: bool,
json_rendered: HumanReadableErrorType,
terminal_width: Option<usize>,
macro_backtrace: bool,
) -> JsonEmitter {
let file_path_mapping = FilePathMapping::empty();
JsonEmitter::stderr(
None,
Lrc::new(SourceMap::new(file_path_mapping)),
pretty,
json_rendered,
terminal_width,
macro_backtrace,
)
}
pub fn new(
dst: Box<dyn Write + Send>,
registry: Option<Registry>,
source_map: Lrc<SourceMap>,
pretty: bool,
json_rendered: HumanReadableErrorType,
terminal_width: Option<usize>,
macro_backtrace: bool,
) -> JsonEmitter {
JsonEmitter {
dst,
registry,
sm: source_map,
pretty,
ui_testing: false,
json_rendered,
terminal_width,
macro_backtrace,
}
}
pub fn ui_testing(self, ui_testing: bool) -> Self {
Self { ui_testing, ..self }
}
}
impl Emitter for JsonEmitter {
fn emit_diagnostic(&mut self, diag: &crate::Diagnostic) {
let data = Diagnostic::from_errors_diagnostic(diag, self);
let result = if self.pretty {
writeln!(&mut self.dst, "{}", as_pretty_json(&data))
} else {
writeln!(&mut self.dst, "{}", as_json(&data))
}
.and_then(|_| self.dst.flush());
if let Err(e) = result {
panic!("failed to print diagnostics: {:?}", e);
}
}
fn emit_artifact_notification(&mut self, path: &Path, artifact_type: &str) {
let data = ArtifactNotification { artifact: path, emit: artifact_type };
let result = if self.pretty {
writeln!(&mut self.dst, "{}", as_pretty_json(&data))
} else {
writeln!(&mut self.dst, "{}", as_json(&data))
}
.and_then(|_| self.dst.flush());
if let Err(e) = result {
panic!("failed to print notification: {:?}", e);
}
}
fn source_map(&self) -> Option<&Lrc<SourceMap>> {
Some(&self.sm)
}
fn should_show_explain(&self) -> bool {
match self.json_rendered {
HumanReadableErrorType::Short(_) => false,
_ => true,
}
}
}
// The following data types are provided just for serialisation.
#[derive(Encodable)]
struct Diagnostic {
/// The primary error message.
message: String,
code: Option<DiagnosticCode>,
/// "error: internal compiler error", "error", "warning", "note", "help".
level: &'static str,
spans: Vec<DiagnosticSpan>,
/// Associated diagnostic messages.
children: Vec<Diagnostic>,
/// The message as rustc would render it.
rendered: Option<String>,
}
#[derive(Encodable)]
struct DiagnosticSpan {
file_name: String,
byte_start: u32,
byte_end: u32,
/// 1-based.
line_start: usize,
line_end: usize,
/// 1-based, character offset.
column_start: usize,
column_end: usize,
/// Is this a "primary" span -- meaning the point, or one of the points,
/// where the error occurred?
is_primary: bool,
/// Source text from the start of line_start to the end of line_end.
text: Vec<DiagnosticSpanLine>,
/// Label that should be placed at this location (if any)
label: Option<String>,
/// If we are suggesting a replacement, this will contain text
/// that should be sliced in atop this span.
suggested_replacement: Option<String>,
/// If the suggestion is approximate
suggestion_applicability: Option<Applicability>,
/// Macro invocations that created the code at this span, if any.
expansion: Option<Box<DiagnosticSpanMacroExpansion>>,
}
#[derive(Encodable)]
struct DiagnosticSpanLine {
text: String,
/// 1-based, character offset in self.text.
highlight_start: usize,
highlight_end: usize,
}
#[derive(Encodable)]
struct DiagnosticSpanMacroExpansion {
/// span where macro was applied to generate this code; note that
/// this may itself derive from a macro (if
/// `span.expansion.is_some()`)
span: DiagnosticSpan,
/// name of macro that was applied (e.g., "foo!" or "#[derive(Eq)]")
macro_decl_name: String,
/// span where macro was defined (if known)
def_site_span: DiagnosticSpan,
}
#[derive(Encodable)]
struct DiagnosticCode {
/// The code itself.
code: String,
/// An explanation for the code.
explanation: Option<&'static str>,
}
#[derive(Encodable)]
struct ArtifactNotification<'a> {
/// The path of the artifact.
artifact: &'a Path,
/// What kind of artifact we're emitting.
emit: &'a str,
}
impl Diagnostic {
fn from_errors_diagnostic(diag: &crate::Diagnostic, je: &JsonEmitter) -> Diagnostic {
let sugg = diag.suggestions.iter().map(|sugg| Diagnostic {
message: sugg.msg.clone(),
code: None,
level: "help",
spans: DiagnosticSpan::from_suggestion(sugg, je),
children: vec![],
rendered: None,
});
// generate regular command line output and store it in the json
// A threadsafe buffer for writing.
#[derive(Default, Clone)]
struct BufWriter(Arc<Mutex<Vec<u8>>>);
impl Write for BufWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.0.lock().unwrap().write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.0.lock().unwrap().flush()
}
}
let buf = BufWriter::default();
let output = buf.clone();
je.json_rendered
.new_emitter(
Box::new(buf),
Some(je.sm.clone()),
false,
je.terminal_width,
je.macro_backtrace,
)
.ui_testing(je.ui_testing)
.emit_diagnostic(diag);
let output = Arc::try_unwrap(output.0).unwrap().into_inner().unwrap();
let output = String::from_utf8(output).unwrap();
Diagnostic {
message: diag.message(),
code: DiagnosticCode::map_opt_string(diag.code.clone(), je),
level: diag.level.to_str(),
spans: DiagnosticSpan::from_multispan(&diag.span, je),
children: diag
.children
.iter()
.map(|c| Diagnostic::from_sub_diagnostic(c, je))
.chain(sugg)
.collect(),
rendered: Some(output),
}
}
fn from_sub_diagnostic(diag: &SubDiagnostic, je: &JsonEmitter) -> Diagnostic {
Diagnostic {
message: diag.message(),
code: None,
level: diag.level.to_str(),
spans: diag
.render_span
.as_ref()
.map(|sp| DiagnosticSpan::from_multispan(sp, je))
.unwrap_or_else(|| DiagnosticSpan::from_multispan(&diag.span, je)),
children: vec![],
rendered: None,
}
}
}
impl DiagnosticSpan {
fn from_span_label(
span: SpanLabel,
suggestion: Option<(&String, Applicability)>,
je: &JsonEmitter,
) -> DiagnosticSpan {
Self::from_span_etc(span.span, span.is_primary, span.label, suggestion, je)
}
fn from_span_etc(
span: Span,
is_primary: bool,
label: Option<String>,
suggestion: Option<(&String, Applicability)>,
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 = span.macro_backtrace();
DiagnosticSpan::from_span_full(span, is_primary, label, suggestion, backtrace, je)
}
fn from_span_full(
span: Span,
is_primary: bool,
label: Option<String>,
suggestion: Option<(&String, Applicability)>,
mut backtrace: impl Iterator<Item = ExpnData>,
je: &JsonEmitter,
) -> DiagnosticSpan {
let start = je.sm.lookup_char_pos(span.lo());
let end = je.sm.lookup_char_pos(span.hi());
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 =
Self::from_span_full(bt.def_site, false, None, None, vec![].into_iter(), je);
Box::new(DiagnosticSpanMacroExpansion {
span: call_site,
macro_decl_name: bt.kind.descr(),
def_site_span,
})
});
DiagnosticSpan {
file_name: start.file.name.to_string(),
byte_start: start.file.original_relative_byte_pos(span.lo()).0,
byte_end: start.file.original_relative_byte_pos(span.hi()).0,
line_start: start.line,
line_end: end.line,
column_start: start.col.0 + 1,
column_end: end.col.0 + 1,
is_primary,
text: DiagnosticSpanLine::from_span(span, je),
suggested_replacement: suggestion.map(|x| x.0.clone()),
suggestion_applicability: suggestion.map(|x| x.1),
expansion: backtrace_step,
label,
}
}
fn from_multispan(msp: &MultiSpan, je: &JsonEmitter) -> Vec<DiagnosticSpan> {
msp.span_labels()
.into_iter()
.map(|span_str| Self::from_span_label(span_str, None, je))
.collect()
}
fn from_suggestion(suggestion: &CodeSuggestion, je: &JsonEmitter) -> Vec<DiagnosticSpan> {
suggestion
.substitutions
.iter()
.flat_map(|substitution| {
substitution.parts.iter().map(move |suggestion_inner| {
let span_label =
SpanLabel { span: suggestion_inner.span, is_primary: true, label: None };
DiagnosticSpan::from_span_label(
span_label,
Some((&suggestion_inner.snippet, suggestion.applicability)),
je,
)
})
})
.collect()
}
}
impl DiagnosticSpanLine {
fn line_from_source_file(
sf: &rustc_span::SourceFile,
index: usize,
h_start: usize,
h_end: usize,
) -> DiagnosticSpanLine {
DiagnosticSpanLine {
text: sf.get_line(index).map_or(String::new(), |l| l.into_owned()),
highlight_start: h_start,
highlight_end: h_end,
}
}
/// Creates a list of DiagnosticSpanLines from span - each line with any part
/// of `span` gets a DiagnosticSpanLine, with the highlight indicating the
/// `span` within the line.
fn from_span(span: Span, je: &JsonEmitter) -> Vec<DiagnosticSpanLine> {
je.sm
.span_to_lines(span)
.map(|lines| {
// We can't get any lines if the source is unavailable.
if !je.sm.ensure_source_file_source_present(lines.file.clone()) {
return vec![];
}
let sf = &*lines.file;
lines
.lines
.iter()
.map(|line| {
DiagnosticSpanLine::line_from_source_file(
sf,
line.line_index,
line.start_col.0 + 1,
line.end_col.0 + 1,
)
})
.collect()
})
.unwrap_or_else(|_| vec![])
}
}
impl DiagnosticCode {
fn map_opt_string(s: Option<DiagnosticId>, je: &JsonEmitter) -> Option<DiagnosticCode> {
s.map(|s| {
let s = match s {
DiagnosticId::Error(s) => s,
DiagnosticId::Lint(s) => s,
};
let je_result =
je.registry.as_ref().map(|registry| registry.try_find_description(&s)).unwrap();
DiagnosticCode { code: s, explanation: je_result.unwrap_or(None) }
})
}
}

View file

@ -0,0 +1,204 @@
use super::*;
use crate::json::JsonEmitter;
use rustc_span::source_map::{FilePathMapping, SourceMap};
use crate::emitter::{ColorConfig, HumanReadableErrorType};
use crate::Handler;
use rustc_serialize::json::decode;
use rustc_span::{BytePos, Span};
use std::str;
#[derive(Decodable, Debug, PartialEq, Eq)]
struct TestData {
spans: Vec<SpanTestData>,
}
#[derive(Decodable, Debug, PartialEq, Eq)]
struct SpanTestData {
pub byte_start: u32,
pub byte_end: u32,
pub line_start: u32,
pub column_start: u32,
pub line_end: u32,
pub column_end: u32,
}
struct Shared<T> {
data: Arc<Mutex<T>>,
}
impl<T: Write> Write for Shared<T> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.data.lock().unwrap().write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.data.lock().unwrap().flush()
}
}
fn with_default_session_globals(f: impl FnOnce()) {
let session_globals = rustc_span::SessionGlobals::new(rustc_span::edition::DEFAULT_EDITION);
rustc_span::SESSION_GLOBALS.set(&session_globals, f);
}
/// Test the span yields correct positions in JSON.
fn test_positions(code: &str, span: (u32, u32), expected_output: SpanTestData) {
let expected_output = TestData { spans: vec![expected_output] };
with_default_session_globals(|| {
let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
sm.new_source_file(Path::new("test.rs").to_owned().into(), code.to_owned());
let output = Arc::new(Mutex::new(Vec::new()));
let je = JsonEmitter::new(
Box::new(Shared { data: output.clone() }),
None,
sm,
true,
HumanReadableErrorType::Short(ColorConfig::Never),
None,
false,
);
let span = Span::with_root_ctxt(BytePos(span.0), BytePos(span.1));
let handler = Handler::with_emitter(true, None, Box::new(je));
handler.span_err(span, "foo");
let bytes = output.lock().unwrap();
let actual_output = str::from_utf8(&bytes).unwrap();
let actual_output: TestData = decode(actual_output).unwrap();
assert_eq!(expected_output, actual_output)
})
}
#[test]
fn empty() {
test_positions(
" ",
(0, 1),
SpanTestData {
byte_start: 0,
byte_end: 1,
line_start: 1,
column_start: 1,
line_end: 1,
column_end: 2,
},
)
}
#[test]
fn bom() {
test_positions(
"\u{feff} ",
(0, 1),
SpanTestData {
byte_start: 3,
byte_end: 4,
line_start: 1,
column_start: 1,
line_end: 1,
column_end: 2,
},
)
}
#[test]
fn lf_newlines() {
test_positions(
"\nmod foo;\nmod bar;\n",
(5, 12),
SpanTestData {
byte_start: 5,
byte_end: 12,
line_start: 2,
column_start: 5,
line_end: 3,
column_end: 3,
},
)
}
#[test]
fn crlf_newlines() {
test_positions(
"\r\nmod foo;\r\nmod bar;\r\n",
(5, 12),
SpanTestData {
byte_start: 6,
byte_end: 14,
line_start: 2,
column_start: 5,
line_end: 3,
column_end: 3,
},
)
}
#[test]
fn crlf_newlines_with_bom() {
test_positions(
"\u{feff}\r\nmod foo;\r\nmod bar;\r\n",
(5, 12),
SpanTestData {
byte_start: 9,
byte_end: 17,
line_start: 2,
column_start: 5,
line_end: 3,
column_end: 3,
},
)
}
#[test]
fn span_before_crlf() {
test_positions(
"foo\r\nbar",
(2, 3),
SpanTestData {
byte_start: 2,
byte_end: 3,
line_start: 1,
column_start: 3,
line_end: 1,
column_end: 4,
},
)
}
#[test]
fn span_on_crlf() {
test_positions(
"foo\r\nbar",
(3, 4),
SpanTestData {
byte_start: 3,
byte_end: 5,
line_start: 1,
column_start: 4,
line_end: 2,
column_end: 1,
},
)
}
#[test]
fn span_after_crlf() {
test_positions(
"foo\r\nbar",
(4, 5),
SpanTestData {
byte_start: 5,
byte_end: 6,
line_start: 2,
column_start: 1,
line_end: 2,
column_end: 2,
},
)
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,93 @@
//! Bindings to acquire a global named lock.
//!
//! This is intended to be used to synchronize multiple compiler processes to
//! ensure that we can output complete errors without interleaving on Windows.
//! Note that this is currently only needed for allowing only one 32-bit MSVC
//! linker to execute at once on MSVC hosts, so this is only implemented for
//! `cfg(windows)`. Also note that this may not always be used on Windows,
//! only when targeting 32-bit MSVC.
//!
//! For more information about why this is necessary, see where this is called.
use std::any::Any;
#[cfg(windows)]
pub fn acquire_global_lock(name: &str) -> Box<dyn Any> {
use std::ffi::CString;
use std::io;
use winapi::shared::ntdef::HANDLE;
use winapi::um::handleapi::CloseHandle;
use winapi::um::synchapi::{CreateMutexA, ReleaseMutex, WaitForSingleObject};
use winapi::um::winbase::{INFINITE, WAIT_ABANDONED, WAIT_OBJECT_0};
struct Handle(HANDLE);
impl Drop for Handle {
fn drop(&mut self) {
unsafe {
CloseHandle(self.0);
}
}
}
struct Guard(Handle);
impl Drop for Guard {
fn drop(&mut self) {
unsafe {
ReleaseMutex((self.0).0);
}
}
}
let cname = CString::new(name).unwrap();
unsafe {
// Create a named mutex, with no security attributes and also not
// acquired when we create it.
//
// This will silently create one if it doesn't already exist, or it'll
// open up a handle to one if it already exists.
let mutex = CreateMutexA(std::ptr::null_mut(), 0, cname.as_ptr());
if mutex.is_null() {
panic!(
"failed to create global mutex named `{}`: {}",
name,
io::Error::last_os_error()
);
}
let mutex = Handle(mutex);
// Acquire the lock through `WaitForSingleObject`.
//
// A return value of `WAIT_OBJECT_0` means we successfully acquired it.
//
// A return value of `WAIT_ABANDONED` means that the previous holder of
// the thread exited without calling `ReleaseMutex`. This can happen,
// for example, when the compiler crashes or is interrupted via ctrl-c
// or the like. In this case, however, we are still transferred
// ownership of the lock so we continue.
//
// If an error happens.. well... that's surprising!
match WaitForSingleObject(mutex.0, INFINITE) {
WAIT_OBJECT_0 | WAIT_ABANDONED => {}
code => {
panic!(
"WaitForSingleObject failed on global mutex named \
`{}`: {} (ret={:x})",
name,
io::Error::last_os_error(),
code
);
}
}
// Return a guard which will call `ReleaseMutex` when dropped.
Box::new(Guard(mutex))
}
}
#[cfg(not(windows))]
pub fn acquire_global_lock(_name: &str) -> Box<dyn Any> {
Box::new(())
}

View file

@ -0,0 +1,29 @@
use rustc_data_structures::fx::FxHashMap;
#[derive(Debug)]
pub struct InvalidErrorCode;
#[derive(Clone)]
pub struct Registry {
long_descriptions: FxHashMap<&'static str, Option<&'static str>>,
}
impl Registry {
pub fn new(long_descriptions: &[(&'static str, Option<&'static str>)]) -> Registry {
Registry { long_descriptions: long_descriptions.iter().copied().collect() }
}
/// This will panic if an invalid error code is passed in
pub fn find_description(&self, code: &str) -> Option<&'static str> {
self.long_descriptions[code]
}
/// Returns `InvalidErrorCode` if the code requested does not exist in the
/// registry. Otherwise, returns an `Option` where `None` means the error
/// code is valid but has no extended information.
pub fn try_find_description(
&self,
code: &str,
) -> Result<Option<&'static str>, InvalidErrorCode> {
self.long_descriptions.get(code).copied().ok_or(InvalidErrorCode)
}
}

View file

@ -0,0 +1,190 @@
// Code for annotating snippets.
use crate::Level;
#[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
pub struct Line {
pub line_index: usize,
pub annotations: Vec<Annotation>,
}
#[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
pub struct MultilineAnnotation {
pub depth: usize,
pub line_start: usize,
pub line_end: usize,
pub start_col: usize,
pub end_col: usize,
pub is_primary: bool,
pub label: Option<String>,
pub overlaps_exactly: bool,
}
impl MultilineAnnotation {
pub fn increase_depth(&mut self) {
self.depth += 1;
}
/// Compare two `MultilineAnnotation`s considering only the `Span` they cover.
pub fn same_span(&self, other: &MultilineAnnotation) -> bool {
self.line_start == other.line_start
&& self.line_end == other.line_end
&& self.start_col == other.start_col
&& self.end_col == other.end_col
}
pub fn as_start(&self) -> Annotation {
Annotation {
start_col: self.start_col,
end_col: self.start_col + 1,
is_primary: self.is_primary,
label: None,
annotation_type: AnnotationType::MultilineStart(self.depth),
}
}
pub fn as_end(&self) -> Annotation {
Annotation {
start_col: self.end_col.saturating_sub(1),
end_col: self.end_col,
is_primary: self.is_primary,
label: self.label.clone(),
annotation_type: AnnotationType::MultilineEnd(self.depth),
}
}
pub fn as_line(&self) -> Annotation {
Annotation {
start_col: 0,
end_col: 0,
is_primary: self.is_primary,
label: None,
annotation_type: AnnotationType::MultilineLine(self.depth),
}
}
}
#[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
pub enum AnnotationType {
/// Annotation under a single line of code
Singleline,
/// Annotation enclosing the first and last character of a multiline span
Multiline(MultilineAnnotation),
// 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,
// | _________^ < MultilineStart
// x | | y), < MultilineLine
// | |______________^ 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
// Just a placeholder for the drawing algorithm, to know that it shouldn't skip the first 4
// and last 2 lines of code. The actual line is drawn in `emit_message_default` and not in
// `draw_multiline_line`.
MultilineLine(usize),
}
#[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
pub 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.
pub start_col: usize,
/// End column within the line (exclusive)
pub end_col: usize,
/// Is this annotation derived from primary span
pub is_primary: bool,
/// Optional label to display adjacent to the annotation.
pub label: Option<String>,
/// Is this a single line, multiline or multiline span minimized down to a
/// smaller span.
pub annotation_type: AnnotationType,
}
impl Annotation {
/// Whether this annotation is a vertical line placeholder.
pub fn is_line(&self) -> bool {
if let AnnotationType::MultilineLine(_) = self.annotation_type { true } else { false }
}
pub fn is_multiline(&self) -> bool {
match self.annotation_type {
AnnotationType::Multiline(_)
| AnnotationType::MultilineStart(_)
| AnnotationType::MultilineLine(_)
| AnnotationType::MultilineEnd(_) => true,
_ => false,
}
}
pub fn len(&self) -> usize {
// Account for usize underflows
if self.end_col > self.start_col {
self.end_col - self.start_col
} else {
self.start_col - self.end_col
}
}
pub fn has_label(&self) -> bool {
if let Some(ref label) = self.label {
// Consider labels with no text as effectively not being there
// to avoid weird output with unnecessary vertical lines, like:
//
// X | fn foo(x: u32) {
// | -------^------
// | | |
// | |
// |
//
// Note that this would be the complete output users would see.
!label.is_empty()
} else {
false
}
}
pub fn takes_space(&self) -> bool {
// Multiline annotations always have to keep vertical space.
match self.annotation_type {
AnnotationType::MultilineStart(_) | AnnotationType::MultilineEnd(_) => true,
_ => false,
}
}
}
#[derive(Debug)]
pub struct StyledString {
pub text: String,
pub style: Style,
}
#[derive(Copy, Clone, Debug, PartialEq, Hash, Encodable, Decodable)]
pub enum Style {
MainHeaderMsg,
HeaderMsg,
LineAndColumn,
LineNumber,
Quotation,
UnderlinePrimary,
UnderlineSecondary,
LabelPrimary,
LabelSecondary,
NoStyle,
Level(Level),
Highlight,
}

View file

@ -0,0 +1,151 @@
// Code for creating styled buffers
use crate::snippet::{Style, StyledString};
#[derive(Debug)]
pub struct StyledBuffer {
text: Vec<Vec<char>>,
styles: Vec<Vec<Style>>,
}
impl StyledBuffer {
pub fn new() -> StyledBuffer {
StyledBuffer { text: vec![], styles: vec![] }
}
fn replace_tabs(&mut self) {
for (line_pos, line) in self.text.iter_mut().enumerate() {
let mut tab_pos = vec![];
for (pos, c) in line.iter().enumerate() {
if *c == '\t' {
tab_pos.push(pos);
}
}
// start with the tabs at the end of the line to replace them with 4 space chars
for pos in tab_pos.iter().rev() {
assert_eq!(line.remove(*pos), '\t');
// fix the position of the style to match up after replacing the tabs
let s = self.styles[line_pos].remove(*pos);
for _ in 0..4 {
line.insert(*pos, ' ');
self.styles[line_pos].insert(*pos, s);
}
}
}
}
pub fn render(&mut self) -> Vec<Vec<StyledString>> {
let mut output: Vec<Vec<StyledString>> = vec![];
let mut styled_vec: Vec<StyledString> = vec![];
// before we render, replace tabs with spaces
self.replace_tabs();
for (row, row_style) in self.text.iter().zip(&self.styles) {
let mut current_style = Style::NoStyle;
let mut current_text = String::new();
for (&c, &s) in row.iter().zip(row_style) {
if s != current_style {
if !current_text.is_empty() {
styled_vec.push(StyledString { text: current_text, style: current_style });
}
current_style = s;
current_text = String::new();
}
current_text.push(c);
}
if !current_text.is_empty() {
styled_vec.push(StyledString { text: current_text, style: current_style });
}
// We're done with the row, push and keep going
output.push(styled_vec);
styled_vec = vec![];
}
output
}
fn ensure_lines(&mut self, line: usize) {
while line >= self.text.len() {
self.text.push(vec![]);
self.styles.push(vec![]);
}
}
pub fn putc(&mut self, line: usize, col: usize, chr: char, style: Style) {
self.ensure_lines(line);
if col < self.text[line].len() {
self.text[line][col] = chr;
self.styles[line][col] = style;
} else {
let mut i = self.text[line].len();
while i < col {
self.text[line].push(' ');
self.styles[line].push(Style::NoStyle);
i += 1;
}
self.text[line].push(chr);
self.styles[line].push(style);
}
}
pub 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;
}
}
pub fn prepend(&mut self, line: usize, string: &str, style: Style) {
self.ensure_lines(line);
let string_len = string.chars().count();
// Push the old content over to make room for new content
for _ in 0..string_len {
self.styles[line].insert(0, Style::NoStyle);
self.text[line].insert(0, ' ');
}
self.puts(line, 0, string, style);
}
pub 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);
}
}
pub fn num_lines(&self) -> usize {
self.text.len()
}
pub fn set_style_range(
&mut self,
line: usize,
col_start: usize,
col_end: usize,
style: Style,
overwrite: bool,
) {
for col in col_start..col_end {
self.set_style(line, col, style, overwrite);
}
}
pub fn set_style(&mut self, line: usize, col: usize, style: Style, overwrite: bool) {
if let Some(ref mut line) = self.styles.get_mut(line) {
if let Some(s) = line.get_mut(col) {
if *s == Style::NoStyle || *s == Style::Quotation || overwrite {
*s = style;
}
}
}
}
}