From dd89a980e2a2e8426b64e5b55169a568f19e1527 Mon Sep 17 00:00:00 2001 From: Federico Poli Date: Tue, 24 Jul 2018 16:14:45 +0200 Subject: [PATCH] Match errors using the callsite of macro expansions Closes #48 This commit corresponds to 6a1c0637ce44aeea6c60527f4c0e7fb33f2bcd0d on rust-lang/rust --- src/json.rs | 33 ++++++++++++++++++++---- src/runtest.rs | 6 ++++- test-project/tests/compile-fail/macro.rs | 27 +++++++++++++++++++ 3 files changed, 60 insertions(+), 6 deletions(-) create mode 100644 test-project/tests/compile-fail/macro.rs diff --git a/src/json.rs b/src/json.rs index 0863062..3d7fd57 100644 --- a/src/json.rs +++ b/src/json.rs @@ -39,6 +39,21 @@ struct DiagnosticSpan { 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 @@ -89,15 +104,23 @@ fn push_expected_errors(expected_errors: &mut Vec, diagnostic: &Diagnostic, default_spans: &[&DiagnosticSpan], file_name: &str) { - let spans_in_this_file: Vec<_> = diagnostic.spans + // In case of macro expansions, we need to get the span of the callsite + let spans_info_in_this_file: Vec<_> = diagnostic + .spans .iter() - .filter(|span| Path::new(&span.file_name) == Path::new(&file_name)) + .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 primary_spans: Vec<_> = spans_in_this_file.iter() - .cloned() - .filter(|span| span.is_primary) + 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; diff --git a/src/runtest.rs b/src/runtest.rs index a13d0a2..4232e55 100644 --- a/src/runtest.rs +++ b/src/runtest.rs @@ -1008,6 +1008,10 @@ actual:\n\ self.fatal_proc_rec("process did not return an error status", proc_res); } + // On Windows, keep all '\' path separators to match the paths reported in the JSON output + // from the compiler + let os_file_name = self.testpaths.file.display().to_string(); + let file_name = format!("{}", self.testpaths.file.display()) .replace(r"\", "/"); // on windows, translate all '\' path separators to '/' @@ -1020,7 +1024,7 @@ actual:\n\ let expect_note = expected_errors.iter().any(|ee| ee.kind == Some(ErrorKind::Note)); // Parse the JSON output from the compiler and extract out the messages. - let actual_errors = json::parse_output(&file_name, &proc_res.stderr, proc_res); + let actual_errors = json::parse_output(&os_file_name, &proc_res.stderr, proc_res); let mut unexpected = Vec::new(); let mut found = vec![false; expected_errors.len()]; for actual_error in &actual_errors { diff --git a/test-project/tests/compile-fail/macro.rs b/test-project/tests/compile-fail/macro.rs new file mode 100644 index 0000000..3c34910 --- /dev/null +++ b/test-project/tests/compile-fail/macro.rs @@ -0,0 +1,27 @@ +// Copyright 2018 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. + +macro_rules! macro_with_error { + ( ) => { + println!("{"); //~ ERROR invalid + }; +} + +fn foo() { + +} + +fn main() { + macro_with_error!(); + //^ In case of a local macro we want the error to be matched in the macro definition, not here + + println!("}"); //~ ERROR invalid + //^ In case of an external macro we want the error to be matched here +}