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
;
18 CodeSuggestion
, FluentBundle
, LazyFallbackBundle
, MultiSpan
, SpanLabel
, SubDiagnostic
,
20 use rustc_lint_defs
::Applicability
;
22 use rustc_data_structures
::sync
::Lrc
;
23 use rustc_error_messages
::FluentArgs
;
24 use rustc_span
::hygiene
::ExpnData
;
26 use std
::io
::{self, Write}
;
28 use std
::sync
::{Arc, Mutex}
;
36 pub struct JsonEmitter
{
37 dst
: Box
<dyn Write
+ Send
>,
38 registry
: Option
<Registry
>,
40 fluent_bundle
: Option
<Lrc
<FluentBundle
>>,
41 fallback_bundle
: LazyFallbackBundle
,
44 json_rendered
: HumanReadableErrorType
,
45 terminal_width
: Option
<usize>,
46 macro_backtrace
: bool
,
51 registry
: Option
<Registry
>,
52 source_map
: Lrc
<SourceMap
>,
53 fluent_bundle
: Option
<Lrc
<FluentBundle
>>,
54 fallback_bundle
: LazyFallbackBundle
,
56 json_rendered
: HumanReadableErrorType
,
57 terminal_width
: Option
<usize>,
58 macro_backtrace
: bool
,
61 dst
: Box
::new(io
::BufWriter
::new(io
::stderr())),
76 json_rendered
: HumanReadableErrorType
,
77 fluent_bundle
: Option
<Lrc
<FluentBundle
>>,
78 fallback_bundle
: LazyFallbackBundle
,
79 terminal_width
: Option
<usize>,
80 macro_backtrace
: bool
,
82 let file_path_mapping
= FilePathMapping
::empty();
85 Lrc
::new(SourceMap
::new(file_path_mapping
)),
96 dst
: Box
<dyn Write
+ Send
>,
97 registry
: Option
<Registry
>,
98 source_map
: Lrc
<SourceMap
>,
99 fluent_bundle
: Option
<Lrc
<FluentBundle
>>,
100 fallback_bundle
: LazyFallbackBundle
,
102 json_rendered
: HumanReadableErrorType
,
103 terminal_width
: Option
<usize>,
104 macro_backtrace
: bool
,
120 pub fn ui_testing(self, ui_testing
: bool
) -> Self {
121 Self { ui_testing, ..self }
125 impl Emitter
for JsonEmitter
{
126 fn emit_diagnostic(&mut self, diag
: &crate::Diagnostic
) {
127 let data
= Diagnostic
::from_errors_diagnostic(diag
, self);
128 let result
= if self.pretty
{
129 writeln
!(&mut self.dst
, "{}", serde_json
::to_string_pretty(&data
).unwrap())
131 writeln
!(&mut self.dst
, "{}", serde_json
::to_string(&data
).unwrap())
133 .and_then(|_
| self.dst
.flush());
134 if let Err(e
) = result
{
135 panic
!("failed to print diagnostics: {:?}", e
);
139 fn emit_artifact_notification(&mut self, path
: &Path
, artifact_type
: &str) {
140 let data
= ArtifactNotification { artifact: path, emit: artifact_type }
;
141 let result
= if self.pretty
{
142 writeln
!(&mut self.dst
, "{}", serde_json
::to_string_pretty(&data
).unwrap())
144 writeln
!(&mut self.dst
, "{}", serde_json
::to_string(&data
).unwrap())
146 .and_then(|_
| self.dst
.flush());
147 if let Err(e
) = result
{
148 panic
!("failed to print notification: {:?}", e
);
152 fn emit_future_breakage_report(&mut self, diags
: Vec
<crate::Diagnostic
>) {
153 let data
: Vec
<FutureBreakageItem
> = diags
156 if diag
.level
== crate::Level
::Allow
{
157 diag
.level
= crate::Level
::Warning(None
);
159 FutureBreakageItem { diagnostic: Diagnostic::from_errors_diagnostic(&diag, self) }
162 let report
= FutureIncompatReport { future_incompat_report: data }
;
163 let result
= if self.pretty
{
164 writeln
!(&mut self.dst
, "{}", serde_json
::to_string_pretty(&report
).unwrap())
166 writeln
!(&mut self.dst
, "{}", serde_json
::to_string(&report
).unwrap())
168 .and_then(|_
| self.dst
.flush());
169 if let Err(e
) = result
{
170 panic
!("failed to print future breakage report: {:?}", e
);
174 fn emit_unused_externs(&mut self, lint_level
: rustc_lint_defs
::Level
, unused_externs
: &[&str]) {
175 let lint_level
= lint_level
.as_str();
176 let data
= UnusedExterns { lint_level, unused_extern_names: unused_externs }
;
177 let result
= if self.pretty
{
178 writeln
!(&mut self.dst
, "{}", serde_json
::to_string_pretty(&data
).unwrap())
180 writeln
!(&mut self.dst
, "{}", serde_json
::to_string(&data
).unwrap())
182 .and_then(|_
| self.dst
.flush());
183 if let Err(e
) = result
{
184 panic
!("failed to print unused externs: {:?}", e
);
188 fn source_map(&self) -> Option
<&Lrc
<SourceMap
>> {
192 fn fluent_bundle(&self) -> Option
<&Lrc
<FluentBundle
>> {
193 self.fluent_bundle
.as_ref()
196 fn fallback_fluent_bundle(&self) -> &FluentBundle
{
197 &**self.fallback_bundle
200 fn should_show_explain(&self) -> bool
{
201 !matches
!(self.json_rendered
, HumanReadableErrorType
::Short(_
))
205 // The following data types are provided just for serialisation.
209 /// The primary error message.
211 code
: Option
<DiagnosticCode
>,
212 /// "error: internal compiler error", "error", "warning", "note", "help".
214 spans
: Vec
<DiagnosticSpan
>,
215 /// Associated diagnostic messages.
216 children
: Vec
<Diagnostic
>,
217 /// The message as rustc would render it.
218 rendered
: Option
<String
>,
222 struct DiagnosticSpan
{
229 /// 1-based, character offset.
232 /// Is this a "primary" span -- meaning the point, or one of the points,
233 /// where the error occurred?
235 /// Source text from the start of line_start to the end of line_end.
236 text
: Vec
<DiagnosticSpanLine
>,
237 /// Label that should be placed at this location (if any)
238 label
: Option
<String
>,
239 /// If we are suggesting a replacement, this will contain text
240 /// that should be sliced in atop this span.
241 suggested_replacement
: Option
<String
>,
242 /// If the suggestion is approximate
243 suggestion_applicability
: Option
<Applicability
>,
244 /// Macro invocations that created the code at this span, if any.
245 expansion
: Option
<Box
<DiagnosticSpanMacroExpansion
>>,
249 struct DiagnosticSpanLine
{
252 /// 1-based, character offset in self.text.
253 highlight_start
: usize,
255 highlight_end
: usize,
259 struct DiagnosticSpanMacroExpansion
{
260 /// span where macro was applied to generate this code; note that
261 /// this may itself derive from a macro (if
262 /// `span.expansion.is_some()`)
263 span
: DiagnosticSpan
,
265 /// name of macro that was applied (e.g., "foo!" or "#[derive(Eq)]")
266 macro_decl_name
: String
,
268 /// span where macro was defined (if known)
269 def_site_span
: DiagnosticSpan
,
273 struct DiagnosticCode
{
276 /// An explanation for the code.
277 explanation
: Option
<&'
static str>,
281 struct ArtifactNotification
<'a
> {
282 /// The path of the artifact.
284 /// What kind of artifact we're emitting.
289 struct FutureBreakageItem
{
290 diagnostic
: Diagnostic
,
294 struct FutureIncompatReport
{
295 future_incompat_report
: Vec
<FutureBreakageItem
>,
298 // NOTE: Keep this in sync with the equivalent structs in rustdoc's
299 // doctest component (as well as cargo).
300 // We could unify this struct the one in rustdoc but they have different
301 // ownership semantics, so doing so would create wasteful allocations.
303 struct UnusedExterns
<'a
, 'b
, 'c
> {
304 /// The severity level of the unused dependencies lint
306 /// List of unused externs by their names.
307 unused_extern_names
: &'b
[&'c
str],
311 fn from_errors_diagnostic(diag
: &crate::Diagnostic
, je
: &JsonEmitter
) -> Diagnostic
{
312 let args
= je
.to_fluent_args(diag
.args());
313 let sugg
= diag
.suggestions
.iter().flatten().map(|sugg
| {
314 let translated_message
= je
.translate_message(&sugg
.msg
, &args
);
316 message
: translated_message
.to_string(),
319 spans
: DiagnosticSpan
::from_suggestion(sugg
, &args
, je
),
325 // generate regular command line output and store it in the json
327 // A threadsafe buffer for writing.
328 #[derive(Default, Clone)]
329 struct BufWriter(Arc
<Mutex
<Vec
<u8>>>);
331 impl Write
for BufWriter
{
332 fn write(&mut self, buf
: &[u8]) -> io
::Result
<usize> {
333 self.0.lock().unwrap().write(buf
)
335 fn flush(&mut self) -> io
::Result
<()> {
336 self.0.lock().unwrap().flush()
339 let buf
= BufWriter
::default();
340 let output
= buf
.clone();
345 je
.fluent_bundle
.clone(),
346 je
.fallback_bundle
.clone(),
351 .ui_testing(je
.ui_testing
)
352 .emit_diagnostic(diag
);
353 let output
= Arc
::try_unwrap(output
.0).unwrap().into_inner().unwrap();
354 let output
= String
::from_utf8(output
).unwrap();
356 let translated_message
= je
.translate_messages(&diag
.message
, &args
);
358 message
: translated_message
.to_string(),
359 code
: DiagnosticCode
::map_opt_string(diag
.code
.clone(), je
),
360 level
: diag
.level
.to_str(),
361 spans
: DiagnosticSpan
::from_multispan(&diag
.span
, &args
, je
),
365 .map(|c
| Diagnostic
::from_sub_diagnostic(c
, &args
, je
))
368 rendered
: Some(output
),
372 fn from_sub_diagnostic(
373 diag
: &SubDiagnostic
,
374 args
: &FluentArgs
<'_
>,
377 let translated_message
= je
.translate_messages(&diag
.message
, args
);
379 message
: translated_message
.to_string(),
381 level
: diag
.level
.to_str(),
385 .map(|sp
| DiagnosticSpan
::from_multispan(sp
, args
, je
))
386 .unwrap_or_else(|| DiagnosticSpan
::from_multispan(&diag
.span
, args
, je
)),
393 impl DiagnosticSpan
{
396 suggestion
: Option
<(&String
, Applicability
)>,
397 args
: &FluentArgs
<'_
>,
399 ) -> DiagnosticSpan
{
403 span
.label
.as_ref().map(|m
| je
.translate_message(m
, args
)).map(|m
| m
.to_string()),
412 label
: Option
<String
>,
413 suggestion
: Option
<(&String
, Applicability
)>,
415 ) -> DiagnosticSpan
{
416 // obtain the full backtrace from the `macro_backtrace`
417 // helper; in some ways, it'd be better to expand the
418 // backtrace ourselves, but the `macro_backtrace` helper makes
419 // some decision, such as dropping some frames, and I don't
420 // want to duplicate that logic here.
421 let backtrace
= span
.macro_backtrace();
422 DiagnosticSpan
::from_span_full(span
, is_primary
, label
, suggestion
, backtrace
, je
)
428 label
: Option
<String
>,
429 suggestion
: Option
<(&String
, Applicability
)>,
430 mut backtrace
: impl Iterator
<Item
= ExpnData
>,
432 ) -> DiagnosticSpan
{
433 let start
= je
.sm
.lookup_char_pos(span
.lo());
434 let end
= je
.sm
.lookup_char_pos(span
.hi());
435 let backtrace_step
= backtrace
.next().map(|bt
| {
436 let call_site
= Self::from_span_full(bt
.call_site
, false, None
, None
, backtrace
, je
);
437 let def_site_span
= Self::from_span_full(
438 je
.sm
.guess_head_span(bt
.def_site
),
445 Box
::new(DiagnosticSpanMacroExpansion
{
447 macro_decl_name
: bt
.kind
.descr(),
453 file_name
: je
.sm
.filename_for_diagnostics(&start
.file
.name
).to_string(),
454 byte_start
: start
.file
.original_relative_byte_pos(span
.lo()).0,
455 byte_end
: start
.file
.original_relative_byte_pos(span
.hi()).0,
456 line_start
: start
.line
,
458 column_start
: start
.col
.0 + 1,
459 column_end
: end
.col
.0 + 1,
461 text
: DiagnosticSpanLine
::from_span(span
, je
),
462 suggested_replacement
: suggestion
.map(|x
| x
.0.clone()),
463 suggestion_applicability
: suggestion
.map(|x
| x
.1),
464 expansion
: backtrace_step
,
471 args
: &FluentArgs
<'_
>,
473 ) -> Vec
<DiagnosticSpan
> {
476 .map(|span_str
| Self::from_span_label(span_str
, None
, args
, je
))
481 suggestion
: &CodeSuggestion
,
482 args
: &FluentArgs
<'_
>,
484 ) -> Vec
<DiagnosticSpan
> {
488 .flat_map(|substitution
| {
489 substitution
.parts
.iter().map(move |suggestion_inner
| {
491 SpanLabel { span: suggestion_inner.span, is_primary: true, label: None }
;
492 DiagnosticSpan
::from_span_label(
494 Some((&suggestion_inner
.snippet
, suggestion
.applicability
)),
504 impl DiagnosticSpanLine
{
505 fn line_from_source_file(
506 sf
: &rustc_span
::SourceFile
,
510 ) -> DiagnosticSpanLine
{
512 text
: sf
.get_line(index
).map_or_else(String
::new
, |l
| l
.into_owned()),
513 highlight_start
: h_start
,
514 highlight_end
: h_end
,
518 /// Creates a list of DiagnosticSpanLines from span - each line with any part
519 /// of `span` gets a DiagnosticSpanLine, with the highlight indicating the
520 /// `span` within the line.
521 fn from_span(span
: Span
, je
: &JsonEmitter
) -> Vec
<DiagnosticSpanLine
> {
525 // We can't get any lines if the source is unavailable.
526 if !je
.sm
.ensure_source_file_source_present(lines
.file
.clone()) {
530 let sf
= &*lines
.file
;
535 DiagnosticSpanLine
::line_from_source_file(
538 line
.start_col
.0 + 1,
544 .unwrap_or_else(|_
| vec
![])
548 impl DiagnosticCode
{
549 fn map_opt_string(s
: Option
<DiagnosticId
>, je
: &JsonEmitter
) -> Option
<DiagnosticCode
> {
552 DiagnosticId
::Error(s
) => s
,
553 DiagnosticId
::Lint { name, .. }
=> name
,
556 je
.registry
.as_ref().map(|registry
| registry
.try_find_description(&s
)).unwrap();
558 DiagnosticCode { code: s, explanation: je_result.unwrap_or(None) }