1use std::path::{Path, PathBuf};
4use std::str::FromStr;
5
6use serde::Deserialize;
7
8use crate::errors::{Error, ErrorKind};
9use crate::runtest::ProcRes;
10
11#[derive(Deserialize)]
12struct Diagnostic {
13 message: String,
14 code: Option<DiagnosticCode>,
15 level: String,
16 spans: Vec<DiagnosticSpan>,
17 children: Vec<Diagnostic>,
18 rendered: Option<String>,
19}
20
21#[derive(Deserialize)]
22struct ArtifactNotification {
23 #[allow(dead_code)]
24 artifact: PathBuf,
25}
26
27#[derive(Deserialize)]
28struct UnusedExternNotification {
29 #[allow(dead_code)]
30 lint_level: String,
31 #[allow(dead_code)]
32 unused_extern_names: Vec<String>,
33}
34
35#[derive(Deserialize, Clone)]
36struct DiagnosticSpan {
37 file_name: String,
38 line_start: usize,
39 line_end: usize,
40 column_start: usize,
41 column_end: usize,
42 is_primary: bool,
43 label: Option<String>,
44 suggested_replacement: Option<String>,
45 expansion: Option<Box<DiagnosticSpanMacroExpansion>>,
46}
47
48#[derive(Deserialize)]
49struct FutureIncompatReport {
50 future_incompat_report: Vec<FutureBreakageItem>,
51}
52
53#[derive(Deserialize)]
54struct FutureBreakageItem {
55 diagnostic: Diagnostic,
56}
57
58impl DiagnosticSpan {
59 fn first_callsite_in_file(&self, file_name: &str) -> &DiagnosticSpan {
62 if self.file_name == file_name {
63 self
64 } else {
65 self.expansion
66 .as_ref()
67 .map(|origin| origin.span.first_callsite_in_file(file_name))
68 .unwrap_or(self)
69 }
70 }
71}
72
73#[derive(Deserialize, Clone)]
74struct DiagnosticSpanMacroExpansion {
75 span: DiagnosticSpan,
77
78 macro_decl_name: String,
80}
81
82#[derive(Deserialize, Clone)]
83struct DiagnosticCode {
84 code: String,
86}
87
88pub fn rustfix_diagnostics_only(output: &str) -> String {
89 output
90 .lines()
91 .filter(|line| line.starts_with('{') && serde_json::from_str::<Diagnostic>(line).is_ok())
92 .collect()
93}
94
95pub fn extract_rendered(output: &str) -> String {
96 output
97 .lines()
98 .filter_map(|line| {
99 if line.starts_with('{') {
100 if let Ok(diagnostic) = serde_json::from_str::<Diagnostic>(line) {
101 diagnostic.rendered
102 } else if let Ok(report) = serde_json::from_str::<FutureIncompatReport>(line) {
103 if report.future_incompat_report.is_empty() {
104 None
105 } else {
106 Some(format!(
107 "Future incompatibility report: {}",
108 report
109 .future_incompat_report
110 .into_iter()
111 .map(|item| {
112 format!(
113 "Future breakage diagnostic:\n{}",
114 item.diagnostic
115 .rendered
116 .unwrap_or_else(|| "Not rendered".to_string())
117 )
118 })
119 .collect::<String>()
120 ))
121 }
122 } else if serde_json::from_str::<ArtifactNotification>(line).is_ok() {
123 None
125 } else if serde_json::from_str::<UnusedExternNotification>(line).is_ok() {
126 None
128 } else {
129 Some(format!("{line}\n"))
133 }
134 } else {
135 Some(format!("{}\n", line))
137 }
138 })
139 .collect()
140}
141
142pub 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()
144}
145
146fn parse_line(file_name: &str, line: &str, output: &str, proc_res: &ProcRes) -> Vec<Error> {
147 if line.starts_with('{') {
150 match serde_json::from_str::<Diagnostic>(line) {
151 Ok(diagnostic) => {
152 let mut expected_errors = vec![];
153 push_expected_errors(&mut expected_errors, &diagnostic, &[], file_name);
154 expected_errors
155 }
156 Err(error) => {
157 if serde_json::from_str::<FutureIncompatReport>(line).is_ok() {
160 vec![]
161 } else {
162 proc_res.fatal(
163 Some(&format!(
164 "failed to decode compiler output as json: \
165 `{}`\nline: {}\noutput: {}",
166 error, line, output
167 )),
168 || (),
169 );
170 }
171 }
172 }
173 } else {
174 vec![]
175 }
176}
177
178fn push_expected_errors(
179 expected_errors: &mut Vec<Error>,
180 diagnostic: &Diagnostic,
181 default_spans: &[&DiagnosticSpan],
182 file_name: &str,
183) {
184 let spans_info_in_this_file: Vec<_> = diagnostic
186 .spans
187 .iter()
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))
190 .collect();
191
192 let spans_in_this_file: Vec<_> = spans_info_in_this_file.iter().map(|(_, span)| span).collect();
193
194 let primary_spans: Vec<_> = spans_info_in_this_file
195 .iter()
196 .filter(|(is_primary, _)| *is_primary)
197 .map(|(_, span)| span)
198 .take(1) .cloned()
200 .collect();
201 let primary_spans = if primary_spans.is_empty() {
202 default_spans
205 } else {
206 &primary_spans
207 };
208
209 let with_code = |span: &DiagnosticSpan, text: &str| {
217 match diagnostic.code {
218 Some(ref code) =>
219 {
228 format!(
229 "{}:{}: {}:{}: {} [{}]",
230 span.line_start,
231 span.column_start,
232 span.line_end,
233 span.column_end,
234 text,
235 code.code.clone()
236 )
237 }
238 None =>
239 {
241 format!(
242 "{}:{}: {}:{}: {}",
243 span.line_start, span.column_start, span.line_end, span.column_end, text
244 )
245 }
246 }
247 };
248
249 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 });
258 }
259 }
260 for next_line in message_lines {
261 for span in primary_spans {
262 expected_errors.push(Error {
263 line_num: span.line_start,
264 kind: None,
265 msg: with_code(span, next_line),
266 });
267 }
268 }
269
270 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(),
278 });
279 }
280 }
281 }
282
283 for span in primary_spans {
285 if let Some(frame) = &span.expansion {
286 push_backtrace(expected_errors, frame, file_name);
287 }
288 }
289
290 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(),
296 });
297 }
298
299 for child in &diagnostic.children {
301 push_expected_errors(expected_errors, child, primary_spans, file_name);
302 }
303}
304
305fn push_backtrace(
306 expected_errors: &mut Vec<Error>,
307 expansion: &DiagnosticSpanMacroExpansion,
308 file_name: &str,
309) {
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),
315 });
316 }
317
318 if let Some(previous_expansion) = &expansion.span.expansion {
319 push_backtrace(expected_errors, previous_expansion, file_name);
320 }
321}