summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_builtin_macros/src/test.rs
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_builtin_macros/src/test.rs')
-rw-r--r--compiler/rustc_builtin_macros/src/test.rs185
1 files changed, 125 insertions, 60 deletions
diff --git a/compiler/rustc_builtin_macros/src/test.rs b/compiler/rustc_builtin_macros/src/test.rs
index e02c7e6c0..79d8be248 100644
--- a/compiler/rustc_builtin_macros/src/test.rs
+++ b/compiler/rustc_builtin_macros/src/test.rs
@@ -1,14 +1,13 @@
/// The expansion from a test function to the appropriate test struct for libtest
/// Ideally, this code would be in libtest but for efficiency and error messages it lives here.
use crate::util::{check_builtin_macro_attribute, warn_on_duplicate_attribute};
-use rustc_ast as ast;
use rustc_ast::ptr::P;
+use rustc_ast::{self as ast, attr};
use rustc_ast_pretty::pprust;
use rustc_errors::Applicability;
use rustc_expand::base::*;
-use rustc_session::Session;
use rustc_span::symbol::{sym, Ident, Symbol};
-use rustc_span::Span;
+use rustc_span::{FileNameDisplayPreference, Span};
use std::iter;
use thin_vec::{thin_vec, ThinVec};
@@ -33,7 +32,23 @@ pub fn expand_test_case(
}
let sp = ecx.with_def_site_ctxt(attr_sp);
- let mut item = anno_item.expect_item();
+ let (mut item, is_stmt) = match anno_item {
+ Annotatable::Item(item) => (item, false),
+ Annotatable::Stmt(stmt) if let ast::StmtKind::Item(_) = stmt.kind => if let ast::StmtKind::Item(i) = stmt.into_inner().kind {
+ (i, true)
+ } else {
+ unreachable!()
+ },
+ _ => {
+ ecx.struct_span_err(
+ anno_item.span(),
+ "`#[test_case]` attribute is only allowed on items",
+ )
+ .emit();
+
+ return vec![];
+ }
+ };
item = item.map(|mut item| {
let test_path_symbol = Symbol::intern(&item_path(
// skip the name of the root module
@@ -50,7 +65,13 @@ pub fn expand_test_case(
item
});
- return vec![Annotatable::Item(item)];
+ let ret = if is_stmt {
+ Annotatable::Stmt(P(ecx.stmt_item(item.span, item)))
+ } else {
+ Annotatable::Item(item)
+ };
+
+ vec![ret]
}
pub fn expand_test(
@@ -97,34 +118,22 @@ pub fn expand_test_or_bench(
}
}
other => {
- cx.struct_span_err(
- other.span(),
- "`#[test]` attribute is only allowed on non associated functions",
- )
- .emit();
+ not_testable_error(cx, attr_sp, None);
return vec![other];
}
};
- // Note: non-associated fn items are already handled by `expand_test_or_bench`
let ast::ItemKind::Fn(fn_) = &item.kind else {
- let diag = &cx.sess.parse_sess.span_diagnostic;
- let msg = "the `#[test]` attribute may only be used on a non-associated function";
- let mut err = match item.kind {
- // These were a warning before #92959 and need to continue being that to avoid breaking
- // stable user code (#94508).
- ast::ItemKind::MacCall(_) => diag.struct_span_warn(attr_sp, msg),
- // `.forget_guarantee()` needed to get these two arms to match types. Because of how
- // locally close the `.emit()` call is I'm comfortable with it, but if it can be
- // reworked in the future to not need it, it'd be nice.
- _ => diag.struct_span_err(attr_sp, msg).forget_guarantee(),
+ not_testable_error(cx, attr_sp, Some(&item));
+ return if is_stmt {
+ vec![Annotatable::Stmt(P(ast::Stmt {
+ id: ast::DUMMY_NODE_ID,
+ span: item.span,
+ kind: ast::StmtKind::Item(item),
+ }))]
+ } else {
+ vec![Annotatable::Item(item)]
};
- err.span_label(attr_sp, "the `#[test]` macro causes a function to be run on a test and has no effect on non-functions")
- .span_label(item.span, format!("expected a non-associated function, found {} {}", item.kind.article(), item.kind.descr()))
- .span_suggestion(attr_sp, "replace with conditional compilation to make the item only exist when tests are being run", "#[cfg(test)]", Applicability::MaybeIncorrect)
- .emit();
-
- return vec![Annotatable::Item(item)];
};
// has_*_signature will report any errors in the type so compilation
@@ -231,25 +240,29 @@ pub fn expand_test_or_bench(
&item.ident,
));
- let mut test_const = cx.item(
- sp,
- Ident::new(item.ident.name, sp),
- thin_vec![
- // #[cfg(test)]
- cx.attr_nested_word(sym::cfg, sym::test, attr_sp),
- // #[rustc_test_marker = "test_case_sort_key"]
- cx.attr_name_value_str(sym::rustc_test_marker, test_path_symbol, attr_sp),
- ],
- // const $ident: test::TestDescAndFn =
- ast::ItemKind::Const(
- ast::Defaultness::Final,
- cx.ty(sp, ast::TyKind::Path(None, test_path("TestDescAndFn"))),
- // test::TestDescAndFn {
- Some(
- cx.expr_struct(
- sp,
- test_path("TestDescAndFn"),
- thin_vec![
+ let location_info = get_location_info(cx, &item);
+
+ let mut test_const =
+ cx.item(
+ sp,
+ Ident::new(item.ident.name, sp),
+ thin_vec![
+ // #[cfg(test)]
+ cx.attr_nested_word(sym::cfg, sym::test, attr_sp),
+ // #[rustc_test_marker = "test_case_sort_key"]
+ cx.attr_name_value_str(sym::rustc_test_marker, test_path_symbol, attr_sp),
+ ],
+ // const $ident: test::TestDescAndFn =
+ ast::ItemKind::Const(
+ ast::ConstItem {
+ defaultness: ast::Defaultness::Final,
+ ty: cx.ty(sp, ast::TyKind::Path(None, test_path("TestDescAndFn"))),
+ // test::TestDescAndFn {
+ expr: Some(
+ cx.expr_struct(
+ sp,
+ test_path("TestDescAndFn"),
+ thin_vec![
// desc: test::TestDesc {
field(
"desc",
@@ -267,19 +280,26 @@ pub fn expand_test_or_bench(
),
),
// ignore: true | false
- field(
- "ignore",
- cx.expr_bool(sp, should_ignore(&cx.sess, &item)),
- ),
+ field("ignore", cx.expr_bool(sp, should_ignore(&item)),),
// ignore_message: Some("...") | None
field(
"ignore_message",
- if let Some(msg) = should_ignore_message(cx, &item) {
+ if let Some(msg) = should_ignore_message(&item) {
cx.expr_some(sp, cx.expr_str(sp, msg))
} else {
cx.expr_none(sp)
},
),
+ // source_file: <relative_path_of_source_file>
+ field("source_file", cx.expr_str(sp, location_info.0)),
+ // start_line: start line of the test fn identifier.
+ field("start_line", cx.expr_usize(sp, location_info.1)),
+ // start_col: start column of the test fn identifier.
+ field("start_col", cx.expr_usize(sp, location_info.2)),
+ // end_line: end line of the test fn identifier.
+ field("end_line", cx.expr_usize(sp, location_info.3)),
+ // end_col: end column of the test fn identifier.
+ field("end_col", cx.expr_usize(sp, location_info.4)),
// compile_fail: true | false
field("compile_fail", cx.expr_bool(sp, false)),
// no_run: true | false
@@ -329,10 +349,12 @@ pub fn expand_test_or_bench(
// testfn: test::StaticTestFn(...) | test::StaticBenchFn(...)
field("testfn", test_fn), // }
],
- ), // }
+ ), // }
+ ),
+ }
+ .into(),
),
- ),
- );
+ );
test_const = test_const.map(|mut tc| {
tc.vis.kind = ast::VisibilityKind::Public;
tc
@@ -364,6 +386,49 @@ pub fn expand_test_or_bench(
}
}
+fn not_testable_error(cx: &ExtCtxt<'_>, attr_sp: Span, item: Option<&ast::Item>) {
+ let diag = &cx.sess.parse_sess.span_diagnostic;
+ let msg = "the `#[test]` attribute may only be used on a non-associated function";
+ let mut err = match item.map(|i| &i.kind) {
+ // These were a warning before #92959 and need to continue being that to avoid breaking
+ // stable user code (#94508).
+ Some(ast::ItemKind::MacCall(_)) => diag.struct_span_warn(attr_sp, msg),
+ // `.forget_guarantee()` needed to get these two arms to match types. Because of how
+ // locally close the `.emit()` call is I'm comfortable with it, but if it can be
+ // reworked in the future to not need it, it'd be nice.
+ _ => diag.struct_span_err(attr_sp, msg).forget_guarantee(),
+ };
+ if let Some(item) = item {
+ err.span_label(
+ item.span,
+ format!(
+ "expected a non-associated function, found {} {}",
+ item.kind.article(),
+ item.kind.descr()
+ ),
+ );
+ }
+ err.span_label(attr_sp, "the `#[test]` macro causes a function to be run as a test and has no effect on non-functions")
+ .span_suggestion(attr_sp,
+ "replace with conditional compilation to make the item only exist when tests are being run",
+ "#[cfg(test)]",
+ Applicability::MaybeIncorrect)
+ .emit();
+}
+
+fn get_location_info(cx: &ExtCtxt<'_>, item: &ast::Item) -> (Symbol, usize, usize, usize, usize) {
+ let span = item.ident.span;
+ let (source_file, lo_line, lo_col, hi_line, hi_col) =
+ cx.sess.source_map().span_to_location_info(span);
+
+ let file_name = match source_file {
+ Some(sf) => sf.name.display(FileNameDisplayPreference::Remapped).to_string(),
+ None => "no-location".to_string(),
+ };
+
+ (Symbol::intern(&file_name), lo_line, lo_col, hi_line, hi_col)
+}
+
fn item_path(mod_path: &[Ident], item_ident: &Ident) -> String {
mod_path
.iter()
@@ -378,12 +443,12 @@ enum ShouldPanic {
Yes(Option<Symbol>),
}
-fn should_ignore(sess: &Session, i: &ast::Item) -> bool {
- sess.contains_name(&i.attrs, sym::ignore)
+fn should_ignore(i: &ast::Item) -> bool {
+ attr::contains_name(&i.attrs, sym::ignore)
}
-fn should_ignore_message(cx: &ExtCtxt<'_>, i: &ast::Item) -> Option<Symbol> {
- match cx.sess.find_by_name(&i.attrs, sym::ignore) {
+fn should_ignore_message(i: &ast::Item) -> Option<Symbol> {
+ match attr::find_by_name(&i.attrs, sym::ignore) {
Some(attr) => {
match attr.meta_item_list() {
// Handle #[ignore(bar = "foo")]
@@ -397,7 +462,7 @@ fn should_ignore_message(cx: &ExtCtxt<'_>, i: &ast::Item) -> Option<Symbol> {
}
fn should_panic(cx: &ExtCtxt<'_>, i: &ast::Item) -> ShouldPanic {
- match cx.sess.find_by_name(&i.attrs, sym::should_panic) {
+ match attr::find_by_name(&i.attrs, sym::should_panic) {
Some(attr) => {
let sd = &cx.sess.parse_sess.span_diagnostic;
@@ -463,7 +528,7 @@ fn test_type(cx: &ExtCtxt<'_>) -> TestType {
}
fn has_test_signature(cx: &ExtCtxt<'_>, i: &ast::Item) -> bool {
- let has_should_panic_attr = cx.sess.contains_name(&i.attrs, sym::should_panic);
+ let has_should_panic_attr = attr::contains_name(&i.attrs, sym::should_panic);
let sd = &cx.sess.parse_sess.span_diagnostic;
match &i.kind {
ast::ItemKind::Fn(box ast::Fn { sig, generics, .. }) => {