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.
4 use crate::errors::{Error, ErrorKind};
5 use crate::runtest::ProcRes;
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>>,
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 {
48 .map(|origin| origin.span.first_callsite_in_file(file_name))
54 #[derive(Deserialize, Clone)]
55 struct DiagnosticSpanMacroExpansion {
56 /// span where macro was applied to generate this code
59 /// name of macro that was applied (e.g., "foo!" or "#[derive(Eq)]")
60 macro_decl_name: String,
63 #[derive(Deserialize, Clone)]
64 struct DiagnosticCode {
67 /// An explanation for the code.
68 explanation: Option<String>,
71 pub fn extract_rendered(output: &str) -> String {
75 if line.starts_with('{') {
76 if let Ok(diagnostic) = serde_json::from_str::<Diagnostic>(line) {
78 } else if let Ok(_) = serde_json::from_str::<ArtifactNotification>(line) {
79 // Ignore the notification.
83 "failed to decode compiler output as json: line: {}\noutput: {}",
89 // preserve non-JSON lines, such as ICEs
90 Some(format!("{}\n", line))
96 pub fn parse_output(file_name: &str, output: &str, proc_res: &ProcRes) -> Vec<Error> {
99 .flat_map(|line| parse_line(file_name, line, output, proc_res))
103 fn parse_line(file_name: &str, line: &str, output: &str, proc_res: &ProcRes) -> Vec<Error> {
104 // The compiler sometimes intermingles non-JSON stuff into the
105 // output. This hack just skips over such lines. Yuck.
106 if line.starts_with('{') {
107 match serde_json::from_str::<Diagnostic>(line) {
109 let mut expected_errors = vec![];
110 push_expected_errors(&mut expected_errors, &diagnostic, &[], file_name);
114 proc_res.fatal(Some(&format!(
115 "failed to decode compiler output as json: \
116 `{}`\nline: {}\noutput: {}",
126 fn push_expected_errors(
127 expected_errors: &mut Vec<Error>,
128 diagnostic: &Diagnostic,
129 default_spans: &[&DiagnosticSpan],
132 // In case of macro expansions, we need to get the span of the callsite
133 let spans_info_in_this_file: Vec<_> = diagnostic
136 .map(|span| (span.is_primary, span.first_callsite_in_file(file_name)))
137 .filter(|(_, span)| Path::new(&span.file_name) == Path::new(&file_name))
140 let spans_in_this_file: Vec<_> = spans_info_in_this_file.iter()
141 .map(|(_, span)| span)
144 let primary_spans: Vec<_> = spans_info_in_this_file.iter()
145 .filter(|(is_primary, _)| *is_primary)
146 .map(|(_, span)| span)
147 .take(1) // sometimes we have more than one showing up in the json; pick first
150 let primary_spans = if primary_spans.is_empty() {
151 // subdiagnostics often don't have a span of their own;
152 // inherit the span from the parent in that case
158 // We break the output into multiple lines, and then append the
159 // [E123] to every line in the output. This may be overkill. The
160 // intention was to match existing tests that do things like "//|
161 // found `i32` [E123]" and expect to match that somewhere, and yet
162 // also ensure that `//~ ERROR E123` *always* works. The
163 // assumption is that these multi-line error messages are on their
165 let with_code = |span: &DiagnosticSpan, text: &str| {
166 match diagnostic.code {
168 // FIXME(#33000) -- it'd be better to use a dedicated
169 // UI harness than to include the line/col number like
170 // this, but some current tests rely on it.
172 // Note: Do NOT include the filename. These can easily
173 // cause false matches where the expected message
174 // appears in the filename, and hence the message
175 // changes but the test still passes.
176 format!("{}:{}: {}:{}: {} [{}]",
177 span.line_start, span.column_start,
178 span.line_end, span.column_end,
179 text, code.code.clone()),
181 // FIXME(#33000) -- it'd be better to use a dedicated UI harness
182 format!("{}:{}: {}:{}: {}",
183 span.line_start, span.column_start,
184 span.line_end, span.column_end,
189 // Convert multi-line messages into multiple expected
190 // errors. We expect to replace these with something
191 // more structured shortly anyhow.
192 let mut message_lines = diagnostic.message.lines();
193 if let Some(first_line) = message_lines.next() {
194 for span in primary_spans {
195 let msg = with_code(span, first_line);
196 let kind = ErrorKind::from_str(&diagnostic.level).ok();
197 expected_errors.push(Error {
198 line_num: span.line_start,
204 for next_line in message_lines {
205 for span in primary_spans {
206 expected_errors.push(Error {
207 line_num: span.line_start,
209 msg: with_code(span, next_line),
214 // If the message has a suggestion, register that.
215 for span in primary_spans {
216 if let Some(ref suggested_replacement) = span.suggested_replacement {
217 for (index, line) in suggested_replacement.lines().enumerate() {
218 expected_errors.push(Error {
219 line_num: span.line_start + index,
220 kind: Some(ErrorKind::Suggestion),
221 msg: line.to_string(),
227 // Add notes for the backtrace
228 for span in primary_spans {
229 for frame in &span.expansion {
230 push_backtrace(expected_errors, frame, file_name);
234 // Add notes for any labels that appear in the message.
235 for span in spans_in_this_file
237 .filter(|span| span.label.is_some())
239 expected_errors.push(Error {
240 line_num: span.line_start,
241 kind: Some(ErrorKind::Note),
242 msg: span.label.clone().unwrap(),
246 // Flatten out the children.
247 for child in &diagnostic.children {
248 push_expected_errors(expected_errors, child, primary_spans, file_name);
253 expected_errors: &mut Vec<Error>,
254 expansion: &DiagnosticSpanMacroExpansion,
257 if Path::new(&expansion.span.file_name) == Path::new(&file_name) {
258 expected_errors.push(Error {
259 line_num: expansion.span.line_start,
260 kind: Some(ErrorKind::Note),
261 msg: format!("in this expansion of {}", expansion.macro_decl_name),
265 for previous_expansion in &expansion.span.expansion {
266 push_backtrace(expected_errors, previous_expansion, file_name);