diff options
Diffstat (limited to 'compiler/rustc_builtin_macros/src/test.rs')
-rw-r--r-- | compiler/rustc_builtin_macros/src/test.rs | 185 |
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, .. }) => { |