about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2021-03-03 08:36:46 +0000
committerbors <bors@rust-lang.org>2021-03-03 08:36:46 +0000
commit770ed1cf4bff98345df4f9f54754d6f4bc84559f (patch)
tree3dc1b1fa3dd555c339d458ff473362e1e5b041b4
parentcbca5689a5a0c63c6c5fda22bb0678164b52fec3 (diff)
parent374c90b4c6d9c0c2532b525ac4b4a8d1408e097d (diff)
downloadrust-770ed1cf4bff98345df4f9f54754d6f4bc84559f.tar.gz
rust-770ed1cf4bff98345df4f9f54754d6f4bc84559f.zip
Auto merge of #82718 - JohnTitor:rollup-vpfx3j2, r=JohnTitor
Rollup of 10 pull requests

Successful merges:

 - #81223 ([rustdoc] Generate redirect map file)
 - #82439 (BTree: fix untrue safety)
 - #82469 (Use a crate to produce rustdoc tree comparisons instead of the `diff` command)
 - #82589 (unix: Non-mutable bufs in send_vectored_with_ancillary_to)
 - #82689 (meta: Notify Zulip for rustdoc nominated issues)
 - #82695 (Revert non-power-of-two vector restriction)
 - #82706 (use outer_expn_data() instead of outer_expn().expn_data())
 - #82710 (FloatToInit: Replacing round_unchecked_to --> to_int_unchecked)
 - #82712 (Remove unnecessary conditional `cfg(target_os)` for `redox` and `vxworks`)
 - #82713 (Update cargo)

Failed merges:

