1 // Copyright 2012-2015 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
11 use errors::{Error, ErrorKind};
15 use std::str::FromStr;
17 // These structs are a subset of the ones found in
20 #[derive(Deserialize)]
23 code: Option<DiagnosticCode>,
25 spans: Vec<DiagnosticSpan>,
26 children: Vec<Diagnostic>,
27 rendered: Option<String>,
30 #[derive(Deserialize, Clone)]
31 struct DiagnosticSpan {
38 label: Option<String>,
39 suggested_replacement: Option<String>,
40 expansion: Option<Box<DiagnosticSpanMacroExpansion>>,
44 /// Returns the deepest source span in the macro call stack with a given file name.
45 /// This is either the supplied span, or the span for some macro callsite that expanded to it.
46 fn first_callsite_in_file(&self, file_name: &str) -> &DiagnosticSpan {
47 if self.file_name == file_name {
52 .map(|origin| origin.span.first_callsite_in_file(file_name))
58 #[derive(Deserialize, Clone)]
59 struct DiagnosticSpanMacroExpansion {
60 /// span where macro was applied to generate this code
63 /// name of macro that was applied (e.g., "foo!" or "#[derive(Eq)]")
64 macro_decl_name: String,
67 #[derive(Deserialize, Clone)]
68 struct DiagnosticCode {
71 /// An explanation for the code.
72 explanation: Option<String>,
75 pub fn extract_rendered(output: &str, proc_res: &ProcRes) -> String {
79 if line.starts_with('{') {
80 match serde_json::from_str::<Diagnostic>(line) {
81 Ok(diagnostic) => diagnostic.rendered,
83 proc_res.fatal(Some(&format!(
84 "failed to decode compiler output as json: \
85 `{}`\nline: {}\noutput: {}",
97 pub fn parse_output(file_name: &str, output: &str, proc_res: &ProcRes) -> Vec<Error> {
100 .flat_map(|line| parse_line(file_name, line, output, proc_res))
104 fn parse_line(file_name: &str, line: &str, output: &str, proc_res: &ProcRes) -> Vec<Error> {
105 // The compiler sometimes intermingles non-JSON stuff into the
106 // output. This hack just skips over such lines. Yuck.
107 if line.starts_with('{') {
108 match serde_json::from_str::<Diagnostic>(line) {
110 let mut expected_errors = vec![];
111 push_expected_errors(&mut expected_errors, &diagnostic, &[], file_name);
115 proc_res.fatal(Some(&format!(
116 "failed to decode compiler output as json: \
117 `{}`\nline: {}\noutput: {}",
127 fn push_expected_errors(
128 expected_errors: &mut Vec<Error>,
129 diagnostic: &Diagnostic,
130 default_spans: &[&DiagnosticSpan],
133 // In case of macro expansions, we need to get the span of the callsite
134 let spans_info_in_this_file: Vec<_> = diagnostic
137 .map(|span| (span.is_primary, span.first_callsite_in_file(file_name)))
138 .filter(|(_, span)| Path::new(&span.file_name) == Path::new(&file_name))
141 let spans_in_this_file: Vec<_> = spans_info_in_this_file.iter()
142 .map(|(_, span)| span)
145 let primary_spans: Vec<_> = spans_info_in_this_file.iter()
146 .filter(|(is_primary, _)| *is_primary)
147 .map(|(_, span)| span)
148 .take(1) // sometimes we have more than one showing up in the json; pick first
151 let primary_spans = if primary_spans.is_empty() {
152 // subdiagnostics often don't have a span of their own;
153 // inherit the span from the parent in that case
159 // We break the output into multiple lines, and then append the
160 // [E123] to every line in the output. This may be overkill. The
161 // intention was to match existing tests that do things like "//|
162 // found `i32` [E123]" and expect to match that somewhere, and yet
163 // also ensure that `//~ ERROR E123` *always* works. The
164 // assumption is that these multi-line error messages are on their
166 let with_code = |span: &DiagnosticSpan, text: &str| {
167 match diagnostic.code {
169 // FIXME(#33000) -- it'd be better to use a dedicated
170 // UI harness than to include the line/col number like
171 // this, but some current tests rely on it.
173 // Note: Do NOT include the filename. These can easily
174 // cause false matches where the expected message
175 // appears in the filename, and hence the message
176 // changes but the test still passes.
177 format!("{}:{}: {}:{}: {} [{}]",
178 span.line_start, span.column_start,
179 span.line_end, span.column_end,
180 text, code.code.clone()),
182 // FIXME(#33000) -- it'd be better to use a dedicated UI harness
183 format!("{}:{}: {}:{}: {}",
184 span.line_start, span.column_start,
185 span.line_end, span.column_end,
190 // Convert multi-line messages into multiple expected
191 // errors. We expect to replace these with something
192 // more structured shortly anyhow.
193 let mut message_lines = diagnostic.message.lines();
194 if let Some(first_line) = message_lines.next() {
195 for span in primary_spans {
196 let msg = with_code(span, first_line);
197 let kind = ErrorKind::from_str(&diagnostic.level).ok();
198 expected_errors.push(Error {
199 line_num: span.line_start,
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
238 .filter(|span| span.label.is_some())
240 expected_errors.push(Error {
241 line_num: span.line_start,
242 kind: Some(ErrorKind::Note),
243 msg: span.label.clone().unwrap(),
247 // Flatten out the children.
248 for child in &diagnostic.children {
249 push_expected_errors(expected_errors, child, primary_spans, file_name);
254 expected_errors: &mut Vec<Error>,
255 expansion: &DiagnosticSpanMacroExpansion,
258 if Path::new(&expansion.span.file_name) == Path::new(&file_name) {
259 expected_errors.push(Error {
260 line_num: expansion.span.line_start,
261 kind: Some(ErrorKind::Note),
262 msg: format!("in this expansion of {}", expansion.macro_decl_name),
266 for previous_expansion in &expansion.span.expansion {
267 push_backtrace(expected_errors, previous_expansion, file_name);