about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_ast_passes/src/feature_gate.rs6
-rw-r--r--compiler/rustc_feature/src/active.rs6
-rw-r--r--compiler/rustc_feature/src/removed.rs6
-rw-r--r--compiler/rustc_passes/src/check_attr.rs2
-rw-r--r--compiler/rustc_span/src/symbol.rs1
-rw-r--r--compiler/rustc_target/src/spec/aarch64_unknown_linux_musl.rs6
-rw-r--r--library/core/src/lib.rs5
-rw-r--r--library/core/src/time.rs41
-rw-r--r--library/core/tests/lib.rs2
-rw-r--r--library/std/src/lib.rs5
-rw-r--r--library/std/src/process/tests.rs64
-rw-r--r--src/doc/rustdoc/src/unstable-features.md5
-rw-r--r--src/librustdoc/html/render/mod.rs18
-rw-r--r--src/librustdoc/html/static/css/rustdoc.css4
-rw-r--r--src/librustdoc/html/static/css/themes/ayu.css2
-rw-r--r--src/librustdoc/html/static/css/themes/dark.css2
-rw-r--r--src/librustdoc/html/static/css/themes/light.css2
-rw-r--r--src/test/rustdoc-gui/anchors.goml28
-rw-r--r--src/test/rustdoc-gui/headers-color.goml9
-rw-r--r--src/test/rustdoc-gui/item-info-width.goml2
-rw-r--r--src/test/rustdoc-gui/src/test_docs/lib.rs2
-rw-r--r--src/test/rustdoc-json/primitive.rs2
-rw-r--r--src/test/rustdoc-ui/coverage/exotic.rs3
-rw-r--r--src/test/rustdoc-ui/invalid-keyword.rs2
-rw-r--r--src/test/rustdoc/issue-32374.rs9
-rw-r--r--src/test/rustdoc/keyword.rs2
-rw-r--r--src/test/rustdoc/mixing-doc-comments-and-attrs.rs26
-rw-r--r--src/test/rustdoc/tab_title.rs2
-rw-r--r--src/test/ui-fulldeps/internal-lints/existing_doc_keyword.rs2
-rw-r--r--src/test/ui/feature-gates/feature-gate-doc_keyword.rs5
-rw-r--r--src/test/ui/feature-gates/feature-gate-doc_keyword.stderr12
-rw-r--r--src/test/ui/feature-gates/feature-gate-rustdoc_internals.rs5
-rw-r--r--src/test/ui/feature-gates/feature-gate-rustdoc_internals.stderr12
-rw-r--r--src/test/ui/rustdoc/doc_keyword.rs2
-rw-r--r--src/test/ui/rustdoc/renamed-features-rustdoc_internals.rs5
-rw-r--r--src/test/ui/rustdoc/renamed-features-rustdoc_internals.stderr19
36 files changed, 199 insertions, 127 deletions
diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs
index 6a19984f8ea..ad539f2564d 100644
--- a/compiler/rustc_ast_passes/src/feature_gate.rs
+++ b/compiler/rustc_ast_passes/src/feature_gate.rs
@@ -325,8 +325,12 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> {
                     cfg_hide => doc_cfg_hide
                     masked => doc_masked
                     notable_trait => doc_notable_trait
-                    keyword => doc_keyword
                 );
+
+                if nested_meta.has_name(sym::keyword) {
+                    let msg = "`#[doc(keyword)]` is meant for internal use only";
+                    gate_feature_post!(self, rustdoc_internals, attr.span, msg);
+                }
             }
         }
 
