about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_codegen_ssa/src/back/link.rs17
-rw-r--r--compiler/rustc_driver_impl/src/lib.rs6
-rw-r--r--compiler/rustc_interface/src/tests.rs2
-rw-r--r--compiler/rustc_lexer/src/lib.rs12
-rw-r--r--compiler/rustc_middle/src/ty/sty.rs1
-rw-r--r--compiler/rustc_parse/messages.ftl2
-rw-r--r--compiler/rustc_parse/src/errors.rs8
-rw-r--r--compiler/rustc_parse/src/lexer/mod.rs14
-rw-r--r--compiler/rustc_session/src/config.rs280
-rw-r--r--src/bootstrap/defaults/config.dist.toml1
-rw-r--r--src/bootstrap/defaults/config.library.toml3
-rw-r--r--src/bootstrap/defaults/config.tools.toml5
-rw-r--r--src/bootstrap/src/core/config/config.rs55
-rw-r--r--src/bootstrap/src/core/config/tests.rs3
-rw-r--r--src/librustdoc/lib.rs925
-rw-r--r--src/tools/run-make-support/src/lib.rs1
-rw-r--r--tests/run-make/rustc-help/help-v.diff29
-rw-r--r--tests/run-make/rustc-help/help-v.stdout77
-rw-r--r--tests/run-make/rustc-help/help.stdout60
-rw-r--r--tests/run-make/rustc-help/rmake.rs21
-rw-r--r--tests/rustdoc/hidden-implementors-90781.rs78
-rw-r--r--tests/ui/lifetimes/raw/immediately-followed-by-lt.rs14
-rw-r--r--tests/ui/lifetimes/raw/immediately-followed-by-lt.stderr13
-rw-r--r--tests/ui/lifetimes/raw/raw-lt-invalid-raw-id.rs20
-rw-r--r--tests/ui/lifetimes/raw/raw-lt-invalid-raw-id.stderr32
-rw-r--r--tests/ui/mismatched_types/similar_paths_primitive.rs4
-rw-r--r--tests/ui/mismatched_types/similar_paths_primitive.stderr27
27 files changed, 1059 insertions, 651 deletions
diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs
index 8f754debaf0..3120b5bf0af 100644
--- a/compiler/rustc_codegen_ssa/src/back/link.rs
+++ b/compiler/rustc_codegen_ssa/src/back/link.rs
@@ -3305,23 +3305,6 @@ fn add_lld_args(
     let self_contained_cli = sess.opts.cg.link_self_contained.is_linker_enabled();
     let self_contained_target = self_contained_components.is_linker_enabled();
 
-    // FIXME: in the future, codegen backends may need to have more control over this process: they
-    // don't always support all the features the linker expects here, and vice versa. For example,
-    // at the time of writing this, lld expects a newer style of aarch64 TLS relocations that
-    // cranelift doesn't implement yet. That in turn can impact whether linking would succeed on
-    // such a target when using the `cg_clif` backend and lld.
-    //
-    // Until interactions between backends and linker features are expressible, we limit target
-    // specs to opt-in to lld only when we're on the llvm backend, where it's expected to work and
-    // tested on CI. As usual, the CLI still has precedence over this, so that users and developers
-    // can still override this default when needed (e.g. for tests).
-    let uses_llvm_backend =
-        matches!(sess.opts.unstable_opts.codegen_backend.as_deref(), None | Some("llvm"));
-    if !uses_llvm_backend && !self_contained_cli && sess.opts.cg.linker_flavor.is_none() {
-        // We bail if we're not using llvm and lld was not explicitly requested on the CLI.
-        return;
-    }
-
     let self_contained_linker = self_contained_cli || self_contained_target;
     if self_contained_linker && !sess.opts.cg.link_self_contained.is_linker_disabled() {
         let mut linker_path_exists = false;
diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs
index d2c4335cf2b..78ba841d89f 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 ce90ceeda56..2361231b3fb 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_lexer/src/lib.rs b/compiler/rustc_lexer/src/lib.rs
index b0ab50dd773..f9f2a14dbd2 100644
--- a/compiler/rustc_lexer/src/lib.rs
+++ b/compiler/rustc_lexer/src/lib.rs
@@ -715,7 +715,17 @@ impl Cursor<'_> {
             self.bump();
             self.bump();
             self.eat_while(is_id_continue);
-            return RawLifetime;
+            match self.first() {
+                '\'' => {
+                    // Check if after skipping literal contents we've met a closing
+                    // single quote (which means that user attempted to create a
+                    // string with single quotes).
+                    self.bump();
+                    let kind = Char { terminated: true };
+                    return Literal { kind, suffix_start: self.pos_within_token() };
+                }
+                _ => return RawLifetime,
+            }
         }
 
         // Either a lifetime or a character literal with
diff --git a/compiler/rustc_middle/src/ty/sty.rs b/compiler/rustc_middle/src/ty/sty.rs
index e27bb2fd135..142db8a17f0 100644
--- a/compiler/rustc_middle/src/ty/sty.rs
+++ b/compiler/rustc_middle/src/ty/sty.rs
@@ -1933,6 +1933,7 @@ impl<'tcx> Ty<'tcx> {
                 ty::UintTy::U64 => Some(sym::u64),
                 ty::UintTy::U128 => Some(sym::u128),
             },
+            ty::Str => Some(sym::str),
             _ => None,
         }
     }
