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