diff --git a/compiler/rustc_abi/src/lib.rs b/compiler/rustc_abi/src/lib.rs
index 1442747fe1e17..12dd1542d7994 100644
--- a/compiler/rustc_abi/src/lib.rs
+++ b/compiler/rustc_abi/src/lib.rs
@@ -1,4 +1,5 @@
 #![cfg_attr(feature = "nightly", feature(step_trait, rustc_attrs, min_specialization))]
+#![cfg_attr(all(not(bootstrap), feature = "nightly"), allow(internal_features))]
 
 use std::fmt;
 #[cfg(feature = "nightly")]
diff --git a/compiler/rustc_arena/src/lib.rs b/compiler/rustc_arena/src/lib.rs
index ba47ebd68cbf5..f4900ece1cc51 100644
--- a/compiler/rustc_arena/src/lib.rs
+++ b/compiler/rustc_arena/src/lib.rs
@@ -23,6 +23,7 @@
 #![deny(unsafe_op_in_unsafe_fn)]
 #![deny(rustc::untranslatable_diagnostic)]
 #![deny(rustc::diagnostic_outside_of_impl)]
+#![cfg_attr(not(bootstrap), allow(internal_features))]
 #![allow(clippy::mut_from_ref)] // Arena allocators are one of the places where this pattern is fine.
 
 use smallvec::SmallVec;
diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs
index 317af8ae1f62c..efe525c224d0a 100644
--- a/compiler/rustc_borrowck/src/lib.rs
+++ b/compiler/rustc_borrowck/src/lib.rs
@@ -11,6 +11,7 @@
 #![feature(trusted_step)]
 #![feature(try_blocks)]
 #![recursion_limit = "256"]
+#![cfg_attr(not(bootstrap), allow(internal_features))]
 
 #[macro_use]
 extern crate rustc_middle;
diff --git a/compiler/rustc_data_structures/src/lib.rs b/compiler/rustc_data_structures/src/lib.rs
index 3deb9c5c2f5ce..33772089744d1 100644
--- a/compiler/rustc_data_structures/src/lib.rs
+++ b/compiler/rustc_data_structures/src/lib.rs
@@ -37,6 +37,7 @@
 #![allow(rustc::potential_query_instability)]
 #![deny(rustc::untranslatable_diagnostic)]
 #![deny(rustc::diagnostic_outside_of_impl)]
+#![cfg_attr(not(bootstrap), allow(internal_features))]
 #![deny(unsafe_op_in_unsafe_fn)]
 
 #[macro_use]
diff --git a/compiler/rustc_error_codes/src/error_codes/E0092.md b/compiler/rustc_error_codes/src/error_codes/E0092.md
index 5cbe2a188b022..84ec0656d1ac1 100644
--- a/compiler/rustc_error_codes/src/error_codes/E0092.md
+++ b/compiler/rustc_error_codes/src/error_codes/E0092.md
@@ -4,6 +4,7 @@ Erroneous code example:
 
 ```compile_fail,E0092
 #![feature(intrinsics)]
+#![allow(internal_features)]
 
 extern "rust-intrinsic" {
     fn atomic_foo(); // error: unrecognized atomic operation
@@ -17,6 +18,7 @@ functions are defined in `compiler/rustc_codegen_llvm/src/intrinsic.rs` and in
 
 ```
 #![feature(intrinsics)]
