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