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
;
8 use std
::path
::{Path, PathBuf}
;
11 #[derive(Deserialize)]
14 code
: Option
<DiagnosticCode
>,
16 spans
: Vec
<DiagnosticSpan
>,
17 children
: Vec
<Diagnostic
>,
18 rendered
: Option
<String
>,
21 #[derive(Deserialize)]
22 struct ArtifactNotification
{
27 #[derive(Deserialize, Clone)]
28 struct DiagnosticSpan
{
35 label
: Option
<String
>,
36 suggested_replacement
: Option
<String
>,
37 expansion
: Option
<Box
<DiagnosticSpanMacroExpansion
>>,
41 /// Returns the deepest source span in the macro call stack with a given file name.
42 /// This is either the supplied span, or the span for some macro callsite that expanded to it.
43 fn first_callsite_in_file(&self, file_name
: &str) -> &DiagnosticSpan
{
44 if self.file_name
== file_name
{
49 .map(|origin
| origin
.span
.first_callsite_in_file(file_name
))
55 #[derive(Deserialize, Clone)]
56 struct DiagnosticSpanMacroExpansion
{
57 /// span where macro was applied to generate this code
60 /// name of macro that was applied (e.g., "foo!" or "#[derive(Eq)]")
61 macro_decl_name
: String
,
64 #[derive(Deserialize, Clone)]
65 struct DiagnosticCode
{
68 /// An explanation for the code.
69 explanation
: Option
<String
>,
72 pub fn extract_rendered(output
: &str) -> String
{
76 if line
.starts_with('
{'
) {
77 if let Ok(diagnostic
) = serde_json
::from_str
::<Diagnostic
>(line
) {
79 } else if let Ok(_
) = serde_json
::from_str
::<ArtifactNotification
>(line
) {
80 // Ignore the notification.
84 "failed to decode compiler output as json: line: {}\noutput: {}",
90 // preserve non-JSON lines, such as ICEs
91 Some(format
!("{}\n", line
))
97 pub fn parse_output(file_name
: &str, output
: &str, proc_res
: &ProcRes
) -> Vec
<Error
> {
98 output
.lines().flat_map(|line
| parse_line(file_name
, line
, output
, proc_res
)).collect()
101 fn parse_line(file_name
: &str, line
: &str, output
: &str, proc_res
: &ProcRes
) -> Vec
<Error
> {
102 // The compiler sometimes intermingles non-JSON stuff into the
103 // output. This hack just skips over such lines. Yuck.
104 if line
.starts_with('
{'
) {
105 match serde_json
::from_str
::<Diagnostic
>(line
) {
107 let mut expected_errors
= vec
![];
108 push_expected_errors(&mut expected_errors
, &diagnostic
, &[], file_name
);
112 proc_res
.fatal(Some(&format
!(
113 "failed to decode compiler output as json: \
114 `{}`\nline: {}\noutput: {}",
124 fn push_expected_errors(
125 expected_errors
: &mut Vec
<Error
>,
126 diagnostic
: &Diagnostic
,
127 default_spans
: &[&DiagnosticSpan
],
130 // In case of macro expansions, we need to get the span of the callsite
131 let spans_info_in_this_file
: Vec
<_
> = diagnostic
134 .map(|span
| (span
.is_primary
, span
.first_callsite_in_file(file_name
)))
135 .filter(|(_
, span
)| Path
::new(&span
.file_name
) == Path
::new(&file_name
))
138 let spans_in_this_file
: Vec
<_
> = spans_info_in_this_file
.iter().map(|(_
, span
)| span
).collect();
140 let primary_spans
: Vec
<_
> = spans_info_in_this_file
142 .filter(|(is_primary
, _
)| *is_primary
)
143 .map(|(_
, span
)| span
)
144 .take(1) // sometimes we have more than one showing up in the json; pick first
147 let primary_spans
= if primary_spans
.is_empty() {
148 // subdiagnostics often don't have a span of their own;
149 // inherit the span from the parent in that case
155 // We break the output into multiple lines, and then append the
156 // [E123] to every line in the output. This may be overkill. The
157 // intention was to match existing tests that do things like "//|
158 // found `i32` [E123]" and expect to match that somewhere, and yet
159 // also ensure that `//~ ERROR E123` *always* works. The
160 // assumption is that these multi-line error messages are on their
162 let with_code
= |span
: &DiagnosticSpan
, text
: &str| {
163 match diagnostic
.code
{
165 // FIXME(#33000) -- it'd be better to use a dedicated
166 // UI harness than to include the line/col number like
167 // this, but some current tests rely on it.
169 // Note: Do NOT include the filename. These can easily
170 // cause false matches where the expected message
171 // appears in the filename, and hence the message
172 // changes but the test still passes.
175 "{}:{}: {}:{}: {} [{}]",
185 // FIXME(#33000) -- it'd be better to use a dedicated UI harness
189 span
.line_start
, span
.column_start
, span
.line_end
, span
.column_end
, text
195 // Convert multi-line messages into multiple expected
196 // errors. We expect to replace these with something
197 // more structured shortly anyhow.
198 let mut message_lines
= diagnostic
.message
.lines();
199 if let Some(first_line
) = message_lines
.next() {
200 for span
in primary_spans
{
201 let msg
= with_code(span
, first_line
);
202 let kind
= ErrorKind
::from_str(&diagnostic
.level
).ok();
203 expected_errors
.push(Error { line_num: span.line_start, kind, msg }
);
206 for next_line
in message_lines
{
207 for span
in primary_spans
{
208 expected_errors
.push(Error
{
209 line_num
: span
.line_start
,
211 msg
: with_code(span
, next_line
),
216 // If the message has a suggestion, register that.
217 for span
in primary_spans
{
218 if let Some(ref suggested_replacement
) = span
.suggested_replacement
{
219 for (index
, line
) in suggested_replacement
.lines().enumerate() {
220 expected_errors
.push(Error
{
221 line_num
: span
.line_start
+ index
,
222 kind
: Some(ErrorKind
::Suggestion
),
223 msg
: line
.to_string(),
229 // Add notes for the backtrace
230 for span
in primary_spans
{
231 for frame
in &span
.expansion
{
232 push_backtrace(expected_errors
, frame
, file_name
);
236 // Add notes for any labels that appear in the message.
237 for span
in spans_in_this_file
.iter().filter(|span
| span
.label
.is_some()) {
238 expected_errors
.push(Error
{
239 line_num
: span
.line_start
,
240 kind
: Some(ErrorKind
::Note
),
241 msg
: span
.label
.clone().unwrap(),
245 // Flatten out the children.
246 for child
in &diagnostic
.children
{
247 push_expected_errors(expected_errors
, child
, primary_spans
, file_name
);
252 expected_errors
: &mut Vec
<Error
>,
253 expansion
: &DiagnosticSpanMacroExpansion
,
256 if Path
::new(&expansion
.span
.file_name
) == Path
::new(&file_name
) {
257 expected_errors
.push(Error
{
258 line_num
: expansion
.span
.line_start
,
259 kind
: Some(ErrorKind
::Note
),
260 msg
: format
!("in this expansion of {}", expansion
.macro_decl_name
),
264 for previous_expansion
in &expansion
.span
.expansion
{
265 push_backtrace(expected_errors
, previous_expansion
, file_name
);