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