From 35d2783f7813306c80a17c8f1dd0cd20a217d496 Mon Sep 17 00:00:00 2001 From: Syed Albiz Date: Sat, 19 Sep 2026 23:33:21 -0400 Subject: [PATCH 1/2] Extend rustdoc to accept the same set of command line flags as rustc This is intended to make it easier to use the same flag-set for both rustc and rustdoc in build systems without having invocations fail. This is accomplished by sharing rustc_optgroups() between rustc and rustdoc, and adding test coverage to ensure that the two stay in sync. Rustdoc-specific flags are added on top of the rustc_optgroups table. Certain incompatible flags are *not* inherited from rustc_optgroups and redeclared. As a result, -l, -O, and -g now reach doctest compilation. Ordering of forwarded rustc flags is preserved precisely to maintain order-dependent flag semantics. Some caveats and known limitations: - There are some overlapping flags with differing semantics between rustc and rustdoc (e.g. -o/--out-dir) - In most cases during documentation builds, rustc-specific flags are simply inert when passed to rustdoc. This could create some confusion e.g. with flags like -j. Not included in this change is warning on such inert flags that are only accepted for compatibility reasons. - The help text has been overridden in a few cases to explain more clearly how the flag is interpreted in rustdoc, but in most cases the help text from rustc_optgroups() is inherited verbatim. - --json will now allow repeated values, accumulating options across them as rustc does. --- src/librustdoc/config.rs | 47 ++++-- src/librustdoc/config/tests.rs | 90 ++++++++++++ src/librustdoc/doctest.rs | 21 +-- src/librustdoc/lib.rs | 137 ++++++++---------- .../rustdoc/accepts-rustc-flags/cfoo.c | 1 + .../rustdoc/accepts-rustc-flags/lib.rs | 7 + .../rustdoc/accepts-rustc-flags/rmake.rs | 20 +++ .../default-output/output-default.stdout | 105 ++++++++------ .../doctest/opt-level-forwarded.noopt.stdout | 22 +++ .../doctest/opt-level-forwarded.opt.stdout | 6 + .../rustdoc-ui/doctest/opt-level-forwarded.rs | 17 +++ 11 files changed, 322 insertions(+), 151 deletions(-) create mode 100644 src/librustdoc/config/tests.rs create mode 100644 tests/run-make/rustdoc/accepts-rustc-flags/cfoo.c create mode 100644 tests/run-make/rustdoc/accepts-rustc-flags/lib.rs create mode 100644 tests/run-make/rustdoc/accepts-rustc-flags/rmake.rs create mode 100644 tests/rustdoc-ui/doctest/opt-level-forwarded.noopt.stdout create mode 100644 tests/rustdoc-ui/doctest/opt-level-forwarded.opt.stdout create mode 100644 tests/rustdoc-ui/doctest/opt-level-forwarded.rs diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs index c2c58a345fa22..1e1d76aa97e08 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -30,6 +30,9 @@ use crate::html::static_files; use crate::scrape_examples::{AllCallLocations, ScrapeExamplesOptions}; use crate::{html, opts, theme}; +#[cfg(test)] +mod tests; + #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub(crate) enum OutputFormat { /// `--output-format=json` without `--show-coverage`. @@ -75,8 +78,6 @@ pub(crate) struct Options { pub(crate) diagnostic_width: Option, /// Library search paths to hand to the compiler. pub(crate) libs: Vec, - /// Library search paths strings to hand to the compiler. - pub(crate) lib_strs: Vec, /// The list of external crates to link against. pub(crate) externs: Externs, /// The list of external crates strings to link against. @@ -87,12 +88,10 @@ pub(crate) struct Options { pub(crate) check_cfgs: Vec, /// Codegen options to hand to the compiler. pub(crate) codegen_options: CodegenOptions, - /// Codegen options strings to hand to the compiler. - pub(crate) codegen_options_strs: Vec, /// Unstable (`-Z`) options to pass to the compiler. pub(crate) unstable_opts: UnstableOptions, - /// Unstable (`-Z`) options strings to pass to the compiler. - pub(crate) unstable_opts_strs: Vec, + /// Compiler options, in command-line order, to hand to the compiler building doctests. + pub(crate) forwarded_rustc_args: Vec, /// The target used to compile the crate against. pub(crate) target: TargetTuple, /// Edition used when reading the crate. Defaults to "2015". Also used by default when @@ -853,9 +852,7 @@ impl Options { let test_run_directory = matches.opt_str("test-run-directory").map(PathBuf::from); let persist_doctests = matches.opt_str("persist-doctests").map(PathBuf::from); let test_builder = matches.opt_str("test-builder").map(PathBuf::from); - let codegen_options_strs = matches.opt_strs("C"); - let unstable_opts_strs = matches.opt_strs("Z"); - let lib_strs = matches.opt_strs("L"); + let forwarded_rustc_args = forwarded_rustc_args(matches); let extern_strs = matches.opt_strs("extern"); let test_runtool = matches.opt_str("test-runtool"); let test_runtool_args = matches.opt_strs("test-runtool-arg"); @@ -902,15 +899,13 @@ impl Options { error_format, diagnostic_width, libs, - lib_strs, externs, extern_strs, cfgs, check_cfgs, codegen_options, - codegen_options_strs, unstable_opts, - unstable_opts_strs, + forwarded_rustc_args, target, edition, sysroot, @@ -986,6 +981,34 @@ pub(crate) fn markdown_input(input: &Input) -> Option<&Path> { input.opt_path().filter(|p| matches!(p.extension(), Some(e) if e == "md" || e == "markdown")) } +/// Re-serializes, in their original command-line order, the options that the compiler building +/// doctests must see. Order is preserved because rustc resolves `-O` against `-C opt-level` and +/// `-g` against `-C debuginfo` by position. +pub(crate) fn forwarded_rustc_args(matches: &getopts::Matches) -> Vec { + let mut args = Vec::new(); + for (name, prefix) in [ + ("cfg", "--cfg="), + ("check-cfg", "--check-cfg="), + ("library-path", "-L"), + ("extern", "--extern="), + ("l", "-l"), + ("codegen", "-C"), + ("Z", "-Z"), + ] { + args.extend( + matches + .opt_strs_pos(name) + .into_iter() + .map(|(pos, val)| (pos, format!("{prefix}{val}"))), + ); + } + for name in ["O", "g"] { + args.extend(matches.opt_positions(name).into_iter().map(|pos| (pos, format!("-{name}")))); + } + args.sort_by_key(|(pos, _)| *pos); + args.into_iter().map(|(_, arg)| arg).collect() +} + fn parse_remap_path_prefix( matches: &getopts::Matches, ) -> Result, &'static str> { diff --git a/src/librustdoc/config/tests.rs b/src/librustdoc/config/tests.rs new file mode 100644 index 0000000000000..983163294a727 --- /dev/null +++ b/src/librustdoc/config/tests.rs @@ -0,0 +1,90 @@ +use rustc_session::config::rustc_optgroups; +use rustc_session::getopts; + +use crate::opts; + +fn rustdoc_getopts() -> getopts::Options { + let mut options = getopts::Options::new(); + for option in opts() { + option.apply(&mut options); + } + options +} + +#[test] +fn every_rustc_option_is_accepted_by_rustdoc() { + let options = rustdoc_getopts(); + let unrecognized: Vec = rustc_optgroups() + .iter() + .map(|opt| { + let long = opt.long_name(); + if long.is_empty() { format!("-{}", opt.name) } else { format!("--{long}") } + }) + .filter(|arg| { + matches!( + options.parse([arg.as_str(), "dummy"]), + Err(getopts::Fail::UnrecognizedOption(_)) + ) + }) + .collect(); + assert!(unrecognized.is_empty(), "rustc options rejected by rustdoc: {unrecognized:?}"); +} + +#[test] +fn every_override_names_a_rustc_option() { + let rustc_names: Vec<&str> = rustc_optgroups().iter().map(|opt| opt.name).collect(); + let stale: Vec<&str> = + crate::RUSTDOC_OVERRIDES.iter().copied().filter(|n| !rustc_names.contains(n)).collect(); + assert!(stale.is_empty(), "overrides that no longer match a rustc option: {stale:?}"); +} + +#[test] +fn options_are_declared_once() { + let mut names: Vec<&str> = opts().iter().map(|opt| opt.name).collect(); + names.sort_unstable(); + let before = names.len(); + names.dedup(); + assert_eq!(before, names.len(), "duplicate option declarations: {names:?}"); +} + +#[test] +fn forwarded_rustc_args_preserve_command_line_order() { + let matches = rustdoc_getopts() + .parse([ + "-Copt-level=1", + "--cfg", + "a", + "-O", + "-Zfoo", + "-lbar", + "-Lx", + "--library-path=y", + "--extern", + "e=path", + "-g", + "--codegen", + "debuginfo=1", + "--check-cfg", + "cfg(a)", + "--out-dir", + "ignored", + "input.rs", + ]) + .unwrap(); + assert_eq!( + super::forwarded_rustc_args(&matches), + [ + "-Copt-level=1", + "--cfg=a", + "-O", + "-Zfoo", + "-lbar", + "-Lx", + "-Ly", + "--extern=e=path", + "-g", + "-Cdebuginfo=1", + "--check-cfg=cfg(a)", + ] + ); +} diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs index 0ecbc5927cf48..7aca0bd6c7c76 100644 --- a/src/librustdoc/doctest.rs +++ b/src/librustdoc/doctest.rs @@ -96,27 +96,8 @@ pub(crate) fn generate_args_file(file_path: &Path, options: &RustdocOptions) -> // We now put the common arguments into the file we created. let mut content = vec![]; - for cfg in &options.cfgs { - content.push(format!("--cfg={cfg}")); - } - for check_cfg in &options.check_cfgs { - content.push(format!("--check-cfg={check_cfg}")); - } - - for lib_str in &options.lib_strs { - content.push(format!("-L{lib_str}")); - } - for extern_str in &options.extern_strs { - content.push(format!("--extern={extern_str}")); - } content.push("-Ccodegen-units=1".to_string()); - for codegen_options_str in &options.codegen_options_strs { - content.push(format!("-C{codegen_options_str}")); - } - for unstable_option_str in &options.unstable_opts_strs { - content.push(format!("-Z{unstable_option_str}")); - } - + content.extend(options.forwarded_rustc_args.iter().cloned()); content.extend(options.doctest_build_args.clone()); let content = content.join("\n"); diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index 24427ff5cb3d3..a32aaf28208f8 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -70,7 +70,7 @@ use rustc_errors::DiagCtxtHandle; use rustc_hir::def_id::LOCAL_CRATE; use rustc_interface::interface; use rustc_middle::ty::TyCtxt; -use rustc_session::config::{ErrorOutputType, Input, RustcOptGroup, make_crate_type_option}; +use rustc_session::config::{ErrorOutputType, Input, RustcOptGroup, rustc_optgroups}; use rustc_session::{EarlyDiagCtxt, getopts}; use rustc_span::{BytePos, Span, SyntaxContext}; use tracing::info; @@ -195,12 +195,43 @@ fn init_logging(early_dcx: &EarlyDiagCtxt) { tracing::subscriber::set_global_default(subscriber).unwrap(); } +/// rustc options that rustdoc declares itself instead of inheriting from `rustc_optgroups`, +/// because their meaning, kind or stability differs in rustdoc. +const RUSTDOC_OVERRIDES: &[&str] = &[ + // repeat-tolerant (`FlagMulti`) in rustdoc, only singular versions accepted in rustc + "help", + "version", + "verbose", + // `-o` is an output directory, not a file as in rustc + // both long and short forms MUST be listed as they are declared separately in rustc + "o", + "out-dir", + // runs doctests instead of building a test harness as rustc does + "test", + // rustdoc-specific emission types + "emit", + // unstable in rustdoc + "print", + "remap-path-scope", + // rustc only accepts the short-flag, rustdoc accepts both short and long names. + "L", + // same meaning as in rustc, redeclared to carry rustdoc-specific help text + "l", + "cap-lints", +]; + fn opts() -> Vec { use rustc_session::config::OptionKind::{Flag, FlagMulti, Multi, Opt}; use rustc_session::config::OptionStability::{Stable, Unstable}; use rustc_session::config::make_opt as opt; - vec![ + // rustc-only flags are accepted for command-line compatibility but they are mostly inert for + // the documentation build and a subset may be forwarded to doctest compilation. + let mut options: Vec = rustc_optgroups() + .into_iter() + .filter(|opt| !RUSTDOC_OVERRIDES.contains(&opt.name)) + .collect(); + options.extend([ opt(Stable, FlagMulti, "h", "help", "show this help message", ""), opt(Stable, FlagMulti, "V", "version", "print rustdoc's version", ""), opt(Stable, FlagMulti, "v", "verbose", "use verbose output", ""), @@ -214,12 +245,31 @@ fn opts() -> Vec { "PATH", ), opt(Stable, Opt, "o", "out-dir", "which directory to place the output", "PATH"), - opt(Stable, Opt, "", "crate-name", "specify the name of this crate", "NAME"), - make_crate_type_option(), opt(Stable, Multi, "L", "library-path", "directory to add to crate search path", "DIR"), - opt(Stable, Multi, "", "cfg", "pass a --cfg to rustc", ""), - opt(Stable, Multi, "", "check-cfg", "pass a --check-cfg to rustc", ""), - opt(Stable, Multi, "", "extern", "pass an --extern to rustc", "NAME[=PATH]"), + opt( + Stable, + Multi, + "l", + "", + "Link the generated doctest(s) to the specified native\n\ + library NAME. The optional KIND can be one of\n\ + (default: dylib).\n\ + Optional comma separated MODIFIERS\n\ + \n\ + may be specified each with a prefix of either '+' to\n\ + enable or '-' to disable.", + "[[:]=][:]", + ), + opt( + Stable, + Multi, + "", + "cap-lints", + "Set the most restrictive lint level. \ + More restrictive lints are capped at this level. \ + By default, it is at `forbid` level.", + "LEVEL", + ), opt( Unstable, Multi, @@ -237,7 +287,6 @@ fn opts() -> Vec { "give precedence to `--extern-html-root-url`, not `html_root_url`", "", ), - opt(Stable, Multi, "C", "codegen", "pass a codegen option to rustc", "OPT[=VALUE]"), opt(Stable, FlagMulti, "", "document-private-items", "document private items", ""), opt( Unstable, @@ -257,7 +306,6 @@ fn opts() -> Vec { "The working directory in which to run tests", "PATH", ), - opt(Stable, Opt, "", "target", "target triple to document", "TRIPLE"), opt( Stable, Multi, @@ -322,15 +370,6 @@ fn opts() -> Vec { However, your theme might break if the rustdoc's generated HTML changes, so be careful!", "PATH", ), - opt( - Unstable, - Multi, - "Z", - "", - "unstable / perma-unstable options (only on nightly build)", - "FLAG", - ), - opt(Stable, Opt, "", "sysroot", "Override the system root", "PATH"), opt( Unstable, Opt, @@ -402,65 +441,6 @@ fn opts() -> Vec { e.g., \"search-index.js\" will become \"search-index-suffix.js\"", "PATH", ), - opt( - Stable, - Opt, - "", - "edition", - "edition to use when compiling rust code (default: 2015)", - "EDITION", - ), - opt( - Stable, - Opt, - "", - "color", - "Configure coloring of output: - auto = colorize, if output goes to a tty (default); - always = always colorize output; - never = never colorize output", - "auto|always|never", - ), - opt( - Stable, - Opt, - "", - "error-format", - "How errors and other messages are produced", - "human|json|short", - ), - opt( - Stable, - Opt, - "", - "diagnostic-width", - "Provide width of the output for truncated error messages", - "WIDTH", - ), - opt(Stable, Opt, "", "json", "Configure the structure of JSON diagnostics", "CONFIG"), - opt(Stable, Multi, "A", "allow", "Set lint allowed", "LINT"), - opt(Stable, Multi, "W", "warn", "Set lint warnings", "LINT"), - opt(Stable, Multi, "", "force-warn", "Set lint force-warn", "LINT"), - opt(Stable, Multi, "D", "deny", "Set lint denied", "LINT"), - opt(Stable, Multi, "F", "forbid", "Set lint forbidden", "LINT"), - opt( - Stable, - Multi, - "", - "cap-lints", - "Set the most restrictive lint level. \ - More restrictive lints are capped at this level. \ - By default, it is at `forbid` level.", - "LEVEL", - ), - opt( - Stable, - Multi, - "", - "remap-path-prefix", - "Remap source names in compiler messages", - "FROM=TO", - ), opt(Unstable, Opt, "", "index-page", "Markdown file to be used as index page", "PATH"), opt( Unstable, @@ -717,7 +697,8 @@ fn opts() -> Vec { "removed, see issue #44136 for more information", "[rust]", ), - ] + ]); + options } fn usage(argv0: &str) { diff --git a/tests/run-make/rustdoc/accepts-rustc-flags/cfoo.c b/tests/run-make/rustdoc/accepts-rustc-flags/cfoo.c new file mode 100644 index 0000000000000..3fd9ac839ed99 --- /dev/null +++ b/tests/run-make/rustdoc/accepts-rustc-flags/cfoo.c @@ -0,0 +1 @@ +int cfoo(void) { return 42; } diff --git a/tests/run-make/rustdoc/accepts-rustc-flags/lib.rs b/tests/run-make/rustdoc/accepts-rustc-flags/lib.rs new file mode 100644 index 0000000000000..8dd6f0a99c1f0 --- /dev/null +++ b/tests/run-make/rustdoc/accepts-rustc-flags/lib.rs @@ -0,0 +1,7 @@ +/// ``` +/// unsafe extern "C" { +/// fn cfoo() -> i32; +/// } +/// assert_eq!(unsafe { cfoo() }, 42); +/// ``` +pub fn documented() {} diff --git a/tests/run-make/rustdoc/accepts-rustc-flags/rmake.rs b/tests/run-make/rustdoc/accepts-rustc-flags/rmake.rs new file mode 100644 index 0000000000000..af03479f8245f --- /dev/null +++ b/tests/run-make/rustdoc/accepts-rustc-flags/rmake.rs @@ -0,0 +1,20 @@ +//@ ignore-cross-compile (the doctest binary is executed) + +// See . + +use run_make_support::{build_native_static_lib, cwd, rustdoc}; + +fn main() { + rustdoc() + .input("lib.rs") + .out_dir("doc") + .arg("-lcfoo") + .arg("-O") + .arg("-g") + .arg("--explain") + .arg("E0001") + .run(); + + build_native_static_lib("cfoo"); + rustdoc().input("lib.rs").arg("--test").arg("-L").arg(cwd()).arg("-lstatic=cfoo").run(); +} diff --git a/tests/run-make/rustdoc/default-output/output-default.stdout b/tests/run-make/rustdoc/default-output/output-default.stdout index 3093a01ec79f7..5121ea039ea28 100644 --- a/tests/run-make/rustdoc/default-output/output-default.stdout +++ b/tests/run-make/rustdoc/default-output/output-default.stdout @@ -1,6 +1,58 @@ rustdoc [options] Options: + --cfg Configure the compilation environment. + SPEC supports the syntax `[=""]`. + --check-cfg + Provide list of expected cfgs for checking + --crate-type + Comma separated list of types of crates + for the compiler to emit + --crate-name + Specify the name of the crate being built + --edition <2015|2018|2021|2024|future> + Specify which edition of the compiler to use when + compiling code. The default is 2015 and the latest + stable edition is 2024. + -g Equivalent to -C debuginfo=2 + -O Equivalent to -C opt-level=3 + --explain Provide a detailed explanation of an error message + --target + Target tuple for which the code is compiled + -A, --allow Set lint allowed + -W, --warn Set lint warnings + --force-warn + Set lint force-warn + -D, --deny Set lint denied + -F, --forbid Set lint forbidden + -C, --codegen [=] + Set a codegen option + --extern [=] + Specify where an external rust library is located + --sysroot + Override the system root + -Z Set unstable / perma-unstable options + --error-format + How errors and other messages are produced + --json Configure the JSON output of the compiler + --color + Configure coloring of output: + * auto = colorize, if output goes to a tty (default); + * always = always colorize output; + * never = never colorize output + --diagnostic-width + Inform rustc of the width of the output so that + diagnostics can be truncated to fit + --remap-path-prefix = + Remap source names in all output (compiler messages + and output files) + -j, --jobs Limit on the number of used parallel jobs + --jobs-frontend + Limit on the number of parallel jobs used by frontend + --jobs-backend + Limit on the number of parallel jobs used by backend + --jobs-linker + Limit on the number of parallel jobs used by linker -h, --help show this help message -V, --version print rustdoc's version -v, --verbose use verbose output @@ -9,17 +61,20 @@ Options: --output PATH Which directory to place the output. This option is deprecated, use --out-dir instead. -o, --out-dir PATH which directory to place the output - --crate-name NAME - specify the name of this crate - --crate-type - Comma separated list of types of crates - for the compiler to emit -L, --library-path DIR directory to add to crate search path - --cfg pass a --cfg to rustc - --check-cfg pass a --check-cfg to rustc - --extern NAME[=PATH] - pass an --extern to rustc + -l [[:]=][:] + Link the generated doctest(s) to the specified native + library NAME. The optional KIND can be one of + (default: dylib). + Optional comma separated MODIFIERS + + may be specified each with a prefix of either '+' to + enable or '-' to disable. + --cap-lints LEVEL + Set the most restrictive lint level. More restrictive + lints are capped at this level. By default, it is at + `forbid` level. --extern-html-root-url NAME=URL base URL to use for dependencies; for example, "std=/doc" links std::vec::Vec to @@ -27,8 +82,6 @@ Options: --extern-html-root-takes-precedence give precedence to `--extern-html-root-url`, not `html_root_url` - -C, --codegen OPT[=VALUE] - pass a codegen option to rustc --document-private-items document private items --document-hidden-items @@ -38,7 +91,6 @@ Options: arguments to pass to the test runner --test-run-directory PATH The working directory in which to run tests - --target TRIPLE target triple to document --markdown-css FILES CSS files to include via in a rendered Markdown file @@ -68,9 +120,6 @@ Options: doc with your own theme. However, your theme might break if the rustdoc's generated HTML changes, so be careful! - -Z FLAG unstable / perma-unstable options (only on nightly - build) - --sysroot PATH Override the system root --playground-url URL URL to send code snippets to, may be reset by --markdown-playground-url or @@ -101,32 +150,6 @@ Options: --resource-suffix PATH suffix to add to CSS and JavaScript files, e.g., "search-index.js" will become "search-index-suffix.js" - --edition EDITION - edition to use when compiling rust code (default: - 2015) - --color auto|always|never - Configure coloring of output: - auto = colorize, if output goes to a tty (default); - always = always colorize output; - never = never colorize output - --error-format human|json|short - How errors and other messages are produced - --diagnostic-width WIDTH - Provide width of the output for truncated error - messages - --json CONFIG Configure the structure of JSON diagnostics - -A, --allow LINT Set lint allowed - -W, --warn LINT Set lint warnings - --force-warn LINT - Set lint force-warn - -D, --deny LINT Set lint denied - -F, --forbid LINT Set lint forbidden - --cap-lints LEVEL - Set the most restrictive lint level. More restrictive - lints are capped at this level. By default, it is at - `forbid` level. - --remap-path-prefix FROM=TO - Remap source names in compiler messages --index-page PATH Markdown file to be used as index page --enable-index-page diff --git a/tests/rustdoc-ui/doctest/opt-level-forwarded.noopt.stdout b/tests/rustdoc-ui/doctest/opt-level-forwarded.noopt.stdout new file mode 100644 index 0000000000000..40191e3ad5889 --- /dev/null +++ b/tests/rustdoc-ui/doctest/opt-level-forwarded.noopt.stdout @@ -0,0 +1,22 @@ + +running 1 test +test $DIR/opt-level-forwarded.rs - Bar (line 12) ... FAILED + +failures: + +---- $DIR/opt-level-forwarded.rs - Bar (line 12) stdout ---- +error: debug assertions are enabled + --> $DIR/opt-level-forwarded.rs:14:1 + | +LL | compile_error!("debug assertions are enabled"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + +Couldn't compile the test. + +failures: + $DIR/opt-level-forwarded.rs - Bar (line 12) + +test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + diff --git a/tests/rustdoc-ui/doctest/opt-level-forwarded.opt.stdout b/tests/rustdoc-ui/doctest/opt-level-forwarded.opt.stdout new file mode 100644 index 0000000000000..602eb9c6ebfd4 --- /dev/null +++ b/tests/rustdoc-ui/doctest/opt-level-forwarded.opt.stdout @@ -0,0 +1,6 @@ + +running 1 test +test $DIR/opt-level-forwarded.rs - Bar (line 12) ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + diff --git a/tests/rustdoc-ui/doctest/opt-level-forwarded.rs b/tests/rustdoc-ui/doctest/opt-level-forwarded.rs new file mode 100644 index 0000000000000..c3db893231e43 --- /dev/null +++ b/tests/rustdoc-ui/doctest/opt-level-forwarded.rs @@ -0,0 +1,17 @@ +// See . + +//@ revisions: opt noopt +//@ compile-flags: --test +//@[opt] compile-flags: -O +//@[noopt] compile-flags: -O -Copt-level=0 +//@ normalize-stdout: "tests/rustdoc-ui/doctest" -> "$$DIR" +//@ normalize-stdout: "finished in \d+\.\d+s" -> "finished in $$TIME" +//@[opt] check-pass +//@[noopt] failure-status: 101 + +/// ``` +/// #[cfg(debug_assertions)] +/// compile_error!("debug assertions are enabled"); +/// fn main() {} +/// ``` +pub struct Bar; From da1ba02fc43631da2b6095ee4dd4727971f5955c Mon Sep 17 00:00:00 2001 From: Syed Albiz Date: Sun, 20 Sep 2026 02:25:11 -0400 Subject: [PATCH 2/2] Special handling for -l for compatibility with merged-doctest mode. --- src/librustdoc/config.rs | 8 +++++++- src/librustdoc/config/tests.rs | 16 +++++++++++++++- src/librustdoc/doctest.rs | 3 +++ .../rustdoc/accepts-rustc-flags/rmake.rs | 12 ++++++++++++ 4 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs index 1e1d76aa97e08..980262091727c 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -92,6 +92,8 @@ pub(crate) struct Options { pub(crate) unstable_opts: UnstableOptions, /// Compiler options, in command-line order, to hand to the compiler building doctests. pub(crate) forwarded_rustc_args: Vec, + /// Native libraries (`-l`) to link into doctest binaries. + pub(crate) native_lib_strs: Vec, /// The target used to compile the crate against. pub(crate) target: TargetTuple, /// Edition used when reading the crate. Defaults to "2015". Also used by default when @@ -853,6 +855,7 @@ impl Options { let persist_doctests = matches.opt_str("persist-doctests").map(PathBuf::from); let test_builder = matches.opt_str("test-builder").map(PathBuf::from); let forwarded_rustc_args = forwarded_rustc_args(matches); + let native_lib_strs = matches.opt_strs("l"); let extern_strs = matches.opt_strs("extern"); let test_runtool = matches.opt_str("test-runtool"); let test_runtool_args = matches.opt_strs("test-runtool-arg"); @@ -906,6 +909,7 @@ impl Options { codegen_options, unstable_opts, forwarded_rustc_args, + native_lib_strs, target, edition, sysroot, @@ -984,6 +988,9 @@ pub(crate) fn markdown_input(input: &Input) -> Option<&Path> { /// Re-serializes, in their original command-line order, the options that the compiler building /// doctests must see. Order is preserved because rustc resolves `-O` against `-C opt-level` and /// `-g` against `-C debuginfo` by position. +/// +/// `-l` is deliberately absent: these arguments are shared with the merged-doctest runner, which +/// links the native libraries through the bundle rlib and must not be given them a second time. pub(crate) fn forwarded_rustc_args(matches: &getopts::Matches) -> Vec { let mut args = Vec::new(); for (name, prefix) in [ @@ -991,7 +998,6 @@ pub(crate) fn forwarded_rustc_args(matches: &getopts::Matches) -> Vec { ("check-cfg", "--check-cfg="), ("library-path", "-L"), ("extern", "--extern="), - ("l", "-l"), ("codegen", "-C"), ("Z", "-Z"), ] { diff --git a/src/librustdoc/config/tests.rs b/src/librustdoc/config/tests.rs index 983163294a727..ca5dc93309cc7 100644 --- a/src/librustdoc/config/tests.rs +++ b/src/librustdoc/config/tests.rs @@ -78,7 +78,6 @@ fn forwarded_rustc_args_preserve_command_line_order() { "--cfg=a", "-O", "-Zfoo", - "-lbar", "-Lx", "-Ly", "--extern=e=path", @@ -88,3 +87,18 @@ fn forwarded_rustc_args_preserve_command_line_order() { ] ); } + +#[test] +fn overridden_short_flags_resolve_to_rustdoc_declarations() { + let matches = rustdoc_getopts() + .parse([ + "-o", "out", "-L", "search", "-h", "-h", "-V", "-V", "-v", "-v", "--test", "--test", + ]) + .unwrap(); + assert_eq!(matches.opt_str("out-dir").as_deref(), Some("out")); + assert_eq!(matches.opt_strs("library-path"), ["search"]); + assert_eq!(matches.opt_count("help"), 2); + assert_eq!(matches.opt_count("version"), 2); + assert_eq!(matches.opt_count("verbose"), 2); + assert_eq!(matches.opt_count("test"), 2); +} diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs index 7aca0bd6c7c76..2f4c297ca1eb8 100644 --- a/src/librustdoc/doctest.rs +++ b/src/librustdoc/doctest.rs @@ -676,6 +676,9 @@ fn run_test( let mut compiler = wrapped_rustc_command(&rustdoc_options.test_builder_wrappers, rustc_binary); compiler.args(&compiler_args); + for lib in &rustdoc_options.native_lib_strs { + compiler.arg(format!("-l{lib}")); + } // If this is a merged doctest, we need to write it into a file instead of using stdin // because if the size of the merged doctests is too big, it'll simply break stdin. diff --git a/tests/run-make/rustdoc/accepts-rustc-flags/rmake.rs b/tests/run-make/rustdoc/accepts-rustc-flags/rmake.rs index af03479f8245f..6395af34f122d 100644 --- a/tests/run-make/rustdoc/accepts-rustc-flags/rmake.rs +++ b/tests/run-make/rustdoc/accepts-rustc-flags/rmake.rs @@ -17,4 +17,16 @@ fn main() { build_native_static_lib("cfoo"); rustdoc().input("lib.rs").arg("--test").arg("-L").arg(cwd()).arg("-lstatic=cfoo").run(); + + // Merged doctests link the native library through the bundle rlib, not the runner. + rustdoc() + .input("lib.rs") + .edition("2024") + .arg("--test") + .arg("-Zunstable-options") + .arg("--merge-doctests=yes") + .arg("-L") + .arg(cwd()) + .arg("-lstatic:+whole-archive=cfoo") + .run(); }