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