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)]
27 struct UnusedExternNotification {
31 unused_extern_names: Vec<String>,
34 #[derive(Deserialize, Clone)]
35 struct DiagnosticSpan {
42 label: Option<String>,
43 suggested_replacement: Option<String>,
44 expansion: Option<Box<DiagnosticSpanMacroExpansion>>,
47 #[derive(Deserialize)]
48 struct FutureIncompatReport {
49 future_incompat_report: Vec<FutureBreakageItem>,
52 #[derive(Deserialize)]
53 struct FutureBreakageItem {
54 diagnostic: Diagnostic,
58 /// Returns the deepest source span in the macro call stack with a given file name.
59 /// This is either the supplied span, or the span for some macro callsite that expanded to it.
60 fn first_callsite_in_file(&self, file_name: &str) -> &DiagnosticSpan {
61 if self.file_name == file_name {
66 .map(|origin| origin.span.first_callsite_in_file(file_name))
72 #[derive(Deserialize, Clone)]
73 struct DiagnosticSpanMacroExpansion {
74 /// span where macro was applied to generate this code
77 /// name of macro that was applied (e.g., "foo!" or "#[derive(Eq)]")
78 macro_decl_name: String,
81 #[derive(Deserialize, Clone)]
82 struct DiagnosticCode {
87 pub fn rustfix_diagnostics_only(output: &str) -> String {
90 .filter(|line| line.starts_with('{') && serde_json::from_str::<Diagnostic>(line).is_ok())
94 pub fn extract_rendered(output: &str) -> String {
98 if line.starts_with('{') {
99 if let Ok(diagnostic) = serde_json::from_str::<Diagnostic>(line) {
101 } else if let Ok(report) = serde_json::from_str::<FutureIncompatReport>(line) {
102 if report.future_incompat_report.is_empty() {
106 "Future incompatibility report: {}",
108 .future_incompat_report
112 "Future breakage diagnostic:\n{}",
115 .unwrap_or_else(|| "Not rendered".to_string())
121 } else if serde_json::from_str::<ArtifactNotification>(line).is_ok() {
122 // Ignore the notification.
124 } else if serde_json::from_str::<UnusedExternNotification>(line).is_ok() {
125 // Ignore the notification.
129 "failed to decode compiler output as json: line: {}\noutput: {}",
135 // preserve non-JSON lines, such as ICEs
136 Some(format!("{}\n", line))
142 pub fn parse_output(file_name: &str, output: &str, proc_res: &ProcRes) -> Vec<Error> {
143 output.lines().flat_map(|line| parse_line(file_name, line, output, proc_res)).collect()
146 fn parse_line(file_name: &str, line: &str, output: &str, proc_res: &ProcRes) -> Vec<Error> {
147 // The compiler sometimes intermingles non-JSON stuff into the
148 // output. This hack just skips over such lines. Yuck.
149 if line.starts_with('{') {
150 match serde_json::from_str::<Diagnostic>(line) {
152 let mut expected_errors = vec![];
153 push_expected_errors(&mut expected_errors, &diagnostic, &[], file_name);
157 // Ignore the future compat report message - this is handled
158 // by `extract_rendered`
159 if serde_json::from_str::<FutureIncompatReport>(line).is_ok() {
164 "failed to decode compiler output as json: \
165 `{}`\nline: {}\noutput: {}",
178 fn push_expected_errors(
179 expected_errors: &mut Vec<Error>,
180 diagnostic: &Diagnostic,
181 default_spans: &[&DiagnosticSpan],
184 // In case of macro expansions, we need to get the span of the callsite
185 let spans_info_in_this_file: Vec<_> = diagnostic
188 .map(|span| (span.is_primary, span.first_callsite_in_file(file_name)))
189 .filter(|(_, span)| Path::new(&span.file_name) == Path::new(&file_name))
192 let spans_in_this_file: Vec<_> = spans_info_in_this_file.iter().map(|(_, span)| span).collect();
194 let primary_spans: Vec<_> = spans_info_in_this_file
196 .filter(|(is_primary, _)| *is_primary)
197 .map(|(_, span)| span)
198 .take(1) // sometimes we have more than one showing up in the json; pick first
201 let primary_spans = if primary_spans.is_empty() {
202 // subdiagnostics often don't have a span of their own;
203 // inherit the span from the parent in that case
209 // We break the output into multiple lines, and then append the
210 // [E123] to every line in the output. This may be overkill. The
211 // intention was to match existing tests that do things like "//|
212 // found `i32` [E123]" and expect to match that somewhere, and yet
213 // also ensure that `//~ ERROR E123` *always* works. The
214 // assumption is that these multi-line error messages are on their
216 let with_code = |span: &DiagnosticSpan, text: &str| {
217 match diagnostic.code {
219 // FIXME(#33000) -- it'd be better to use a dedicated
220 // UI harness than to include the line/col number like
221 // this, but some current tests rely on it.
223 // Note: Do NOT include the filename. These can easily
224 // cause false matches where the expected message
225 // appears in the filename, and hence the message
226 // changes but the test still passes.
229 "{}:{}: {}:{}: {} [{}]",
239 // FIXME(#33000) -- it'd be better to use a dedicated UI harness
243 span.line_start, span.column_start, span.line_end, span.column_end, text
249 // Convert multi-line messages into multiple expected
250 // errors. We expect to replace these with something
251 // more structured shortly anyhow.
252 let mut message_lines = diagnostic.message.lines();
253 if let Some(first_line) = message_lines.next() {
254 for span in primary_spans {
255 let msg = with_code(span, first_line);
256 let kind = ErrorKind::from_str(&diagnostic.level).ok();
257 expected_errors.push(Error { line_num: span.line_start, kind, msg });
260 for next_line in message_lines {
261 for span in primary_spans {
262 expected_errors.push(Error {
263 line_num: span.line_start,
265 msg: with_code(span, next_line),
270 // If the message has a suggestion, register that.
271 for span in primary_spans {
272 if let Some(ref suggested_replacement) = span.suggested_replacement {
273 for (index, line) in suggested_replacement.lines().enumerate() {
274 expected_errors.push(Error {
275 line_num: span.line_start + index,
276 kind: Some(ErrorKind::Suggestion),
277 msg: line.to_string(),
283 // Add notes for the backtrace
284 for span in primary_spans {
285 for frame in &span.expansion {
286 push_backtrace(expected_errors, frame, file_name);
290 // Add notes for any labels that appear in the message.
291 for span in spans_in_this_file.iter().filter(|span| span.label.is_some()) {
292 expected_errors.push(Error {
293 line_num: span.line_start,
294 kind: Some(ErrorKind::Note),
295 msg: span.label.clone().unwrap(),
299 // Flatten out the children.
300 for child in &diagnostic.children {
301 push_expected_errors(expected_errors, child, primary_spans, file_name);
306 expected_errors: &mut Vec<Error>,
307 expansion: &DiagnosticSpanMacroExpansion,
310 if Path::new(&expansion.span.file_name) == Path::new(&file_name) {
311 expected_errors.push(Error {
312 line_num: expansion.span.line_start,
313 kind: Some(ErrorKind::Note),
314 msg: format!("in this expansion of {}", expansion.macro_decl_name),
318 for previous_expansion in &expansion.span.expansion {
319 push_backtrace(expected_errors, previous_expansion, file_name);