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};
12 use rustc_serialize::json;
13 use std::str::FromStr;
17 // These structs are a subset of the ones found in
20 #[derive(RustcEncodable, RustcDecodable)]
23 code: Option<DiagnosticCode>,
25 spans: Vec<DiagnosticSpan>,
26 children: Vec<Diagnostic>,
27 rendered: Option<String>,
30 #[derive(RustcEncodable, RustcDecodable, Clone)]
31 struct DiagnosticSpan {
38 label: Option<String>,
39 expansion: Option<Box<DiagnosticSpanMacroExpansion>>,
42 #[derive(RustcEncodable, RustcDecodable, Clone)]
43 struct DiagnosticSpanMacroExpansion {
44 /// span where macro was applied to generate this code
47 /// name of macro that was applied (e.g., "foo!" or "#[derive(Eq)]")
48 macro_decl_name: String,
51 #[derive(RustcEncodable, RustcDecodable, Clone)]
52 struct DiagnosticCode {
55 /// An explanation for the code.
56 explanation: Option<String>,
59 pub fn parse_output(file_name: &str, output: &str, proc_res: &ProcRes) -> Vec<Error> {
61 .flat_map(|line| parse_line(file_name, line, output, proc_res))
65 fn parse_line(file_name: &str, line: &str, output: &str, proc_res: &ProcRes) -> Vec<Error> {
66 // The compiler sometimes intermingles non-JSON stuff into the
67 // output. This hack just skips over such lines. Yuck.
68 if line.chars().next() == Some('{') {
69 match json::decode::<Diagnostic>(line) {
71 let mut expected_errors = vec![];
72 push_expected_errors(&mut expected_errors, &diagnostic, &[], file_name);
76 proc_res.fatal(Some(&format!("failed to decode compiler output as json: \
77 `{}`\noutput: {}\nline: {}",
88 fn push_expected_errors(expected_errors: &mut Vec<Error>,
89 diagnostic: &Diagnostic,
90 default_spans: &[&DiagnosticSpan],
92 let spans_in_this_file: Vec<_> = diagnostic.spans
94 .filter(|span| Path::new(&span.file_name) == Path::new(&file_name))
97 let primary_spans: Vec<_> = spans_in_this_file.iter()
99 .filter(|span| span.is_primary)
101 let primary_spans = if primary_spans.is_empty() {
102 // subdiagnostics often don't have a span of their own;
103 // inherit the span from the parent in that case
109 // We break the output into multiple lines, and then append the
110 // [E123] to every line in the output. This may be overkill. The
111 // intention was to match existing tests that do things like "//|
112 // found `i32` [E123]" and expect to match that somewhere, and yet
113 // also ensure that `//~ ERROR E123` *always* works. The
114 // assumption is that these multi-line error messages are on their
116 let with_code = |span: &DiagnosticSpan, text: &str| {
117 match diagnostic.code {
119 // FIXME(#33000) -- it'd be better to use a dedicated
120 // UI harness than to include the line/col number like
121 // this, but some current tests rely on it.
123 // Note: Do NOT include the filename. These can easily
124 // cause false matches where the expected message
125 // appears in the filename, and hence the message
126 // changes but the test still passes.
127 format!("{}:{}: {}:{}: {} [{}]",
128 span.line_start, span.column_start,
129 span.line_end, span.column_end,
130 text, code.code.clone()),
132 // FIXME(#33000) -- it'd be better to use a dedicated UI harness
133 format!("{}:{}: {}:{}: {}",
134 span.line_start, span.column_start,
135 span.line_end, span.column_end,
140 // Convert multi-line messages into multiple expected
141 // errors. We expect to replace these with something
142 // more structured shortly anyhow.
143 let mut message_lines = diagnostic.message.lines();
144 if let Some(first_line) = message_lines.next() {
145 for span in primary_spans {
146 let msg = with_code(span, first_line);
147 let kind = ErrorKind::from_str(&diagnostic.level).ok();
148 expected_errors.push(Error {
149 line_num: span.line_start,
155 for next_line in message_lines {
156 for span in primary_spans {
157 expected_errors.push(Error {
158 line_num: span.line_start,
160 msg: with_code(span, next_line),
165 // If the message has a suggestion, register that.
166 if let Some(ref rendered) = diagnostic.rendered {
167 let start_line = primary_spans.iter().map(|s| s.line_start).min().expect("\
168 every suggestion should have at least one span");
169 for (index, line) in rendered.lines().enumerate() {
170 expected_errors.push(Error {
171 line_num: start_line + index,
172 kind: Some(ErrorKind::Suggestion),
173 msg: line.to_string(),
178 // Add notes for the backtrace
179 for span in primary_spans {
180 for frame in &span.expansion {
181 push_backtrace(expected_errors, frame, file_name);
185 // Add notes for any labels that appear in the message.
186 for span in spans_in_this_file.iter()
187 .filter(|span| span.label.is_some()) {
188 expected_errors.push(Error {
189 line_num: span.line_start,
190 kind: Some(ErrorKind::Note),
191 msg: span.label.clone().unwrap(),
195 // Flatten out the children.
196 for child in &diagnostic.children {
197 push_expected_errors(expected_errors, child, primary_spans, file_name);
201 fn push_backtrace(expected_errors: &mut Vec<Error>,
202 expansion: &DiagnosticSpanMacroExpansion,
204 if Path::new(&expansion.span.file_name) == Path::new(&file_name) {
205 expected_errors.push(Error {
206 line_num: expansion.span.line_start,
207 kind: Some(ErrorKind::Note),
208 msg: format!("in this expansion of {}", expansion.macro_decl_name),
212 for previous_expansion in &expansion.span.expansion {
213 push_backtrace(expected_errors, previous_expansion, file_name);