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.
4 use crate::errors::{Error, ErrorKind};
5 use crate::runtest::ProcRes;
6 use serde::Deserialize;
7 use std::path::{Path, PathBuf};
10 #[derive(Deserialize)]
13 code: Option<DiagnosticCode>,
15 spans: Vec<DiagnosticSpan>,
16 children: Vec<Diagnostic>,
17 rendered: Option<String>,
20 #[derive(Deserialize)]
21 struct ArtifactNotification {
26 #[derive(Deserialize, Clone)]
27 struct DiagnosticSpan {
34 label: Option<String>,
35 suggested_replacement: Option<String>,
36 expansion: Option<Box<DiagnosticSpanMacroExpansion>>,
39 #[derive(Deserialize)]
40 struct FutureIncompatReport {
41 future_incompat_report: Vec<FutureBreakageItem>,
44 #[derive(Deserialize)]
45 struct FutureBreakageItem {
46 diagnostic: Diagnostic,
50 /// Returns the deepest source span in the macro call stack with a given file name.
51 /// This is either the supplied span, or the span for some macro callsite that expanded to it.
52 fn first_callsite_in_file(&self, file_name: &str) -> &DiagnosticSpan {
53 if self.file_name == file_name {
58 .map(|origin| origin.span.first_callsite_in_file(file_name))
64 #[derive(Deserialize, Clone)]
65 struct DiagnosticSpanMacroExpansion {
66 /// span where macro was applied to generate this code
69 /// name of macro that was applied (e.g., "foo!" or "#[derive(Eq)]")
70 macro_decl_name: String,
73 #[derive(Deserialize, Clone)]
74 struct DiagnosticCode {
77 /// An explanation for the code.
78 explanation: Option<String>,
81 pub fn rustfix_diagnostics_only(output: &str) -> String {
84 .filter(|line| line.starts_with('{') && serde_json::from_str::<Diagnostic>(line).is_ok())
88 pub fn extract_rendered(output: &str) -> String {
92 if line.starts_with('{') {
93 if let Ok(diagnostic) = serde_json::from_str::<Diagnostic>(line) {
95 } else if let Ok(report) = serde_json::from_str::<FutureIncompatReport>(line) {
96 if report.future_incompat_report.is_empty() {
100 "Future incompatibility report: {}",
102 .future_incompat_report
106 "Future breakage diagnostic:\n{}",
109 .unwrap_or_else(|| "Not rendered".to_string())
115 } else if serde_json::from_str::<ArtifactNotification>(line).is_ok() {
116 // Ignore the notification.
120 "failed to decode compiler output as json: line: {}\noutput: {}",
126 // preserve non-JSON lines, such as ICEs
127 Some(format!("{}\n", line))
133 pub fn parse_output(file_name: &str, output: &str, proc_res: &ProcRes) -> Vec<Error> {
134 output.lines().flat_map(|line| parse_line(file_name, line, output, proc_res)).collect()
137 fn parse_line(file_name: &str, line: &str, output: &str, proc_res: &ProcRes) -> Vec<Error> {
138 // The compiler sometimes intermingles non-JSON stuff into the
139 // output. This hack just skips over such lines. Yuck.
140 if line.starts_with('{') {
141 match serde_json::from_str::<Diagnostic>(line) {
143 let mut expected_errors = vec![];
144 push_expected_errors(&mut expected_errors, &diagnostic, &[], file_name);
148 // Ignore the future compat report message - this is handled
149 // by `extract_rendered`
150 if serde_json::from_str::<FutureIncompatReport>(line).is_ok() {
155 "failed to decode compiler output as json: \
156 `{}`\nline: {}\noutput: {}",
169 fn push_expected_errors(
170 expected_errors: &mut Vec<Error>,
171 diagnostic: &Diagnostic,
172 default_spans: &[&DiagnosticSpan],
175 // In case of macro expansions, we need to get the span of the callsite
176 let spans_info_in_this_file: Vec<_> = diagnostic
179 .map(|span| (span.is_primary, span.first_callsite_in_file(file_name)))
180 .filter(|(_, span)| Path::new(&span.file_name) == Path::new(&file_name))
183 let spans_in_this_file: Vec<_> = spans_info_in_this_file.iter().map(|(_, span)| span).collect();
185 let primary_spans: Vec<_> = spans_info_in_this_file
187 .filter(|(is_primary, _)| *is_primary)
188 .map(|(_, span)| span)
189 .take(1) // sometimes we have more than one showing up in the json; pick first
192 let primary_spans = if primary_spans.is_empty() {
193 // subdiagnostics often don't have a span of their own;
194 // inherit the span from the parent in that case
200 // We break the output into multiple lines, and then append the
201 // [E123] to every line in the output. This may be overkill. The
202 // intention was to match existing tests that do things like "//|
203 // found `i32` [E123]" and expect to match that somewhere, and yet
204 // also ensure that `//~ ERROR E123` *always* works. The
205 // assumption is that these multi-line error messages are on their
207 let with_code = |span: &DiagnosticSpan, text: &str| {
208 match diagnostic.code {
210 // FIXME(#33000) -- it'd be better to use a dedicated
211 // UI harness than to include the line/col number like
212 // this, but some current tests rely on it.
214 // Note: Do NOT include the filename. These can easily
215 // cause false matches where the expected message
216 // appears in the filename, and hence the message
217 // changes but the test still passes.
220 "{}:{}: {}:{}: {} [{}]",
230 // FIXME(#33000) -- it'd be better to use a dedicated UI harness
234 span.line_start, span.column_start, span.line_end, span.column_end, text
240 // Convert multi-line messages into multiple expected
241 // errors. We expect to replace these with something
242 // more structured shortly anyhow.
243 let mut message_lines = diagnostic.message.lines();
244 if let Some(first_line) = message_lines.next() {
245 for span in primary_spans {
246 let msg = with_code(span, first_line);
247 let kind = ErrorKind::from_str(&diagnostic.level).ok();
248 expected_errors.push(Error { line_num: span.line_start, kind, msg });
251 for next_line in message_lines {
252 for span in primary_spans {
253 expected_errors.push(Error {
254 line_num: span.line_start,
256 msg: with_code(span, next_line),
261 // If the message has a suggestion, register that.
262 for span in primary_spans {
263 if let Some(ref suggested_replacement) = span.suggested_replacement {
264 for (index, line) in suggested_replacement.lines().enumerate() {
265 expected_errors.push(Error {
266 line_num: span.line_start + index,
267 kind: Some(ErrorKind::Suggestion),
268 msg: line.to_string(),
274 // Add notes for the backtrace
275 for span in primary_spans {
276 for frame in &span.expansion {
277 push_backtrace(expected_errors, frame, file_name);
281 // Add notes for any labels that appear in the message.
282 for span in spans_in_this_file.iter().filter(|span| span.label.is_some()) {
283 expected_errors.push(Error {
284 line_num: span.line_start,
285 kind: Some(ErrorKind::Note),
286 msg: span.label.clone().unwrap(),
290 // Flatten out the children.
291 for child in &diagnostic.children {
292 push_expected_errors(expected_errors, child, primary_spans, file_name);
297 expected_errors: &mut Vec<Error>,
298 expansion: &DiagnosticSpanMacroExpansion,
301 if Path::new(&expansion.span.file_name) == Path::new(&file_name) {
302 expected_errors.push(Error {
303 line_num: expansion.span.line_start,
304 kind: Some(ErrorKind::Note),
305 msg: format!("in this expansion of {}", expansion.macro_decl_name),
309 for previous_expansion in &expansion.span.expansion {
310 push_backtrace(expected_errors, previous_expansion, file_name);