about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--RELEASES.md2
-rw-r--r--library/core/src/future/future.rs10
-rw-r--r--library/core/src/hint.rs2
-rw-r--r--library/core/src/macros/mod.rs10
-rw-r--r--library/core/src/macros/panic.md16
-rw-r--r--library/core/src/num/dec2flt/mod.rs4
-rw-r--r--library/core/src/ptr/const_ptr.rs2
-rw-r--r--library/core/src/ptr/mut_ptr.rs2
-rw-r--r--library/profiler_builtins/build.rs1
-rw-r--r--library/std/src/error.rs17
-rw-r--r--src/librustc_error_codes/error_codes/E0761.md10
-rw-r--r--src/librustc_parse_format/lib.rs2
-rw-r--r--src/librustdoc/clean/cfg.rs151
-rw-r--r--src/librustdoc/doctest.rs (renamed from src/librustdoc/test.rs)0
-rw-r--r--src/librustdoc/doctest/tests.rs (renamed from src/librustdoc/test/tests.rs)0
-rw-r--r--src/librustdoc/html/markdown.rs6
-rw-r--r--src/librustdoc/html/render/mod.rs10
-rw-r--r--src/librustdoc/lib.rs4
-rw-r--r--src/librustdoc/markdown.rs2
-rw-r--r--src/librustdoc/passes/doc_test_lints.rs2
-rw-r--r--src/test/rustdoc/duplicate-cfg.rs26
-rw-r--r--src/test/ui/issues/issue-70381.rs6
-rw-r--r--src/test/ui/issues/issue-70381.stderr8
-rw-r--r--src/tools/compiletest/src/runtest.rs12
24 files changed, 220 insertions, 85 deletions
diff --git a/RELEASES.md b/RELEASES.md
index 37aed773545..64fe2df3c49 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -51,7 +51,7 @@ Compatibility Notes
 -------------------
 - [The target configuration option `abi_blacklist` has been renamed
   to `unsupported_abis`.][74150] The old name will still continue to work.
-- [Rustc will now warn if you have a C-like enum that implements `Drop`.][72331]
+- [Rustc will now warn if you cast a C-like enum that implements `Drop`.][72331]
   This was previously accepted but will become a hard error in a future release.
 - [Rustc will fail to compile if you have a struct with
   `#[repr(i128)]` or `#[repr(u128)]`.][74109] This representation is currently only
diff --git a/library/core/src/future/future.rs b/library/core/src/future/future.rs
index d9b91acc3ad..e9a99ddb6b1 100644
--- a/library/core/src/future/future.rs
+++ b/library/core/src/future/future.rs
@@ -23,7 +23,7 @@ use crate::task::{Context, Poll};
 /// When using a future, you generally won't call `poll` directly, but instead
 /// `.await` the value.
 ///
-/// [`Waker`]: ../task/struct.Waker.html
+/// [`Waker`]: crate::task::Waker
 #[doc(spotlight)]
 #[must_use = "futures do nothing unless you `.await` or poll them"]
 #[stable(feature = "futures_api", since = "1.36.0")]
