diff options
| author | bors <bors@rust-lang.org> | 2020-08-30 15:57:57 +0000 | 
|---|---|---|
| committer | bors <bors@rust-lang.org> | 2020-08-30 15:57:57 +0000 | 
| commit | 85fbf49ce0e2274d0acf798f6e703747674feec3 (patch) | |
| tree | 158a05eb3f204a8e72939b58427d0c2787a4eade /compiler/rustc_errors/src/json.rs | |
| parent | db534b3ac286cf45688c3bbae6aa6e77439e52d2 (diff) | |
| parent | 9e5f7d5631b8f4009ac1c693e585d4b7108d4275 (diff) | |
| download | rust-85fbf49ce0e2274d0acf798f6e703747674feec3.tar.gz rust-85fbf49ce0e2274d0acf798f6e703747674feec3.zip | |
Auto merge of #74862 - mark-i-m:mv-compiler, r=petrochenkov
Move almost all compiler crates to compiler/ This PR implements https://github.com/rust-lang/compiler-team/issues/336 and moves all `rustc_*` crates from `src` to the new `compiler` directory. `librustc_foo` directories are renamed to `rustc_foo`. `src` directories are introduced inside `rustc_*` directories to mirror the scheme already use for `library` crates.
Diffstat (limited to 'compiler/rustc_errors/src/json.rs')
| -rw-r--r-- | compiler/rustc_errors/src/json.rs | 446 | 
1 files changed, 446 insertions, 0 deletions
| diff --git a/compiler/rustc_errors/src/json.rs b/compiler/rustc_errors/src/json.rs new file mode 100644 index 00000000000..750d36d3d89 --- /dev/null +++ b/compiler/rustc_errors/src/json.rs @@ -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) } + }) + } +} | 