diff --git a/compiler/rustc_feature/src/active.rs b/compiler/rustc_feature/src/active.rs
index c34ecc966d0..7860f92f96f 100644
--- a/compiler/rustc_feature/src/active.rs
+++ b/compiler/rustc_feature/src/active.rs
@@ -206,6 +206,8 @@ declare_features! (
     (active, rustc_allow_const_fn_unstable, "1.49.0", Some(69399), None),
     /// Allows using compiler's own crates.
     (active, rustc_private, "1.0.0", Some(27812), None),
+    /// Allows using internal rustdoc features like `doc(primitive)` or `doc(keyword)`.
+    (active, rustdoc_internals, "1.58.0", Some(90418), None),
     /// Allows using `#[start]` on a function indicating that it is the program entrypoint.
     (active, start, "1.0.0", Some(29633), None),
     /// Allows using `#[structural_match]` which indicates that a type is structurally matchable.
@@ -366,12 +368,8 @@ declare_features! (
     (active, doc_cfg, "1.21.0", Some(43781), None),
     /// Allows `#[doc(cfg_hide(...))]`.
     (active, doc_cfg_hide, "1.57.0", Some(43781), None),
-    /// Allows using `#[doc(keyword = "...")]`.
-    (active, doc_keyword, "1.28.0", Some(51315), None),
     /// Allows `#[doc(masked)]`.
     (active, doc_masked, "1.21.0", Some(44027), None),
-    /// Allows using doc(primitive) without a future-incompat warning
-    (active, doc_primitive, "1.56.0", Some(88070), None),
     /// Allows `X..Y` patterns.
     (active, exclusive_range_pattern, "1.11.0", Some(37854), None),
     /// Allows exhaustive pattern matching on types that contain uninhabited types.
diff --git a/compiler/rustc_feature/src/removed.rs b/compiler/rustc_feature/src/removed.rs
index 4b40040a036..b9f3b5ad1b1 100644
--- a/compiler/rustc_feature/src/removed.rs
+++ b/compiler/rustc_feature/src/removed.rs
@@ -76,6 +76,12 @@ declare_features! (
     /// Allows the use of `#[derive(Anything)]` as sugar for `#[derive_Anything]`.
     (removed, custom_derive, "1.32.0", Some(29644), None,
      Some("subsumed by `#[proc_macro_derive]`")),
+    /// Allows using `#[doc(keyword = "...")]`.
+    (removed, doc_keyword, "1.28.0", Some(51315), None,
+     Some("merged into `#![feature(rustdoc_internals)]`")),
+    /// Allows using `doc(primitive)` without a future-incompat warning.
+    (removed, doc_primitive, "1.56.0", Some(88070), None,
+     Some("merged into `#![feature(rustdoc_internals)]`")),
     /// Allows `#[doc(spotlight)]`.
     /// The attribute was renamed to `#[doc(notable_trait)]`
     /// and the feature to `doc_notable_trait`.
diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs
index 2def57cf02a..f761eaae5ab 100644
--- a/compiler/rustc_passes/src/check_attr.rs
+++ b/compiler/rustc_passes/src/check_attr.rs
@@ -982,7 +982,7 @@ impl CheckAttrVisitor<'tcx> {
                         }
 
                         sym::primitive => {
-                            if !self.tcx.features().doc_primitive {
+                            if !self.tcx.features().rustdoc_internals {
                                 self.tcx.struct_span_lint_hir(
                                     INVALID_DOC_ATTRIBUTES,
                                     hir_id,
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index c34cf822765..97b155d2377 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -1155,6 +1155,7 @@ symbols! {
         rustc_unsafe_specialization_marker,
         rustc_variance,
         rustdoc,
+        rustdoc_internals,
         rustfmt,
         rvalue_static_promotion,
         s,
diff --git a/compiler/rustc_target/src/spec/aarch64_unknown_linux_musl.rs b/compiler/rustc_target/src/spec/aarch64_unknown_linux_musl.rs
index 0770f3496c2..6a16b4ce419 100644
--- a/compiler/rustc_target/src/spec/aarch64_unknown_linux_musl.rs
+++ b/compiler/rustc_target/src/spec/aarch64_unknown_linux_musl.rs
@@ -9,10 +9,6 @@ pub fn target() -> Target {
         pointer_width: 64,
         data_layout: "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128".to_string(),
         arch: "aarch64".to_string(),
-        options: TargetOptions {
-            features: "+outline-atomics".to_string(),
-            mcount: "\u{1}_mcount".to_string(),
-            ..base
-        },
+        options: TargetOptions { mcount: "\u{1}_mcount".to_string(), ..base },
     }
 }
diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs
index 2486999ffb5..fdb23529599 100644
--- a/library/core/src/lib.rs
+++ b/library/core/src/lib.rs
@@ -138,7 +138,7 @@
 #![feature(const_type_id)]
 #![feature(const_type_name)]
 #![feature(const_default_impls)]
-#![feature(duration_consts_2)]
+#![feature(duration_consts_float)]
 #![feature(ptr_metadata)]
 #![feature(slice_ptr_get)]
 #![feature(str_internals)]
@@ -166,7 +166,8 @@
 #![feature(derive_default_enum)]
 #![feature(doc_cfg)]
 #![feature(doc_notable_trait)]
-#![feature(doc_primitive)]
+#![cfg_attr(bootstrap, feature(doc_primitive))]
+#![cfg_attr(not(bootstrap), feature(rustdoc_internals))]
 #![feature(exhaustive_patterns)]
 #![feature(doc_cfg_hide)]
 #![feature(extern_types)]
diff --git a/library/core/src/time.rs b/library/core/src/time.rs
index 7330c86a11a..5efa04f7e5c 100644
--- a/library/core/src/time.rs
+++ b/library/core/src/time.rs
@@ -180,8 +180,9 @@ impl Duration {
     /// ```
     #[stable(feature = "duration", since = "1.3.0")]
     #[inline]
-    #[rustc_const_unstable(feature = "duration_consts_2", issue = "72440")]
     #[must_use]
+    #[rustc_const_stable(feature = "duration_consts_2", since = "1.58.0")]
+    #[cfg_attr(bootstrap, rustc_allow_const_fn_unstable(const_panic))]
     pub const fn new(secs: u64, nanos: u32) -> Duration {
         let secs = match secs.checked_add((nanos / NANOS_PER_SEC) as u64) {
             Some(secs) => secs,
@@ -480,7 +481,8 @@ impl Duration {
     #[must_use = "this returns the result of the operation, \
                   without modifying the original"]
     #[inline]
-    #[rustc_const_unstable(feature = "duration_consts_2", issue = "72440")]
+    #[rustc_const_stable(feature = "duration_consts_2", since = "1.58.0")]
+    #[cfg_attr(bootstrap, rustc_allow_const_fn_unstable(const_panic))]
     pub const fn checked_add(self, rhs: Duration) -> Option<Duration> {
         if let Some(mut secs) = self.secs.checked_add(rhs.secs) {
             let mut nanos = self.nanos + rhs.nanos;
@@ -515,7 +517,7 @@ impl Duration {
     #[must_use = "this returns the result of the operation, \
                   without modifying the original"]
     #[inline]
-    #[rustc_const_unstable(feature = "duration_consts_2", issue = "72440")]
+    #[rustc_const_stable(feature = "duration_consts_2", since = "1.58.0")]
     pub const fn saturating_add(self, rhs: Duration) -> Duration {
         match self.checked_add(rhs) {
             Some(res) => res,
@@ -540,7 +542,8 @@ impl Duration {
     #[must_use = "this returns the result of the operation, \
                   without modifying the original"]
     #[inline]
-    #[rustc_const_unstable(feature = "duration_consts_2", issue = "72440")]
+    #[rustc_const_stable(feature = "duration_consts_2", since = "1.58.0")]
+    #[cfg_attr(bootstrap, rustc_allow_const_fn_unstable(const_panic))]
     pub const fn checked_sub(self, rhs: Duration) -> Option<Duration> {
         if let Some(mut secs) = self.secs.checked_sub(rhs.secs) {
             let nanos = if self.nanos >= rhs.nanos {
@@ -573,7 +576,7 @@ impl Duration {
     #[must_use = "this returns the result of the operation, \
                   without modifying the original"]
     #[inline]
-    #[rustc_const_unstable(feature = "duration_consts_2", issue = "72440")]
+    #[rustc_const_stable(feature = "duration_consts_2", since = "1.58.0")]
     pub const fn saturating_sub(self, rhs: Duration) -> Duration {
         match self.checked_sub(rhs) {
             Some(res) => res,
@@ -598,7 +601,8 @@ impl Duration {
     #[must_use = "this returns the result of the operation, \
                   without modifying the original"]
     #[inline]
-    #[rustc_const_unstable(feature = "duration_consts_2", issue = "72440")]
+    #[rustc_const_stable(feature = "duration_consts_2", since = "1.58.0")]
+    #[cfg_attr(bootstrap, rustc_allow_const_fn_unstable(const_panic))]
     pub const fn checked_mul(self, rhs: u32) -> Option<Duration> {
         // Multiply nanoseconds as u64, because it cannot overflow that way.
         let total_nanos = self.nanos as u64 * rhs as u64;
@@ -629,7 +633,7 @@ impl Duration {
     #[must_use = "this returns the result of the operation, \
                   without modifying the original"]
     #[inline]
-    #[rustc_const_unstable(feature = "duration_consts_2", issue = "72440")]
+    #[rustc_const_stable(feature = "duration_consts_2", since = "1.58.0")]
     pub const fn saturating_mul(self, rhs: u32) -> Duration {
         match self.checked_mul(rhs) {
             Some(res) => res,
@@ -655,7 +659,8 @@ impl Duration {
     #[must_use = "this returns the result of the operation, \
                   without modifying the original"]
     #[inline]
-    #[rustc_const_unstable(feature = "duration_consts_2", issue = "72440")]
+    #[rustc_const_stable(feature = "duration_consts_2", since = "1.58.0")]
+    #[cfg_attr(bootstrap, rustc_allow_const_fn_unstable(const_panic))]
     pub const fn checked_div(self, rhs: u32) -> Option<Duration> {
         if rhs != 0 {
             let secs = self.secs / (rhs as u64);
@@ -683,7 +688,7 @@ impl Duration {
     #[stable(feature = "duration_float", since = "1.38.0")]
     #[must_use]
     #[inline]
-    #[rustc_const_unstable(feature = "duration_consts_2", issue = "72440")]
+    #[rustc_const_unstable(feature = "duration_consts_float", issue = "72440")]
     pub const fn as_secs_f64(&self) -> f64 {
         (self.secs as f64) + (self.nanos as f64) / (NANOS_PER_SEC as f64)
     }
@@ -702,7 +707,7 @@ impl Duration {
     #[stable(feature = "duration_float", since = "1.38.0")]
     #[must_use]
     #[inline]
-    #[rustc_const_unstable(feature = "duration_consts_2", issue = "72440")]
+    #[rustc_const_unstable(feature = "duration_consts_float", issue = "72440")]
     pub const fn as_secs_f32(&self) -> f32 {
         (self.secs as f32) + (self.nanos as f32) / (NANOS_PER_SEC as f32)
     }
@@ -723,7 +728,7 @@ impl Duration {
     #[stable(feature = "duration_float", since = "1.38.0")]
     #[must_use]
     #[inline]
-    #[rustc_const_unstable(feature = "duration_consts_2", issue = "72440")]
+    #[rustc_const_unstable(feature = "duration_consts_float", issue = "72440")]
     pub const fn from_secs_f64(secs: f64) -> Duration {
         match Duration::try_from_secs_f64(secs) {
             Ok(v) => v,
@@ -784,7 +789,7 @@ impl Duration {
     #[stable(feature = "duration_float", since = "1.38.0")]
     #[must_use]
     #[inline]
-    #[rustc_const_unstable(feature = "duration_consts_2", issue = "72440")]
+    #[rustc_const_unstable(feature = "duration_consts_float", issue = "72440")]
     pub const fn from_secs_f32(secs: f32) -> Duration {
         match Duration::try_from_secs_f32(secs) {
             Ok(v) => v,
@@ -846,7 +851,7 @@ impl Duration {
     #[must_use = "this returns the result of the operation, \
                   without modifying the original"]
     #[inline]
-    #[rustc_const_unstable(feature = "duration_consts_2", issue = "72440")]
+    #[rustc_const_unstable(feature = "duration_consts_float", issue = "72440")]
     pub const fn mul_f64(self, rhs: f64) -> Duration {
         Duration::from_secs_f64(rhs * self.as_secs_f64())
     }
@@ -870,7 +875,7 @@ impl Duration {
     #[must_use = "this returns the result of the operation, \
                   without modifying the original"]
     #[inline]
-    #[rustc_const_unstable(feature = "duration_consts_2", issue = "72440")]
+    #[rustc_const_unstable(feature = "duration_consts_float", issue = "72440")]
     pub const fn mul_f32(self, rhs: f32) -> Duration {
         Duration::from_secs_f32(rhs * self.as_secs_f32())
     }
@@ -893,7 +898,7 @@ impl Duration {
     #[must_use = "this returns the result of the operation, \
                   without modifying the original"]
     #[inline]
-    #[rustc_const_unstable(feature = "duration_consts_2", issue = "72440")]
+    #[rustc_const_unstable(feature = "duration_consts_float", issue = "72440")]
     pub const fn div_f64(self, rhs: f64) -> Duration {
         Duration::from_secs_f64(self.as_secs_f64() / rhs)
     }
@@ -918,7 +923,7 @@ impl Duration {
     #[must_use = "this returns the result of the operation, \
                   without modifying the original"]
     #[inline]
-    #[rustc_const_unstable(feature = "duration_consts_2", issue = "72440")]
+    #[rustc_const_unstable(feature = "duration_consts_float", issue = "72440")]
     pub const fn div_f32(self, rhs: f32) -> Duration {
         Duration::from_secs_f32(self.as_secs_f32() / rhs)
     }
@@ -938,7 +943,7 @@ impl Duration {
     #[must_use = "this returns the result of the operation, \
                   without modifying the original"]
     #[inline]
-    #[rustc_const_unstable(feature = "duration_consts_2", issue = "72440")]
+    #[rustc_const_unstable(feature = "duration_consts_float", issue = "72440")]
     pub const fn div_duration_f64(self, rhs: Duration) -> f64 {
         self.as_secs_f64() / rhs.as_secs_f64()
     }
@@ -958,7 +963,7 @@ impl Duration {
     #[must_use = "this returns the result of the operation, \
                   without modifying the original"]
     #[inline]
-    #[rustc_const_unstable(feature = "duration_consts_2", issue = "72440")]
+    #[rustc_const_unstable(feature = "duration_consts_float", issue = "72440")]
     pub const fn div_duration_f32(self, rhs: Duration) -> f32 {
         self.as_secs_f32() / rhs.as_secs_f32()
     }
diff --git a/library/core/tests/lib.rs b/library/core/tests/lib.rs
index a56a1dbd17a..4563c2085c1 100644
--- a/library/core/tests/lib.rs
+++ b/library/core/tests/lib.rs
@@ -21,7 +21,7 @@
 #![feature(core_private_diy_float)]
 #![feature(dec2flt)]
 #![feature(div_duration)]
-#![feature(duration_consts_2)]
+#![feature(duration_consts_float)]
 #![feature(duration_constants)]
 #![feature(exact_size_is_empty)]
 #![feature(extern_types)]
diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs
index afd8d8edaa1..504c3b7e9f9 100644
--- a/library/std/src/lib.rs
+++ b/library/std/src/lib.rs
@@ -275,10 +275,11 @@
 #![feature(decl_macro)]
 #![feature(doc_cfg)]
 #![feature(doc_cfg_hide)]
-#![feature(doc_keyword)]
+#![cfg_attr(bootstrap, feature(doc_primitive))]
+#![cfg_attr(bootstrap, feature(doc_keyword))]
+#![cfg_attr(not(bootstrap), feature(rustdoc_internals))]
 #![feature(doc_masked)]
 #![feature(doc_notable_trait)]
-#![feature(doc_primitive)]
 #![feature(dropck_eyepatch)]
 #![feature(duration_checked_float)]
 #![feature(duration_constants)]
diff --git a/library/std/src/process/tests.rs b/library/std/src/process/tests.rs
index 094d2efbdd5..67b747e4107 100644
--- a/library/std/src/process/tests.rs
+++ b/library/std/src/process/tests.rs
@@ -4,15 +4,23 @@ use super::{Command, Output, Stdio};
 use crate::io::ErrorKind;
 use crate::str;
 
-// FIXME(#10380) these tests should not all be ignored on android.
+#[cfg(target_os = "android")]
+fn shell_cmd() -> Command {
+    Command::new("/system/bin/sh")
+}
+
+#[cfg(not(target_os = "android"))]
+fn shell_cmd() -> Command {
+    Command::new("/bin/sh")
+}
 
 #[test]
-#[cfg_attr(any(target_os = "vxworks", target_os = "android"), ignore)]
+#[cfg_attr(any(target_os = "vxworks"), ignore)]
 fn smoke() {
     let p = if cfg!(target_os = "windows") {
         Command::new("cmd").args(&["/C", "exit 0"]).spawn()
     } else {
-        Command::new("true").spawn()
+        shell_cmd().arg("-c").arg("true").spawn()
     };
     assert!(p.is_ok());
     let mut p = p.unwrap();
@@ -29,12 +37,12 @@ fn smoke_failure() {
 }
 
 #[test]
-#[cfg_attr(any(target_os = "vxworks", target_os = "android"), ignore)]
+#[cfg_attr(any(target_os = "vxworks"), ignore)]
 fn exit_reported_right() {
     let p = if cfg!(target_os = "windows") {
         Command::new("cmd").args(&["/C", "exit 1"]).spawn()
     } else {
-        Command::new("false").spawn()
+        shell_cmd().arg("-c").arg("false").spawn()
     };
     assert!(p.is_ok());
     let mut p = p.unwrap();
@@ -44,12 +52,11 @@ fn exit_reported_right() {
 
 #[test]
 #[cfg(unix)]
-#[cfg_attr(any(target_os = "vxworks", target_os = "android"), ignore)]
+#[cfg_attr(any(target_os = "vxworks"), ignore)]
 fn signal_reported_right() {
     use crate::os::unix::process::ExitStatusExt;
 
-    let mut p =
-        Command::new("/bin/sh").arg("-c").arg("read a").stdin(Stdio::piped()).spawn().unwrap();
+    let mut p = shell_cmd().arg("-c").arg("read a").stdin(Stdio::piped()).spawn().unwrap();
     p.kill().unwrap();
     match p.wait().unwrap().signal() {
         Some(9) => {}
@@ -69,31 +76,31 @@ pub fn run_output(mut cmd: Command) -> String {
 }
 
 #[test]
-#[cfg_attr(any(target_os = "vxworks", target_os = "android"), ignore)]
+#[cfg_attr(any(target_os = "vxworks"), ignore)]
 fn stdout_works() {
     if cfg!(target_os = "windows") {
         let mut cmd = Command::new("cmd");
         cmd.args(&["/C", "echo foobar"]).stdout(Stdio::piped());
         assert_eq!(run_output(cmd), "foobar\r\n");
     } else {
-        let mut cmd = Command::new("echo");
-        cmd.arg("foobar").stdout(Stdio::piped());
+        let mut cmd = shell_cmd();
+        cmd.arg("-c").arg("echo foobar").stdout(Stdio::piped());
         assert_eq!(run_output(cmd), "foobar\n");
     }
 }
 
 #[test]
-#[cfg_attr(any(windows, target_os = "android", target_os = "vxworks"), ignore)]
+#[cfg_attr(any(windows, target_os = "vxworks"), ignore)]
 fn set_current_dir_works() {
-    let mut cmd = Command::new("/bin/sh");
+    let mut cmd = shell_cmd();
     cmd.arg("-c").arg("pwd").current_dir("/").stdout(Stdio::piped());
     assert_eq!(run_output(cmd), "/\n");
 }
 
 #[test]
-#[cfg_attr(any(windows, target_os = "android", target_os = "vxworks"), ignore)]
+#[cfg_attr(any(windows, target_os = "vxworks"), ignore)]
 fn stdin_works() {
-    let mut p = Command::new("/bin/sh")
+    let mut p = shell_cmd()
         .arg("-c")
         .arg("read line; echo $line")
         .stdin(Stdio::piped())
@@ -109,19 +116,19 @@ fn stdin_works() {
 }
 
 #[test]
-#[cfg_attr(any(target_os = "vxworks", target_os = "android"), ignore)]
+#[cfg_attr(any(target_os = "vxworks"), ignore)]
 fn test_process_status() {
     let mut status = if cfg!(target_os = "windows") {
         Command::new("cmd").args(&["/C", "exit 1"]).status().unwrap()
     } else {
-        Command::new("false").status().unwrap()
+        shell_cmd().arg("-c").arg("false").status().unwrap()
     };
     assert!(status.code() == Some(1));
 
     status = if cfg!(target_os = "windows") {
         Command::new("cmd").args(&["/C", "exit 0"]).status().unwrap()
     } else {
-        Command::new("true").status().unwrap()
+        shell_cmd().arg("-c").arg("true").status().unwrap()
     };
     assert!(status.success());
 }
@@ -135,12 +142,12 @@ fn test_process_output_fail_to_start() {
 }
 
 #[test]
-#[cfg_attr(any(target_os = "vxworks", target_os = "android"), ignore)]
+#[cfg_attr(any(target_os = "vxworks"), ignore)]
 fn test_process_output_output() {
     let Output { status, stdout, stderr } = if cfg!(target_os = "windows") {
         Command::new("cmd").args(&["/C", "echo hello"]).output().unwrap()
     } else {
-        Command::new("echo").arg("hello").output().unwrap()
+        shell_cmd().arg("-c").arg("echo hello").output().unwrap()
     };
     let output_str = str::from_utf8(&stdout).unwrap();
 
@@ -150,7 +157,7 @@ fn test_process_output_output() {
 }
 
 #[test]
-#[cfg_attr(any(target_os = "vxworks", target_os = "android"), ignore)]
+#[cfg_attr(any(target_os = "vxworks"), ignore)]
 fn test_process_output_error() {
     let Output { status, stdout, stderr } = if cfg!(target_os = "windows") {
         Command::new("cmd").args(&["/C", "mkdir ."]).output().unwrap()
@@ -158,41 +165,42 @@ fn test_process_output_error() {
         Command::new("mkdir").arg("./").output().unwrap()
     };
 
-    assert!(status.code() == Some(1));
+    assert!(status.code().is_some());
+    assert!(status.code() != Some(0));
     assert_eq!(stdout, Vec::new());
     assert!(!stderr.is_empty());
 }
 
 #[test]
-#[cfg_attr(any(target_os = "vxworks", target_os = "android"), ignore)]
+#[cfg_attr(any(target_os = "vxworks"), ignore)]
 fn test_finish_once() {
     let mut prog = if cfg!(target_os = "windows") {
         Command::new("cmd").args(&["/C", "exit 1"]).spawn().unwrap()
     } else {
-        Command::new("false").spawn().unwrap()
+        shell_cmd().arg("-c").arg("false").spawn().unwrap()
     };
     assert!(prog.wait().unwrap().code() == Some(1));
 }
 
 #[test]
-#[cfg_attr(any(target_os = "vxworks", target_os = "android"), ignore)]
+#[cfg_attr(any(target_os = "vxworks"), ignore)]
 fn test_finish_twice() {
     let mut prog = if cfg!(target_os = "windows") {
         Command::new("cmd").args(&["/C", "exit 1"]).spawn().unwrap()
     } else {
-        Command::new("false").spawn().unwrap()
+        shell_cmd().arg("-c").arg("false").spawn().unwrap()
     };
     assert!(prog.wait().unwrap().code() == Some(1));
     assert!(prog.wait().unwrap().code() == Some(1));
 }
 
 #[test]
-#[cfg_attr(any(target_os = "vxworks", target_os = "android"), ignore)]
+#[cfg_attr(any(target_os = "vxworks"), ignore)]
 fn test_wait_with_output_once() {
     let prog = if cfg!(target_os = "windows") {
         Command::new("cmd").args(&["/C", "echo hello"]).stdout(Stdio::piped()).spawn().unwrap()
     } else {
-        Command::new("echo").arg("hello").stdout(Stdio::piped()).spawn().unwrap()
+        shell_cmd().arg("-c").arg("echo hello").stdout(Stdio::piped()).spawn().unwrap()
     };
 
     let Output { status, stdout, stderr } = prog.wait_with_output().unwrap();
diff --git a/src/doc/rustdoc/src/unstable-features.md b/src/doc/rustdoc/src/unstable-features.md
index 8da1d22a4d1..6e52127591c 100644
--- a/src/doc/rustdoc/src/unstable-features.md
+++ b/src/doc/rustdoc/src/unstable-features.md
@@ -138,7 +138,8 @@ This is for Rust compiler internal use only.
 
 Since primitive types are defined in the compiler, there's no place to attach documentation
 attributes. The `#[doc(primitive)]` attribute is used by the standard library to provide a way
-to generate documentation for primitive types, and requires `#![feature(doc_primitive)]` to enable.
+to generate documentation for primitive types, and requires `#![feature(rustdoc_internals)]` to
+enable.
 
 ## Document keywords
 
@@ -149,7 +150,7 @@ Rust keywords are documented in the standard library (look for `match` for examp
 To do so, the `#[doc(keyword = "...")]` attribute is used. Example:
 
 ```rust
-#![feature(doc_keyword)]
+#![feature(rustdoc_internals)]
 
 /// Some documentation about the keyword.
 #[doc(keyword = "keyword")]
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index ffd09663f82..24baca285c6 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -682,7 +682,7 @@ fn short_item_info(
 
     // Render unstable items. But don't render "rustc_private" crates (internal compiler crates).
     // Those crates are permanently unstable so it makes no sense to render "unstable" everywhere.
-    if let Some((StabilityLevel::Unstable { reason, issue, .. }, feature)) = item
+    if let Some((StabilityLevel::Unstable { reason: _, issue, .. }, feature)) = item
         .stability(cx.tcx())
         .as_ref()
         .filter(|stab| stab.feature != sym::rustc_private)
@@ -702,22 +702,6 @@ fn short_item_info(
 
         message.push_str(&format!(" ({})", feature));
 
-        if let Some(unstable_reason) = reason {
-            let mut ids = cx.id_map.borrow_mut();
-            message = format!(
-                "<details><summary>{}</summary>{}</details>",
-                message,
-                MarkdownHtml(
-                    &unstable_reason.as_str(),
-                    &mut ids,
-                    error_codes,
-                    cx.shared.edition(),
-                    &cx.shared.playground,
-                )
-                .into_string()
-            );
-        }
-
         extra_info.push(format!("<div class=\"stab unstable\">{}</div>", message));
     }
 
diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css
index 27f9c183b8e..0f3eb2ca07d 100644
--- a/src/librustdoc/html/static/css/rustdoc.css
+++ b/src/librustdoc/html/static/css/rustdoc.css
@@ -964,8 +964,6 @@ body.blur > :not(#help) {
 	display: table;
 }
 .stab {
-	border-width: 1px;
-	border-style: solid;
 	padding: 3px;
 	margin-bottom: 5px;
 	font-size: 90%;
@@ -976,7 +974,7 @@ body.blur > :not(#help) {
 }
 
 .stab .emoji {
-	font-size: 1.5em;
+	font-size: 1.2em;
 }
 
 /* Black one-pixel outline around emoji shapes */
diff --git a/src/librustdoc/html/static/css/themes/ayu.css b/src/librustdoc/html/static/css/themes/ayu.css
index 85fb34b3959..13e8dc85a24 100644
--- a/src/librustdoc/html/static/css/themes/ayu.css
+++ b/src/librustdoc/html/static/css/themes/ayu.css
@@ -218,6 +218,8 @@ a {
 }
 a.srclink,
 a#toggle-all-docs,
+a.anchor,
+.section-header a,
 #source-sidebar a,
 pre.rust a,
 .sidebar a,
diff --git a/src/librustdoc/html/static/css/themes/dark.css b/src/librustdoc/html/static/css/themes/dark.css
index 5a6c08bafd2..8caf8a05d50 100644
--- a/src/librustdoc/html/static/css/themes/dark.css
+++ b/src/librustdoc/html/static/css/themes/dark.css
@@ -180,6 +180,8 @@ a {
 }
 a.srclink,
 a#toggle-all-docs,
+a.anchor,
+.section-header a,
 #source-sidebar a,
 pre.rust a,
 .sidebar a,
diff --git a/src/librustdoc/html/static/css/themes/light.css b/src/librustdoc/html/static/css/themes/light.css
index 05f12fc365d..fec71674e63 100644
--- a/src/librustdoc/html/static/css/themes/light.css
+++ b/src/librustdoc/html/static/css/themes/light.css
@@ -175,6 +175,8 @@ a {
 }
 a.srclink,
 a#toggle-all-docs,
+a.anchor,
+.section-header a,
 #source-sidebar a,
 pre.rust a,
 .sidebar a,
diff --git a/src/test/rustdoc-gui/anchors.goml b/src/test/rustdoc-gui/anchors.goml
index 6953009604c..4ce0ed1a4b8 100644
--- a/src/test/rustdoc-gui/anchors.goml
+++ b/src/test/rustdoc-gui/anchors.goml
@@ -1,17 +1,29 @@
+// This test is to ensure that the anchors (`ยง`) have the expected color.
 goto: file://|DOC_PATH|/test_docs/struct.HeavilyDocumentedStruct.html
 
+// This is needed to ensure that the text color is computed.
+show-text: true
+
 // Set the theme to light.
 local-storage: {"rustdoc-theme": "light", "rustdoc-use-system-theme": "false"}
 // We reload the page so the local storage settings are being used.
 reload:
 
-assert-css: ("#toggle-all-docs", {"color": "rgba(0, 0, 0, 0)"})
-assert-css: (".fqn .in-band a:nth-of-type(1)", {"color": "rgba(0, 0, 0, 0)"})
-assert-css: (".fqn .in-band a:nth-of-type(2)", {"color": "rgba(0, 0, 0, 0)"})
-assert-css: (".srclink", {"color": "rgba(0, 0, 0, 0)"})
-assert-css: (".srclink", {"color": "rgba(0, 0, 0, 0)"})
+assert-css: ("#toggle-all-docs", {"color": "rgb(0, 0, 0)"})
+assert-css: (".fqn .in-band a:nth-of-type(1)", {"color": "rgb(0, 0, 0)"})
+assert-css: (".fqn .in-band a:nth-of-type(2)", {"color": "rgb(173, 68, 142)"})
+assert-css: (".srclink", {"color": "rgb(0, 0, 0)"})
+assert-css: (".srclink", {"color": "rgb(0, 0, 0)"})
+
+assert-css: ("#top-doc-prose-title", {"color": "rgb(0, 0, 0)"})
+
+assert-css: (".sidebar a", {"color": "rgb(0, 0, 0)"})
+assert-css: (".in-band a", {"color": "rgb(0, 0, 0)"})
 
-assert-css: ("#top-doc-prose-title", {"color": "rgba(0, 0, 0, 0)"})
+// We move the cursor over the "Implementations" title so the anchor is displayed.
+move-cursor-to: "h2#implementations"
+assert-css: ("h2#implementations a.anchor", {"color": "rgb(0, 0, 0)"})
 
-assert-css: (".sidebar a", {"color": "rgba(0, 0, 0, 0)"})
-assert-css: (".in-band a", {"color": "rgba(0, 0, 0, 0)"})
+// Same thing with the impl block title.
+move-cursor-to: "#impl"
+assert-css: ("#impl a.anchor", {"color": "rgb(0, 0, 0)"})
diff --git a/src/test/rustdoc-gui/headers-color.goml b/src/test/rustdoc-gui/headers-color.goml
index b5be31bd2cc..7002812bb62 100644
--- a/src/test/rustdoc-gui/headers-color.goml
+++ b/src/test/rustdoc-gui/headers-color.goml
@@ -17,6 +17,9 @@ assert-css: ("#impl", {"color": "rgb(197, 197, 197)", "background-color": "rgba(
 goto: file://|DOC_PATH|/test_docs/struct.Foo.html#method.must_use
 assert-css: ("#method\.must_use", {"color": "rgb(197, 197, 197)", "background-color": "rgba(255, 236, 164, 0.06)"}, ALL)
 
+goto: file://|DOC_PATH|/test_docs/index.html
+assert-css: (".section-header a", {"color": "rgb(197, 197, 197)"}, ALL)
+
 // Dark theme
 local-storage: {"rustdoc-theme": "dark", "rustdoc-preferred-dark-theme": "dark", "rustdoc-use-system-theme": "false"}
 goto: file://|DOC_PATH|/test_docs/struct.Foo.html
@@ -30,6 +33,9 @@ assert-css: ("#impl", {"color": "rgb(221, 221, 221)", "background-color": "rgb(7
 goto: file://|DOC_PATH|/test_docs/struct.Foo.html#method.must_use
 assert-css: ("#method\.must_use", {"color": "rgb(221, 221, 221)", "background-color": "rgb(73, 74, 61)"}, ALL)
 
+goto: file://|DOC_PATH|/test_docs/index.html
+assert-css: (".section-header a", {"color": "rgb(221, 221, 221)"}, ALL)
+
 // Light theme
 local-storage: {"rustdoc-theme": "light", "rustdoc-use-system-theme": "false"}
 reload:
@@ -44,3 +50,6 @@ assert-css: ("#impl", {"color": "rgb(0, 0, 0)", "background-color": "rgb(253, 25
 
 goto: file://|DOC_PATH|/test_docs/struct.Foo.html#method.must_use
 assert-css: ("#method\.must_use", {"color": "rgb(0, 0, 0)", "background-color": "rgb(253, 255, 211)"}, ALL)
+
+goto: file://|DOC_PATH|/test_docs/index.html
+assert-css: (".section-header a", {"color": "rgb(0, 0, 0)"}, ALL)
diff --git a/src/test/rustdoc-gui/item-info-width.goml b/src/test/rustdoc-gui/item-info-width.goml
index 44b79e60912..cdc00d34114 100644
--- a/src/test/rustdoc-gui/item-info-width.goml
+++ b/src/test/rustdoc-gui/item-info-width.goml
@@ -4,4 +4,4 @@ goto: file://|DOC_PATH|/lib2/struct.Foo.html
 size: (1100, 800)
 // We check that ".item-info" is bigger than its content.
 assert-css: (".item-info", {"width": "807px"})
-assert-css: (".item-info .stab", {"width": "343px"})
+assert-css: (".item-info .stab", {"width": "341px"})
diff --git a/src/test/rustdoc-gui/src/test_docs/lib.rs b/src/test/rustdoc-gui/src/test_docs/lib.rs
index 14d8b186130..458bcc4780c 100644
--- a/src/test/rustdoc-gui/src/test_docs/lib.rs
+++ b/src/test/rustdoc-gui/src/test_docs/lib.rs
@@ -2,7 +2,7 @@
 //! documentation generated so we can test each different features.
 
 #![crate_name = "test_docs"]
-#![feature(doc_keyword)]
+#![feature(rustdoc_internals)]
 #![feature(doc_cfg)]
 
 use std::convert::AsRef;
diff --git a/src/test/rustdoc-json/primitive.rs b/src/test/rustdoc-json/primitive.rs
index 3a7d6d18c1b..b84c2f7c6ac 100644
--- a/src/test/rustdoc-json/primitive.rs
+++ b/src/test/rustdoc-json/primitive.rs
@@ -1,6 +1,6 @@
 // edition:2018
 
-#![feature(doc_primitive)]
+#![feature(rustdoc_internals)]
 
 #[doc(primitive = "usize")]
 mod usize {}
diff --git a/src/test/rustdoc-ui/coverage/exotic.rs b/src/test/rustdoc-ui/coverage/exotic.rs
index 18f2014d9e4..72b70d6980b 100644
--- a/src/test/rustdoc-ui/coverage/exotic.rs
+++ b/src/test/rustdoc-ui/coverage/exotic.rs
@@ -1,8 +1,7 @@
 // compile-flags:-Z unstable-options --show-coverage
 // check-pass
 
-#![feature(doc_keyword)]
-#![feature(doc_primitive)]
+#![feature(rustdoc_internals)]
 
 //! the features only used in std also have entries in the table, so make sure those get pulled out
 //! properly as well
diff --git a/src/test/rustdoc-ui/invalid-keyword.rs b/src/test/rustdoc-ui/invalid-keyword.rs
index ce2abc69bbd..2d70471c85e 100644
--- a/src/test/rustdoc-ui/invalid-keyword.rs
+++ b/src/test/rustdoc-ui/invalid-keyword.rs
@@ -1,4 +1,4 @@
-#![feature(doc_keyword)]
+#![feature(rustdoc_internals)]
 
 #[doc(keyword = "foo df")] //~ ERROR
 mod foo {}
diff --git a/src/test/rustdoc/issue-32374.rs b/src/test/rustdoc/issue-32374.rs
index 4e92ae49a20..7654a561527 100644
--- a/src/test/rustdoc/issue-32374.rs
+++ b/src/test/rustdoc/issue-32374.rs
@@ -1,7 +1,6 @@
 #![feature(staged_api)]
 #![doc(issue_tracker_base_url = "https://issue_url/")]
-
-#![unstable(feature="test", issue = "32374")]
+#![unstable(feature = "test", issue = "32374")]
 
 // @matches issue_32374/index.html '//*[@class="item-left unstable deprecated module-item"]/span[@class="stab deprecated"]' \
 //      'Deprecated'
@@ -23,12 +22,6 @@ pub struct T;
 //      '๐Ÿ‘Ž Deprecated since 1.0.0: deprecated'
 // @has issue_32374/struct.U.html '//*[@class="stab unstable"]' \
 //      '๐Ÿ”ฌ This is a nightly-only experimental API. (test #32374)'
-// @has issue_32374/struct.U.html '//details' \
-//      '๐Ÿ”ฌ This is a nightly-only experimental API. (test #32374)'
-// @has issue_32374/struct.U.html '//summary' \
-//      '๐Ÿ”ฌ This is a nightly-only experimental API. (test #32374)'
-// @has issue_32374/struct.U.html '//details/p' \
-//      'unstable'
 #[rustc_deprecated(since = "1.0.0", reason = "deprecated")]
 #[unstable(feature = "test", issue = "32374", reason = "unstable")]
 pub struct U;
diff --git a/src/test/rustdoc/keyword.rs b/src/test/rustdoc/keyword.rs
index 652517c5c90..16f7cac5f51 100644
--- a/src/test/rustdoc/keyword.rs
+++ b/src/test/rustdoc/keyword.rs
@@ -1,6 +1,6 @@
 #![crate_name = "foo"]
 
-#![feature(doc_keyword)]
+#![feature(rustdoc_internals)]
 
 // @has foo/index.html '//h2[@id="keywords"]' 'Keywords'
 // @has foo/index.html '//a[@href="keyword.match.html"]' 'match'
diff --git a/src/test/rustdoc/mixing-doc-comments-and-attrs.rs b/src/test/rustdoc/mixing-doc-comments-and-attrs.rs
new file mode 100644
index 00000000000..c26d3a31987
--- /dev/null
+++ b/src/test/rustdoc/mixing-doc-comments-and-attrs.rs
@@ -0,0 +1,26 @@
+#![crate_name = "foo"]
+
+// @has 'foo/struct.S1.html'
+// @count - '//details[@class="rustdoc-toggle top-doc"]/div[@class="docblock"]/p' \
+//     1
+// @has - '//details[@class="rustdoc-toggle top-doc"]/div[@class="docblock"]/p[1]' \
+//     'Hello world! Goodbye! Hello again!'
+
+#[doc = "Hello world!\n\n"]
+/// Goodbye!
+#[doc = "  Hello again!\n"]
+pub struct S1;
+
+// @has 'foo/struct.S2.html'
+// @count - '//details[@class="rustdoc-toggle top-doc"]/div[@class="docblock"]/p' \
+//     2
+// @has - '//details[@class="rustdoc-toggle top-doc"]/div[@class="docblock"]/p[1]' \
+//     'Hello world!'
+// @has - '//details[@class="rustdoc-toggle top-doc"]/div[@class="docblock"]/p[2]' \
+//     'Goodbye! Hello again!'
+
+/// Hello world!
+///
+#[doc = "Goodbye!"]
+/// Hello again!
+pub struct S2;
diff --git a/src/test/rustdoc/tab_title.rs b/src/test/rustdoc/tab_title.rs
index 7dce6092dea..0cc4f147e1c 100644
--- a/src/test/rustdoc/tab_title.rs
+++ b/src/test/rustdoc/tab_title.rs
@@ -1,5 +1,5 @@
 #![crate_name = "foo"]
-#![feature(doc_keyword)]
+#![feature(rustdoc_internals)]
 
 // tests for the html <title> element
 
diff --git a/src/test/ui-fulldeps/internal-lints/existing_doc_keyword.rs b/src/test/ui-fulldeps/internal-lints/existing_doc_keyword.rs
index 053712a4b4e..7783dc40fcf 100644
--- a/src/test/ui-fulldeps/internal-lints/existing_doc_keyword.rs
+++ b/src/test/ui-fulldeps/internal-lints/existing_doc_keyword.rs
@@ -1,7 +1,7 @@
 // compile-flags: -Z unstable-options
 
 #![feature(rustc_private)]
-#![feature(doc_keyword)]
+#![feature(rustdoc_internals)]
 
 #![crate_type = "lib"]
 
diff --git a/src/test/ui/feature-gates/feature-gate-doc_keyword.rs b/src/test/ui/feature-gates/feature-gate-doc_keyword.rs
deleted file mode 100644
index 4bb9a40deb0..00000000000
--- a/src/test/ui/feature-gates/feature-gate-doc_keyword.rs
+++ /dev/null
@@ -1,5 +0,0 @@
-#[doc(keyword = "match")] //~ ERROR: `#[doc(keyword)]` is experimental
-/// wonderful
-mod foo{}
-
-fn main() {}
diff --git a/src/test/ui/feature-gates/feature-gate-doc_keyword.stderr b/src/test/ui/feature-gates/feature-gate-doc_keyword.stderr
deleted file mode 100644
index c5dc7d537fd..00000000000
--- a/src/test/ui/feature-gates/feature-gate-doc_keyword.stderr
+++ /dev/null
@@ -1,12 +0,0 @@
-error[E0658]: `#[doc(keyword)]` is experimental
-  --> $DIR/feature-gate-doc_keyword.rs:1:1
-   |
-LL | #[doc(keyword = "match")]
-   | ^^^^^^^^^^^^^^^^^^^^^^^^^
-   |
-   = note: see issue #51315 <https://github.com/rust-lang/rust/issues/51315> for more information
-   = help: add `#![feature(doc_keyword)]` to the crate attributes to enable
-
-error: aborting due to previous error
-
-For more information about this error, try `rustc --explain E0658`.
diff --git a/src/test/ui/feature-gates/feature-gate-rustdoc_internals.rs b/src/test/ui/feature-gates/feature-gate-rustdoc_internals.rs
new file mode 100644
index 00000000000..d2ff4f62009
--- /dev/null
+++ b/src/test/ui/feature-gates/feature-gate-rustdoc_internals.rs
@@ -0,0 +1,5 @@
+#[doc(keyword = "match")] //~ ERROR: `#[doc(keyword)]` is meant for internal use only
+/// wonderful
+mod foo {}
+
+fn main() {}
diff --git a/src/test/ui/feature-gates/feature-gate-rustdoc_internals.stderr b/src/test/ui/feature-gates/feature-gate-rustdoc_internals.stderr
new file mode 100644
index 00000000000..e96461ac38a
--- /dev/null
+++ b/src/test/ui/feature-gates/feature-gate-rustdoc_internals.stderr
@@ -0,0 +1,12 @@
+error[E0658]: `#[doc(keyword)]` is meant for internal use only
+  --> $DIR/feature-gate-rustdoc_internals.rs:1:1
+   |
+LL | #[doc(keyword = "match")]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: see issue #90418 <https://github.com/rust-lang/rust/issues/90418> for more information
+   = help: add `#![feature(rustdoc_internals)]` to the crate attributes to enable
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0658`.
diff --git a/src/test/ui/rustdoc/doc_keyword.rs b/src/test/ui/rustdoc/doc_keyword.rs
index 4518f77ef93..43b84e5018c 100644
--- a/src/test/ui/rustdoc/doc_keyword.rs
+++ b/src/test/ui/rustdoc/doc_keyword.rs
@@ -1,5 +1,5 @@
 #![crate_type = "lib"]
-#![feature(doc_keyword)]
+#![feature(rustdoc_internals)]
 
 #![doc(keyword = "hello")] //~ ERROR
 
diff --git a/src/test/ui/rustdoc/renamed-features-rustdoc_internals.rs b/src/test/ui/rustdoc/renamed-features-rustdoc_internals.rs
new file mode 100644
index 00000000000..739c624d0c6
--- /dev/null
+++ b/src/test/ui/rustdoc/renamed-features-rustdoc_internals.rs
@@ -0,0 +1,5 @@
+#![feature(doc_keyword)] //~ ERROR
+#![feature(doc_primitive)] //~ ERROR
+#![crate_type = "lib"]
+
+pub fn foo() {}
diff --git a/src/test/ui/rustdoc/renamed-features-rustdoc_internals.stderr b/src/test/ui/rustdoc/renamed-features-rustdoc_internals.stderr
new file mode 100644
index 00000000000..d0979ce97ac
--- /dev/null
+++ b/src/test/ui/rustdoc/renamed-features-rustdoc_internals.stderr
@@ -0,0 +1,19 @@
+error[E0557]: feature has been removed
+  --> $DIR/renamed-features-rustdoc_internals.rs:1:12
+   |
+LL | #![feature(doc_keyword)]
+   |            ^^^^^^^^^^^ feature has been removed
+   |
+   = note: merged into `#![feature(rustdoc_internals)]`
+
+error[E0557]: feature has been removed
+  --> $DIR/renamed-features-rustdoc_internals.rs:2:12
+   |
+LL | #![feature(doc_primitive)]
+   |            ^^^^^^^^^^^^^ feature has been removed
+   |
+   = note: merged into `#![feature(rustdoc_internals)]`
+
+error: aborting due to 2 previous errors
+
+For more information about this error, try `rustc --explain E0557`.