From 1eab1b19a39f54a825c9107086f0c1752b81224d Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Thu, 8 Feb 2018 17:16:39 -0500 Subject: [PATCH 01/11] support unit tests with return values that implement `Terminaton` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend `Termination` trait with a method to determine what happens with a unit test. This commit incorporates work by Bastian Köcher . --- src/librustc_driver/driver.rs | 3 +- src/libstd/termination.rs | 7 + src/libsyntax/lib.rs | 1 + src/libsyntax/test.rs | 170 +++++++++++++----- .../run-pass/termination-trait-in-test.rs | 28 +++ 5 files changed, 166 insertions(+), 43 deletions(-) create mode 100644 src/test/run-pass/termination-trait-in-test.rs diff --git a/src/librustc_driver/driver.rs b/src/librustc_driver/driver.rs index b8a1fe99105..e4600f25ea7 100644 --- a/src/librustc_driver/driver.rs +++ b/src/librustc_driver/driver.rs @@ -816,7 +816,8 @@ pub fn phase_2_configure_and_expand_inner<'a, F>(sess: &'a Session, &mut resolver, sess.opts.test, krate, - sess.diagnostic()) + sess.diagnostic(), + &sess.features.borrow()) }); // If we're actually rustdoc then there's no need to actually compile diff --git a/src/libstd/termination.rs b/src/libstd/termination.rs index dc7fa53aab6..f02fad009d8 100644 --- a/src/libstd/termination.rs +++ b/src/libstd/termination.rs @@ -37,6 +37,13 @@ pub trait Termination { /// Is called to get the representation of the value as status code. /// This status code is returned to the operating system. fn report(self) -> i32; + + /// Invoked when unit tests terminate. Should panic if the unit + /// test is considered a failure. By default, invokes `report()` + /// and checks for a `0` result. + fn assert_unit_test_successful(self) where Self: Sized { + assert_eq!(self.report(), 0); + } } #[unstable(feature = "termination_trait", issue = "43301")] diff --git a/src/libsyntax/lib.rs b/src/libsyntax/lib.rs index 9181cca215c..53ff3ccd48a 100644 --- a/src/libsyntax/lib.rs +++ b/src/libsyntax/lib.rs @@ -105,6 +105,7 @@ pub mod syntax { pub use ext; pub use parse; pub use ast; + pub use tokenstream; } pub mod abi; diff --git a/src/libsyntax/test.rs b/src/libsyntax/test.rs index e73550d0719..094de6868a5 100644 --- a/src/libsyntax/test.rs +++ b/src/libsyntax/test.rs @@ -32,6 +32,7 @@ use ext::build::AstBuilder; use ext::expand::ExpansionConfig; use ext::hygiene::{Mark, SyntaxContext}; use fold::Folder; +use feature_gate::Features; use util::move_map::MoveMap; use fold; use parse::{token, ParseSess}; @@ -63,6 +64,7 @@ struct TestCtxt<'a> { reexport_test_harness_main: Option, is_libtest: bool, ctxt: SyntaxContext, + features: &'a Features, // top-level re-export submodule, filled out after folding is finished toplevel_reexport: Option, @@ -74,7 +76,8 @@ pub fn modify_for_testing(sess: &ParseSess, resolver: &mut Resolver, should_test: bool, krate: ast::Crate, - span_diagnostic: &errors::Handler) -> ast::Crate { + span_diagnostic: &errors::Handler, + features: &Features) -> ast::Crate { // Check for #[reexport_test_harness_main = "some_name"] which // creates a `use some_name = __test::main;`. This needs to be // unconditional, so that the attribute is still marked as used in @@ -84,7 +87,8 @@ pub fn modify_for_testing(sess: &ParseSess, "reexport_test_harness_main"); if should_test { - generate_test_harness(sess, resolver, reexport_test_harness_main, krate, span_diagnostic) + generate_test_harness(sess, resolver, reexport_test_harness_main, + krate, span_diagnostic, features) } else { krate } @@ -265,16 +269,20 @@ fn generate_test_harness(sess: &ParseSess, resolver: &mut Resolver, reexport_test_harness_main: Option, krate: ast::Crate, - sd: &errors::Handler) -> ast::Crate { + sd: &errors::Handler, + features: &Features) -> ast::Crate { // Remove the entry points let mut cleaner = EntryPointCleaner { depth: 0 }; let krate = cleaner.fold_crate(krate); let mark = Mark::fresh(Mark::root()); + let mut econfig = ExpansionConfig::default("test".to_string()); + econfig.features = Some(features); + let cx = TestCtxt { span_diagnostic: sd, - ext_cx: ExtCtxt::new(sess, ExpansionConfig::default("test".to_string()), resolver), + ext_cx: ExtCtxt::new(sess, econfig, resolver), path: Vec::new(), testfns: Vec::new(), reexport_test_harness_main, @@ -282,6 +290,7 @@ fn generate_test_harness(sess: &ParseSess, is_libtest: attr::find_crate_name(&krate.attrs).map(|s| s == "test").unwrap_or(false), toplevel_reexport: None, ctxt: SyntaxContext::empty().apply_mark(mark), + features, }; mark.set_expn_info(ExpnInfo { @@ -318,71 +327,105 @@ enum HasTestSignature { fn is_test_fn(cx: &TestCtxt, i: &ast::Item) -> bool { let has_test_attr = attr::contains_name(&i.attrs, "test"); - fn has_test_signature(i: &ast::Item) -> HasTestSignature { + fn has_test_signature(cx: &TestCtxt, i: &ast::Item) -> HasTestSignature { match i.node { - ast::ItemKind::Fn(ref decl, _, _, _, ref generics, _) => { - let no_output = match decl.output { - ast::FunctionRetTy::Default(..) => true, - ast::FunctionRetTy::Ty(ref t) if t.node == ast::TyKind::Tup(vec![]) => true, - _ => false - }; - if decl.inputs.is_empty() - && no_output - && !generics.is_parameterized() { - Yes - } else { - No + ast::ItemKind::Fn(ref decl, _, _, _, ref generics, _) => { + // If the termination trait is active, the compiler will check that the output + // type implements the `Termination` trait as `libtest` enforces that. + let output_matches = if cx.features.termination_trait { + true + } else { + let no_output = match decl.output { + ast::FunctionRetTy::Default(..) => true, + ast::FunctionRetTy::Ty(ref t) if t.node == ast::TyKind::Tup(vec![]) => true, + _ => false + }; + + no_output && !generics.is_parameterized() + }; + + if decl.inputs.is_empty() && output_matches { + Yes + } else { + No + } } - } - _ => NotEvenAFunction, + _ => NotEvenAFunction, } } - if has_test_attr { + let has_test_signature = if has_test_attr { let diag = cx.span_diagnostic; - match has_test_signature(i) { - Yes => {}, - No => diag.span_err(i.span, "functions used as tests must have signature fn() -> ()"), - NotEvenAFunction => diag.span_err(i.span, - "only functions may be used as tests"), + match has_test_signature(cx, i) { + Yes => true, + No => { + if cx.features.termination_trait { + diag.span_err(i.span, "functions used as tests can not have any arguments"); + } else { + diag.span_err(i.span, "functions used as tests must have signature fn() -> ()"); + } + false + }, + NotEvenAFunction => { + diag.span_err(i.span, "only functions may be used as tests"); + false + }, } - } + } else { + false + }; - has_test_attr && has_test_signature(i) == Yes + has_test_attr && has_test_signature } fn is_bench_fn(cx: &TestCtxt, i: &ast::Item) -> bool { let has_bench_attr = attr::contains_name(&i.attrs, "bench"); - fn has_test_signature(i: &ast::Item) -> bool { + fn has_bench_signature(cx: &TestCtxt, i: &ast::Item) -> bool { match i.node { ast::ItemKind::Fn(ref decl, _, _, _, ref generics, _) => { let input_cnt = decl.inputs.len(); - let no_output = match decl.output { - ast::FunctionRetTy::Default(..) => true, - ast::FunctionRetTy::Ty(ref t) if t.node == ast::TyKind::Tup(vec![]) => true, - _ => false + + // If the termination trait is active, the compiler will check that the output + // type implements the `Termination` trait as `libtest` enforces that. + let output_matches = if cx.features.termination_trait { + true + } else { + let no_output = match decl.output { + ast::FunctionRetTy::Default(..) => true, + ast::FunctionRetTy::Ty(ref t) if t.node == ast::TyKind::Tup(vec![]) => true, + _ => false + }; + let tparm_cnt = generics.params.iter() + .filter(|param| param.is_type_param()) + .count(); + + no_output && tparm_cnt == 0 }; - let tparm_cnt = generics.params.iter() - .filter(|param| param.is_type_param()) - .count(); // NB: inadequate check, but we're running // well before resolve, can't get too deep. - input_cnt == 1 - && no_output && tparm_cnt == 0 + input_cnt == 1 && output_matches } _ => false } } - if has_bench_attr && !has_test_signature(i) { + let has_bench_signature = has_bench_signature(cx, i); + + if has_bench_attr && !has_bench_signature { let diag = cx.span_diagnostic; - diag.span_err(i.span, "functions used as benches must have signature \ - `fn(&mut Bencher) -> ()`"); + + if cx.features.termination_trait { + diag.span_err(i.span, "functions used as benches must have signature \ + `fn(&mut Bencher) -> impl Termination`"); + } else { + diag.span_err(i.span, "functions used as benches must have signature \ + `fn(&mut Bencher) -> ()`"); + } } - has_bench_attr && has_test_signature(i) + has_bench_attr && has_bench_signature } fn is_ignored(i: &ast::Item) -> bool { @@ -700,9 +743,52 @@ fn mk_test_desc_and_fn_rec(cx: &TestCtxt, test: &Test) -> P { }; visible_path.extend(path); - let fn_expr = ecx.expr_path(ecx.path_global(span, visible_path)); + // If termination feature is enabled, create a wrapper that invokes the fn + // like this: + // + // fn wrapper() { + // assert_eq!(0, real_function().report()); + // } + // + // and then put a reference to `wrapper` into the test descriptor. Otherwise, + // just put a direct reference to `real_function`. + let fn_expr = { + let base_fn_expr = ecx.expr_path(ecx.path_global(span, visible_path)); + if cx.features.termination_trait { + // ::std::Termination::assert_unit_test_successful + let assert_unit_test_successful = ecx.path_global( + span, + vec![ + ecx.ident_of("std"), + ecx.ident_of("Termination"), + ecx.ident_of("assert_unit_test_successful"), + ], + ); + // || {..} + ecx.lambda( + span, + vec![], + // ::std::Termination::assert_unit_test_successful(..) + ecx.expr_call( + span, + ecx.expr_path(assert_unit_test_successful), + vec![ + // $base_fn_expr() + ecx.expr_call( + span, + base_fn_expr, + vec![], + ) + ], + ), + ) + } else { + base_fn_expr + } + }; let variant_name = if test.bench { "StaticBenchFn" } else { "StaticTestFn" }; + // self::test::$variant_name($fn_expr) let testfn_expr = ecx.expr_call(span, ecx.expr_path(test_path(variant_name)), vec![fn_expr]); diff --git a/src/test/run-pass/termination-trait-in-test.rs b/src/test/run-pass/termination-trait-in-test.rs new file mode 100644 index 00000000000..e67e0de5c31 --- /dev/null +++ b/src/test/run-pass/termination-trait-in-test.rs @@ -0,0 +1,28 @@ +// Copyright 2017 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. + +// compile-flags: --test + +#![feature(termination_trait)] + +use std::num::ParseIntError; + +#[test] +fn is_a_num() -> Result<(), ParseIntError> { + let _: u32 = "22".parse()?; + Ok(()) +} + +#[test] +#[should_panic] +fn not_a_num() -> Result<(), ParseIntError> { + let _: u32 = "abc".parse()?; + Ok(()) +} From 0625d4c282a9fa2a5ba3c6448017898baccfcf9a Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sun, 11 Feb 2018 09:28:47 -0500 Subject: [PATCH 02/11] begin crate-relative paths with `crate` --- src/libsyntax/ext/build.rs | 10 +++------ src/libsyntax/test.rs | 9 +++++--- .../rfc-2126-extern-absolute-paths/test.rs | 21 +++++++++++++++++++ 3 files changed, 30 insertions(+), 10 deletions(-) create mode 100644 src/test/run-pass/rfc-2126-extern-absolute-paths/test.rs diff --git a/src/libsyntax/ext/build.rs b/src/libsyntax/ext/build.rs index 2e6de96d65a..57978ca9c80 100644 --- a/src/libsyntax/ext/build.rs +++ b/src/libsyntax/ext/build.rs @@ -319,14 +319,8 @@ impl<'a> AstBuilder for ExtCtxt<'a> { types: Vec>, bindings: Vec ) -> ast::Path { - use syntax::parse::token; - let last_identifier = idents.pop().unwrap(); let mut segments: Vec = Vec::new(); - if global && - !idents.first().map_or(false, |&ident| token::Ident(ident).is_path_segment_keyword()) { - segments.push(ast::PathSegment::crate_root(span)); - } segments.extend(idents.into_iter().map(|i| ast::PathSegment::from_ident(i, span))); let parameters = if !lifetimes.is_empty() || !types.is_empty() || !bindings.is_empty() { @@ -335,7 +329,9 @@ impl<'a> AstBuilder for ExtCtxt<'a> { None }; segments.push(ast::PathSegment { identifier: last_identifier, span, parameters }); - ast::Path { span, segments } + let path = ast::Path { span, segments }; + + if global { path.default_to_global() } else { path } } /// Constructs a qualified path. diff --git a/src/libsyntax/test.rs b/src/libsyntax/test.rs index 094de6868a5..6cbb7ab393f 100644 --- a/src/libsyntax/test.rs +++ b/src/libsyntax/test.rs @@ -733,9 +733,12 @@ fn mk_test_desc_and_fn_rec(cx: &TestCtxt, test: &Test) -> P { field("should_panic", fail_expr), field("allow_fail", allow_fail_expr)]); - - let mut visible_path = match cx.toplevel_reexport { - Some(id) => vec![id], + let mut visible_path = vec![]; + if cx.features.extern_absolute_paths { + visible_path.push(keywords::Crate.ident()); + } + match cx.toplevel_reexport { + Some(id) => visible_path.push(id), None => { let diag = cx.span_diagnostic; diag.bug("expected to find top-level re-export name, but found None"); diff --git a/src/test/run-pass/rfc-2126-extern-absolute-paths/test.rs b/src/test/run-pass/rfc-2126-extern-absolute-paths/test.rs new file mode 100644 index 00000000000..796f652d6b5 --- /dev/null +++ b/src/test/run-pass/rfc-2126-extern-absolute-paths/test.rs @@ -0,0 +1,21 @@ +// Copyright 2017 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. + +// Check that `#[test]` works with extern-absolute-paths enabled. +// +// Regression test for #47075. + +// compile-flags: --test + +#![feature(extern_absolute_paths)] + +#[test] +fn test() { +} From e446f706a89e3d5c26c01318bd70904d492ab8b2 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Wed, 14 Feb 2018 10:06:12 -0500 Subject: [PATCH 03/11] put the "unit test" logic into libtest Also make `std::termination` module public and rename feature. The lib feature needs a different name from the language feature. --- src/libstd/lib.rs | 5 +- src/libstd/termination.rs | 23 ++++--- src/libsyntax/test.rs | 60 ++++++++----------- src/libtest/lib.rs | 9 +++ .../termination-trait-main-wrong-type.rs | 2 +- .../termination-trait-not-satisfied.rs | 2 +- 6 files changed, 47 insertions(+), 54 deletions(-) diff --git a/src/libstd/lib.rs b/src/libstd/lib.rs index 3c9004cdd19..b247d121648 100644 --- a/src/libstd/lib.rs +++ b/src/libstd/lib.rs @@ -501,11 +501,10 @@ mod memchr; // The runtime entry point and a few unstable public functions used by the // compiler pub mod rt; -// The trait to support returning arbitrary types in the main function -mod termination; +// The trait to support returning arbitrary types in the main function #[unstable(feature = "termination_trait", issue = "43301")] -pub use self::termination::Termination; +pub mod termination; // Include a number of private modules that exist solely to provide // the rustdoc documentation for primitive types. Using `include!` diff --git a/src/libstd/termination.rs b/src/libstd/termination.rs index f02fad009d8..203870766a9 100644 --- a/src/libstd/termination.rs +++ b/src/libstd/termination.rs @@ -8,7 +8,11 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +//! Defines the meaning of the return value from `main`, and hence +//! controls what happens in a Rust program after `main` returns. + use fmt::Debug; + #[cfg(target_arch = "wasm32")] mod exit { pub const SUCCESS: i32 = 0; @@ -30,28 +34,21 @@ mod exit { /// The default implementations are returning `libc::EXIT_SUCCESS` to indicate /// a successful execution. In case of a failure, `libc::EXIT_FAILURE` is returned. #[cfg_attr(not(test), lang = "termination")] -#[unstable(feature = "termination_trait", issue = "43301")] +#[unstable(feature = "termination_trait_lib", issue = "43301")] #[rustc_on_unimplemented = "`main` can only return types that implement {Termination}, not `{Self}`"] pub trait Termination { /// Is called to get the representation of the value as status code. /// This status code is returned to the operating system. fn report(self) -> i32; - - /// Invoked when unit tests terminate. Should panic if the unit - /// test is considered a failure. By default, invokes `report()` - /// and checks for a `0` result. - fn assert_unit_test_successful(self) where Self: Sized { - assert_eq!(self.report(), 0); - } } -#[unstable(feature = "termination_trait", issue = "43301")] +#[unstable(feature = "termination_trait_lib", issue = "43301")] impl Termination for () { fn report(self) -> i32 { exit::SUCCESS } } -#[unstable(feature = "termination_trait", issue = "43301")] +#[unstable(feature = "termination_trait_lib", issue = "43301")] impl Termination for Result { fn report(self) -> i32 { match self { @@ -64,19 +61,19 @@ impl Termination for Result { } } -#[unstable(feature = "termination_trait", issue = "43301")] +#[unstable(feature = "termination_trait_lib", issue = "43301")] impl Termination for ! { fn report(self) -> i32 { unreachable!(); } } -#[unstable(feature = "termination_trait", issue = "43301")] +#[unstable(feature = "termination_trait_lib", issue = "43301")] impl Termination for bool { fn report(self) -> i32 { if self { exit::SUCCESS } else { exit::FAILURE } } } -#[unstable(feature = "termination_trait", issue = "43301")] +#[unstable(feature = "termination_trait_lib", issue = "43301")] impl Termination for i32 { fn report(self) -> i32 { self diff --git a/src/libsyntax/test.rs b/src/libsyntax/test.rs index 6cbb7ab393f..b48713fcf7a 100644 --- a/src/libsyntax/test.rs +++ b/src/libsyntax/test.rs @@ -746,48 +746,36 @@ fn mk_test_desc_and_fn_rec(cx: &TestCtxt, test: &Test) -> P { }; visible_path.extend(path); - // If termination feature is enabled, create a wrapper that invokes the fn - // like this: + // Rather than directly give the test function to the test + // harness, we create a wrapper like this: // - // fn wrapper() { - // assert_eq!(0, real_function().report()); - // } + // || test::assert_test_result(real_function()) // - // and then put a reference to `wrapper` into the test descriptor. Otherwise, - // just put a direct reference to `real_function`. + // this will coerce into a fn pointer that is specialized to the + // actual return type of `real_function` (Typically `()`, but not always). let fn_expr = { - let base_fn_expr = ecx.expr_path(ecx.path_global(span, visible_path)); - if cx.features.termination_trait { - // ::std::Termination::assert_unit_test_successful - let assert_unit_test_successful = ecx.path_global( + // construct `real_function()` (this will be inserted into the overall expr) + let real_function_expr = ecx.expr_path(ecx.path_global(span, visible_path)); + // construct path `test::assert_test_result` + let assert_test_result = test_path("assert_test_result"); + // construct `|| {..}` + ecx.lambda( + span, + vec![], + // construct `assert_test_result(..)` + ecx.expr_call( span, + ecx.expr_path(assert_test_result), vec![ - ecx.ident_of("std"), - ecx.ident_of("Termination"), - ecx.ident_of("assert_unit_test_successful"), + // construct `real_function()` + ecx.expr_call( + span, + real_function_expr, + vec![], + ) ], - ); - // || {..} - ecx.lambda( - span, - vec![], - // ::std::Termination::assert_unit_test_successful(..) - ecx.expr_call( - span, - ecx.expr_path(assert_unit_test_successful), - vec![ - // $base_fn_expr() - ecx.expr_call( - span, - base_fn_expr, - vec![], - ) - ], - ), - ) - } else { - base_fn_expr - } + ), + ) }; let variant_name = if test.bench { "StaticBenchFn" } else { "StaticTestFn" }; diff --git a/src/libtest/lib.rs b/src/libtest/lib.rs index 9ea5f39b71f..932952d649b 100644 --- a/src/libtest/lib.rs +++ b/src/libtest/lib.rs @@ -40,6 +40,7 @@ #![feature(set_stdio)] #![feature(panic_unwind)] #![feature(staged_api)] +#![feature(termination_trait_lib)] extern crate getopts; extern crate term; @@ -69,6 +70,7 @@ use std::iter::repeat; use std::path::PathBuf; use std::sync::mpsc::{channel, Sender}; use std::sync::{Arc, Mutex}; +use std::termination::Termination; use std::thread; use std::time::{Instant, Duration}; use std::borrow::Cow; @@ -322,6 +324,13 @@ pub fn test_main_static(tests: &[TestDescAndFn]) { test_main(&args, owned_tests, Options::new()) } +/// Invoked when unit tests terminate. Should panic if the unit +/// test is considered a failure. By default, invokes `report()` +/// and checks for a `0` result. +pub fn assert_test_result(result: T) { + assert_eq!(result.report(), 0); +} + #[derive(Copy, Clone, Debug)] pub enum ColorConfig { AutoColor, diff --git a/src/test/compile-fail/rfc-1937-termination-trait/termination-trait-main-wrong-type.rs b/src/test/compile-fail/rfc-1937-termination-trait/termination-trait-main-wrong-type.rs index a63162cf73d..2da51851952 100644 --- a/src/test/compile-fail/rfc-1937-termination-trait/termination-trait-main-wrong-type.rs +++ b/src/test/compile-fail/rfc-1937-termination-trait/termination-trait-main-wrong-type.rs @@ -10,6 +10,6 @@ #![feature(termination_trait)] fn main() -> char { -//~^ ERROR: the trait bound `char: std::Termination` is not satisfied +//~^ ERROR: the trait bound `char: std::termination::Termination` is not satisfied ' ' } diff --git a/src/test/compile-fail/rfc-1937-termination-trait/termination-trait-not-satisfied.rs b/src/test/compile-fail/rfc-1937-termination-trait/termination-trait-not-satisfied.rs index 788c38c55be..fac60d6d399 100644 --- a/src/test/compile-fail/rfc-1937-termination-trait/termination-trait-not-satisfied.rs +++ b/src/test/compile-fail/rfc-1937-termination-trait/termination-trait-not-satisfied.rs @@ -12,6 +12,6 @@ struct ReturnType {} -fn main() -> ReturnType { //~ ERROR `ReturnType: std::Termination` is not satisfied +fn main() -> ReturnType { //~ ERROR `ReturnType: std::termination::Termination` is not satisfied ReturnType {} } From 5f1e78f19ad40c6265a200b41c772c321b8b08cd Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Thu, 22 Feb 2018 17:36:55 -0500 Subject: [PATCH 04/11] move Termination trait to std::process --- src/libstd/lib.rs | 4 - src/libstd/process.rs | 67 +++++++++++++++ src/libstd/rt.rs | 2 +- src/libstd/termination.rs | 81 ------------------- src/libtest/lib.rs | 2 +- .../termination-trait-main-wrong-type.rs | 2 +- .../termination-trait-not-satisfied.rs | 2 +- 7 files changed, 71 insertions(+), 89 deletions(-) delete mode 100644 src/libstd/termination.rs diff --git a/src/libstd/lib.rs b/src/libstd/lib.rs index b247d121648..bdda7416336 100644 --- a/src/libstd/lib.rs +++ b/src/libstd/lib.rs @@ -502,10 +502,6 @@ mod memchr; // compiler pub mod rt; -// The trait to support returning arbitrary types in the main function -#[unstable(feature = "termination_trait", issue = "43301")] -pub mod termination; - // Include a number of private modules that exist solely to provide // the rustdoc documentation for primitive types. Using `include!` // because rustdoc only looks for these modules at the crate level. diff --git a/src/libstd/process.rs b/src/libstd/process.rs index 9b2f815b713..e25599b8bd8 100644 --- a/src/libstd/process.rs +++ b/src/libstd/process.rs @@ -1392,6 +1392,73 @@ pub fn id() -> u32 { ::sys::os::getpid() } +#[cfg(target_arch = "wasm32")] +mod exit { + pub const SUCCESS: i32 = 0; + pub const FAILURE: i32 = 1; +} +#[cfg(not(target_arch = "wasm32"))] +mod exit { + use libc; + pub const SUCCESS: i32 = libc::EXIT_SUCCESS; + pub const FAILURE: i32 = libc::EXIT_FAILURE; +} + +/// A trait for implementing arbitrary return types in the `main` function. +/// +/// The c-main function only supports to return integers as return type. +/// So, every type implementing the `Termination` trait has to be converted +/// to an integer. +/// +/// The default implementations are returning `libc::EXIT_SUCCESS` to indicate +/// a successful execution. In case of a failure, `libc::EXIT_FAILURE` is returned. +#[cfg_attr(not(test), lang = "termination")] +#[unstable(feature = "termination_trait_lib", issue = "43301")] +#[rustc_on_unimplemented = + "`main` can only return types that implement {Termination}, not `{Self}`"] +pub trait Termination { + /// Is called to get the representation of the value as status code. + /// This status code is returned to the operating system. + fn report(self) -> i32; +} + +#[unstable(feature = "termination_trait_lib", issue = "43301")] +impl Termination for () { + fn report(self) -> i32 { exit::SUCCESS } +} + +#[unstable(feature = "termination_trait_lib", issue = "43301")] +impl Termination for Result { + fn report(self) -> i32 { + match self { + Ok(val) => val.report(), + Err(err) => { + eprintln!("Error: {:?}", err); + exit::FAILURE + } + } + } +} + +#[unstable(feature = "termination_trait_lib", issue = "43301")] +impl Termination for ! { + fn report(self) -> i32 { unreachable!(); } +} + +#[unstable(feature = "termination_trait_lib", issue = "43301")] +impl Termination for bool { + fn report(self) -> i32 { + if self { exit::SUCCESS } else { exit::FAILURE } + } +} + +#[unstable(feature = "termination_trait_lib", issue = "43301")] +impl Termination for i32 { + fn report(self) -> i32 { + self + } +} + #[cfg(all(test, not(any(target_os = "cloudabi", target_os = "emscripten"))))] mod tests { use io::prelude::*; diff --git a/src/libstd/rt.rs b/src/libstd/rt.rs index 9dbaf784f89..e1392762a59 100644 --- a/src/libstd/rt.rs +++ b/src/libstd/rt.rs @@ -68,7 +68,7 @@ fn lang_start_internal(main: &(Fn() -> i32 + Sync + ::panic::RefUnwindSafe), #[cfg(not(test))] #[lang = "start"] -fn lang_start +fn lang_start (main: fn() -> T, argc: isize, argv: *const *const u8) -> isize { lang_start_internal(&move || main().report(), argc, argv) diff --git a/src/libstd/termination.rs b/src/libstd/termination.rs deleted file mode 100644 index 203870766a9..00000000000 --- a/src/libstd/termination.rs +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2017 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. - -//! Defines the meaning of the return value from `main`, and hence -//! controls what happens in a Rust program after `main` returns. - -use fmt::Debug; - -#[cfg(target_arch = "wasm32")] -mod exit { - pub const SUCCESS: i32 = 0; - pub const FAILURE: i32 = 1; -} -#[cfg(not(target_arch = "wasm32"))] -mod exit { - use libc; - pub const SUCCESS: i32 = libc::EXIT_SUCCESS; - pub const FAILURE: i32 = libc::EXIT_FAILURE; -} - -/// A trait for implementing arbitrary return types in the `main` function. -/// -/// The c-main function only supports to return integers as return type. -/// So, every type implementing the `Termination` trait has to be converted -/// to an integer. -/// -/// The default implementations are returning `libc::EXIT_SUCCESS` to indicate -/// a successful execution. In case of a failure, `libc::EXIT_FAILURE` is returned. -#[cfg_attr(not(test), lang = "termination")] -#[unstable(feature = "termination_trait_lib", issue = "43301")] -#[rustc_on_unimplemented = - "`main` can only return types that implement {Termination}, not `{Self}`"] -pub trait Termination { - /// Is called to get the representation of the value as status code. - /// This status code is returned to the operating system. - fn report(self) -> i32; -} - -#[unstable(feature = "termination_trait_lib", issue = "43301")] -impl Termination for () { - fn report(self) -> i32 { exit::SUCCESS } -} - -#[unstable(feature = "termination_trait_lib", issue = "43301")] -impl Termination for Result { - fn report(self) -> i32 { - match self { - Ok(val) => val.report(), - Err(err) => { - eprintln!("Error: {:?}", err); - exit::FAILURE - } - } - } -} - -#[unstable(feature = "termination_trait_lib", issue = "43301")] -impl Termination for ! { - fn report(self) -> i32 { unreachable!(); } -} - -#[unstable(feature = "termination_trait_lib", issue = "43301")] -impl Termination for bool { - fn report(self) -> i32 { - if self { exit::SUCCESS } else { exit::FAILURE } - } -} - -#[unstable(feature = "termination_trait_lib", issue = "43301")] -impl Termination for i32 { - fn report(self) -> i32 { - self - } -} diff --git a/src/libtest/lib.rs b/src/libtest/lib.rs index 932952d649b..06a23cd8818 100644 --- a/src/libtest/lib.rs +++ b/src/libtest/lib.rs @@ -68,9 +68,9 @@ use std::io::prelude::*; use std::io; use std::iter::repeat; use std::path::PathBuf; +use std::process::Termination; use std::sync::mpsc::{channel, Sender}; use std::sync::{Arc, Mutex}; -use std::termination::Termination; use std::thread; use std::time::{Instant, Duration}; use std::borrow::Cow; diff --git a/src/test/compile-fail/rfc-1937-termination-trait/termination-trait-main-wrong-type.rs b/src/test/compile-fail/rfc-1937-termination-trait/termination-trait-main-wrong-type.rs index 2da51851952..93e2561adf7 100644 --- a/src/test/compile-fail/rfc-1937-termination-trait/termination-trait-main-wrong-type.rs +++ b/src/test/compile-fail/rfc-1937-termination-trait/termination-trait-main-wrong-type.rs @@ -10,6 +10,6 @@ #![feature(termination_trait)] fn main() -> char { -//~^ ERROR: the trait bound `char: std::termination::Termination` is not satisfied +//~^ ERROR: the trait bound `char: std::process::Termination` is not satisfied ' ' } diff --git a/src/test/compile-fail/rfc-1937-termination-trait/termination-trait-not-satisfied.rs b/src/test/compile-fail/rfc-1937-termination-trait/termination-trait-not-satisfied.rs index fac60d6d399..e87e0ceebf1 100644 --- a/src/test/compile-fail/rfc-1937-termination-trait/termination-trait-not-satisfied.rs +++ b/src/test/compile-fail/rfc-1937-termination-trait/termination-trait-not-satisfied.rs @@ -12,6 +12,6 @@ struct ReturnType {} -fn main() -> ReturnType { //~ ERROR `ReturnType: std::termination::Termination` is not satisfied +fn main() -> ReturnType { //~ ERROR `ReturnType: std::process::Termination` is not satisfied ReturnType {} } From 8f35141fbad39b94b071186e2d8259694bd4960d Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Thu, 22 Feb 2018 18:26:01 -0500 Subject: [PATCH 05/11] remove tokenstream --- src/libsyntax/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libsyntax/lib.rs b/src/libsyntax/lib.rs index 53ff3ccd48a..9181cca215c 100644 --- a/src/libsyntax/lib.rs +++ b/src/libsyntax/lib.rs @@ -105,7 +105,6 @@ pub mod syntax { pub use ext; pub use parse; pub use ast; - pub use tokenstream; } pub mod abi; From 067c2e3d0393311ac3dc9d8abb8750fd2fd5dd11 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Thu, 22 Feb 2018 20:09:10 -0500 Subject: [PATCH 06/11] handle `#[bench]` functions better --- src/libsyntax/test.rs | 62 ++++++++++++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 19 deletions(-) diff --git a/src/libsyntax/test.rs b/src/libsyntax/test.rs index b48713fcf7a..1b48901d4eb 100644 --- a/src/libsyntax/test.rs +++ b/src/libsyntax/test.rs @@ -747,9 +747,10 @@ fn mk_test_desc_and_fn_rec(cx: &TestCtxt, test: &Test) -> P { visible_path.extend(path); // Rather than directly give the test function to the test - // harness, we create a wrapper like this: + // harness, we create a wrapper like one of the following: // - // || test::assert_test_result(real_function()) + // || test::assert_test_result(real_function()) // for test + // |b| test::assert_test_result(real_function(b)) // for bench // // this will coerce into a fn pointer that is specialized to the // actual return type of `real_function` (Typically `()`, but not always). @@ -758,24 +759,47 @@ fn mk_test_desc_and_fn_rec(cx: &TestCtxt, test: &Test) -> P { let real_function_expr = ecx.expr_path(ecx.path_global(span, visible_path)); // construct path `test::assert_test_result` let assert_test_result = test_path("assert_test_result"); - // construct `|| {..}` - ecx.lambda( - span, - vec![], - // construct `assert_test_result(..)` - ecx.expr_call( + if test.bench { + // construct `|b| {..}` + let b_ident = Ident::with_empty_ctxt(Symbol::gensym("b")); + let b_expr = ecx.expr_ident(span, b_ident); + ecx.lambda( span, - ecx.expr_path(assert_test_result), - vec![ - // construct `real_function()` - ecx.expr_call( - span, - real_function_expr, - vec![], - ) - ], - ), - ) + vec![b_ident], + // construct `assert_test_result(..)` + ecx.expr_call( + span, + ecx.expr_path(assert_test_result), + vec![ + // construct `real_function(b)` + ecx.expr_call( + span, + real_function_expr, + vec![b_expr], + ) + ], + ), + ) + } else { + // construct `|| {..}` + ecx.lambda( + span, + vec![], + // construct `assert_test_result(..)` + ecx.expr_call( + span, + ecx.expr_path(assert_test_result), + vec![ + // construct `real_function()` + ecx.expr_call( + span, + real_function_expr, + vec![], + ) + ], + ), + ) + } }; let variant_name = if test.bench { "StaticBenchFn" } else { "StaticTestFn" }; From e0ed88df3d33637ef8a41d3655cd20587f566048 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Thu, 22 Feb 2018 20:10:36 -0500 Subject: [PATCH 07/11] add test for `fn main() -> !` --- .../termination-trait-for-never.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/test/run-fail/rfc-1937-termination-trait/termination-trait-for-never.rs diff --git a/src/test/run-fail/rfc-1937-termination-trait/termination-trait-for-never.rs b/src/test/run-fail/rfc-1937-termination-trait/termination-trait-for-never.rs new file mode 100644 index 00000000000..c1dd44a9176 --- /dev/null +++ b/src/test/run-fail/rfc-1937-termination-trait/termination-trait-for-never.rs @@ -0,0 +1,17 @@ +// Copyright 2017 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. + +#![feature(termination_trait)] + +// error-pattern:oh, dear + +fn main() -> ! { + panic!("oh, dear"); +} From a0562ec369629abc0b365c95b77e67aec232bd85 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Thu, 22 Feb 2018 20:16:06 -0500 Subject: [PATCH 08/11] delete this test file: it also appears as src/rfc-1937-termination-trait/termination-trait-for-result-box-error_ok.rs --- ...termination-trait-for-result-box-error_ok.rs | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 src/test/run-pass/termination-trait-for-result-box-error_ok.rs diff --git a/src/test/run-pass/termination-trait-for-result-box-error_ok.rs b/src/test/run-pass/termination-trait-for-result-box-error_ok.rs deleted file mode 100644 index 269ac451cf4..00000000000 --- a/src/test/run-pass/termination-trait-for-result-box-error_ok.rs +++ /dev/null @@ -1,17 +0,0 @@ -// 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. - -#![feature(termination_trait)] - -use std::io::Error; - -fn main() -> Result<(), Box> { - Ok(()) -} From 0a5f4aebb170a7ac758176d5eaf5e88ed71accbc Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Thu, 22 Feb 2018 20:16:30 -0500 Subject: [PATCH 09/11] move test to the proper directory and test #[bench] --- .../termination-trait-in-test.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) rename src/test/run-pass/{ => rfc-1937-termination-trait}/termination-trait-in-test.rs (72%) diff --git a/src/test/run-pass/termination-trait-in-test.rs b/src/test/run-pass/rfc-1937-termination-trait/termination-trait-in-test.rs similarity index 72% rename from src/test/run-pass/termination-trait-in-test.rs rename to src/test/run-pass/rfc-1937-termination-trait/termination-trait-in-test.rs index e67e0de5c31..494500d522a 100644 --- a/src/test/run-pass/termination-trait-in-test.rs +++ b/src/test/run-pass/rfc-1937-termination-trait/termination-trait-in-test.rs @@ -11,8 +11,11 @@ // compile-flags: --test #![feature(termination_trait)] +#![feature(test)] +extern crate test; use std::num::ParseIntError; +use test::Bencher; #[test] fn is_a_num() -> Result<(), ParseIntError> { @@ -26,3 +29,15 @@ fn not_a_num() -> Result<(), ParseIntError> { let _: u32 = "abc".parse()?; Ok(()) } + +#[bench] +fn test_a_positive_bench(_: &mut Bencher) -> Result<(), ParseIntError> { + Ok(()) +} + +#[bench] +#[should_panic] +fn test_a_neg_bench(_: &mut Bencher) -> Result<(), ParseIntError> { + let _: u32 = "abc".parse()?; + Ok(()) +} From 068e3832cd9c3c9ac43128e1056a8d8b05fe1406 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Fri, 23 Feb 2018 07:34:00 -0500 Subject: [PATCH 10/11] update test -- we now give a slightly different error --- src/test/compile-fail/issue-12997-2.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/test/compile-fail/issue-12997-2.rs b/src/test/compile-fail/issue-12997-2.rs index 85d91bb2db2..8d3df68577b 100644 --- a/src/test/compile-fail/issue-12997-2.rs +++ b/src/test/compile-fail/issue-12997-2.rs @@ -15,6 +15,3 @@ #[bench] fn bar(x: isize) { } //~^ ERROR mismatched types -//~| expected type `for<'r> fn(&'r mut __test::test::Bencher)` -//~| found type `fn(isize) {bar}` -//~| expected mutable reference, found isize From 10f7c110928ee7d3db7fef15fd7dce776b17e161 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Fri, 23 Feb 2018 10:11:06 -0500 Subject: [PATCH 11/11] re-export `assert_test_result` for use when testing libtest itself --- src/libtest/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libtest/lib.rs b/src/libtest/lib.rs index 06a23cd8818..82077bc4cd4 100644 --- a/src/libtest/lib.rs +++ b/src/libtest/lib.rs @@ -83,8 +83,8 @@ const QUIET_MODE_MAX_COLUMN: usize = 100; // insert a '\n' after 100 tests in qu pub mod test { pub use {Bencher, TestName, TestResult, TestDesc, TestDescAndFn, TestOpts, TrFailed, TrFailedMsg, TrIgnored, TrOk, Metric, MetricMap, StaticTestFn, StaticTestName, - DynTestName, DynTestFn, run_test, test_main, test_main_static, filter_tests, - parse_opts, StaticBenchFn, ShouldPanic, Options}; + DynTestName, DynTestFn, assert_test_result, run_test, test_main, test_main_static, + filter_tests, parse_opts, StaticBenchFn, ShouldPanic, Options}; } pub mod stats;