diff --git a/compiler/rustc_parse/messages.ftl b/compiler/rustc_parse/messages.ftl
index 1af7fb9b86a..ef259703f0c 100644
--- a/compiler/rustc_parse/messages.ftl
+++ b/compiler/rustc_parse/messages.ftl
@@ -77,6 +77,8 @@ parse_box_syntax_removed_suggestion = use `Box::new()` instead
 
 parse_cannot_be_raw_ident = `{$ident}` cannot be a raw identifier
 
+parse_cannot_be_raw_lifetime = `{$ident}` cannot be a raw lifetime
+
 parse_catch_after_try = keyword `catch` cannot follow a `try` block
     .help = try using `match` on the result of the `try` block instead
 
diff --git a/compiler/rustc_parse/src/errors.rs b/compiler/rustc_parse/src/errors.rs
index fdd500e90f8..7ec4ad6dc35 100644
--- a/compiler/rustc_parse/src/errors.rs
+++ b/compiler/rustc_parse/src/errors.rs
@@ -2019,6 +2019,14 @@ pub(crate) struct CannotBeRawIdent {
 }
 
 #[derive(Diagnostic)]
+#[diag(parse_cannot_be_raw_lifetime)]
+pub(crate) struct CannotBeRawLifetime {
+    #[primary_span]
+    pub span: Span,
+    pub ident: Symbol,
+}
+
+#[derive(Diagnostic)]
 #[diag(parse_keyword_lifetime)]
 pub(crate) struct KeywordLifetime {
     #[primary_span]
diff --git a/compiler/rustc_parse/src/lexer/mod.rs b/compiler/rustc_parse/src/lexer/mod.rs
index d627ef3d2cb..226de65445c 100644
--- a/compiler/rustc_parse/src/lexer/mod.rs
+++ b/compiler/rustc_parse/src/lexer/mod.rs
@@ -294,15 +294,21 @@ impl<'psess, 'src> StringReader<'psess, 'src> {
                     let prefix_span = self.mk_sp(start, ident_start);
 
                     if prefix_span.at_least_rust_2021() {
-                        let lifetime_name_without_tick = self.str_from(ident_start);
+                        let span = self.mk_sp(start, self.pos);
+
+                        let lifetime_name_without_tick = Symbol::intern(&self.str_from(ident_start));
+                        if !lifetime_name_without_tick.can_be_raw() {
+                            self.dcx().emit_err(errors::CannotBeRawLifetime { span, ident: lifetime_name_without_tick });
+                        }
+
                         // Put the `'` back onto the lifetime name.
-                        let mut lifetime_name = String::with_capacity(lifetime_name_without_tick.len() + 1);
+                        let mut lifetime_name = String::with_capacity(lifetime_name_without_tick.as_str().len() + 1);
                         lifetime_name.push('\'');
-                        lifetime_name += lifetime_name_without_tick;
+                        lifetime_name += lifetime_name_without_tick.as_str();
                         let sym = Symbol::intern(&lifetime_name);
 
                         // Make sure we mark this as a raw identifier.
-                        self.psess.raw_identifier_spans.push(self.mk_sp(start, self.pos));
+                        self.psess.raw_identifier_spans.push(span);
 
                         token::Lifetime(sym, IdentIsRaw::Yes)
                     } else {
diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs
index fe05605c1b9..fa2403db925 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,13 +1367,38 @@ 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 {
-    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,73 +1408,42 @@ impl RustcOptGroup {
         self.stability == OptionStability::Stable
     }
 
-    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 apply(&self, options: &mut getopts::Options) {
+        (self.apply)(options);
     }
 }
 
-// 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)
+                })
+            }
+        },
     }
 }
 
