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