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