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;
10 #[derive(Deserialize)]
13 code: Option<DiagnosticCode>,
15 spans: Vec<DiagnosticSpan>,
16 children: Vec<Diagnostic>,
17 rendered: Option<String>,
20 #[derive(Deserialize, Clone)]
21 struct DiagnosticSpan {
28 label: Option<String>,
29 suggested_replacement: Option<String>,
30 expansion: Option<Box<DiagnosticSpanMacroExpansion>>,
34 /// Returns the deepest source span in the macro call stack with a given file name.
35 /// This is either the supplied span, or the span for some macro callsite that expanded to it.
36 fn first_callsite_in_file(&self, file_name: &str) -> &DiagnosticSpan {
37 if self.file_name == file_name {
42 .map(|origin| origin.span.first_callsite_in_file(file_name))
48 #[derive(Deserialize, Clone)]
49 struct DiagnosticSpanMacroExpansion {
50 /// span where macro was applied to generate this code
53 /// name of macro that was applied (e.g., "foo!" or "#[derive(Eq)]")
54 macro_decl_name: String,
57 #[derive(Deserialize, Clone)]
58 struct DiagnosticCode {
61 /// An explanation for the code.
62 explanation: Option<String>,
65 pub fn extract_rendered(output: &str) -> String {
69 if line.starts_with('{') {
70 match serde_json::from_str::<Diagnostic>(line) {
71 Ok(diagnostic) => diagnostic.rendered,
74 "failed to decode compiler output as json: \
75 `{}`\nline: {}\noutput: {}",
82 // preserve non-JSON lines, such as ICEs
83 Some(format!("{}\n", line))
89 pub fn parse_output(file_name: &str, output: &str, proc_res: &ProcRes) -> Vec<Error> {
92 .flat_map(|line| parse_line(file_name, line, output, proc_res))
96 fn parse_line(file_name: &str, line: &str, output: &str, proc_res: &ProcRes) -> Vec<Error> {
97 // The compiler sometimes intermingles non-JSON stuff into the
98 // output. This hack just skips over such lines. Yuck.
99 if line.starts_with('{') {
100 match serde_json::from_str::<Diagnostic>(line) {
102 let mut expected_errors = vec![];
103 push_expected_errors(&mut expected_errors, &diagnostic, &[], file_name);
107 proc_res.fatal(Some(&format!(
108 "failed to decode compiler output as json: \
109 `{}`\nline: {}\noutput: {}",
119 fn push_expected_errors(
120 expected_errors: &mut Vec<Error>,
121 diagnostic: &Diagnostic,
122 default_spans: &[&DiagnosticSpan],
125 // In case of macro expansions, we need to get the span of the callsite
126 let spans_info_in_this_file: Vec<_> = diagnostic
129 .map(|span| (span.is_primary, span.first_callsite_in_file(file_name)))
130 .filter(|(_, span)| Path::new(&span.file_name) == Path::new(&file_name))
133 let spans_in_this_file: Vec<_> = spans_info_in_this_file.iter()
134 .map(|(_, span)| span)
137 let primary_spans: Vec<_> = spans_info_in_this_file.iter()
138 .filter(|(is_primary, _)| *is_primary)
139 .map(|(_, span)| span)
140 .take(1) // sometimes we have more than one showing up in the json; pick first
143 let primary_spans = if primary_spans.is_empty() {
144 // subdiagnostics often don't have a span of their own;
145 // inherit the span from the parent in that case
151 // We break the output into multiple lines, and then append the
152 // [E123] to every line in the output. This may be overkill. The
153 // intention was to match existing tests that do things like "//|
154 // found `i32` [E123]" and expect to match that somewhere, and yet
155 // also ensure that `//~ ERROR E123` *always* works. The
156 // assumption is that these multi-line error messages are on their
158 let with_code = |span: &DiagnosticSpan, text: &str| {
159 match diagnostic.code {
161 // FIXME(#33000) -- it'd be better to use a dedicated
162 // UI harness than to include the line/col number like
163 // this, but some current tests rely on it.
165 // Note: Do NOT include the filename. These can easily
166 // cause false matches where the expected message
167 // appears in the filename, and hence the message
168 // changes but the test still passes.
169 format!("{}:{}: {}:{}: {} [{}]",
170 span.line_start, span.column_start,
171 span.line_end, span.column_end,
172 text, code.code.clone()),
174 // FIXME(#33000) -- it'd be better to use a dedicated UI harness
175 format!("{}:{}: {}:{}: {}",
176 span.line_start, span.column_start,
177 span.line_end, span.column_end,
182 // Convert multi-line messages into multiple expected
183 // errors. We expect to replace these with something
184 // more structured shortly anyhow.
185 let mut message_lines = diagnostic.message.lines();
186 if let Some(first_line) = message_lines.next() {
187 for span in primary_spans {
188 let msg = with_code(span, first_line);
189 let kind = ErrorKind::from_str(&diagnostic.level).ok();
190 expected_errors.push(Error {
191 line_num: span.line_start,
197 for next_line in message_lines {
198 for span in primary_spans {
199 expected_errors.push(Error {
200 line_num: span.line_start,
202 msg: with_code(span, next_line),
207 // If the message has a suggestion, register that.
208 for span in primary_spans {
209 if let Some(ref suggested_replacement) = span.suggested_replacement {
210 for (index, line) in suggested_replacement.lines().enumerate() {
211 expected_errors.push(Error {
212 line_num: span.line_start + index,
213 kind: Some(ErrorKind::Suggestion),
214 msg: line.to_string(),
220 // Add notes for the backtrace
221 for span in primary_spans {
222 for frame in &span.expansion {
223 push_backtrace(expected_errors, frame, file_name);
227 // Add notes for any labels that appear in the message.
228 for span in spans_in_this_file
230 .filter(|span| span.label.is_some())
232 expected_errors.push(Error {
233 line_num: span.line_start,
234 kind: Some(ErrorKind::Note),
235 msg: span.label.clone().unwrap(),
239 // Flatten out the children.
240 for child in &diagnostic.children {
241 push_expected_errors(expected_errors, child, primary_spans, file_name);
246 expected_errors: &mut Vec<Error>,
247 expansion: &DiagnosticSpanMacroExpansion,
250 if Path::new(&expansion.span.file_name) == Path::new(&file_name) {
251 expected_errors.push(Error {
252 line_num: expansion.span.line_start,
253 kind: Some(ErrorKind::Note),
254 msg: format!("in this expansion of {}", expansion.macro_decl_name),
258 for previous_expansion in &expansion.span.expansion {
259 push_backtrace(expected_errors, previous_expansion, file_name);