rustdoc: Add unstable `--merge-doctests=yes/no/auto` flag
This is useful for changing the default for whether doctests are merged or not. Currently, that default is solely controlled by edition = 2024, which adds a high switching cost to get doctest merging. This flag allows opting in even on earlier editions.
Unlike the edition = 2024 default, --merge-doctests=yes gives a hard error if merging fails instead of falling back to running standalone tests. The user has explicitly said they want merging, so we shouldn't silently do something else.
--merge-doctests=auto is equivalent to the current 2024 edition behavior, but available on earlier editions.
Helps with https://github.com/rust-lang/rust/issues/141240. @epage said in that issue he would like a per-doctest opt-in, and that seems useful to me in addition to this flag, but I think it's a separate use case from changing the default.
r? @notriddle
rustbot has assigned @notriddle. They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.
Use r? to explicitly pick a reviewer
oh, let me add a test that --merge-doctests is feature-gated.
The job tidy failed! Check out the build log: (web) (plain enhanced) (plain)
Click to see the possible cause of the failure (guessed by this bot)
Diff in /checkout/src/librustdoc/config.rs:1063:
}
}
-fn parse_merge_doctests(m: &getopts::Matches, edition: Edition, dcx: DiagCtxtHandle<'_>) -> MergeDoctests {
+fn parse_merge_doctests(
+ m: &getopts::Matches,
+ edition: Edition,
+ dcx: DiagCtxtHandle<'_>,
+) -> MergeDoctests {
match m.opt_str("merge-doctests").as_deref() {
Some("y") | Some("yes") | Some("on") | Some("true") => MergeDoctests::Always,
Some("n") | Some("no") | Some("off") | Some("false") => MergeDoctests::Never,
Diff in /checkout/src/librustdoc/lib.rs:544:
"[toolchain-shared-resources,invocation-specific,dep-info]",
),
opt(Unstable, FlagMulti, "", "no-run", "Compile doctests without running them", ""),
- opt(Unstable, Opt, "", "merge-doctests", "Force all doctests to be compiled as a single binary, instead of one binary per test. If merging fails, rustdoc will emit a hard error.", ""),
+ opt(
+ Unstable,
+ Opt,
+ "",
+ "merge-doctests",
+ "Force all doctests to be compiled as a single binary, instead of one binary per test. If merging fails, rustdoc will emit a hard error.",
The job x86_64-gnu-gcc failed! Check out the build log: (web) (plain enhanced) (plain)
Click to see the possible cause of the failure (guessed by this bot)
---- [run-make] tests/run-make/rustdoc-default-output stdout ----
error: rmake recipe failed to complete
status: exit status: 101
command: cd "/checkout/obj/build/x86_64-unknown-linux-gnu/test/run-make/rustdoc-default-output/rmake_out" && env -u RUSTFLAGS -u __RUSTC_DEBUG_ASSERTIONS_ENABLED -u __STD_DEBUG_ASSERTIONS_ENABLED AR="ar" BUILD_ROOT="/checkout/obj/build/x86_64-unknown-linux-gnu" CC="cc" CC_DEFAULT_FLAGS="-ffunction-sections -fdata-sections -fPIC -m64" CXX="c++" CXX_DEFAULT_FLAGS="-ffunction-sections -fdata-sections -fPIC -m64" HOST_RUSTC_DYLIB_PATH="/checkout/obj/build/x86_64-unknown-linux-gnu/stage2/lib" LD_LIBRARY_PATH="/checkout/obj/build/x86_64-unknown-linux-gnu/bootstrap-tools/x86_64-unknown-linux-gnu/release/deps:/checkout/obj/build/x86_64-unknown-linux-gnu/stage0/lib/rustlib/x86_64-unknown-linux-gnu/lib" LD_LIB_PATH_ENVVAR="LD_LIBRARY_PATH" LLVM_BIN_DIR="/checkout/obj/build/x86_64-unknown-linux-gnu/ci-llvm/bin" LLVM_COMPONENTS="aarch64 aarch64asmparser aarch64codegen aarch64desc aarch64disassembler aarch64info aarch64utils aggressiveinstcombine all all-targets amdgpu amdgpuasmparser amdgpucodegen amdgpudesc amdgpudisassembler amdgpuinfo amdgputargetmca amdgpuutils analysis arm armasmparser armcodegen armdesc armdisassembler arminfo armutils asmparser asmprinter avr avrasmparser avrcodegen avrdesc avrdisassembler avrinfo binaryformat bitreader bitstreamreader bitwriter bpf bpfasmparser bpfcodegen bpfdesc bpfdisassembler bpfinfo cfguard cgdata codegen codegentypes core coroutines coverage csky cskyasmparser cskycodegen cskydesc cskydisassembler cskyinfo debuginfobtf debuginfocodeview debuginfodwarf debuginfodwarflowlevel debuginfogsym debuginfologicalview debuginfomsf debuginfopdb demangle dlltooldriver dwarfcfichecker dwarflinker dwarflinkerclassic dwarflinkerparallel dwp engine executionengine extensions filecheck frontendatomic frontenddirective frontenddriver frontendhlsl frontendoffloading frontendopenacc frontendopenmp fuzzercli fuzzmutate globalisel hexagon hexagonasmparser hexagoncodegen hexagondesc hexagondisassembler hexagoninfo hipstdpar instcombine instrumentation interfacestub interpreter ipo irprinter irreader jitlink libdriver lineeditor linker loongarch loongarchasmparser loongarchcodegen loongarchdesc loongarchdisassembler loongarchinfo lto m68k m68kasmparser m68kcodegen m68kdesc m68kdisassembler m68kinfo mc mca mcdisassembler mcjit mcparser mips mipsasmparser mipscodegen mipsdesc mipsdisassembler mipsinfo mirparser msp430 msp430asmparser msp430codegen msp430desc msp430disassembler msp430info native nativecodegen nvptx nvptxcodegen nvptxdesc nvptxinfo objcarcopts objcopy object objectyaml option orcdebugging orcjit orcshared orctargetprocess passes powerpc powerpcasmparser powerpccodegen powerpcdesc powerpcdisassembler powerpcinfo profiledata remarks riscv riscvasmparser riscvcodegen riscvdesc riscvdisassembler riscvinfo riscvtargetmca runtimedyld sandboxir scalaropts selectiondag sparc sparcasmparser sparccodegen sparcdesc sparcdisassembler sparcinfo support symbolize systemz systemzasmparser systemzcodegen systemzdesc systemzdisassembler systemzinfo tablegen target targetparser telemetry textapi textapibinaryreader transformutils vectorize webassembly webassemblyasmparser webassemblycodegen webassemblydesc webassemblydisassembler webassemblyinfo webassemblyutils windowsdriver windowsmanifest x86 x86asmparser x86codegen x86desc x86disassembler x86info x86targetmca xray xtensa xtensaasmparser xtensacodegen xtensadesc xtensadisassembler xtensainfo" LLVM_FILECHECK="/checkout/obj/build/x86_64-unknown-linux-gnu/ci-llvm/bin/FileCheck" PYTHON="/usr/bin/python3" RUSTC="/checkout/obj/build/x86_64-unknown-linux-gnu/stage2/bin/rustc" RUSTDOC="/checkout/obj/build/x86_64-unknown-linux-gnu/stage2/bin/rustdoc" SOURCE_ROOT="/checkout" TARGET="x86_64-unknown-linux-gnu" TARGET_EXE_DYLIB_PATH="/checkout/obj/build/x86_64-unknown-linux-gnu/stage2/lib/rustlib/x86_64-unknown-linux-gnu/lib" "/checkout/obj/build/x86_64-unknown-linux-gnu/test/run-make/rustdoc-default-output/rmake"
stdout: none
--- stderr -------------------------------
thread 'main' (51145) panicked at /checkout/tests/run-make/rustdoc-default-output/rmake.rs:15:10:
test failed: `output-default.stdout` is different from `actual`
---
0: __rustc::rust_begin_unwind
at /rustc/3b4dd9bf1410f8da6329baa36ce5e37673cbbd1f/library/std/src/panicking.rs:698:5
1: core::panicking::panic_fmt
at /rustc/3b4dd9bf1410f8da6329baa36ce5e37673cbbd1f/library/core/src/panicking.rs:80:14
2: <run_make_support::diff::Diff>::run
3: rmake::main
4: core::ops::function::FnOnce::call_once
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
------------------------------------------
---- [run-make] tests/run-make/rustdoc-default-output stdout end ----
For more information how to resolve CI failures of this job, visit this link.
The job x86_64-gnu-tools failed! Check out the build log: (web) (plain enhanced) (plain)
Click to see the possible cause of the failure (guessed by this bot)
hm this broke the reference’s doctest somehow
failures:
---- items/constant-items.md - Constant_items (line 71) stdout ----
Test compiled successfully, but it's marked `compile_fail`.
---- items/constant-items.md - Constant_items (line 97) stdout ----
Test compiled successfully, but it's marked `compile_fail`.
failures:
items/constant-items.md - Constant_items (line 71)
items/constant-items.md - Constant_items (line 97)
i'm completely unable to reproduce this :/ i see ci has this output which i don't see locally:
[2025-12-03T00:03:32Z WARN mdbook::preprocess::cmd] The command wasn't found, is the "spec" preprocessor installed?
[2025-12-03T00:03:32Z WARN mdbook::preprocess::cmd] Command: cargo run --release --manifest-path mdbook-spec/Cargo.toml
i ran rustdoc +stage2 --test src/items/constant-items.md manually and it doesn't even show the same line numbers as CI :/ what on earth
This PR was rebased onto a different main commit. Here's a range-diff highlighting what actually changed.
Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers.
The job aarch64-gnu-llvm-20-1 failed! Check out the build log: (web) (plain enhanced) (plain)
Click to see the possible cause of the failure (guessed by this bot)
diff of stdout:
1
2 running 2 tests
- test $DIR/doctest/force-no-merge.rs - Foo (line 15) ... ok
- test $DIR/doctest/force-no-merge.rs - Foo (line 20) ... ok
+ test $DIR/force-no-merge.rs - Foo (line 15) ... ok
+ test $DIR/force-no-merge.rs - Foo (line 20) ... ok
5
6 test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME
7
Note: some mismatched output was normalized before being compared
- test /checkout/tests/rustdoc-ui/doctest/force-no-merge.rs - Foo (line 15) ... ok
- test /checkout/tests/rustdoc-ui/doctest/force-no-merge.rs - Foo (line 20) ... ok
+ test $DIR/force-no-merge.rs - Foo (line 15) ... ok
+ test $DIR/force-no-merge.rs - Foo (line 20) ... ok
The actual stdout differed from the expected stdout
To update references, rerun the tests and pass the `--bless` flag
To only update this specific test, also pass `--test-args doctest/force-no-merge.rs`
error: 1 errors occurred comparing output.
status: exit status: 0
command: env -u RUSTC_LOG_COLOR RUSTC_ICE="0" RUST_BACKTRACE="short" "/checkout/obj/build/aarch64-unknown-linux-gnu/stage2/bin/rustdoc" "/checkout/tests/rustdoc-ui/doctest/force-no-merge.rs" "-Zthreads=1" "-Zsimulate-remapped-rust-src-base=/rustc/FAKE_PREFIX" "-Ztranslate-remapped-path-to-local-path=no" "-Z" "ignore-directory-in-diagnostics-source-blocks=/cargo" "-Z" "ignore-directory-in-diagnostics-source-blocks=/checkout/vendor" "--sysroot" "/checkout/obj/build/aarch64-unknown-linux-gnu/stage2" "--target=aarch64-unknown-linux-gnu" "--check-cfg" "cfg(test,FALSE)" "--error-format" "json" "--json" "future-incompat" "-Ccodegen-units=1" "-Zui-testing" "-Zdeduplicate-diagnostics=no" "-Zwrite-long-types-to-disk=no" "-Cstrip=debuginfo" "-o" "/checkout/obj/build/aarch64-unknown-linux-gnu/test/rustdoc-ui/doctest/force-no-merge" "-A" "internal_features" "-A" "unused_parens" "-A" "unused_braces" "-Cdebuginfo=0" "--edition=2024" "--test" "--test-args=--test-threads=1" "--merge-doctests=no" "-Z" "unstable-options"
--- stdout -------------------------------
running 2 tests
test /checkout/tests/rustdoc-ui/doctest/force-no-merge.rs - Foo (line 15) ... ok
test /checkout/tests/rustdoc-ui/doctest/force-no-merge.rs - Foo (line 20) ... ok
---
diff of stdout:
1
2 running 2 tests
- test $DIR/doctest/force-merge.rs - Foo (line 14) ... ok
- test $DIR/doctest/force-merge.rs - Foo (line 20) ... ok
+ test $DIR/force-merge.rs - Foo (line 14) ... ok
+ test $DIR/force-merge.rs - Foo (line 20) ... ok
5
6 test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME
7
Note: some mismatched output was normalized before being compared
- test /checkout/tests/rustdoc-ui/doctest/force-merge.rs - Foo (line 14) ... ok
- test /checkout/tests/rustdoc-ui/doctest/force-merge.rs - Foo (line 20) ... ok
+ test $DIR/force-merge.rs - Foo (line 14) ... ok
+ test $DIR/force-merge.rs - Foo (line 20) ... ok
The actual stdout differed from the expected stdout
To update references, rerun the tests and pass the `--bless` flag
To only update this specific test, also pass `--test-args doctest/force-merge.rs`
error: 1 errors occurred comparing output.
status: exit status: 0
command: env -u RUSTC_LOG_COLOR RUSTC_ICE="0" RUST_BACKTRACE="short" "/checkout/obj/build/aarch64-unknown-linux-gnu/stage2/bin/rustdoc" "/checkout/tests/rustdoc-ui/doctest/force-merge.rs" "-Zthreads=1" "-Zsimulate-remapped-rust-src-base=/rustc/FAKE_PREFIX" "-Ztranslate-remapped-path-to-local-path=no" "-Z" "ignore-directory-in-diagnostics-source-blocks=/cargo" "-Z" "ignore-directory-in-diagnostics-source-blocks=/checkout/vendor" "--sysroot" "/checkout/obj/build/aarch64-unknown-linux-gnu/stage2" "--target=aarch64-unknown-linux-gnu" "--check-cfg" "cfg(test,FALSE)" "--error-format" "json" "--json" "future-incompat" "-Ccodegen-units=1" "-Zui-testing" "-Zdeduplicate-diagnostics=no" "-Zwrite-long-types-to-disk=no" "-Cstrip=debuginfo" "-o" "/checkout/obj/build/aarch64-unknown-linux-gnu/test/rustdoc-ui/doctest/force-merge" "-A" "internal_features" "-A" "unused_parens" "-A" "unused_braces" "-Cdebuginfo=0" "--edition=2018" "--test" "--test-args=--test-threads=1" "--merge-doctests=yes" "-Z" "unstable-options"
--- stdout -------------------------------
running 2 tests
test /checkout/tests/rustdoc-ui/doctest/force-merge.rs - Foo (line 14) ... ok
test /checkout/tests/rustdoc-ui/doctest/force-merge.rs - Foo (line 20) ... ok
---
diff of stdout:
1
2 running 2 tests
- test $DIR/doctest/merge-doctests-auto.rs - Foo (line 17) ... ok
- test $DIR/doctest/merge-doctests-auto.rs - Foo (line 23) ... ok
+ test $DIR/merge-doctests-auto.rs - Foo (line 17) ... ok
+ test $DIR/merge-doctests-auto.rs - Foo (line 23) ... ok
5
6 test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME
7
Note: some mismatched output was normalized before being compared
- test /checkout/tests/rustdoc-ui/doctest/merge-doctests-auto.rs - Foo (line 17) ... ok
- test /checkout/tests/rustdoc-ui/doctest/merge-doctests-auto.rs - Foo (line 23) ... ok
+ test $DIR/merge-doctests-auto.rs - Foo (line 17) ... ok
+ test $DIR/merge-doctests-auto.rs - Foo (line 23) ... ok
The actual stdout differed from the expected stdout
To update references, rerun the tests and pass the `--bless` flag
To only update this specific test, also pass `--test-args doctest/merge-doctests-auto.rs`
error: 1 errors occurred comparing output.
status: exit status: 0
command: env -u RUSTC_LOG_COLOR RUSTC_ICE="0" RUST_BACKTRACE="short" "/checkout/obj/build/aarch64-unknown-linux-gnu/stage2/bin/rustdoc" "/checkout/tests/rustdoc-ui/doctest/merge-doctests-auto.rs" "-Zthreads=1" "-Zsimulate-remapped-rust-src-base=/rustc/FAKE_PREFIX" "-Ztranslate-remapped-path-to-local-path=no" "-Z" "ignore-directory-in-diagnostics-source-blocks=/cargo" "-Z" "ignore-directory-in-diagnostics-source-blocks=/checkout/vendor" "--sysroot" "/checkout/obj/build/aarch64-unknown-linux-gnu/stage2" "--target=aarch64-unknown-linux-gnu" "--check-cfg" "cfg(test,FALSE)" "--error-format" "json" "--json" "future-incompat" "-Ccodegen-units=1" "-Zui-testing" "-Zdeduplicate-diagnostics=no" "-Zwrite-long-types-to-disk=no" "-Cstrip=debuginfo" "-o" "/checkout/obj/build/aarch64-unknown-linux-gnu/test/rustdoc-ui/doctest/merge-doctests-auto" "-A" "internal_features" "-A" "unused_parens" "-A" "unused_braces" "-Cdebuginfo=0" "--edition=2018" "--test" "--test-args=--test-threads=1" "--merge-doctests=auto" "-Z" "unstable-options"
--- stdout -------------------------------
running 2 tests
test /checkout/tests/rustdoc-ui/doctest/merge-doctests-auto.rs - Foo (line 17) ... ok
test /checkout/tests/rustdoc-ui/doctest/merge-doctests-auto.rs - Foo (line 23) ... ok
something really weird is going on with compiletest; locally it applies the normalize-stdout directive, but in CI it ignores it and replaces $DIR. the two don't match, hence the failure, but I don't understand why $DIR would possibly work in CI but not locally.
for now I'm just going to change the normalization to match compiletest's normalization.
oh, i wonder if this is about absolute vs relative paths :/
i think the proper fix here is that rustdoc should use to_embeddable_absolute_path when -Z ui-testing is set. but i've already spent a bunch of time on this so i'm just going to open an issue and hopefully someone gets to it later.
reminder that i no longer have bors privileges and need someone else to approve :)
@bors r+
:pushpin: Commit 415953a317f04b0b5ad26b1ded3671a3e2a01532 has been approved by notriddle
It is now in the queue for this repository.