]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_errors/src/json.rs
Rollup merge of #78065 - tshepang:nits, r=dtolnay
[rust.git] / compiler / rustc_errors / src / json.rs
1 //! A JSON emitter for errors.
2 //!
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.
6 //!
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.
9
10 // FIXME: spec the JSON output properly.
11
12 use rustc_span::source_map::{FilePathMapping, SourceMap};
13
14 use crate::emitter::{Emitter, HumanReadableErrorType};
15 use crate::registry::Registry;
16 use crate::DiagnosticId;
17 use crate::{CodeSuggestion, SubDiagnostic};
18 use rustc_lint_defs::{Applicability, FutureBreakage};
19
20 use rustc_data_structures::sync::Lrc;
21 use rustc_span::hygiene::ExpnData;
22 use rustc_span::{MultiSpan, Span, SpanLabel};
23 use std::io::{self, Write};
24 use std::path::Path;
25 use std::sync::{Arc, Mutex};
26 use std::vec;
27
28 use rustc_serialize::json::{as_json, as_pretty_json};
29
30 #[cfg(test)]
31 mod tests;
32
33 pub struct JsonEmitter {
34     dst: Box<dyn Write + Send>,
35     registry: Option<Registry>,
36     sm: Lrc<SourceMap>,
37     pretty: bool,
38     ui_testing: bool,
39     json_rendered: HumanReadableErrorType,
40     terminal_width: Option<usize>,
41     macro_backtrace: bool,
42 }
43
44 impl JsonEmitter {
45     pub fn stderr(
46         registry: Option<Registry>,
47         source_map: Lrc<SourceMap>,
48         pretty: bool,
49         json_rendered: HumanReadableErrorType,
50         terminal_width: Option<usize>,
51         macro_backtrace: bool,
52     ) -> JsonEmitter {
53         JsonEmitter {
54             dst: Box::new(io::BufWriter::new(io::stderr())),
55             registry,
56             sm: source_map,
57             pretty,
58             ui_testing: false,
59             json_rendered,
60             terminal_width,
61             macro_backtrace,
62         }
63     }
64
65     pub fn basic(
66         pretty: bool,
67         json_rendered: HumanReadableErrorType,
68         terminal_width: Option<usize>,
69         macro_backtrace: bool,
70     ) -> JsonEmitter {
71         let file_path_mapping = FilePathMapping::empty();
72         JsonEmitter::stderr(
73             None,
74             Lrc::new(SourceMap::new(file_path_mapping)),
75             pretty,
76             json_rendered,
77             terminal_width,
78             macro_backtrace,
79         )
80     }
81
82     pub fn new(
83         dst: Box<dyn Write + Send>,
84         registry: Option<Registry>,
85         source_map: Lrc<SourceMap>,
86         pretty: bool,
87         json_rendered: HumanReadableErrorType,
88         terminal_width: Option<usize>,
89         macro_backtrace: bool,
90     ) -> JsonEmitter {
91         JsonEmitter {
92             dst,
93             registry,
94             sm: source_map,
95             pretty,
96             ui_testing: false,
97             json_rendered,
98             terminal_width,
99             macro_backtrace,
100         }
101     }
102
103     pub fn ui_testing(self, ui_testing: bool) -> Self {
104         Self { ui_testing, ..self }
105     }
106 }
107
108 impl Emitter for JsonEmitter {
109     fn emit_diagnostic(&mut self, diag: &crate::Diagnostic) {
110         let data = Diagnostic::from_errors_diagnostic(diag, self);
111         let result = if self.pretty {
112             writeln!(&mut self.dst, "{}", as_pretty_json(&data))
113         } else {
114             writeln!(&mut self.dst, "{}", as_json(&data))
115         }
116         .and_then(|_| self.dst.flush());
117         if let Err(e) = result {
118             panic!("failed to print diagnostics: {:?}", e);
119         }
120     }
121
122     fn emit_artifact_notification(&mut self, path: &Path, artifact_type: &str) {
123         let data = ArtifactNotification { artifact: path, emit: artifact_type };
124         let result = if self.pretty {
125             writeln!(&mut self.dst, "{}", as_pretty_json(&data))
126         } else {
127             writeln!(&mut self.dst, "{}", as_json(&data))
128         }
129         .and_then(|_| self.dst.flush());
130         if let Err(e) = result {
131             panic!("failed to print notification: {:?}", e);
132         }
133     }
134
135     fn emit_future_breakage_report(&mut self, diags: Vec<(FutureBreakage, crate::Diagnostic)>) {
136         let data: Vec<FutureBreakageItem> = diags
137             .into_iter()
138             .map(|(breakage, mut diag)| {
139                 if diag.level == crate::Level::Allow {
140                     diag.level = crate::Level::Warning;
141                 }
142                 FutureBreakageItem {
143                     future_breakage_date: breakage.date,
144                     diagnostic: Diagnostic::from_errors_diagnostic(&diag, self),
145                 }
146             })
147             .collect();
148         let report = FutureIncompatReport { future_incompat_report: data };
149         let result = if self.pretty {
150             writeln!(&mut self.dst, "{}", as_pretty_json(&report))
151         } else {
152             writeln!(&mut self.dst, "{}", as_json(&report))
153         }
154         .and_then(|_| self.dst.flush());
155         if let Err(e) = result {
156             panic!("failed to print future breakage report: {:?}", e);
157         }
158     }
159
160     fn source_map(&self) -> Option<&Lrc<SourceMap>> {
161         Some(&self.sm)
162     }
163
164     fn should_show_explain(&self) -> bool {
165         !matches!(self.json_rendered, HumanReadableErrorType::Short(_))
166     }
167 }
168
169 // The following data types are provided just for serialisation.
170
171 #[derive(Encodable)]
172 struct Diagnostic {
173     /// The primary error message.
174     message: String,
175     code: Option<DiagnosticCode>,
176     /// "error: internal compiler error", "error", "warning", "note", "help".
177     level: &'static str,
178     spans: Vec<DiagnosticSpan>,
179     /// Associated diagnostic messages.
180     children: Vec<Diagnostic>,
181     /// The message as rustc would render it.
182     rendered: Option<String>,
183 }
184
185 #[derive(Encodable)]
186 struct DiagnosticSpan {
187     file_name: String,
188     byte_start: u32,
189     byte_end: u32,
190     /// 1-based.
191     line_start: usize,
192     line_end: usize,
193     /// 1-based, character offset.
194     column_start: usize,
195     column_end: usize,
196     /// Is this a "primary" span -- meaning the point, or one of the points,
197     /// where the error occurred?
198     is_primary: bool,
199     /// Source text from the start of line_start to the end of line_end.
200     text: Vec<DiagnosticSpanLine>,
201     /// Label that should be placed at this location (if any)
202     label: Option<String>,
203     /// If we are suggesting a replacement, this will contain text
204     /// that should be sliced in atop this span.
205     suggested_replacement: Option<String>,
206     /// If the suggestion is approximate
207     suggestion_applicability: Option<Applicability>,
208     /// Macro invocations that created the code at this span, if any.
209     expansion: Option<Box<DiagnosticSpanMacroExpansion>>,
210 }
211
212 #[derive(Encodable)]
213 struct DiagnosticSpanLine {
214     text: String,
215
216     /// 1-based, character offset in self.text.
217     highlight_start: usize,
218
219     highlight_end: usize,
220 }
221
222 #[derive(Encodable)]
223 struct DiagnosticSpanMacroExpansion {
224     /// span where macro was applied to generate this code; note that
225     /// this may itself derive from a macro (if
226     /// `span.expansion.is_some()`)
227     span: DiagnosticSpan,
228
229     /// name of macro that was applied (e.g., "foo!" or "#[derive(Eq)]")
230     macro_decl_name: String,
231
232     /// span where macro was defined (if known)
233     def_site_span: DiagnosticSpan,
234 }
235
236 #[derive(Encodable)]
237 struct DiagnosticCode {
238     /// The code itself.
239     code: String,
240     /// An explanation for the code.
241     explanation: Option<&'static str>,
242 }
243
244 #[derive(Encodable)]
245 struct ArtifactNotification<'a> {
246     /// The path of the artifact.
247     artifact: &'a Path,
248     /// What kind of artifact we're emitting.
249     emit: &'a str,
250 }
251
252 #[derive(Encodable)]
253 struct FutureBreakageItem {
254     future_breakage_date: Option<&'static str>,
255     diagnostic: Diagnostic,
256 }
257
258 #[derive(Encodable)]
259 struct FutureIncompatReport {
260     future_incompat_report: Vec<FutureBreakageItem>,
261 }
262
263 impl Diagnostic {
264     fn from_errors_diagnostic(diag: &crate::Diagnostic, je: &JsonEmitter) -> Diagnostic {
265         let sugg = diag.suggestions.iter().map(|sugg| Diagnostic {
266             message: sugg.msg.clone(),
267             code: None,
268             level: "help",
269             spans: DiagnosticSpan::from_suggestion(sugg, je),
270             children: vec![],
271             rendered: None,
272         });
273
274         // generate regular command line output and store it in the json
275
276         // A threadsafe buffer for writing.
277         #[derive(Default, Clone)]
278         struct BufWriter(Arc<Mutex<Vec<u8>>>);
279
280         impl Write for BufWriter {
281             fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
282                 self.0.lock().unwrap().write(buf)
283             }
284             fn flush(&mut self) -> io::Result<()> {
285                 self.0.lock().unwrap().flush()
286             }
287         }
288         let buf = BufWriter::default();
289         let output = buf.clone();
290         je.json_rendered
291             .new_emitter(
292                 Box::new(buf),
293                 Some(je.sm.clone()),
294                 false,
295                 je.terminal_width,
296                 je.macro_backtrace,
297             )
298             .ui_testing(je.ui_testing)
299             .emit_diagnostic(diag);
300         let output = Arc::try_unwrap(output.0).unwrap().into_inner().unwrap();
301         let output = String::from_utf8(output).unwrap();
302
303         Diagnostic {
304             message: diag.message(),
305             code: DiagnosticCode::map_opt_string(diag.code.clone(), je),
306             level: diag.level.to_str(),
307             spans: DiagnosticSpan::from_multispan(&diag.span, je),
308             children: diag
309                 .children
310                 .iter()
311                 .map(|c| Diagnostic::from_sub_diagnostic(c, je))
312                 .chain(sugg)
313                 .collect(),
314             rendered: Some(output),
315         }
316     }
317
318     fn from_sub_diagnostic(diag: &SubDiagnostic, je: &JsonEmitter) -> Diagnostic {
319         Diagnostic {
320             message: diag.message(),
321             code: None,
322             level: diag.level.to_str(),
323             spans: diag
324                 .render_span
325                 .as_ref()
326                 .map(|sp| DiagnosticSpan::from_multispan(sp, je))
327                 .unwrap_or_else(|| DiagnosticSpan::from_multispan(&diag.span, je)),
328             children: vec![],
329             rendered: None,
330         }
331     }
332 }
333
334 impl DiagnosticSpan {
335     fn from_span_label(
336         span: SpanLabel,
337         suggestion: Option<(&String, Applicability)>,
338         je: &JsonEmitter,
339     ) -> DiagnosticSpan {
340         Self::from_span_etc(span.span, span.is_primary, span.label, suggestion, je)
341     }
342
343     fn from_span_etc(
344         span: Span,
345         is_primary: bool,
346         label: Option<String>,
347         suggestion: Option<(&String, Applicability)>,
348         je: &JsonEmitter,
349     ) -> DiagnosticSpan {
350         // obtain the full backtrace from the `macro_backtrace`
351         // helper; in some ways, it'd be better to expand the
352         // backtrace ourselves, but the `macro_backtrace` helper makes
353         // some decision, such as dropping some frames, and I don't
354         // want to duplicate that logic here.
355         let backtrace = span.macro_backtrace();
356         DiagnosticSpan::from_span_full(span, is_primary, label, suggestion, backtrace, je)
357     }
358
359     fn from_span_full(
360         span: Span,
361         is_primary: bool,
362         label: Option<String>,
363         suggestion: Option<(&String, Applicability)>,
364         mut backtrace: impl Iterator<Item = ExpnData>,
365         je: &JsonEmitter,
366     ) -> DiagnosticSpan {
367         let start = je.sm.lookup_char_pos(span.lo());
368         let end = je.sm.lookup_char_pos(span.hi());
369         let backtrace_step = backtrace.next().map(|bt| {
370             let call_site = Self::from_span_full(bt.call_site, false, None, None, backtrace, je);
371             let def_site_span =
372                 Self::from_span_full(bt.def_site, false, None, None, vec![].into_iter(), je);
373             Box::new(DiagnosticSpanMacroExpansion {
374                 span: call_site,
375                 macro_decl_name: bt.kind.descr(),
376                 def_site_span,
377             })
378         });
379
380         DiagnosticSpan {
381             file_name: start.file.name.to_string(),
382             byte_start: start.file.original_relative_byte_pos(span.lo()).0,
383             byte_end: start.file.original_relative_byte_pos(span.hi()).0,
384             line_start: start.line,
385             line_end: end.line,
386             column_start: start.col.0 + 1,
387             column_end: end.col.0 + 1,
388             is_primary,
389             text: DiagnosticSpanLine::from_span(span, je),
390             suggested_replacement: suggestion.map(|x| x.0.clone()),
391             suggestion_applicability: suggestion.map(|x| x.1),
392             expansion: backtrace_step,
393             label,
394         }
395     }
396
397     fn from_multispan(msp: &MultiSpan, je: &JsonEmitter) -> Vec<DiagnosticSpan> {
398         msp.span_labels()
399             .into_iter()
400             .map(|span_str| Self::from_span_label(span_str, None, je))
401             .collect()
402     }
403
404     fn from_suggestion(suggestion: &CodeSuggestion, je: &JsonEmitter) -> Vec<DiagnosticSpan> {
405         suggestion
406             .substitutions
407             .iter()
408             .flat_map(|substitution| {
409                 substitution.parts.iter().map(move |suggestion_inner| {
410                     let span_label =
411                         SpanLabel { span: suggestion_inner.span, is_primary: true, label: None };
412                     DiagnosticSpan::from_span_label(
413                         span_label,
414                         Some((&suggestion_inner.snippet, suggestion.applicability)),
415                         je,
416                     )
417                 })
418             })
419             .collect()
420     }
421 }
422
423 impl DiagnosticSpanLine {
424     fn line_from_source_file(
425         sf: &rustc_span::SourceFile,
426         index: usize,
427         h_start: usize,
428         h_end: usize,
429     ) -> DiagnosticSpanLine {
430         DiagnosticSpanLine {
431             text: sf.get_line(index).map_or(String::new(), |l| l.into_owned()),
432             highlight_start: h_start,
433             highlight_end: h_end,
434         }
435     }
436
437     /// Creates a list of DiagnosticSpanLines from span - each line with any part
438     /// of `span` gets a DiagnosticSpanLine, with the highlight indicating the
439     /// `span` within the line.
440     fn from_span(span: Span, je: &JsonEmitter) -> Vec<DiagnosticSpanLine> {
441         je.sm
442             .span_to_lines(span)
443             .map(|lines| {
444                 // We can't get any lines if the source is unavailable.
445                 if !je.sm.ensure_source_file_source_present(lines.file.clone()) {
446                     return vec![];
447                 }
448
449                 let sf = &*lines.file;
450                 lines
451                     .lines
452                     .iter()
453                     .map(|line| {
454                         DiagnosticSpanLine::line_from_source_file(
455                             sf,
456                             line.line_index,
457                             line.start_col.0 + 1,
458                             line.end_col.0 + 1,
459                         )
460                     })
461                     .collect()
462             })
463             .unwrap_or_else(|_| vec![])
464     }
465 }
466
467 impl DiagnosticCode {
468     fn map_opt_string(s: Option<DiagnosticId>, je: &JsonEmitter) -> Option<DiagnosticCode> {
469         s.map(|s| {
470             let s = match s {
471                 DiagnosticId::Error(s) => s,
472                 DiagnosticId::Lint { name, has_future_breakage: _ } => name,
473             };
474             let je_result =
475                 je.registry.as_ref().map(|registry| registry.try_find_description(&s)).unwrap();
476
477             DiagnosticCode { code: s, explanation: je_result.unwrap_or(None) }
478         })
479     }
480 }