summaryrefslogtreecommitdiffstats
path: root/vendor/compiletest_rs/src/json.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/compiletest_rs/src/json.rs')
-rw-r--r--vendor/compiletest_rs/src/json.rs263
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);
+ }
+}