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