summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_errors/src/json.rs
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_errors/src/json.rs')
-rw-r--r--compiler/rustc_errors/src/json.rs561
1 files changed, 561 insertions, 0 deletions
diff --git a/compiler/rustc_errors/src/json.rs b/compiler/rustc_errors/src/json.rs
new file mode 100644
index 000000000..b8cd334b4
--- /dev/null
+++ b/compiler/rustc_errors/src/json.rs
@@ -0,0 +1,561 @@
+//! 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::DiagnosticId;
+use crate::{
+ CodeSuggestion, FluentBundle, LazyFallbackBundle, MultiSpan, SpanLabel, SubDiagnostic,
+};
+use rustc_lint_defs::Applicability;
+
+use rustc_data_structures::sync::Lrc;
+use rustc_error_messages::FluentArgs;
+use rustc_span::hygiene::ExpnData;
+use rustc_span::Span;
+use std::io::{self, Write};
+use std::path::Path;
+use std::sync::{Arc, Mutex};
+use std::vec;
+
+use serde::Serialize;
+
+#[cfg(test)]
+mod tests;
+
+pub struct JsonEmitter {
+ dst: Box<dyn Write + Send>,
+ registry: Option<Registry>,
+ sm: Lrc<SourceMap>,
+ fluent_bundle: Option<Lrc<FluentBundle>>,
+ fallback_bundle: LazyFallbackBundle,
+ pretty: bool,
+ ui_testing: bool,
+ json_rendered: HumanReadableErrorType,
+ diagnostic_width: Option<usize>,
+ macro_backtrace: bool,
+}
+
+impl JsonEmitter {
+ pub fn stderr(
+ registry: Option<Registry>,
+ source_map: Lrc<SourceMap>,
+ fluent_bundle: Option<Lrc<FluentBundle>>,
+ fallback_bundle: LazyFallbackBundle,
+ pretty: bool,
+ json_rendered: HumanReadableErrorType,
+ diagnostic_width: Option<usize>,
+ macro_backtrace: bool,
+ ) -> JsonEmitter {
+ JsonEmitter {
+ dst: Box::new(io::BufWriter::new(io::stderr())),
+ registry,
+ sm: source_map,
+ fluent_bundle,
+ fallback_bundle,
+ pretty,
+ ui_testing: false,
+ json_rendered,
+ diagnostic_width,
+ macro_backtrace,
+ }
+ }
+
+ pub fn basic(
+ pretty: bool,
+ json_rendered: HumanReadableErrorType,
+ fluent_bundle: Option<Lrc<FluentBundle>>,
+ fallback_bundle: LazyFallbackBundle,
+ diagnostic_width: Option<usize>,
+ macro_backtrace: bool,
+ ) -> JsonEmitter {
+ let file_path_mapping = FilePathMapping::empty();
+ JsonEmitter::stderr(
+ None,
+ Lrc::new(SourceMap::new(file_path_mapping)),
+ fluent_bundle,
+ fallback_bundle,
+ pretty,
+ json_rendered,
+ diagnostic_width,
+ macro_backtrace,
+ )
+ }
+
+ pub fn new(
+ dst: Box<dyn Write + Send>,
+ registry: Option<Registry>,
+ source_map: Lrc<SourceMap>,
+ fluent_bundle: Option<Lrc<FluentBundle>>,
+ fallback_bundle: LazyFallbackBundle,
+ pretty: bool,
+ json_rendered: HumanReadableErrorType,
+ diagnostic_width: Option<usize>,
+ macro_backtrace: bool,
+ ) -> JsonEmitter {
+ JsonEmitter {
+ dst,
+ registry,
+ sm: source_map,
+ fluent_bundle,
+ fallback_bundle,
+ pretty,
+ ui_testing: false,
+ json_rendered,
+ diagnostic_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, "{}", serde_json::to_string_pretty(&data).unwrap())
+ } else {
+ writeln!(&mut self.dst, "{}", serde_json::to_string(&data).unwrap())
+ }
+ .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, "{}", serde_json::to_string_pretty(&data).unwrap())
+ } else {
+ writeln!(&mut self.dst, "{}", serde_json::to_string(&data).unwrap())
+ }
+ .and_then(|_| self.dst.flush());
+ if let Err(e) = result {
+ panic!("failed to print notification: {:?}", e);
+ }
+ }
+
+ fn emit_future_breakage_report(&mut self, diags: Vec<crate::Diagnostic>) {
+ let data: Vec<FutureBreakageItem> = diags
+ .into_iter()
+ .map(|mut diag| {
+ if diag.level == crate::Level::Allow {
+ diag.level = crate::Level::Warning(None);
+ }
+ FutureBreakageItem { diagnostic: Diagnostic::from_errors_diagnostic(&diag, self) }
+ })
+ .collect();
+ let report = FutureIncompatReport { future_incompat_report: data };
+ let result = if self.pretty {
+ writeln!(&mut self.dst, "{}", serde_json::to_string_pretty(&report).unwrap())
+ } else {
+ writeln!(&mut self.dst, "{}", serde_json::to_string(&report).unwrap())
+ }
+ .and_then(|_| self.dst.flush());
+ if let Err(e) = result {
+ panic!("failed to print future breakage report: {:?}", e);
+ }
+ }
+
+ fn emit_unused_externs(&mut self, lint_level: rustc_lint_defs::Level, unused_externs: &[&str]) {
+ let lint_level = lint_level.as_str();
+ let data = UnusedExterns { lint_level, unused_extern_names: unused_externs };
+ let result = if self.pretty {
+ writeln!(&mut self.dst, "{}", serde_json::to_string_pretty(&data).unwrap())
+ } else {
+ writeln!(&mut self.dst, "{}", serde_json::to_string(&data).unwrap())
+ }
+ .and_then(|_| self.dst.flush());
+ if let Err(e) = result {
+ panic!("failed to print unused externs: {:?}", e);
+ }
+ }
+
+ fn source_map(&self) -> Option<&Lrc<SourceMap>> {
+ Some(&self.sm)
+ }
+
+ fn fluent_bundle(&self) -> Option<&Lrc<FluentBundle>> {
+ self.fluent_bundle.as_ref()
+ }
+
+ fn fallback_fluent_bundle(&self) -> &FluentBundle {
+ &**self.fallback_bundle
+ }
+
+ fn should_show_explain(&self) -> bool {
+ !matches!(self.json_rendered, HumanReadableErrorType::Short(_))
+ }
+}
+
+// The following data types are provided just for serialisation.
+
+#[derive(Serialize)]
+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(Serialize)]
+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(Serialize)]
+struct DiagnosticSpanLine {
+ text: String,
+
+ /// 1-based, character offset in self.text.
+ highlight_start: usize,
+
+ highlight_end: usize,
+}
+
+#[derive(Serialize)]
+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(Serialize)]
+struct DiagnosticCode {
+ /// The code itself.
+ code: String,
+ /// An explanation for the code.
+ explanation: Option<&'static str>,
+}
+
+#[derive(Serialize)]
+struct ArtifactNotification<'a> {
+ /// The path of the artifact.
+ artifact: &'a Path,
+ /// What kind of artifact we're emitting.
+ emit: &'a str,
+}
+
+#[derive(Serialize)]
+struct FutureBreakageItem {
+ diagnostic: Diagnostic,
+}
+
+#[derive(Serialize)]
+struct FutureIncompatReport {
+ future_incompat_report: Vec<FutureBreakageItem>,
+}
+
+// NOTE: Keep this in sync with the equivalent structs in rustdoc's
+// doctest component (as well as cargo).
+// We could unify this struct the one in rustdoc but they have different
+// ownership semantics, so doing so would create wasteful allocations.
+#[derive(Serialize)]
+struct UnusedExterns<'a, 'b, 'c> {
+ /// The severity level of the unused dependencies lint
+ lint_level: &'a str,
+ /// List of unused externs by their names.
+ unused_extern_names: &'b [&'c str],
+}
+
+impl Diagnostic {
+ fn from_errors_diagnostic(diag: &crate::Diagnostic, je: &JsonEmitter) -> Diagnostic {
+ let args = je.to_fluent_args(diag.args());
+ let sugg = diag.suggestions.iter().flatten().map(|sugg| {
+ let translated_message = je.translate_message(&sugg.msg, &args);
+ Diagnostic {
+ message: translated_message.to_string(),
+ code: None,
+ level: "help",
+ spans: DiagnosticSpan::from_suggestion(sugg, &args, 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()),
+ je.fluent_bundle.clone(),
+ je.fallback_bundle.clone(),
+ false,
+ je.diagnostic_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();
+
+ let translated_message = je.translate_messages(&diag.message, &args);
+ Diagnostic {
+ message: translated_message.to_string(),
+ code: DiagnosticCode::map_opt_string(diag.code.clone(), je),
+ level: diag.level.to_str(),
+ spans: DiagnosticSpan::from_multispan(&diag.span, &args, je),
+ children: diag
+ .children
+ .iter()
+ .map(|c| Diagnostic::from_sub_diagnostic(c, &args, je))
+ .chain(sugg)
+ .collect(),
+ rendered: Some(output),
+ }
+ }
+
+ fn from_sub_diagnostic(
+ diag: &SubDiagnostic,
+ args: &FluentArgs<'_>,
+ je: &JsonEmitter,
+ ) -> Diagnostic {
+ let translated_message = je.translate_messages(&diag.message, args);
+ Diagnostic {
+ message: translated_message.to_string(),
+ code: None,
+ level: diag.level.to_str(),
+ spans: diag
+ .render_span
+ .as_ref()
+ .map(|sp| DiagnosticSpan::from_multispan(sp, args, je))
+ .unwrap_or_else(|| DiagnosticSpan::from_multispan(&diag.span, args, je)),
+ children: vec![],
+ rendered: None,
+ }
+ }
+}
+
+impl DiagnosticSpan {
+ fn from_span_label(
+ span: SpanLabel,
+ suggestion: Option<(&String, Applicability)>,
+ args: &FluentArgs<'_>,
+ je: &JsonEmitter,
+ ) -> DiagnosticSpan {
+ Self::from_span_etc(
+ span.span,
+ span.is_primary,
+ span.label.as_ref().map(|m| je.translate_message(m, args)).map(|m| m.to_string()),
+ 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(
+ je.sm.guess_head_span(bt.def_site),
+ false,
+ None,
+ None,
+ [].into_iter(),
+ je,
+ );
+ Box::new(DiagnosticSpanMacroExpansion {
+ span: call_site,
+ macro_decl_name: bt.kind.descr(),
+ def_site_span,
+ })
+ });
+
+ DiagnosticSpan {
+ file_name: je.sm.filename_for_diagnostics(&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,
+ args: &FluentArgs<'_>,
+ je: &JsonEmitter,
+ ) -> Vec<DiagnosticSpan> {
+ msp.span_labels()
+ .into_iter()
+ .map(|span_str| Self::from_span_label(span_str, None, args, je))
+ .collect()
+ }
+
+ fn from_suggestion(
+ suggestion: &CodeSuggestion,
+ args: &FluentArgs<'_>,
+ 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)),
+ args,
+ 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_else(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 { name, .. } => name,
+ };
+ let je_result =
+ je.registry.as_ref().map(|registry| registry.try_find_description(&s)).unwrap();
+
+ DiagnosticCode { code: s, explanation: je_result.unwrap_or(None) }
+ })
+ }
+}