diff options
Diffstat (limited to 'vendor/compiletest_rs/src/json.rs')
-rw-r--r-- | vendor/compiletest_rs/src/json.rs | 263 |
1 files changed, 263 insertions, 0 deletions
diff --git a/vendor/compiletest_rs/src/json.rs b/vendor/compiletest_rs/src/json.rs new file mode 100644 index 000000000..6f9e2ff10 --- /dev/null +++ b/vendor/compiletest_rs/src/json.rs @@ -0,0 +1,263 @@ +// 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 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<DiagnosticCode>, + level: String, + spans: Vec<DiagnosticSpan>, + children: Vec<Diagnostic>, + rendered: Option<String>, +} + +#[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<String>, + suggested_replacement: Option<String>, + expansion: Option<Box<DiagnosticSpanMacroExpansion>>, +} + +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<String>, +} + +pub fn extract_rendered(output: &str, proc_res: &ProcRes) -> String { + output + .lines() + .filter_map(|line| { + if line.starts_with('{') { + match serde_json::from_str::<Diagnostic>(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<Error> { + 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<Error> { + // 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::<Diagnostic>(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<Error>, + 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<Error>, + 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); + } +} |