1 //! A JSON emitter for errors.
3 //! This works by converting errors to a simplified structural format (see the
4 //! structs at the start of the file) and then serializing them. These should
5 //! contain as much information about the error as possible.
7 //! The format of the JSON output should be considered *unstable*. For now the
8 //! structs at the end of this file (Diagnostic*) specify the error format.
10 // FIXME: spec the JSON output properly.
12 use rustc_span
::source_map
::{FilePathMapping, SourceMap}
;
14 use crate::emitter
::{Emitter, HumanReadableErrorType}
;
15 use crate::registry
::Registry
;
16 use crate::DiagnosticId
;
17 use crate::ToolMetadata
;
18 use crate::{CodeSuggestion, SubDiagnostic}
;
19 use rustc_lint_defs
::Applicability
;
21 use rustc_data_structures
::sync
::Lrc
;
22 use rustc_span
::hygiene
::ExpnData
;
23 use rustc_span
::{MultiSpan, Span, SpanLabel}
;
24 use std
::io
::{self, Write}
;
26 use std
::sync
::{Arc, Mutex}
;
29 use rustc_serialize
::json
::{as_json, as_pretty_json}
;
30 use rustc_serialize
::{Encodable, Encoder}
;
35 pub struct JsonEmitter
{
36 dst
: Box
<dyn Write
+ Send
>,
37 registry
: Option
<Registry
>,
41 json_rendered
: HumanReadableErrorType
,
42 terminal_width
: Option
<usize>,
43 macro_backtrace
: bool
,
48 registry
: Option
<Registry
>,
49 source_map
: Lrc
<SourceMap
>,
51 json_rendered
: HumanReadableErrorType
,
52 terminal_width
: Option
<usize>,
53 macro_backtrace
: bool
,
56 dst
: Box
::new(io
::BufWriter
::new(io
::stderr())),
69 json_rendered
: HumanReadableErrorType
,
70 terminal_width
: Option
<usize>,
71 macro_backtrace
: bool
,
73 let file_path_mapping
= FilePathMapping
::empty();
76 Lrc
::new(SourceMap
::new(file_path_mapping
)),
85 dst
: Box
<dyn Write
+ Send
>,
86 registry
: Option
<Registry
>,
87 source_map
: Lrc
<SourceMap
>,
89 json_rendered
: HumanReadableErrorType
,
90 terminal_width
: Option
<usize>,
91 macro_backtrace
: bool
,
105 pub fn ui_testing(self, ui_testing
: bool
) -> Self {
106 Self { ui_testing, ..self }
110 impl Emitter
for JsonEmitter
{
111 fn emit_diagnostic(&mut self, diag
: &crate::Diagnostic
) {
112 let data
= Diagnostic
::from_errors_diagnostic(diag
, self);
113 let result
= if self.pretty
{
114 writeln
!(&mut self.dst
, "{}", as_pretty_json(&data
))
116 writeln
!(&mut self.dst
, "{}", as_json(&data
))
118 .and_then(|_
| self.dst
.flush());
119 if let Err(e
) = result
{
120 panic
!("failed to print diagnostics: {:?}", e
);
124 fn emit_artifact_notification(&mut self, path
: &Path
, artifact_type
: &str) {
125 let data
= ArtifactNotification { artifact: path, emit: artifact_type }
;
126 let result
= if self.pretty
{
127 writeln
!(&mut self.dst
, "{}", as_pretty_json(&data
))
129 writeln
!(&mut self.dst
, "{}", as_json(&data
))
131 .and_then(|_
| self.dst
.flush());
132 if let Err(e
) = result
{
133 panic
!("failed to print notification: {:?}", e
);
137 fn emit_future_breakage_report(&mut self, diags
: Vec
<crate::Diagnostic
>) {
138 let data
: Vec
<FutureBreakageItem
> = diags
141 if diag
.level
== crate::Level
::Allow
{
142 diag
.level
= crate::Level
::Warning
;
144 FutureBreakageItem { diagnostic: Diagnostic::from_errors_diagnostic(&diag, self) }
147 let report
= FutureIncompatReport { future_incompat_report: data }
;
148 let result
= if self.pretty
{
149 writeln
!(&mut self.dst
, "{}", as_pretty_json(&report
))
151 writeln
!(&mut self.dst
, "{}", as_json(&report
))
153 .and_then(|_
| self.dst
.flush());
154 if let Err(e
) = result
{
155 panic
!("failed to print future breakage report: {:?}", e
);
159 fn emit_unused_externs(&mut self, lint_level
: &str, unused_externs
: &[&str]) {
160 let data
= UnusedExterns { lint_level, unused_extern_names: unused_externs }
;
161 let result
= if self.pretty
{
162 writeln
!(&mut self.dst
, "{}", as_pretty_json(&data
))
164 writeln
!(&mut self.dst
, "{}", as_json(&data
))
166 .and_then(|_
| self.dst
.flush());
167 if let Err(e
) = result
{
168 panic
!("failed to print unused externs: {:?}", e
);
172 fn source_map(&self) -> Option
<&Lrc
<SourceMap
>> {
176 fn should_show_explain(&self) -> bool
{
177 !matches
!(self.json_rendered
, HumanReadableErrorType
::Short(_
))
181 // The following data types are provided just for serialisation.
183 // NOTE: this has a manual implementation of Encodable which needs to be updated in
186 /// The primary error message.
188 code
: Option
<DiagnosticCode
>,
189 /// "error: internal compiler error", "error", "warning", "note", "help".
191 spans
: Vec
<DiagnosticSpan
>,
192 /// Associated diagnostic messages.
193 children
: Vec
<Diagnostic
>,
194 /// The message as rustc would render it.
195 rendered
: Option
<String
>,
196 /// Extra tool metadata
197 tool_metadata
: ToolMetadata
,
200 macro_rules
! encode_fields
{
202 $enc
:expr
, // encoder
203 $idx
:expr
, // starting field index
204 $
struct:expr
, // struct we're serializing
205 $struct_name
:ident
, // struct name
206 [ $
($name
:ident
),+$
(,)?
], // fields to encode
207 [ $
($ignore
:ident
),+$
(,)?
] // fields we're skipping
210 // Pattern match to make sure all fields are accounted for
211 let $struct_name { $($name,)+ $($ignore: _,)+ }
= $
struct;
214 $enc
.emit_struct_field(
217 |enc
| $name
.encode(enc
),
226 // Special-case encoder to skip tool_metadata if not set
227 impl<E
: Encoder
> Encodable
<E
> for Diagnostic
{
228 fn encode(&self, s
: &mut E
) -> Result
<(), E
::Error
> {
229 s
.emit_struct(false, |s
| {
232 idx
= encode_fields
!(
237 [message
, code
, level
, spans
, children
, rendered
],
240 if self.tool_metadata
.is_set() {
241 idx
= encode_fields
!(
247 [message
, code
, level
, spans
, children
, rendered
]
258 struct DiagnosticSpan
{
265 /// 1-based, character offset.
268 /// Is this a "primary" span -- meaning the point, or one of the points,
269 /// where the error occurred?
271 /// Source text from the start of line_start to the end of line_end.
272 text
: Vec
<DiagnosticSpanLine
>,
273 /// Label that should be placed at this location (if any)
274 label
: Option
<String
>,
275 /// If we are suggesting a replacement, this will contain text
276 /// that should be sliced in atop this span.
277 suggested_replacement
: Option
<String
>,
278 /// If the suggestion is approximate
279 suggestion_applicability
: Option
<Applicability
>,
280 /// Macro invocations that created the code at this span, if any.
281 expansion
: Option
<Box
<DiagnosticSpanMacroExpansion
>>,
285 struct DiagnosticSpanLine
{
288 /// 1-based, character offset in self.text.
289 highlight_start
: usize,
291 highlight_end
: usize,
295 struct DiagnosticSpanMacroExpansion
{
296 /// span where macro was applied to generate this code; note that
297 /// this may itself derive from a macro (if
298 /// `span.expansion.is_some()`)
299 span
: DiagnosticSpan
,
301 /// name of macro that was applied (e.g., "foo!" or "#[derive(Eq)]")
302 macro_decl_name
: String
,
304 /// span where macro was defined (if known)
305 def_site_span
: DiagnosticSpan
,
309 struct DiagnosticCode
{
312 /// An explanation for the code.
313 explanation
: Option
<&'
static str>,
317 struct ArtifactNotification
<'a
> {
318 /// The path of the artifact.
320 /// What kind of artifact we're emitting.
325 struct FutureBreakageItem
{
326 diagnostic
: Diagnostic
,
330 struct FutureIncompatReport
{
331 future_incompat_report
: Vec
<FutureBreakageItem
>,
334 // NOTE: Keep this in sync with the equivalent structs in rustdoc's
335 // doctest component (as well as cargo).
336 // We could unify this struct the one in rustdoc but they have different
337 // ownership semantics, so doing so would create wasteful allocations.
339 struct UnusedExterns
<'a
, 'b
, 'c
> {
340 /// The severity level of the unused dependencies lint
342 /// List of unused externs by their names.
343 unused_extern_names
: &'b
[&'c
str],
347 fn from_errors_diagnostic(diag
: &crate::Diagnostic
, je
: &JsonEmitter
) -> Diagnostic
{
348 let sugg
= diag
.suggestions
.iter().map(|sugg
| Diagnostic
{
349 message
: sugg
.msg
.clone(),
352 spans
: DiagnosticSpan
::from_suggestion(sugg
, je
),
355 tool_metadata
: sugg
.tool_metadata
.clone(),
358 // generate regular command line output and store it in the json
360 // A threadsafe buffer for writing.
361 #[derive(Default, Clone)]
362 struct BufWriter(Arc
<Mutex
<Vec
<u8>>>);
364 impl Write
for BufWriter
{
365 fn write(&mut self, buf
: &[u8]) -> io
::Result
<usize> {
366 self.0.lock().unwrap().write(buf
)
368 fn flush(&mut self) -> io
::Result
<()> {
369 self.0.lock().unwrap().flush()
372 let buf
= BufWriter
::default();
373 let output
= buf
.clone();
382 .ui_testing(je
.ui_testing
)
383 .emit_diagnostic(diag
);
384 let output
= Arc
::try_unwrap(output
.0).unwrap().into_inner().unwrap();
385 let output
= String
::from_utf8(output
).unwrap();
388 message
: diag
.message(),
389 code
: DiagnosticCode
::map_opt_string(diag
.code
.clone(), je
),
390 level
: diag
.level
.to_str(),
391 spans
: DiagnosticSpan
::from_multispan(&diag
.span
, je
),
395 .map(|c
| Diagnostic
::from_sub_diagnostic(c
, je
))
398 rendered
: Some(output
),
399 tool_metadata
: ToolMetadata
::default(),
403 fn from_sub_diagnostic(diag
: &SubDiagnostic
, je
: &JsonEmitter
) -> Diagnostic
{
405 message
: diag
.message(),
407 level
: diag
.level
.to_str(),
411 .map(|sp
| DiagnosticSpan
::from_multispan(sp
, je
))
412 .unwrap_or_else(|| DiagnosticSpan
::from_multispan(&diag
.span
, je
)),
415 tool_metadata
: ToolMetadata
::default(),
420 impl DiagnosticSpan
{
423 suggestion
: Option
<(&String
, Applicability
)>,
425 ) -> DiagnosticSpan
{
426 Self::from_span_etc(span
.span
, span
.is_primary
, span
.label
, suggestion
, je
)
432 label
: Option
<String
>,
433 suggestion
: Option
<(&String
, Applicability
)>,
435 ) -> DiagnosticSpan
{
436 // obtain the full backtrace from the `macro_backtrace`
437 // helper; in some ways, it'd be better to expand the
438 // backtrace ourselves, but the `macro_backtrace` helper makes
439 // some decision, such as dropping some frames, and I don't
440 // want to duplicate that logic here.
441 let backtrace
= span
.macro_backtrace();
442 DiagnosticSpan
::from_span_full(span
, is_primary
, label
, suggestion
, backtrace
, je
)
448 label
: Option
<String
>,
449 suggestion
: Option
<(&String
, Applicability
)>,
450 mut backtrace
: impl Iterator
<Item
= ExpnData
>,
452 ) -> DiagnosticSpan
{
453 let start
= je
.sm
.lookup_char_pos(span
.lo());
454 let end
= je
.sm
.lookup_char_pos(span
.hi());
455 let backtrace_step
= backtrace
.next().map(|bt
| {
456 let call_site
= Self::from_span_full(bt
.call_site
, false, None
, None
, backtrace
, je
);
458 Self::from_span_full(bt
.def_site
, false, None
, None
, vec
![].into_iter(), je
);
459 Box
::new(DiagnosticSpanMacroExpansion
{
461 macro_decl_name
: bt
.kind
.descr(),
467 file_name
: start
.file
.name
.prefer_local().to_string(),
468 byte_start
: start
.file
.original_relative_byte_pos(span
.lo()).0,
469 byte_end
: start
.file
.original_relative_byte_pos(span
.hi()).0,
470 line_start
: start
.line
,
472 column_start
: start
.col
.0 + 1,
473 column_end
: end
.col
.0 + 1,
475 text
: DiagnosticSpanLine
::from_span(span
, je
),
476 suggested_replacement
: suggestion
.map(|x
| x
.0.clone()),
477 suggestion_applicability
: suggestion
.map(|x
| x
.1),
478 expansion
: backtrace_step
,
483 fn from_multispan(msp
: &MultiSpan
, je
: &JsonEmitter
) -> Vec
<DiagnosticSpan
> {
486 .map(|span_str
| Self::from_span_label(span_str
, None
, je
))
490 fn from_suggestion(suggestion
: &CodeSuggestion
, je
: &JsonEmitter
) -> Vec
<DiagnosticSpan
> {
494 .flat_map(|substitution
| {
495 substitution
.parts
.iter().map(move |suggestion_inner
| {
497 SpanLabel { span: suggestion_inner.span, is_primary: true, label: None }
;
498 DiagnosticSpan
::from_span_label(
500 Some((&suggestion_inner
.snippet
, suggestion
.applicability
)),
509 impl DiagnosticSpanLine
{
510 fn line_from_source_file(
511 sf
: &rustc_span
::SourceFile
,
515 ) -> DiagnosticSpanLine
{
517 text
: sf
.get_line(index
).map_or_else(String
::new
, |l
| l
.into_owned()),
518 highlight_start
: h_start
,
519 highlight_end
: h_end
,
523 /// Creates a list of DiagnosticSpanLines from span - each line with any part
524 /// of `span` gets a DiagnosticSpanLine, with the highlight indicating the
525 /// `span` within the line.
526 fn from_span(span
: Span
, je
: &JsonEmitter
) -> Vec
<DiagnosticSpanLine
> {
530 // We can't get any lines if the source is unavailable.
531 if !je
.sm
.ensure_source_file_source_present(lines
.file
.clone()) {
535 let sf
= &*lines
.file
;
540 DiagnosticSpanLine
::line_from_source_file(
543 line
.start_col
.0 + 1,
549 .unwrap_or_else(|_
| vec
![])
553 impl DiagnosticCode
{
554 fn map_opt_string(s
: Option
<DiagnosticId
>, je
: &JsonEmitter
) -> Option
<DiagnosticCode
> {
557 DiagnosticId
::Error(s
) => s
,
558 DiagnosticId
::Lint { name, .. }
=> name
,
561 je
.registry
.as_ref().map(|registry
| registry
.try_find_description(&s
)).unwrap();
563 DiagnosticCode { code: s, explanation: je_result.unwrap_or(None) }