From 8f7f9b93b2cf1f16d4ded534ff14ce9a673177c0 Mon Sep 17 00:00:00 2001
From: Zalathar <Zalathar@users.noreply.github.com>
Date: Fri, 8 Nov 2024 00:11:21 +1100
Subject: [PATCH 1/4] Add a run-make test for `rustc --help` and similar

---
 src/tools/run-make-support/src/lib.rs   |  1 +
 tests/run-make/rustc-help/help-v.diff   | 29 ++++++++++
 tests/run-make/rustc-help/help-v.stdout | 77 +++++++++++++++++++++++++
 tests/run-make/rustc-help/help.stdout   | 60 +++++++++++++++++++
 tests/run-make/rustc-help/rmake.rs      | 21 +++++++
 5 files changed, 188 insertions(+)
 create mode 100644 tests/run-make/rustc-help/help-v.diff
 create mode 100644 tests/run-make/rustc-help/help-v.stdout
 create mode 100644 tests/run-make/rustc-help/help.stdout
 create mode 100644 tests/run-make/rustc-help/rmake.rs

diff --git a/src/tools/run-make-support/src/lib.rs b/src/tools/run-make-support/src/lib.rs
index 368b98c9f0dfd..5765cb97a7ef7 100644
--- a/src/tools/run-make-support/src/lib.rs
+++ b/src/tools/run-make-support/src/lib.rs
@@ -41,6 +41,7 @@ pub use libc;
 pub use object;
 pub use regex;
 pub use serde_json;
+pub use similar;
 pub use wasmparser;
 // tidy-alphabetical-end
 
diff --git a/tests/run-make/rustc-help/help-v.diff b/tests/run-make/rustc-help/help-v.diff
new file mode 100644
index 0000000000000..22c5dd81bdb05
--- /dev/null
+++ b/tests/run-make/rustc-help/help-v.diff
@@ -0,0 +1,29 @@
+@@ -51,10 +51,27 @@
+                         Set a codegen option
+     -V, --version       Print version info and exit
+     -v, --verbose       Use verbose output
++        --extern NAME[=PATH]
++                        Specify where an external rust library is located
++        --sysroot PATH  Override the system root
++        --error-format human|json|short
++                        How errors and other messages are produced
++        --json CONFIG   Configure the JSON output of the compiler
++        --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
++        --diagnostic-width WIDTH
++                        Inform rustc of the width of the output so that
++                        diagnostics can be truncated to fit
++        --remap-path-prefix FROM=TO
++                        Remap source names in all output (compiler messages
++                        and output files)
++    @path               Read newline separated options from `path`
+ 
+ Additional help:
+     -C help             Print codegen options
+     -W help             Print 'lint' options and default settings
+     -Z help             Print unstable compiler options
+-    --help -v           Print the full set of options rustc accepts
+ 
diff --git a/tests/run-make/rustc-help/help-v.stdout b/tests/run-make/rustc-help/help-v.stdout
new file mode 100644
index 0000000000000..dbd67b57df2f0
--- /dev/null
+++ b/tests/run-make/rustc-help/help-v.stdout
@@ -0,0 +1,77 @@
+Usage: rustc [OPTIONS] INPUT
+
+Options:
+    -h, --help          Display this message
+        --cfg SPEC      Configure the compilation environment.
+                        SPEC supports the syntax `NAME[="VALUE"]`.
+        --check-cfg SPEC
+                        Provide list of expected cfgs for checking
+    -L [KIND=]PATH      Add a directory to the library search path. The
+                        optional KIND can be one of dependency, crate, native,
+                        framework, or all (the default).
+    -l [KIND[:MODIFIERS]=]NAME[:RENAME]
+                        Link the generated crate(s) to the specified native
+                        library NAME. The optional KIND can be one of
+                        static, framework, or dylib (the default).
+                        Optional comma separated MODIFIERS
+                        (bundle|verbatim|whole-archive|as-needed)
+                        may be specified each with a prefix of either '+' to
+                        enable or '-' to disable.
+        --crate-type [bin|lib|rlib|dylib|cdylib|staticlib|proc-macro]
+                        Comma separated list of types of crates
+                        for the compiler to emit
+        --crate-name NAME
+                        Specify the name of the crate being built
+        --edition 2015|2018|2021|2024
+                        Specify which edition of the compiler to use when
+                        compiling code. The default is 2015 and the latest
+                        stable edition is 2021.
+        --emit [asm|llvm-bc|llvm-ir|obj|metadata|link|dep-info|mir]
+                        Comma separated list of types of output for the
+                        compiler to emit
+        --print [crate-name|file-names|sysroot|target-libdir|cfg|check-cfg|calling-conventions|target-list|target-cpus|target-features|relocation-models|code-models|tls-models|target-spec-json|all-target-specs-json|native-static-libs|stack-protector-strategies|link-args|deployment-target]
+                        Compiler information to print on stdout
+    -g                  Equivalent to -C debuginfo=2
+    -O                  Equivalent to -C opt-level=2
+    -o FILENAME         Write output to <filename>
+        --out-dir DIR   Write output to compiler-chosen filename in <dir>
+        --explain OPT   Provide a detailed explanation of an error message
+        --test          Build a test harness
+        --target TARGET Target triple for which the code is compiled
+    -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
+    -C, --codegen OPT[=VALUE]
+                        Set a codegen option
+    -V, --version       Print version info and exit
+    -v, --verbose       Use verbose output
+        --extern NAME[=PATH]
+                        Specify where an external rust library is located
+        --sysroot PATH  Override the system root
+        --error-format human|json|short
+                        How errors and other messages are produced
+        --json CONFIG   Configure the JSON output of the compiler
+        --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
+        --diagnostic-width WIDTH
+                        Inform rustc of the width of the output so that
+                        diagnostics can be truncated to fit
+        --remap-path-prefix FROM=TO
+                        Remap source names in all output (compiler messages
+                        and output files)
+    @path               Read newline separated options from `path`
+
+Additional help:
+    -C help             Print codegen options
+    -W help             Print 'lint' options and default settings
+    -Z help             Print unstable compiler options
+
diff --git a/tests/run-make/rustc-help/help.stdout b/tests/run-make/rustc-help/help.stdout
new file mode 100644
index 0000000000000..a7d07162799b0
--- /dev/null
+++ b/tests/run-make/rustc-help/help.stdout
@@ -0,0 +1,60 @@
+Usage: rustc [OPTIONS] INPUT
+
+Options:
+    -h, --help          Display this message
+        --cfg SPEC      Configure the compilation environment.
+                        SPEC supports the syntax `NAME[="VALUE"]`.
+        --check-cfg SPEC
+                        Provide list of expected cfgs for checking
+    -L [KIND=]PATH      Add a directory to the library search path. The
+                        optional KIND can be one of dependency, crate, native,
+                        framework, or all (the default).
+    -l [KIND[:MODIFIERS]=]NAME[:RENAME]
+                        Link the generated crate(s) to the specified native
+                        library NAME. The optional KIND can be one of
+                        static, framework, or dylib (the default).
+                        Optional comma separated MODIFIERS
+                        (bundle|verbatim|whole-archive|as-needed)
+                        may be specified each with a prefix of either '+' to
+                        enable or '-' to disable.
+        --crate-type [bin|lib|rlib|dylib|cdylib|staticlib|proc-macro]
+                        Comma separated list of types of crates
+                        for the compiler to emit
+        --crate-name NAME
+                        Specify the name of the crate being built
+        --edition 2015|2018|2021|2024
+                        Specify which edition of the compiler to use when
+                        compiling code. The default is 2015 and the latest
+                        stable edition is 2021.
+        --emit [asm|llvm-bc|llvm-ir|obj|metadata|link|dep-info|mir]
+                        Comma separated list of types of output for the
+                        compiler to emit
+        --print [crate-name|file-names|sysroot|target-libdir|cfg|check-cfg|calling-conventions|target-list|target-cpus|target-features|relocation-models|code-models|tls-models|target-spec-json|all-target-specs-json|native-static-libs|stack-protector-strategies|link-args|deployment-target]
+                        Compiler information to print on stdout
+    -g                  Equivalent to -C debuginfo=2
+    -O                  Equivalent to -C opt-level=2
+    -o FILENAME         Write output to <filename>
+        --out-dir DIR   Write output to compiler-chosen filename in <dir>
+        --explain OPT   Provide a detailed explanation of an error message
+        --test          Build a test harness
+        --target TARGET Target triple for which the code is compiled
+    -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
+    -C, --codegen OPT[=VALUE]
+                        Set a codegen option
+    -V, --version       Print version info and exit
+    -v, --verbose       Use verbose output
+
+Additional help:
+    -C help             Print codegen options
+    -W help             Print 'lint' options and default settings
+    -Z help             Print unstable compiler options
+    --help -v           Print the full set of options rustc accepts
+
diff --git a/tests/run-make/rustc-help/rmake.rs b/tests/run-make/rustc-help/rmake.rs
new file mode 100644
index 0000000000000..85e90e6352d09
--- /dev/null
+++ b/tests/run-make/rustc-help/rmake.rs
@@ -0,0 +1,21 @@
+// Tests `rustc --help` and similar invocations against snapshots and each other.
+
+use run_make_support::{bare_rustc, diff, similar};
+
+fn main() {
+    // `rustc --help`
+    let help = bare_rustc().arg("--help").run().stdout_utf8();
+    diff().expected_file("help.stdout").actual_text("(rustc --help)", &help).run();
+
+    // `rustc` should be the same as `rustc --help`
+    let bare = bare_rustc().run().stdout_utf8();
+    diff().expected_text("(rustc --help)", &help).actual_text("(rustc)", &bare).run();
+
+    // `rustc --help -v` should give a similar but longer help message
+    let help_v = bare_rustc().arg("--help").arg("-v").run().stdout_utf8();
+    diff().expected_file("help-v.stdout").actual_text("(rustc --help -v)", &help_v).run();
+
+    // Check the diff between `rustc --help` and `rustc --help -v`.
+    let help_v_diff = similar::TextDiff::from_lines(&help, &help_v).unified_diff().to_string();
+    diff().expected_file("help-v.diff").actual_text("actual", &help_v_diff).run();
+}

