// Copyright 2012-2015 The Rust Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution and at // http://rust-lang.org/COPYRIGHT. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. use errors::{Error, ErrorKind}; use serde_json; use std::str::FromStr; use std::path::Path; use runtest::ProcRes; // These structs are a subset of the ones found in // `syntax::json`. #[derive(Deserialize)] struct Diagnostic { message: String, code: Option, level: String, spans: Vec, children: Vec, rendered: Option, } #[derive(Deserialize, Clone)] struct DiagnosticSpan { file_name: String, line_start: usize, line_end: usize, column_start: usize, column_end: usize, is_primary: bool, label: Option, suggested_replacement: Option, expansion: Option>, } impl DiagnosticSpan { /// Returns the deepest source span in the macro call stack with a given file name. /// This is either the supplied span, or the span for some macro callsite that expanded to it. fn first_callsite_in_file(&self, file_name: &str) -> &DiagnosticSpan { if self.file_name == file_name { self } else { self.expansion .as_ref() .map(|origin| origin.span.first_callsite_in_file(file_name)) .unwrap_or(self) } } } #[derive(Deserialize, Clone)] struct DiagnosticSpanMacroExpansion { /// span where macro was applied to generate this code span: DiagnosticSpan, /// name of macro that was applied (e.g., "foo!" or "#[derive(Eq)]") macro_decl_name: String, } #[derive(Deserialize, Clone)] struct DiagnosticCode { /// The code itself. code: String, /// An explanation for the code. explanation: Option, } pub fn extract_rendered(output: &str, proc_res: &ProcRes) -> String { output .lines() .filter_map(|line| { if line.starts_with('{') { match serde_json::from_str::(line) { Ok(diagnostic) => diagnostic.rendered, Err(error) => { proc_res.fatal(Some(&format!( "failed to decode compiler output as json: \ `{}`\nline: {}\noutput: {}", error, line, output ))); } } } else { // preserve non-JSON lines, such as ICEs Some(format!("{}\n", line)) } }) .collect() } pub fn parse_output(file_name: &str, output: &str, proc_res: &ProcRes) -> Vec { output.lines() .flat_map(|line| parse_line(file_name, line, output, proc_res)) .collect() } fn parse_line(file_name: &str, line: &str, output: &str, proc_res: &ProcRes) -> Vec { // The compiler sometimes intermingles non-JSON stuff into the // output. This hack just skips over such lines. Yuck. if line.starts_with('{') { match serde_json::from_str::(line) { Ok(diagnostic) => { let mut expected_errors = vec![]; push_expected_errors(&mut expected_errors, &diagnostic, &[], file_name); expected_errors } Err(error) => { proc_res.fatal(Some(&format!("failed to decode compiler output as json: \ `{}`\noutput: {}\nline: {}", error, line, output))); } } } else { vec![] } } fn push_expected_errors(expected_errors: &mut Vec, diagnostic: &Diagnostic, default_spans: &[&DiagnosticSpan], file_name: &str) { // In case of macro expansions, we need to get the span of the callsite let spans_info_in_this_file: Vec<_> = diagnostic .spans .iter() .map(|span| (span.is_primary, span.first_callsite_in_file(file_name))) .filter(|(_, span)| Path::new(&span.file_name) == Path::new(&file_name)) .collect(); let spans_in_this_file: Vec<_> = spans_info_in_this_file.iter() .map(|(_, span)| span) .collect(); let primary_spans: Vec<_> = spans_info_in_this_file.iter() .filter(|(is_primary, _)| *is_primary) .map(|(_, span)| span) .take(1) // sometimes we have more than one showing up in the json; pick first .cloned() .collect(); let primary_spans = if primary_spans.is_empty() { // subdiagnostics often don't have a span of their own; // inherit the span from the parent in that case default_spans } else { &primary_spans }; // We break the output into multiple lines, and then append the // [E123] to every line in the output. This may be overkill. The // intention was to match existing tests that do things like "//| // found `i32` [E123]" and expect to match that somewhere, and yet // also ensure that `//~ ERROR E123` *always* works. The // assumption is that these multi-line error messages are on their // way out anyhow. let with_code = |span: &DiagnosticSpan, text: &str| { match diagnostic.code { Some(ref code) => // FIXME(#33000) -- it'd be better to use a dedicated // UI harness than to include the line/col number like // this, but some current tests rely on it. // // Note: Do NOT include the filename. These can easily // cause false matches where the expected message // appears in the filename, and hence the message // changes but the test still passes. format!("{}:{}: {}:{}: {} [{}]", span.line_start, span.column_start, span.line_end, span.column_end, text, code.code.clone()), None => // FIXME(#33000) -- it'd be better to use a dedicated UI harness format!("{}:{}: {}:{}: {}", span.line_start, span.column_start, span.line_end, span.column_end, text), } }; // Convert multi-line messages into multiple expected // errors. We expect to replace these with something // more structured shortly anyhow. let mut message_lines = diagnostic.message.lines(); if let Some(first_line) = message_lines.next() { for span in primary_spans { let msg = with_code(span, first_line); let kind = ErrorKind::from_str(&diagnostic.level).ok(); expected_errors.push(Error { line_num: span.line_start, kind, msg, }); } } for next_line in message_lines { for span in primary_spans { expected_errors.push(Error { line_num: span.line_start, kind: None, msg: with_code(span, next_line), }); } } // If the message has a suggestion, register that. for span in primary_spans { if let Some(ref suggested_replacement) = span.suggested_replacement { for (index, line) in suggested_replacement.lines().enumerate() { expected_errors.push(Error { line_num: span.line_start + index, kind: Some(ErrorKind::Suggestion), msg: line.to_string(), }); } } } // Add notes for the backtrace for span in primary_spans { for frame in &span.expansion { push_backtrace(expected_errors, frame, file_name); } } // Add notes for any labels that appear in the message. for span in spans_in_this_file.iter() .filter(|span| span.label.is_some()) { expected_errors.push(Error { line_num: span.line_start, kind: Some(ErrorKind::Note), msg: span.label.clone().unwrap(), }); } // Flatten out the children. for child in &diagnostic.children { push_expected_errors(expected_errors, child, primary_spans, file_name); } } fn push_backtrace(expected_errors: &mut Vec, expansion: &DiagnosticSpanMacroExpansion, file_name: &str) { if Path::new(&expansion.span.file_name) == Path::new(&file_name) { expected_errors.push(Error { line_num: expansion.span.line_start, kind: Some(ErrorKind::Note), msg: format!("in this expansion of {}", expansion.macro_decl_name), }); } for previous_expansion in &expansion.span.expansion { push_backtrace(expected_errors, previous_expansion, file_name); } }