@@ -91,11 +91,9 @@ pub trait Future {
     /// (memory corruption, incorrect use of `unsafe` functions, or the like),
     /// regardless of the future's state.
     ///
-    /// [`Poll::Pending`]: ../task/enum.Poll.html#variant.Pending
-    /// [`Poll::Ready(val)`]: ../task/enum.Poll.html#variant.Ready
-    /// [`Context`]: ../task/struct.Context.html
-    /// [`Waker`]: ../task/struct.Waker.html
-    /// [`Waker::wake`]: ../task/struct.Waker.html#method.wake
+    /// [`Poll::Ready(val)`]: Poll::Ready
+    /// [`Waker`]: crate::task::Waker
+    /// [`Waker::wake`]: crate::task::Waker::wake
     #[lang = "poll"]
     #[stable(feature = "futures_api", since = "1.36.0")]
     fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
diff --git a/library/core/src/hint.rs b/library/core/src/hint.rs
index 461b4c79a1d..d40a3802867 100644
--- a/library/core/src/hint.rs
+++ b/library/core/src/hint.rs
@@ -101,7 +101,7 @@ pub fn spin_loop() {
 /// [`std::convert::identity`]: https://doc.rust-lang.org/core/convert/fn.identity.html
 ///
 /// Unlike [`std::convert::identity`], a Rust compiler is encouraged to assume that `black_box` can
-/// use `x` in any possible valid way that Rust code is allowed to without introducing undefined
+/// use `dummy` in any possible valid way that Rust code is allowed to without introducing undefined
 /// behavior in the calling code. This property makes `black_box` useful for writing code in which
 /// certain optimizations are not desired, such as benchmarks.
 ///
diff --git a/library/core/src/macros/mod.rs b/library/core/src/macros/mod.rs
index d26f2124f15..4e0da1fc4a6 100644
--- a/library/core/src/macros/mod.rs
+++ b/library/core/src/macros/mod.rs
@@ -333,16 +333,16 @@ macro_rules! r#try {
 /// This macro accepts a format string, a list of arguments, and a 'writer'. Arguments will be
 /// formatted according to the specified format string and the result will be passed to the writer.
 /// The writer may be any value with a `write_fmt` method; generally this comes from an
-/// implementation of either the [`std::fmt::Write`] or the [`std::io::Write`] trait. The macro
-/// returns whatever the `write_fmt` method returns; commonly a [`std::fmt::Result`], or an
+/// implementation of either the [`fmt::Write`] or the [`io::Write`] trait. The macro
+/// returns whatever the `write_fmt` method returns; commonly a [`fmt::Result`], or an
 /// [`io::Result`].
 ///
 /// See [`std::fmt`] for more information on the format string syntax.
 ///
 /// [`std::fmt`]: crate::fmt
-/// [`std::fmt::Write`]: crate::fmt::Write
-/// [`std::io::Write`]: ../std/io/trait.Write.html
-/// [`std::fmt::Result`]: crate::fmt::Result
+/// [`fmt::Write`]: crate::fmt::Write
+/// [`io::Write`]: ../std/io/trait.Write.html
+/// [`fmt::Result`]: crate::fmt::Result
 /// [`io::Result`]: ../std/io/type.Result.html
 ///
 /// # Examples
diff --git a/library/core/src/macros/panic.md b/library/core/src/macros/panic.md
index 3ecfc43be04..a02e74d5e5a 100644
--- a/library/core/src/macros/panic.md
+++ b/library/core/src/macros/panic.md
@@ -5,12 +5,12 @@ to the caller of the program. `panic!` should be used when a program reaches
 an unrecoverable state.
 
 This macro is the perfect way to assert conditions in example code and in
-tests. `panic!` is closely tied with the `unwrap` method of both [`Option`]
-and [`Result`][runwrap] enums. Both implementations call `panic!` when they are set
-to None or Err variants.
+tests. `panic!` is closely tied with the `unwrap` method of both
+[`Option`][ounwrap] and [`Result`][runwrap] enums. Both implementations call
+`panic!` when they are set to [`None`] or [`Err`] variants.
 
 This macro is used to inject panic into a Rust thread, causing the thread to
-panic entirely. Each thread's panic can be reaped as the `Box<Any>` type,
+panic entirely. Each thread's panic can be reaped as the [`Box`]`<`[`Any`]`>` type,
 and the single-argument form of the `panic!` macro will be the value which
 is transmitted.
 
@@ -24,11 +24,11 @@ The multi-argument form of this macro panics with a string and has the
 
 See also the macro [`compile_error!`], for raising errors during compilation.
 
-[runwrap]: ../std/result/enum.Result.html#method.unwrap
-[`Option`]: ../std/option/enum.Option.html#method.unwrap
-[`Result`]: ../std/result/enum.Result.html
+[ounwrap]: Option::unwrap
+[runwrap]: Result::unwrap
+[`Box`]: ../std/boxed/struct.Box.html
+[`Any`]: crate::any::Any
 [`format!`]: ../std/macro.format.html
-[`compile_error!`]: ../std/macro.compile_error.html
 [book]: ../book/ch09-00-error-handling.html
 
 # Current implementation
diff --git a/library/core/src/num/dec2flt/mod.rs b/library/core/src/num/dec2flt/mod.rs
index c83c6b0eccb..ed6202bb82f 100644
--- a/library/core/src/num/dec2flt/mod.rs
+++ b/library/core/src/num/dec2flt/mod.rs
@@ -166,10 +166,6 @@ from_str_float_impl!(f64);
 ///
 /// This error is used as the error type for the [`FromStr`] implementation
 /// for [`f32`] and [`f64`].
-///
-/// [`FromStr`]: ../str/trait.FromStr.html
-/// [`f32`]: ../../std/primitive.f32.html
-/// [`f64`]: ../../std/primitive.f64.html
 #[derive(Debug, Clone, PartialEq, Eq)]
 #[stable(feature = "rust1", since = "1.0.0")]
 pub struct ParseFloatError {
diff --git a/library/core/src/ptr/const_ptr.rs b/library/core/src/ptr/const_ptr.rs
index fc70dec16f6..d09cdb44e08 100644
--- a/library/core/src/ptr/const_ptr.rs
+++ b/library/core/src/ptr/const_ptr.rs
@@ -836,7 +836,7 @@ impl<T: ?Sized> *const T {
     /// # use std::mem::align_of;
     /// # unsafe {
     /// let x = [5u8, 6u8, 7u8, 8u8, 9u8];
-    /// let ptr = &x[n] as *const u8;
+    /// let ptr = x.as_ptr().add(n) as *const u8;
     /// let offset = ptr.align_offset(align_of::<u16>());
     /// if offset < x.len() - n - 1 {
     ///     let u16_ptr = ptr.add(offset) as *const u16;
diff --git a/library/core/src/ptr/mut_ptr.rs b/library/core/src/ptr/mut_ptr.rs
index 2d25f21e55c..537aa20bf1d 100644
--- a/library/core/src/ptr/mut_ptr.rs
+++ b/library/core/src/ptr/mut_ptr.rs
@@ -1094,7 +1094,7 @@ impl<T: ?Sized> *mut T {
     /// # use std::mem::align_of;
     /// # unsafe {
     /// let x = [5u8, 6u8, 7u8, 8u8, 9u8];
-    /// let ptr = &x[n] as *const u8;
+    /// let ptr = x.as_ptr().add(n) as *const u8;
     /// let offset = ptr.align_offset(align_of::<u16>());
     /// if offset < x.len() - n - 1 {
     ///     let u16_ptr = ptr.add(offset) as *const u16;
diff --git a/library/profiler_builtins/build.rs b/library/profiler_builtins/build.rs
index b674f73ebf3..2a5d5853fec 100644
--- a/library/profiler_builtins/build.rs
+++ b/library/profiler_builtins/build.rs
@@ -20,6 +20,7 @@ fn main() {
         "InstrProfilingMergeFile.c",
         "InstrProfilingNameVar.c",
         "InstrProfilingPlatformDarwin.c",
+        "InstrProfilingPlatformFuchsia.c",
         "InstrProfilingPlatformLinux.c",
         "InstrProfilingPlatformOther.c",
         "InstrProfilingPlatformWindows.c",
diff --git a/library/std/src/error.rs b/library/std/src/error.rs
index 84e686c2fef..d3b0f8ceb68 100644
--- a/library/std/src/error.rs
+++ b/library/std/src/error.rs
@@ -33,15 +33,14 @@ use crate::string;
 /// themselves through the [`Display`] and [`Debug`] traits, and may provide
 /// cause chain information:
 ///
-/// The [`source`] method is generally used when errors cross "abstraction
-/// boundaries". If one module must report an error that is caused by an error
-/// from a lower-level module, it can allow access to that error via the
-/// [`source`] method. This makes it possible for the high-level module to
-/// provide its own errors while also revealing some of the implementation for
-/// debugging via [`source`] chains.
+/// [`Error::source()`] is generally used when errors cross
+/// "abstraction boundaries". If one module must report an error that is caused
+/// by an error from a lower-level module, it can allow accessing that error
+/// via [`Error::source()`]. This makes it possible for the high-level
+/// module to provide its own errors while also revealing some of the
+/// implementation for debugging via `source` chains.
 ///
 /// [`Result<T, E>`]: Result
-/// [`source`]: Error::source
 #[stable(feature = "rust1", since = "1.0.0")]
 pub trait Error: Debug + Display {
     /// The lower-level source of this error, if any.
@@ -636,7 +635,7 @@ impl dyn Error {
     }
 
     /// Returns an iterator starting with the current error and continuing with
-    /// recursively calling [`source`].
+    /// recursively calling [`Error::source`].
     ///
     /// If you want to omit the current error and only use its sources,
     /// use `skip(1)`.
@@ -686,8 +685,6 @@ impl dyn Error {
     /// assert!(iter.next().is_none());
     /// assert!(iter.next().is_none());
     /// ```
-    ///
-    /// [`source`]: Error::source
     #[unstable(feature = "error_iter", issue = "58520")]
     #[inline]
     pub fn chain(&self) -> Chain<'_> {
diff --git a/src/librustc_error_codes/error_codes/E0761.md b/src/librustc_error_codes/error_codes/E0761.md
index c01574e413c..e112674fbcc 100644
--- a/src/librustc_error_codes/error_codes/E0761.md
+++ b/src/librustc_error_codes/error_codes/E0761.md
@@ -2,24 +2,20 @@ Multiple candidate files were found for an out-of-line module.
 
 Erroneous code example:
 
-```rust
+```ignore (multiple source files required for compile_fail)
 // file: ambiguous_module/mod.rs
 
 fn foo() {}
-```
 
-```rust
 // file: ambiguous_module.rs
 
 fn foo() {}
-```
 
-```ignore (multiple source files required for compile_fail)
+// file: lib.rs
+
 mod ambiguous_module; // error: file for module `ambiguous_module`
                       // found at both ambiguous_module.rs and
                       // ambiguous_module.rs/mod.rs
-
-fn main() {}
 ```
 
 Please remove this ambiguity by deleting/renaming one of the candidate files.
diff --git a/src/librustc_parse_format/lib.rs b/src/librustc_parse_format/lib.rs
index ebb3aa3866e..e07b8b86aef 100644
--- a/src/librustc_parse_format/lib.rs
+++ b/src/librustc_parse_format/lib.rs
@@ -760,7 +760,7 @@ fn find_skips_from_snippet(
                 (' ' | '\n' | '\t', _) if eat_ws => {
                     skips.push(pos);
                 }
-                ('\\', Some((next_pos, 'n' | 't' | '0' | '\\' | '\'' | '\"'))) => {
+                ('\\', Some((next_pos, 'n' | 't' | 'r' | '0' | '\\' | '\'' | '\"'))) => {
                     skips.push(*next_pos);
                     let _ = s.next();
                 }
diff --git a/src/librustdoc/clean/cfg.rs b/src/librustdoc/clean/cfg.rs
index ab0b332ee19..c039b181178 100644
--- a/src/librustdoc/clean/cfg.rs
+++ b/src/librustdoc/clean/cfg.rs
@@ -135,7 +135,7 @@ impl Cfg {
 
     /// Renders the configuration for human display, as a short HTML description.
     pub(crate) fn render_short_html(&self) -> String {
-        let mut msg = Html(self, true).to_string();
+        let mut msg = Display(self, Format::ShortHtml).to_string();
         if self.should_capitalize_first_letter() {
             if let Some(i) = msg.find(|c: char| c.is_ascii_alphanumeric()) {
                 msg[i..i + 1].make_ascii_uppercase();
@@ -148,7 +148,11 @@ impl Cfg {
     pub(crate) fn render_long_html(&self) -> String {
         let on = if self.should_use_with_in_description() { "with" } else { "on" };
 
-        let mut msg = format!("This is supported {} <strong>{}</strong>", on, Html(self, false));
+        let mut msg = format!(
+            "This is supported {} <strong>{}</strong>",
+            on,
+            Display(self, Format::LongHtml)
+        );
         if self.should_append_only_to_description() {
             msg.push_str(" only");
         }
@@ -156,6 +160,17 @@ impl Cfg {
         msg
     }
 
+    /// Renders the configuration for long display, as a long plain text description.
+    pub(crate) fn render_long_plain(&self) -> String {
+        let on = if self.should_use_with_in_description() { "with" } else { "on" };
+
+        let mut msg = format!("This is supported {} {}", on, Display(self, Format::LongPlain));
+        if self.should_append_only_to_description() {
+            msg.push_str(" only");
+        }
+        msg
+    }
+
     fn should_capitalize_first_letter(&self) -> bool {
         match *self {
             Cfg::False | Cfg::True | Cfg::Not(..) => true,
@@ -286,9 +301,31 @@ impl ops::BitOr for Cfg {
     }
 }
 
-/// Pretty-print wrapper for a `Cfg`. Also indicates whether the "short-form" rendering should be
-/// used.
-struct Html<'a>(&'a Cfg, bool);
+#[derive(Clone, Copy)]
+enum Format {
+    LongHtml,
+    LongPlain,
+    ShortHtml,
+}
+
+impl Format {
+    fn is_long(self) -> bool {
+        match self {
+            Format::LongHtml | Format::LongPlain => true,
+            Format::ShortHtml => false,
+        }
+    }
+
+    fn is_html(self) -> bool {
+        match self {
+            Format::LongHtml | Format::ShortHtml => true,
+            Format::LongPlain => false,
+        }
+    }
+}
+
+/// Pretty-print wrapper for a `Cfg`. Also indicates what form of rendering should be used.
+struct Display<'a>(&'a Cfg, Format);
 
 fn write_with_opt_paren<T: fmt::Display>(
     fmt: &mut fmt::Formatter<'_>,
@@ -305,7 +342,7 @@ fn write_with_opt_paren<T: fmt::Display>(
     Ok(())
 }
 
-impl<'a> fmt::Display for Html<'a> {
+impl<'a> fmt::Display for Display<'a> {
     fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
         match *self.0 {
             Cfg::Not(ref child) => match **child {
@@ -314,31 +351,86 @@ impl<'a> fmt::Display for Html<'a> {
                         if sub_cfgs.iter().all(Cfg::is_simple) { " nor " } else { ", nor " };
                     for (i, sub_cfg) in sub_cfgs.iter().enumerate() {
                         fmt.write_str(if i == 0 { "neither " } else { separator })?;
-                        write_with_opt_paren(fmt, !sub_cfg.is_all(), Html(sub_cfg, self.1))?;
+                        write_with_opt_paren(fmt, !sub_cfg.is_all(), Display(sub_cfg, self.1))?;
                     }
                     Ok(())
                 }
-                ref simple @ Cfg::Cfg(..) => write!(fmt, "non-{}", Html(simple, self.1)),
-                ref c => write!(fmt, "not ({})", Html(c, self.1)),
+                ref simple @ Cfg::Cfg(..) => write!(fmt, "non-{}", Display(simple, self.1)),
+                ref c => write!(fmt, "not ({})", Display(c, self.1)),
             },
 
             Cfg::Any(ref sub_cfgs) => {
                 let separator = if sub_cfgs.iter().all(Cfg::is_simple) { " or " } else { ", or " };
+
+                let short_longhand = self.1.is_long() && {
+                    let all_crate_features = sub_cfgs
+                        .iter()
+                        .all(|sub_cfg| matches!(sub_cfg, Cfg::Cfg(sym::feature, Some(_))));
+                    let all_target_features = sub_cfgs
+                        .iter()
+                        .all(|sub_cfg| matches!(sub_cfg, Cfg::Cfg(sym::target_feature, Some(_))));
+
+                    if all_crate_features {
+                        fmt.write_str("crate features ")?;
+                        true
+                    } else if all_target_features {
+                        fmt.write_str("target features ")?;
+                        true
+                    } else {
+                        false
+                    }
+                };
+
                 for (i, sub_cfg) in sub_cfgs.iter().enumerate() {
                     if i != 0 {
                         fmt.write_str(separator)?;
                     }
-                    write_with_opt_paren(fmt, !sub_cfg.is_all(), Html(sub_cfg, self.1))?;
+                    if let (true, Cfg::Cfg(_, Some(feat))) = (short_longhand, sub_cfg) {
+                        if self.1.is_html() {
+                            write!(fmt, "<code>{}</code>", feat)?;
+                        } else {
+                            write!(fmt, "`{}`", feat)?;
+                        }
+                    } else {
+                        write_with_opt_paren(fmt, !sub_cfg.is_all(), Display(sub_cfg, self.1))?;
+                    }
                 }
                 Ok(())
             }
 
             Cfg::All(ref sub_cfgs) => {
+                let short_longhand = self.1.is_long() && {
+                    let all_crate_features = sub_cfgs
+                        .iter()
+                        .all(|sub_cfg| matches!(sub_cfg, Cfg::Cfg(sym::feature, Some(_))));
+                    let all_target_features = sub_cfgs
+                        .iter()
+                        .all(|sub_cfg| matches!(sub_cfg, Cfg::Cfg(sym::target_feature, Some(_))));
+
+                    if all_crate_features {
+                        fmt.write_str("crate features ")?;
+                        true
+                    } else if all_target_features {
+                        fmt.write_str("target features ")?;
+                        true
+                    } else {
+                        false
+                    }
+                };
+
                 for (i, sub_cfg) in sub_cfgs.iter().enumerate() {
                     if i != 0 {
                         fmt.write_str(" and ")?;
                     }
-                    write_with_opt_paren(fmt, !sub_cfg.is_simple(), Html(sub_cfg, self.1))?;
+                    if let (true, Cfg::Cfg(_, Some(feat))) = (short_longhand, sub_cfg) {
+                        if self.1.is_html() {
+                            write!(fmt, "<code>{}</code>", feat)?;
+                        } else {
+                            write!(fmt, "`{}`", feat)?;
+                        }
+                    } else {
+                        write_with_opt_paren(fmt, !sub_cfg.is_simple(), Display(sub_cfg, self.1))?;
+                    }
                 }
                 Ok(())
             }
@@ -406,26 +498,39 @@ impl<'a> fmt::Display for Html<'a> {
                     },
                     (sym::target_endian, Some(endian)) => return write!(fmt, "{}-endian", endian),
                     (sym::target_pointer_width, Some(bits)) => return write!(fmt, "{}-bit", bits),
-                    (sym::target_feature, Some(feat)) => {
-                        if self.1 {
-                            return write!(fmt, "<code>{}</code>", feat);
-                        } else {
+                    (sym::target_feature, Some(feat)) => match self.1 {
+                        Format::LongHtml => {
                             return write!(fmt, "target feature <code>{}</code>", feat);
                         }
-                    }
+                        Format::LongPlain => return write!(fmt, "target feature `{}`", feat),
+                        Format::ShortHtml => return write!(fmt, "<code>{}</code>", feat),
+                    },
+                    (sym::feature, Some(feat)) => match self.1 {
+                        Format::LongHtml => {
+                            return write!(fmt, "crate feature <code>{}</code>", feat);
+                        }
+                        Format::LongPlain => return write!(fmt, "crate feature `{}`", feat),
+                        Format::ShortHtml => return write!(fmt, "<code>{}</code>", feat),
+                    },
                     _ => "",
                 };
                 if !human_readable.is_empty() {
                     fmt.write_str(human_readable)
                 } else if let Some(v) = value {
-                    write!(
-                        fmt,
-                        "<code>{}=\"{}\"</code>",
-                        Escape(&name.as_str()),
-                        Escape(&v.as_str())
-                    )
-                } else {
+                    if self.1.is_html() {
+                        write!(
+                            fmt,
+                            r#"<code>{}="{}"</code>"#,
+                            Escape(&name.as_str()),
+                            Escape(&v.as_str())
+                        )
+                    } else {
+                        write!(fmt, r#"`{}="{}"`"#, name, v)
+                    }
+                } else if self.1.is_html() {
                     write!(fmt, "<code>{}</code>", Escape(&name.as_str()))
+                } else {
+                    write!(fmt, "`{}`", name)
                 }
             }
         }
diff --git a/src/librustdoc/test.rs b/src/librustdoc/doctest.rs
index 7b7c152d8ab..7b7c152d8ab 100644
--- a/src/librustdoc/test.rs
+++ b/src/librustdoc/doctest.rs
diff --git a/src/librustdoc/test/tests.rs b/src/librustdoc/doctest/tests.rs
index a96186a95e1..a96186a95e1 100644
--- a/src/librustdoc/test/tests.rs
+++ b/src/librustdoc/doctest/tests.rs
diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs
index b2589e5b806..56499f736e1 100644
--- a/src/librustdoc/html/markdown.rs
+++ b/src/librustdoc/html/markdown.rs
@@ -34,9 +34,9 @@ use std::fmt::Write;
 use std::ops::Range;
 use std::str;
 
+use crate::doctest;
 use crate::html::highlight;
 use crate::html::toc::TocBuilder;
-use crate::test;
 
 use pulldown_cmark::{html, CodeBlockKind, CowStr, Event, Options, Parser, Tag};
 
@@ -243,7 +243,7 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
                 .collect::<Vec<Cow<'_, str>>>()
                 .join("\n");
             let krate = krate.as_ref().map(|s| &**s);
-            let (test, _) = test::make_test(&test, krate, false, &Default::default(), edition);
+            let (test, _) = doctest::make_test(&test, krate, false, &Default::default(), edition);
             let channel = if test.contains("#![feature(") { "&amp;version=nightly" } else { "" };
 
             let edition_string = format!("&amp;edition={}", edition);
@@ -568,7 +568,7 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for Footnotes<'a, I> {
     }
 }
 
-pub fn find_testable_code<T: test::Tester>(
+pub fn find_testable_code<T: doctest::Tester>(
     doc: &str,
     tests: &mut T,
     error_codes: ErrorCodes,
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index 2aadb2357f9..15afe9257d1 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -2130,8 +2130,8 @@ fn item_module(w: &mut Buffer, cx: &Context, item: &clean::Item, items: &[clean:
 fn stability_tags(item: &clean::Item) -> String {
     let mut tags = String::new();
 
-    fn tag_html(class: &str, contents: &str) -> String {
-        format!(r#"<span class="stab {}">{}</span>"#, class, contents)
+    fn tag_html(class: &str, title: &str, contents: &str) -> String {
+        format!(r#"<span class="stab {}" title="{}">{}</span>"#, class, Escape(title), contents)
     }
 
     // The trailing space after each tag is to space it properly against the rest of the docs.
@@ -2140,7 +2140,7 @@ fn stability_tags(item: &clean::Item) -> String {
         if !stability::deprecation_in_effect(depr.is_since_rustc_version, depr.since.as_deref()) {
             message = "Deprecation planned";
         }
-        tags += &tag_html("deprecated", message);
+        tags += &tag_html("deprecated", "", message);
     }
 
     // The "rustc_private" crates are permanently unstable so it makes no sense
@@ -2151,11 +2151,11 @@ fn stability_tags(item: &clean::Item) -> String {
         .map(|s| s.level == stability::Unstable && s.feature != "rustc_private")
         == Some(true)
     {
-        tags += &tag_html("unstable", "Experimental");
+        tags += &tag_html("unstable", "", "Experimental");
     }
 
     if let Some(ref cfg) = item.attrs.cfg {
-        tags += &tag_html("portability", &cfg.render_short_html());
+        tags += &tag_html("portability", &cfg.render_long_plain(), &cfg.render_short_html());
     }
 
     tags
diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs
index b5e246b5d17..8658b39b455 100644
--- a/src/librustdoc/lib.rs
+++ b/src/librustdoc/lib.rs
@@ -64,13 +64,13 @@ mod docfs;
 mod doctree;
 #[macro_use]
 mod error;
+mod doctest;
 mod fold;
 crate mod formats;
 pub mod html;
 mod json;
 mod markdown;
 mod passes;
-mod test;
 mod theme;
 mod visit_ast;
 mod visit_lib;
@@ -476,7 +476,7 @@ fn main_options(options: config::Options) -> MainResult {
 
     match (options.should_test, options.markdown_input()) {
         (true, true) => return wrap_return(&diag, markdown::test(options)),
-        (true, false) => return test::run(options),
+        (true, false) => return doctest::run(options),
         (false, true) => {
             return wrap_return(
                 &diag,
diff --git a/src/librustdoc/markdown.rs b/src/librustdoc/markdown.rs
index 89d184e35cb..3a87e1c46a6 100644
--- a/src/librustdoc/markdown.rs
+++ b/src/librustdoc/markdown.rs
@@ -7,10 +7,10 @@ use rustc_span::edition::Edition;
 use rustc_span::source_map::DUMMY_SP;
 
 use crate::config::{Options, RenderOptions};
+use crate::doctest::{Collector, TestOptions};
 use crate::html::escape::Escape;
 use crate::html::markdown;
 use crate::html::markdown::{find_testable_code, ErrorCodes, IdMap, Markdown, MarkdownWithToc};
-use crate::test::{Collector, TestOptions};
 
 /// Separate any lines at the start of the file that begin with `# ` or `%`.
 fn extract_leading_metadata(s: &str) -> (Vec<&str>, &str) {
diff --git a/src/librustdoc/passes/doc_test_lints.rs b/src/librustdoc/passes/doc_test_lints.rs
index 367f93cfd38..cbbe86dc433 100644
--- a/src/librustdoc/passes/doc_test_lints.rs
+++ b/src/librustdoc/passes/doc_test_lints.rs
@@ -54,7 +54,7 @@ impl Tests {
     }
 }
 
-impl crate::test::Tester for Tests {
+impl crate::doctest::Tester for Tests {
     fn add_test(&mut self, _: String, _: LangString, _: usize) {
         self.found_tests += 1;
     }
diff --git a/src/test/rustdoc/duplicate-cfg.rs b/src/test/rustdoc/duplicate-cfg.rs
index 9ccc5d7882e..47ba362c977 100644
--- a/src/test/rustdoc/duplicate-cfg.rs
+++ b/src/test/rustdoc/duplicate-cfg.rs
@@ -3,22 +3,34 @@
 #![crate_name = "foo"]
 #![feature(doc_cfg)]
 
+// @has 'foo/index.html'
+// @matches '-' '//*[@class="module-item"]//*[@class="stab portability"]' '^sync$'
+// @has '-' '//*[@class="module-item"]//*[@class="stab portability"]/@title' 'This is supported on crate feature `sync` only'
+
 // @has 'foo/struct.Foo.html'
-// @has '-' '//*[@class="stab portability"]' 'This is supported on feature="sync" only.'
+// @has '-' '//*[@class="stab portability"]' 'sync'
 #[doc(cfg(feature = "sync"))]
 #[doc(cfg(feature = "sync"))]
 pub struct Foo;
 
+// @has 'foo/bar/index.html'
+// @matches '-' '//*[@class="module-item"]//*[@class="stab portability"]' '^sync$'
+// @has '-' '//*[@class="module-item"]//*[@class="stab portability"]/@title' 'This is supported on crate feature `sync` only'
+
 // @has 'foo/bar/struct.Bar.html'
-// @has '-' '//*[@class="stab portability"]' 'This is supported on feature="sync" only.'
+// @has '-' '//*[@class="stab portability"]' 'This is supported on crate feature sync only.'
 #[doc(cfg(feature = "sync"))]
 pub mod bar {
     #[doc(cfg(feature = "sync"))]
     pub struct Bar;
 }
 
+// @has 'foo/baz/index.html'
+// @matches '-' '//*[@class="module-item"]//*[@class="stab portability"]' '^sync and send$'
+// @has '-' '//*[@class="module-item"]//*[@class="stab portability"]/@title' 'This is supported on crate features `sync` and `send` only'
+
 // @has 'foo/baz/struct.Baz.html'
-// @has '-' '//*[@class="stab portability"]' 'This is supported on feature="sync" and feature="send" only.'
+// @has '-' '//*[@class="stab portability"]' 'This is supported on crate features sync and send only.'
 #[doc(cfg(all(feature = "sync", feature = "send")))]
 pub mod baz {
     #[doc(cfg(feature = "sync"))]
@@ -26,15 +38,19 @@ pub mod baz {
 }
 
 // @has 'foo/qux/struct.Qux.html'
-// @has '-' '//*[@class="stab portability"]' 'This is supported on feature="sync" and feature="send" only.'
+// @has '-' '//*[@class="stab portability"]' 'This is supported on crate features sync and send only.'
 #[doc(cfg(feature = "sync"))]
 pub mod qux {
     #[doc(cfg(all(feature = "sync", feature = "send")))]
     pub struct Qux;
 }
 
+// @has 'foo/quux/index.html'
+// @matches '-' '//*[@class="module-item"]//*[@class="stab portability"]' '^sync and send and foo and bar$'
+// @has '-' '//*[@class="module-item"]//*[@class="stab portability"]/@title' 'This is supported on crate feature `sync` and crate feature `send` and `foo` and `bar` only'
+
 // @has 'foo/quux/struct.Quux.html'
-// @has '-' '//*[@class="stab portability"]' 'This is supported on feature="sync" and feature="send" and foo and bar only.'
+// @has '-' '//*[@class="stab portability"]' 'This is supported on crate feature sync and crate feature send and foo and bar only.'
 #[doc(cfg(all(feature = "sync", feature = "send", foo)))]
 pub mod quux {
     #[doc(cfg(all(feature = "send", feature = "sync", bar)))]
diff --git a/src/test/ui/issues/issue-70381.rs b/src/test/ui/issues/issue-70381.rs
new file mode 100644
index 00000000000..3df8277b873
--- /dev/null
+++ b/src/test/ui/issues/issue-70381.rs
@@ -0,0 +1,6 @@
+// Test that multi-byte unicode characters with missing parameters do not ICE.
+
+fn main() {
+  println!("\r¡{}")
+  //~^ ERROR 1 positional argument in format string
+}
diff --git a/src/test/ui/issues/issue-70381.stderr b/src/test/ui/issues/issue-70381.stderr
new file mode 100644
index 00000000000..96b8e656991
--- /dev/null
+++ b/src/test/ui/issues/issue-70381.stderr
@@ -0,0 +1,8 @@
+error: 1 positional argument in format string, but no arguments were given
+  --> $DIR/issue-70381.rs:4:16
+   |
+LL |   println!("\r¡{}")
+   |                ^^
+
+error: aborting due to previous error
+
diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs
index 124a9adcab9..965b20f5202 100644
--- a/src/tools/compiletest/src/runtest.rs
+++ b/src/tools/compiletest/src/runtest.rs
@@ -2782,6 +2782,18 @@ impl<'test> TestCx<'test> {
             cmd.env("RUSTFLAGS", "-Ctarget-feature=-crt-static").env("IS_MUSL_HOST", "1");
         }
 
+        if self.config.bless {
+            cmd.env("RUSTC_BLESS_TEST", "--bless");
+            // Assume this option is active if the environment variable is "defined", with _any_ value.
+            // As an example, a `Makefile` can use this option by:
+            //
+            //   ifdef RUSTC_BLESS_TEST
+            //       cp "$(TMPDIR)"/actual_something.ext expected_something.ext
+            //   else
+            //       $(DIFF) expected_something.ext "$(TMPDIR)"/actual_something.ext
+            //   endif
+        }
+
         if self.config.target.contains("msvc") && self.config.cc != "" {
             // We need to pass a path to `lib.exe`, so assume that `cc` is `cl.exe`
             // and that `lib.exe` lives next to it.