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