@@ -1464,46 +1458,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",
@@ -1512,41 +1520,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", ""),
     ]
 }
 
@@ -1554,25 +1557,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:
@@ -1581,19 +1595,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
 }
@@ -2756,7 +2774,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
diff --git a/src/bootstrap/defaults/config.dist.toml b/src/bootstrap/defaults/config.dist.toml
index d4feffe0227..4346a9c2dd1 100644
--- a/src/bootstrap/defaults/config.dist.toml
+++ b/src/bootstrap/defaults/config.dist.toml
@@ -11,6 +11,7 @@ extended = true
 # Most users installing from source want to build all parts of the project from source.
 [llvm]
 download-ci-llvm = false
+
 [rust]
 # We have several defaults in bootstrap that depend on whether the channel is `dev` (e.g. `omit-git-hash` and `download-ci-llvm`).
 # Make sure they don't get set when installing from source.
diff --git a/src/bootstrap/defaults/config.library.toml b/src/bootstrap/defaults/config.library.toml
index 3d697be8156..5447565a4b0 100644
--- a/src/bootstrap/defaults/config.library.toml
+++ b/src/bootstrap/defaults/config.library.toml
@@ -8,9 +8,6 @@ bench-stage = 0
 [rust]
 # This greatly increases the speed of rebuilds, especially when there are only minor changes. However, it makes the initial build slightly slower.
 incremental = true
-# Download rustc from CI instead of building it from source.
-# For stage > 1 builds, this cuts compile times significantly when there are no changes on "compiler" tree.
-download-rustc = "if-unchanged"
 # Make the compiler and standard library faster to build, at the expense of a ~20% runtime slowdown.
 lto = "off"
 
diff --git a/src/bootstrap/defaults/config.tools.toml b/src/bootstrap/defaults/config.tools.toml
index 27c1d1cf26d..76b47a841b3 100644
--- a/src/bootstrap/defaults/config.tools.toml
+++ b/src/bootstrap/defaults/config.tools.toml
@@ -3,11 +3,6 @@
 [rust]
 # This greatly increases the speed of rebuilds, especially when there are only minor changes. However, it makes the initial build slightly slower.
 incremental = true
-# Download rustc from CI instead of building it from source.
-# For stage > 1 builds, this cuts compile times significantly when there are no changes on "compiler" tree.
-# Using these defaults will download the stage2 compiler (see `download-rustc`
-# setting) and the stage2 toolchain should therefore be used for these defaults.
-download-rustc = "if-unchanged"
 
 [build]
 # Document with the in-tree rustdoc by default, since `download-rustc` makes it quick to compile.
diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs
index f977c285a74..a93038d51d3 100644
--- a/src/bootstrap/src/core/config/config.rs
+++ b/src/bootstrap/src/core/config/config.rs
@@ -1665,10 +1665,26 @@ impl Config {
         let mut debuginfo_level_tools = None;
         let mut debuginfo_level_tests = None;
         let mut optimize = None;
-        let mut omit_git_hash = None;
         let mut lld_enabled = None;
         let mut std_features = None;
 
+        let default = config.channel == "dev";
+        config.omit_git_hash = toml.rust.as_ref().and_then(|r| r.omit_git_hash).unwrap_or(default);
+
+        config.rust_info = GitInfo::new(config.omit_git_hash, &config.src);
+        config.cargo_info = GitInfo::new(config.omit_git_hash, &config.src.join("src/tools/cargo"));
+        config.rust_analyzer_info =
+            GitInfo::new(config.omit_git_hash, &config.src.join("src/tools/rust-analyzer"));
+        config.clippy_info =
+            GitInfo::new(config.omit_git_hash, &config.src.join("src/tools/clippy"));
+        config.miri_info = GitInfo::new(config.omit_git_hash, &config.src.join("src/tools/miri"));
+        config.rustfmt_info =
+            GitInfo::new(config.omit_git_hash, &config.src.join("src/tools/rustfmt"));
+        config.enzyme_info =
+            GitInfo::new(config.omit_git_hash, &config.src.join("src/tools/enzyme"));
+        config.in_tree_llvm_info = GitInfo::new(false, &config.src.join("src/llvm-project"));
+        config.in_tree_gcc_info = GitInfo::new(false, &config.src.join("src/gcc"));
+
         let mut is_user_configured_rust_channel = false;
 
         if let Some(rust) = toml.rust {
@@ -1699,7 +1715,7 @@ impl Config {
                 verbose_tests,
                 optimize_tests,
                 codegen_tests,
-                omit_git_hash: omit_git_hash_toml,
+                omit_git_hash: _, // already handled above
                 dist_src,
                 save_toolstates,
                 codegen_backends,
@@ -1750,7 +1766,6 @@ impl Config {
             std_features = std_features_toml;
 
             optimize = optimize_toml;
-            omit_git_hash = omit_git_hash_toml;
             config.rust_new_symbol_mangling = new_symbol_mangling;
             set(&mut config.rust_optimize_tests, optimize_tests);
             set(&mut config.codegen_tests, codegen_tests);
@@ -1826,24 +1841,6 @@ impl Config {
 
         config.reproducible_artifacts = flags.reproducible_artifact;
 
-        // rust_info must be set before is_ci_llvm_available() is called.
-        let default = config.channel == "dev";
-        config.omit_git_hash = omit_git_hash.unwrap_or(default);
-        config.rust_info = GitInfo::new(config.omit_git_hash, &config.src);
-
-        config.cargo_info = GitInfo::new(config.omit_git_hash, &config.src.join("src/tools/cargo"));
-        config.rust_analyzer_info =
-            GitInfo::new(config.omit_git_hash, &config.src.join("src/tools/rust-analyzer"));
-        config.clippy_info =
-            GitInfo::new(config.omit_git_hash, &config.src.join("src/tools/clippy"));
-        config.miri_info = GitInfo::new(config.omit_git_hash, &config.src.join("src/tools/miri"));
-        config.rustfmt_info =
-            GitInfo::new(config.omit_git_hash, &config.src.join("src/tools/rustfmt"));
-        config.enzyme_info =
-            GitInfo::new(config.omit_git_hash, &config.src.join("src/tools/enzyme"));
-        config.in_tree_llvm_info = GitInfo::new(false, &config.src.join("src/llvm-project"));
-        config.in_tree_gcc_info = GitInfo::new(false, &config.src.join("src/gcc"));
-
         // We need to override `rust.channel` if it's manually specified when using the CI rustc.
         // This is because if the compiler uses a different channel than the one specified in config.toml,
         // tests may fail due to using a different channel than the one used by the compiler during tests.
@@ -2760,9 +2757,19 @@ impl Config {
 
         // If `download-rustc` is not set, default to rebuilding.
         let if_unchanged = match download_rustc {
-            None | Some(StringOrBool::Bool(false)) => return None,
+            None => self.rust_info.is_managed_git_subrepository(),
+            Some(StringOrBool::Bool(false)) => return None,
             Some(StringOrBool::Bool(true)) => false,
-            Some(StringOrBool::String(s)) if s == "if-unchanged" => true,
+            Some(StringOrBool::String(s)) if s == "if-unchanged" => {
+                if !self.rust_info.is_managed_git_subrepository() {
+                    println!(
+                        "ERROR: `download-rustc=if-unchanged` is only compatible with Git managed sources."
+                    );
+                    crate::exit!(1);
+                }
+
+                true
+            }
             Some(StringOrBool::String(other)) => {
                 panic!("unrecognized option for download-rustc: {other}")
             }
@@ -2789,7 +2796,7 @@ impl Config {
                     }
                     println!("ERROR: could not find commit hash for downloading rustc");
                     println!("HELP: maybe your repository history is too shallow?");
-                    println!("HELP: consider disabling `download-rustc`");
+                    println!("HELP: consider setting `rust.download-rustc=false` in config.toml");
                     println!("HELP: or fetch enough history to include one upstream commit");
                     crate::exit!(1);
                 }
diff --git a/src/bootstrap/src/core/config/tests.rs b/src/bootstrap/src/core/config/tests.rs
index 1f02757682c..b6523a458e2 100644
--- a/src/bootstrap/src/core/config/tests.rs
+++ b/src/bootstrap/src/core/config/tests.rs
@@ -135,6 +135,7 @@ change-id = 0
 [rust]
 lto = "off"
 deny-warnings = true
+download-rustc=false
 
 [build]
 gdb = "foo"
@@ -200,6 +201,8 @@ runner = "x86_64-runner"
             .collect(),
         "setting dictionary value"
     );
+    assert!(!config.llvm_from_ci);
+    assert!(!config.download_rustc());
 }
 
 #[test]
diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs
index 7c9dcd41e6a..78dc0b8e2f9 100644
--- a/src/librustdoc/lib.rs
+++ b/src/librustdoc/lib.rs
@@ -215,477 +215,482 @@ 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", ""),
     ]
 }
 
 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 +774,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,
diff --git a/src/tools/run-make-support/src/lib.rs b/src/tools/run-make-support/src/lib.rs
index 368b98c9f0d..5765cb97a7e 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 00000000000..22c5dd81bdb
--- /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 00000000000..dbd67b57df2
--- /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 00000000000..a7d07162799
--- /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 00000000000..85e90e6352d
--- /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();
+}
diff --git a/tests/rustdoc/hidden-implementors-90781.rs b/tests/rustdoc/hidden-implementors-90781.rs
new file mode 100644
index 00000000000..960a85b91f0
--- /dev/null
+++ b/tests/rustdoc/hidden-implementors-90781.rs
@@ -0,0 +1,78 @@
+//@ compile-flags: -Z unstable-options --document-hidden-items --document-private-items
+
+// regression test for https://github.com/rust-lang/rust/issues/90781
+#![crate_name = "foo"]
+
+//@ has foo/trait.TPubVis.html
+//@ has - '//*[@id="implementors-list"]' 'HidPriv'
+//@ has - '//*[@id="implementors-list"]' 'HidPub'
+//@ has - '//*[@id="implementors-list"]' 'VisPriv'
+//@ has - '//*[@id="implementors-list"]' 'VisPub'
+pub trait TPubVis {}
+
+//@ has foo/trait.TPubHidden.html
+//@ has - '//*[@id="implementors-list"]' 'HidPriv'
+//@ has - '//*[@id="implementors-list"]' 'HidPub'
+//@ has - '//*[@id="implementors-list"]' 'VisPriv'
+//@ has - '//*[@id="implementors-list"]' 'VisPub'
+#[doc(hidden)]
+pub trait TPubHidden {}
+
+//@ has foo/trait.TPrivVis.html
+//@ has - '//*[@id="implementors-list"]' 'HidPriv'
+//@ has - '//*[@id="implementors-list"]' 'HidPub'
+//@ has - '//*[@id="implementors-list"]' 'VisPriv'
+//@ has - '//*[@id="implementors-list"]' 'VisPub'
+trait TPrivVis {}
+
+#[doc(hidden)]
+//@ has foo/trait.TPrivHidden.html
+//@ has - '//*[@id="impl-TPrivHidden-for-HidPriv"]' 'HidPriv'
+//@ has - '//*[@id="impl-TPrivHidden-for-HidPub"]' 'HidPub'
+//@ has - '//*[@id="impl-TPrivHidden-for-VisPriv"]' 'VisPriv'
+//@ has - '//*[@id="impl-TPrivHidden-for-VisPub"]' 'VisPub'
+trait TPrivHidden {}
+
+//@ has foo/struct.VisPub.html
+//@ has - '//*[@id="trait-implementations-list"]' 'TPrivHidden'
+//@ has - '//*[@id="trait-implementations-list"]' 'TPrivVis'
+//@ has - '//*[@id="trait-implementations-list"]' 'TPubHidden'
+//@ has - '//*[@id="trait-implementations-list"]' 'TPubVis'
+pub struct VisPub;
+
+//@ has foo/struct.VisPriv.html
+//@ has - '//*[@id="trait-implementations-list"]' 'TPrivHidden'
+//@ has - '//*[@id="trait-implementations-list"]' 'TPrivVis'
+//@ has - '//*[@id="trait-implementations-list"]' 'TPubHidden'
+//@ has - '//*[@id="trait-implementations-list"]' 'TPubVis'
+struct VisPriv;
+
+//@ has foo/struct.HidPub.html
+//@ has - '//*[@id="trait-implementations-list"]' 'TPrivHidden'
+//@ has - '//*[@id="trait-implementations-list"]' 'TPrivVis'
+//@ has - '//*[@id="trait-implementations-list"]' 'TPubHidden'
+//@ has - '//*[@id="trait-implementations-list"]' 'TPubVis'
+#[doc(hidden)]
+pub struct HidPub;
+
+//@ has foo/struct.HidPriv.html
+//@ has - '//*[@id="trait-implementations-list"]' 'TPrivHidden'
+//@ has - '//*[@id="trait-implementations-list"]' 'TPrivVis'
+//@ has - '//*[@id="trait-implementations-list"]' 'TPubHidden'
+//@ has - '//*[@id="trait-implementations-list"]' 'TPubVis'
+#[doc(hidden)]
+struct HidPriv;
+
+macro_rules! implement {
+    ($trait:ident - $($struct:ident)+) => {
+        $(
+            impl $trait for $struct {}
+        )+
+    }
+}
+
+
+implement!(TPubVis - VisPub VisPriv HidPub HidPriv);
+implement!(TPubHidden - VisPub VisPriv HidPub HidPriv);
+implement!(TPrivVis - VisPub VisPriv HidPub HidPriv);
+implement!(TPrivHidden - VisPub VisPriv HidPub HidPriv);
diff --git a/tests/ui/lifetimes/raw/immediately-followed-by-lt.rs b/tests/ui/lifetimes/raw/immediately-followed-by-lt.rs
new file mode 100644
index 00000000000..fe2b6de7bb3
--- /dev/null
+++ b/tests/ui/lifetimes/raw/immediately-followed-by-lt.rs
@@ -0,0 +1,14 @@
+//@ edition: 2021
+
+// Make sure we reject the case where a raw lifetime is immediately followed by another
+// lifetime. This reserves a modest amount of space for changing lexing to, for example,
+// delay rejection of overlong char literals like `'r#long'id`.
+
+macro_rules! w {
+    ($($tt:tt)*) => {}
+}
+
+w!('r#long'id);
+//~^ ERROR character literal may only contain one codepoint
+
+fn main() {}
diff --git a/tests/ui/lifetimes/raw/immediately-followed-by-lt.stderr b/tests/ui/lifetimes/raw/immediately-followed-by-lt.stderr
new file mode 100644
index 00000000000..1caeec84b22
--- /dev/null
+++ b/tests/ui/lifetimes/raw/immediately-followed-by-lt.stderr
@@ -0,0 +1,13 @@
+error: character literal may only contain one codepoint
+  --> $DIR/immediately-followed-by-lt.rs:11:4
+   |
+LL | w!('r#long'id);
+   |    ^^^^^^^^
+   |
+help: if you meant to write a string literal, use double quotes
+   |
+LL | w!("r#long"id);
+   |    ~      ~
+
+error: aborting due to 1 previous error
+
diff --git a/tests/ui/lifetimes/raw/raw-lt-invalid-raw-id.rs b/tests/ui/lifetimes/raw/raw-lt-invalid-raw-id.rs
new file mode 100644
index 00000000000..882fad925f3
--- /dev/null
+++ b/tests/ui/lifetimes/raw/raw-lt-invalid-raw-id.rs
@@ -0,0 +1,20 @@
+//@ edition: 2021
+
+// Reject raw lifetimes with identifier parts that wouldn't be valid raw identifiers.
+
+macro_rules! w {
+    ($tt:tt) => {};
+}
+
+w!('r#_);
+//~^ ERROR `_` cannot be a raw lifetime
+w!('r#self);
+//~^ ERROR `self` cannot be a raw lifetime
+w!('r#super);
+//~^ ERROR `super` cannot be a raw lifetime
+w!('r#Self);
+//~^ ERROR `Self` cannot be a raw lifetime
+w!('r#crate);
+//~^ ERROR `crate` cannot be a raw lifetime
+
+fn main() {}
diff --git a/tests/ui/lifetimes/raw/raw-lt-invalid-raw-id.stderr b/tests/ui/lifetimes/raw/raw-lt-invalid-raw-id.stderr
new file mode 100644
index 00000000000..4cbb89b7a55
--- /dev/null
+++ b/tests/ui/lifetimes/raw/raw-lt-invalid-raw-id.stderr
@@ -0,0 +1,32 @@
+error: `_` cannot be a raw lifetime
+  --> $DIR/raw-lt-invalid-raw-id.rs:9:4
+   |
+LL | w!('r#_);
+   |    ^^^^
+
+error: `self` cannot be a raw lifetime
+  --> $DIR/raw-lt-invalid-raw-id.rs:11:4
+   |
+LL | w!('r#self);
+   |    ^^^^^^^
+
+error: `super` cannot be a raw lifetime
+  --> $DIR/raw-lt-invalid-raw-id.rs:13:4
+   |
+LL | w!('r#super);
+   |    ^^^^^^^^
+
+error: `Self` cannot be a raw lifetime
+  --> $DIR/raw-lt-invalid-raw-id.rs:15:4
+   |
+LL | w!('r#Self);
+   |    ^^^^^^^
+
+error: `crate` cannot be a raw lifetime
+  --> $DIR/raw-lt-invalid-raw-id.rs:17:4
+   |
+LL | w!('r#crate);
+   |    ^^^^^^^^
+
+error: aborting due to 5 previous errors
+
diff --git a/tests/ui/mismatched_types/similar_paths_primitive.rs b/tests/ui/mismatched_types/similar_paths_primitive.rs
index 8f5b7cce469..98890a15d98 100644
--- a/tests/ui/mismatched_types/similar_paths_primitive.rs
+++ b/tests/ui/mismatched_types/similar_paths_primitive.rs
@@ -1,10 +1,14 @@
 #![allow(non_camel_case_types)]
 
 struct bool;
+struct str;
 
 fn foo(_: bool) {}
+fn bar(_: &str) {}
 
 fn main() {
     foo(true);
     //~^ ERROR mismatched types [E0308]
+    bar("hello");
+    //~^ ERROR mismatched types [E0308]
 }
diff --git a/tests/ui/mismatched_types/similar_paths_primitive.stderr b/tests/ui/mismatched_types/similar_paths_primitive.stderr
index c9881891319..0530bf5863e 100644
--- a/tests/ui/mismatched_types/similar_paths_primitive.stderr
+++ b/tests/ui/mismatched_types/similar_paths_primitive.stderr
@@ -1,5 +1,5 @@
 error[E0308]: mismatched types
-  --> $DIR/similar_paths_primitive.rs:8:9
+  --> $DIR/similar_paths_primitive.rs:10:9
    |
 LL |     foo(true);
    |     --- ^^^^ expected `bool`, found a different `bool`
@@ -14,11 +14,32 @@ note: `bool` is defined in the current crate
 LL | struct bool;
    | ^^^^^^^^^^^
 note: function defined here
-  --> $DIR/similar_paths_primitive.rs:5:4
+  --> $DIR/similar_paths_primitive.rs:6:4
    |
 LL | fn foo(_: bool) {}
    |    ^^^ -------
 
-error: aborting due to 1 previous error
+error[E0308]: mismatched types
+  --> $DIR/similar_paths_primitive.rs:12:9
+   |
+LL |     bar("hello");
+   |     --- ^^^^^^^ expected `str`, found a different `str`
+   |     |
+   |     arguments to this function are incorrect
+   |
+   = note: str and `str` have similar names, but are actually distinct types
+   = note: str is a primitive defined by the language
+note: `str` is defined in the current crate
+  --> $DIR/similar_paths_primitive.rs:4:1
+   |
+LL | struct str;
+   | ^^^^^^^^^^
+note: function defined here
+  --> $DIR/similar_paths_primitive.rs:7:4
+   |
+LL | fn bar(_: &str) {}
+   |    ^^^ -------
+
+error: aborting due to 2 previous errors
 
 For more information about this error, try `rustc --explain E0308`.