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
>>,
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
> {
97 output
.lines().flat_map(|line
| parse_line(file_name
, line
, output
, proc_res
)).collect()
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
) {
106 let mut expected_errors
= vec
![];
107 push_expected_errors(&mut expected_errors
, &diagnostic
, &[], file_name
);
111 proc_res
.fatal(Some(&format
!(
112 "failed to decode compiler output as json: \
113 `{}`\nline: {}\noutput: {}",
123 fn push_expected_errors(
124 expected_errors
: &mut Vec
<Error
>,
125 diagnostic
: &Diagnostic
,
126 default_spans
: &[&DiagnosticSpan
],
129 // In case of macro expansions, we need to get the span of the callsite
130 let spans_info_in_this_file
: Vec
<_
> = diagnostic
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
))
137 let spans_in_this_file
: Vec
<_
> = spans_info_in_this_file
.iter().map(|(_
, span
)| span
).collect();
139 let primary_spans
: Vec
<_
> = spans_info_in_this_file
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
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
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
161 let with_code
= |span
: &DiagnosticSpan
, text
: &str| {
162 match diagnostic
.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.
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.
174 "{}:{}: {}:{}: {} [{}]",
184 // FIXME(#33000) -- it'd be better to use a dedicated UI harness
188 span
.line_start
, span
.column_start
, span
.line_end
, span
.column_end
, text
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 }
);
205 for next_line
in message_lines
{
206 for span
in primary_spans
{
207 expected_errors
.push(Error
{
208 line_num
: span
.line_start
,
210 msg
: with_code(span
, next_line
),
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(),
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
);
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(),
244 // Flatten out the children.
245 for child
in &diagnostic
.children
{
246 push_expected_errors(expected_errors
, child
, primary_spans
, file_name
);
251 expected_errors
: &mut Vec
<Error
>,
252 expansion
: &DiagnosticSpanMacroExpansion
,
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
),
263 for previous_expansion
in &expansion
.span
.expansion
{
264 push_backtrace(expected_errors
, previous_expansion
, file_name
);