From 584c8200de3c83078d7cbb271a16ef4962be761e Mon Sep 17 00:00:00 2001
From: Zalathar <Zalathar@users.noreply.github.com>
Date: Fri, 8 Nov 2024 12:20:51 +1100
Subject: [PATCH 2/4] Use a method to apply `RustcOptGroup` to
 `getopts::Options`

---
 compiler/rustc_driver_impl/src/lib.rs | 6 +++---
 compiler/rustc_interface/src/tests.rs | 2 +-
 compiler/rustc_session/src/config.rs  | 6 +++++-
 src/librustdoc/lib.rs                 | 4 ++--
 4 files changed, 11 insertions(+), 7 deletions(-)

diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs
index d2c4335cf2b44..78ba841d89f4b 100644
--- a/compiler/rustc_driver_impl/src/lib.rs
+++ b/compiler/rustc_driver_impl/src/lib.rs
@@ -937,7 +937,7 @@ fn usage(verbose: bool, include_unstable_options: bool, nightly_build: bool) {
     let groups = if verbose { config::rustc_optgroups() } else { config::rustc_short_optgroups() };
     let mut options = getopts::Options::new();
     for option in groups.iter().filter(|x| include_unstable_options || x.is_stable()) {
-        (option.apply)(&mut options);
+        option.apply(&mut options);
     }
     let message = "Usage: rustc [OPTIONS] INPUT";
     let nightly_help = if nightly_build {
@@ -1219,7 +1219,7 @@ pub fn handle_options(early_dcx: &EarlyDiagCtxt, args: &[String]) -> Option<geto
     let mut options = getopts::Options::new();
     let optgroups = config::rustc_optgroups();
     for option in &optgroups {
-        (option.apply)(&mut options);
+        option.apply(&mut options);
     }
     let matches = options.parse(args).unwrap_or_else(|e| {
         let msg: Option<String> = match e {
@@ -1233,7 +1233,7 @@ pub fn handle_options(early_dcx: &EarlyDiagCtxt, args: &[String]) -> Option<geto
                 optgroups.iter().find(|option| option.name == opt).map(|option| {
                     // Print the help just for the option in question.
                     let mut options = getopts::Options::new();
-                    (option.apply)(&mut options);
+                    option.apply(&mut options);
                     // getopt requires us to pass a function for joining an iterator of
                     // strings, even though in this case we expect exactly one string.
                     options.usage_with_format(|it| {
diff --git a/compiler/rustc_interface/src/tests.rs b/compiler/rustc_interface/src/tests.rs
index ce90ceeda5618..2361231b3fba1 100644
--- a/compiler/rustc_interface/src/tests.rs
+++ b/compiler/rustc_interface/src/tests.rs
@@ -102,7 +102,7 @@ where
 fn optgroups() -> getopts::Options {
     let mut opts = getopts::Options::new();
     for group in rustc_optgroups() {
-        (group.apply)(&mut opts);
+        group.apply(&mut opts);
     }
     return opts;
 }
diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs
index fe05605c1b9a1..0c4827ef54d2f 100644
--- a/compiler/rustc_session/src/config.rs
+++ b/compiler/rustc_session/src/config.rs
@@ -1373,7 +1373,7 @@ enum OptionStability {
 }
 
 pub struct RustcOptGroup {
-    pub apply: Box<dyn Fn(&mut getopts::Options) -> &mut getopts::Options>,
+    apply: Box<dyn Fn(&mut getopts::Options) -> &mut getopts::Options>,
     pub name: &'static str,
     stability: OptionStability,
 }
@@ -1383,6 +1383,10 @@ impl RustcOptGroup {
         self.stability == OptionStability::Stable
     }
 
+    pub fn apply(&self, options: &mut getopts::Options) {
+        (self.apply)(options);
+    }
+
     pub fn stable<F>(name: &'static str, f: F) -> RustcOptGroup
     where
         F: Fn(&mut getopts::Options) -> &mut getopts::Options + 'static,
diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs
index 7c9dcd41e6a06..40e649915cfca 100644
--- a/src/librustdoc/lib.rs
+++ b/src/librustdoc/lib.rs
@@ -685,7 +685,7 @@ fn opts() -> Vec<RustcOptGroup> {
 fn usage(argv0: &str) {
     let mut options = getopts::Options::new();
     for option in opts() {
-        (option.apply)(&mut options);
+        option.apply(&mut options);
     }
     println!("{}", options.usage(&format!("{argv0} [options] <input>")));
     println!("    @path               Read newline separated options from `path`\n");
@@ -769,7 +769,7 @@ fn main_args(
 
     let mut options = getopts::Options::new();
     for option in opts() {
-        (option.apply)(&mut options);
+        option.apply(&mut options);
     }
     let matches = match options.parse(&args) {
         Ok(m) => m,

From 001013c63cccb3ca88b547596cec5b891e09151a Mon Sep 17 00:00:00 2001
From: Zalathar <Zalathar@users.noreply.github.com>
Date: Thu, 7 Nov 2024 22:47:15 +1100
Subject: [PATCH 3/4] Simplify command-line-option declarations in the compiler

---
 compiler/rustc_session/src/config.rs | 264 +++++++++++++++------------
 1 file changed, 147 insertions(+), 117 deletions(-)

diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs
index 0c4827ef54d2f..979db9424f3e9 100644
--- a/compiler/rustc_session/src/config.rs
+++ b/compiler/rustc_session/src/config.rs
@@ -12,7 +12,7 @@ use std::hash::Hash;
 use std::path::{Path, PathBuf};
 use std::str::{self, FromStr};
 use std::sync::LazyLock;
-use std::{fmt, fs, iter};
+use std::{cmp, fmt, fs, iter};
 
 use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
 use rustc_data_structures::stable_hasher::{StableOrd, ToStableHashKey};
@@ -1367,11 +1367,36 @@ pub fn build_target_config(early_dcx: &EarlyDiagCtxt, opts: &Options, sysroot: &
 }
 
 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
-enum OptionStability {
+pub enum OptionStability {
     Stable,
     Unstable,
 }
 
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+pub enum OptionKind {
+    /// An option that takes a value, and cannot appear more than once (e.g. `--out-dir`).
+    ///
+    /// Corresponds to [`getopts::Options::optopt`].
+    Opt,
+
+    /// An option that takes a value, and can appear multiple times (e.g. `--emit`).
+    ///
+    /// Corresponds to [`getopts::Options::optmulti`].
+    Multi,
+
+    /// An option that does not take a value, and cannot appear more than once (e.g. `--help`).
+    ///
+    /// Corresponds to [`getopts::Options::optflag`].
+    /// The `hint` string must be empty.
+    Flag,
+
+    /// An option that does not take a value, and can appear multiple times (e.g. `-O`).
+    ///
+    /// Corresponds to [`getopts::Options::optflagmulti`].
+    /// The `hint` string must be empty.
+    FlagMulti,
+}
+
 pub struct RustcOptGroup {
     apply: Box<dyn Fn(&mut getopts::Options) -> &mut getopts::Options>,
     pub name: &'static str,
@@ -1402,58 +1427,37 @@ impl RustcOptGroup {
     }
 }
 
-// The `opt` local module holds wrappers around the `getopts` API that
-// adds extra rustc-specific metadata to each option; such metadata
-// is exposed by . The public
-// functions below ending with `_u` are the functions that return
-// *unstable* options, i.e., options that are only enabled when the
-// user also passes the `-Z unstable-options` debugging flag.
-mod opt {
-    // The `fn flag*` etc below are written so that we can use them
-    // in the future; do not warn about them not being used right now.
-    #![allow(dead_code)]
-
-    use super::RustcOptGroup;
-
-    type R = RustcOptGroup;
-    type S = &'static str;
-
-    fn stable<F>(name: S, f: F) -> R
-    where
-        F: Fn(&mut getopts::Options) -> &mut getopts::Options + 'static,
-    {
-        RustcOptGroup::stable(name, f)
-    }
-
-    fn unstable<F>(name: S, f: F) -> R
-    where
-        F: Fn(&mut getopts::Options) -> &mut getopts::Options + 'static,
-    {
-        RustcOptGroup::unstable(name, f)
-    }
-
-    fn longer(a: S, b: S) -> S {
-        if a.len() > b.len() { a } else { b }
-    }
-
-    pub(crate) fn opt_s(a: S, b: S, c: S, d: S) -> R {
-        stable(longer(a, b), move |opts| opts.optopt(a, b, c, d))
-    }
-    pub(crate) fn multi_s(a: S, b: S, c: S, d: S) -> R {
-        stable(longer(a, b), move |opts| opts.optmulti(a, b, c, d))
-    }
-    pub(crate) fn flag_s(a: S, b: S, c: S) -> R {
-        stable(longer(a, b), move |opts| opts.optflag(a, b, c))
-    }
-    pub(crate) fn flagmulti_s(a: S, b: S, c: S) -> R {
-        stable(longer(a, b), move |opts| opts.optflagmulti(a, b, c))
-    }
-
-    fn opt(a: S, b: S, c: S, d: S) -> R {
-        unstable(longer(a, b), move |opts| opts.optopt(a, b, c, d))
-    }
-    pub(crate) fn multi(a: S, b: S, c: S, d: S) -> R {
-        unstable(longer(a, b), move |opts| opts.optmulti(a, b, c, d))
+pub fn make_opt(
+    stability: OptionStability,
+    kind: OptionKind,
+    short_name: &'static str,
+    long_name: &'static str,
+    desc: &'static str,
+    hint: &'static str,
+) -> RustcOptGroup {
+    RustcOptGroup {
+        name: cmp::max_by_key(short_name, long_name, |s| s.len()),
+        stability,
+        apply: match kind {
+            OptionKind::Opt => Box::new(move |opts: &mut getopts::Options| {
+                opts.optopt(short_name, long_name, desc, hint)
+            }),
+            OptionKind::Multi => Box::new(move |opts: &mut getopts::Options| {
+                opts.optmulti(short_name, long_name, desc, hint)
+            }),
+            OptionKind::Flag => {
+                assert_eq!(hint, "");
+                Box::new(move |opts: &mut getopts::Options| {
+                    opts.optflag(short_name, long_name, desc)
+                })
+            }
+            OptionKind::FlagMulti => {
+                assert_eq!(hint, "");
+                Box::new(move |opts: &mut getopts::Options| {
+                    opts.optflagmulti(short_name, long_name, desc)
+                })
+            }
+        },
     }
 }
 
@@ -1468,46 +1472,60 @@ The default is {DEFAULT_EDITION} and the latest stable edition is {LATEST_STABLE
 /// including metadata for each option, such as whether the option is
 /// part of the stable long-term interface for rustc.
 pub fn rustc_short_optgroups() -> Vec<RustcOptGroup> {
+    use OptionKind::{Flag, FlagMulti, Multi, Opt};
+    use OptionStability::Stable;
+
+    use self::make_opt as opt;
+
     vec![
-        opt::flag_s("h", "help", "Display this message"),
-        opt::multi_s("", "cfg", "Configure the compilation environment.
-                             SPEC supports the syntax `NAME[=\"VALUE\"]`.", "SPEC"),
-        opt::multi_s("", "check-cfg", "Provide list of expected cfgs for checking", "SPEC"),
-        opt::multi_s(
+        opt(Stable, Flag, "h", "help", "Display this message", ""),
+        opt(
+            Stable,
+            Multi,
+            "",
+            "cfg",
+            "Configure the compilation environment.\n\
+                SPEC supports the syntax `NAME[=\"VALUE\"]`.",
+            "SPEC",
+        ),
+        opt(Stable, Multi, "", "check-cfg", "Provide list of expected cfgs for checking", "SPEC"),
+        opt(
+            Stable,
+            Multi,
             "L",
             "",
-            "Add a directory to the library search path. The
-                             optional KIND can be one of dependency, crate, native,
-                             framework, or all (the default).",
+            "Add a directory to the library search path. \
+                The optional KIND can be one of dependency, crate, native, framework, or all (the default).",
             "[KIND=]PATH",
         ),
-        opt::multi_s(
+        opt(
+            Stable,
+            Multi,
             "l",
             "",
-            "Link the generated crate(s) to the specified native
-                             library NAME. The optional KIND can be one of
-                             static, framework, or dylib (the default).
-                             Optional comma separated MODIFIERS (bundle|verbatim|whole-archive|as-needed)
-                             may be specified each with a prefix of either '+' to
-                             enable or '-' to disable.",
+            "Link the generated crate(s) to the specified native\n\
+                library NAME. The optional KIND can be one of\n\
+                static, framework, or dylib (the default).\n\
+                Optional comma separated MODIFIERS\n\
+                (bundle|verbatim|whole-archive|as-needed)\n\
+                may be specified each with a prefix of either '+' to\n\
+                enable or '-' to disable.",
             "[KIND[:MODIFIERS]=]NAME[:RENAME]",
         ),
         make_crate_type_option(),
-        opt::opt_s("", "crate-name", "Specify the name of the crate being built", "NAME"),
-        opt::opt_s(
-            "",
-            "edition",
-            &EDITION_STRING,
-            EDITION_NAME_LIST,
-        ),
-        opt::multi_s(
+        opt(Stable, Opt, "", "crate-name", "Specify the name of the crate being built", "NAME"),
+        opt(Stable, Opt, "", "edition", &EDITION_STRING, EDITION_NAME_LIST),
+        opt(
+            Stable,
+            Multi,
             "",
             "emit",
-            "Comma separated list of types of output for \
-             the compiler to emit",
+            "Comma separated list of types of output for the compiler to emit",
             "[asm|llvm-bc|llvm-ir|obj|metadata|link|dep-info|mir]",
         ),
-        opt::multi_s(
+        opt(
+            Stable,
+            Multi,
             "",
             "print",
             "Compiler information to print on stdout",
@@ -1516,41 +1534,36 @@ pub fn rustc_short_optgroups() -> Vec<RustcOptGroup> {
              tls-models|target-spec-json|all-target-specs-json|native-static-libs|\
              stack-protector-strategies|link-args|deployment-target]",
         ),
-        opt::flagmulti_s("g", "", "Equivalent to -C debuginfo=2"),
-        opt::flagmulti_s("O", "", "Equivalent to -C opt-level=2"),
-        opt::opt_s("o", "", "Write output to <filename>", "FILENAME"),
-        opt::opt_s(
-            "",
-            "out-dir",
-            "Write output to compiler-chosen filename \
-             in <dir>",
-            "DIR",
-        ),
-        opt::opt_s(
+        opt(Stable, FlagMulti, "g", "", "Equivalent to -C debuginfo=2", ""),
+        opt(Stable, FlagMulti, "O", "", "Equivalent to -C opt-level=2", ""),
+        opt(Stable, Opt, "o", "", "Write output to <filename>", "FILENAME"),
+        opt(Stable, Opt, "", "out-dir", "Write output to compiler-chosen filename in <dir>", "DIR"),
+        opt(
+            Stable,
+            Opt,
             "",
             "explain",
-            "Provide a detailed explanation of an error \
-             message",
+            "Provide a detailed explanation of an error message",
             "OPT",
         ),
-        opt::flag_s("", "test", "Build a test harness"),
-        opt::opt_s("", "target", "Target triple for which the code is compiled", "TARGET"),
-        opt::multi_s("A", "allow", "Set lint allowed", "LINT"),
-        opt::multi_s("W", "warn", "Set lint warnings", "LINT"),
-        opt::multi_s("", "force-warn", "Set lint force-warn", "LINT"),
-        opt::multi_s("D", "deny", "Set lint denied", "LINT"),
-        opt::multi_s("F", "forbid", "Set lint forbidden", "LINT"),
-        opt::multi_s(
+        opt(Stable, Flag, "", "test", "Build a test harness", ""),
+        opt(Stable, Opt, "", "target", "Target triple for which the code is compiled", "TARGET"),
+        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",
+            "Set the most restrictive lint level. More restrictive lints are capped at this level",
             "LEVEL",
         ),
-        opt::multi_s("C", "codegen", "Set a codegen option", "OPT[=VALUE]"),
-        opt::flag_s("V", "version", "Print version info and exit"),
-        opt::flag_s("v", "verbose", "Use verbose output"),
+        opt(Stable, Multi, "C", "codegen", "Set a codegen option", "OPT[=VALUE]"),
+        opt(Stable, Flag, "V", "version", "Print version info and exit", ""),
+        opt(Stable, Flag, "v", "verbose", "Use verbose output", ""),
     ]
 }
 
@@ -1558,25 +1571,36 @@ pub fn rustc_short_optgroups() -> Vec<RustcOptGroup> {
 /// each option, such as whether the option is part of the stable
 /// long-term interface for rustc.
 pub fn rustc_optgroups() -> Vec<RustcOptGroup> {
+    use OptionKind::{Multi, Opt};
+    use OptionStability::{Stable, Unstable};
+
+    use self::make_opt as opt;
+
     let mut opts = rustc_short_optgroups();
     // FIXME: none of these descriptions are actually used
     opts.extend(vec![
-        opt::multi_s(
+        opt(
+            Stable,
+            Multi,
             "",
             "extern",
             "Specify where an external rust library is located",
             "NAME[=PATH]",
         ),
-        opt::opt_s("", "sysroot", "Override the system root", "PATH"),
-        opt::multi("Z", "", "Set unstable / perma-unstable options", "FLAG"),
-        opt::opt_s(
+        opt(Stable, Opt, "", "sysroot", "Override the system root", "PATH"),
+        opt(Unstable, Multi, "Z", "", "Set unstable / perma-unstable options", "FLAG"),
+        opt(
+            Stable,
+            Opt,
             "",
             "error-format",
             "How errors and other messages are produced",
             "human|json|short",
         ),
-        opt::multi_s("", "json", "Configure the JSON output of the compiler", "CONFIG"),
-        opt::opt_s(
+        opt(Stable, Multi, "", "json", "Configure the JSON output of the compiler", "CONFIG"),
+        opt(
+            Stable,
+            Opt,
             "",
             "color",
             "Configure coloring of output:
@@ -1585,19 +1609,23 @@ pub fn rustc_optgroups() -> Vec<RustcOptGroup> {
                                  never  = never colorize output",
             "auto|always|never",
         ),
-        opt::opt_s(
+        opt(
+            Stable,
+            Opt,
             "",
             "diagnostic-width",
             "Inform rustc of the width of the output so that diagnostics can be truncated to fit",
             "WIDTH",
         ),
-        opt::multi_s(
+        opt(
+            Stable,
+            Multi,
             "",
             "remap-path-prefix",
             "Remap source names in all output (compiler messages and output files)",
             "FROM=TO",
         ),
-        opt::multi("", "env-set", "Inject an environment variable", "VAR=VALUE"),
+        opt(Unstable, Multi, "", "env-set", "Inject an environment variable", "VAR=VALUE"),
     ]);
     opts
 }
@@ -2760,7 +2788,9 @@ fn parse_pretty(early_dcx: &EarlyDiagCtxt, unstable_opts: &UnstableOptions) -> O
 }
 
 pub fn make_crate_type_option() -> RustcOptGroup {
-    opt::multi_s(
+    make_opt(
+        OptionStability::Stable,
+        OptionKind::Multi,
         "",
         "crate-type",
         "Comma separated list of types of crates

From b8377e58445fe5488664bcbbedd04a686cfdad03 Mon Sep 17 00:00:00 2001
From: Zalathar <Zalathar@users.noreply.github.com>
Date: Thu, 7 Nov 2024 23:27:20 +1100
Subject: [PATCH 4/4] Simplify command-line-argument declarations in librustdoc

---
 compiler/rustc_session/src/config.rs |  14 -
 src/librustdoc/lib.rs                | 921 ++++++++++++++-------------
 2 files changed, 463 insertions(+), 472 deletions(-)

diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs
index 979db9424f3e9..fa2403db92551 100644
--- a/compiler/rustc_session/src/config.rs
+++ b/compiler/rustc_session/src/config.rs
@@ -1411,20 +1411,6 @@ impl RustcOptGroup {
     pub fn apply(&self, options: &mut getopts::Options) {
         (self.apply)(options);
     }
-
-    pub fn stable<F>(name: &'static str, f: F) -> RustcOptGroup
-    where
-        F: Fn(&mut getopts::Options) -> &mut getopts::Options + 'static,
-    {
-        RustcOptGroup { name, apply: Box::new(f), stability: OptionStability::Stable }
-    }
-
-    pub fn unstable<F>(name: &'static str, f: F) -> RustcOptGroup
-    where
-        F: Fn(&mut getopts::Options) -> &mut getopts::Options + 'static,
-    {
-        RustcOptGroup { name, apply: Box::new(f), stability: OptionStability::Unstable }
-    }
 }
 
 pub fn make_opt(
diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs
index 40e649915cfca..78dc0b8e2f98a 100644
--- a/src/librustdoc/lib.rs
+++ b/src/librustdoc/lib.rs
@@ -215,470 +215,475 @@ fn init_logging(early_dcx: &EarlyDiagCtxt) {
 }
 
 fn opts() -> Vec<RustcOptGroup> {
-    let stable: fn(_, fn(&mut getopts::Options) -> &mut _) -> _ = RustcOptGroup::stable;
-    let unstable: fn(_, fn(&mut getopts::Options) -> &mut _) -> _ = RustcOptGroup::unstable;
+    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![
-        stable("h", |o| o.optflagmulti("h", "help", "show this help message")),
-        stable("V", |o| o.optflagmulti("V", "version", "print rustdoc's version")),
-        stable("v", |o| o.optflagmulti("v", "verbose", "use verbose output")),
-        stable("w", |o| o.optopt("w", "output-format", "the output type to write", "[html]")),
-        stable("output", |o| {
-            o.optopt(
-                "",
-                "output",
-                "Which directory to place the output. \
-                 This option is deprecated, use --out-dir instead.",
-                "PATH",
-            )
-        }),
-        stable("o", |o| o.optopt("o", "out-dir", "which directory to place the output", "PATH")),
-        stable("crate-name", |o| {
-            o.optopt("", "crate-name", "specify the name of this crate", "NAME")
-        }),
+        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", ""),
+        opt(Stable, Opt, "w", "output-format", "the output type to write", "[html]"),
+        opt(
+            Stable,
+            Opt,
+            "",
+            "output",
+            "Which directory to place the output. This option is deprecated, use --out-dir instead.",
+            "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(),
-        stable("L", |o| {
-            o.optmulti("L", "library-path", "directory to add to crate search path", "DIR")
-        }),
-        stable("cfg", |o| o.optmulti("", "cfg", "pass a --cfg to rustc", "")),
-        stable("check-cfg", |o| o.optmulti("", "check-cfg", "pass a --check-cfg to rustc", "")),
-        stable("extern", |o| o.optmulti("", "extern", "pass an --extern to rustc", "NAME[=PATH]")),
-        unstable("extern-html-root-url", |o| {
-            o.optmulti(
-                "",
-                "extern-html-root-url",
-                "base URL to use for dependencies; for example, \
-                 \"std=/doc\" links std::vec::Vec to /doc/std/vec/struct.Vec.html",
-                "NAME=URL",
-            )
-        }),
-        unstable("extern-html-root-takes-precedence", |o| {
-            o.optflagmulti(
-                "",
-                "extern-html-root-takes-precedence",
-                "give precedence to `--extern-html-root-url`, not `html_root_url`",
-            )
-        }),
-        stable("C", |o| {
-            o.optmulti("C", "codegen", "pass a codegen option to rustc", "OPT[=VALUE]")
-        }),
-        stable("document-private-items", |o| {
-            o.optflagmulti("", "document-private-items", "document private items")
-        }),
-        unstable("document-hidden-items", |o| {
-            o.optflagmulti("", "document-hidden-items", "document items that have doc(hidden)")
-        }),
-        stable("test", |o| o.optflagmulti("", "test", "run code examples as tests")),
-        stable("test-args", |o| {
-            o.optmulti("", "test-args", "arguments to pass to the test runner", "ARGS")
-        }),
-        stable("test-run-directory", |o| {
-            o.optopt(
-                "",
-                "test-run-directory",
-                "The working directory in which to run tests",
-                "PATH",
-            )
-        }),
-        stable("target", |o| o.optopt("", "target", "target triple to document", "TRIPLE")),
-        stable("markdown-css", |o| {
-            o.optmulti(
-                "",
-                "markdown-css",
-                "CSS files to include via <link> in a rendered Markdown file",
-                "FILES",
-            )
-        }),
-        stable("html-in-header", |o| {
-            o.optmulti(
-                "",
-                "html-in-header",
-                "files to include inline in the <head> section of a rendered Markdown file \
-                 or generated documentation",
-                "FILES",
-            )
-        }),
-        stable("html-before-content", |o| {
-            o.optmulti(
-                "",
-                "html-before-content",
-                "files to include inline between <body> and the content of a rendered \
-                 Markdown file or generated documentation",
-                "FILES",
-            )
-        }),
-        stable("html-after-content", |o| {
-            o.optmulti(
-                "",
-                "html-after-content",
-                "files to include inline between the content and </body> of a rendered \
-                 Markdown file or generated documentation",
-                "FILES",
-            )
-        }),
-        unstable("markdown-before-content", |o| {
-            o.optmulti(
-                "",
-                "markdown-before-content",
-                "files to include inline between <body> and the content of a rendered \
-                 Markdown file or generated documentation",
-                "FILES",
-            )
-        }),
-        unstable("markdown-after-content", |o| {
-            o.optmulti(
-                "",
-                "markdown-after-content",
-                "files to include inline between the content and </body> of a rendered \
-                 Markdown file or generated documentation",
-                "FILES",
-            )
-        }),
-        stable("markdown-playground-url", |o| {
-            o.optopt("", "markdown-playground-url", "URL to send code snippets to", "URL")
-        }),
-        stable("markdown-no-toc", |o| {
-            o.optflagmulti("", "markdown-no-toc", "don't include table of contents")
-        }),
-        stable("e", |o| {
-            o.optopt(
-                "e",
-                "extend-css",
-                "To add some CSS rules with a given file to generate doc with your \
-                 own theme. However, your theme might break if the rustdoc's generated HTML \
-                 changes, so be careful!",
-                "PATH",
-            )
-        }),
-        unstable("Z", |o| {
-            o.optmulti("Z", "", "unstable / perma-unstable options (only on nightly build)", "FLAG")
-        }),
-        stable("sysroot", |o| o.optopt("", "sysroot", "Override the system root", "PATH")),
-        unstable("playground-url", |o| {
-            o.optopt(
-                "",
-                "playground-url",
-                "URL to send code snippets to, may be reset by --markdown-playground-url \
-                 or `#![doc(html_playground_url=...)]`",
-                "URL",
-            )
-        }),
-        unstable("display-doctest-warnings", |o| {
-            o.optflagmulti(
-                "",
-                "display-doctest-warnings",
-                "show warnings that originate in doctests",
-            )
-        }),
-        stable("crate-version", |o| {
-            o.optopt("", "crate-version", "crate version to print into documentation", "VERSION")
-        }),
-        unstable("sort-modules-by-appearance", |o| {
-            o.optflagmulti(
-                "",
-                "sort-modules-by-appearance",
-                "sort modules by where they appear in the program, rather than alphabetically",
-            )
-        }),
-        stable("default-theme", |o| {
-            o.optopt(
-                "",
-                "default-theme",
-                "Set the default theme. THEME should be the theme name, generally lowercase. \
-                 If an unknown default theme is specified, the builtin default is used. \
-                 The set of themes, and the rustdoc built-in default, are not stable.",
-                "THEME",
-            )
-        }),
-        unstable("default-setting", |o| {
-            o.optmulti(
-                "",
-                "default-setting",
-                "Default value for a rustdoc setting (used when \"rustdoc-SETTING\" is absent \
-                 from web browser Local Storage). If VALUE is not supplied, \"true\" is used. \
-                 Supported SETTINGs and VALUEs are not documented and not stable.",
-                "SETTING[=VALUE]",
-            )
-        }),
-        stable("theme", |o| {
-            o.optmulti(
-                "",
-                "theme",
-                "additional themes which will be added to the generated docs",
-                "FILES",
-            )
-        }),
-        stable("check-theme", |o| {
-            o.optmulti("", "check-theme", "check if given theme is valid", "FILES")
-        }),
-        unstable("resource-suffix", |o| {
-            o.optopt(
-                "",
-                "resource-suffix",
-                "suffix to add to CSS and JavaScript files, e.g., \"search-index.js\" will \
-                 become \"search-index-suffix.js\"",
-                "PATH",
-            )
-        }),
-        stable("edition", |o| {
-            o.optopt(
-                "",
-                "edition",
-                "edition to use when compiling rust code (default: 2015)",
-                "EDITION",
-            )
-        }),
-        stable("color", |o| {
-            o.optopt(
-                "",
-                "color",
-                "Configure coloring of output:
+        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(
+            Unstable,
+            Multi,
+            "",
+            "extern-html-root-url",
+            "base URL to use for dependencies; for example, \
+                \"std=/doc\" links std::vec::Vec to /doc/std/vec/struct.Vec.html",
+            "NAME=URL",
+        ),
+        opt(
+            Unstable,
+            FlagMulti,
+            "",
+            "extern-html-root-takes-precedence",
+            "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,
+            FlagMulti,
+            "",
+            "document-hidden-items",
+            "document items that have doc(hidden)",
+            "",
+        ),
+        opt(Stable, FlagMulti, "", "test", "run code examples as tests", ""),
+        opt(Stable, Multi, "", "test-args", "arguments to pass to the test runner", "ARGS"),
+        opt(
+            Stable,
+            Opt,
+            "",
+            "test-run-directory",
+            "The working directory in which to run tests",
+            "PATH",
+        ),
+        opt(Stable, Opt, "", "target", "target triple to document", "TRIPLE"),
+        opt(
+            Stable,
+            Multi,
+            "",
+            "markdown-css",
+            "CSS files to include via <link> in a rendered Markdown file",
+            "FILES",
+        ),
+        opt(
+            Stable,
+            Multi,
+            "",
+            "html-in-header",
+            "files to include inline in the <head> section of a rendered Markdown file \
+                or generated documentation",
+            "FILES",
+        ),
+        opt(
+            Stable,
+            Multi,
+            "",
+            "html-before-content",
+            "files to include inline between <body> and the content of a rendered \
+                Markdown file or generated documentation",
+            "FILES",
+        ),
+        opt(
+            Stable,
+            Multi,
+            "",
+            "html-after-content",
+            "files to include inline between the content and </body> of a rendered \
+                Markdown file or generated documentation",
+            "FILES",
+        ),
+        opt(
+            Unstable,
+            Multi,
+            "",
+            "markdown-before-content",
+            "files to include inline between <body> and the content of a rendered \
+                Markdown file or generated documentation",
+            "FILES",
+        ),
+        opt(
+            Unstable,
+            Multi,
+            "",
+            "markdown-after-content",
+            "files to include inline between the content and </body> of a rendered \
+                Markdown file or generated documentation",
+            "FILES",
+        ),
+        opt(Stable, Opt, "", "markdown-playground-url", "URL to send code snippets to", "URL"),
+        opt(Stable, FlagMulti, "", "markdown-no-toc", "don't include table of contents", ""),
+        opt(
+            Stable,
+            Opt,
+            "e",
+            "extend-css",
+            "To add some CSS rules with a given file to generate doc with your own theme. \
+                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,
+            "",
+            "playground-url",
+            "URL to send code snippets to, may be reset by --markdown-playground-url \
+                or `#![doc(html_playground_url=...)]`",
+            "URL",
+        ),
+        opt(
+            Unstable,
+            FlagMulti,
+            "",
+            "display-doctest-warnings",
+            "show warnings that originate in doctests",
+            "",
+        ),
+        opt(
+            Stable,
+            Opt,
+            "",
+            "crate-version",
+            "crate version to print into documentation",
+            "VERSION",
+        ),
+        opt(
+            Unstable,
+            FlagMulti,
+            "",
+            "sort-modules-by-appearance",
+            "sort modules by where they appear in the program, rather than alphabetically",
+            "",
+        ),
+        opt(
+            Stable,
+            Opt,
+            "",
+            "default-theme",
+            "Set the default theme. THEME should be the theme name, generally lowercase. \
+                If an unknown default theme is specified, the builtin default is used. \
+                The set of themes, and the rustdoc built-in default, are not stable.",
+            "THEME",
+        ),
+        opt(
+            Unstable,
+            Multi,
+            "",
+            "default-setting",
+            "Default value for a rustdoc setting (used when \"rustdoc-SETTING\" is absent \
+                from web browser Local Storage). If VALUE is not supplied, \"true\" is used. \
+                Supported SETTINGs and VALUEs are not documented and not stable.",
+            "SETTING[=VALUE]",
+        ),
+        opt(
+            Stable,
+            Multi,
+            "",
+            "theme",
+            "additional themes which will be added to the generated docs",
+            "FILES",
+        ),
+        opt(Stable, Multi, "", "check-theme", "check if given theme is valid", "FILES"),
+        opt(
+            Unstable,
+            Opt,
+            "",
+            "resource-suffix",
+            "suffix to add to CSS and JavaScript files, \
+                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",
-            )
-        }),
-        stable("error-format", |o| {
-            o.optopt(
-                "",
-                "error-format",
-                "How errors and other messages are produced",
-                "human|json|short",
-            )
-        }),
-        stable("diagnostic-width", |o| {
-            o.optopt(
-                "",
-                "diagnostic-width",
-                "Provide width of the output for truncated error messages",
-                "WIDTH",
-            )
-        }),
-        stable("json", |o| {
-            o.optopt("", "json", "Configure the structure of JSON diagnostics", "CONFIG")
-        }),
-        stable("allow", |o| o.optmulti("A", "allow", "Set lint allowed", "LINT")),
-        stable("warn", |o| o.optmulti("W", "warn", "Set lint warnings", "LINT")),
-        stable("force-warn", |o| o.optmulti("", "force-warn", "Set lint force-warn", "LINT")),
-        stable("deny", |o| o.optmulti("D", "deny", "Set lint denied", "LINT")),
-        stable("forbid", |o| o.optmulti("F", "forbid", "Set lint forbidden", "LINT")),
-        stable("cap-lints", |o| {
-            o.optmulti(
-                "",
-                "cap-lints",
-                "Set the most restrictive lint level. \
-                 More restrictive lints are capped at this \
-                 level. By default, it is at `forbid` level.",
-                "LEVEL",
-            )
-        }),
-        unstable("index-page", |o| {
-            o.optopt("", "index-page", "Markdown file to be used as index page", "PATH")
-        }),
-        unstable("enable-index-page", |o| {
-            o.optflagmulti("", "enable-index-page", "To enable generation of the index page")
-        }),
-        unstable("static-root-path", |o| {
-            o.optopt(
-                "",
-                "static-root-path",
-                "Path string to force loading static files from in output pages. \
-                 If not set, uses combinations of '../' to reach the documentation root.",
-                "PATH",
-            )
-        }),
-        unstable("persist-doctests", |o| {
-            o.optopt(
-                "",
-                "persist-doctests",
-                "Directory to persist doctest executables into",
-                "PATH",
-            )
-        }),
-        unstable("show-coverage", |o| {
-            o.optflagmulti(
-                "",
-                "show-coverage",
-                "calculate percentage of public items with documentation",
-            )
-        }),
-        unstable("enable-per-target-ignores", |o| {
-            o.optflagmulti(
-                "",
-                "enable-per-target-ignores",
-                "parse ignore-foo for ignoring doctests on a per-target basis",
-            )
-        }),
-        unstable("runtool", |o| {
-            o.optopt(
-                "",
-                "runtool",
-                "",
-                "The tool to run tests with when building for a different target than host",
-            )
-        }),
-        unstable("runtool-arg", |o| {
-            o.optmulti(
-                "",
-                "runtool-arg",
-                "",
-                "One (of possibly many) arguments to pass to the runtool",
-            )
-        }),
-        unstable("test-builder", |o| {
-            o.optopt("", "test-builder", "The rustc-like binary to use as the test builder", "PATH")
-        }),
-        unstable("test-builder-wrapper", |o| {
-            o.optmulti(
-                "",
-                "test-builder-wrapper",
-                "Wrapper program to pass test-builder and arguments",
-                "PATH",
-            )
-        }),
-        unstable("check", |o| o.optflagmulti("", "check", "Run rustdoc checks")),
-        unstable("generate-redirect-map", |o| {
-            o.optflagmulti(
-                "",
-                "generate-redirect-map",
-                "Generate JSON file at the top level instead of generating HTML redirection files",
-            )
-        }),
-        unstable("emit", |o| {
-            o.optmulti(
-                "",
-                "emit",
-                "Comma separated list of types of output for rustdoc to emit",
-                "[unversioned-shared-resources,toolchain-shared-resources,invocation-specific]",
-            )
-        }),
-        unstable("no-run", |o| {
-            o.optflagmulti("", "no-run", "Compile doctests without running them")
-        }),
-        unstable("remap-path-prefix", |o| {
-            o.optmulti(
-                "",
-                "remap-path-prefix",
-                "Remap source names in compiler messages",
-                "FROM=TO",
-            )
-        }),
-        unstable("show-type-layout", |o| {
-            o.optflagmulti("", "show-type-layout", "Include the memory layout of types in the docs")
-        }),
-        unstable("nocapture", |o| {
-            o.optflag("", "nocapture", "Don't capture stdout and stderr of tests")
-        }),
-        unstable("generate-link-to-definition", |o| {
-            o.optflag(
-                "",
-                "generate-link-to-definition",
-                "Make the identifiers in the HTML source code pages navigable",
-            )
-        }),
-        unstable("scrape-examples-output-path", |o| {
-            o.optopt(
-                "",
-                "scrape-examples-output-path",
-                "",
-                "collect function call information and output at the given path",
-            )
-        }),
-        unstable("scrape-examples-target-crate", |o| {
-            o.optmulti(
-                "",
-                "scrape-examples-target-crate",
-                "",
-                "collect function call information for functions from the target crate",
-            )
-        }),
-        unstable("scrape-tests", |o| {
-            o.optflag("", "scrape-tests", "Include test code when scraping examples")
-        }),
-        unstable("with-examples", |o| {
-            o.optmulti(
-                "",
-                "with-examples",
-                "",
-                "path to function call information (for displaying examples in the documentation)",
-            )
-        }),
-        unstable("merge", |o| {
-            o.optopt(
-                "",
-                "merge",
-                "Controls how rustdoc handles files from previously documented crates in the doc root
-                      none = Do not write cross-crate information to the --out-dir
-                      shared = Append current crate's info to files found in the --out-dir
-                      finalize = Write current crate's info and --include-parts-dir info to the --out-dir, overwriting conflicting files",
-                "none|shared|finalize",
-            )
-        }),
-        unstable("parts-out-dir", |o| {
-            o.optopt(
-                "",
-                "parts-out-dir",
-                "Writes trait implementations and other info for the current crate to provided path. Only use with --merge=none",
-                "path/to/doc.parts/<crate-name>",
-            )
-        }),
-        unstable("include-parts-dir", |o| {
-            o.optmulti(
-                "",
-                "include-parts-dir",
-                "Includes trait implementations and other crate info from provided path. Only use with --merge=finalize",
-                "path/to/doc.parts/<crate-name>",
-            )
-        }),
+            "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(Unstable, Opt, "", "index-page", "Markdown file to be used as index page", "PATH"),
+        opt(
+            Unstable,
+            FlagMulti,
+            "",
+            "enable-index-page",
+            "To enable generation of the index page",
+            "",
+        ),
+        opt(
+            Unstable,
+            Opt,
+            "",
+            "static-root-path",
+            "Path string to force loading static files from in output pages. \
+                If not set, uses combinations of '../' to reach the documentation root.",
+            "PATH",
+        ),
+        opt(
+            Unstable,
+            Opt,
+            "",
+            "persist-doctests",
+            "Directory to persist doctest executables into",
+            "PATH",
+        ),
+        opt(
+            Unstable,
+            FlagMulti,
+            "",
+            "show-coverage",
+            "calculate percentage of public items with documentation",
+            "",
+        ),
+        opt(
+            Unstable,
+            FlagMulti,
+            "",
+            "enable-per-target-ignores",
+            "parse ignore-foo for ignoring doctests on a per-target basis",
+            "",
+        ),
+        opt(
+            Unstable,
+            Opt,
+            "",
+            "runtool",
+            "",
+            "The tool to run tests with when building for a different target than host",
+        ),
+        opt(
+            Unstable,
+            Multi,
+            "",
+            "runtool-arg",
+            "",
+            "One (of possibly many) arguments to pass to the runtool",
+        ),
+        opt(
+            Unstable,
+            Opt,
+            "",
+            "test-builder",
+            "The rustc-like binary to use as the test builder",
+            "PATH",
+        ),
+        opt(
+            Unstable,
+            Multi,
+            "",
+            "test-builder-wrapper",
+            "Wrapper program to pass test-builder and arguments",
+            "PATH",
+        ),
+        opt(Unstable, FlagMulti, "", "check", "Run rustdoc checks", ""),
+        opt(
+            Unstable,
+            FlagMulti,
+            "",
+            "generate-redirect-map",
+            "Generate JSON file at the top level instead of generating HTML redirection files",
+            "",
+        ),
+        opt(
+            Unstable,
+            Multi,
+            "",
+            "emit",
+            "Comma separated list of types of output for rustdoc to emit",
+            "[unversioned-shared-resources,toolchain-shared-resources,invocation-specific]",
+        ),
+        opt(Unstable, FlagMulti, "", "no-run", "Compile doctests without running them", ""),
+        opt(
+            Unstable,
+            Multi,
+            "",
+            "remap-path-prefix",
+            "Remap source names in compiler messages",
+            "FROM=TO",
+        ),
+        opt(
+            Unstable,
+            FlagMulti,
+            "",
+            "show-type-layout",
+            "Include the memory layout of types in the docs",
+            "",
+        ),
+        opt(Unstable, Flag, "", "nocapture", "Don't capture stdout and stderr of tests", ""),
+        opt(
+            Unstable,
+            Flag,
+            "",
+            "generate-link-to-definition",
+            "Make the identifiers in the HTML source code pages navigable",
+            "",
+        ),
+        opt(
+            Unstable,
+            Opt,
+            "",
+            "scrape-examples-output-path",
+            "",
+            "collect function call information and output at the given path",
+        ),
+        opt(
+            Unstable,
+            Multi,
+            "",
+            "scrape-examples-target-crate",
+            "",
+            "collect function call information for functions from the target crate",
+        ),
+        opt(Unstable, Flag, "", "scrape-tests", "Include test code when scraping examples", ""),
+        opt(
+            Unstable,
+            Multi,
+            "",
+            "with-examples",
+            "",
+            "path to function call information (for displaying examples in the documentation)",
+        ),
+        opt(
+            Unstable,
+            Opt,
+            "",
+            "merge",
+            "Controls how rustdoc handles files from previously documented crates in the doc root\n\
+                none = Do not write cross-crate information to the --out-dir\n\
+                shared = Append current crate's info to files found in the --out-dir\n\
+                finalize = Write current crate's info and --include-parts-dir info to the --out-dir, overwriting conflicting files",
+            "none|shared|finalize",
+        ),
+        opt(
+            Unstable,
+            Opt,
+            "",
+            "parts-out-dir",
+            "Writes trait implementations and other info for the current crate to provided path. Only use with --merge=none",
+            "path/to/doc.parts/<crate-name>",
+        ),
+        opt(
+            Unstable,
+            Multi,
+            "",
+            "include-parts-dir",
+            "Includes trait implementations and other crate info from provided path. Only use with --merge=finalize",
+            "path/to/doc.parts/<crate-name>",
+        ),
         // deprecated / removed options
-        unstable("disable-minification", |o| o.optflagmulti("", "disable-minification", "removed")),
-        stable("plugin-path", |o| {
-            o.optmulti(
-                "",
-                "plugin-path",
-                "removed, see issue #44136 <https://github.com/rust-lang/rust/issues/44136> \
-                for more information",
-                "DIR",
-            )
-        }),
-        stable("passes", |o| {
-            o.optmulti(
-                "",
-                "passes",
-                "removed, see issue #44136 <https://github.com/rust-lang/rust/issues/44136> \
-                for more information",
-                "PASSES",
-            )
-        }),
-        stable("plugins", |o| {
-            o.optmulti(
-                "",
-                "plugins",
-                "removed, see issue #44136 <https://github.com/rust-lang/rust/issues/44136> \
-                for more information",
-                "PLUGINS",
-            )
-        }),
-        stable("no-default", |o| {
-            o.optflagmulti(
-                "",
-                "no-defaults",
-                "removed, see issue #44136 <https://github.com/rust-lang/rust/issues/44136> \
-                for more information",
-            )
-        }),
-        stable("r", |o| {
-            o.optopt(
-                "r",
-                "input-format",
-                "removed, see issue #44136 <https://github.com/rust-lang/rust/issues/44136> \
-                for more information",
-                "[rust]",
-            )
-        }),
-        unstable("html-no-source", |o| {
-            o.optflag("", "html-no-source", "Disable HTML source code pages generation")
-        }),
+        opt(Unstable, FlagMulti, "", "disable-minification", "removed", ""),
+        opt(
+            Stable,
+            Multi,
+            "",
+            "plugin-path",
+            "removed, see issue #44136 <https://github.com/rust-lang/rust/issues/44136> for more information",
+            "DIR",
+        ),
+        opt(
+            Stable,
+            Multi,
+            "",
+            "passes",
+            "removed, see issue #44136 <https://github.com/rust-lang/rust/issues/44136> for more information",
+            "PASSES",
+        ),
+        opt(
+            Stable,
+            Multi,
+            "",
+            "plugins",
+            "removed, see issue #44136 <https://github.com/rust-lang/rust/issues/44136> for more information",
+            "PLUGINS",
+        ),
+        opt(
+            Stable,
+            FlagMulti,
+            "",
+            "no-defaults",
+            "removed, see issue #44136 <https://github.com/rust-lang/rust/issues/44136> for more information",
+            "",
+        ),
+        opt(
+            Stable,
+            Opt,
+            "r",
+            "input-format",
+            "removed, see issue #44136 <https://github.com/rust-lang/rust/issues/44136> for more information",
+            "[rust]",
+        ),
+        opt(Unstable, Flag, "", "html-no-source", "Disable HTML source code pages generation", ""),
     ]
 }