+#![allow(internal_features)]
 
 extern "rust-intrinsic" {
     fn atomic_fence_seqcst(); // ok!
diff --git a/compiler/rustc_error_codes/src/error_codes/E0093.md b/compiler/rustc_error_codes/src/error_codes/E0093.md
index b1683cf4fc4ae..2bda4d74f726d 100644
--- a/compiler/rustc_error_codes/src/error_codes/E0093.md
+++ b/compiler/rustc_error_codes/src/error_codes/E0093.md
@@ -4,6 +4,7 @@ Erroneous code example:
 
 ```compile_fail,E0093
 #![feature(intrinsics)]
+#![allow(internal_features)]
 
 extern "rust-intrinsic" {
     fn foo(); // error: unrecognized intrinsic function: `foo`
@@ -22,6 +23,7 @@ functions are defined in `compiler/rustc_codegen_llvm/src/intrinsic.rs` and in
 
 ```
 #![feature(intrinsics)]
+#![allow(internal_features)]
 
 extern "rust-intrinsic" {
     fn atomic_fence_seqcst(); // ok!
diff --git a/compiler/rustc_error_codes/src/error_codes/E0094.md b/compiler/rustc_error_codes/src/error_codes/E0094.md
index cc546bdbb3b35..67a8c3678c5ff 100644
--- a/compiler/rustc_error_codes/src/error_codes/E0094.md
+++ b/compiler/rustc_error_codes/src/error_codes/E0094.md
@@ -4,6 +4,7 @@ Erroneous code example:
 
 ```compile_fail,E0094
 #![feature(intrinsics)]
+#![allow(internal_features)]
 
 extern "rust-intrinsic" {
     #[rustc_safe_intrinsic]
@@ -18,6 +19,7 @@ Example:
 
 ```
 #![feature(intrinsics)]
+#![allow(internal_features)]
 
 extern "rust-intrinsic" {
     #[rustc_safe_intrinsic]
diff --git a/compiler/rustc_error_codes/src/error_codes/E0208.md b/compiler/rustc_error_codes/src/error_codes/E0208.md
index c6db9b5d61bea..2b811b4b8500f 100644
--- a/compiler/rustc_error_codes/src/error_codes/E0208.md
+++ b/compiler/rustc_error_codes/src/error_codes/E0208.md
@@ -8,6 +8,7 @@ Erroneous code example:
 ```compile_fail
 // NOTE: this feature is perma-unstable and should *only* be used for
 //       testing purposes.
+#![allow(internal_features)]
 #![feature(rustc_attrs)]
 
 #[rustc_variance]
diff --git a/compiler/rustc_error_codes/src/error_codes/E0211.md b/compiler/rustc_error_codes/src/error_codes/E0211.md
index 8c2462ebd9b86..70f14fffae673 100644
--- a/compiler/rustc_error_codes/src/error_codes/E0211.md
+++ b/compiler/rustc_error_codes/src/error_codes/E0211.md
@@ -5,6 +5,7 @@ used. Erroneous code examples:
 
 ```compile_fail
 #![feature(intrinsics)]
+#![allow(internal_features)]
 
 extern "rust-intrinsic" {
     #[rustc_safe_intrinsic]
@@ -41,6 +42,7 @@ For the first code example, please check the function definition. Example:
 
 ```
 #![feature(intrinsics)]
+#![allow(internal_features)]
 
 extern "rust-intrinsic" {
     #[rustc_safe_intrinsic]
diff --git a/compiler/rustc_error_codes/src/error_codes/E0230.md b/compiler/rustc_error_codes/src/error_codes/E0230.md
index cfb72e74319c1..87ea90e73c902 100644
--- a/compiler/rustc_error_codes/src/error_codes/E0230.md
+++ b/compiler/rustc_error_codes/src/error_codes/E0230.md
@@ -5,6 +5,7 @@ compiled:
 
 ```compile_fail,E0230
 #![feature(rustc_attrs)]
+#![allow(internal_features)]
 
 #[rustc_on_unimplemented = "error on `{Self}` with params `<{A},{B}>`"] // error
 trait BadAnnotation<A> {}
diff --git a/compiler/rustc_error_codes/src/error_codes/E0231.md b/compiler/rustc_error_codes/src/error_codes/E0231.md
index 23a0a88ecdd9b..a1aaf90df496a 100644
--- a/compiler/rustc_error_codes/src/error_codes/E0231.md
+++ b/compiler/rustc_error_codes/src/error_codes/E0231.md
@@ -5,6 +5,7 @@ compiled:
 
 ```compile_fail,E0231
 #![feature(rustc_attrs)]
+#![allow(internal_features)]
 
 #[rustc_on_unimplemented = "error on `{Self}` with params `<{A},{}>`"] // error!
 trait BadAnnotation<A> {}
diff --git a/compiler/rustc_error_codes/src/error_codes/E0232.md b/compiler/rustc_error_codes/src/error_codes/E0232.md
index b310caefa6e31..0e50cf589ee69 100644
--- a/compiler/rustc_error_codes/src/error_codes/E0232.md
+++ b/compiler/rustc_error_codes/src/error_codes/E0232.md
@@ -5,6 +5,7 @@ compiled:
 
 ```compile_fail,E0232
 #![feature(rustc_attrs)]
+#![allow(internal_features)]
 
 #[rustc_on_unimplemented(lorem="")] // error!
 trait BadAnnotation {}
diff --git a/compiler/rustc_error_codes/src/error_codes/E0264.md b/compiler/rustc_error_codes/src/error_codes/E0264.md
index e2a27f7b10672..d790607622992 100644
--- a/compiler/rustc_error_codes/src/error_codes/E0264.md
+++ b/compiler/rustc_error_codes/src/error_codes/E0264.md
@@ -4,6 +4,7 @@ Erroneous code example:
 
 ```compile_fail,E0264
 #![feature(lang_items)]
+#![allow(internal_features)]
 
 extern "C" {
     #[lang = "cake"] // error: unknown external lang item: `cake`
@@ -16,6 +17,7 @@ A list of available external lang items is available in
 
 ```
 #![feature(lang_items)]
+#![allow(internal_features)]
 
 extern "C" {
     #[lang = "panic_impl"] // ok!
diff --git a/compiler/rustc_error_codes/src/error_codes/E0539.md b/compiler/rustc_error_codes/src/error_codes/E0539.md
index c53d60a5f4757..cd28afbc48de2 100644
--- a/compiler/rustc_error_codes/src/error_codes/E0539.md
+++ b/compiler/rustc_error_codes/src/error_codes/E0539.md
@@ -4,6 +4,7 @@ Erroneous code example:
 
 ```compile_fail,E0539
 #![feature(staged_api)]
+#![allow(internal_features)]
 #![stable(since = "1.0.0", feature = "test")]
 
 #[deprecated(note)] // error!
@@ -28,6 +29,7 @@ To fix these issues you need to give required key-value pairs.
 
 ```
 #![feature(staged_api)]
+#![allow(internal_features)]
 #![stable(since = "1.0.0", feature = "test")]
 
 #[deprecated(since = "1.39.0", note = "reason")] // ok!
diff --git a/compiler/rustc_error_codes/src/error_codes/E0542.md b/compiler/rustc_error_codes/src/error_codes/E0542.md
index c69e574179b10..be186dbd2cc3b 100644
--- a/compiler/rustc_error_codes/src/error_codes/E0542.md
+++ b/compiler/rustc_error_codes/src/error_codes/E0542.md
@@ -4,6 +4,7 @@ Erroneous code example:
 
 ```compile_fail,E0542
 #![feature(staged_api)]
+#![allow(internal_features)]
 #![stable(since = "1.0.0", feature = "test")]
 
 #[stable(feature = "_stable_fn")] // invalid
@@ -23,6 +24,7 @@ To fix this issue, you need to provide the `since` field. Example:
 
 ```
 #![feature(staged_api)]
+#![allow(internal_features)]
 #![stable(since = "1.0.0", feature = "test")]
 
 #[stable(feature = "_stable_fn", since = "1.0.0")] // ok!
diff --git a/compiler/rustc_error_codes/src/error_codes/E0543.md b/compiler/rustc_error_codes/src/error_codes/E0543.md
index d0b2e2f7a7d0f..5051c72a15149 100644
--- a/compiler/rustc_error_codes/src/error_codes/E0543.md
+++ b/compiler/rustc_error_codes/src/error_codes/E0543.md
@@ -4,6 +4,7 @@ Erroneous code example:
 
 ```compile_fail,E0543
 #![feature(staged_api)]
+#![allow(internal_features)]
 #![stable(since = "1.0.0", feature = "test")]
 
 #[stable(since = "0.1.0", feature = "_deprecated_fn")]
@@ -17,6 +18,7 @@ To fix this issue, you need to provide the `note` field. Example:
 
 ```
 #![feature(staged_api)]
+#![allow(internal_features)]
 #![stable(since = "1.0.0", feature = "test")]
 
 #[stable(since = "0.1.0", feature = "_deprecated_fn")]
diff --git a/compiler/rustc_error_codes/src/error_codes/E0544.md b/compiler/rustc_error_codes/src/error_codes/E0544.md
index 2227e2a06bf92..202401f9d45f9 100644
--- a/compiler/rustc_error_codes/src/error_codes/E0544.md
+++ b/compiler/rustc_error_codes/src/error_codes/E0544.md
@@ -4,6 +4,7 @@ Erroneous code example:
 
 ```compile_fail,E0544
 #![feature(staged_api)]
+#![allow(internal_features)]
 #![stable(since = "1.0.0", feature = "rust1")]
 
 #[stable(feature = "rust1", since = "1.0.0")]
@@ -15,6 +16,7 @@ To fix this issue, ensure that each item has at most one stability attribute.
 
 ```
 #![feature(staged_api)]
+#![allow(internal_features)]
 #![stable(since = "1.0.0", feature = "rust1")]
 
 #[stable(feature = "test", since = "2.0.0")] // ok!
diff --git a/compiler/rustc_error_codes/src/error_codes/E0545.md b/compiler/rustc_error_codes/src/error_codes/E0545.md
index 7aba084f4d3aa..880378ebd3a27 100644
--- a/compiler/rustc_error_codes/src/error_codes/E0545.md
+++ b/compiler/rustc_error_codes/src/error_codes/E0545.md
@@ -4,6 +4,7 @@ Erroneous code example:
 
 ```compile_fail,E0545
 #![feature(staged_api)]
+#![allow(internal_features)]
 #![stable(since = "1.0.0", feature = "test")]
 
 #[unstable(feature = "_unstable_fn", issue = "0")] // invalid
@@ -18,6 +19,7 @@ Example:
 
 ```
 #![feature(staged_api)]
+#![allow(internal_features)]
 #![stable(since = "1.0.0", feature = "test")]
 
 #[unstable(feature = "_unstable_fn", issue = "none")] // ok!
diff --git a/compiler/rustc_error_codes/src/error_codes/E0546.md b/compiler/rustc_error_codes/src/error_codes/E0546.md
index a33dcb7a9ac58..8c98eaa07bbaf 100644
--- a/compiler/rustc_error_codes/src/error_codes/E0546.md
+++ b/compiler/rustc_error_codes/src/error_codes/E0546.md
@@ -4,6 +4,7 @@ Erroneous code example:
 
 ```compile_fail,E0546
 #![feature(staged_api)]
+#![allow(internal_features)]
 #![stable(since = "1.0.0", feature = "test")]
 
 #[unstable(issue = "none")] // invalid
@@ -17,6 +18,7 @@ To fix this issue, you need to provide the `feature` field. Example:
 
 ```
 #![feature(staged_api)]
+#![allow(internal_features)]
 #![stable(since = "1.0.0", feature = "test")]
 
 #[unstable(feature = "unstable_fn", issue = "none")] // ok!
diff --git a/compiler/rustc_error_codes/src/error_codes/E0547.md b/compiler/rustc_error_codes/src/error_codes/E0547.md
index 4950325df6400..5b0f7cd44cf8c 100644
--- a/compiler/rustc_error_codes/src/error_codes/E0547.md
+++ b/compiler/rustc_error_codes/src/error_codes/E0547.md
@@ -4,6 +4,7 @@ Erroneous code example:
 
 ```compile_fail,E0547
 #![feature(staged_api)]
+#![allow(internal_features)]
 #![stable(since = "1.0.0", feature = "test")]
 
 #[unstable(feature = "_unstable_fn")] // invalid
@@ -17,6 +18,7 @@ To fix this issue, you need to provide the `issue` field. Example:
 
 ```
 #![feature(staged_api)]
+#![allow(internal_features)]
 #![stable(since = "1.0.0", feature = "test")]
 
 #[unstable(feature = "_unstable_fn", issue = "none")] // ok!
diff --git a/compiler/rustc_error_codes/src/error_codes/E0549.md b/compiler/rustc_error_codes/src/error_codes/E0549.md
index 70e458a98673c..cc6a47fe253e2 100644
--- a/compiler/rustc_error_codes/src/error_codes/E0549.md
+++ b/compiler/rustc_error_codes/src/error_codes/E0549.md
@@ -5,6 +5,7 @@ Erroneous code example:
 
 ```compile_fail,E0549
 #![feature(staged_api)]
+#![allow(internal_features)]
 #![stable(since = "1.0.0", feature = "test")]
 
 #[deprecated(
@@ -19,6 +20,7 @@ Example:
 
 ```
 #![feature(staged_api)]
+#![allow(internal_features)]
 #![stable(since = "1.0.0", feature = "test")]
 
 #[stable(since = "1.0.0", feature = "test")]
diff --git a/compiler/rustc_error_codes/src/error_codes/E0622.md b/compiler/rustc_error_codes/src/error_codes/E0622.md
index 3ba3ed10e5c62..5d71ee9949d86 100644
--- a/compiler/rustc_error_codes/src/error_codes/E0622.md
+++ b/compiler/rustc_error_codes/src/error_codes/E0622.md
@@ -4,6 +4,8 @@ Erroneous code example:
 
 ```compile_fail,E0622
 #![feature(intrinsics)]
+#![allow(internal_features)]
+
 extern "rust-intrinsic" {
     pub static breakpoint: fn(); // error: intrinsic must be a function
 }
@@ -17,6 +19,8 @@ error, just declare a function. Example:
 
 ```no_run
 #![feature(intrinsics)]
+#![allow(internal_features)]
+
 extern "rust-intrinsic" {
     pub fn breakpoint(); // ok!
 }
diff --git a/compiler/rustc_error_codes/src/error_codes/E0773.md b/compiler/rustc_error_codes/src/error_codes/E0773.md
index b19a58bf33d2c..aa65a475a4f9d 100644
--- a/compiler/rustc_error_codes/src/error_codes/E0773.md
+++ b/compiler/rustc_error_codes/src/error_codes/E0773.md
@@ -5,6 +5,7 @@ Erroneous code example:
 ```compile_fail,E0773
 #![feature(decl_macro)]
 #![feature(rustc_attrs)]
+#![allow(internal_features)]
 
 #[rustc_builtin_macro]
 pub macro test($item:item) {
@@ -24,6 +25,7 @@ To fix the issue, remove the duplicate declaration:
 ```
 #![feature(decl_macro)]
 #![feature(rustc_attrs)]
+#![allow(internal_features)]
 
 #[rustc_builtin_macro]
 pub macro test($item:item) {
diff --git a/compiler/rustc_error_codes/src/error_codes/E0789.md b/compiler/rustc_error_codes/src/error_codes/E0789.md
index 89b7cd422fe96..2c0018cc799f4 100644
--- a/compiler/rustc_error_codes/src/error_codes/E0789.md
+++ b/compiler/rustc_error_codes/src/error_codes/E0789.md
@@ -10,6 +10,7 @@ Erroneous code example:
 //       used outside of the compiler and standard library.
 #![feature(rustc_attrs)]
 #![feature(staged_api)]
+#![allow(internal_features)]
 
 #![unstable(feature = "foo_module", reason = "...", issue = "123")]
 
diff --git a/compiler/rustc_error_messages/src/lib.rs b/compiler/rustc_error_messages/src/lib.rs
index 77e2c900c0d7c..3bf15505090cf 100644
--- a/compiler/rustc_error_messages/src/lib.rs
+++ b/compiler/rustc_error_messages/src/lib.rs
@@ -4,6 +4,7 @@
 #![feature(type_alias_impl_trait)]
 #![deny(rustc::untranslatable_diagnostic)]
 #![deny(rustc::diagnostic_outside_of_impl)]
+#![cfg_attr(not(bootstrap), allow(internal_features))]
 
 #[macro_use]
 extern crate tracing;
diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs
index 3d1639db4afb0..3b75362912826 100644
--- a/compiler/rustc_errors/src/lib.rs
+++ b/compiler/rustc_errors/src/lib.rs
@@ -15,6 +15,7 @@
 #![feature(box_patterns)]
 #![feature(error_reporter)]
 #![allow(incomplete_features)]
+#![cfg_attr(not(bootstrap), allow(internal_features))]
 
 #[macro_use]
 extern crate rustc_macros;
diff --git a/compiler/rustc_expand/src/lib.rs b/compiler/rustc_expand/src/lib.rs
index 83a5043b0aa6f..c4a9b2ace9a02 100644
--- a/compiler/rustc_expand/src/lib.rs
+++ b/compiler/rustc_expand/src/lib.rs
@@ -11,6 +11,7 @@
 #![feature(try_blocks)]
 #![recursion_limit = "256"]
 #![deny(rustc::untranslatable_diagnostic)]
+#![cfg_attr(not(bootstrap), allow(internal_features))]
 
 #[macro_use]
 extern crate rustc_macros;
diff --git a/compiler/rustc_feature/src/active.rs b/compiler/rustc_feature/src/active.rs
index bbc3d291e2007..ff25f744dedfe 100644
--- a/compiler/rustc_feature/src/active.rs
+++ b/compiler/rustc_feature/src/active.rs
@@ -16,12 +16,22 @@ macro_rules! set {
     }};
 }
 
+#[derive(PartialEq)]
+enum FeatureStatus {
+    Default,
+    Incomplete,
+    Internal,
+}
+
 macro_rules! declare_features {
-    (__status_to_bool active) => {
-        false
+    (__status_to_enum active) => {
+        FeatureStatus::Default
     };
-    (__status_to_bool incomplete) => {
-        true
+    (__status_to_enum incomplete) => {
+        FeatureStatus::Incomplete
+    };
+    (__status_to_enum internal) => {
+        FeatureStatus::Internal
     };
     ($(
         $(#[doc = $doc:tt])* ($status:ident, $feature:ident, $ver:expr, $issue:expr, $edition:expr),
@@ -83,7 +93,7 @@ macro_rules! declare_features {
             pub fn incomplete(&self, feature: Symbol) -> bool {
                 match feature {
                     $(
-                        sym::$feature => declare_features!(__status_to_bool $status),
+                        sym::$feature => declare_features!(__status_to_enum $status) == FeatureStatus::Incomplete,
                     )*
                     // accepted and removed features aren't in this file but are never incomplete
                     _ if self.declared_lang_features.iter().any(|f| f.0 == feature) => false,
@@ -91,6 +101,22 @@ macro_rules! declare_features {
                     _ => panic!("`{}` was not listed in `declare_features`", feature),
                 }
             }
+
+            /// Some features are internal to the compiler and standard library and should not
+            /// be used in normal projects. We warn the user about these
+            /// to alert them.
+            pub fn internal(&self, feature: Symbol) -> bool {
+                match feature {
+                    $(
+                        sym::$feature => declare_features!(__status_to_enum $status) == FeatureStatus::Internal,
+                    )*
+                    // accepted and removed features aren't in this file but are never internal
+                    // (a removed feature might have been internal, but it doesn't matter anymore)
+                    _ if self.declared_lang_features.iter().any(|f| f.0 == feature) => false,
+                    _ if self.declared_lib_features.iter().any(|f| f.0 == feature) => false,
+                    _ => panic!("`{}` was not listed in `declare_features`", feature),
+                }
+            }
         }
     };
 }
@@ -137,29 +163,29 @@ declare_features! (
     /// Allows using the `vectorcall` ABI.
     (active, abi_vectorcall, "1.7.0", None, None),
     /// Allows using `#![needs_allocator]`, an implementation detail of `#[global_allocator]`.
-    (active, allocator_internals, "1.20.0", None, None),
+    (internal, allocator_internals, "1.20.0", None, None),
     /// Allows using `#[allow_internal_unsafe]`. This is an
     /// attribute on `macro_rules!` and can't use the attribute handling
     /// below (it has to be checked before expansion possibly makes
     /// macros disappear).
-    (active, allow_internal_unsafe, "1.0.0", None, None),
+    (internal, allow_internal_unsafe, "1.0.0", None, None),
     /// Allows using `#[allow_internal_unstable]`. This is an
     /// attribute on `macro_rules!` and can't use the attribute handling
     /// below (it has to be checked before expansion possibly makes
     /// macros disappear).
-    (active, allow_internal_unstable, "1.0.0", None, None),
+    (internal, allow_internal_unstable, "1.0.0", None, None),
     /// Allows using anonymous lifetimes in argument-position impl-trait.
     (active, anonymous_lifetime_in_impl_trait, "1.63.0", None, None),
     /// Allows identifying the `compiler_builtins` crate.
-    (active, compiler_builtins, "1.13.0", None, None),
+    (internal, compiler_builtins, "1.13.0", None, None),
     /// Allows writing custom MIR
-    (active, custom_mir, "1.65.0", None, None),
+    (internal, custom_mir, "1.65.0", None, None),
     /// Outputs useful `assert!` messages
     (active, generic_assert, "1.63.0", None, None),
     /// Allows using the `rust-intrinsic`'s "ABI".
-    (active, intrinsics, "1.0.0", None, None),
+    (internal, intrinsics, "1.0.0", None, None),
     /// Allows using `#[lang = ".."]` attribute for linking items to special compiler logic.
-    (active, lang_items, "1.0.0", None, None),
+    (internal, lang_items, "1.0.0", None, None),
     /// Allows `#[link(..., cfg(..))]`; perma-unstable per #37406
     (active, link_cfg, "1.14.0", None, None),
     /// Allows the `multiple_supertrait_upcastable` lint.
@@ -167,22 +193,22 @@ declare_features! (
     /// Allow negative trait bounds. This is an internal-only feature for testing the trait solver!
     (incomplete, negative_bounds, "1.71.0", None, None),
     /// Allows using `#[omit_gdb_pretty_printer_section]`.
-    (active, omit_gdb_pretty_printer_section, "1.5.0", None, None),
+    (internal, omit_gdb_pretty_printer_section, "1.5.0", None, None),
     /// Allows using `#[prelude_import]` on glob `use` items.
-    (active, prelude_import, "1.2.0", None, None),
+    (internal, prelude_import, "1.2.0", None, None),
     /// Used to identify crates that contain the profiler runtime.
-    (active, profiler_runtime, "1.18.0", None, None),
+    (internal, profiler_runtime, "1.18.0", None, None),
     /// Allows using `rustc_*` attributes (RFC 572).
-    (active, rustc_attrs, "1.0.0", None, None),
+    (internal, rustc_attrs, "1.0.0", None, None),
     /// Allows using the `#[stable]` and `#[unstable]` attributes.
-    (active, staged_api, "1.0.0", None, None),
+    (internal, staged_api, "1.0.0", None, None),
     /// Added for testing E0705; perma-unstable.
-    (active, test_2018_feature, "1.31.0", None, Some(Edition::Edition2018)),
+    (internal, test_2018_feature, "1.31.0", None, Some(Edition::Edition2018)),
     /// Added for testing unstable lints; perma-unstable.
-    (active, test_unstable_lint, "1.60.0", None, None),
+    (internal, test_unstable_lint, "1.60.0", None, None),
     /// Allows non-`unsafe` —and thus, unsound— access to `Pin` constructions.
-    /// Marked `incomplete` since perma-unstable and unsound.
-    (incomplete, unsafe_pin_internals, "1.60.0", None, None),
+    /// Marked `internal` since perma-unstable and unsound.
+    (internal, unsafe_pin_internals, "1.60.0", None, None),
     /// Use for stable + negative coherence and strict coherence depending on trait's
     /// rustc_strict_coherence value.
     (active, with_negative_coherence, "1.60.0", None, None),
@@ -216,19 +242,19 @@ declare_features! (
     /// Allows using the `#[linkage = ".."]` attribute.
     (active, linkage, "1.0.0", Some(29603), None),
     /// Allows declaring with `#![needs_panic_runtime]` that a panic runtime is needed.
-    (active, needs_panic_runtime, "1.10.0", Some(32837), None),
+    (internal, needs_panic_runtime, "1.10.0", Some(32837), None),
     /// Allows using `+bundled,+whole-archive` native libs.
     (active, packed_bundled_libs, "1.69.0", Some(108081), None),
     /// Allows using the `#![panic_runtime]` attribute.
-    (active, panic_runtime, "1.10.0", Some(32837), None),
+    (internal, panic_runtime, "1.10.0", Some(32837), None),
     /// Allows using `#[rustc_allow_const_fn_unstable]`.
     /// This is an attribute on `const fn` for the same
     /// purpose as `#[allow_internal_unstable]`.
-    (active, rustc_allow_const_fn_unstable, "1.49.0", Some(69399), None),
+    (internal, 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(keyword)`.
-    (active, rustdoc_internals, "1.58.0", Some(90418), None),
+    (internal, rustdoc_internals, "1.58.0", Some(90418), None),
     /// Allows using the `rustdoc::missing_doc_code_examples` lint
     (active, rustdoc_missing_doc_code_examples, "1.31.0", Some(101730), None),
     /// Allows using `#[start]` on a function indicating that it is the program entrypoint.
diff --git a/compiler/rustc_hir/src/lib.rs b/compiler/rustc_hir/src/lib.rs
index 616de57dc6372..34214931a081d 100644
--- a/compiler/rustc_hir/src/lib.rs
+++ b/compiler/rustc_hir/src/lib.rs
@@ -13,6 +13,7 @@
 #![recursion_limit = "256"]
 #![deny(rustc::untranslatable_diagnostic)]
 #![deny(rustc::diagnostic_outside_of_impl)]
+#![cfg_attr(not(bootstrap), allow(internal_features))]
 
 #[macro_use]
 extern crate rustc_macros;
diff --git a/compiler/rustc_incremental/src/assert_module_sources.rs b/compiler/rustc_incremental/src/assert_module_sources.rs
index 0111a6d302d4a..8e22ab4083eef 100644
--- a/compiler/rustc_incremental/src/assert_module_sources.rs
+++ b/compiler/rustc_incremental/src/assert_module_sources.rs
@@ -6,6 +6,7 @@
 //!
 //! ```
 //! # #![feature(rustc_attrs)]
+//! # #![allow(internal_features)]
 //! #![rustc_partition_reused(module="spike", cfg="rpass2")]
 //! #![rustc_partition_codegened(module="spike-x", cfg="rpass2")]
 //! ```
diff --git a/compiler/rustc_index/src/lib.rs b/compiler/rustc_index/src/lib.rs
index 6fd9f34b29ef1..9942c70c4ae71 100644
--- a/compiler/rustc_index/src/lib.rs
+++ b/compiler/rustc_index/src/lib.rs
@@ -12,6 +12,7 @@
         test
     )
 )]
+#![cfg_attr(all(not(bootstrap), feature = "nightly"), allow(internal_features))]
 
 #[cfg(feature = "nightly")]
 pub mod bit_set;
diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl
index f482e3d7c1280..e696a18207bce 100644
--- a/compiler/rustc_lint/messages.ftl
+++ b/compiler/rustc_lint/messages.ftl
@@ -72,6 +72,9 @@ lint_builtin_incomplete_features = the feature `{$name}` is incomplete and may n
     .note = see issue #{$n} <https://github.com/rust-lang/rust/issues/{$n}> for more information
     .help = consider using `min_{$name}` instead, which is more stable and complete
 
+lint_builtin_internal_features = the feature `{$name}` is internal to the compiler or standard library
+    .note = using it is strongly discouraged
+
 lint_builtin_keyword_idents = `{$kw}` is a keyword in the {$next} edition
     .suggestion = you can use a raw identifier to stay compatible
 
diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs
index e6917f4b2d3b7..cc6d5330c3356 100644
--- a/compiler/rustc_lint/src/builtin.rs
+++ b/compiler/rustc_lint/src/builtin.rs
@@ -28,8 +28,8 @@ use crate::{
         BuiltinClashingExternSub, BuiltinConstNoMangle, BuiltinDeprecatedAttrLink,
         BuiltinDeprecatedAttrLinkSuggestion, BuiltinDeprecatedAttrUsed, BuiltinDerefNullptr,
         BuiltinEllipsisInclusiveRangePatternsLint, BuiltinExplicitOutlives,
-        BuiltinExplicitOutlivesSuggestion, BuiltinIncompleteFeatures,
-        BuiltinIncompleteFeaturesHelp, BuiltinIncompleteFeaturesNote, BuiltinKeywordIdents,
+        BuiltinExplicitOutlivesSuggestion, BuiltinFeatureIssueNote, BuiltinIncompleteFeatures,
+        BuiltinIncompleteFeaturesHelp, BuiltinInternalFeatures, BuiltinKeywordIdents,
         BuiltinMissingCopyImpl, BuiltinMissingDebugImpl, BuiltinMissingDoc,
         BuiltinMutablesTransmutes, BuiltinNoMangleGeneric, BuiltinNonShorthandFieldPatterns,
         BuiltinSpecialModuleNameUsed, BuiltinTrivialBounds, BuiltinTypeAliasGenericBounds,
@@ -2301,12 +2301,36 @@ declare_lint! {
     "incomplete features that may function improperly in some or all cases"
 }
 
+declare_lint! {
+    /// The `internal_features` lint detects unstable features enabled with
+    /// the [`feature` attribute] that are internal to the compiler or standard
+    /// library.
+    ///
+    /// [`feature` attribute]: https://doc.rust-lang.org/nightly/unstable-book/
+    ///
+    /// ### Example
+    ///
+    /// ```rust,compile_fail
+    /// #![feature(rustc_attrs)]
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// These features are an implementation detail of the compiler and standard
+    /// library and are not supposed to be used in user code.
+    pub INTERNAL_FEATURES,
+    Deny,
+    "internal features are not supposed to be used"
+}
+
 declare_lint_pass!(
     /// Check for used feature gates in `INCOMPLETE_FEATURES` in `rustc_feature/src/active.rs`.
-    IncompleteFeatures => [INCOMPLETE_FEATURES]
+    IncompleteInternalFeatures => [INCOMPLETE_FEATURES, INTERNAL_FEATURES]
 );
 
-impl EarlyLintPass for IncompleteFeatures {
+impl EarlyLintPass for IncompleteInternalFeatures {
     fn check_crate(&mut self, cx: &EarlyContext<'_>, _: &ast::Crate) {
         let features = cx.sess().features_untracked();
         features
@@ -2314,17 +2338,26 @@ impl EarlyLintPass for IncompleteFeatures {
             .iter()
             .map(|(name, span, _)| (name, span))
             .chain(features.declared_lib_features.iter().map(|(name, span)| (name, span)))
-            .filter(|(&name, _)| features.incomplete(name))
+            .filter(|(&name, _)| features.incomplete(name) || features.internal(name))
             .for_each(|(&name, &span)| {
                 let note = rustc_feature::find_feature_issue(name, GateIssue::Language)
-                    .map(|n| BuiltinIncompleteFeaturesNote { n });
-                let help =
-                    HAS_MIN_FEATURES.contains(&name).then_some(BuiltinIncompleteFeaturesHelp);
-                cx.emit_spanned_lint(
-                    INCOMPLETE_FEATURES,
-                    span,
-                    BuiltinIncompleteFeatures { name, note, help },
-                );
+                    .map(|n| BuiltinFeatureIssueNote { n });
+
+                if features.incomplete(name) {
+                    let help =
+                        HAS_MIN_FEATURES.contains(&name).then_some(BuiltinIncompleteFeaturesHelp);
+                    cx.emit_spanned_lint(
+                        INCOMPLETE_FEATURES,
+                        span,
+                        BuiltinIncompleteFeatures { name, note, help },
+                    );
+                } else {
+                    cx.emit_spanned_lint(
+                        INTERNAL_FEATURES,
+                        span,
+                        BuiltinInternalFeatures { name, note },
+                    );
+                }
             });
     }
 }
diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs
index 96fd3ccf77444..2c8b15de04089 100644
--- a/compiler/rustc_lint/src/lib.rs
+++ b/compiler/rustc_lint/src/lib.rs
@@ -40,6 +40,7 @@
 #![recursion_limit = "256"]
 #![deny(rustc::untranslatable_diagnostic)]
 #![deny(rustc::diagnostic_outside_of_impl)]
+#![cfg_attr(not(bootstrap), allow(internal_features))]
 
 #[macro_use]
 extern crate rustc_middle;
@@ -173,7 +174,7 @@ early_lint_methods!(
             WhileTrue: WhileTrue,
             NonAsciiIdents: NonAsciiIdents,
             HiddenUnicodeCodepoints: HiddenUnicodeCodepoints,
-            IncompleteFeatures: IncompleteFeatures,
+            IncompleteInternalFeatures: IncompleteInternalFeatures,
             RedundantSemicolons: RedundantSemicolons,
             UnusedDocComment: UnusedDocComment,
             UnexpectedCfgs: UnexpectedCfgs,
diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs
index a6a48bf4ffa74..a23eb8b6d47ee 100644
--- a/compiler/rustc_lint/src/lints.rs
+++ b/compiler/rustc_lint/src/lints.rs
@@ -405,18 +405,27 @@ pub struct BuiltinExplicitOutlivesSuggestion {
 pub struct BuiltinIncompleteFeatures {
     pub name: Symbol,
     #[subdiagnostic]
-    pub note: Option<BuiltinIncompleteFeaturesNote>,
+    pub note: Option<BuiltinFeatureIssueNote>,
     #[subdiagnostic]
     pub help: Option<BuiltinIncompleteFeaturesHelp>,
 }
 
+#[derive(LintDiagnostic)]
+#[diag(lint_builtin_internal_features)]
+#[note]
+pub struct BuiltinInternalFeatures {
+    pub name: Symbol,
+    #[subdiagnostic]
+    pub note: Option<BuiltinFeatureIssueNote>,
+}
+
 #[derive(Subdiagnostic)]
 #[help(lint_help)]
 pub struct BuiltinIncompleteFeaturesHelp;
 
 #[derive(Subdiagnostic)]
 #[note(lint_note)]
-pub struct BuiltinIncompleteFeaturesNote {
+pub struct BuiltinFeatureIssueNote {
     pub n: NonZeroU32,
 }
 
diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs
index 7e3b6e9e218cc..1a61273180872 100644
--- a/compiler/rustc_lint_defs/src/builtin.rs
+++ b/compiler/rustc_lint_defs/src/builtin.rs
@@ -3925,7 +3925,6 @@ declare_lint! {
     ///
     /// // in crate B
     /// #![feature(non_exhaustive_omitted_patterns_lint)]
-    ///
     /// match Bar::A {
     ///     Bar::A => {},
     ///     #[warn(non_exhaustive_omitted_patterns)]
diff --git a/compiler/rustc_macros/src/lib.rs b/compiler/rustc_macros/src/lib.rs
index 904f8eb5731ec..f4593d0fe736d 100644
--- a/compiler/rustc_macros/src/lib.rs
+++ b/compiler/rustc_macros/src/lib.rs
@@ -7,6 +7,7 @@
 #![allow(rustc::default_hash_types)]
 #![deny(rustc::untranslatable_diagnostic)]
 #![deny(rustc::diagnostic_outside_of_impl)]
+#![cfg_attr(not(bootstrap), allow(internal_features))]
 #![recursion_limit = "128"]
 
 use synstructure::decl_derive;
diff --git a/compiler/rustc_middle/src/lib.rs b/compiler/rustc_middle/src/lib.rs
index 1b125e8e26dbc..f5576b59571f5 100644
--- a/compiler/rustc_middle/src/lib.rs
+++ b/compiler/rustc_middle/src/lib.rs
@@ -64,6 +64,7 @@
 #![feature(macro_metavar_expr)]
 #![recursion_limit = "512"]
 #![allow(rustc::potential_query_instability)]
+#![cfg_attr(not(bootstrap), allow(internal_features))]
 
 #[macro_use]
 extern crate bitflags;
diff --git a/compiler/rustc_parse/src/lib.rs b/compiler/rustc_parse/src/lib.rs
index 6712db2669314..892be36aae770 100644
--- a/compiler/rustc_parse/src/lib.rs
+++ b/compiler/rustc_parse/src/lib.rs
@@ -8,6 +8,7 @@
 #![feature(never_type)]
 #![feature(rustc_attrs)]
 #![recursion_limit = "256"]
+#![cfg_attr(not(bootstrap), allow(internal_features))]
 
 #[macro_use]
 extern crate tracing;
diff --git a/compiler/rustc_query_impl/src/lib.rs b/compiler/rustc_query_impl/src/lib.rs
index 4cf0f1305a709..53005ede84378 100644
--- a/compiler/rustc_query_impl/src/lib.rs
+++ b/compiler/rustc_query_impl/src/lib.rs
@@ -11,6 +11,7 @@
 #![allow(rustc::potential_query_instability, unused_parens)]
 #![deny(rustc::untranslatable_diagnostic)]
 #![deny(rustc::diagnostic_outside_of_impl)]
+#![cfg_attr(not(bootstrap), allow(internal_features))]
 
 #[macro_use]
 extern crate rustc_middle;
diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs
index da5e92a075a41..e403386e60c25 100644
--- a/compiler/rustc_resolve/src/lib.rs
+++ b/compiler/rustc_resolve/src/lib.rs
@@ -18,6 +18,7 @@
 #![recursion_limit = "256"]
 #![allow(rustdoc::private_intra_doc_links)]
 #![allow(rustc::potential_query_instability)]
+#![cfg_attr(not(bootstrap), allow(internal_features))]
 
 #[macro_use]
 extern crate tracing;
diff --git a/compiler/rustc_session/src/lib.rs b/compiler/rustc_session/src/lib.rs
index d57aa820fcb47..a270817f3109b 100644
--- a/compiler/rustc_session/src/lib.rs
+++ b/compiler/rustc_session/src/lib.rs
@@ -10,6 +10,7 @@
 #![allow(rustc::potential_query_instability)]
 #![deny(rustc::untranslatable_diagnostic)]
 #![deny(rustc::diagnostic_outside_of_impl)]
+#![cfg_attr(not(bootstrap), allow(internal_features))]
 
 #[macro_use]
 extern crate rustc_macros;
diff --git a/compiler/rustc_span/src/lib.rs b/compiler/rustc_span/src/lib.rs
index fbd9bf0552850..afee5c0fe2b4f 100644
--- a/compiler/rustc_span/src/lib.rs
+++ b/compiler/rustc_span/src/lib.rs
@@ -23,6 +23,7 @@
 #![feature(round_char_boundary)]
 #![deny(rustc::untranslatable_diagnostic)]
 #![deny(rustc::diagnostic_outside_of_impl)]
+#![cfg_attr(not(bootstrap), allow(internal_features))]
 
 #[macro_use]
 extern crate rustc_macros;
diff --git a/compiler/rustc_target/src/lib.rs b/compiler/rustc_target/src/lib.rs
index 3307244a21729..b52002b123968 100644
--- a/compiler/rustc_target/src/lib.rs
+++ b/compiler/rustc_target/src/lib.rs
@@ -19,6 +19,7 @@
 #![feature(step_trait)]
 #![deny(rustc::untranslatable_diagnostic)]
 #![deny(rustc::diagnostic_outside_of_impl)]
+#![cfg_attr(not(bootstrap), allow(internal_features))]
 
 use std::path::{Path, PathBuf};
 
diff --git a/compiler/rustc_type_ir/src/lib.rs b/compiler/rustc_type_ir/src/lib.rs
index c2655d68b795f..1a6a253f36036 100644
--- a/compiler/rustc_type_ir/src/lib.rs
+++ b/compiler/rustc_type_ir/src/lib.rs
@@ -6,6 +6,7 @@
 #![feature(unwrap_infallible)]
 #![deny(rustc::untranslatable_diagnostic)]
 #![deny(rustc::diagnostic_outside_of_impl)]
+#![cfg_attr(not(bootstrap), allow(internal_features))]
 
 #[macro_use]
 extern crate bitflags;
diff --git a/library/alloc/src/lib.rs b/library/alloc/src/lib.rs
index 967ad3a0e6901..80681a7a7cfed 100644
--- a/library/alloc/src/lib.rs
+++ b/library/alloc/src/lib.rs
@@ -88,6 +88,7 @@
 #![warn(missing_docs)]
 #![allow(explicit_outlives_requirements)]
 #![warn(multiple_supertrait_upcastable)]
+#![cfg_attr(not(bootstrap), allow(internal_features))]
 //
 // Library features:
 // tidy-alphabetical-start
diff --git a/library/core/src/intrinsics/mir.rs b/library/core/src/intrinsics/mir.rs
index 5944a0de1a4b2..036edbebbf3eb 100644
--- a/library/core/src/intrinsics/mir.rs
+++ b/library/core/src/intrinsics/mir.rs
@@ -14,6 +14,7 @@
 //!
 //! ```rust
 //! #![feature(core_intrinsics, custom_mir)]
+#![cfg_attr(not(bootstrap), doc = "#![allow(internal_features)]")]
 //!
 //! use core::intrinsics::mir::*;
 //!
@@ -63,6 +64,7 @@
 //!
 //! ```rust
 //! #![feature(core_intrinsics, custom_mir)]
+#![cfg_attr(not(bootstrap), doc = "#![allow(internal_features)]")]
 //!
 //! use core::intrinsics::mir::*;
 //!
@@ -315,6 +317,7 @@ define!(
     /// # Examples
     ///
     /// ```rust
+    #[cfg_attr(not(bootstrap), doc = "#![allow(internal_features)]")]
     /// #![feature(custom_mir, core_intrinsics)]
     ///
     /// use core::intrinsics::mir::*;
diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs
index 37216d6a7210b..ded799160bf89 100644
--- a/library/core/src/lib.rs
+++ b/library/core/src/lib.rs
@@ -96,6 +96,7 @@
 #![allow(explicit_outlives_requirements)]
 #![allow(incomplete_features)]
 #![warn(multiple_supertrait_upcastable)]
+#![cfg_attr(not(bootstrap), allow(internal_features))]
 //
 // Library features:
 // tidy-alphabetical-start
diff --git a/library/panic_abort/src/lib.rs b/library/panic_abort/src/lib.rs
index b193d79b0e1a8..76b3591965851 100644
--- a/library/panic_abort/src/lib.rs
+++ b/library/panic_abort/src/lib.rs
@@ -14,6 +14,7 @@
 #![feature(staged_api)]
 #![feature(rustc_attrs)]
 #![feature(c_unwind)]
+#![cfg_attr(not(bootstrap), allow(internal_features))]
 
 #[cfg(target_os = "android")]
 mod android;
diff --git a/library/panic_unwind/src/lib.rs b/library/panic_unwind/src/lib.rs
index ce78ab82ef942..2e5459321a2c1 100644
--- a/library/panic_unwind/src/lib.rs
+++ b/library/panic_unwind/src/lib.rs
@@ -26,6 +26,7 @@
 #![feature(c_unwind)]
 // `real_imp` is unused with Miri, so silence warnings.
 #![cfg_attr(miri, allow(dead_code))]
+#![cfg_attr(not(bootstrap), allow(internal_features))]
 
 use alloc::boxed::Box;
 use core::any::Any;
diff --git a/library/proc_macro/src/lib.rs b/library/proc_macro/src/lib.rs
index 2c0d113dbc589..be89afa32b33d 100644
--- a/library/proc_macro/src/lib.rs
+++ b/library/proc_macro/src/lib.rs
@@ -33,6 +33,7 @@
 #![feature(min_specialization)]
 #![feature(strict_provenance)]
 #![recursion_limit = "256"]
+#![cfg_attr(not(bootstrap), allow(internal_features))]
 
 #[unstable(feature = "proc_macro_internals", issue = "27812")]
 #[doc(hidden)]
diff --git a/library/profiler_builtins/src/lib.rs b/library/profiler_builtins/src/lib.rs
index 0c83bcee06ff4..a81d0a63547f8 100644
--- a/library/profiler_builtins/src/lib.rs
+++ b/library/profiler_builtins/src/lib.rs
@@ -7,4 +7,5 @@
     issue = "none"
 )]
 #![allow(unused_features)]
+#![cfg_attr(not(bootstrap), allow(internal_features))]
 #![feature(staged_api)]
diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs
index 238c74229cc49..2f9cd7bc0ca78 100644
--- a/library/std/src/lib.rs
+++ b/library/std/src/lib.rs
@@ -220,6 +220,7 @@
 #![warn(missing_debug_implementations)]
 #![allow(explicit_outlives_requirements)]
 #![allow(unused_lifetimes)]
+#![cfg_attr(not(bootstrap), allow(internal_features))]
 #![deny(rustc::existing_doc_keyword)]
 #![deny(fuzzy_provenance_casts)]
 // Ensure that std can be linked against panic_abort despite compiled with `-C panic=unwind`
diff --git a/library/test/src/lib.rs b/library/test/src/lib.rs
index 0bd447552cf51..64d10dd5712ab 100644
--- a/library/test/src/lib.rs
+++ b/library/test/src/lib.rs
@@ -21,6 +21,7 @@
 #![feature(process_exitcode_internals)]
 #![feature(panic_can_unwind)]
 #![feature(test)]
+#![cfg_attr(not(bootstrap), allow(internal_features))]
 
 // Public reexports
 pub use self::bench::{black_box, Bencher};
diff --git a/library/unwind/src/lib.rs b/library/unwind/src/lib.rs
index b655bae967372..0b4daeafe46df 100644
--- a/library/unwind/src/lib.rs
+++ b/library/unwind/src/lib.rs
@@ -5,6 +5,7 @@
 #![feature(c_unwind)]
 #![feature(cfg_target_abi)]
 #![cfg_attr(not(target_env = "msvc"), feature(libc))]
+#![cfg_attr(not(bootstrap), allow(internal_features))]
 
 cfg_if::cfg_if! {
     if #[cfg(target_env = "msvc")] {
diff --git a/src/doc/rustdoc/src/unstable-features.md b/src/doc/rustdoc/src/unstable-features.md
index 013b93e0129c0..f69156b7c05e2 100644
--- a/src/doc/rustdoc/src/unstable-features.md
+++ b/src/doc/rustdoc/src/unstable-features.md
@@ -200,6 +200,7 @@ To do so, the `#[doc(keyword = "...")]` attribute is used. Example:
 
 ```rust
 #![feature(rustdoc_internals)]
+#![allow(internal_features)]
 
 /// Some documentation about the keyword.
 #[doc(keyword = "keyword")]
diff --git a/src/doc/unstable-book/src/language-features/intrinsics.md b/src/doc/unstable-book/src/language-features/intrinsics.md
index ea9bace6dd47f..8fa8f567d7eed 100644
--- a/src/doc/unstable-book/src/language-features/intrinsics.md
+++ b/src/doc/unstable-book/src/language-features/intrinsics.md
@@ -17,6 +17,7 @@ via a declaration like
 
 ```rust
 #![feature(intrinsics)]
+#![allow(internal_features)]
 # fn main() {}
 
 extern "rust-intrinsic" {
diff --git a/src/doc/unstable-book/src/language-features/lang-items.md b/src/doc/unstable-book/src/language-features/lang-items.md
index f4bc18bc7dabd..1f3d472c3aa8b 100644
--- a/src/doc/unstable-book/src/language-features/lang-items.md
+++ b/src/doc/unstable-book/src/language-features/lang-items.md
@@ -17,6 +17,7 @@ sugar for dynamic allocations via `malloc` and `free`:
 
 ```rust,ignore (libc-is-finicky)
 #![feature(lang_items, start, libc, core_intrinsics, rustc_private, rustc_attrs)]
+#![allow(internal_features)]
 #![no_std]
 use core::intrinsics;
 use core::panic::PanicInfo;
@@ -119,6 +120,7 @@ in the same format as C:
 ```rust,ignore (libc-is-finicky)
 #![feature(lang_items, core_intrinsics, rustc_private)]
 #![feature(start)]
+#![allow(internal_features)]
 #![no_std]
 use core::intrinsics;
 use core::panic::PanicInfo;
@@ -155,6 +157,7 @@ compiler's name mangling too:
 ```rust,ignore (libc-is-finicky)
 #![feature(lang_items, core_intrinsics, rustc_private)]
 #![feature(start)]
+#![allow(internal_features)]
 #![no_std]
 #![no_main]
 use core::intrinsics;
diff --git a/src/tools/clippy/tests/compile-test.rs b/src/tools/clippy/tests/compile-test.rs
index 385e25ce6b8f0..e46f8bf6fabdf 100644
--- a/src/tools/clippy/tests/compile-test.rs
+++ b/src/tools/clippy/tests/compile-test.rs
@@ -140,6 +140,7 @@ fn base_config(test_dir: &str) -> compiletest::Config {
         [
             "--emit=metadata",
             "-Aunused",
+            "-Ainternal_features",
             "-Zui-testing",
             "-Dwarnings",
             &format!("-Ldependency={}", deps_path.display()),
diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs
index 45582ddcbafc6..7aa86c66bad34 100644
--- a/src/tools/compiletest/src/runtest.rs
+++ b/src/tools/compiletest/src/runtest.rs
@@ -868,6 +868,8 @@ impl<'test> TestCx<'test> {
             .args(&["--target", &self.config.target])
             .arg("-L")
             .arg(&aux_dir)
+            .arg("-A")
+            .arg("internal_features")
             .args(&self.props.compile_flags)
             .envs(self.props.rustc_env.clone());
         self.maybe_add_external_args(&mut rustc, &self.config.target_rustcflags);
@@ -936,7 +938,9 @@ impl<'test> TestCx<'test> {
             .arg("-L")
             .arg(&self.config.build_base)
             .arg("-L")
-            .arg(aux_dir);
+            .arg(aux_dir)
+            .arg("-A")
+            .arg("internal_features");
         self.set_revision_flags(&mut rustc);
         self.maybe_add_external_args(&mut rustc, &self.config.target_rustcflags);
         rustc.args(&self.props.compile_flags);
@@ -1867,6 +1871,8 @@ impl<'test> TestCx<'test> {
             .arg("--deny")
             .arg("warnings")
             .arg(&self.testpaths.file)
+            .arg("-A")
+            .arg("internal_features")
             .args(&self.props.compile_flags);
 
         if self.config.mode == RustdocJson {
@@ -2459,6 +2465,9 @@ impl<'test> TestCx<'test> {
             rustc.args(&["-A", "unused"]);
         }
 
+        // Allow tests to use internal features.
+        rustc.args(&["-A", "internal_features"]);
+
         if self.props.force_host {
             self.maybe_add_external_args(&mut rustc, &self.config.host_rustcflags);
             if !is_rustdoc {
diff --git a/src/tools/miri/tests/compiletest.rs b/src/tools/miri/tests/compiletest.rs
index fa17923446fd0..5c3a194214b42 100644
--- a/src/tools/miri/tests/compiletest.rs
+++ b/src/tools/miri/tests/compiletest.rs
@@ -53,6 +53,7 @@ fn test_config(target: &str, path: &str, mode: Mode, with_dependencies: bool) ->
     // Add some flags we always want.
     program.args.push("-Dwarnings".into());
     program.args.push("-Dunused".into());
+    program.args.push("-Ainternal_features".into());
     if let Ok(extra_flags) = env::var("MIRIFLAGS") {
         for flag in extra_flags.split_whitespace() {
             program.args.push(flag.into());
diff --git a/src/tools/tidy/src/features.rs b/src/tools/tidy/src/features.rs
index 7ad8f5c5c05f8..d900c04c124e8 100644
--- a/src/tools/tidy/src/features.rs
+++ b/src/tools/tidy/src/features.rs
@@ -338,6 +338,7 @@ fn collect_lang_features_in(features: &mut Features, base: &Path, file: &str, ba
         let level = match parts.next().map(|l| l.trim().trim_start_matches('(')) {
             Some("active") => Status::Unstable,
             Some("incomplete") => Status::Unstable,
+            Some("internal") => Status::Unstable,
             Some("removed") => Status::Removed,
             Some("accepted") => Status::Stable,
             _ => continue,
diff --git a/tests/run-make/tools.mk b/tests/run-make/tools.mk
index ea06b620c4cff..6121a91e9203f 100644
--- a/tests/run-make/tools.mk
+++ b/tests/run-make/tools.mk
@@ -8,7 +8,7 @@ TARGET_RPATH_ENV = \
 RUSTC_ORIGINAL := $(RUSTC)
 BARE_RUSTC := $(HOST_RPATH_ENV) '$(RUSTC)'
 BARE_RUSTDOC := $(HOST_RPATH_ENV) '$(RUSTDOC)'
-RUSTC := $(BARE_RUSTC) --out-dir $(TMPDIR) -L $(TMPDIR) $(RUSTFLAGS)
+RUSTC := $(BARE_RUSTC) --out-dir $(TMPDIR) -L $(TMPDIR) $(RUSTFLAGS) -Ainternal_features
 RUSTDOC := $(BARE_RUSTDOC) -L $(TARGET_RPATH_DIR)
 ifdef RUSTC_LINKER
 RUSTC := $(RUSTC) -Clinker='$(RUSTC_LINKER)'
diff --git a/tests/rustdoc-gui/src/staged_api/lib.rs b/tests/rustdoc-gui/src/staged_api/lib.rs
index 5934593a8999b..0c914470e2861 100644
--- a/tests/rustdoc-gui/src/staged_api/lib.rs
+++ b/tests/rustdoc-gui/src/staged_api/lib.rs
@@ -1,4 +1,5 @@
 #![feature(staged_api)]
+#![allow(internal_features)]
 #![stable(feature = "some_feature", since = "1.3.5")]
 
 #[stable(feature = "some_feature", since = "1.3.5")]
diff --git a/tests/rustdoc-gui/src/test_docs/lib.rs b/tests/rustdoc-gui/src/test_docs/lib.rs
index ecf3a7cc147ca..49484ee086960 100644
--- a/tests/rustdoc-gui/src/test_docs/lib.rs
+++ b/tests/rustdoc-gui/src/test_docs/lib.rs
@@ -3,6 +3,7 @@
 #![doc(html_playground_url="https://play.rust-lang.org/")]
 
 #![crate_name = "test_docs"]
+#![allow(internal_features)]
 #![feature(rustdoc_internals)]
 #![feature(doc_cfg)]
 #![feature(associated_type_defaults)]
diff --git a/tests/rustdoc/issue-18199.rs b/tests/rustdoc/issue-18199.rs
index bc0c4a5650294..9cc58b162f37e 100644
--- a/tests/rustdoc/issue-18199.rs
+++ b/tests/rustdoc/issue-18199.rs
@@ -3,6 +3,7 @@
 #![doc(test(attr(feature(staged_api))))]
 
 /// ```
+/// #![allow(internal_features)]
 /// #![unstable(feature="test", issue="18199")]
 /// fn main() {}
 /// ```
diff --git a/tests/ui/feature-gates/feature-gate-unsafe_pin_internals.rs b/tests/ui/feature-gates/feature-gate-unsafe_pin_internals.rs
index dce94c9eab275..134ea25b75afb 100644
--- a/tests/ui/feature-gates/feature-gate-unsafe_pin_internals.rs
+++ b/tests/ui/feature-gates/feature-gate-unsafe_pin_internals.rs
@@ -1,7 +1,7 @@
 // edition:2018
-#![forbid(incomplete_features, unsafe_code)]
+#![forbid(internal_features, unsafe_code)]
 #![feature(unsafe_pin_internals)]
-//~^ ERROR the feature `unsafe_pin_internals` is incomplete and may not be safe to use
+//~^ ERROR the feature `unsafe_pin_internals` is internal to the compiler or standard library
 
 use core::{marker::PhantomPinned, pin::Pin};
 
diff --git a/tests/ui/feature-gates/feature-gate-unsafe_pin_internals.stderr b/tests/ui/feature-gates/feature-gate-unsafe_pin_internals.stderr
index 4d0c931b404e6..39afbf2db7e56 100644
--- a/tests/ui/feature-gates/feature-gate-unsafe_pin_internals.stderr
+++ b/tests/ui/feature-gates/feature-gate-unsafe_pin_internals.stderr
@@ -1,14 +1,15 @@
-error: the feature `unsafe_pin_internals` is incomplete and may not be safe to use and/or cause compiler crashes
+error: the feature `unsafe_pin_internals` is internal to the compiler or standard library
   --> $DIR/feature-gate-unsafe_pin_internals.rs:3:12
    |
 LL | #![feature(unsafe_pin_internals)]
    |            ^^^^^^^^^^^^^^^^^^^^
    |
+   = note: using it is strongly discouraged
 note: the lint level is defined here
   --> $DIR/feature-gate-unsafe_pin_internals.rs:2:11
    |
-LL | #![forbid(incomplete_features, unsafe_code)]
-   |           ^^^^^^^^^^^^^^^^^^^
+LL | #![forbid(internal_features, unsafe_code)]
+   |           ^^^^^^^^^^^^^^^^^
 
 error: aborting due to previous error