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