diff --git a/compiler/rustc_error_codes/src/error_codes/E0322.md b/compiler/rustc_error_codes/src/error_codes/E0322.md
index ccef8681dd6..194bbd83b0f 100644
--- a/compiler/rustc_error_codes/src/error_codes/E0322.md
+++ b/compiler/rustc_error_codes/src/error_codes/E0322.md
@@ -1,4 +1,5 @@
-The `Sized` trait was implemented explicitly.
+A built-in trait was implemented explicitly. All implementations of the trait
+are provided automatically by the compiler.
 
 Erroneous code example:
 
diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs
index 01477265f61..4b6e068db43 100644
--- a/compiler/rustc_feature/src/builtin_attrs.rs
+++ b/compiler/rustc_feature/src/builtin_attrs.rs
@@ -691,6 +691,10 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
         rustc_allow_incoherent_impl, AttributeType::Normal, template!(Word), ErrorFollowing, @only_local: true,
         "#[rustc_allow_incoherent_impl] has to be added to all impl items of an incoherent inherent impl."
     ),
+    rustc_attr!(
+        rustc_deny_explicit_impl, AttributeType::Normal, template!(Word), ErrorFollowing, @only_local: false,
+        "#[rustc_deny_explicit_impl] enforces that a trait can have no user-provided impls"
+    ),
     rustc_attr!(
         rustc_has_incoherent_inherent_impls, AttributeType::Normal, template!(Word), ErrorFollowing,
         "#[rustc_has_incoherent_inherent_impls] allows the addition of incoherent inherent impls for \
diff --git a/compiler/rustc_hir_analysis/src/coherence/mod.rs b/compiler/rustc_hir_analysis/src/coherence/mod.rs
index ae9ebe59091..1bf3768fead 100644
--- a/compiler/rustc_hir_analysis/src/coherence/mod.rs
+++ b/compiler/rustc_hir_analysis/src/coherence/mod.rs
@@ -5,10 +5,11 @@
 // done by the orphan and overlap modules. Then we build up various
 // mappings. That mapping code resides here.
 
-use rustc_errors::struct_span_err;
+use rustc_errors::{error_code, struct_span_err};
 use rustc_hir::def_id::{DefId, LocalDefId};
 use rustc_middle::ty::query::Providers;
 use rustc_middle::ty::{self, TyCtxt, TypeVisitable};
+use rustc_span::sym;
 use rustc_trait_selection::traits;
 
 mod builtin;
@@ -39,61 +40,26 @@ fn enforce_trait_manually_implementable(
     impl_def_id: LocalDefId,
     trait_def_id: DefId,
 ) {
-    let did = Some(trait_def_id);
-    let li = tcx.lang_items();
     let impl_header_span = tcx.def_span(impl_def_id);
 
-    // Disallow *all* explicit impls of `Pointee`, `DiscriminantKind`, `Sized` and `Unsize` for now.
-    if did == li.pointee_trait() {
-        struct_span_err!(
+    // Disallow *all* explicit impls of traits marked `#[rustc_deny_explicit_impl]`
+    if tcx.has_attr(trait_def_id, sym::rustc_deny_explicit_impl) {
+        let trait_name = tcx.item_name(trait_def_id);
+        let mut err = struct_span_err!(
             tcx.sess,
             impl_header_span,
             E0322,
-            "explicit impls for the `Pointee` trait are not permitted"
-        )
-        .span_label(impl_header_span, "impl of `Pointee` not allowed")
-        .emit();
-        return;
-    }
+            "explicit impls for the `{trait_name}` trait are not permitted"
+        );
+        err.span_label(impl_header_span, format!("impl of `{trait_name}` not allowed"));
 
-    if did == li.discriminant_kind_trait() {
-        struct_span_err!(
-            tcx.sess,
-            impl_header_span,
-            E0322,
-            "explicit impls for the `DiscriminantKind` trait are not permitted"
-        )
-        .span_label(impl_header_span, "impl of `DiscriminantKind` not allowed")
-        .emit();
-        return;
-    }
+        // Maintain explicit error code for `Unsize`, since it has a useful
+        // explanation about using `CoerceUnsized` instead.
+        if Some(trait_def_id) == tcx.lang_items().unsize_trait() {
+            err.code(error_code!(E0328));
+        }
 
-    if did == li.sized_trait() {
-        struct_span_err!(
-            tcx.sess,
-            impl_header_span,
-            E0322,
-            "explicit impls for the `Sized` trait are not permitted"
-        )
-        .span_label(impl_header_span, "impl of `Sized` not allowed")
-        .emit();
-        return;
-    }
-
-    if did == li.unsize_trait() {
-        struct_span_err!(
-            tcx.sess,
-            impl_header_span,
-            E0328,
-            "explicit impls for the `Unsize` trait are not permitted"
-        )
-        .span_label(impl_header_span, "impl of `Unsize` not allowed")
-        .emit();
-        return;
-    }
-
-    if tcx.features().unboxed_closures {
-        // the feature gate allows all Fn traits
+        err.emit();
         return;
     }
 
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index bad6d587907..a56450a3573 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -1210,6 +1210,7 @@ symbols! {
         rustc_deallocator,
         rustc_def_path,
         rustc_default_body_unstable,
+        rustc_deny_explicit_impl,
         rustc_diagnostic_item,
         rustc_diagnostic_macros,
         rustc_dirty,
diff --git a/library/core/src/marker.rs b/library/core/src/marker.rs
index ae4ebf44442..3eff6033f8d 100644
--- a/library/core/src/marker.rs
+++ b/library/core/src/marker.rs
@@ -96,6 +96,7 @@ unsafe impl<T: Sync + ?Sized> Send for &T {}
 )]
 #[fundamental] // for Default, for example, which requires that `[T]: !Default` be evaluatable
 #[rustc_specialization_trait]
+#[cfg_attr(not(bootstrap), rustc_deny_explicit_impl)]
 pub trait Sized {
     // Empty.
 }
@@ -127,6 +128,7 @@ pub trait Sized {
 /// [nomicon-coerce]: ../../nomicon/coercions.html
 #[unstable(feature = "unsize", issue = "27732")]
 #[lang = "unsize"]
+#[cfg_attr(not(bootstrap), rustc_deny_explicit_impl)]
 pub trait Unsize<T: ?Sized> {
     // Empty.
 }
@@ -693,6 +695,7 @@ impl<T: ?Sized> StructuralEq for PhantomData<T> {}
     reason = "this trait is unlikely to ever be stabilized, use `mem::discriminant` instead"
 )]
 #[lang = "discriminant_kind"]
+#[cfg_attr(not(bootstrap), rustc_deny_explicit_impl)]
 pub trait DiscriminantKind {
     /// The type of the discriminant, which must satisfy the trait
     /// bounds required by `mem::Discriminant`.
@@ -793,6 +796,7 @@ impl<T: ?Sized> Unpin for *mut T {}
 #[lang = "destruct"]
 #[rustc_on_unimplemented(message = "can't drop `{Self}`", append_const_msg)]
 #[const_trait]
+#[cfg_attr(not(bootstrap), rustc_deny_explicit_impl)]
 pub trait Destruct {}
 
 /// A marker for tuple types.
@@ -802,6 +806,7 @@ pub trait Destruct {}
 #[unstable(feature = "tuple_trait", issue = "none")]
 #[lang = "tuple_trait"]
 #[rustc_on_unimplemented(message = "`{Self}` is not a tuple")]
+#[cfg_attr(not(bootstrap), rustc_deny_explicit_impl)]
 pub trait Tuple {}
 
 /// Implementations of `Copy` for primitive types.
diff --git a/library/core/src/ptr/metadata.rs b/library/core/src/ptr/metadata.rs
index caa10f1818b..a8604843e96 100644
--- a/library/core/src/ptr/metadata.rs
+++ b/library/core/src/ptr/metadata.rs
@@ -50,6 +50,7 @@ use crate::hash::{Hash, Hasher};
 ///
 /// [`to_raw_parts`]: *const::to_raw_parts
 #[lang = "pointee_trait"]
+#[cfg_attr(not(bootstrap), rustc_deny_explicit_impl)]
 pub trait Pointee {
     /// The type for metadata in pointers and references to `Self`.
     #[lang = "metadata_type"]