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::translation
::Translate
;
17 use crate::DiagnosticId
;
19 CodeSuggestion
, FluentBundle
, LazyFallbackBundle
, MultiSpan
, SpanLabel
, SubDiagnostic
,
21 use rustc_lint_defs
::Applicability
;
23 use rustc_data_structures
::sync
::Lrc
;
24 use rustc_error_messages
::FluentArgs
;
25 use rustc_span
::hygiene
::ExpnData
;
27 use std
::io
::{self, Write}
;
29 use std
::sync
::{Arc, Mutex}
;
37 pub struct JsonEmitter
{
38 dst
: Box
<dyn Write
+ Send
>,
39 registry
: Option
<Registry
>,
41 fluent_bundle
: Option
<Lrc
<FluentBundle
>>,
42 fallback_bundle
: LazyFallbackBundle
,
45 json_rendered
: HumanReadableErrorType
,
46 diagnostic_width
: Option
<usize>,
47 macro_backtrace
: bool
,
52 registry
: Option
<Registry
>,
53 source_map
: Lrc
<SourceMap
>,
54 fluent_bundle
: Option
<Lrc
<FluentBundle
>>,
55 fallback_bundle
: LazyFallbackBundle
,
57 json_rendered
: HumanReadableErrorType
,
58 diagnostic_width
: Option
<usize>,
59 macro_backtrace
: bool
,
62 dst
: Box
::new(io
::BufWriter
::new(io
::stderr())),
77 json_rendered
: HumanReadableErrorType
,
78 fluent_bundle
: Option
<Lrc
<FluentBundle
>>,
79 fallback_bundle
: LazyFallbackBundle
,
80 diagnostic_width
: Option
<usize>,
81 macro_backtrace
: bool
,
83 let file_path_mapping
= FilePathMapping
::empty();
86 Lrc
::new(SourceMap
::new(file_path_mapping
)),
97 dst
: Box
<dyn Write
+ Send
>,
98 registry
: Option
<Registry
>,
99 source_map
: Lrc
<SourceMap
>,
100 fluent_bundle
: Option
<Lrc
<FluentBundle
>>,
101 fallback_bundle
: LazyFallbackBundle
,
103 json_rendered
: HumanReadableErrorType
,
104 diagnostic_width
: Option
<usize>,
105 macro_backtrace
: bool
,
121 pub fn ui_testing(self, ui_testing
: bool
) -> Self {
122 Self { ui_testing, ..self }
126 impl Translate
for JsonEmitter
{
127 fn fluent_bundle(&self) -> Option
<&Lrc
<FluentBundle
>> {
128 self.fluent_bundle
.as_ref()
131 fn fallback_fluent_bundle(&self) -> &FluentBundle
{
132 &**self.fallback_bundle
136 impl Emitter
for JsonEmitter
{
137 fn emit_diagnostic(&mut self, diag
: &crate::Diagnostic
) {
138 let data
= Diagnostic
::from_errors_diagnostic(diag
, self);
139 let result
= if self.pretty
{
140 writeln
!(&mut self.dst
, "{}", serde_json
::to_string_pretty(&data
).unwrap())
142 writeln
!(&mut self.dst
, "{}", serde_json
::to_string(&data
).unwrap())
144 .and_then(|_
| self.dst
.flush());
145 if let Err(e
) = result
{
146 panic
!("failed to print diagnostics: {:?}", e
);
150 fn emit_artifact_notification(&mut self, path
: &Path
, artifact_type
: &str) {
151 let data
= ArtifactNotification { artifact: path, emit: artifact_type }
;
152 let result
= if self.pretty
{
153 writeln
!(&mut self.dst
, "{}", serde_json
::to_string_pretty(&data
).unwrap())
155 writeln
!(&mut self.dst
, "{}", serde_json
::to_string(&data
).unwrap())
157 .and_then(|_
| self.dst
.flush());
158 if let Err(e
) = result
{
159 panic
!("failed to print notification: {:?}", e
);
163 fn emit_future_breakage_report(&mut self, diags
: Vec
<crate::Diagnostic
>) {
164 let data
: Vec
<FutureBreakageItem
> = diags
167 if diag
.level
== crate::Level
::Allow
{
168 diag
.level
= crate::Level
::Warning(None
);
170 FutureBreakageItem { diagnostic: Diagnostic::from_errors_diagnostic(&diag, self) }
173 let report
= FutureIncompatReport { future_incompat_report: data }
;
174 let result
= if self.pretty
{
175 writeln
!(&mut self.dst
, "{}", serde_json
::to_string_pretty(&report
).unwrap())
177 writeln
!(&mut self.dst
, "{}", serde_json
::to_string(&report
).unwrap())
179 .and_then(|_
| self.dst
.flush());
180 if let Err(e
) = result
{
181 panic
!("failed to print future breakage report: {:?}", e
);
185 fn emit_unused_externs(&mut self, lint_level
: rustc_lint_defs
::Level
, unused_externs
: &[&str]) {
186 let lint_level
= lint_level
.as_str();
187 let data
= UnusedExterns { lint_level, unused_extern_names: unused_externs }
;
188 let result
= if self.pretty
{
189 writeln
!(&mut self.dst
, "{}", serde_json
::to_string_pretty(&data
).unwrap())
191 writeln
!(&mut self.dst
, "{}", serde_json
::to_string(&data
).unwrap())
193 .and_then(|_
| self.dst
.flush());
194 if let Err(e
) = result
{
195 panic
!("failed to print unused externs: {:?}", e
);
199 fn source_map(&self) -> Option
<&Lrc
<SourceMap
>> {
203 fn should_show_explain(&self) -> bool
{
204 !matches
!(self.json_rendered
, HumanReadableErrorType
::Short(_
))
208 // The following data types are provided just for serialisation.
212 /// The primary error message.
214 code
: Option
<DiagnosticCode
>,
215 /// "error: internal compiler error", "error", "warning", "note", "help".
217 spans
: Vec
<DiagnosticSpan
>,
218 /// Associated diagnostic messages.
219 children
: Vec
<Diagnostic
>,
220 /// The message as rustc would render it.
221 rendered
: Option
<String
>,
225 struct DiagnosticSpan
{
232 /// 1-based, character offset.
235 /// Is this a "primary" span -- meaning the point, or one of the points,
236 /// where the error occurred?
238 /// Source text from the start of line_start to the end of line_end.
239 text
: Vec
<DiagnosticSpanLine
>,
240 /// Label that should be placed at this location (if any)
241 label
: Option
<String
>,
242 /// If we are suggesting a replacement, this will contain text
243 /// that should be sliced in atop this span.
244 suggested_replacement
: Option
<String
>,
245 /// If the suggestion is approximate
246 suggestion_applicability
: Option
<Applicability
>,
247 /// Macro invocations that created the code at this span, if any.
248 expansion
: Option
<Box
<DiagnosticSpanMacroExpansion
>>,
252 struct DiagnosticSpanLine
{
255 /// 1-based, character offset in self.text.
256 highlight_start
: usize,
258 highlight_end
: usize,
262 struct DiagnosticSpanMacroExpansion
{
263 /// span where macro was applied to generate this code; note that
264 /// this may itself derive from a macro (if
265 /// `span.expansion.is_some()`)
266 span
: DiagnosticSpan
,
268 /// name of macro that was applied (e.g., "foo!" or "#[derive(Eq)]")
269 macro_decl_name
: String
,
271 /// span where macro was defined (if known)
272 def_site_span
: DiagnosticSpan
,
276 struct DiagnosticCode
{
279 /// An explanation for the code.
280 explanation
: Option
<&'
static str>,
284 struct ArtifactNotification
<'a
> {
285 /// The path of the artifact.
287 /// What kind of artifact we're emitting.
292 struct FutureBreakageItem
{
293 diagnostic
: Diagnostic
,
297 struct FutureIncompatReport
{
298 future_incompat_report
: Vec
<FutureBreakageItem
>,
301 // NOTE: Keep this in sync with the equivalent structs in rustdoc's
302 // doctest component (as well as cargo).
303 // We could unify this struct the one in rustdoc but they have different
304 // ownership semantics, so doing so would create wasteful allocations.
306 struct UnusedExterns
<'a
, 'b
, 'c
> {
307 /// The severity level of the unused dependencies lint
309 /// List of unused externs by their names.
310 unused_extern_names
: &'b
[&'c
str],
314 fn from_errors_diagnostic(diag
: &crate::Diagnostic
, je
: &JsonEmitter
) -> Diagnostic
{
315 let args
= je
.to_fluent_args(diag
.args());
316 let sugg
= diag
.suggestions
.iter().flatten().map(|sugg
| {
317 let translated_message
= je
.translate_message(&sugg
.msg
, &args
);
319 message
: translated_message
.to_string(),
322 spans
: DiagnosticSpan
::from_suggestion(sugg
, &args
, je
),
328 // generate regular command line output and store it in the json
330 // A threadsafe buffer for writing.
331 #[derive(Default, Clone)]
332 struct BufWriter(Arc
<Mutex
<Vec
<u8>>>);
334 impl Write
for BufWriter
{
335 fn write(&mut self, buf
: &[u8]) -> io
::Result
<usize> {
336 self.0.lock().unwrap().write(buf
)
338 fn flush(&mut self) -> io
::Result
<()> {
339 self.0.lock().unwrap().flush()
342 let buf
= BufWriter
::default();
343 let output
= buf
.clone();
348 je
.fluent_bundle
.clone(),
349 je
.fallback_bundle
.clone(),
354 .ui_testing(je
.ui_testing
)
355 .emit_diagnostic(diag
);
356 let output
= Arc
::try_unwrap(output
.0).unwrap().into_inner().unwrap();
357 let output
= String
::from_utf8(output
).unwrap();
359 let translated_message
= je
.translate_messages(&diag
.message
, &args
);
361 message
: translated_message
.to_string(),
362 code
: DiagnosticCode
::map_opt_string(diag
.code
.clone(), je
),
363 level
: diag
.level
.to_str(),
364 spans
: DiagnosticSpan
::from_multispan(&diag
.span
, &args
, je
),
368 .map(|c
| Diagnostic
::from_sub_diagnostic(c
, &args
, je
))
371 rendered
: Some(output
),
375 fn from_sub_diagnostic(
376 diag
: &SubDiagnostic
,
377 args
: &FluentArgs
<'_
>,
380 let translated_message
= je
.translate_messages(&diag
.message
, args
);
382 message
: translated_message
.to_string(),
384 level
: diag
.level
.to_str(),
388 .map(|sp
| DiagnosticSpan
::from_multispan(sp
, args
, je
))
389 .unwrap_or_else(|| DiagnosticSpan
::from_multispan(&diag
.span
, args
, je
)),
396 impl DiagnosticSpan
{
399 suggestion
: Option
<(&String
, Applicability
)>,
400 args
: &FluentArgs
<'_
>,
402 ) -> DiagnosticSpan
{
406 span
.label
.as_ref().map(|m
| je
.translate_message(m
, args
)).map(|m
| m
.to_string()),
415 label
: Option
<String
>,
416 suggestion
: Option
<(&String
, Applicability
)>,
418 ) -> DiagnosticSpan
{
419 // obtain the full backtrace from the `macro_backtrace`
420 // helper; in some ways, it'd be better to expand the
421 // backtrace ourselves, but the `macro_backtrace` helper makes
422 // some decision, such as dropping some frames, and I don't
423 // want to duplicate that logic here.
424 let backtrace
= span
.macro_backtrace();
425 DiagnosticSpan
::from_span_full(span
, is_primary
, label
, suggestion
, backtrace
, je
)
431 label
: Option
<String
>,
432 suggestion
: Option
<(&String
, Applicability
)>,
433 mut backtrace
: impl Iterator
<Item
= ExpnData
>,
435 ) -> DiagnosticSpan
{
436 let start
= je
.sm
.lookup_char_pos(span
.lo());
437 let end
= je
.sm
.lookup_char_pos(span
.hi());
438 let backtrace_step
= backtrace
.next().map(|bt
| {
439 let call_site
= Self::from_span_full(bt
.call_site
, false, None
, None
, backtrace
, je
);
440 let def_site_span
= Self::from_span_full(
441 je
.sm
.guess_head_span(bt
.def_site
),
448 Box
::new(DiagnosticSpanMacroExpansion
{
450 macro_decl_name
: bt
.kind
.descr(),
456 file_name
: je
.sm
.filename_for_diagnostics(&start
.file
.name
).to_string(),
457 byte_start
: start
.file
.original_relative_byte_pos(span
.lo()).0,
458 byte_end
: start
.file
.original_relative_byte_pos(span
.hi()).0,
459 line_start
: start
.line
,
461 column_start
: start
.col
.0 + 1,
462 column_end
: end
.col
.0 + 1,
464 text
: DiagnosticSpanLine
::from_span(span
, je
),
465 suggested_replacement
: suggestion
.map(|x
| x
.0.clone()),
466 suggestion_applicability
: suggestion
.map(|x
| x
.1),
467 expansion
: backtrace_step
,
474 args
: &FluentArgs
<'_
>,
476 ) -> Vec
<DiagnosticSpan
> {
479 .map(|span_str
| Self::from_span_label(span_str
, None
, args
, je
))
484 suggestion
: &CodeSuggestion
,
485 args
: &FluentArgs
<'_
>,
487 ) -> Vec
<DiagnosticSpan
> {
491 .flat_map(|substitution
| {
492 substitution
.parts
.iter().map(move |suggestion_inner
| {
494 SpanLabel { span: suggestion_inner.span, is_primary: true, label: None }
;
495 DiagnosticSpan
::from_span_label(
497 Some((&suggestion_inner
.snippet
, suggestion
.applicability
)),
507 impl DiagnosticSpanLine
{
508 fn line_from_source_file(
509 sf
: &rustc_span
::SourceFile
,
513 ) -> DiagnosticSpanLine
{
515 text
: sf
.get_line(index
).map_or_else(String
::new
, |l
| l
.into_owned()),
516 highlight_start
: h_start
,
517 highlight_end
: h_end
,
521 /// Creates a list of DiagnosticSpanLines from span - each line with any part
522 /// of `span` gets a DiagnosticSpanLine, with the highlight indicating the
523 /// `span` within the line.
524 fn from_span(span
: Span
, je
: &JsonEmitter
) -> Vec
<DiagnosticSpanLine
> {
528 // We can't get any lines if the source is unavailable.
529 if !je
.sm
.ensure_source_file_source_present(lines
.file
.clone()) {
533 let sf
= &*lines
.file
;
538 DiagnosticSpanLine
::line_from_source_file(
541 line
.start_col
.0 + 1,
547 .unwrap_or_else(|_
| vec
![])
551 impl DiagnosticCode
{
552 fn map_opt_string(s
: Option
<DiagnosticId
>, je
: &JsonEmitter
) -> Option
<DiagnosticCode
> {
555 DiagnosticId
::Error(s
) => s
,
556 DiagnosticId
::Lint { name, .. }
=> name
,
559 je
.registry
.as_ref().map(|registry
| registry
.try_find_description(&s
)).unwrap();
561 DiagnosticCode { code: s, explanation: je_result.unwrap_or(None) }