r? `@ghost`
`@rustbot` modify labels: rollup
-rw-r--r--Cargo.lock11
-rw-r--r--compiler/rustc_middle/src/ty/layout.rs5
-rw-r--r--compiler/rustc_resolve/src/lib.rs2
-rw-r--r--compiler/rustc_typeck/src/check/check.rs9
-rw-r--r--library/alloc/src/collections/btree/node.rs31
-rw-r--r--library/core/src/convert/num.rs2
-rw-r--r--library/std/src/os/mod.rs2
-rw-r--r--library/std/src/sys/unix/ext/net/ancillary.rs10
-rw-r--r--library/std/src/sys/unix/ext/net/datagram.rs44
-rw-r--r--library/std/src/sys/unix/ext/net/stream.rs21
-rw-r--r--library/std/src/sys/unix/ext/net/tests.rs18
-rw-r--r--src/librustdoc/config.rs4
-rw-r--r--src/librustdoc/html/render/mod.rs53
-rw-r--r--src/librustdoc/lib.rs7
-rw-r--r--src/test/run-make-fulldeps/rustdoc-map-file/Makefile5
-rw-r--r--src/test/run-make-fulldeps/rustdoc-map-file/expected.json5
-rw-r--r--src/test/run-make-fulldeps/rustdoc-map-file/foo.rs16
-rwxr-xr-xsrc/test/run-make-fulldeps/rustdoc-map-file/validate_json.py41
-rw-r--r--src/test/rustdoc/redirect-map-empty.rs6
-rw-r--r--src/test/rustdoc/redirect-map.rs23
-rw-r--r--src/test/ui/simd/issue-17170.rs2
-rw-r--r--src/test/ui/simd/issue-17170.stderr11
-rw-r--r--src/test/ui/simd/issue-39720.rs3
-rw-r--r--src/test/ui/simd/issue-39720.stderr15
-rw-r--r--src/test/ui/simd/simd-type-generic-monomorphisation-power-of-two.rs4
-rw-r--r--src/test/ui/simd/simd-type-generic-monomorphisation-power-of-two.stderr4
-rw-r--r--src/test/ui/simd/simd-type.rs2
-rw-r--r--src/test/ui/simd/simd-type.stderr8
m---------src/tools/cargo0
-rw-r--r--src/tools/compiletest/Cargo.toml2
-rw-r--r--src/tools/compiletest/src/runtest.rs82
-rw-r--r--triagebot.toml9
32 files changed, 312 insertions, 145 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 2e46d43a790..0666abadcba 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -676,6 +676,7 @@ dependencies = [
 name = "compiletest"
 version = "0.0.0"
 dependencies = [
+ "colored",
  "diff",
  "getopts",
  "glob",
@@ -688,6 +689,7 @@ dependencies = [
  "serde_json",
  "tracing",
  "tracing-subscriber",
+ "unified-diff",
  "walkdir",
  "winapi 0.3.9",
 ]
@@ -5527,6 +5529,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
 
 [[package]]
+name = "unified-diff"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "496a3d395ed0c30f411ceace4a91f7d93b148fb5a9b383d5d4cff7850f048d5f"
+dependencies = [
+ "diff",
+]
+
+[[package]]
 name = "unstable-book-gen"
 version = "0.1.0"
 dependencies = [
diff --git a/compiler/rustc_middle/src/ty/layout.rs b/compiler/rustc_middle/src/ty/layout.rs
index 725c144257c..6d2ab0e5f5a 100644
--- a/compiler/rustc_middle/src/ty/layout.rs
+++ b/compiler/rustc_middle/src/ty/layout.rs
@@ -732,11 +732,6 @@ impl<'tcx> LayoutCx<'tcx, TyCtxt<'tcx>> {
                 // Can't be caught in typeck if the array length is generic.
                 if e_len == 0 {
                     tcx.sess.fatal(&format!("monomorphising SIMD type `{}` of zero length", ty));
-                } else if !e_len.is_power_of_two() {
-                    tcx.sess.fatal(&format!(
-                        "monomorphising SIMD type `{}` of non-power-of-two length",
-                        ty
-                    ));
                 } else if e_len > MAX_SIMD_LANES {
                     tcx.sess.fatal(&format!(
                         "monomorphising SIMD type `{}` of length greater than {}",
diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs
index e63fd4ce635..7aee718bfa9 100644
--- a/compiler/rustc_resolve/src/lib.rs
+++ b/compiler/rustc_resolve/src/lib.rs
@@ -1422,7 +1422,7 @@ impl<'a> Resolver<'a> {
 
     fn macro_def(&self, mut ctxt: SyntaxContext) -> DefId {
         loop {
-            match ctxt.outer_expn().expn_data().macro_def_id {
+            match ctxt.outer_expn_data().macro_def_id {
                 Some(def_id) => return def_id,
                 None => ctxt.remove_mark(),
             };
diff --git a/compiler/rustc_typeck/src/check/check.rs b/compiler/rustc_typeck/src/check/check.rs
index 5d7f5bf1c7b..0010d59f710 100644
--- a/compiler/rustc_typeck/src/check/check.rs
+++ b/compiler/rustc_typeck/src/check/check.rs
@@ -1161,15 +1161,6 @@ pub fn check_simd(tcx: TyCtxt<'_>, sp: Span, def_id: LocalDefId) {
                 if len == 0 {
                     struct_span_err!(tcx.sess, sp, E0075, "SIMD vector cannot be empty").emit();
                     return;
-                } else if !len.is_power_of_two() {
-                    struct_span_err!(
-                        tcx.sess,
-                        sp,
-                        E0075,
-                        "SIMD vector length must be a power of two"
-                    )
-                    .emit();
-                    return;
                 } else if len > MAX_SIMD_LANES {
                     struct_span_err!(
                         tcx.sess,
diff --git a/library/alloc/src/collections/btree/node.rs b/library/alloc/src/collections/btree/node.rs
index eb8ec7cd2e4..9a7119470f3 100644
--- a/library/alloc/src/collections/btree/node.rs
+++ b/library/alloc/src/collections/btree/node.rs
@@ -77,9 +77,8 @@ impl<K, V> LeafNode<K, V> {
         }
     }
 
-    /// Creates a new boxed `LeafNode`. Unsafe because all nodes should really be hidden behind
-    /// `BoxedNode`, preventing accidental dropping of uninitialized keys and values.
-    unsafe fn new() -> Box<Self> {
+    /// Creates a new boxed `LeafNode`.
+    fn new() -> Box<Self> {
         unsafe {
             let mut leaf = Box::new_uninit();
             LeafNode::init(leaf.as_mut_ptr());
@@ -107,10 +106,9 @@ struct InternalNode<K, V> {
 impl<K, V> InternalNode<K, V> {
     /// Creates a new boxed `InternalNode`.
     ///
-    /// This is unsafe for two reasons. First, it returns an owned `InternalNode` in a box, risking
-    /// dropping of uninitialized fields. Second, an invariant of internal nodes is that `len + 1`
-    /// edges are initialized and valid, meaning that even when the node is empty (having a
-    /// `len` of 0), there must be one initialized and valid edge. This function does not set up
+    /// # Safety
+    /// An invariant of internal nodes is that they have at least one
+    /// initialized and valid edge. This function does not set up
     /// such an edge.
     unsafe fn new() -> Box<Self> {
         unsafe {
@@ -144,7 +142,7 @@ impl<K, V> Root<K, V> {
 
 impl<K, V> NodeRef<marker::Owned, K, V, marker::Leaf> {
     fn new_leaf() -> Self {
-        Self::from_new_leaf(unsafe { LeafNode::new() })
+        Self::from_new_leaf(LeafNode::new())
     }
 
     fn from_new_leaf(leaf: Box<LeafNode<K, V>>) -> Self {
@@ -156,10 +154,13 @@ impl<K, V> NodeRef<marker::Owned, K, V, marker::Internal> {
     fn new_internal(child: Root<K, V>) -> Self {
         let mut new_node = unsafe { InternalNode::new() };
         new_node.edges[0].write(child.node);
-        NodeRef::from_new_internal(new_node, child.height + 1)
+        unsafe { NodeRef::from_new_internal(new_node, child.height + 1) }
     }
 
-    fn from_new_internal(internal: Box<InternalNode<K, V>>, height: usize) -> Self {
+    /// # Safety
+    /// `height` must not be zero.
+    unsafe fn from_new_internal(internal: Box<InternalNode<K, V>>, height: usize) -> Self {
+        debug_assert!(height > 0);
         let node = NonNull::from(Box::leak(internal)).cast();
         let mut this = NodeRef { height, node, _marker: PhantomData };
         this.borrow_mut().correct_all_childrens_parent_links();
@@ -1080,14 +1081,12 @@ impl<'a, K: 'a, V: 'a> Handle<NodeRef<marker::Mut<'a>, K, V, marker::Leaf>, mark
     /// - All the key-value pairs to the right of this handle are put into a newly
     ///   allocated node.
     pub fn split(mut self) -> SplitResult<'a, K, V, marker::Leaf> {
-        unsafe {
-            let mut new_node = LeafNode::new();
+        let mut new_node = LeafNode::new();
 
-            let kv = self.split_leaf_data(&mut new_node);
+        let kv = self.split_leaf_data(&mut new_node);
 
-            let right = NodeRef::from_new_leaf(new_node);
-            SplitResult { left: self.node, kv, right }
-        }
+        let right = NodeRef::from_new_leaf(new_node);
+        SplitResult { left: self.node, kv, right }
     }
 
     /// Removes the key-value pair pointed to by this handle and returns it, along with the edge
diff --git a/library/core/src/convert/num.rs b/library/core/src/convert/num.rs
index 2dd5e813d6f..5b113610a5d 100644
--- a/library/core/src/convert/num.rs
+++ b/library/core/src/convert/num.rs
@@ -9,7 +9,7 @@ mod private {
     pub trait Sealed {}
 }
 
-/// Supporting trait for inherent methods of `f32` and `f64` such as `round_unchecked_to`.
+/// Supporting trait for inherent methods of `f32` and `f64` such as `to_int_unchecked`.
 /// Typically doesn’t need to be used directly.
 #[unstable(feature = "convert_float_to_int", issue = "67057")]
 pub trait FloatToInt<Int>: private::Sealed + Sized {
diff --git a/library/std/src/os/mod.rs b/library/std/src/os/mod.rs
index 31e39b5ad04..b95511e43d8 100644
--- a/library/std/src/os/mod.rs
+++ b/library/std/src/os/mod.rs
@@ -29,7 +29,7 @@ pub use crate::sys::wasi_ext as wasi;
 // If we're not documenting libstd then we just expose the main modules as we otherwise would.
 
 #[cfg(not(doc))]
-#[cfg(any(target_os = "redox", unix, target_os = "vxworks", target_os = "hermit"))]
+#[cfg(any(unix, target_os = "hermit"))]
 #[stable(feature = "rust1", since = "1.0.0")]
 pub use crate::sys::ext as unix;
 
diff --git a/library/std/src/sys/unix/ext/net/ancillary.rs b/library/std/src/sys/unix/ext/net/ancillary.rs
index 0964b6335aa..33d6a39af07 100644
--- a/library/std/src/sys/unix/ext/net/ancillary.rs
+++ b/library/std/src/sys/unix/ext/net/ancillary.rs
@@ -1,6 +1,6 @@
 use super::{sockaddr_un, SocketAddr};
 use crate::convert::TryFrom;
-use crate::io::{self, IoSliceMut};
+use crate::io::{self, IoSlice, IoSliceMut};
 use crate::marker::PhantomData;
 use crate::mem::{size_of, zeroed};
 use crate::os::unix::io::RawFd;
@@ -68,7 +68,7 @@ pub(super) fn recv_vectored_with_ancillary_from(
 pub(super) fn send_vectored_with_ancillary_to(
     socket: &Socket,
     path: Option<&Path>,
-    bufs: &mut [IoSliceMut<'_>],
+    bufs: &[IoSlice<'_>],
     ancillary: &mut SocketAncillary<'_>,
 ) -> io::Result<usize> {
     unsafe {
@@ -78,7 +78,7 @@ pub(super) fn send_vectored_with_ancillary_to(
         let mut msg: libc::msghdr = zeroed();
         msg.msg_name = &mut msg_name as *mut _ as *mut _;
         msg.msg_namelen = msg_namelen;
-        msg.msg_iov = bufs.as_mut_ptr().cast();
+        msg.msg_iov = bufs.as_ptr() as *mut _;
         msg.msg_control = ancillary.buffer.as_mut_ptr().cast();
         cfg_if::cfg_if! {
             if #[cfg(any(target_os = "android", all(target_os = "linux", target_env = "gnu")))] {
@@ -567,7 +567,7 @@ impl<'a> SocketAncillary<'a> {
     /// #![feature(unix_socket_ancillary_data)]
     /// use std::os::unix::net::{UnixStream, SocketAncillary};
     /// use std::os::unix::io::AsRawFd;
-    /// use std::io::IoSliceMut;
+    /// use std::io::IoSlice;
     ///
     /// fn main() -> std::io::Result<()> {
     ///     let sock = UnixStream::connect("/tmp/sock")?;
@@ -577,7 +577,7 @@ impl<'a> SocketAncillary<'a> {
     ///     ancillary.add_fds(&[sock.as_raw_fd()][..]);
     ///
     ///     let mut buf = [1; 8];
-    ///     let mut bufs = &mut [IoSliceMut::new(&mut buf[..])][..];
+    ///     let mut bufs = &mut [IoSlice::new(&mut buf[..])][..];
     ///     sock.send_vectored_with_ancillary(bufs, &mut ancillary)?;
     ///     Ok(())
     /// }
diff --git a/library/std/src/sys/unix/ext/net/datagram.rs b/library/std/src/sys/unix/ext/net/datagram.rs
index 0f532c47c8f..a8c13fbb874 100644
--- a/library/std/src/sys/unix/ext/net/datagram.rs
+++ b/library/std/src/sys/unix/ext/net/datagram.rs
@@ -19,7 +19,7 @@ use super::{sockaddr_un, SocketAddr};
     target_os = "netbsd",
     target_os = "openbsd",
 ))]
-use crate::io::IoSliceMut;
+use crate::io::{IoSlice, IoSliceMut};
 use crate::net::Shutdown;
 use crate::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
 use crate::path::Path;
@@ -506,23 +506,24 @@ impl UnixDatagram {
     /// ```no_run
     /// #![feature(unix_socket_ancillary_data)]
     /// use std::os::unix::net::{UnixDatagram, SocketAncillary};
-    /// use std::io::IoSliceMut;
+    /// use std::io::IoSlice;
     ///
     /// fn main() -> std::io::Result<()> {
     ///     let sock = UnixDatagram::unbound()?;
-    ///     let mut buf1 = [1; 8];
-    ///     let mut buf2 = [2; 16];
-    ///     let mut buf3 = [3; 8];
-    ///     let mut bufs = &mut [
-    ///         IoSliceMut::new(&mut buf1),
-    ///         IoSliceMut::new(&mut buf2),
-    ///         IoSliceMut::new(&mut buf3),
+    ///     let buf1 = [1; 8];
+    ///     let buf2 = [2; 16];
+    ///     let buf3 = [3; 8];
+    ///     let bufs = &[
+    ///         IoSlice::new(&buf1),
+    ///         IoSlice::new(&buf2),
+    ///         IoSlice::new(&buf3),
     ///     ][..];
     ///     let fds = [0, 1, 2];
     ///     let mut ancillary_buffer = [0; 128];
     ///     let mut ancillary = SocketAncillary::new(&mut ancillary_buffer[..]);
     ///     ancillary.add_fds(&fds[..]);
-    ///     sock.send_vectored_with_ancillary_to(bufs, &mut ancillary, "/some/sock").expect("send_vectored_with_ancillary_to function failed");
+    ///     sock.send_vectored_with_ancillary_to(bufs, &mut ancillary, "/some/sock")
+    ///         .expect("send_vectored_with_ancillary_to function failed");
     ///     Ok(())
     /// }
     /// ```
@@ -538,7 +539,7 @@ impl UnixDatagram {
     #[unstable(feature = "unix_socket_ancillary_data", issue = "76915")]
     pub fn send_vectored_with_ancillary_to<P: AsRef<Path>>(
         &self,
-        bufs: &mut [IoSliceMut<'_>],
+        bufs: &[IoSlice<'_>],
         ancillary: &mut SocketAncillary<'_>,
         path: P,
     ) -> io::Result<usize> {
@@ -554,23 +555,24 @@ impl UnixDatagram {
     /// ```no_run
     /// #![feature(unix_socket_ancillary_data)]
     /// use std::os::unix::net::{UnixDatagram, SocketAncillary};
-    /// use std::io::IoSliceMut;
+    /// use std::io::IoSlice;
     ///
     /// fn main() -> std::io::Result<()> {
     ///     let sock = UnixDatagram::unbound()?;
-    ///     let mut buf1 = [1; 8];
-    ///     let mut buf2 = [2; 16];
-    ///     let mut buf3 = [3; 8];
-    ///     let mut bufs = &mut [
-    ///         IoSliceMut::new(&mut buf1),
-    ///         IoSliceMut::new(&mut buf2),
-    ///         IoSliceMut::new(&mut buf3),
+    ///     let buf1 = [1; 8];
+    ///     let buf2 = [2; 16];
+    ///     let buf3 = [3; 8];
+    ///     let bufs = &[
+    ///         IoSlice::new(&buf1),
+    ///         IoSlice::new(&buf2),
+    ///         IoSlice::new(&buf3),
     ///     ][..];
     ///     let fds = [0, 1, 2];
     ///     let mut ancillary_buffer = [0; 128];
     ///     let mut ancillary = SocketAncillary::new(&mut ancillary_buffer[..]);
     ///     ancillary.add_fds(&fds[..]);
-    ///     sock.send_vectored_with_ancillary(bufs, &mut ancillary).expect("send_vectored_with_ancillary function failed");
+    ///     sock.send_vectored_with_ancillary(bufs, &mut ancillary)
+    ///         .expect("send_vectored_with_ancillary function failed");
     ///     Ok(())
     /// }
     /// ```
@@ -586,7 +588,7 @@ impl UnixDatagram {
     #[unstable(feature = "unix_socket_ancillary_data", issue = "76915")]
     pub fn send_vectored_with_ancillary(
         &self,
-        bufs: &mut [IoSliceMut<'_>],
+        bufs: &[IoSlice<'_>],
         ancillary: &mut SocketAncillary<'_>,
     ) -> io::Result<usize> {
         send_vectored_with_ancillary_to(&self.0, None, bufs, ancillary)
diff --git a/library/std/src/sys/unix/ext/net/stream.rs b/library/std/src/sys/unix/ext/net/stream.rs
index 9fe6b85837e..fc08edacb82 100644
--- a/library/std/src/sys/unix/ext/net/stream.rs
+++ b/library/std/src/sys/unix/ext/net/stream.rs
@@ -530,23 +530,24 @@ impl UnixStream {
     /// ```no_run
     /// #![feature(unix_socket_ancillary_data)]
     /// use std::os::unix::net::{UnixStream, SocketAncillary};
-    /// use std::io::IoSliceMut;
+    /// use std::io::IoSlice;
     ///
     /// fn main() -> std::io::Result<()> {
     ///     let socket = UnixStream::connect("/tmp/sock")?;
-    ///     let mut buf1 = [1; 8];
-    ///     let mut buf2 = [2; 16];
-    ///     let mut buf3 = [3; 8];
-    ///     let mut bufs = &mut [
-    ///         IoSliceMut::new(&mut buf1),
-    ///         IoSliceMut::new(&mut buf2),
-    ///         IoSliceMut::new(&mut buf3),
+    ///     let buf1 = [1; 8];
+    ///     let buf2 = [2; 16];
+    ///     let buf3 = [3; 8];
+    ///     let bufs = &[
+    ///         IoSlice::new(&buf1),
+    ///         IoSlice::new(&buf2),
+    ///         IoSlice::new(&buf3),
     ///     ][..];
     ///     let fds = [0, 1, 2];
     ///     let mut ancillary_buffer = [0; 128];
     ///     let mut ancillary = SocketAncillary::new(&mut ancillary_buffer[..]);
     ///     ancillary.add_fds(&fds[..]);
-    ///     socket.send_vectored_with_ancillary(bufs, &mut ancillary).expect("send_vectored_with_ancillary function failed");
+    ///     socket.send_vectored_with_ancillary(bufs, &mut ancillary)
+    ///         .expect("send_vectored_with_ancillary function failed");
     ///     Ok(())
     /// }
     /// ```
@@ -562,7 +563,7 @@ impl UnixStream {
     #[unstable(feature = "unix_socket_ancillary_data", issue = "76915")]
     pub fn send_vectored_with_ancillary(
         &self,
-        bufs: &mut [IoSliceMut<'_>],
+        bufs: &[IoSlice<'_>],
         ancillary: &mut SocketAncillary<'_>,
     ) -> io::Result<usize> {
         send_vectored_with_ancillary_to(&self.0, None, bufs, ancillary)
diff --git a/library/std/src/sys/unix/ext/net/tests.rs b/library/std/src/sys/unix/ext/net/tests.rs
index 97a016904b4..bd9b6dd727b 100644
--- a/library/std/src/sys/unix/ext/net/tests.rs
+++ b/library/std/src/sys/unix/ext/net/tests.rs
@@ -485,14 +485,14 @@ fn test_unix_datagram_peek_from() {
 fn test_send_vectored_fds_unix_stream() {
     let (s1, s2) = or_panic!(UnixStream::pair());
 
-    let mut buf1 = [1; 8];
-    let mut bufs_send = &mut [IoSliceMut::new(&mut buf1[..])][..];
+    let buf1 = [1; 8];
+    let bufs_send = &[IoSlice::new(&buf1[..])][..];
 
     let mut ancillary1_buffer = [0; 128];
     let mut ancillary1 = SocketAncillary::new(&mut ancillary1_buffer[..]);
     assert!(ancillary1.add_fds(&[s1.as_raw_fd()][..]));
 
-    let usize = or_panic!(s1.send_vectored_with_ancillary(&mut bufs_send, &mut ancillary1));
+    let usize = or_panic!(s1.send_vectored_with_ancillary(&bufs_send, &mut ancillary1));
     assert_eq!(usize, 8);
 
     let mut buf2 = [0; 8];
@@ -542,8 +542,8 @@ fn test_send_vectored_with_ancillary_to_unix_datagram() {
 
     or_panic!(bsock2.set_passcred(true));
 
-    let mut buf1 = [1; 8];
-    let mut bufs_send = &mut [IoSliceMut::new(&mut buf1[..])][..];
+    let buf1 = [1; 8];
+    let bufs_send = &[IoSlice::new(&buf1[..])][..];
 
     let mut ancillary1_buffer = [0; 128];
     let mut ancillary1 = SocketAncillary::new(&mut ancillary1_buffer[..]);
@@ -554,7 +554,7 @@ fn test_send_vectored_with_ancillary_to_unix_datagram() {
     assert!(ancillary1.add_creds(&[cred1.clone()][..]));
 
     let usize =
-        or_panic!(bsock1.send_vectored_with_ancillary_to(&mut bufs_send, &mut ancillary1, &path2));
+        or_panic!(bsock1.send_vectored_with_ancillary_to(&bufs_send, &mut ancillary1, &path2));
     assert_eq!(usize, 8);
 
     let mut buf2 = [0; 8];
@@ -603,15 +603,15 @@ fn test_send_vectored_with_ancillary_unix_datagram() {
     let bsock1 = or_panic!(UnixDatagram::bind(&path1));
     let bsock2 = or_panic!(UnixDatagram::bind(&path2));
 
-    let mut buf1 = [1; 8];
-    let mut bufs_send = &mut [IoSliceMut::new(&mut buf1[..])][..];
+    let buf1 = [1; 8];
+    let bufs_send = &[IoSlice::new(&buf1[..])][..];
 
     let mut ancillary1_buffer = [0; 128];
     let mut ancillary1 = SocketAncillary::new(&mut ancillary1_buffer[..]);
     assert!(ancillary1.add_fds(&[bsock1.as_raw_fd()][..]));
 
     or_panic!(bsock1.connect(&path2));
-    let usize = or_panic!(bsock1.send_vectored_with_ancillary(&mut bufs_send, &mut ancillary1));
+    let usize = or_panic!(bsock1.send_vectored_with_ancillary(&bufs_send, &mut ancillary1));
     assert_eq!(usize, 8);
 
     let mut buf2 = [0; 8];
diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs
index d9f5b5bfa3a..de6942968ea 100644
--- a/src/librustdoc/config.rs
+++ b/src/librustdoc/config.rs
@@ -263,6 +263,8 @@ crate struct RenderOptions {
     crate document_private: bool,
     /// Document items that have `doc(hidden)`.
     crate document_hidden: bool,
+    /// If `true`, generate a JSON file in the crate folder instead of HTML redirection files.
+    crate generate_redirect_map: bool,
     crate unstable_features: rustc_feature::UnstableFeatures,
 }
 
@@ -570,6 +572,7 @@ impl Options {
         let document_private = matches.opt_present("document-private-items");
         let document_hidden = matches.opt_present("document-hidden-items");
         let run_check = matches.opt_present("check");
+        let generate_redirect_map = matches.opt_present("generate-redirect-map");
 
         let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(matches, error_format);
 
@@ -627,6 +630,7 @@ impl Options {
                 generate_search_filter,
                 document_private,
                 document_hidden,
+                generate_redirect_map,
                 unstable_features: rustc_feature::UnstableFeatures::from_environment(
                     crate_name.as_deref(),
                 ),
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index 4e762a40f08..394c57c7214 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -111,6 +111,10 @@ crate struct Context<'tcx> {
     /// real location of an item. This is used to allow external links to
     /// publicly reused items to redirect to the right location.
     crate render_redirect_pages: bool,
+    /// `None` by default, depends on the `generate-redirect-map` option flag. If this field is set
+    /// to `Some(...)`, it'll store redirections and then generate a JSON file at the top level of
+    /// the crate.
+    crate redirections: Option<Rc<RefCell<FxHashMap<String, String>>>>,
     /// The map used to ensure all generated 'id=' attributes are unique.
     id_map: Rc<RefCell<IdMap>>,
     /// Tracks section IDs for `Deref` targets so they match in both the main
@@ -404,6 +408,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
             static_root_path,
             generate_search_filter,
             unstable_features,
+            generate_redirect_map,
             ..
         } = options;
 
@@ -509,6 +514,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
             all: Rc::new(RefCell::new(AllTypes::new())),
             errors: Rc::new(receiver),
             cache: Rc::new(cache),
+            redirections: if generate_redirect_map { Some(Default::default()) } else { None },
         };
 
         CURRENT_DEPTH.with(|s| s.set(0));
@@ -587,6 +593,15 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
             &style_files,
         );
         self.shared.fs.write(&settings_file, v.as_bytes())?;
+        if let Some(redirections) = self.redirections.take() {
+            if !redirections.borrow().is_empty() {
+                let redirect_map_path =
+                    self.dst.join(&*krate.name.as_str()).join("redirect-map.json");
+                let paths = serde_json::to_string(&*redirections.borrow()).unwrap();
+                self.shared.ensure_dir(&self.dst.join(&*krate.name.as_str()))?;
+                self.shared.fs.write(&redirect_map_path, paths.as_bytes())?;
+            }
+        }
 
         // Flush pending errors.
         Arc::get_mut(&mut self.shared).unwrap().fs.close();
@@ -675,9 +690,17 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
             // to the new one (without).
             if item_type == ItemType::Macro {
                 let redir_name = format!("{}.{}!.html", item_type, name);
-                let redir_dst = self.dst.join(redir_name);
-                let v = layout::redirect(file_name);
-                self.shared.fs.write(&redir_dst, v.as_bytes())?;
+                if let Some(ref redirections) = self.redirections {
+                    let crate_name = &self.shared.layout.krate;
+                    redirections.borrow_mut().insert(
+                        format!("{}/{}", crate_name, redir_name),
+                        format!("{}/{}", crate_name, file_name),
+                    );
+                } else {
+                    let v = layout::redirect(file_name);
+                    let redir_dst = self.dst.join(redir_name);
+                    self.shared.fs.write(&redir_dst, v.as_bytes())?;
+                }
             }
         }
         Ok(())
@@ -1588,17 +1611,27 @@ impl Context<'_> {
                 &self.shared.style_files,
             )
         } else {
-            let mut url = self.root_path();
             if let Some(&(ref names, ty)) = self.cache.paths.get(&it.def_id) {
+                let mut path = String::new();
                 for name in &names[..names.len() - 1] {
-                    url.push_str(name);
-                    url.push('/');
+                    path.push_str(name);
+                    path.push('/');
+                }
+                path.push_str(&item_path(ty, names.last().unwrap()));
+                match self.redirections {
+                    Some(ref redirections) => {
+                        let mut current_path = String::new();
+                        for name in &self.current {
+                            current_path.push_str(name);
+                            current_path.push('/');
+                        }
+                        current_path.push_str(&item_path(ty, names.last().unwrap()));
+                        redirections.borrow_mut().insert(current_path, path);
+                    }
+                    None => return layout::redirect(&format!("{}{}", self.root_path(), path)),
                 }
-                url.push_str(&item_path(ty, names.last().unwrap()));
-                layout::redirect(&url)
-            } else {
-                String::new()
             }
+            String::new()
         }
     }
 
diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs
index 6b37643a395..c0e91a05dff 100644
--- a/src/librustdoc/lib.rs
+++ b/src/librustdoc/lib.rs
@@ -497,6 +497,13 @@ fn opts() -> Vec<RustcOptGroup> {
             o.optopt("", "test-builder", "The rustc-like binary to use as the test builder", "PATH")
         }),
         unstable("check", |o| o.optflag("", "check", "Run rustdoc checks")),
+        unstable("generate-redirect-map", |o| {
+            o.optflag(
+                "",
+                "generate-redirect-map",
+                "Generate JSON file at the top level instead of generating HTML redirection files",
+            )
+        }),
     ]
 }
 
diff --git a/src/test/run-make-fulldeps/rustdoc-map-file/Makefile b/src/test/run-make-fulldeps/rustdoc-map-file/Makefile
new file mode 100644
index 00000000000..ce977fa0cea
--- /dev/null
+++ b/src/test/run-make-fulldeps/rustdoc-map-file/Makefile
@@ -0,0 +1,5 @@
+-include ../tools.mk
+
+all:
+	$(RUSTDOC) -Z unstable-options --generate-redirect-map foo.rs -o "$(TMPDIR)/out"
+	"$(PYTHON)" validate_json.py "$(TMPDIR)/out"
diff --git a/src/test/run-make-fulldeps/rustdoc-map-file/expected.json b/src/test/run-make-fulldeps/rustdoc-map-file/expected.json
new file mode 100644
index 00000000000..6b1ccbeac30
--- /dev/null
+++ b/src/test/run-make-fulldeps/rustdoc-map-file/expected.json
@@ -0,0 +1,5 @@
+{
+  "foo/macro.foo!.html": "foo/macro.foo.html",
+  "foo/private/struct.Quz.html": "foo/struct.Quz.html",
+  "foo/hidden/struct.Bar.html": "foo/struct.Bar.html"
+}
diff --git a/src/test/run-make-fulldeps/rustdoc-map-file/foo.rs b/src/test/run-make-fulldeps/rustdoc-map-file/foo.rs
new file mode 100644
index 00000000000..e12b9d2292c
--- /dev/null
+++ b/src/test/run-make-fulldeps/rustdoc-map-file/foo.rs
@@ -0,0 +1,16 @@
+pub use private::Quz;
+pub use hidden::Bar;
+
+mod private {
+    pub struct Quz;
+}
+
+#[doc(hidden)]
+pub mod hidden {
+    pub struct Bar;
+}
+
+#[macro_export]
+macro_rules! foo {
+    () => {}
+}
diff --git a/src/test/run-make-fulldeps/rustdoc-map-file/validate_json.py b/src/test/run-make-fulldeps/rustdoc-map-file/validate_json.py
new file mode 100755
index 00000000000..5c14c90b70d
--- /dev/null
+++ b/src/test/run-make-fulldeps/rustdoc-map-file/validate_json.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python
+
+import os
+import sys
+import json
+
+
+def find_redirect_map_file(folder, errors):
+    for root, dirs, files in os.walk(folder):
+        for name in files:
+            if not name.endswith("redirect-map.json"):
+                continue
+            with open(os.path.join(root, name)) as f:
+                data = json.load(f)
+            with open("expected.json") as f:
+                expected = json.load(f)
+            for key in expected:
+                if expected[key] != data.get(key):
+                    errors.append("Expected `{}` for key `{}`, found: `{}`".format(
+                        expected[key], key, data.get(key)))
+                else:
+                    del data[key]
+            for key in data:
+                errors.append("Extra data not expected: key: `{}`, data: `{}`".format(
+                    key, data[key]))
+            return True
+    return False
+
+
+if len(sys.argv) != 2:
+    print("Expected doc directory to check!")
+    sys.exit(1)
+
+errors = []
+if not find_redirect_map_file(sys.argv[1], errors):
+    print("Didn't find the map file in `{}`...".format(sys.argv[1]))
+    sys.exit(1)
+for err in errors:
+    print("=> {}".format(err))
+if len(errors) != 0:
+    sys.exit(1)
diff --git a/src/test/rustdoc/redirect-map-empty.rs b/src/test/rustdoc/redirect-map-empty.rs
new file mode 100644
index 00000000000..e9d021e0fa8
--- /dev/null
+++ b/src/test/rustdoc/redirect-map-empty.rs
@@ -0,0 +1,6 @@
+// compile-flags: -Z unstable-options --generate-redirect-map
+
+#![crate_name = "foo"]
+
+// @!has foo/redirect-map.json
+pub struct Foo;
diff --git a/src/test/rustdoc/redirect-map.rs b/src/test/rustdoc/redirect-map.rs
new file mode 100644
index 00000000000..b7f16b64e38
--- /dev/null
+++ b/src/test/rustdoc/redirect-map.rs
@@ -0,0 +1,23 @@
+// compile-flags: -Z unstable-options --generate-redirect-map
+
+#![crate_name = "foo"]
+
+// @!has foo/private/struct.Quz.html
+// @!has foo/hidden/struct.Bar.html
+// @has foo/redirect-map.json
+pub use private::Quz;
+pub use hidden::Bar;
+
+mod private {
+    pub struct Quz;
+}
+
+#[doc(hidden)]
+pub mod hidden {
+    pub struct Bar;
+}
+
+#[macro_export]
+macro_rules! foo {
+  () => {}
+}
diff --git a/src/test/ui/simd/issue-17170.rs b/src/test/ui/simd/issue-17170.rs
index 49cfbab9a3e..8d70dacdc90 100644
--- a/src/test/ui/simd/issue-17170.rs
+++ b/src/test/ui/simd/issue-17170.rs
@@ -1,8 +1,8 @@
+// run-pass
 #![feature(repr_simd)]
 
 #[repr(simd)]
 struct T(f64, f64, f64);
-//~^ ERROR SIMD vector length must be a power of two
 
 static X: T = T(0.0, 0.0, 0.0);
 
diff --git a/src/test/ui/simd/issue-17170.stderr b/src/test/ui/simd/issue-17170.stderr
deleted file mode 100644
index b35c3c4dc98..00000000000
--- a/src/test/ui/simd/issue-17170.stderr
+++ /dev/null
@@ -1,11 +0,0 @@
-error[E0075]: SIMD vector length must be a power of two
-  --> $DIR/issue-17170.rs:4:1
-   |
-LL | struct T(f64, f64, f64);
-   | ^^^^^^^^^^^^^^^^^^^^^^^^
-
-error: monomorphising SIMD type `T` of non-power-of-two length
-
-error: aborting due to 2 previous errors
-
-For more information about this error, try `rustc --explain E0075`.
diff --git a/src/test/ui/simd/issue-39720.rs b/src/test/ui/simd/issue-39720.rs
index 7d596926512..8cf841f9371 100644
--- a/src/test/ui/simd/issue-39720.rs
+++ b/src/test/ui/simd/issue-39720.rs
@@ -1,3 +1,4 @@
+// run-pass
 // ignore-emscripten FIXME(#45351)
 
 #![feature(repr_simd, platform_intrinsics)]
@@ -5,12 +6,10 @@
 #[repr(simd)]
 #[derive(Copy, Clone, Debug)]
 pub struct Char3(pub i8, pub i8, pub i8);
-//~^ ERROR SIMD vector length must be a power of two
 
 #[repr(simd)]
 #[derive(Copy, Clone, Debug)]
 pub struct Short3(pub i16, pub i16, pub i16);
-//~^ ERROR SIMD vector length must be a power of two
 
 extern "platform-intrinsic" {
     fn simd_cast<T, U>(x: T) -> U;
diff --git a/src/test/ui/simd/issue-39720.stderr b/src/test/ui/simd/issue-39720.stderr
deleted file mode 100644
index 355ceff0050..00000000000
--- a/src/test/ui/simd/issue-39720.stderr
+++ /dev/null
@@ -1,15 +0,0 @@
-error[E0075]: SIMD vector length must be a power of two
-  --> $DIR/issue-39720.rs:7:1
-   |
-LL | pub struct Char3(pub i8, pub i8, pub i8);
-   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-error[E0075]: SIMD vector length must be a power of two
-  --> $DIR/issue-39720.rs:12:1
-   |
-LL | pub struct Short3(pub i16, pub i16, pub i16);
-   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-error: aborting due to 2 previous errors
-
-For more information about this error, try `rustc --explain E0075`.
diff --git a/src/test/ui/simd/simd-type-generic-monomorphisation-power-of-two.rs b/src/test/ui/simd/simd-type-generic-monomorphisation-power-of-two.rs
index 3a0b9e02663..9b645d363e9 100644
--- a/src/test/ui/simd/simd-type-generic-monomorphisation-power-of-two.rs
+++ b/src/test/ui/simd/simd-type-generic-monomorphisation-power-of-two.rs
@@ -1,9 +1,7 @@
-// build-fail
+// run-pass
 
 #![feature(repr_simd, platform_intrinsics)]
 
-// error-pattern:monomorphising SIMD type `Simd<3_usize>` of non-power-of-two length
-
 #[repr(simd)]
 struct Simd<const N: usize>([f32; N]);
 
diff --git a/src/test/ui/simd/simd-type-generic-monomorphisation-power-of-two.stderr b/src/test/ui/simd/simd-type-generic-monomorphisation-power-of-two.stderr
deleted file mode 100644
index 82cc0d8714a..00000000000
--- a/src/test/ui/simd/simd-type-generic-monomorphisation-power-of-two.stderr
+++ /dev/null
@@ -1,4 +0,0 @@
-error: monomorphising SIMD type `Simd<3_usize>` of non-power-of-two length
-
-error: aborting due to previous error
-
diff --git a/src/test/ui/simd/simd-type.rs b/src/test/ui/simd/simd-type.rs
index cc7443d0485..73d032a0c8e 100644
--- a/src/test/ui/simd/simd-type.rs
+++ b/src/test/ui/simd/simd-type.rs
@@ -10,7 +10,7 @@ struct empty; //~ ERROR SIMD vector cannot be empty
 struct empty2([f32; 0]); //~ ERROR SIMD vector cannot be empty
 
 #[repr(simd)]
-struct pow2([f32; 7]); //~ ERROR SIMD vector length must be a power of two
+struct pow2([f32; 7]);
 
 #[repr(simd)]
 struct i64f64(i64, f64); //~ ERROR SIMD vector should be homogeneous
diff --git a/src/test/ui/simd/simd-type.stderr b/src/test/ui/simd/simd-type.stderr
index 8b15ef05e03..823f10f5daf 100644
--- a/src/test/ui/simd/simd-type.stderr
+++ b/src/test/ui/simd/simd-type.stderr
@@ -10,12 +10,6 @@ error[E0075]: SIMD vector cannot be empty
 LL | struct empty2([f32; 0]);
    | ^^^^^^^^^^^^^^^^^^^^^^^^
 
-error[E0075]: SIMD vector length must be a power of two
-  --> $DIR/simd-type.rs:13:1
-   |
-LL | struct pow2([f32; 7]);
-   | ^^^^^^^^^^^^^^^^^^^^^^
-
 error[E0076]: SIMD vector should be homogeneous
   --> $DIR/simd-type.rs:16:1
    |
@@ -40,7 +34,7 @@ error[E0075]: SIMD vector cannot have more than 32768 elements
 LL | struct TooBig([f32; 65536]);
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-error: aborting due to 7 previous errors
+error: aborting due to 6 previous errors
 
 Some errors have detailed explanations: E0075, E0076, E0077.
 For more information about an error, try `rustc --explain E0075`.
diff --git a/src/tools/cargo b/src/tools/cargo
-Subproject 572e201536dc2e4920346e28037b63c0f4d88b3
+Subproject c68432f1e5cbbc09833699a951b1b5b059651df
diff --git a/src/tools/compiletest/Cargo.toml b/src/tools/compiletest/Cargo.toml
index 209254a5d5e..1ab560ac09d 100644
--- a/src/tools/compiletest/Cargo.toml
+++ b/src/tools/compiletest/Cargo.toml
@@ -5,7 +5,9 @@ version = "0.0.0"
 edition = "2018"
 
 [dependencies]
+colored = "2"
 diff = "0.1.10"
+unified-diff = "0.2.1"
 getopts = "0.2"
 tracing = "0.1"
 tracing-subscriber = { version = "0.2.13", default-features = false, features = ["fmt", "env-filter", "smallvec", "parking_lot", "ansi"] }
diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs
index 723c7f86832..2e7b42b6c7c 100644
--- a/src/tools/compiletest/src/runtest.rs
+++ b/src/tools/compiletest/src/runtest.rs
@@ -13,6 +13,7 @@ use crate::header::TestProps;
 use crate::json;
 use crate::util::get_pointer_width;
 use crate::util::{logv, PathBufExt};
+use crate::ColorConfig;
 use regex::{Captures, Regex};
 use rustfix::{apply_suggestions, get_suggestions_from_json, Filter};
 
@@ -2440,12 +2441,43 @@ impl<'test> TestCx<'test> {
                 }
             })
         };
-        let mut diff = Command::new("diff");
-        // diff recursively, showing context, and excluding .css files
-        diff.args(&["-u", "-r", "-x", "*.css"]).args(&[&compare_dir, out_dir]);
 
-        let output = if let Some(pager) = pager {
-            let diff_pid = diff.stdout(Stdio::piped()).spawn().expect("failed to run `diff`");
+        let diff_filename = format!("build/tmp/rustdoc-compare-{}.diff", std::process::id());
+
+        {
+            let mut diff_output = File::create(&diff_filename).unwrap();
+            for entry in walkdir::WalkDir::new(out_dir) {
+                let entry = entry.expect("failed to read file");
+                let extension = entry.path().extension().and_then(|p| p.to_str());
+                if entry.file_type().is_file()
+                    && (extension == Some("html".into()) || extension == Some("js".into()))
+                {
+                    let expected_path =
+                        compare_dir.join(entry.path().strip_prefix(&out_dir).unwrap());
+                    let expected =
+                        if let Ok(s) = std::fs::read(&expected_path) { s } else { continue };
+                    let actual_path = entry.path();
+                    let actual = std::fs::read(&actual_path).unwrap();
+                    diff_output
+                        .write_all(&unified_diff::diff(
+                            &expected,
+                            &expected_path.to_string_lossy(),
+                            &actual,
+                            &actual_path.to_string_lossy(),
+                            3,
+                        ))
+                        .unwrap();
+                }
+            }
+        }
+
+        match self.config.color {
+            ColorConfig::AlwaysColor => colored::control::set_override(true),
+            ColorConfig::NeverColor => colored::control::set_override(false),
+            _ => {}
+        }
+
+        if let Some(pager) = pager {
             let pager = pager.trim();
             if self.config.verbose {
                 eprintln!("using pager {}", pager);
@@ -2453,24 +2485,48 @@ impl<'test> TestCx<'test> {
             let output = Command::new(pager)
                 // disable paging; we want this to be non-interactive
                 .env("PAGER", "")
-                .stdin(diff_pid.stdout.unwrap())
+                .stdin(File::open(&diff_filename).unwrap())
                 // Capture output and print it explicitly so it will in turn be
                 // captured by libtest.
                 .output()
                 .unwrap();
             assert!(output.status.success());
-            output
+            println!("{}", String::from_utf8_lossy(&output.stdout));
+            eprintln!("{}", String::from_utf8_lossy(&output.stderr));
         } else {
-            eprintln!("warning: no pager configured, falling back to `diff --color`");
+            use colored::Colorize;
+            eprintln!("warning: no pager configured, falling back to unified diff");
             eprintln!(
                 "help: try configuring a git pager (e.g. `delta`) with `git config --global core.pager delta`"
             );
-            let output = diff.arg("--color").output().unwrap();
-            assert!(output.status.success() || output.status.code() == Some(1));
-            output
+            let mut out = io::stdout();
+            let mut diff = BufReader::new(File::open(&diff_filename).unwrap());
+            let mut line = Vec::new();
+            loop {
+                line.truncate(0);
+                match diff.read_until(b'\n', &mut line) {
+                    Ok(0) => break,
+                    Ok(_) => {}
+                    Err(e) => eprintln!("ERROR: {:?}", e),
+                }
+                match String::from_utf8(line.clone()) {
+                    Ok(line) => {
+                        if line.starts_with("+") {
+                            write!(&mut out, "{}", line.green()).unwrap();
+                        } else if line.starts_with("-") {
+                            write!(&mut out, "{}", line.red()).unwrap();
+                        } else if line.starts_with("@") {
+                            write!(&mut out, "{}", line.blue()).unwrap();
+                        } else {
+                            out.write_all(line.as_bytes()).unwrap();
+                        }
+                    }
+                    Err(_) => {
+                        write!(&mut out, "{}", String::from_utf8_lossy(&line).reversed()).unwrap();
+                    }
+                }
+            }
         };
-        println!("{}", String::from_utf8_lossy(&output.stdout));
-        eprintln!("{}", String::from_utf8_lossy(&output.stderr));
     }
 
     fn run_rustdoc_json_test(&self) {
diff --git a/triagebot.toml b/triagebot.toml
index c0cf50e5167..8b6157cd4aa 100644
--- a/triagebot.toml
+++ b/triagebot.toml
@@ -102,6 +102,15 @@ message_on_add = """\
 """
 message_on_remove = "Issue #{number}'s prioritization request has been removed."
 
+[notify-zulip."T-rustdoc"]
+required_labels = ["I-nominated"]
+zulip_stream = 266220 # #rustdoc
+topic = "nominated: #{number}"
+message_on_add = """\
+@*T-rustdoc* issue #{number} "{title}" has been nominated for `T-rustdoc` discussion.
+"""
+message_on_remove = "Issue #{number}'s nomination request has been removed."
+
 [github-releases]
 format = "rustc"
 project-name = "Rust"