diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs
index 77dd4ccd64ef2..258692a982cf7 100644
--- a/compiler/rustc_ast_passes/src/feature_gate.rs
+++ b/compiler/rustc_ast_passes/src/feature_gate.rs
@@ -401,6 +401,11 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> {
                     let msg = "`#[doc(keyword)]` is meant for internal use only";
                     gate_feature_post!(self, rustdoc_internals, attr.span, msg);
                 }
+
+                if nested_meta.has_name(sym::tuple_variadic) {
+                    let msg = "`#[doc(tuple_variadic)]` is meant for internal use only";
+                    gate_feature_post!(self, rustdoc_internals, attr.span, msg);
+                }
             }
         }
 
diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs
index 5cc97d326d3d8..1d94130f22204 100644
--- a/compiler/rustc_passes/src/check_attr.rs
+++ b/compiler/rustc_passes/src/check_attr.rs
@@ -804,6 +804,37 @@ impl CheckAttrVisitor<'_> {
         true
     }
 
+    fn check_doc_tuple_variadic(&self, meta: &NestedMetaItem, hir_id: HirId) -> bool {
+        match self.tcx.hir().find(hir_id).and_then(|node| match node {
+            hir::Node::Item(item) => Some(&item.kind),
+            _ => None,
+        }) {
+            Some(ItemKind::Impl(ref i)) => {
+                if !matches!(&i.self_ty.kind, hir::TyKind::Tup([_])) {
+                    self.tcx
+                        .sess
+                        .struct_span_err(
+                            meta.span(),
+                            "`#[doc(tuple_variadic)]` must be used on the first of a set of tuple trait impls with varying arity",
+                        )
+                        .emit();
+                    return false;
+                }
+            }
+            _ => {
+                self.tcx
+                    .sess
+                    .struct_span_err(
+                        meta.span(),
+                        "`#[doc(keyword = \"...\")]` can only be used on impl blocks",
+                    )
+                    .emit();
+                return false;
+            }
+        }
+        true
+    }
+
     /// Checks `#[doc(inline)]`/`#[doc(no_inline)]` attributes. Returns `true` if valid.
     ///
     /// A doc inlining attribute is invalid if it is applied to a non-`use` item, or
@@ -1064,6 +1095,13 @@ impl CheckAttrVisitor<'_> {
                             is_valid = false
                         }
 
+                        sym::tuple_variadic
+                            if !self.check_attr_not_crate_level(meta, hir_id, "tuple_variadic")
+                                || !self.check_doc_tuple_variadic(meta, hir_id) =>
+                        {
+                            is_valid = false
+                        }
+
                         sym::html_favicon_url
                         | sym::html_logo_url
                         | sym::html_playground_url
@@ -1117,7 +1155,8 @@ impl CheckAttrVisitor<'_> {
                         | sym::no_inline
                         | sym::notable_trait
                         | sym::passes
