]> git.lizzy.rs Git - rust.git/blob - src/tools/compiletest/src/json.rs
Rollup merge of #100296 - BlackHoleFox:os-error-aliases, r=thomcc
[rust.git] / src / tools / compiletest / src / json.rs
1 //! These structs are a subset of the ones found in `rustc_errors::json`.
2 //! They are only used for deserialization of JSON output provided by libtest.
3
4 use crate::errors::{Error, ErrorKind};
5 use crate::runtest::ProcRes;
6 use serde::Deserialize;
7 use std::path::{Path, PathBuf};
8 use std::str::FromStr;
9
10 #[derive(Deserialize)]
11 struct Diagnostic {
12     message: String,
13     code: Option<DiagnosticCode>,
14     level: String,
15     spans: Vec<DiagnosticSpan>,
16     children: Vec<Diagnostic>,
17     rendered: Option<String>,
18 }
19
20 #[derive(Deserialize)]
21 struct ArtifactNotification {
22     #[allow(dead_code)]
23     artifact: PathBuf,
24 }
25
26 #[derive(Deserialize)]
27 struct UnusedExternNotification {
28     #[allow(dead_code)]
29     lint_level: String,
30     #[allow(dead_code)]
31     unused_extern_names: Vec<String>,
32 }
33
34 #[derive(Deserialize, Clone)]
35 struct DiagnosticSpan {
36     file_name: String,
37     line_start: usize,
38     line_end: usize,
39     column_start: usize,
40     column_end: usize,
41     is_primary: bool,
42     label: Option<String>,
43     suggested_replacement: Option<String>,
44     expansion: Option<Box<DiagnosticSpanMacroExpansion>>,
45 }
46
47 #[derive(Deserialize)]
48 struct FutureIncompatReport {
49     future_incompat_report: Vec<FutureBreakageItem>,
50 }
51
52 #[derive(Deserialize)]
53 struct FutureBreakageItem {
54     diagnostic: Diagnostic,
55 }
56
57 impl DiagnosticSpan {
58     /// Returns the deepest source span in the macro call stack with a given file name.
59     /// This is either the supplied span, or the span for some macro callsite that expanded to it.
60     fn first_callsite_in_file(&self, file_name: &str) -> &DiagnosticSpan {
61         if self.file_name == file_name {
62             self
63         } else {
64             self.expansion
65                 .as_ref()
66                 .map(|origin| origin.span.first_callsite_in_file(file_name))
67                 .unwrap_or(self)
68         }
69     }
70 }
71
72 #[derive(Deserialize, Clone)]
73 struct DiagnosticSpanMacroExpansion {
74     /// span where macro was applied to generate this code
75     span: DiagnosticSpan,
76
77     /// name of macro that was applied (e.g., "foo!" or "#[derive(Eq)]")
78     macro_decl_name: String,
79 }
80
81 #[derive(Deserialize, Clone)]
82 struct DiagnosticCode {
83     /// The code itself.
84     code: String,
85 }
86
87 pub fn rustfix_diagnostics_only(output: &str) -> String {
88     output
89         .lines()
90         .filter(|line| line.starts_with('{') && serde_json::from_str::<Diagnostic>(line).is_ok())
91         .collect()
92 }
93
94 pub fn extract_rendered(output: &str) -> String {
95     output
96         .lines()
97         .filter_map(|line| {
98             if line.starts_with('{') {
99                 if let Ok(diagnostic) = serde_json::from_str::<Diagnostic>(line) {
100                     diagnostic.rendered
101                 } else if let Ok(report) = serde_json::from_str::<FutureIncompatReport>(line) {
102                     if report.future_incompat_report.is_empty() {
103                         None
104                     } else {
105                         Some(format!(
106                             "Future incompatibility report: {}",
107                             report
108                                 .future_incompat_report
109                                 .into_iter()
110                                 .map(|item| {
111                                     format!(
112                                         "Future breakage diagnostic:\n{}",
113                                         item.diagnostic
114                                             .rendered
115                                             .unwrap_or_else(|| "Not rendered".to_string())
116                                     )
117                                 })
118                                 .collect::<String>()
119                         ))
120                     }
121                 } else if serde_json::from_str::<ArtifactNotification>(line).is_ok() {
122                     // Ignore the notification.
123                     None
124                 } else if serde_json::from_str::<UnusedExternNotification>(line).is_ok() {
125                     // Ignore the notification.
126                     None
127                 } else {
128                     print!(
129                         "failed to decode compiler output as json: line: {}\noutput: {}",
130                         line, output
131                     );
132                     panic!()
133                 }
134             } else {
135                 // preserve non-JSON lines, such as ICEs
136                 Some(format!("{}\n", line))
137             }
138         })
139         .collect()
140 }
141
142 pub fn parse_output(file_name: &str, output: &str, proc_res: &ProcRes) -> Vec<Error> {
143     output.lines().flat_map(|line| parse_line(file_name, line, output, proc_res)).collect()
144 }
145
146 fn parse_line(file_name: &str, line: &str, output: &str, proc_res: &ProcRes) -> Vec<Error> {
147     // The compiler sometimes intermingles non-JSON stuff into the
148     // output.  This hack just skips over such lines. Yuck.
149     if line.starts_with('{') {
150         match serde_json::from_str::<Diagnostic>(line) {
151             Ok(diagnostic) => {
152                 let mut expected_errors = vec![];
153                 push_expected_errors(&mut expected_errors, &diagnostic, &[], file_name);
154                 expected_errors
155             }
156             Err(error) => {
157                 // Ignore the future compat report message - this is handled
158                 // by `extract_rendered`
159                 if serde_json::from_str::<FutureIncompatReport>(line).is_ok() {
160                     vec![]
161                 } else {
162                     proc_res.fatal(
163                         Some(&format!(
164                             "failed to decode compiler output as json: \
165                          `{}`\nline: {}\noutput: {}",
166                             error, line, output
167                         )),
168                         || (),
169                     );
170                 }
171             }
172         }
173     } else {
174         vec![]
175     }
176 }
177
178 fn push_expected_errors(
179     expected_errors: &mut Vec<Error>,
180     diagnostic: &Diagnostic,
181     default_spans: &[&DiagnosticSpan],
182     file_name: &str,
183 ) {
184     // In case of macro expansions, we need to get the span of the callsite
185     let spans_info_in_this_file: Vec<_> = diagnostic
186         .spans
187         .iter()
188         .map(|span| (span.is_primary, span.first_callsite_in_file(file_name)))
189         .filter(|(_, span)| Path::new(&span.file_name) == Path::new(&file_name))
190         .collect();
191
192     let spans_in_this_file: Vec<_> = spans_info_in_this_file.iter().map(|(_, span)| span).collect();
193
194     let primary_spans: Vec<_> = spans_info_in_this_file
195         .iter()
196         .filter(|(is_primary, _)| *is_primary)
197         .map(|(_, span)| span)
198         .take(1) // sometimes we have more than one showing up in the json; pick first
199         .cloned()
200         .collect();
201     let primary_spans = if primary_spans.is_empty() {
202         // subdiagnostics often don't have a span of their own;
203         // inherit the span from the parent in that case
204         default_spans
205     } else {
206         &primary_spans
207     };
208
209     // We break the output into multiple lines, and then append the
210     // [E123] to every line in the output. This may be overkill.  The
211     // intention was to match existing tests that do things like "//|
212     // found `i32` [E123]" and expect to match that somewhere, and yet
213     // also ensure that `//~ ERROR E123` *always* works. The
214     // assumption is that these multi-line error messages are on their
215     // way out anyhow.
216     let with_code = |span: &DiagnosticSpan, text: &str| {
217         match diagnostic.code {
218             Some(ref code) =>
219             // FIXME(#33000) -- it'd be better to use a dedicated
220             // UI harness than to include the line/col number like
221             // this, but some current tests rely on it.
222             //
223             // Note: Do NOT include the filename. These can easily
224             // cause false matches where the expected message
225             // appears in the filename, and hence the message
226             // changes but the test still passes.
227             {
228                 format!(
229                     "{}:{}: {}:{}: {} [{}]",
230                     span.line_start,
231                     span.column_start,
232                     span.line_end,
233                     span.column_end,
234                     text,
235                     code.code.clone()
236                 )
237             }
238             None =>
239             // FIXME(#33000) -- it'd be better to use a dedicated UI harness
240             {
241                 format!(
242                     "{}:{}: {}:{}: {}",
243                     span.line_start, span.column_start, span.line_end, span.column_end, text
244                 )
245             }
246         }
247     };
248
249     // Convert multi-line messages into multiple expected
250     // errors. We expect to replace these with something
251     // more structured shortly anyhow.
252     let mut message_lines = diagnostic.message.lines();
253     if let Some(first_line) = message_lines.next() {
254         for span in primary_spans {
255             let msg = with_code(span, first_line);
256             let kind = ErrorKind::from_str(&diagnostic.level).ok();
257             expected_errors.push(Error { line_num: span.line_start, kind, msg });
258         }
259     }
260     for next_line in message_lines {
261         for span in primary_spans {
262             expected_errors.push(Error {
263                 line_num: span.line_start,
264                 kind: None,
265                 msg: with_code(span, next_line),
266             });
267         }
268     }
269
270     // If the message has a suggestion, register that.
271     for span in primary_spans {
272         if let Some(ref suggested_replacement) = span.suggested_replacement {
273             for (index, line) in suggested_replacement.lines().enumerate() {
274                 expected_errors.push(Error {
275                     line_num: span.line_start + index,
276                     kind: Some(ErrorKind::Suggestion),
277                     msg: line.to_string(),
278                 });
279             }
280         }
281     }
282
283     // Add notes for the backtrace
284     for span in primary_spans {
285         for frame in &span.expansion {
286             push_backtrace(expected_errors, frame, file_name);
287         }
288     }
289
290     // Add notes for any labels that appear in the message.
291     for span in spans_in_this_file.iter().filter(|span| span.label.is_some()) {
292         expected_errors.push(Error {
293             line_num: span.line_start,
294             kind: Some(ErrorKind::Note),
295             msg: span.label.clone().unwrap(),
296         });
297     }
298
299     // Flatten out the children.
300     for child in &diagnostic.children {
301         push_expected_errors(expected_errors, child, primary_spans, file_name);
302     }
303 }
304
305 fn push_backtrace(
306     expected_errors: &mut Vec<Error>,
307     expansion: &DiagnosticSpanMacroExpansion,
308     file_name: &str,
309 ) {
310     if Path::new(&expansion.span.file_name) == Path::new(&file_name) {
311         expected_errors.push(Error {
312             line_num: expansion.span.line_start,
313             kind: Some(ErrorKind::Note),
314             msg: format!("in this expansion of {}", expansion.macro_decl_name),
315         });
316     }
317
318     for previous_expansion in &expansion.span.expansion {
319         push_backtrace(expected_errors, previous_expansion, file_name);
320     }
321 }