diff --git a/compiler/rustc_ast/src/token.rs b/compiler/rustc_ast/src/token.rs index 6dc6d1026f621..e1231312a2afd 100644 --- a/compiler/rustc_ast/src/token.rs +++ b/compiler/rustc_ast/src/token.rs @@ -881,11 +881,11 @@ impl Token { } pub fn is_qpath_start(&self) -> bool { - self == &Lt || self == &Shl + matches!(self.kind, Lt | Shl) } pub fn is_path_start(&self) -> bool { - self == &PathSep + self.kind == PathSep || self.is_qpath_start() || matches!(self.is_metavar_seq(), Some(MetaVarKind::Path)) || self.is_path_segment_keyword() diff --git a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs index 7978bf28214cf..f09b02251e4d0 100644 --- a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs @@ -6,6 +6,7 @@ use crate::session_diagnostics::{ NakedFunctionIncompatibleAttribute, NullOnExport, NullOnObjcClass, NullOnObjcSelector, ObjcClassExpectedStringLiteral, ObjcSelectorExpectedStringLiteral, }; +use crate::target_checking::Policy::AllowSilent; pub(crate) struct OptimizeParser; @@ -362,6 +363,8 @@ impl NoArgsAttributeParser for NoMangleParser { Allow(Target::Static), Allow(Target::Method(MethodKind::Inherent)), Allow(Target::Method(MethodKind::TraitImpl)), + AllowSilent(Target::Const), // Handled in the `InvalidNoMangleItems` pass + Error(Target::Closure), ]); const CREATE: fn(Span) -> AttributeKind = AttributeKind::NoMangle; } diff --git a/compiler/rustc_attr_parsing/src/target_checking.rs b/compiler/rustc_attr_parsing/src/target_checking.rs index d28f43943ba84..fabd364d3d7f8 100644 --- a/compiler/rustc_attr_parsing/src/target_checking.rs +++ b/compiler/rustc_attr_parsing/src/target_checking.rs @@ -31,7 +31,9 @@ impl AllowedTargets { pub(crate) fn is_allowed(&self, target: Target) -> AllowedResult { match self { AllowedTargets::AllowList(list) => { - if list.contains(&Policy::Allow(target)) { + if list.contains(&Policy::Allow(target)) + || list.contains(&Policy::AllowSilent(target)) + { AllowedResult::Allowed } else if list.contains(&Policy::Warn(target)) { AllowedResult::Warn @@ -40,7 +42,9 @@ impl AllowedTargets { } } AllowedTargets::AllowListWarnRest(list) => { - if list.contains(&Policy::Allow(target)) { + if list.contains(&Policy::Allow(target)) + || list.contains(&Policy::AllowSilent(target)) + { AllowedResult::Allowed } else if list.contains(&Policy::Error(target)) { AllowedResult::Error @@ -61,6 +65,7 @@ impl AllowedTargets { .iter() .filter_map(|target| match target { Policy::Allow(target) => Some(*target), + Policy::AllowSilent(_) => None, // Not listed in possible targets Policy::Warn(_) => None, Policy::Error(_) => None, }) @@ -68,10 +73,18 @@ impl AllowedTargets { } } +/// This policy determines what diagnostics should be emitted based on the `Target` of the attribute. #[derive(Debug, Eq, PartialEq)] pub(crate) enum Policy { + /// A target that is allowed. Allow(Target), + /// A target that is allowed and not listed in the possible targets. + /// This is useful if the target is checked elsewhere. + AllowSilent(Target), + /// Emits a FCW on this target. + /// This is useful if the target was previously allowed but should not be. Warn(Target), + /// Emits an error on this target. Error(Target), } diff --git a/compiler/rustc_builtin_macros/src/format.rs b/compiler/rustc_builtin_macros/src/format.rs index bffc0407e8112..a0ee7ac19899b 100644 --- a/compiler/rustc_builtin_macros/src/format.rs +++ b/compiler/rustc_builtin_macros/src/format.rs @@ -69,35 +69,26 @@ struct MacroInput { /// Ok((fmtstr, parsed arguments)) /// ``` fn parse_args<'a>(ecx: &ExtCtxt<'a>, sp: Span, tts: TokenStream) -> PResult<'a, MacroInput> { - let mut args = FormatArguments::new(); - let mut p = ecx.new_parser_from_tts(tts); - if p.token == token::Eof { - return Err(ecx.dcx().create_err(errors::FormatRequiresString { span: sp })); - } - - let first_token = &p.token; - - let fmtstr = if let token::Literal(lit) = first_token.kind - && matches!(lit.kind, token::Str | token::StrRaw(_)) - { + // parse the format string + let fmtstr = match p.token.kind { + token::Eof => return Err(ecx.dcx().create_err(errors::FormatRequiresString { span: sp })), // This allows us to properly handle cases when the first comma // after the format string is mistakenly replaced with any operator, // which cause the expression parser to eat too much tokens. - p.parse_literal_maybe_minus()? - } else { + token::Literal(token::Lit { kind: token::Str | token::StrRaw(_), .. }) => { + p.parse_literal_maybe_minus()? + } // Otherwise, we fall back to the expression parser. - p.parse_expr()? + _ => p.parse_expr()?, }; - // Only allow implicit captures to be used when the argument is a direct literal - // instead of a macro expanding to one. - let is_direct_literal = matches!(fmtstr.kind, ExprKind::Lit(_)); - + // parse comma FormatArgument pairs + let mut args = FormatArguments::new(); let mut first = true; - while p.token != token::Eof { + // parse a comma, or else report an error if !p.eat(exp!(Comma)) { if first { p.clear_expected_token_types(); @@ -120,9 +111,11 @@ fn parse_args<'a>(ecx: &ExtCtxt<'a>, sp: Span, tts: TokenStream) -> PResult<'a, } } first = false; + // accept a trailing comma if p.token == token::Eof { break; - } // accept trailing commas + } + // parse a FormatArgument match p.token.ident() { Some((ident, _)) if p.look_ahead(1, |t| *t == token::Eq) => { p.bump(); @@ -156,6 +149,10 @@ fn parse_args<'a>(ecx: &ExtCtxt<'a>, sp: Span, tts: TokenStream) -> PResult<'a, } } } + + // Only allow implicit captures for direct literals + let is_direct_literal = matches!(fmtstr.kind, ExprKind::Lit(_)); + Ok(MacroInput { fmtstr, args, is_direct_literal }) } diff --git a/compiler/rustc_codegen_ssa/messages.ftl b/compiler/rustc_codegen_ssa/messages.ftl index 97ff04f66daec..e321b0773ec39 100644 --- a/compiler/rustc_codegen_ssa/messages.ftl +++ b/compiler/rustc_codegen_ssa/messages.ftl @@ -223,8 +223,6 @@ codegen_ssa_multiple_main_functions = entry symbol `main` declared multiple time codegen_ssa_no_field = no field `{$name}` -codegen_ssa_no_mangle_nameless = `#[no_mangle]` cannot be used on {$definition} as it has no name - codegen_ssa_no_module_named = no module named `{$user_path}` (mangled: {$cgu_name}). available modules: {$cgu_names} diff --git a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs index 2c7643e46ceaa..e8c8729f597b9 100644 --- a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs +++ b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs @@ -19,7 +19,6 @@ use rustc_span::{Ident, Span, sym}; use rustc_target::spec::SanitizerSet; use crate::errors; -use crate::errors::NoMangleNameless; use crate::target_features::{ check_target_feature_trait_unsafe, check_tied_features, from_target_feature_attr, }; @@ -182,14 +181,10 @@ fn process_builtin_attrs( if tcx.opt_item_name(did.to_def_id()).is_some() { codegen_fn_attrs.flags |= CodegenFnAttrFlags::NO_MANGLE; } else { - tcx.dcx().emit_err(NoMangleNameless { - span: *attr_span, - definition: format!( - "{} {}", - tcx.def_descr_article(did.to_def_id()), - tcx.def_descr(did.to_def_id()) - ), - }); + tcx.dcx().span_delayed_bug( + *attr_span, + "no_mangle should be on a named function", + ); } } AttributeKind::Optimize(optimize, _) => codegen_fn_attrs.optimize = *optimize, diff --git a/compiler/rustc_codegen_ssa/src/errors.rs b/compiler/rustc_codegen_ssa/src/errors.rs index d5c30c5c7a6b0..2dd7c6fa7c080 100644 --- a/compiler/rustc_codegen_ssa/src/errors.rs +++ b/compiler/rustc_codegen_ssa/src/errors.rs @@ -1284,14 +1284,6 @@ impl Diagnostic<'_, G> for TargetFeatureDisableOrEnable<'_ } } -#[derive(Diagnostic)] -#[diag(codegen_ssa_no_mangle_nameless)] -pub(crate) struct NoMangleNameless { - #[primary_span] - pub span: Span, - pub definition: String, -} - #[derive(Diagnostic)] #[diag(codegen_ssa_feature_not_valid)] pub(crate) struct FeatureNotValid<'a> { diff --git a/compiler/rustc_mir_transform/src/ctfe_limit.rs b/compiler/rustc_mir_transform/src/ctfe_limit.rs index ac46336b83473..e2f518bb4ee81 100644 --- a/compiler/rustc_mir_transform/src/ctfe_limit.rs +++ b/compiler/rustc_mir_transform/src/ctfe_limit.rs @@ -28,12 +28,12 @@ impl<'tcx> crate::MirPass<'tcx> for CtfeLimit { } }) .collect(); + + let basic_blocks = body.basic_blocks.as_mut_preserves_cfg(); for index in indices { - insert_counter( - body.basic_blocks_mut() - .get_mut(index) - .expect("basic_blocks index {index} should exist"), - ); + let bbdata = &mut basic_blocks[index]; + let source_info = bbdata.terminator().source_info; + bbdata.statements.push(Statement::new(source_info, StatementKind::ConstEvalCounter)); } } @@ -53,10 +53,3 @@ fn has_back_edge( // Check if any of the dominators of the node are also the node's successor. node_data.terminator().successors().any(|succ| doms.dominates(succ, node)) } - -fn insert_counter(basic_block_data: &mut BasicBlockData<'_>) { - basic_block_data.statements.push(Statement::new( - basic_block_data.terminator().source_info, - StatementKind::ConstEvalCounter, - )); -} diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 1318f28ff89f3..71b0f408b5d88 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -981,10 +981,12 @@ symbols! { external_doc, f, f16, + f16_consts_mod, f16_epsilon, f16_nan, f16c_target_feature, f32, + f32_consts_mod, f32_epsilon, f32_legacy_const_digits, f32_legacy_const_epsilon, @@ -1002,6 +1004,7 @@ symbols! { f32_legacy_const_radix, f32_nan, f64, + f64_consts_mod, f64_epsilon, f64_legacy_const_digits, f64_legacy_const_epsilon, @@ -1019,6 +1022,7 @@ symbols! { f64_legacy_const_radix, f64_nan, f128, + f128_consts_mod, f128_epsilon, f128_nan, fabsf16, diff --git a/library/core/src/num/f128.rs b/library/core/src/num/f128.rs index 4fe4735e304c9..e7101537b298f 100644 --- a/library/core/src/num/f128.rs +++ b/library/core/src/num/f128.rs @@ -18,6 +18,7 @@ use crate::{intrinsics, mem}; /// Basic mathematical constants. #[unstable(feature = "f128", issue = "116909")] +#[rustc_diagnostic_item = "f128_consts_mod"] pub mod consts { // FIXME: replace with mathematical constants from cmath. diff --git a/library/core/src/num/f16.rs b/library/core/src/num/f16.rs index 0bea6bc8801d8..aa8342a22ad58 100644 --- a/library/core/src/num/f16.rs +++ b/library/core/src/num/f16.rs @@ -20,6 +20,7 @@ use crate::{intrinsics, mem}; /// Basic mathematical constants. #[unstable(feature = "f16", issue = "116909")] +#[rustc_diagnostic_item = "f16_consts_mod"] pub mod consts { // FIXME: replace with mathematical constants from cmath. diff --git a/library/core/src/num/f32.rs b/library/core/src/num/f32.rs index e380cc698f574..3070e1dedbe43 100644 --- a/library/core/src/num/f32.rs +++ b/library/core/src/num/f32.rs @@ -277,6 +277,7 @@ pub const NEG_INFINITY: f32 = f32::NEG_INFINITY; /// Basic mathematical constants. #[stable(feature = "rust1", since = "1.0.0")] +#[rustc_diagnostic_item = "f32_consts_mod"] pub mod consts { // FIXME: replace with mathematical constants from cmath. diff --git a/library/core/src/num/f64.rs b/library/core/src/num/f64.rs index ff7449fd996ce..dc8ccc551b2da 100644 --- a/library/core/src/num/f64.rs +++ b/library/core/src/num/f64.rs @@ -277,6 +277,7 @@ pub const NEG_INFINITY: f64 = f64::NEG_INFINITY; /// Basic mathematical constants. #[stable(feature = "rust1", since = "1.0.0")] +#[rustc_diagnostic_item = "f64_consts_mod"] pub mod consts { // FIXME: replace with mathematical constants from cmath. diff --git a/library/std/src/error.rs b/library/std/src/error.rs index def5f984c88e4..09bfc83ebca6c 100644 --- a/library/std/src/error.rs +++ b/library/std/src/error.rs @@ -123,7 +123,7 @@ use crate::fmt::{self, Write}; /// the `Debug` output means `Report` is an ideal starting place for formatting errors returned /// from `main`. /// -/// ```should_panic +/// ``` /// #![feature(error_reporter)] /// use std::error::Report; /// # use std::error::Error; @@ -154,10 +154,14 @@ use crate::fmt::{self, Write}; /// # Err(SuperError { source: SuperErrorSideKick }) /// # } /// -/// fn main() -> Result<(), Report> { +/// fn run() -> Result<(), Report> { /// get_super_error()?; /// Ok(()) /// } +/// +/// fn main() { +/// assert!(run().is_err()); +/// } /// ``` /// /// This example produces the following output: @@ -170,7 +174,7 @@ use crate::fmt::{self, Write}; /// output format. If you want to make sure your `Report`s are pretty printed and include backtrace /// you will need to manually convert and enable those flags. /// -/// ```should_panic +/// ``` /// #![feature(error_reporter)] /// use std::error::Report; /// # use std::error::Error; @@ -201,12 +205,16 @@ use crate::fmt::{self, Write}; /// # Err(SuperError { source: SuperErrorSideKick }) /// # } /// -/// fn main() -> Result<(), Report> { +/// fn run() -> Result<(), Report> { /// get_super_error() /// .map_err(Report::from) /// .map_err(|r| r.pretty(true).show_backtrace(true))?; /// Ok(()) /// } +/// +/// fn main() { +/// assert!(run().is_err()); +/// } /// ``` /// /// This example produces the following output: diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs index 2ab4052fedff9..2fdfba1d69c85 100644 --- a/src/librustdoc/doctest.rs +++ b/src/librustdoc/doctest.rs @@ -7,6 +7,8 @@ mod rust; use std::fs::File; use std::hash::{Hash, Hasher}; use std::io::{self, Write}; +#[cfg(unix)] +use std::os::unix::process::ExitStatusExt; use std::path::{Path, PathBuf}; use std::process::{self, Command, Stdio}; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -351,7 +353,7 @@ pub(crate) fn run_tests( ); for (doctest, scraped_test) in &doctests { - tests_runner.add_test(doctest, scraped_test, &target_str); + tests_runner.add_test(doctest, scraped_test, &target_str, rustdoc_options); } let (duration, ret) = tests_runner.run_merged_tests( rustdoc_test_options, @@ -461,8 +463,8 @@ enum TestFailure { /// /// This typically means an assertion in the test failed or another form of panic occurred. ExecutionFailure(process::Output), - /// The test is marked `should_panic` but the test binary executed successfully. - UnexpectedRunPass, + /// The test is marked `should_panic` but the test binary didn't panic. + NoPanic(Option), } enum DirState { @@ -801,6 +803,25 @@ fn run_test( let duration = instant.elapsed(); if doctest.no_run { return (duration, Ok(())); + } else if doctest.langstr.should_panic + // Equivalent of: + // + // ``` + // (cfg!(target_family = "wasm") || cfg!(target_os = "zkvm")) + // && !cfg!(target_os = "emscripten") + // ``` + // + // FIXME: All this code is terrible and doesn't take into account `TargetTuple::TargetJson`. + // If `libtest` doesn't allow to handle this case, we'll need to use a rustc's API instead. + && let TargetTuple::TargetTuple(ref s) = rustdoc_options.target + && let mut iter = s.split('-') + && let Some(arch) = iter.next() + && iter.next().is_some() + && let os = iter.next() + && (arch.starts_with("wasm") || os == Some("zkvm")) && os != Some("emscripten") + { + // We cannot correctly handle `should_panic` in some wasm targets so we exit early. + return (duration, Ok(())); } // Run the code! @@ -831,12 +852,68 @@ fn run_test( } else { cmd.output() }; + + // FIXME: Make `test::get_result_from_exit_code` public and use this code instead of this. + // + // On Zircon (the Fuchsia kernel), an abort from userspace calls the + // LLVM implementation of __builtin_trap(), e.g., ud2 on x86, which + // raises a kernel exception. If a userspace process does not + // otherwise arrange exception handling, the kernel kills the process + // with this return code. + #[cfg(target_os = "fuchsia")] + const ZX_TASK_RETCODE_EXCEPTION_KILL: i32 = -1028; + // On Windows we use __fastfail to abort, which is documented to use this + // exception code. + #[cfg(windows)] + const STATUS_FAIL_FAST_EXCEPTION: i32 = 0xC0000409u32 as i32; + #[cfg(unix)] + const SIGABRT: std::ffi::c_int = 6; match result { Err(e) => return (duration, Err(TestFailure::ExecutionError(e))), Ok(out) => { - if langstr.should_panic && out.status.success() { - return (duration, Err(TestFailure::UnexpectedRunPass)); - } else if !langstr.should_panic && !out.status.success() { + if langstr.should_panic { + match out.status.code() { + Some(test::ERROR_EXIT_CODE) => {} + #[cfg(windows)] + Some(STATUS_FAIL_FAST_EXCEPTION) => {} + #[cfg(unix)] + None => match out.status.signal() { + Some(SIGABRT) => {} + Some(signal) => { + return ( + duration, + Err(TestFailure::NoPanic(Some(format!( + "Test didn't panic, but it's marked `should_panic` (exit signal: {signal}).", + )))), + ); + } + None => { + return ( + duration, + Err(TestFailure::NoPanic(Some(format!( + "Test didn't panic, but it's marked `should_panic` and exited with no error code and no signal.", + )))), + ); + } + }, + #[cfg(not(unix))] + None => return (duration, Err(TestFailure::NoPanic(None))), + // Upon an abort, Fuchsia returns the status code + // `ZX_TASK_RETCODE_EXCEPTION_KILL`. + #[cfg(target_os = "fuchsia")] + Some(ZX_TASK_RETCODE_EXCEPTION_KILL) => {} + Some(exit_code) => { + let err_msg = if !out.status.success() { + Some(format!( + "Test didn't panic, but it's marked `should_panic` (exit status: {exit_code}).", + )) + } else { + None + }; + return (duration, Err(TestFailure::NoPanic(err_msg))); + } + } + } else if !out.status.success() { return (duration, Err(TestFailure::ExecutionFailure(out))); } } @@ -1143,8 +1220,12 @@ fn doctest_run_fn( TestFailure::UnexpectedCompilePass => { eprint!("Test compiled successfully, but it's marked `compile_fail`."); } - TestFailure::UnexpectedRunPass => { - eprint!("Test executable succeeded, but it's marked `should_panic`."); + TestFailure::NoPanic(msg) => { + if let Some(msg) = msg { + eprint!("{msg}"); + } else { + eprint!("Test didn't panic, but it's marked `should_panic`."); + } } TestFailure::MissingErrorCodes(codes) => { eprint!("Some expected error codes were not found: {codes:?}"); diff --git a/src/librustdoc/doctest/runner.rs b/src/librustdoc/doctest/runner.rs index fcfa424968e48..1327b0717a7f5 100644 --- a/src/librustdoc/doctest/runner.rs +++ b/src/librustdoc/doctest/runner.rs @@ -39,6 +39,7 @@ impl DocTestRunner { doctest: &DocTestBuilder, scraped_test: &ScrapedDocTest, target_str: &str, + opts: &RustdocOptions, ) { let ignore = match scraped_test.langstr.ignore { Ignore::All => true, @@ -62,6 +63,7 @@ impl DocTestRunner { self.nb_tests, &mut self.output, &mut self.output_merged_tests, + opts, ), )); self.supports_color &= doctest.supports_color; @@ -121,12 +123,17 @@ impl DocTestRunner { {output} mod __doctest_mod {{ - use std::sync::OnceLock; + #[cfg(unix)] + use std::os::unix::process::ExitStatusExt; use std::path::PathBuf; use std::process::ExitCode; + use std::sync::OnceLock; pub static BINARY_PATH: OnceLock = OnceLock::new(); pub const RUN_OPTION: &str = \"RUSTDOC_DOCTEST_RUN_NB_TEST\"; + pub const SHOULD_PANIC_DISABLED: bool = ( + cfg!(target_family = \"wasm\") || cfg!(target_os = \"zkvm\") + ) && !cfg!(target_os = \"emscripten\"); #[allow(unused)] pub fn doctest_path() -> Option<&'static PathBuf> {{ @@ -134,13 +141,63 @@ mod __doctest_mod {{ }} #[allow(unused)] - pub fn doctest_runner(bin: &std::path::Path, test_nb: usize) -> ExitCode {{ + pub fn doctest_runner(bin: &std::path::Path, test_nb: usize, should_panic: bool) -> ExitCode {{ let out = std::process::Command::new(bin) .env(self::RUN_OPTION, test_nb.to_string()) .args(std::env::args().skip(1).collect::>()) .output() .expect(\"failed to run command\"); - if !out.status.success() {{ + if should_panic {{ + // FIXME: Make `test::get_result_from_exit_code` public and use this code instead of this. + // + // On Zircon (the Fuchsia kernel), an abort from userspace calls the + // LLVM implementation of __builtin_trap(), e.g., ud2 on x86, which + // raises a kernel exception. If a userspace process does not + // otherwise arrange exception handling, the kernel kills the process + // with this return code. + #[cfg(target_os = \"fuchsia\")] + const ZX_TASK_RETCODE_EXCEPTION_KILL: i32 = -1028; + // On Windows we use __fastfail to abort, which is documented to use this + // exception code. + #[cfg(windows)] + const STATUS_FAIL_FAST_EXCEPTION: i32 = 0xC0000409u32 as i32; + #[cfg(unix)] + const SIGABRT: std::ffi::c_int = 6; + + match out.status.code() {{ + Some(test::ERROR_EXIT_CODE) => ExitCode::SUCCESS, + #[cfg(windows)] + Some(STATUS_FAIL_FAST_EXCEPTION) => ExitCode::SUCCESS, + #[cfg(unix)] + None => match out.status.signal() {{ + Some(SIGABRT) => ExitCode::SUCCESS, + Some(signal) => {{ + eprintln!(\"Test didn't panic, but it's marked `should_panic` (exit signal: {{signal}}).\"); + ExitCode::FAILURE + }} + None => {{ + eprintln!(\"Test didn't panic, but it's marked `should_panic` and exited with no error code and no signal.\"); + ExitCode::FAILURE + }} + }}, + #[cfg(not(unix))] + None => {{ + eprintln!(\"Test didn't panic, but it's marked `should_panic`.\"); + ExitCode::FAILURE + }} + // Upon an abort, Fuchsia returns the status code ZX_TASK_RETCODE_EXCEPTION_KILL. + #[cfg(target_os = \"fuchsia\")] + Some(ZX_TASK_RETCODE_EXCEPTION_KILL) => ExitCode::SUCCESS, + Some(exit_code) => {{ + if !out.status.success() {{ + eprintln!(\"Test didn't panic, but it's marked `should_panic` (exit status: {{exit_code}}).\"); + }} else {{ + eprintln!(\"Test didn't panic, but it's marked `should_panic`.\"); + }} + ExitCode::FAILURE + }} + }} + }} else if !out.status.success() {{ if let Some(code) = out.status.code() {{ eprintln!(\"Test executable failed (exit status: {{code}}).\"); }} else {{ @@ -223,6 +280,7 @@ fn generate_mergeable_doctest( id: usize, output: &mut String, output_merged_tests: &mut String, + opts: &RustdocOptions, ) -> String { let test_id = format!("__doctest_{id}"); @@ -256,13 +314,14 @@ fn main() {returns_result} {{ ) .unwrap(); } - let not_running = ignore || scraped_test.langstr.no_run; + let should_panic = scraped_test.langstr.should_panic; + let not_running = ignore || scraped_test.no_run(opts); writeln!( output_merged_tests, " mod {test_id} {{ pub const TEST: test::TestDescAndFn = test::TestDescAndFn::new_doctest( -{test_name:?}, {ignore}, {file:?}, {line}, {no_run}, {should_panic}, +{test_name:?}, {ignore} || ({should_panic} && crate::__doctest_mod::SHOULD_PANIC_DISABLED), {file:?}, {line}, {no_run}, false, test::StaticTestFn( || {{{runner}}}, )); @@ -270,8 +329,7 @@ test::StaticTestFn( test_name = scraped_test.name, file = scraped_test.path(), line = scraped_test.line, - no_run = scraped_test.langstr.no_run, - should_panic = !scraped_test.langstr.no_run && scraped_test.langstr.should_panic, + no_run = scraped_test.no_run(opts), // Setting `no_run` to `true` in `TestDesc` still makes the test run, so we simply // don't give it the function to run. runner = if not_running { @@ -279,8 +337,10 @@ test::StaticTestFn( } else { format!( " -if let Some(bin_path) = crate::__doctest_mod::doctest_path() {{ - test::assert_test_result(crate::__doctest_mod::doctest_runner(bin_path, {id})) +if {should_panic} && crate::__doctest_mod::SHOULD_PANIC_DISABLED {{ + test::assert_test_result(Ok::<(), String>(())) +}} else if let Some(bin_path) = crate::__doctest_mod::doctest_path() {{ + test::assert_test_result(crate::__doctest_mod::doctest_runner(bin_path, {id}, {should_panic})) }} else {{ test::assert_test_result(doctest_bundle::{test_id}::__main_fn()) }} diff --git a/tests/codegen-llvm/cold-attribute.rs b/tests/codegen-llvm/cold-attribute.rs new file mode 100644 index 0000000000000..ca771fc7e1cfa --- /dev/null +++ b/tests/codegen-llvm/cold-attribute.rs @@ -0,0 +1,76 @@ +// Checks that the cold attribute adds the llvm cold attribute. +// +//@ reference: attributes.codegen.cold.intro +//@ reference: attributes.codegen.cold.trait +//@ edition:2024 +//@ compile-flags: -Copt-level=0 + +#![crate_type = "lib"] + +// CHECK-LABEL: ; cold_attribute::free_function +// CHECK-NEXT: Function Attrs: cold {{.*}} +#[cold] +pub fn free_function() {} + +// CHECK-LABEL: ; cold_attribute::async_block +// CHECK-NEXT: Function Attrs: cold {{.*}} +#[cold] +pub async fn async_block() { + async fn x(f: impl Future) { + f.await; + } + x( + // CHECK-LABEL: ; cold_attribute::async_block::{{{{closure}}}}::{{{{closure}}}} + // CHECK-NEXT: Function Attrs: cold {{.*}} + #[cold] + async {}, + ) + .await; +} + +pub fn closure() { + fn x(f: impl Fn()) { + f() + } + x( + // CHECK-LABEL: ; cold_attribute::closure::{{{{closure}}}} + // CHECK-NEXT: Function Attrs: cold {{.*}} + #[cold] + || {}, + ); +} + +pub struct S; + +impl S { + // CHECK-LABEL: ; cold_attribute::S::method + // CHECK-NEXT: Function Attrs: cold {{.*}} + #[cold] + pub fn method(&self) {} +} + +pub trait Trait { + // CHECK-LABEL: ; cold_attribute::Trait::trait_fn + // CHECK-NEXT: Function Attrs: cold {{.*}} + #[cold] + fn trait_fn(&self) {} + + #[cold] + fn trait_fn_overridden(&self) {} + + fn impl_fn(&self); +} + +impl Trait for S { + // CHECK-LABEL: ; ::impl_fn + // CHECK-NEXT: Function Attrs: cold {{.*}} + #[cold] + fn impl_fn(&self) { + self.trait_fn(); + } + + // This does not have #[cold], and does not inherit the cold attribute from the trait. + // CHECK-LABEL: ; ::trait_fn_overridden + // CHECK-NEXT: ; Function Attrs: uwtable + fn trait_fn_overridden(&self) {} +} diff --git a/tests/run-make/rustdoc-should-panic/rmake.rs b/tests/run-make/rustdoc-should-panic/rmake.rs new file mode 100644 index 0000000000000..0e93440af5ace --- /dev/null +++ b/tests/run-make/rustdoc-should-panic/rmake.rs @@ -0,0 +1,43 @@ +// Ensure that `should_panic` doctests only succeed if the test actually panicked. +// Regression test for . + +//@ ignore-cross-compile + +use run_make_support::rustdoc; + +fn check_output(edition: &str, panic_abort: bool) { + let mut rustdoc_cmd = rustdoc(); + rustdoc_cmd.input("test.rs").arg("--test").edition(edition); + if panic_abort { + rustdoc_cmd.args(["-C", "panic=abort"]); + } + let output = rustdoc_cmd.run_fail().stdout_utf8(); + let should_contain = &[ + "test test.rs - bad_exit_code (line 1) ... FAILED", + "test test.rs - did_not_panic (line 6) ... FAILED", + "test test.rs - did_panic (line 11) ... ok", + "---- test.rs - bad_exit_code (line 1) stdout ---- +Test executable failed (exit status: 1).", + "---- test.rs - did_not_panic (line 6) stdout ---- +Test didn't panic, but it's marked `should_panic` (exit status: 1).", + "test result: FAILED. 1 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out;", + ]; + for text in should_contain { + assert!( + output.contains(text), + "output (edition: {edition}) doesn't contain {:?}\nfull output: {output}", + text + ); + } +} + +fn main() { + check_output("2015", false); + + // Same check with the merged doctest feature (enabled with the 2024 edition). + check_output("2024", false); + + // Checking that `-C panic=abort` is working too. + check_output("2015", true); + check_output("2024", true); +} diff --git a/tests/run-make/rustdoc-should-panic/test.rs b/tests/run-make/rustdoc-should-panic/test.rs new file mode 100644 index 0000000000000..1eea8e1e1958c --- /dev/null +++ b/tests/run-make/rustdoc-should-panic/test.rs @@ -0,0 +1,14 @@ +/// ``` +/// std::process::exit(1); +/// ``` +fn bad_exit_code() {} + +/// ```should_panic +/// std::process::exit(1); +/// ``` +fn did_not_panic() {} + +/// ```should_panic +/// panic!("yeay"); +/// ``` +fn did_panic() {} diff --git a/tests/rustdoc-ui/doctest/failed-doctest-should-panic-2021.stdout b/tests/rustdoc-ui/doctest/failed-doctest-should-panic-2021.stdout index 9f4d60e6f4de5..f8413756e3d6d 100644 --- a/tests/rustdoc-ui/doctest/failed-doctest-should-panic-2021.stdout +++ b/tests/rustdoc-ui/doctest/failed-doctest-should-panic-2021.stdout @@ -5,7 +5,7 @@ test $DIR/failed-doctest-should-panic-2021.rs - Foo (line 10) ... FAILED failures: ---- $DIR/failed-doctest-should-panic-2021.rs - Foo (line 10) stdout ---- -Test executable succeeded, but it's marked `should_panic`. +Test didn't panic, but it's marked `should_panic`. failures: $DIR/failed-doctest-should-panic-2021.rs - Foo (line 10) diff --git a/tests/rustdoc-ui/doctest/failed-doctest-should-panic.rs b/tests/rustdoc-ui/doctest/failed-doctest-should-panic.rs index 0504c3dc73033..b95e23715175f 100644 --- a/tests/rustdoc-ui/doctest/failed-doctest-should-panic.rs +++ b/tests/rustdoc-ui/doctest/failed-doctest-should-panic.rs @@ -2,14 +2,17 @@ // adapted to use that, and that normalize line can go away //@ edition: 2024 -//@ compile-flags:--test +//@ compile-flags:--test --test-args=--test-threads=1 //@ normalize-stdout: "tests/rustdoc-ui/doctest" -> "$$DIR" //@ normalize-stdout: "finished in \d+\.\d+s" -> "finished in $$TIME" //@ normalize-stdout: "ran in \d+\.\d+s" -> "ran in $$TIME" //@ normalize-stdout: "compilation took \d+\.\d+s" -> "compilation took $$TIME" //@ failure-status: 101 -/// ```should_panic -/// println!("Hello, world!"); -/// ``` -pub struct Foo; +//! ```should_panic +//! println!("Hello, world!"); +//! ``` +//! +//! ```should_panic +//! std::process::exit(2); +//! ``` diff --git a/tests/rustdoc-ui/doctest/failed-doctest-should-panic.stdout b/tests/rustdoc-ui/doctest/failed-doctest-should-panic.stdout index 9047fe0dcdd93..a8e27fcdda2c9 100644 --- a/tests/rustdoc-ui/doctest/failed-doctest-should-panic.stdout +++ b/tests/rustdoc-ui/doctest/failed-doctest-should-panic.stdout @@ -1,15 +1,21 @@ -running 1 test -test $DIR/failed-doctest-should-panic.rs - Foo (line 12) - should panic ... FAILED +running 2 tests +test $DIR/failed-doctest-should-panic.rs - (line 12) ... FAILED +test $DIR/failed-doctest-should-panic.rs - (line 16) ... FAILED failures: ----- $DIR/failed-doctest-should-panic.rs - Foo (line 12) stdout ---- -note: test did not panic as expected at $DIR/failed-doctest-should-panic.rs:12:0 +---- $DIR/failed-doctest-should-panic.rs - (line 12) stdout ---- +Test didn't panic, but it's marked `should_panic`. + +---- $DIR/failed-doctest-should-panic.rs - (line 16) stdout ---- +Test didn't panic, but it's marked `should_panic` (exit status: 2). + failures: - $DIR/failed-doctest-should-panic.rs - Foo (line 12) + $DIR/failed-doctest-should-panic.rs - (line 12) + $DIR/failed-doctest-should-panic.rs - (line 16) -test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME +test result: FAILED. 0 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME all doctests ran in $TIME; merged doctests compilation took $TIME diff --git a/tests/rustdoc-ui/doctest/no-run.edition2021.stdout b/tests/rustdoc-ui/doctest/no-run.edition2021.stdout new file mode 100644 index 0000000000000..937cd76bfb462 --- /dev/null +++ b/tests/rustdoc-ui/doctest/no-run.edition2021.stdout @@ -0,0 +1,12 @@ + +running 7 tests +test $DIR/no-run.rs - f (line 14) - compile ... ok +test $DIR/no-run.rs - f (line 17) - compile ... ok +test $DIR/no-run.rs - f (line 20) ... ignored +test $DIR/no-run.rs - f (line 23) - compile ... ok +test $DIR/no-run.rs - f (line 29) - compile fail ... ok +test $DIR/no-run.rs - f (line 34) - compile ... ok +test $DIR/no-run.rs - f (line 38) - compile ... ok + +test result: ok. 6 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in $TIME + diff --git a/tests/rustdoc-ui/doctest/no-run.edition2024.stdout b/tests/rustdoc-ui/doctest/no-run.edition2024.stdout new file mode 100644 index 0000000000000..921e059979b1f --- /dev/null +++ b/tests/rustdoc-ui/doctest/no-run.edition2024.stdout @@ -0,0 +1,18 @@ + +running 5 tests +test $DIR/no-run.rs - f (line 14) - compile ... ok +test $DIR/no-run.rs - f (line 17) - compile ... ok +test $DIR/no-run.rs - f (line 23) - compile ... ok +test $DIR/no-run.rs - f (line 34) - compile ... ok +test $DIR/no-run.rs - f (line 38) - compile ... ok + +test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + + +running 2 tests +test $DIR/no-run.rs - f (line 20) ... ignored +test $DIR/no-run.rs - f (line 29) - compile fail ... ok + +test result: ok. 1 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in $TIME + +all doctests ran in $TIME; merged doctests compilation took $TIME diff --git a/tests/rustdoc-ui/doctest/no-run.rs b/tests/rustdoc-ui/doctest/no-run.rs new file mode 100644 index 0000000000000..78198badd43b5 --- /dev/null +++ b/tests/rustdoc-ui/doctest/no-run.rs @@ -0,0 +1,44 @@ +// This test ensures that the `--no-run` flag works the same between normal and merged doctests. +// Regression test for . + +//@ check-pass +//@ revisions: edition2021 edition2024 +//@ [edition2021]edition:2021 +//@ [edition2024]edition:2024 +//@ compile-flags:-Z unstable-options --test --no-run --test-args=--test-threads=1 +//@ normalize-stdout: "tests/rustdoc-ui/doctest" -> "$$DIR" +//@ normalize-stdout: "finished in \d+\.\d+s" -> "finished in $$TIME" +//@ normalize-stdout: "ran in \d+\.\d+s" -> "ran in $$TIME" +//@ normalize-stdout: "compilation took \d+\.\d+s" -> "compilation took $$TIME" + +/// ``` +/// let a = true; +/// ``` +/// ```should_panic +/// panic!() +/// ``` +/// ```ignore (incomplete-code) +/// fn foo() { +/// ``` +/// ```no_run +/// loop { +/// println!("Hello, world"); +/// } +/// ``` +/// fails to compile +/// ```compile_fail +/// let x = 5; +/// x += 2; // shouldn't compile! +/// ``` +/// Ok the test does not run +/// ``` +/// panic!() +/// ``` +/// Ok the test does not run +/// ```should_panic +/// loop { +/// println!("Hello, world"); +/// panic!() +/// } +/// ``` +pub fn f() {} diff --git a/tests/rustdoc-ui/doctest/wrong-ast-2024.stdout b/tests/rustdoc-ui/doctest/wrong-ast-2024.stdout index 13567b41e51f5..27f9a0157a6cc 100644 --- a/tests/rustdoc-ui/doctest/wrong-ast-2024.stdout +++ b/tests/rustdoc-ui/doctest/wrong-ast-2024.stdout @@ -1,6 +1,6 @@ running 1 test -test $DIR/wrong-ast-2024.rs - three (line 20) - should panic ... ok +test $DIR/wrong-ast-2024.rs - three (line 20) ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME diff --git a/tests/ui/attributes/no-mangle-closure.rs b/tests/ui/attributes/no-mangle-closure.rs index c76baa27f38a0..05dcbf8e5bed7 100644 --- a/tests/ui/attributes/no-mangle-closure.rs +++ b/tests/ui/attributes/no-mangle-closure.rs @@ -7,5 +7,5 @@ pub struct S([usize; 8]); pub fn outer_function(x: S, y: S) -> usize { (#[no_mangle] || y.0[0])() - //~^ ERROR `#[no_mangle]` cannot be used on a closure as it has no name + //~^ ERROR `#[no_mangle]` attribute cannot be used on closures } diff --git a/tests/ui/attributes/no-mangle-closure.stderr b/tests/ui/attributes/no-mangle-closure.stderr index c183783493bdb..f81e65d926752 100644 --- a/tests/ui/attributes/no-mangle-closure.stderr +++ b/tests/ui/attributes/no-mangle-closure.stderr @@ -1,8 +1,10 @@ -error: `#[no_mangle]` cannot be used on a closure as it has no name +error: `#[no_mangle]` attribute cannot be used on closures --> $DIR/no-mangle-closure.rs:9:6 | LL | (#[no_mangle] || y.0[0])() | ^^^^^^^^^^^^ + | + = help: `#[no_mangle]` can be applied to functions, methods, and statics error: aborting due to 1 previous error diff --git a/tests/ui/issues/issue-45562.fixed b/tests/ui/issues/issue-45562.fixed index 8dcdd3a541ce4..529b5bd744e03 100644 --- a/tests/ui/issues/issue-45562.fixed +++ b/tests/ui/issues/issue-45562.fixed @@ -1,5 +1,7 @@ //@ run-rustfix +#![deny(unused_attributes)] + #[no_mangle] pub static RAH: usize = 5; //~^ ERROR const items should never be `#[no_mangle]` diff --git a/tests/ui/issues/issue-45562.rs b/tests/ui/issues/issue-45562.rs index 08f6c8046dce4..7c30a967c7848 100644 --- a/tests/ui/issues/issue-45562.rs +++ b/tests/ui/issues/issue-45562.rs @@ -1,5 +1,7 @@ //@ run-rustfix +#![deny(unused_attributes)] + #[no_mangle] pub const RAH: usize = 5; //~^ ERROR const items should never be `#[no_mangle]` diff --git a/tests/ui/issues/issue-45562.stderr b/tests/ui/issues/issue-45562.stderr index 6fae86f9f31ca..55d35f76a0198 100644 --- a/tests/ui/issues/issue-45562.stderr +++ b/tests/ui/issues/issue-45562.stderr @@ -1,5 +1,5 @@ error: const items should never be `#[no_mangle]` - --> $DIR/issue-45562.rs:3:14 + --> $DIR/issue-45562.rs:5:14 | LL | #[no_mangle] pub const RAH: usize = 5; | ---------^^^^^^^^^^^^^^^^