-                        | sym::plugins => {}
+                        | sym::plugins
+                        | sym::tuple_variadic => {}
 
                         sym::test => {
                             if !self.check_test_attr(meta, hir_id) {
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index bcaf53639cc63..ce3efc036ae8e 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -1439,6 +1439,7 @@ symbols! {
         tuple,
         tuple_from_req,
         tuple_indexing,
+        tuple_variadic,
         two_phase,
         ty,
         type_alias_enum_variants,
diff --git a/library/core/src/clone.rs b/library/core/src/clone.rs
index 70f3fe853d97a..fd5624812f554 100644
--- a/library/core/src/clone.rs
+++ b/library/core/src/clone.rs
@@ -95,7 +95,6 @@ use crate::marker::Destruct;
 ///
 /// * Function item types (i.e., the distinct types defined for each function)
 /// * Function pointer types (e.g., `fn() -> i32`)
-/// * Tuple types, if each component also implements `Clone` (e.g., `()`, `(i32, bool)`)
 /// * Closure types, if they capture no value from the environment
 ///   or if all such captured values implement `Clone` themselves.
 ///   Note that variables captured by shared reference always implement `Clone`
diff --git a/library/core/src/fmt/mod.rs b/library/core/src/fmt/mod.rs
index 63655ae8a240b..1c66089fad6e6 100644
--- a/library/core/src/fmt/mod.rs
+++ b/library/core/src/fmt/mod.rs
@@ -2313,29 +2313,46 @@ macro_rules! peel {
 macro_rules! tuple {
     () => ();
     ( $($name:ident,)+ ) => (
-        #[stable(feature = "rust1", since = "1.0.0")]
-        impl<$($name:Debug),+> Debug for ($($name,)+) where last_type!($($name,)+): ?Sized {
-            #[allow(non_snake_case, unused_assignments)]
-            fn fmt(&self, f: &mut Formatter<'_>) -> Result {
-                let mut builder = f.debug_tuple("");
-                let ($(ref $name,)+) = *self;
-                $(
-                    builder.field(&$name);
-                )+
-
-                builder.finish()
+        maybe_tuple_doc! {
+            $($name)+ @
+            #[stable(feature = "rust1", since = "1.0.0")]
+            impl<$($name:Debug),+> Debug for ($($name,)+) where last_type!($($name,)+): ?Sized {
+                #[allow(non_snake_case, unused_assignments)]
+                fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+                    let mut builder = f.debug_tuple("");
+                    let ($(ref $name,)+) = *self;
+                    $(
+                        builder.field(&$name);
+                    )+
+
+                    builder.finish()
+                }
             }
         }
         peel! { $($name,)+ }
     )
 }
 
+macro_rules! maybe_tuple_doc {
+    ($a:ident @ #[$meta:meta] $item:item) => {
+        #[cfg_attr(not(bootstrap), doc(tuple_variadic))]
+        #[doc = "This trait is implemented for tuples up to twelve items long."]
+        #[$meta]
+        $item
+    };
+    ($a:ident $($rest_a:ident)+ @ #[$meta:meta] $item:item) => {
+        #[doc(hidden)]
+        #[$meta]
+        $item
+    };
+}
+
 macro_rules! last_type {
     ($a:ident,) => { $a };
     ($a:ident, $($rest_a:ident,)+) => { last_type!($($rest_a,)+) };
 }
 
-tuple! { T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, }
+tuple! { E, D, C, B, A, Z, Y, X, W, V, U, T, }
 
 #[stable(feature = "rust1", since = "1.0.0")]
 impl<T: Debug> Debug for [T] {
diff --git a/library/core/src/hash/mod.rs b/library/core/src/hash/mod.rs
index 3d168f62a09f5..2c152fe1b2c18 100644
--- a/library/core/src/hash/mod.rs
+++ b/library/core/src/hash/mod.rs
@@ -883,36 +883,53 @@ mod impls {
         );
 
         ( $($name:ident)+) => (
-            #[stable(feature = "rust1", since = "1.0.0")]
-            impl<$($name: Hash),+> Hash for ($($name,)+) where last_type!($($name,)+): ?Sized {
-                #[allow(non_snake_case)]
-                #[inline]
-                fn hash<S: Hasher>(&self, state: &mut S) {
-                    let ($(ref $name,)+) = *self;
-                    $($name.hash(state);)+
+            maybe_tuple_doc! {
+                $($name)+ @
+                #[stable(feature = "rust1", since = "1.0.0")]
+                impl<$($name: Hash),+> Hash for ($($name,)+) where last_type!($($name,)+): ?Sized {
+                    #[allow(non_snake_case)]
+                    #[inline]
+                    fn hash<S: Hasher>(&self, state: &mut S) {
+                        let ($(ref $name,)+) = *self;
+                        $($name.hash(state);)+
+                    }
                 }
             }
         );
     }
 
+    macro_rules! maybe_tuple_doc {
+        ($a:ident @ #[$meta:meta] $item:item) => {
+            #[cfg_attr(not(bootstrap), doc(tuple_variadic))]
+            #[doc = "This trait is implemented for tuples up to twelve items long."]
+            #[$meta]
+            $item
+        };
+        ($a:ident $($rest_a:ident)+ @ #[$meta:meta] $item:item) => {
+            #[doc(hidden)]
+            #[$meta]
+            $item
+        };
+    }
+
     macro_rules! last_type {
         ($a:ident,) => { $a };
         ($a:ident, $($rest_a:ident,)+) => { last_type!($($rest_a,)+) };
     }
 
     impl_hash_tuple! {}
-    impl_hash_tuple! { A }
-    impl_hash_tuple! { A B }
-    impl_hash_tuple! { A B C }
-    impl_hash_tuple! { A B C D }
-    impl_hash_tuple! { A B C D E }
-    impl_hash_tuple! { A B C D E F }
-    impl_hash_tuple! { A B C D E F G }
-    impl_hash_tuple! { A B C D E F G H }
-    impl_hash_tuple! { A B C D E F G H I }
-    impl_hash_tuple! { A B C D E F G H I J }
-    impl_hash_tuple! { A B C D E F G H I J K }
-    impl_hash_tuple! { A B C D E F G H I J K L }
+    impl_hash_tuple! { T }
+    impl_hash_tuple! { T B }
+    impl_hash_tuple! { T B C }
+    impl_hash_tuple! { T B C D }
+    impl_hash_tuple! { T B C D E }
+    impl_hash_tuple! { T B C D E F }
+    impl_hash_tuple! { T B C D E F G }
+    impl_hash_tuple! { T B C D E F G H }
+    impl_hash_tuple! { T B C D E F G H I }
+    impl_hash_tuple! { T B C D E F G H I J }
+    impl_hash_tuple! { T B C D E F G H I J K }
+    impl_hash_tuple! { T B C D E F G H I J K L }
 
     #[stable(feature = "rust1", since = "1.0.0")]
     impl<T: Hash> Hash for [T] {
diff --git a/library/core/src/marker.rs b/library/core/src/marker.rs
index 4a90ef9545d68..2c57897956fcd 100644
--- a/library/core/src/marker.rs
+++ b/library/core/src/marker.rs
@@ -359,7 +359,6 @@ pub trait StructuralEq {
 ///
 /// * Function item types (i.e., the distinct types defined for each function)
 /// * Function pointer types (e.g., `fn() -> i32`)
-/// * Tuple types, if each component also implements `Copy` (e.g., `()`, `(i32, bool)`)
 /// * Closure types, if they capture no value from the environment
 ///   or if all such captured values implement `Copy` themselves.
 ///   Note that variables captured by shared reference always implement `Copy`
diff --git a/library/core/src/primitive_docs.rs b/library/core/src/primitive_docs.rs
index 688ab63bf1366..00793f7f9204d 100644
--- a/library/core/src/primitive_docs.rs
+++ b/library/core/src/primitive_docs.rs
@@ -439,6 +439,27 @@ mod prim_char {}
 #[stable(feature = "rust1", since = "1.0.0")]
 mod prim_unit {}
 
+// Required to make auto trait impls render.
+// See src/librustdoc/passes/collect_trait_impls.rs:collect_trait_impls
+#[doc(hidden)]
+impl () {}
+
+// Fake impl that's only really used for docs.
+#[cfg(doc)]
+#[stable(feature = "rust1", since = "1.0.0")]
+impl Clone for () {
+    fn clone(&self) -> Self {
+        loop {}
+    }
+}
+
+// Fake impl that's only really used for docs.
+#[cfg(doc)]
+#[stable(feature = "rust1", since = "1.0.0")]
+impl Copy for () {
+    // empty
+}
+
 #[doc(primitive = "pointer")]
 #[doc(alias = "ptr")]
 #[doc(alias = "*")]
@@ -893,13 +914,18 @@ mod prim_str {}
 ///
 /// For more about tuples, see [the book](../book/ch03-02-data-types.html#the-tuple-type).
 ///
+// Hardcoded anchor in src/librustdoc/html/format.rs
+// linked to as `#trait-implementations-1`
 /// # Trait implementations
 ///
-/// If every type inside a tuple implements one of the following traits, then a
-/// tuple itself also implements it.
+/// In this documentation the shorthand `(T₁, T₂, …, Tₙ)` is used to represent tuples of varying
+/// length. When that is used, any trait bound expressed on `T` applies to each element of the
+/// tuple independently. Note that this is a convenience notation to avoid repetitive
+/// documentation, not valid Rust syntax.
+///
+/// Due to a temporary restriction in Rust’s type system, the following traits are only
+/// implemented on tuples of arity 12 or less. In the future, this may change:
 ///
-/// * [`Clone`]
-/// * [`Copy`]
 /// * [`PartialEq`]
 /// * [`Eq`]
 /// * [`PartialOrd`]
@@ -911,8 +937,21 @@ mod prim_str {}
 /// [`Debug`]: fmt::Debug
 /// [`Hash`]: hash::Hash
 ///
-/// Due to a temporary restriction in Rust's type system, these traits are only
-/// implemented on tuples of arity 12 or less. In the future, this may change.
+/// The following traits are implemented for tuples of any length. These traits have
+/// implementations that are automatically generated by the compiler, so are not limited by
+/// missing language features.
+///
+/// * [`Clone`]
+/// * [`Copy`]
+/// * [`Send`]
+/// * [`Sync`]
+/// * [`Unpin`]
+/// * [`UnwindSafe`]
+/// * [`RefUnwindSafe`]
+///
+/// [`Unpin`]: marker::Unpin
+/// [`UnwindSafe`]: panic::UnwindSafe
+/// [`RefUnwindSafe`]: panic::RefUnwindSafe
 ///
 /// # Examples
 ///
@@ -949,6 +988,31 @@ mod prim_str {}
 #[stable(feature = "rust1", since = "1.0.0")]
 mod prim_tuple {}
 
+// Required to make auto trait impls render.
+// See src/librustdoc/passes/collect_trait_impls.rs:collect_trait_impls
+#[doc(hidden)]
+impl<T> (T,) {}
+
+// Fake impl that's only really used for docs.
+#[cfg(doc)]
+#[stable(feature = "rust1", since = "1.0.0")]
+#[cfg_attr(not(bootstrap), doc(tuple_variadic))]
+/// This trait is implemented on arbitrary-length tuples.
+impl<T: Clone> Clone for (T,) {
+    fn clone(&self) -> Self {
+        loop {}
+    }
+}
+
+// Fake impl that's only really used for docs.
+#[cfg(doc)]
+#[stable(feature = "rust1", since = "1.0.0")]
+#[cfg_attr(not(bootstrap), doc(tuple_variadic))]
+/// This trait is implemented on arbitrary-length tuples.
+impl<T: Copy> Copy for (T,) {
+    // empty
+}
+
 #[doc(primitive = "f32")]
 /// A 32-bit floating point type (specifically, the "binary32" type defined in IEEE 754-2008).
 ///
diff --git a/library/core/src/tuple.rs b/library/core/src/tuple.rs
index 181717f35bd25..ab3763cbc4196 100644
--- a/library/core/src/tuple.rs
+++ b/library/core/src/tuple.rs
@@ -19,75 +19,106 @@ macro_rules! tuple_impls {
     };
     // "Private" internal implementation
     (@impl $( $T:ident )+) => {
-        #[stable(feature = "rust1", since = "1.0.0")]
-        impl<$($T:PartialEq),+> PartialEq for ($($T,)+)
-        where
-            last_type!($($T,)+): ?Sized
-        {
-            #[inline]
-            fn eq(&self, other: &($($T,)+)) -> bool {
-                $( ${ignore(T)} self.${index()} == other.${index()} )&&+
-            }
-            #[inline]
-            fn ne(&self, other: &($($T,)+)) -> bool {
-                $( ${ignore(T)} self.${index()} != other.${index()} )||+
+        maybe_tuple_doc! {
+            $($T)+ @
+            #[stable(feature = "rust1", since = "1.0.0")]
+            impl<$($T:PartialEq),+> PartialEq for ($($T,)+)
+            where
+                last_type!($($T,)+): ?Sized
+            {
+                #[inline]
+                fn eq(&self, other: &($($T,)+)) -> bool {
+                    $( ${ignore(T)} self.${index()} == other.${index()} )&&+
+                }
+                #[inline]
+                fn ne(&self, other: &($($T,)+)) -> bool {
+                    $( ${ignore(T)} self.${index()} != other.${index()} )||+
+                }
             }
         }
 
-        #[stable(feature = "rust1", since = "1.0.0")]
-        impl<$($T:Eq),+> Eq for ($($T,)+)
-        where
-            last_type!($($T,)+): ?Sized
-        {}
+        maybe_tuple_doc! {
+            $($T)+ @
+            #[stable(feature = "rust1", since = "1.0.0")]
+            impl<$($T:Eq),+> Eq for ($($T,)+)
+            where
+                last_type!($($T,)+): ?Sized
+            {}
+        }
 
-        #[stable(feature = "rust1", since = "1.0.0")]
-        impl<$($T:PartialOrd + PartialEq),+> PartialOrd for ($($T,)+)
-        where
-            last_type!($($T,)+): ?Sized
-        {
-            #[inline]
-            fn partial_cmp(&self, other: &($($T,)+)) -> Option<Ordering> {
-                lexical_partial_cmp!($( ${ignore(T)} self.${index()}, other.${index()} ),+)
-            }
-            #[inline]
-            fn lt(&self, other: &($($T,)+)) -> bool {
-                lexical_ord!(lt, $( ${ignore(T)} self.${index()}, other.${index()} ),+)
-            }
-            #[inline]
-            fn le(&self, other: &($($T,)+)) -> bool {
-                lexical_ord!(le, $( ${ignore(T)} self.${index()}, other.${index()} ),+)
-            }
-            #[inline]
-            fn ge(&self, other: &($($T,)+)) -> bool {
-                lexical_ord!(ge, $( ${ignore(T)} self.${index()}, other.${index()} ),+)
-            }
-            #[inline]
-            fn gt(&self, other: &($($T,)+)) -> bool {
-                lexical_ord!(gt, $( ${ignore(T)} self.${index()}, other.${index()} ),+)
+        maybe_tuple_doc! {
+            $($T)+ @
+            #[stable(feature = "rust1", since = "1.0.0")]
+            impl<$($T:PartialOrd + PartialEq),+> PartialOrd for ($($T,)+)
+            where
+                last_type!($($T,)+): ?Sized
+            {
+                #[inline]
+                fn partial_cmp(&self, other: &($($T,)+)) -> Option<Ordering> {
+                    lexical_partial_cmp!($( ${ignore(T)} self.${index()}, other.${index()} ),+)
+                }
+                #[inline]
+                fn lt(&self, other: &($($T,)+)) -> bool {
+                    lexical_ord!(lt, $( ${ignore(T)} self.${index()}, other.${index()} ),+)
+                }
+                #[inline]
+                fn le(&self, other: &($($T,)+)) -> bool {
+                    lexical_ord!(le, $( ${ignore(T)} self.${index()}, other.${index()} ),+)
+                }
+                #[inline]
+                fn ge(&self, other: &($($T,)+)) -> bool {
+                    lexical_ord!(ge, $( ${ignore(T)} self.${index()}, other.${index()} ),+)
+                }
+                #[inline]
+                fn gt(&self, other: &($($T,)+)) -> bool {
+                    lexical_ord!(gt, $( ${ignore(T)} self.${index()}, other.${index()} ),+)
+                }
             }
         }
 
-        #[stable(feature = "rust1", since = "1.0.0")]
-        impl<$($T:Ord),+> Ord for ($($T,)+)
-        where
-            last_type!($($T,)+): ?Sized
-        {
-            #[inline]
-            fn cmp(&self, other: &($($T,)+)) -> Ordering {
-                lexical_cmp!($( ${ignore(T)} self.${index()}, other.${index()} ),+)
+        maybe_tuple_doc! {
+            $($T)+ @
+            #[stable(feature = "rust1", since = "1.0.0")]
+            impl<$($T:Ord),+> Ord for ($($T,)+)
+            where
+                last_type!($($T,)+): ?Sized
+            {
+                #[inline]
+                fn cmp(&self, other: &($($T,)+)) -> Ordering {
+                    lexical_cmp!($( ${ignore(T)} self.${index()}, other.${index()} ),+)
+                }
             }
         }
 
-        #[stable(feature = "rust1", since = "1.0.0")]
-        impl<$($T:Default),+> Default for ($($T,)+) {
-            #[inline]
-            fn default() -> ($($T,)+) {
-                ($({ let x: $T = Default::default(); x},)+)
+        maybe_tuple_doc! {
+            $($T)+ @
+            #[stable(feature = "rust1", since = "1.0.0")]
+            impl<$($T:Default),+> Default for ($($T,)+) {
+                #[inline]
+                fn default() -> ($($T,)+) {
+                    ($({ let x: $T = Default::default(); x},)+)
+                }
             }
         }
     }
 }
 
+// If this is a unary tuple, it adds a doc comment.
+// Otherwise, it hides the docs entirely.
+macro_rules! maybe_tuple_doc {
+    ($a:ident @ #[$meta:meta] $item:item) => {
+        #[cfg_attr(not(bootstrap), doc(tuple_variadic))]
+        #[doc = "This trait is implemented for tuples up to twelve items long."]
+        #[$meta]
+        $item
+    };
+    ($a:ident $($rest_a:ident)+ @ #[$meta:meta] $item:item) => {
+        #[doc(hidden)]
+        #[$meta]
+        $item
+    };
+}
+
 // Constructs an expression that performs a lexical ordering using method $rel.
 // The values are interleaved, so the macro invocation for
 // `(a1, a2, a3) < (b1, b2, b3)` would be `lexical_ord!(lt, a1, b1, a2, b2,
@@ -125,4 +156,4 @@ macro_rules! last_type {
     ($a:ident, $($rest_a:ident,)+) => { last_type!($($rest_a,)+) };
 }
 
-tuple_impls!(A B C D E F G H I J K L);
+tuple_impls!(E D C B A Z Y X W V U T);
diff --git a/library/std/src/primitive_docs.rs b/library/std/src/primitive_docs.rs
index 688ab63bf1366..00793f7f9204d 100644
--- a/library/std/src/primitive_docs.rs
+++ b/library/std/src/primitive_docs.rs
@@ -439,6 +439,27 @@ mod prim_char {}
 #[stable(feature = "rust1", since = "1.0.0")]
 mod prim_unit {}
 
+// Required to make auto trait impls render.
+// See src/librustdoc/passes/collect_trait_impls.rs:collect_trait_impls
+#[doc(hidden)]
+impl () {}
+
+// Fake impl that's only really used for docs.
+#[cfg(doc)]
+#[stable(feature = "rust1", since = "1.0.0")]
+impl Clone for () {
+    fn clone(&self) -> Self {
+        loop {}
+    }
+}
+
+// Fake impl that's only really used for docs.
+#[cfg(doc)]
+#[stable(feature = "rust1", since = "1.0.0")]
+impl Copy for () {
+    // empty
+}
+
 #[doc(primitive = "pointer")]
 #[doc(alias = "ptr")]
 #[doc(alias = "*")]
@@ -893,13 +914,18 @@ mod prim_str {}
 ///
 /// For more about tuples, see [the book](../book/ch03-02-data-types.html#the-tuple-type).
 ///
+// Hardcoded anchor in src/librustdoc/html/format.rs
+// linked to as `#trait-implementations-1`
 /// # Trait implementations
 ///
-/// If every type inside a tuple implements one of the following traits, then a
-/// tuple itself also implements it.
+/// In this documentation the shorthand `(T₁, T₂, …, Tₙ)` is used to represent tuples of varying
+/// length. When that is used, any trait bound expressed on `T` applies to each element of the
+/// tuple independently. Note that this is a convenience notation to avoid repetitive
+/// documentation, not valid Rust syntax.
+///
+/// Due to a temporary restriction in Rust’s type system, the following traits are only
+/// implemented on tuples of arity 12 or less. In the future, this may change:
 ///
-/// * [`Clone`]
-/// * [`Copy`]
 /// * [`PartialEq`]
 /// * [`Eq`]
 /// * [`PartialOrd`]
@@ -911,8 +937,21 @@ mod prim_str {}
 /// [`Debug`]: fmt::Debug
 /// [`Hash`]: hash::Hash
 ///
-/// Due to a temporary restriction in Rust's type system, these traits are only
-/// implemented on tuples of arity 12 or less. In the future, this may change.
+/// The following traits are implemented for tuples of any length. These traits have
+/// implementations that are automatically generated by the compiler, so are not limited by
+/// missing language features.
+///
+/// * [`Clone`]
+/// * [`Copy`]
+/// * [`Send`]
+/// * [`Sync`]
+/// * [`Unpin`]
+/// * [`UnwindSafe`]
+/// * [`RefUnwindSafe`]
+///
+/// [`Unpin`]: marker::Unpin
+/// [`UnwindSafe`]: panic::UnwindSafe
+/// [`RefUnwindSafe`]: panic::RefUnwindSafe
 ///
 /// # Examples
 ///
@@ -949,6 +988,31 @@ mod prim_str {}
 #[stable(feature = "rust1", since = "1.0.0")]
 mod prim_tuple {}
 
+// Required to make auto trait impls render.
+// See src/librustdoc/passes/collect_trait_impls.rs:collect_trait_impls
+#[doc(hidden)]
+impl<T> (T,) {}
+
+// Fake impl that's only really used for docs.
+#[cfg(doc)]
+#[stable(feature = "rust1", since = "1.0.0")]
+#[cfg_attr(not(bootstrap), doc(tuple_variadic))]
+/// This trait is implemented on arbitrary-length tuples.
+impl<T: Clone> Clone for (T,) {
+    fn clone(&self) -> Self {
+        loop {}
+    }
+}
+
+// Fake impl that's only really used for docs.
+#[cfg(doc)]
+#[stable(feature = "rust1", since = "1.0.0")]
+#[cfg_attr(not(bootstrap), doc(tuple_variadic))]
+/// This trait is implemented on arbitrary-length tuples.
+impl<T: Copy> Copy for (T,) {
+    // empty
+}
+
 #[doc(primitive = "f32")]
 /// A 32-bit floating point type (specifically, the "binary32" type defined in IEEE 754-2008).
 ///
diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs
index a82abe6692655..d4c38f34b5b11 100644
--- a/src/librustdoc/clean/inline.rs
+++ b/src/librustdoc/clean/inline.rs
@@ -500,7 +500,11 @@ pub(crate) fn build_impl(
             for_,
             items: trait_items,
             polarity,
-            kind: ImplKind::Normal,
+            kind: if utils::has_doc_flag(tcx, did, sym::tuple_variadic) {
+                ImplKind::TupleVaradic
+            } else {
+                ImplKind::Normal
+            },
         }),
         box merged_attrs,
         cx,
diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs
index f3070fb35f1d0..fd30691c32489 100644
--- a/src/librustdoc/clean/mod.rs
+++ b/src/librustdoc/clean/mod.rs
@@ -1999,7 +1999,11 @@ fn clean_impl<'tcx>(
             for_,
             items,
             polarity: tcx.impl_polarity(def_id),
-            kind: ImplKind::Normal,
+            kind: if utils::has_doc_flag(tcx, def_id.to_def_id(), sym::tuple_variadic) {
+                ImplKind::TupleVaradic
+            } else {
+                ImplKind::Normal
+            },
         });
         Item::from_hir_id_and_parts(hir_id, None, kind, cx)
     };
diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs
index 4605793d0df94..83ab9acd3005a 100644
--- a/src/librustdoc/clean/types.rs
+++ b/src/librustdoc/clean/types.rs
@@ -1750,7 +1750,7 @@ pub(crate) enum PrimitiveType {
     Never,
 }
 
-type SimplifiedTypes = FxHashMap<PrimitiveType, ArrayVec<SimplifiedType, 2>>;
+type SimplifiedTypes = FxHashMap<PrimitiveType, ArrayVec<SimplifiedType, 3>>;
 impl PrimitiveType {
     pub(crate) fn from_hir(prim: hir::PrimTy) -> PrimitiveType {
         use ast::{FloatTy, IntTy, UintTy};
@@ -1839,10 +1839,10 @@ impl PrimitiveType {
                 //
                 // Either manually update this arrayvec at this point
                 // or start with a more complex refactoring.
-                Tuple => [TupleSimplifiedType(2), TupleSimplifiedType(3)].into(),
+                Tuple => [TupleSimplifiedType(1), TupleSimplifiedType(2), TupleSimplifiedType(3)].into(),
                 Unit => single(TupleSimplifiedType(0)),
-                RawPointer => [PtrSimplifiedType(Mutability::Not), PtrSimplifiedType(Mutability::Mut)].into(),
-                Reference => [RefSimplifiedType(Mutability::Not), RefSimplifiedType(Mutability::Mut)].into(),
+                RawPointer => [PtrSimplifiedType(Mutability::Not), PtrSimplifiedType(Mutability::Mut)].into_iter().collect(),
+                Reference => [RefSimplifiedType(Mutability::Not), RefSimplifiedType(Mutability::Mut)].into_iter().collect(),
                 // FIXME: This will be wrong if we ever add inherent impls
                 // for function pointers.
                 Fn => ArrayVec::new(),
@@ -2394,6 +2394,7 @@ impl Impl {
 pub(crate) enum ImplKind {
     Normal,
     Auto,
+    TupleVaradic,
     Blanket(Box<Type>),
 }
 
@@ -2406,6 +2407,10 @@ impl ImplKind {
         matches!(self, ImplKind::Blanket(_))
     }
 
+    pub(crate) fn is_tuple_variadic(&self) -> bool {
+        matches!(self, ImplKind::TupleVaradic)
+    }
+
     pub(crate) fn as_blanket_ty(&self) -> Option<&Type> {
         match self {
             ImplKind::Blanket(ty) => Some(ty),
diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs
index b7789493df647..5520430b04302 100644
--- a/src/librustdoc/html/format.rs
+++ b/src/librustdoc/html/format.rs
@@ -713,6 +713,16 @@ fn primitive_link(
     prim: clean::PrimitiveType,
     name: &str,
     cx: &Context<'_>,
+) -> fmt::Result {
+    primitive_link_fragment(f, prim, name, "", cx)
+}
+
+fn primitive_link_fragment(
+    f: &mut fmt::Formatter<'_>,
+    prim: clean::PrimitiveType,
+    name: &str,
+    fragment: &str,
+    cx: &Context<'_>,
 ) -> fmt::Result {
     let m = &cx.cache();
     let mut needs_termination = false;
@@ -723,7 +733,7 @@ fn primitive_link(
                 let len = if len == 0 { 0 } else { len - 1 };
                 write!(
                     f,
-                    "<a class=\"primitive\" href=\"{}primitive.{}.html\">",
+                    "<a class=\"primitive\" href=\"{}primitive.{}.html{fragment}\">",
                     "../".repeat(len),
                     prim.as_sym()
                 )?;
@@ -754,7 +764,7 @@ fn primitive_link(
                 };
                 if let Some(mut loc) = loc {
                     loc.push_fmt(format_args!("primitive.{}.html", prim.as_sym()));
-                    write!(f, "<a class=\"primitive\" href=\"{}\">", loc.finish())?;
+                    write!(f, "<a class=\"primitive\" href=\"{}{fragment}\">", loc.finish())?;
                     needs_termination = true;
                 }
             }
@@ -1064,7 +1074,13 @@ impl clean::Impl {
                 write!(f, " for ")?;
             }
 
-            if let Some(ty) = self.kind.as_blanket_ty() {
+            if let clean::Type::Tuple(types) = &self.for_ &&
+                let [clean::Type::Generic(name)] = &types[..] &&
+                (self.kind.is_tuple_variadic() || self.kind.is_auto()) {
+                // Hardcoded anchor library/core/src/primitive_docs.rs
+                // Link should match `# Trait implementations`
+                primitive_link_fragment(f, PrimitiveType::Tuple, &format!("({name}₁, {name}₂, …, {name}ₙ)"), "#trait-implementations-1", cx)?;
+            } else if let Some(ty) = self.kind.as_blanket_ty() {
                 fmt_type(ty, f, use_absolute, cx)?;
             } else {
                 fmt_type(&self.for_, f, use_absolute, cx)?;
diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs
index 51a2abc50bc2b..4fde63c99d4b9 100644
--- a/src/librustdoc/json/conversions.rs
+++ b/src/librustdoc/json/conversions.rs
@@ -552,7 +552,7 @@ impl FromWithTcx<clean::Impl> for Impl {
         let trait_ = trait_.map(|path| clean::Type::Path { path }.into_tcx(tcx));
         // FIXME: use something like ImplKind in JSON?
         let (synthetic, blanket_impl) = match kind {
-            clean::ImplKind::Normal => (false, None),
+            clean::ImplKind::Normal | clean::ImplKind::TupleVaradic => (false, None),
             clean::ImplKind::Auto => (true, None),
             clean::ImplKind::Blanket(ty) => (false, Some(*ty)),
         };
diff --git a/src/test/rustdoc-ui/tuple-variadic-check.rs b/src/test/rustdoc-ui/tuple-variadic-check.rs
new file mode 100644
index 0000000000000..11ce2dbe28050
--- /dev/null
+++ b/src/test/rustdoc-ui/tuple-variadic-check.rs
@@ -0,0 +1,15 @@
+#![feature(rustdoc_internals)]
+
+trait Mine {}
+
+// This one is fine
+#[doc(tuple_variadic)]
+impl<T> Mine for (T,) {}
+
+trait Mine2 {}
+
+// This one is not
+#[doc(tuple_variadic)] //~ ERROR
+impl<T, U> Mine for (T,U) {}
+
+fn main() {}
diff --git a/src/test/rustdoc-ui/tuple-variadic-check.stderr b/src/test/rustdoc-ui/tuple-variadic-check.stderr
new file mode 100644
index 0000000000000..358d06d6a42dd
--- /dev/null
+++ b/src/test/rustdoc-ui/tuple-variadic-check.stderr
@@ -0,0 +1,8 @@
+error: `#[doc(tuple_variadic)]` must be used on the first of a set of tuple trait impls with varying arity
+  --> $DIR/tuple-variadic-check.rs:12:7
+   |
+LL | #[doc(tuple_variadic)]
+   |       ^^^^^^^^^^^^^^
+
+error: aborting due to previous error
+
diff --git a/src/test/rustdoc/primitive-tuple-auto-trait.rs b/src/test/rustdoc/primitive-tuple-auto-trait.rs
new file mode 100644
index 0000000000000..71b0b07700958
--- /dev/null
+++ b/src/test/rustdoc/primitive-tuple-auto-trait.rs
@@ -0,0 +1,22 @@
+// compile-flags: --crate-type lib --edition 2018
+
+#![crate_name = "foo"]
+#![feature(rustdoc_internals)]
+
+// @has foo/primitive.tuple.html '//a[@class="primitive"]' 'tuple'
+// @has - '//span[@class="in-band"]' 'Primitive Type tuple'
+// @has - '//section[@id="main-content"]//div[@class="docblock"]//p' 'this is a test!'
+// @has - '//h2[@id="synthetic-implementations"]' 'Auto Trait Implementations'
+// @has - '//div[@id="synthetic-implementations-list"]//h3' 'Send'
+// @has - '//div[@id="synthetic-implementations-list"]//h3' 'Sync'
+#[doc(primitive = "tuple")]
+/// this is a test!
+///
+// Hardcoded anchor to header written in library/core/src/primitive_docs.rs
+// @has - '//h2[@id="trait-implementations-1"]' 'Trait implementations'
+/// # Trait implementations
+///
+/// This header is hard-coded in the HTML format linking for `#[doc(tuple_variadics)]`.
+/// To make sure it gets linked correctly, we need to make sure the hardcoded anchor
+/// in the code matches what rustdoc generates for the header.
+mod tuple_prim {}
diff --git a/src/test/rustdoc/primitive-tuple-variadic.rs b/src/test/rustdoc/primitive-tuple-variadic.rs
new file mode 100644
index 0000000000000..4fd6254f6740d
--- /dev/null
+++ b/src/test/rustdoc/primitive-tuple-variadic.rs
@@ -0,0 +1,18 @@
+// compile-flags: --crate-type lib --edition 2018
+
+#![crate_name = "foo"]
+#![feature(rustdoc_internals)]
+
+pub trait Foo {}
+
+// @has foo/trait.Foo.html
+// @has - '//section[@id="impl-Foo-for-(T%2C)"]/h3' 'impl<T> Foo for (T₁, T₂, …, Tₙ)'
+#[doc(tuple_variadic)]
+impl<T> Foo for (T,) {}
+
+pub trait Bar {}
+
+// @has foo/trait.Bar.html
+// @has - '//section[@id="impl-Bar-for-(U%2C)"]/h3' 'impl<U: Foo> Bar for (U₁, U₂, …, Uₙ)'
+#[doc(tuple_variadic)]
+impl<U: Foo> Bar for (U,) {}
diff --git a/src/test/rustdoc/primitive-unit-auto-trait.rs b/src/test/rustdoc/primitive-unit-auto-trait.rs
new file mode 100644
index 0000000000000..76182622ef5fb
--- /dev/null
+++ b/src/test/rustdoc/primitive-unit-auto-trait.rs
@@ -0,0 +1,14 @@
+// compile-flags: --crate-type lib --edition 2018
+
+#![crate_name = "foo"]
+#![feature(rustdoc_internals)]
+
+// @has foo/primitive.unit.html '//a[@class="primitive"]' 'unit'
+// @has - '//span[@class="in-band"]' 'Primitive Type unit'
+// @has - '//section[@id="main-content"]//div[@class="docblock"]//p' 'this is a test!'
+// @has - '//h2[@id="synthetic-implementations"]' 'Auto Trait Implementations'
+// @has - '//div[@id="synthetic-implementations-list"]//h3' 'impl Send for ()'
+// @has - '//div[@id="synthetic-implementations-list"]//h3' 'impl Sync for ()'
+#[doc(primitive = "unit")]
+/// this is a test!
+mod unit_prim {}
diff --git a/src/test/ui/feature-gates/feature-gate-rustdoc_internals.rs b/src/test/ui/feature-gates/feature-gate-rustdoc_internals.rs
index d2ff4f6200986..6a144412d0751 100644
--- a/src/test/ui/feature-gates/feature-gate-rustdoc_internals.rs
+++ b/src/test/ui/feature-gates/feature-gate-rustdoc_internals.rs
@@ -2,4 +2,9 @@
 /// wonderful
 mod foo {}
 
+trait Mine {}
+
+#[doc(tuple_variadic)]  //~ ERROR: `#[doc(tuple_variadic)]` is meant for internal use only
+impl<T> Mine for (T,) {}
+
 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
index e96461ac38acb..9fe08afd4f05e 100644
--- a/src/test/ui/feature-gates/feature-gate-rustdoc_internals.stderr
+++ b/src/test/ui/feature-gates/feature-gate-rustdoc_internals.stderr
@@ -7,6 +7,15 @@ 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
+error[E0658]: `#[doc(tuple_variadic)]` is meant for internal use only
+  --> $DIR/feature-gate-rustdoc_internals.rs:7:1
+   |
+LL | #[doc(tuple_variadic)]
+   | ^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = 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 2 previous errors
 
 For more information about this error, try `rustc --explain E0658`.