From c8fde0788ae64577740d45f6f81ac4f15fe63883 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 12 Nov 2025 14:52:38 +0100 Subject: [PATCH 1/2] Add new `failed_merged_doctest_compilation` rustdoc lint --- src/librustdoc/doctest.rs | 170 ++++++++++-------- src/librustdoc/doctest/markdown.rs | 1 + src/librustdoc/lint.rs | 12 ++ tests/rustdoc-ui/doctest/dead-code-2024.rs | 1 + .../rustdoc-ui/doctest/dead-code-2024.stdout | 10 +- tests/rustdoc-ui/doctest/dead-code-items.rs | 1 + .../rustdoc-ui/doctest/dead-code-items.stdout | 82 ++++----- .../rustdoc-ui/doctest/dead-code-module-2.rs | 1 + .../doctest/dead-code-module-2.stdout | 12 +- tests/rustdoc-ui/doctest/dead-code-module.rs | 2 + .../doctest/dead-code-module.stdout | 10 +- .../doctest/doctest-output-include-fail.rs | 2 + ...iled-doctest-test-crate.edition2015.stdout | 8 +- ...iled-doctest-test-crate.edition2024.stdout | 8 +- .../doctest/failed-doctest-test-crate.rs | 2 + ...th-include-bytes-132203.edition2015.stdout | 8 +- .../relative-path-include-bytes-132203.rs | 2 + 17 files changed, 185 insertions(+), 147 deletions(-) diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs index 481aa392007c8..3d8df82baf9bd 100644 --- a/src/librustdoc/doctest.rs +++ b/src/librustdoc/doctest.rs @@ -23,6 +23,7 @@ use rustc_hir as hir; use rustc_hir::CRATE_HIR_ID; use rustc_hir::def_id::LOCAL_CRATE; use rustc_interface::interface; +use rustc_middle::ty::TyCtxt; use rustc_session::config::{self, CrateType, ErrorOutputType, Input}; use rustc_session::lint; use rustc_span::edition::Edition; @@ -204,13 +205,14 @@ pub(crate) fn run(dcx: DiagCtxtHandle<'_>, input: Input, options: RustdocOptions Err(error) => return crate::wrap_return(dcx, Err(error)), }; let args_path = temp_dir.path().join("rustdoc-cfgs"); + let temp_dir_path = temp_dir.path().to_path_buf(); crate::wrap_return(dcx, generate_args_file(&args_path, &options)); let extract_doctests = options.output_format == OutputFormat::Doctest; let result = interface::run_compiler(config, |compiler| { let krate = rustc_interface::passes::parse(&compiler.sess); - let collector = rustc_interface::create_and_enter_global_ctxt(compiler, krate, |tcx| { + rustc_interface::create_and_enter_global_ctxt(compiler, krate, |tcx| { let crate_name = tcx.crate_name(LOCAL_CRATE).to_string(); let crate_attrs = tcx.hir_attrs(CRATE_HIR_ID); let opts = scrape_test_config(crate_name, crate_attrs, args_path); @@ -220,6 +222,8 @@ pub(crate) fn run(dcx: DiagCtxtHandle<'_>, input: Input, options: RustdocOptions tcx, ); let tests = hir_collector.collect_crate(); + tcx.dcx().abort_if_errors(); + if extract_doctests { let mut collector = extracted::ExtractedDocTests::new(); tests.into_iter().for_each(|t| collector.add_test(t, &opts, &options)); @@ -228,90 +232,87 @@ pub(crate) fn run(dcx: DiagCtxtHandle<'_>, input: Input, options: RustdocOptions let mut stdout = stdout.lock(); if let Err(error) = serde_json::ser::to_writer(&mut stdout, &collector) { eprintln!(); - Err(format!("Failed to generate JSON output for doctests: {error:?}")) + return Err(format!("Failed to generate JSON output for doctests: {error:?}")); } else { - Ok(None) + return Ok(()); } - } else { - let mut collector = CreateRunnableDocTests::new(options, opts); - tests.into_iter().for_each(|t| collector.add_test(t, Some(compiler.sess.dcx()))); - - Ok(Some(collector)) } - }); - compiler.sess.dcx().abort_if_errors(); + let mut collector = CreateRunnableDocTests::new(options, opts); + tests.into_iter().for_each(|t| collector.add_test(t, Some(compiler.sess.dcx()))); - collector - }); + let CreateRunnableDocTests { + standalone_tests, + mergeable_tests, + rustdoc_options, + opts, + unused_extern_reports, + compiling_test_count, + .. + } = collector; - let CreateRunnableDocTests { - standalone_tests, - mergeable_tests, - rustdoc_options, - opts, - unused_extern_reports, - compiling_test_count, - .. - } = match result { - Ok(Some(collector)) => collector, - Ok(None) => return, - Err(error) => { - eprintln!("{error}"); - // Since some files in the temporary folder are still owned and alive, we need - // to manually remove the folder. - let _ = std::fs::remove_dir_all(temp_dir.path()); - std::process::exit(1); - } - }; + run_tests( + opts, + &rustdoc_options, + &unused_extern_reports, + standalone_tests, + mergeable_tests, + Some(temp_dir), + Some(tcx), + ); - run_tests( - opts, - &rustdoc_options, - &unused_extern_reports, - standalone_tests, - mergeable_tests, - Some(temp_dir), - ); + let compiling_test_count = compiling_test_count.load(Ordering::SeqCst); + + // Collect and warn about unused externs, but only if we've gotten + // reports for each doctest + if json_unused_externs.is_enabled() { + let unused_extern_reports: Vec<_> = + std::mem::take(&mut unused_extern_reports.lock().unwrap()); + if unused_extern_reports.len() == compiling_test_count { + let extern_names = + externs.iter().map(|(name, _)| name).collect::>(); + let mut unused_extern_names = unused_extern_reports + .iter() + .map(|uexts| { + uexts.unused_extern_names.iter().collect::>() + }) + .fold(extern_names, |uextsa, uextsb| { + uextsa.intersection(&uextsb).copied().collect::>() + }) + .iter() + .map(|v| (*v).clone()) + .collect::>(); + unused_extern_names.sort(); + // Take the most severe lint level + let lint_level = unused_extern_reports + .iter() + .map(|uexts| uexts.lint_level.as_str()) + .max_by_key(|v| match *v { + "warn" => 1, + "deny" => 2, + "forbid" => 3, + // The allow lint level is not expected, + // as if allow is specified, no message + // is to be emitted. + v => unreachable!("Invalid lint level '{v}'"), + }) + .unwrap_or("warn") + .to_string(); + let uext = UnusedExterns { lint_level, unused_extern_names }; + let unused_extern_json = serde_json::to_string(&uext).unwrap(); + eprintln!("{unused_extern_json}"); + } + } - let compiling_test_count = compiling_test_count.load(Ordering::SeqCst); - - // Collect and warn about unused externs, but only if we've gotten - // reports for each doctest - if json_unused_externs.is_enabled() { - let unused_extern_reports: Vec<_> = - std::mem::take(&mut unused_extern_reports.lock().unwrap()); - if unused_extern_reports.len() == compiling_test_count { - let extern_names = - externs.iter().map(|(name, _)| name).collect::>(); - let mut unused_extern_names = unused_extern_reports - .iter() - .map(|uexts| uexts.unused_extern_names.iter().collect::>()) - .fold(extern_names, |uextsa, uextsb| { - uextsa.intersection(&uextsb).copied().collect::>() - }) - .iter() - .map(|v| (*v).clone()) - .collect::>(); - unused_extern_names.sort(); - // Take the most severe lint level - let lint_level = unused_extern_reports - .iter() - .map(|uexts| uexts.lint_level.as_str()) - .max_by_key(|v| match *v { - "warn" => 1, - "deny" => 2, - "forbid" => 3, - // The allow lint level is not expected, - // as if allow is specified, no message - // is to be emitted. - v => unreachable!("Invalid lint level '{v}'"), - }) - .unwrap_or("warn") - .to_string(); - let uext = UnusedExterns { lint_level, unused_extern_names }; - let unused_extern_json = serde_json::to_string(&uext).unwrap(); - eprintln!("{unused_extern_json}"); - } + Ok(()) + }) + }); + + if let Err(error) = result { + eprintln!("{error}"); + // Since some files in the temporary folder are still owned and alive, we need + // to manually remove the folder. + let _ = std::fs::remove_dir_all(temp_dir_path); + std::process::exit(1); } } @@ -323,6 +324,7 @@ pub(crate) fn run_tests( mergeable_tests: FxIndexMap>, // We pass this argument so we can drop it manually before using `exit`. mut temp_dir: Option, + tcx: Option>, ) { let mut test_args = Vec::with_capacity(rustdoc_options.test_args.len() + 1); test_args.insert(0, "rustdoctest".to_string()); @@ -368,6 +370,18 @@ pub(crate) fn run_tests( } continue; } + if let Some(tcx) = tcx { + tcx.node_span_lint( + crate::lint::FAILED_MERGED_DOCTEST_COMPILATION, + CRATE_HIR_ID, + tcx.hir_span(CRATE_HIR_ID), + |lint| { + lint.primary_message( + format!("failed to compile merged doctests for edition {edition}. Reverting to standalone doctests."), + ); + }, + ); + } // We failed to compile all compatible tests as one so we push them into the // `standalone_tests` doctests. debug!("Failed to compile compatible doctests for edition {} all at once", edition); diff --git a/src/librustdoc/doctest/markdown.rs b/src/librustdoc/doctest/markdown.rs index 7f26605f2562c..ff65d6d996b1f 100644 --- a/src/librustdoc/doctest/markdown.rs +++ b/src/librustdoc/doctest/markdown.rs @@ -124,6 +124,7 @@ pub(crate) fn test(input: &Input, options: Options) -> Result<(), String> { standalone_tests, mergeable_tests, None, + None, ); Ok(()) } diff --git a/src/librustdoc/lint.rs b/src/librustdoc/lint.rs index b09ea05688595..94b4ff0f7e872 100644 --- a/src/librustdoc/lint.rs +++ b/src/librustdoc/lint.rs @@ -196,6 +196,17 @@ declare_rustdoc_lint! { "detects redundant explicit links in doc comments" } +declare_rustdoc_lint! { + /// This lint is **warn-by-default**. It warns when merged doctests fail to compile + /// when running doctests. This is a `rustdoc` only lint, see the documentation in + /// the [rustdoc book]. + /// + /// [rustdoc book]: ../../../rustdoc/lints.html#failed_merged_doctest_compilation + FAILED_MERGED_DOCTEST_COMPILATION, + Warn, + "warns when merged doctest fail to compile when running doctests" +} + pub(crate) static RUSTDOC_LINTS: Lazy> = Lazy::new(|| { vec![ BROKEN_INTRA_DOC_LINKS, @@ -209,6 +220,7 @@ pub(crate) static RUSTDOC_LINTS: Lazy> = Lazy::new(|| { MISSING_CRATE_LEVEL_DOCS, UNESCAPED_BACKTICKS, REDUNDANT_EXPLICIT_LINKS, + FAILED_MERGED_DOCTEST_COMPILATION, ] }); diff --git a/tests/rustdoc-ui/doctest/dead-code-2024.rs b/tests/rustdoc-ui/doctest/dead-code-2024.rs index e02d2601c5884..24d17f1d10e16 100644 --- a/tests/rustdoc-ui/doctest/dead-code-2024.rs +++ b/tests/rustdoc-ui/doctest/dead-code-2024.rs @@ -8,6 +8,7 @@ //@ normalize-stdout: "compilation took \d+\.\d+s" -> "compilation took $$TIME" //@ failure-status: 101 +#![allow(rustdoc::failed_merged_doctest_compilation)] #![doc(test(attr(allow(unused_variables), deny(warnings))))] /// Example diff --git a/tests/rustdoc-ui/doctest/dead-code-2024.stdout b/tests/rustdoc-ui/doctest/dead-code-2024.stdout index bf9cd65200b2b..f9f6a0236a830 100644 --- a/tests/rustdoc-ui/doctest/dead-code-2024.stdout +++ b/tests/rustdoc-ui/doctest/dead-code-2024.stdout @@ -1,18 +1,18 @@ running 1 test -test $DIR/dead-code-2024.rs - f (line 15) - compile ... FAILED +test $DIR/dead-code-2024.rs - f (line 16) - compile ... FAILED failures: ----- $DIR/dead-code-2024.rs - f (line 15) stdout ---- +---- $DIR/dead-code-2024.rs - f (line 16) stdout ---- error: trait `T` is never used - --> $DIR/dead-code-2024.rs:16:7 + --> $DIR/dead-code-2024.rs:17:7 | LL | trait T { fn f(); } | ^ | note: the lint level is defined here - --> $DIR/dead-code-2024.rs:14:9 + --> $DIR/dead-code-2024.rs:15:9 | LL | #![deny(warnings)] | ^^^^^^^^ @@ -23,7 +23,7 @@ error: aborting due to 1 previous error Couldn't compile the test. failures: - $DIR/dead-code-2024.rs - f (line 15) + $DIR/dead-code-2024.rs - f (line 16) test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME diff --git a/tests/rustdoc-ui/doctest/dead-code-items.rs b/tests/rustdoc-ui/doctest/dead-code-items.rs index ff59bfaabc478..9dcb43a39acd4 100644 --- a/tests/rustdoc-ui/doctest/dead-code-items.rs +++ b/tests/rustdoc-ui/doctest/dead-code-items.rs @@ -9,6 +9,7 @@ //@ failure-status: 101 #![doc(test(attr(deny(warnings))))] +#![allow(rustdoc::failed_merged_doctest_compilation)] #[doc(test(attr(allow(dead_code))))] /// Example diff --git a/tests/rustdoc-ui/doctest/dead-code-items.stdout b/tests/rustdoc-ui/doctest/dead-code-items.stdout index ecfe09f09ce26..94f97c793e752 100644 --- a/tests/rustdoc-ui/doctest/dead-code-items.stdout +++ b/tests/rustdoc-ui/doctest/dead-code-items.stdout @@ -1,30 +1,30 @@ running 13 tests -test $DIR/dead-code-items.rs - A (line 34) - compile ... ok -test $DIR/dead-code-items.rs - A (line 90) - compile ... ok -test $DIR/dead-code-items.rs - A::field (line 41) - compile ... FAILED -test $DIR/dead-code-items.rs - A::method (line 96) - compile ... ok -test $DIR/dead-code-items.rs - C (line 24) - compile ... FAILED -test $DIR/dead-code-items.rs - Enum (line 72) - compile ... FAILED -test $DIR/dead-code-items.rs - Enum::Variant1 (line 79) - compile ... FAILED -test $DIR/dead-code-items.rs - MyTrait (line 105) - compile ... FAILED -test $DIR/dead-code-items.rs - MyTrait::my_trait_fn (line 112) - compile ... FAILED -test $DIR/dead-code-items.rs - S (line 16) - compile ... ok -test $DIR/dead-code-items.rs - U (line 50) - compile ... ok -test $DIR/dead-code-items.rs - U::field (line 57) - compile ... FAILED -test $DIR/dead-code-items.rs - U::field2 (line 63) - compile ... ok +test $DIR/dead-code-items.rs - A (line 35) - compile ... ok +test $DIR/dead-code-items.rs - A (line 91) - compile ... ok +test $DIR/dead-code-items.rs - A::field (line 42) - compile ... FAILED +test $DIR/dead-code-items.rs - A::method (line 97) - compile ... ok +test $DIR/dead-code-items.rs - C (line 25) - compile ... FAILED +test $DIR/dead-code-items.rs - Enum (line 73) - compile ... FAILED +test $DIR/dead-code-items.rs - Enum::Variant1 (line 80) - compile ... FAILED +test $DIR/dead-code-items.rs - MyTrait (line 106) - compile ... FAILED +test $DIR/dead-code-items.rs - MyTrait::my_trait_fn (line 113) - compile ... FAILED +test $DIR/dead-code-items.rs - S (line 17) - compile ... ok +test $DIR/dead-code-items.rs - U (line 51) - compile ... ok +test $DIR/dead-code-items.rs - U::field (line 58) - compile ... FAILED +test $DIR/dead-code-items.rs - U::field2 (line 64) - compile ... ok failures: ----- $DIR/dead-code-items.rs - A::field (line 41) stdout ---- +---- $DIR/dead-code-items.rs - A::field (line 42) stdout ---- error: trait `DeadCodeInField` is never used - --> $DIR/dead-code-items.rs:42:7 + --> $DIR/dead-code-items.rs:43:7 | LL | trait DeadCodeInField {} | ^^^^^^^^^^^^^^^ | note: the lint level is defined here - --> $DIR/dead-code-items.rs:40:9 + --> $DIR/dead-code-items.rs:41:9 | LL | #![deny(dead_code)] | ^^^^^^^^^ @@ -32,15 +32,15 @@ LL | #![deny(dead_code)] error: aborting due to 1 previous error Couldn't compile the test. ----- $DIR/dead-code-items.rs - C (line 24) stdout ---- +---- $DIR/dead-code-items.rs - C (line 25) stdout ---- error: unused variable: `unused_error` - --> $DIR/dead-code-items.rs:25:5 + --> $DIR/dead-code-items.rs:26:5 | LL | let unused_error = 5; | ^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_unused_error` | note: the lint level is defined here - --> $DIR/dead-code-items.rs:22:9 + --> $DIR/dead-code-items.rs:23:9 | LL | #![deny(warnings)] | ^^^^^^^^ @@ -49,15 +49,15 @@ LL | #![deny(warnings)] error: aborting due to 1 previous error Couldn't compile the test. ----- $DIR/dead-code-items.rs - Enum (line 72) stdout ---- +---- $DIR/dead-code-items.rs - Enum (line 73) stdout ---- error: unused variable: `not_dead_code_but_unused` - --> $DIR/dead-code-items.rs:73:5 + --> $DIR/dead-code-items.rs:74:5 | LL | let not_dead_code_but_unused = 5; | ^^^^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_not_dead_code_but_unused` | note: the lint level is defined here - --> $DIR/dead-code-items.rs:70:9 + --> $DIR/dead-code-items.rs:71:9 | LL | #![deny(warnings)] | ^^^^^^^^ @@ -66,15 +66,15 @@ LL | #![deny(warnings)] error: aborting due to 1 previous error Couldn't compile the test. ----- $DIR/dead-code-items.rs - Enum::Variant1 (line 79) stdout ---- +---- $DIR/dead-code-items.rs - Enum::Variant1 (line 80) stdout ---- error: unused variable: `unused_in_variant` - --> $DIR/dead-code-items.rs:82:17 + --> $DIR/dead-code-items.rs:83:17 | LL | fn main() { let unused_in_variant = 5; } | ^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_unused_in_variant` | note: the lint level is defined here - --> $DIR/dead-code-items.rs:77:9 + --> $DIR/dead-code-items.rs:78:9 | LL | #![deny(warnings)] | ^^^^^^^^ @@ -83,15 +83,15 @@ LL | #![deny(warnings)] error: aborting due to 1 previous error Couldn't compile the test. ----- $DIR/dead-code-items.rs - MyTrait (line 105) stdout ---- +---- $DIR/dead-code-items.rs - MyTrait (line 106) stdout ---- error: trait `StillDeadCodeAtMyTrait` is never used - --> $DIR/dead-code-items.rs:106:7 + --> $DIR/dead-code-items.rs:107:7 | LL | trait StillDeadCodeAtMyTrait { } | ^^^^^^^^^^^^^^^^^^^^^^ | note: the lint level is defined here - --> $DIR/dead-code-items.rs:104:9 + --> $DIR/dead-code-items.rs:105:9 | LL | #![deny(dead_code)] | ^^^^^^^^^ @@ -99,15 +99,15 @@ LL | #![deny(dead_code)] error: aborting due to 1 previous error Couldn't compile the test. ----- $DIR/dead-code-items.rs - MyTrait::my_trait_fn (line 112) stdout ---- +---- $DIR/dead-code-items.rs - MyTrait::my_trait_fn (line 113) stdout ---- error: unused variable: `unused_in_impl` - --> $DIR/dead-code-items.rs:115:17 + --> $DIR/dead-code-items.rs:116:17 | LL | fn main() { let unused_in_impl = 5; } | ^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_unused_in_impl` | note: the lint level is defined here - --> $DIR/dead-code-items.rs:110:9 + --> $DIR/dead-code-items.rs:111:9 | LL | #![deny(warnings)] | ^^^^^^^^ @@ -116,15 +116,15 @@ LL | #![deny(warnings)] error: aborting due to 1 previous error Couldn't compile the test. ----- $DIR/dead-code-items.rs - U::field (line 57) stdout ---- +---- $DIR/dead-code-items.rs - U::field (line 58) stdout ---- error: trait `DeadCodeInUnionField` is never used - --> $DIR/dead-code-items.rs:58:7 + --> $DIR/dead-code-items.rs:59:7 | LL | trait DeadCodeInUnionField {} | ^^^^^^^^^^^^^^^^^^^^ | note: the lint level is defined here - --> $DIR/dead-code-items.rs:56:9 + --> $DIR/dead-code-items.rs:57:9 | LL | #![deny(dead_code)] | ^^^^^^^^^ @@ -134,13 +134,13 @@ error: aborting due to 1 previous error Couldn't compile the test. failures: - $DIR/dead-code-items.rs - A::field (line 41) - $DIR/dead-code-items.rs - C (line 24) - $DIR/dead-code-items.rs - Enum (line 72) - $DIR/dead-code-items.rs - Enum::Variant1 (line 79) - $DIR/dead-code-items.rs - MyTrait (line 105) - $DIR/dead-code-items.rs - MyTrait::my_trait_fn (line 112) - $DIR/dead-code-items.rs - U::field (line 57) + $DIR/dead-code-items.rs - A::field (line 42) + $DIR/dead-code-items.rs - C (line 25) + $DIR/dead-code-items.rs - Enum (line 73) + $DIR/dead-code-items.rs - Enum::Variant1 (line 80) + $DIR/dead-code-items.rs - MyTrait (line 106) + $DIR/dead-code-items.rs - MyTrait::my_trait_fn (line 113) + $DIR/dead-code-items.rs - U::field (line 58) test result: FAILED. 6 passed; 7 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME diff --git a/tests/rustdoc-ui/doctest/dead-code-module-2.rs b/tests/rustdoc-ui/doctest/dead-code-module-2.rs index fd9c313ec9a40..d4a9de589af34 100644 --- a/tests/rustdoc-ui/doctest/dead-code-module-2.rs +++ b/tests/rustdoc-ui/doctest/dead-code-module-2.rs @@ -9,6 +9,7 @@ //@ failure-status: 101 #![doc(test(attr(allow(unused_variables))))] +#![allow(rustdoc::failed_merged_doctest_compilation)] mod my_mod { #![doc(test(attr(deny(warnings))))] diff --git a/tests/rustdoc-ui/doctest/dead-code-module-2.stdout b/tests/rustdoc-ui/doctest/dead-code-module-2.stdout index cf737996d5c97..be5ec9aa8d58d 100644 --- a/tests/rustdoc-ui/doctest/dead-code-module-2.stdout +++ b/tests/rustdoc-ui/doctest/dead-code-module-2.stdout @@ -1,24 +1,24 @@ running 1 test -test $DIR/dead-code-module-2.rs - g (line 26) - compile ... ok +test $DIR/dead-code-module-2.rs - g (line 27) - compile ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME running 1 test -test $DIR/dead-code-module-2.rs - my_mod::f (line 18) - compile ... FAILED +test $DIR/dead-code-module-2.rs - my_mod::f (line 19) - compile ... FAILED failures: ----- $DIR/dead-code-module-2.rs - my_mod::f (line 18) stdout ---- +---- $DIR/dead-code-module-2.rs - my_mod::f (line 19) stdout ---- error: trait `T` is never used - --> $DIR/dead-code-module-2.rs:19:7 + --> $DIR/dead-code-module-2.rs:20:7 | LL | trait T { fn f(); } | ^ | note: the lint level is defined here - --> $DIR/dead-code-module-2.rs:17:9 + --> $DIR/dead-code-module-2.rs:18:9 | LL | #![deny(warnings)] | ^^^^^^^^ @@ -29,7 +29,7 @@ error: aborting due to 1 previous error Couldn't compile the test. failures: - $DIR/dead-code-module-2.rs - my_mod::f (line 18) + $DIR/dead-code-module-2.rs - my_mod::f (line 19) test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME diff --git a/tests/rustdoc-ui/doctest/dead-code-module.rs b/tests/rustdoc-ui/doctest/dead-code-module.rs index d3103ad28e9b3..4de88dc65bcad 100644 --- a/tests/rustdoc-ui/doctest/dead-code-module.rs +++ b/tests/rustdoc-ui/doctest/dead-code-module.rs @@ -8,6 +8,8 @@ //@ normalize-stdout: "compilation took \d+\.\d+s" -> "compilation took $$TIME" //@ failure-status: 101 +#![allow(rustdoc::failed_merged_doctest_compilation)] + mod my_mod { #![doc(test(attr(allow(unused_variables), deny(warnings))))] diff --git a/tests/rustdoc-ui/doctest/dead-code-module.stdout b/tests/rustdoc-ui/doctest/dead-code-module.stdout index 83c6af3775e06..a14b365ee4e7e 100644 --- a/tests/rustdoc-ui/doctest/dead-code-module.stdout +++ b/tests/rustdoc-ui/doctest/dead-code-module.stdout @@ -1,18 +1,18 @@ running 1 test -test $DIR/dead-code-module.rs - my_mod::f (line 16) - compile ... FAILED +test $DIR/dead-code-module.rs - my_mod::f (line 18) - compile ... FAILED failures: ----- $DIR/dead-code-module.rs - my_mod::f (line 16) stdout ---- +---- $DIR/dead-code-module.rs - my_mod::f (line 18) stdout ---- error: trait `T` is never used - --> $DIR/dead-code-module.rs:17:7 + --> $DIR/dead-code-module.rs:19:7 | LL | trait T { fn f(); } | ^ | note: the lint level is defined here - --> $DIR/dead-code-module.rs:15:9 + --> $DIR/dead-code-module.rs:17:9 | LL | #![deny(warnings)] | ^^^^^^^^ @@ -23,7 +23,7 @@ error: aborting due to 1 previous error Couldn't compile the test. failures: - $DIR/dead-code-module.rs - my_mod::f (line 16) + $DIR/dead-code-module.rs - my_mod::f (line 18) test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME diff --git a/tests/rustdoc-ui/doctest/doctest-output-include-fail.rs b/tests/rustdoc-ui/doctest/doctest-output-include-fail.rs index 2f0d6756b27fe..c6d31d1b3d93c 100644 --- a/tests/rustdoc-ui/doctest/doctest-output-include-fail.rs +++ b/tests/rustdoc-ui/doctest/doctest-output-include-fail.rs @@ -6,5 +6,7 @@ //@ normalize-stdout: "compilation took \d+\.\d+s" -> "compilation took $$TIME" //@ failure-status: 101 +#![allow(rustdoc::failed_merged_doctest_compilation)] + // https://github.com/rust-lang/rust/issues/130470 #![doc = include_str!("doctest-output-include-fail.md")] diff --git a/tests/rustdoc-ui/doctest/failed-doctest-test-crate.edition2015.stdout b/tests/rustdoc-ui/doctest/failed-doctest-test-crate.edition2015.stdout index d80c0da323d3a..fc084a3df1449 100644 --- a/tests/rustdoc-ui/doctest/failed-doctest-test-crate.edition2015.stdout +++ b/tests/rustdoc-ui/doctest/failed-doctest-test-crate.edition2015.stdout @@ -1,12 +1,12 @@ running 1 test -test $DIR/failed-doctest-test-crate.rs - m (line 16) ... FAILED +test $DIR/failed-doctest-test-crate.rs - m (line 18) ... FAILED failures: ----- $DIR/failed-doctest-test-crate.rs - m (line 16) stdout ---- +---- $DIR/failed-doctest-test-crate.rs - m (line 18) stdout ---- error[E0432]: unresolved import `test` - --> $DIR/failed-doctest-test-crate.rs:17:5 + --> $DIR/failed-doctest-test-crate.rs:19:5 | LL | use test::*; | ^^^^ use of unresolved module or unlinked crate `test` @@ -22,7 +22,7 @@ For more information about this error, try `rustc --explain E0432`. Couldn't compile the test. failures: - $DIR/failed-doctest-test-crate.rs - m (line 16) + $DIR/failed-doctest-test-crate.rs - m (line 18) test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME diff --git a/tests/rustdoc-ui/doctest/failed-doctest-test-crate.edition2024.stdout b/tests/rustdoc-ui/doctest/failed-doctest-test-crate.edition2024.stdout index 724bb9bee62dc..3643d145eb013 100644 --- a/tests/rustdoc-ui/doctest/failed-doctest-test-crate.edition2024.stdout +++ b/tests/rustdoc-ui/doctest/failed-doctest-test-crate.edition2024.stdout @@ -1,12 +1,12 @@ running 1 test -test $DIR/failed-doctest-test-crate.rs - m (line 16) ... FAILED +test $DIR/failed-doctest-test-crate.rs - m (line 18) ... FAILED failures: ----- $DIR/failed-doctest-test-crate.rs - m (line 16) stdout ---- +---- $DIR/failed-doctest-test-crate.rs - m (line 18) stdout ---- error[E0432]: unresolved import `test` - --> $DIR/failed-doctest-test-crate.rs:17:5 + --> $DIR/failed-doctest-test-crate.rs:19:5 | LL | use test::*; | ^^^^ use of unresolved module or unlinked crate `test` @@ -19,7 +19,7 @@ For more information about this error, try `rustc --explain E0432`. Couldn't compile the test. failures: - $DIR/failed-doctest-test-crate.rs - m (line 16) + $DIR/failed-doctest-test-crate.rs - m (line 18) test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME diff --git a/tests/rustdoc-ui/doctest/failed-doctest-test-crate.rs b/tests/rustdoc-ui/doctest/failed-doctest-test-crate.rs index 75f7ac396f559..3f8e6810cbc35 100644 --- a/tests/rustdoc-ui/doctest/failed-doctest-test-crate.rs +++ b/tests/rustdoc-ui/doctest/failed-doctest-test-crate.rs @@ -11,6 +11,8 @@ //@ normalize-stdout: "compilation took \d+\.\d+s" -> "compilation took $$TIME" //@ failure-status: 101 +#![allow(rustdoc::failed_merged_doctest_compilation)] + /// /// /// ```rust diff --git a/tests/rustdoc-ui/doctest/relative-path-include-bytes-132203.edition2015.stdout b/tests/rustdoc-ui/doctest/relative-path-include-bytes-132203.edition2015.stdout index 0d00a9fc9c45f..befdd0a454619 100644 --- a/tests/rustdoc-ui/doctest/relative-path-include-bytes-132203.edition2015.stdout +++ b/tests/rustdoc-ui/doctest/relative-path-include-bytes-132203.edition2015.stdout @@ -1,12 +1,12 @@ running 1 test -test $DIR/relative-path-include-bytes-132203.rs - (line 20) ... FAILED +test $DIR/relative-path-include-bytes-132203.rs - (line 22) ... FAILED failures: ----- $DIR/relative-path-include-bytes-132203.rs - (line 20) stdout ---- +---- $DIR/relative-path-include-bytes-132203.rs - (line 22) stdout ---- error: couldn't read `$DIR/relative-dir-empty-file`: $FILE_NOT_FOUND_MSG (os error 2) - --> $DIR/relative-path-include-bytes-132203.rs:21:9 + --> $DIR/relative-path-include-bytes-132203.rs:23:9 | LL | let x = include_bytes!("relative-dir-empty-file"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -16,7 +16,7 @@ error: aborting due to 1 previous error Couldn't compile the test. failures: - $DIR/relative-path-include-bytes-132203.rs - (line 20) + $DIR/relative-path-include-bytes-132203.rs - (line 22) test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME diff --git a/tests/rustdoc-ui/doctest/relative-path-include-bytes-132203.rs b/tests/rustdoc-ui/doctest/relative-path-include-bytes-132203.rs index 321edc3ee8438..1341ad0e2e803 100644 --- a/tests/rustdoc-ui/doctest/relative-path-include-bytes-132203.rs +++ b/tests/rustdoc-ui/doctest/relative-path-include-bytes-132203.rs @@ -17,4 +17,6 @@ // relative path. The edition2015 version fails, because paths are // resolved relative to the rs file instead of relative to the md file. +#![allow(rustdoc::failed_merged_doctest_compilation)] + #![doc=include_str!("auxiliary/relative-dir.md")] From 3d4bd835745c809eb544a17fe208f648b79e32f3 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 12 Nov 2025 14:57:13 +0100 Subject: [PATCH 2/2] Add ui regression test for new rustdoc `failed_merged_doctest_compilation` lint --- .../failed_merged_doctest_compilation-lint.rs | 16 ++++++++ ...led_merged_doctest_compilation-lint.stderr | 17 +++++++++ ...led_merged_doctest_compilation-lint.stdout | 37 +++++++++++++++++++ 3 files changed, 70 insertions(+) create mode 100644 tests/rustdoc-ui/doctest/failed_merged_doctest_compilation-lint.rs create mode 100644 tests/rustdoc-ui/doctest/failed_merged_doctest_compilation-lint.stderr create mode 100644 tests/rustdoc-ui/doctest/failed_merged_doctest_compilation-lint.stdout diff --git a/tests/rustdoc-ui/doctest/failed_merged_doctest_compilation-lint.rs b/tests/rustdoc-ui/doctest/failed_merged_doctest_compilation-lint.rs new file mode 100644 index 0000000000000..c0f5d9f302085 --- /dev/null +++ b/tests/rustdoc-ui/doctest/failed_merged_doctest_compilation-lint.rs @@ -0,0 +1,16 @@ +// Regression test for the `failed_merged_doctest_compilation` lint. + +//@ edition: 2024 +//@ compile-flags:--test +//@ 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 + +#![deny(rustdoc::failed_merged_doctest_compilation)] //~ ERROR + +//! ``` +//! +//! let x +//! ``` diff --git a/tests/rustdoc-ui/doctest/failed_merged_doctest_compilation-lint.stderr b/tests/rustdoc-ui/doctest/failed_merged_doctest_compilation-lint.stderr new file mode 100644 index 0000000000000..95f602ac8f66b --- /dev/null +++ b/tests/rustdoc-ui/doctest/failed_merged_doctest_compilation-lint.stderr @@ -0,0 +1,17 @@ +error: failed to compile merged doctests for edition 2024. Reverting to standalone doctests. + --> $DIR/failed_merged_doctest_compilation-lint.rs:11:1 + | +LL | / #![deny(rustdoc::failed_merged_doctest_compilation)] +LL | | +LL | | //! ``` +LL | | //! +LL | | //! let x +LL | | //! ``` + | |_______^ + | +note: the lint level is defined here + --> $DIR/failed_merged_doctest_compilation-lint.rs:11:9 + | +LL | #![deny(rustdoc::failed_merged_doctest_compilation)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + diff --git a/tests/rustdoc-ui/doctest/failed_merged_doctest_compilation-lint.stdout b/tests/rustdoc-ui/doctest/failed_merged_doctest_compilation-lint.stdout new file mode 100644 index 0000000000000..f3578c2edb717 --- /dev/null +++ b/tests/rustdoc-ui/doctest/failed_merged_doctest_compilation-lint.stdout @@ -0,0 +1,37 @@ + +running 1 test +test $DIR/failed_merged_doctest_compilation-lint.rs - (line 13) ... FAILED + +failures: + +---- $DIR/failed_merged_doctest_compilation-lint.rs - (line 13) stdout ---- +error: expected `;`, found `}` + --> $DIR/failed_merged_doctest_compilation-lint.rs:14:6 + | +LL | let x + | ^ help: add `;` here +LL | } _doctest_main_tests_rustdoc_ui_doctest_failed_merged_doctest_compilation_lint_rs_13_0() } + | - unexpected token + +error[E0282]: type annotations needed + --> $DIR/failed_merged_doctest_compilation-lint.rs:14:5 + | +LL | let x + | ^ + | +help: consider giving `x` an explicit type + | +LL | let x: /* Type */ + | ++++++++++++ + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0282`. +Couldn't compile the test. + +failures: + $DIR/failed_merged_doctest_compilation-lint.rs - (line 13) + +test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + +all doctests ran in $TIME; merged doctests compilation took $TIME