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