about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_codegen_ssa/src/mir/rvalue.rs9
-rw-r--r--compiler/rustc_error_codes/src/error_codes/E0591.md4
-rw-r--r--compiler/rustc_hir_analysis/src/check/expr.rs12
-rw-r--r--compiler/rustc_infer/src/infer/mod.rs2
-rw-r--r--compiler/rustc_target/src/spec/mod.rs14
-rw-r--r--library/std/src/io/error/tests.rs2
-rw-r--r--library/std/src/sys/sgx/thread_local_key.rs5
-rw-r--r--library/std/src/sys/solid/thread_local_key.rs5
-rw-r--r--library/std/src/sys/unix/kernel_copy.rs2
-rw-r--r--library/std/src/sys/unix/thread.rs10
-rw-r--r--library/std/src/sys/unix/thread_local_key.rs5
-rw-r--r--library/std/src/sys/unsupported/thread_local_key.rs5
-rw-r--r--library/std/src/sys/wasi/mod.rs3
-rw-r--r--library/std/src/sys/wasi/os.rs34
-rw-r--r--library/std/src/sys/windows/c.rs17
-rw-r--r--library/std/src/sys/windows/thread_local_key.rs196
-rw-r--r--library/std/src/sys/windows/thread_local_key/tests.rs53
-rw-r--r--library/std/src/sys_common/mod.rs9
-rw-r--r--library/std/src/sys_common/thread_local_key.rs24
-rwxr-xr-xsrc/ci/scripts/should-skip-this.sh2
-rw-r--r--src/librustdoc/html/static/css/rustdoc.css9
-rw-r--r--src/test/ui/async-await/issue-64130-1-sync.rs2
-rw-r--r--src/test/ui/async-await/issue-64130-2-send.rs2
-rw-r--r--src/test/ui/async-await/issue-64130-3-other.rs2
-rw-r--r--src/test/ui/const-generics/occurs-check/unused-substs-2.rs2
-rw-r--r--src/test/ui/const-generics/occurs-check/unused-substs-3.rs2
-rw-r--r--src/test/ui/deprecation/deprecation-lint.rs2
-rw-r--r--src/test/ui/dyn-star/box.rs17
-rw-r--r--src/test/ui/explain.stdout4
-rw-r--r--src/test/ui/issues/issue-102964.rs10
-rw-r--r--src/test/ui/issues/issue-102964.stderr19
-rw-r--r--src/test/ui/issues/issue-29746.rs2
-rw-r--r--src/test/ui/issues/issue-75907.rs2
-rw-r--r--src/test/ui/issues/issue-75907_b.rs2
-rw-r--r--src/test/ui/lint/lint-stability-deprecated.rs2
-rw-r--r--src/test/ui/proc-macro/meta-macro-hygiene.rs4
-rw-r--r--src/test/ui/proc-macro/meta-macro-hygiene.stdout4
-rw-r--r--src/test/ui/type/type-check/assignment-in-if.rs6
-rw-r--r--src/test/ui/type/type-check/assignment-in-if.stderr19
-rw-r--r--src/tools/tidy/src/features.rs263
40 files changed, 502 insertions, 286 deletions
diff --git a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs
index abf3c9a363f..4ed99df1e81 100644
--- a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs
+++ b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs
@@ -14,6 +14,7 @@ use rustc_middle::ty::cast::{CastTy, IntTy};
 use rustc_middle::ty::layout::{HasTyCtxt, LayoutOf};
 use rustc_middle::ty::{self, adjustment::PointerCast, Instance, Ty, TyCtxt};
 use rustc_span::source_map::{Span, DUMMY_SP};
+use rustc_target::abi::Size;
 
 impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
     #[instrument(level = "trace", skip(self, bx))]
@@ -285,6 +286,14 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
                                 bug!("Only valid to do a DynStar cast into a DynStar type")
                             };
                         let vtable = get_vtable(bx.cx(), source.ty(self.mir, bx.tcx()), trait_ref);
+                        let vtable = bx.pointercast(vtable, bx.cx().type_ptr_to(bx.cx().type_isize()));
+                        // FIXME(dyn-star): this is probably not the best way to check if this is
+                        // a pointer, and really we should ensure that the value is a suitable
+                        // pointer earlier in the compilation process.
+                        let data = match operand.layout.pointee_info_at(bx.cx(), Size::ZERO) {
+                            Some(_) => bx.ptrtoint(data, bx.cx().type_isize()),
+                            None => data,
+                        };
                         OperandValue::Pair(data, vtable)
                     }
                     mir::CastKind::Pointer(
diff --git a/compiler/rustc_error_codes/src/error_codes/E0591.md b/compiler/rustc_error_codes/src/error_codes/E0591.md
index f49805d9b4e..6ed8370e8c1 100644
--- a/compiler/rustc_error_codes/src/error_codes/E0591.md
+++ b/compiler/rustc_error_codes/src/error_codes/E0591.md
@@ -53,8 +53,8 @@ unsafe {
 ```
 
 Here, transmute is being used to convert the types of the fn arguments.
-This pattern is incorrect because, because the type of `foo` is a function
-**item** (`typeof(foo)`), which is zero-sized, and the target type (`fn()`)
+This pattern is incorrect because the type of `foo` is a function **item**
+(`typeof(foo)`), which is zero-sized, and the target type (`fn()`)
 is a function pointer, which is not zero-sized.
 This pattern should be rewritten. There are a few possible ways to do this:
 
diff --git a/compiler/rustc_hir_analysis/src/check/expr.rs b/compiler/rustc_hir_analysis/src/check/expr.rs
index 375c13d922b..34c25784597 100644
--- a/compiler/rustc_hir_analysis/src/check/expr.rs
+++ b/compiler/rustc_hir_analysis/src/check/expr.rs
@@ -1051,8 +1051,20 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                 rhs_expr,
             ) = lhs.kind
             {
+                // if x == 1 && y == 2 { .. }
+                //                 +
                 let actual_lhs_ty = self.check_expr(&rhs_expr);
                 (Applicability::MaybeIncorrect, self.can_coerce(rhs_ty, actual_lhs_ty))
+            } else if let ExprKind::Binary(
+                Spanned { node: hir::BinOpKind::And | hir::BinOpKind::Or, .. },
+                lhs_expr,
+                _,
+            ) = rhs.kind
+            {
+                // if x == 1 && y == 2 { .. }
+                //       +
+                let actual_rhs_ty = self.check_expr(&lhs_expr);
+                (Applicability::MaybeIncorrect, self.can_coerce(actual_rhs_ty, lhs_ty))
             } else {
                 (Applicability::MaybeIncorrect, false)
             };
diff --git a/compiler/rustc_infer/src/infer/mod.rs b/compiler/rustc_infer/src/infer/mod.rs
index 441dc3c7e88..5a5e9db81a2 100644
--- a/compiler/rustc_infer/src/infer/mod.rs
+++ b/compiler/rustc_infer/src/infer/mod.rs
@@ -1283,7 +1283,7 @@ impl<'tcx> InferCtxt<'tcx> {
         assert!(old_value.is_none());
     }
 
-    /// Process the region constraints and return any any errors that
+    /// Process the region constraints and return any errors that
     /// result. After this, no more unification operations should be
     /// done -- or the compiler will panic -- but it is legal to use
     /// `resolve_vars_if_possible` as well as `fully_resolve`.
diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs
index 9396d769dc7..8909cf33af9 100644
--- a/compiler/rustc_target/src/spec/mod.rs
+++ b/compiler/rustc_target/src/spec/mod.rs
@@ -1739,11 +1739,15 @@ impl TargetOptions {
                     self.lld_flavor_json,
                     self.linker_is_gnu_json,
                 );
-                match linker_flavor {
-                    LinkerFlavor::Gnu(_, Lld::Yes)
-                    | LinkerFlavor::Darwin(_, Lld::Yes)
-                    | LinkerFlavor::Msvc(Lld::Yes) => {}
-                    _ => add_link_args_iter(args, linker_flavor, args_json.iter().cloned()),
+                // Normalize to no lld to avoid asserts.
+                let linker_flavor = match linker_flavor {
+                    LinkerFlavor::Gnu(cc, _) => LinkerFlavor::Gnu(cc, Lld::No),
+                    LinkerFlavor::Darwin(cc, _) => LinkerFlavor::Darwin(cc, Lld::No),
+                    LinkerFlavor::Msvc(_) => LinkerFlavor::Msvc(Lld::No),
+                    _ => linker_flavor,
+                };
+                if !args.contains_key(&linker_flavor) {
+                    add_link_args_iter(args, linker_flavor, args_json.iter().cloned());
                 }
             }
         }
diff --git a/library/std/src/io/error/tests.rs b/library/std/src/io/error/tests.rs
index c897a5e8701..16c634e9afd 100644
--- a/library/std/src/io/error/tests.rs
+++ b/library/std/src/io/error/tests.rs
@@ -86,7 +86,7 @@ fn test_errorkind_packing() {
     assert_eq!(Error::from(ErrorKind::NotFound).kind(), ErrorKind::NotFound);
     assert_eq!(Error::from(ErrorKind::PermissionDenied).kind(), ErrorKind::PermissionDenied);
     assert_eq!(Error::from(ErrorKind::Uncategorized).kind(), ErrorKind::Uncategorized);
-    // Check that the innards look like like what we want.
+    // Check that the innards look like what we want.
     assert_matches!(
         Error::from(ErrorKind::OutOfMemory).repr.data(),
         ErrorData::Simple(ErrorKind::OutOfMemory),
diff --git a/library/std/src/sys/sgx/thread_local_key.rs b/library/std/src/sys/sgx/thread_local_key.rs
index b21784475f0..c7a57d3a3d4 100644
--- a/library/std/src/sys/sgx/thread_local_key.rs
+++ b/library/std/src/sys/sgx/thread_local_key.rs
@@ -21,8 +21,3 @@ pub unsafe fn get(key: Key) -> *mut u8 {
 pub unsafe fn destroy(key: Key) {
     Tls::destroy(AbiKey::from_usize(key))
 }
-
-#[inline]
-pub fn requires_synchronized_create() -> bool {
-    false
-}
diff --git a/library/std/src/sys/solid/thread_local_key.rs b/library/std/src/sys/solid/thread_local_key.rs
index b17521f701d..b37bf999698 100644
--- a/library/std/src/sys/solid/thread_local_key.rs
+++ b/library/std/src/sys/solid/thread_local_key.rs
@@ -19,8 +19,3 @@ pub unsafe fn get(_key: Key) -> *mut u8 {
 pub unsafe fn destroy(_key: Key) {
     panic!("should not be used on the solid target");
 }
-
-#[inline]
-pub fn requires_synchronized_create() -> bool {
-    panic!("should not be used on the solid target");
-}
diff --git a/library/std/src/sys/unix/kernel_copy.rs b/library/std/src/sys/unix/kernel_copy.rs
index 8f7abb55e23..94546ca09d0 100644
--- a/library/std/src/sys/unix/kernel_copy.rs
+++ b/library/std/src/sys/unix/kernel_copy.rs
@@ -20,7 +20,7 @@
 //! Since those syscalls have requirements that cannot be fully checked in advance and
 //! gathering additional information about file descriptors would require additional syscalls
 //! anyway it simply attempts to use them one after another (guided by inaccurate hints) to
-//! figure out which one works and and falls back to the generic read-write copy loop if none of them
+//! figure out which one works and falls back to the generic read-write copy loop if none of them
 //! does.
 //! Once a working syscall is found for a pair of file descriptors it will be called in a loop
 //! until the copy operation is completed.
diff --git a/library/std/src/sys/unix/thread.rs b/library/std/src/sys/unix/thread.rs
index 7df4add8ce1..42ac6fcd8bf 100644
--- a/library/std/src/sys/unix/thread.rs
+++ b/library/std/src/sys/unix/thread.rs
@@ -766,6 +766,16 @@ pub mod guard {
             const GUARD_PAGES: usize = 1;
             let guard = guardaddr..guardaddr + GUARD_PAGES * page_size;
             Some(guard)
+        } else if cfg!(target_os = "openbsd") {
+            // OpenBSD stack already includes a guard page, and stack is
+            // immutable.
+            //
+            // We'll just note where we expect rlimit to start
+            // faulting, so our handler can report "stack overflow", and
+            // trust that the kernel's own stack guard will work.
+            let stackptr = get_stack_start_aligned()?;
+            let stackaddr = stackptr.addr();
+            Some(stackaddr - page_size..stackaddr)
         } else {
             // Reallocate the last page of the stack.
             // This ensures SIGBUS will be raised on
diff --git a/library/std/src/sys/unix/thread_local_key.rs b/library/std/src/sys/unix/thread_local_key.rs
index 2c5b94b1e61..2b2d079ee4d 100644
--- a/library/std/src/sys/unix/thread_local_key.rs
+++ b/library/std/src/sys/unix/thread_local_key.rs
@@ -27,8 +27,3 @@ pub unsafe fn destroy(key: Key) {
     let r = libc::pthread_key_delete(key);
     debug_assert_eq!(r, 0);
 }
-
-#[inline]
-pub fn requires_synchronized_create() -> bool {
-    false
-}
diff --git a/library/std/src/sys/unsupported/thread_local_key.rs b/library/std/src/sys/unsupported/thread_local_key.rs
index c31b61cbf56..b6e5e4cd2e1 100644
--- a/library/std/src/sys/unsupported/thread_local_key.rs
+++ b/library/std/src/sys/unsupported/thread_local_key.rs
@@ -19,8 +19,3 @@ pub unsafe fn get(_key: Key) -> *mut u8 {
 pub unsafe fn destroy(_key: Key) {
     panic!("should not be used on this target");
 }
-
-#[inline]
-pub fn requires_synchronized_create() -> bool {
-    panic!("should not be used on this target");
-}
diff --git a/library/std/src/sys/wasi/mod.rs b/library/std/src/sys/wasi/mod.rs
index 683a07a34dc..c8c47763a34 100644
--- a/library/std/src/sys/wasi/mod.rs
+++ b/library/std/src/sys/wasi/mod.rs
@@ -25,6 +25,9 @@ pub mod cmath;
 pub mod env;
 pub mod fd;
 pub mod fs;
+#[allow(unused)]
+#[path = "../wasm/atomics/futex.rs"]
+pub mod futex;
 pub mod io;
 #[path = "../unsupported/locks/mod.rs"]
 pub mod locks;
diff --git a/library/std/src/sys/wasi/os.rs b/library/std/src/sys/wasi/os.rs
index cab2887dfcf..f5513e9996d 100644
--- a/library/std/src/sys/wasi/os.rs
+++ b/library/std/src/sys/wasi/os.rs
@@ -1,11 +1,11 @@
 #![deny(unsafe_op_in_unsafe_fn)]
 
-use crate::any::Any;
 use crate::error::Error as StdError;
 use crate::ffi::{CStr, OsStr, OsString};
 use crate::fmt;
 use crate::io;
 use crate::marker::PhantomData;
+use crate::ops::Drop;
 use crate::os::wasi::prelude::*;
 use crate::path::{self, PathBuf};
 use crate::str;
@@ -24,10 +24,26 @@ mod libc {
     }
 }
 
-#[cfg(not(target_feature = "atomics"))]
-pub unsafe fn env_lock() -> impl Any {
-    // No need for a lock if we're single-threaded, but this function will need
-    // to get implemented for multi-threaded scenarios
+cfg_if::cfg_if! {
+    if #[cfg(target_feature = "atomics")] {
+        // Access to the environment must be protected by a lock in multi-threaded scenarios.
+        use crate::sync::{PoisonError, RwLock};
+        static ENV_LOCK: RwLock<()> = RwLock::new(());
+        pub fn env_read_lock() -> impl Drop {
+            ENV_LOCK.read().unwrap_or_else(PoisonError::into_inner)
+        }
+        pub fn env_write_lock() -> impl Drop {
+            ENV_LOCK.write().unwrap_or_else(PoisonError::into_inner)
+        }
+    } else {
+        // No need for a lock if we are single-threaded.
+        pub fn env_read_lock() -> impl Drop {
+            Box::new(())
+        }
+        pub fn env_write_lock() -> impl Drop {
+            Box::new(())
+        }
+    }
 }
 
 pub fn errno() -> i32 {
@@ -144,7 +160,7 @@ impl Iterator for Env {
 
 pub fn env() -> Env {
     unsafe {
-        let _guard = env_lock();
+        let _guard = env_read_lock();
         let mut environ = libc::environ;
         let mut result = Vec::new();
         if !environ.is_null() {
@@ -175,7 +191,7 @@ pub fn env() -> Env {
 
 pub fn getenv(k: &OsStr) -> Option<OsString> {
     let s = run_with_cstr(k.as_bytes(), |k| unsafe {
-        let _guard = env_lock();
+        let _guard = env_read_lock();
         Ok(libc::getenv(k.as_ptr()) as *const libc::c_char)
     })
     .ok()?;
@@ -189,7 +205,7 @@ pub fn getenv(k: &OsStr) -> Option<OsString> {
 pub fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> {
     run_with_cstr(k.as_bytes(), |k| {
         run_with_cstr(v.as_bytes(), |v| unsafe {
-            let _guard = env_lock();
+            let _guard = env_write_lock();
             cvt(libc::setenv(k.as_ptr(), v.as_ptr(), 1)).map(drop)
         })
     })
@@ -197,7 +213,7 @@ pub fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> {
 
 pub fn unsetenv(n: &OsStr) -> io::Result<()> {
     run_with_cstr(n.as_bytes(), |nbuf| unsafe {
-        let _guard = env_lock();
+        let _guard = env_write_lock();
         cvt(libc::unsetenv(nbuf.as_ptr())).map(drop)
     })
 }
diff --git a/library/std/src/sys/windows/c.rs b/library/std/src/sys/windows/c.rs
index c61a7e7d1e4..732e227d7e2 100644
--- a/library/std/src/sys/windows/c.rs
+++ b/library/std/src/sys/windows/c.rs
@@ -71,6 +71,7 @@ pub type BCRYPT_ALG_HANDLE = LPVOID;
 pub type PCONDITION_VARIABLE = *mut CONDITION_VARIABLE;
 pub type PLARGE_INTEGER = *mut c_longlong;
 pub type PSRWLOCK = *mut SRWLOCK;
+pub type LPINIT_ONCE = *mut INIT_ONCE;
 
 pub type SOCKET = crate::os::windows::raw::SOCKET;
 pub type socklen_t = c_int;
@@ -194,6 +195,9 @@ pub const DUPLICATE_SAME_ACCESS: DWORD = 0x00000002;
 
 pub const CONDITION_VARIABLE_INIT: CONDITION_VARIABLE = CONDITION_VARIABLE { ptr: ptr::null_mut() };
 pub const SRWLOCK_INIT: SRWLOCK = SRWLOCK { ptr: ptr::null_mut() };
+pub const INIT_ONCE_STATIC_INIT: INIT_ONCE = INIT_ONCE { ptr: ptr::null_mut() };
+
+pub const INIT_ONCE_INIT_FAILED: DWORD = 0x00000004;
 
 pub const DETACHED_PROCESS: DWORD = 0x00000008;
 pub const CREATE_NEW_PROCESS_GROUP: DWORD = 0x00000200;
@@ -565,6 +569,10 @@ pub struct CONDITION_VARIABLE {
 pub struct SRWLOCK {
     pub ptr: LPVOID,
 }
+#[repr(C)]
+pub struct INIT_ONCE {
+    pub ptr: LPVOID,
+}
 
 #[repr(C)]
 pub struct REPARSE_MOUNTPOINT_DATA_BUFFER {
@@ -955,6 +963,7 @@ extern "system" {
     pub fn TlsAlloc() -> DWORD;
     pub fn TlsGetValue(dwTlsIndex: DWORD) -> LPVOID;
     pub fn TlsSetValue(dwTlsIndex: DWORD, lpTlsvalue: LPVOID) -> BOOL;
+    pub fn TlsFree(dwTlsIndex: DWORD) -> BOOL;
     pub fn GetLastError() -> DWORD;
     pub fn QueryPerformanceFrequency(lpFrequency: *mut LARGE_INTEGER) -> BOOL;
     pub fn QueryPerformanceCounter(lpPerformanceCount: *mut LARGE_INTEGER) -> BOOL;
@@ -1114,6 +1123,14 @@ extern "system" {
     pub fn TryAcquireSRWLockExclusive(SRWLock: PSRWLOCK) -> BOOLEAN;
     pub fn TryAcquireSRWLockShared(SRWLock: PSRWLOCK) -> BOOLEAN;
 
+    pub fn InitOnceBeginInitialize(
+        lpInitOnce: LPINIT_ONCE,
+        dwFlags: DWORD,
+        fPending: LPBOOL,
+        lpContext: *mut LPVOID,
+    ) -> BOOL;
+    pub fn InitOnceComplete(lpInitOnce: LPINIT_ONCE, dwFlags: DWORD, lpContext: LPVOID) -> BOOL;
+
     pub fn CompareStringOrdinal(
         lpString1: LPCWSTR,
         cchCount1: c_int,
diff --git a/library/std/src/sys/windows/thread_local_key.rs b/library/std/src/sys/windows/thread_local_key.rs
index ec670238e6f..17628b7579b 100644
--- a/library/std/src/sys/windows/thread_local_key.rs
+++ b/library/std/src/sys/windows/thread_local_key.rs
@@ -1,11 +1,16 @@
-use crate::mem::ManuallyDrop;
+use crate::cell::UnsafeCell;
 use crate::ptr;
-use crate::sync::atomic::AtomicPtr;
-use crate::sync::atomic::Ordering::SeqCst;
+use crate::sync::atomic::{
+    AtomicPtr, AtomicU32,
+    Ordering::{AcqRel, Acquire, Relaxed, Release},
+};
 use crate::sys::c;
 
-pub type Key = c::DWORD;
-pub type Dtor = unsafe extern "C" fn(*mut u8);
+#[cfg(test)]
+mod tests;
+
+type Key = c::DWORD;
+type Dtor = unsafe extern "C" fn(*mut u8);
 
 // Turns out, like pretty much everything, Windows is pretty close the
 // functionality that Unix provides, but slightly different! In the case of
@@ -22,60 +27,109 @@ pub type Dtor = unsafe extern "C" fn(*mut u8);
 // To accomplish this feat, we perform a number of threads, all contained
 // within this module:
 //
-// * All TLS destructors are tracked by *us*, not the windows runtime. This
+// * All TLS destructors are tracked by *us*, not the Windows runtime. This
 //   means that we have a global list of destructors for each TLS key that
 //   we know about.
 // * When a thread exits, we run over the entire list and run dtors for all
 //   non-null keys. This attempts to match Unix semantics in this regard.
 //
-// This ends up having the overhead of using a global list, having some
-// locks here and there, and in general just adding some more code bloat. We
-// attempt to optimize runtime by forgetting keys that don't have
-// destructors, but this only gets us so far.
-//
 // For more details and nitty-gritty, see the code sections below!
 //
 // [1]: https://www.codeproject.com/Articles/8113/Thread-Local-Storage-The-C-Way
-// [2]: https://github.com/ChromiumWebApps/chromium/blob/master/base
-//                        /threading/thread_local_storage_win.cc#L42
+// [2]: https://github.com/ChromiumWebApps/chromium/blob/master/base/threading/thread_local_storage_win.cc#L42
 
-// -------------------------------------------------------------------------
-// Native bindings
-//
-// This section is just raw bindings to the native functions that Windows
-// provides, There's a few extra calls to deal with destructors.
+pub struct StaticKey {
+    /// The key value shifted up by one. Since TLS_OUT_OF_INDEXES == DWORD::MAX
+    /// is not a valid key value, this allows us to use zero as sentinel value
+    /// without risking overflow.
+    key: AtomicU32,
+    dtor: Option<Dtor>,
+    next: AtomicPtr<StaticKey>,
+    /// Currently, destructors cannot be unregistered, so we cannot use racy
+    /// initialization for keys. Instead, we need synchronize initialization.
+    /// Use the Windows-provided `Once` since it does not require TLS.
+    once: UnsafeCell<c::INIT_ONCE>,
+}
 
-#[inline]
-pub unsafe fn create(dtor: Option<Dtor>) -> Key {
-    let key = c::TlsAlloc();
-    assert!(key != c::TLS_OUT_OF_INDEXES);
-    if let Some(f) = dtor {
-        register_dtor(key, f);
+impl StaticKey {
+    #[inline]
+    pub const fn new(dtor: Option<Dtor>) -> StaticKey {
+        StaticKey {
+            key: AtomicU32::new(0),
+            dtor,
+            next: AtomicPtr::new(ptr::null_mut()),
+            once: UnsafeCell::new(c::INIT_ONCE_STATIC_INIT),
+        }
     }
-    key
-}
 
-#[inline]
-pub unsafe fn set(key: Key, value: *mut u8) {
-    let r = c::TlsSetValue(key, value as c::LPVOID);
-    debug_assert!(r != 0);
-}
+    #[inline]
+    pub unsafe fn set(&'static self, val: *mut u8) {
+        let r = c::TlsSetValue(self.key(), val.cast());
+        debug_assert_eq!(r, c::TRUE);
+    }
 
-#[inline]
-pub unsafe fn get(key: Key) -> *mut u8 {
-    c::TlsGetValue(key) as *mut u8
-}
+    #[inline]
+    pub unsafe fn get(&'static self) -> *mut u8 {
+        c::TlsGetValue(self.key()).cast()
+    }
 
-#[inline]
-pub unsafe fn destroy(_key: Key) {
-    rtabort!("can't destroy tls keys on windows")
-}
+    #[inline]
+    unsafe fn key(&'static self) -> Key {
+        match self.key.load(Acquire) {
+            0 => self.init(),
+            key => key - 1,
+        }
+    }
+
+    #[cold]
+    unsafe fn init(&'static self) -> Key {
+        if self.dtor.is_some() {
+            let mut pending = c::FALSE;
+            let r = c::InitOnceBeginInitialize(self.once.get(), 0, &mut pending, ptr::null_mut());
+            assert_eq!(r, c::TRUE);
 
-#[inline]
-pub fn requires_synchronized_create() -> bool {
-    true
+            if pending == c::FALSE {
+                // Some other thread initialized the key, load it.
+                self.key.load(Relaxed) - 1
+            } else {
+                let key = c::TlsAlloc();
+                if key == c::TLS_OUT_OF_INDEXES {
+                    // Wakeup the waiting threads before panicking to avoid deadlock.
+                    c::InitOnceComplete(self.once.get(), c::INIT_ONCE_INIT_FAILED, ptr::null_mut());
+                    panic!("out of TLS indexes");
+                }
+
+                self.key.store(key + 1, Release);
+                register_dtor(self);
+
+                let r = c::InitOnceComplete(self.once.get(), 0, ptr::null_mut());
+                debug_assert_eq!(r, c::TRUE);
+
+                key
+            }
+        } else {
+            // If there is no destructor to clean up, we can use racy initialization.
+
+            let key = c::TlsAlloc();
+            assert_ne!(key, c::TLS_OUT_OF_INDEXES, "out of TLS indexes");
+
+            match self.key.compare_exchange(0, key + 1, AcqRel, Acquire) {
+                Ok(_) => key,
+                Err(new) => {
+                    // Some other thread completed initialization first, so destroy
+                    // our key and use theirs.
+                    let r = c::TlsFree(key);
+                    debug_assert_eq!(r, c::TRUE);
+                    new - 1
+                }
+            }
+        }
+    }
 }
 
+unsafe impl Send for StaticKey {}
+unsafe impl Sync for StaticKey {}
+
 // -------------------------------------------------------------------------
 // Dtor registration
 //
@@ -96,29 +150,21 @@ pub fn requires_synchronized_create() -> bool {
 // Typically processes have a statically known set of TLS keys which is pretty
 // small, and we'd want to keep this memory alive for the whole process anyway
 // really.
-//
-// Perhaps one day we can fold the `Box` here into a static allocation,
-// expanding the `StaticKey` structure to contain not only a slot for the TLS
-// key but also a slot for the destructor queue on windows. An optimization for
-// another day!
-
-static DTORS: AtomicPtr<Node> = AtomicPtr::new(ptr::null_mut());
-
-struct Node {
-    dtor: Dtor,
-    key: Key,
-    next: *mut Node,
-}
 
-unsafe fn register_dtor(key: Key, dtor: Dtor) {
-    let mut node = ManuallyDrop::new(Box::new(Node { key, dtor, next: ptr::null_mut() }));
+static DTORS: AtomicPtr<StaticKey> = AtomicPtr::new(ptr::null_mut());
 
-    let mut head = DTORS.load(SeqCst);
+/// Should only be called once per key, otherwise loops or breaks may occur in
+/// the linked list.
+unsafe fn register_dtor(key: &'static StaticKey) {
+    let this = <*const StaticKey>::cast_mut(key);
+    // Use acquire ordering to pass along the changes done by the previously
+    // registered keys when we store the new head with release ordering.
+    let mut head = DTORS.load(Acquire);
     loop {
-        node.next = head;
-        match DTORS.compare_exchange(head, &mut **node, SeqCst, SeqCst) {
-            Ok(_) => return, // nothing to drop, we successfully added the node to the list
-            Err(cur) => head = cur,
+        key.next.store(head, Relaxed);
+        match DTORS.compare_exchange_weak(head, this, Release, Acquire) {
+            Ok(_) => break,
+            Err(new) => head = new,
         }
     }
 }
@@ -214,25 +260,29 @@ unsafe extern "system" fn on_tls_callback(h: c::LPVOID, dwReason: c::DWORD, pv:
     unsafe fn reference_tls_used() {}
 }
 
-#[allow(dead_code)] // actually called above
+#[allow(dead_code)] // actually called below
 unsafe fn run_dtors() {
-    let mut any_run = true;
     for _ in 0..5 {
-        if !any_run {
-            break;
-        }
-        any_run = false;
-        let mut cur = DTORS.load(SeqCst);
+        let mut any_run = false;
+
+        // Use acquire ordering to observe key initialization.
+        let mut cur = DTORS.load(Acquire);
         while !cur.is_null() {
-            let ptr = c::TlsGetValue((*cur).key);
+            let key = (*cur).key.load(Relaxed) - 1;
+            let dtor = (*cur).dtor.unwrap();
 
+            let ptr = c::TlsGetValue(key);
             if !ptr.is_null() {
-                c::TlsSetValue((*cur).key, ptr::null_mut());
-                ((*cur).dtor)(ptr as *mut _);
+                c::TlsSetValue(key, ptr::null_mut());
+                dtor(ptr as *mut _);
                 any_run = true;
             }
 
-            cur = (*cur).next;
+            cur = (*cur).next.load(Relaxed);
+        }
+
+        if !any_run {
+            break;
         }
     }
 }
diff --git a/library/std/src/sys/windows/thread_local_key/tests.rs b/library/std/src/sys/windows/thread_local_key/tests.rs
new file mode 100644
index 00000000000..c95f383fb90
--- /dev/null
+++ b/library/std/src/sys/windows/thread_local_key/tests.rs
@@ -0,0 +1,53 @@
+use super::StaticKey;
+use crate::ptr;
+
+#[test]
+fn smoke() {
+    static K1: StaticKey = StaticKey::new(None);
+    static K2: StaticKey = StaticKey::new(None);
+
+    unsafe {
+        assert!(K1.get().is_null());
+        assert!(K2.get().is_null());
+        K1.set(ptr::invalid_mut(1));
+        K2.set(ptr::invalid_mut(2));
+        assert_eq!(K1.get() as usize, 1);
+        assert_eq!(K2.get() as usize, 2);
+    }
+}
+
+#[test]
+fn destructors() {
+    use crate::mem::ManuallyDrop;
+    use crate::sync::Arc;
+    use crate::thread;
+
+    unsafe extern "C" fn destruct(ptr: *mut u8) {
+        drop(Arc::from_raw(ptr as *const ()));
+    }
+
+    static KEY: StaticKey = StaticKey::new(Some(destruct));
+
+    let shared1 = Arc::new(());
+    let shared2 = Arc::clone(&shared1);
+
+    unsafe {
+        assert!(KEY.get().is_null());
+        KEY.set(Arc::into_raw(shared1) as *mut u8);
+    }
+
+    thread::spawn(move || unsafe {
+        assert!(KEY.get().is_null());
+        KEY.set(Arc::into_raw(shared2) as *mut u8);
+    })
+    .join()
+    .unwrap();
+
+    // Leak the Arc, let the TLS destructor clean it up.
+    let shared1 = unsafe { ManuallyDrop::new(Arc::from_raw(KEY.get() as *const ())) };
+    assert_eq!(
+        Arc::strong_count(&shared1),
+        1,
+        "destructor should have dropped the other reference on thread exit"
+    );
+}
diff --git a/library/std/src/sys_common/mod.rs b/library/std/src/sys_common/mod.rs
index e4dd0253668..8c19f9332dc 100644
--- a/library/std/src/sys_common/mod.rs
+++ b/library/std/src/sys_common/mod.rs
@@ -34,11 +34,18 @@ pub mod rwlock;
 pub mod thread;
 pub mod thread_info;
 pub mod thread_local_dtor;
-pub mod thread_local_key;
 pub mod thread_parker;
 pub mod wtf8;
 
 cfg_if::cfg_if! {
+    if #[cfg(target_os = "windows")] {
+        pub use crate::sys::thread_local_key;
+    } else {
+        pub mod thread_local_key;
+    }
+}
+
+cfg_if::cfg_if! {
     if #[cfg(any(target_os = "l4re",
                  target_os = "hermit",
                  feature = "restricted-std",
diff --git a/library/std/src/sys_common/thread_local_key.rs b/library/std/src/sys_common/thread_local_key.rs
index 032bf604d73..747579f1781 100644
--- a/library/std/src/sys_common/thread_local_key.rs
+++ b/library/std/src/sys_common/thread_local_key.rs
@@ -53,7 +53,6 @@ mod tests;
 
 use crate::sync::atomic::{self, AtomicUsize, Ordering};
 use crate::sys::thread_local_key as imp;
-use crate::sys_common::mutex::StaticMutex;
 
 /// A type for TLS keys that are statically allocated.
 ///
@@ -151,25 +150,6 @@ impl StaticKey {
     }
 
     unsafe fn lazy_init(&self) -> usize {
-        // Currently the Windows implementation of TLS is pretty hairy, and
-        // it greatly simplifies creation if we just synchronize everything.
-        //
-        // Additionally a 0-index of a tls key hasn't been seen on windows, so
-        // we just simplify the whole branch.
-        if imp::requires_synchronized_create() {
-            // We never call `INIT_LOCK.init()`, so it is UB to attempt to
-            // acquire this mutex reentrantly!
-            static INIT_LOCK: StaticMutex = StaticMutex::new();
-            let _guard = INIT_LOCK.lock();
-            let mut key = self.key.load(Ordering::SeqCst);
-            if key == 0 {
-                key = imp::create(self.dtor) as usize;
-                self.key.store(key, Ordering::SeqCst);
-            }
-            rtassert!(key != 0);
-            return key;
-        }
-
         // POSIX allows the key created here to be 0, but the compare_exchange
         // below relies on using 0 as a sentinel value to check who won the
         // race to set the shared TLS key. As far as I know, there is no
@@ -232,8 +212,6 @@ impl Key {
 
 impl Drop for Key {
     fn drop(&mut self) {
-        // Right now Windows doesn't support TLS key destruction, but this also
-        // isn't used anywhere other than tests, so just leak the TLS key.
-        // unsafe { imp::destroy(self.key) }
+        unsafe { imp::destroy(self.key) }
     }
 }
diff --git a/src/ci/scripts/should-skip-this.sh b/src/ci/scripts/should-skip-this.sh
index 60c2960b160..a8a1899317f 100755
--- a/src/ci/scripts/should-skip-this.sh
+++ b/src/ci/scripts/should-skip-this.sh
@@ -19,7 +19,7 @@ if [[ -n "${CI_ONLY_WHEN_SUBMODULES_CHANGED-}" ]]; then
         # those files are present in the diff a submodule was updated.
         echo "Submodules were updated"
     elif ! (git diff --quiet "$BASE_COMMIT" -- \
-             src/tools/clippy src/tools/rustfmt src/tools/miri
+             src/tools/clippy src/tools/rustfmt src/tools/miri \
              library/std/src/sys); then
         # There is not an easy blanket search for subtrees. For now, manually list
         # the subtrees.
diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css
index 894c7328b5f..5958b389c9f 100644
--- a/src/librustdoc/html/static/css/rustdoc.css
+++ b/src/librustdoc/html/static/css/rustdoc.css
@@ -692,16 +692,13 @@ pre, .rustdoc.source .example-wrap {
 
 .item-info {
 	display: block;
+	margin-left: 24px;
 }
 
-.content .item-info code {
+.item-info code {
 	font-size: 0.875rem;
 }
 
-.content .item-info {
-	margin-left: 24px;
-}
-
 #main-content > .item-info {
 	margin-top: 0;
 	margin-left: 0;
@@ -1945,7 +1942,7 @@ in storage.js plus the media query with (min-width: 701px)
 	}
 
 	/* Align summary-nested and unnested item-info gizmos. */
-	.content .impl-items > .item-info {
+	.impl-items > .item-info {
 		margin-left: 34px;
 	}
 }
diff --git a/src/test/ui/async-await/issue-64130-1-sync.rs b/src/test/ui/async-await/issue-64130-1-sync.rs
index af83f14bbda..1714cec5221 100644
--- a/src/test/ui/async-await/issue-64130-1-sync.rs
+++ b/src/test/ui/async-await/issue-64130-1-sync.rs
@@ -1,7 +1,7 @@
 #![feature(negative_impls)]
 // edition:2018
 
-// This tests the the specialized async-await-specific error when futures don't implement an
+// This tests the specialized async-await-specific error when futures don't implement an
 // auto trait (which is specifically Sync) due to some type that was captured.
 
 struct Foo;
diff --git a/src/test/ui/async-await/issue-64130-2-send.rs b/src/test/ui/async-await/issue-64130-2-send.rs
index 2362831d8b8..7a6e5952cb9 100644
--- a/src/test/ui/async-await/issue-64130-2-send.rs
+++ b/src/test/ui/async-await/issue-64130-2-send.rs
@@ -1,7 +1,7 @@
 #![feature(negative_impls)]
 // edition:2018
 
-// This tests the the specialized async-await-specific error when futures don't implement an
+// This tests the specialized async-await-specific error when futures don't implement an
 // auto trait (which is specifically Send) due to some type that was captured.
 
 struct Foo;
diff --git a/src/test/ui/async-await/issue-64130-3-other.rs b/src/test/ui/async-await/issue-64130-3-other.rs
index 52801c35ba3..630fb2c41cd 100644
--- a/src/test/ui/async-await/issue-64130-3-other.rs
+++ b/src/test/ui/async-await/issue-64130-3-other.rs
@@ -2,7 +2,7 @@
 #![feature(negative_impls)]
 // edition:2018
 
-// This tests the the unspecialized async-await-specific error when futures don't implement an
+// This tests the unspecialized async-await-specific error when futures don't implement an
 // auto trait (which is not Send or Sync) due to some type that was captured.
 
 auto trait Qux {}
diff --git a/src/test/ui/const-generics/occurs-check/unused-substs-2.rs b/src/test/ui/const-generics/occurs-check/unused-substs-2.rs
index 9a73f1a53e5..9b1212694f5 100644
--- a/src/test/ui/const-generics/occurs-check/unused-substs-2.rs
+++ b/src/test/ui/const-generics/occurs-check/unused-substs-2.rs
@@ -1,7 +1,7 @@
 #![feature(generic_const_exprs)]
 #![allow(incomplete_features)]
 
-// The goal is is to get an unevaluated const `ct` with a `Ty::Infer(TyVar(_#1t)` subst.
+// The goal is to get an unevaluated const `ct` with a `Ty::Infer(TyVar(_#1t)` subst.
 //
 // If we are then able to infer `ty::Infer(TyVar(_#1t) := Ty<ct>` we introduced an
 // artificial inference cycle.
diff --git a/src/test/ui/const-generics/occurs-check/unused-substs-3.rs b/src/test/ui/const-generics/occurs-check/unused-substs-3.rs
index 0d38bd39351..d5aeab47e62 100644
--- a/src/test/ui/const-generics/occurs-check/unused-substs-3.rs
+++ b/src/test/ui/const-generics/occurs-check/unused-substs-3.rs
@@ -1,7 +1,7 @@
 #![feature(generic_const_exprs)]
 #![allow(incomplete_features)]
 
-// The goal is is to get an unevaluated const `ct` with a `Ty::Infer(TyVar(_#1t)` subst.
+// The goal is to get an unevaluated const `ct` with a `Ty::Infer(TyVar(_#1t)` subst.
 //
 // If we are then able to infer `ty::Infer(TyVar(_#1t) := Ty<ct>` we introduced an
 // artificial inference cycle.
diff --git a/src/test/ui/deprecation/deprecation-lint.rs b/src/test/ui/deprecation/deprecation-lint.rs
index 65cc4e2ef1e..0417e952eb7 100644
--- a/src/test/ui/deprecation/deprecation-lint.rs
+++ b/src/test/ui/deprecation/deprecation-lint.rs
@@ -51,7 +51,7 @@ mod cross_crate {
 
         let _ = nested::DeprecatedTupleStruct (1); //~ ERROR use of deprecated tuple struct `deprecation_lint::nested::DeprecatedTupleStruct`: text
 
-        // At the moment, the lint checker only checks stability in
+        // At the moment, the lint checker only checks stability
         // in the arguments of macros.
         // Eventually, we will want to lint the contents of the
         // macro in the module *defining* it. Also, stability levels
diff --git a/src/test/ui/dyn-star/box.rs b/src/test/ui/dyn-star/box.rs
new file mode 100644
index 00000000000..d1f1819d9f3
--- /dev/null
+++ b/src/test/ui/dyn-star/box.rs
@@ -0,0 +1,17 @@
+// run-pass
+// compile-flags: -C opt-level=0
+
+#![feature(dyn_star)]
+#![allow(incomplete_features)]
+
+use std::fmt::Display;
+
+fn make_dyn_star() -> dyn* Display {
+    Box::new(42) as dyn* Display
+}
+
+fn main() {
+    let x = make_dyn_star();
+
+    println!("{x}");
+}
diff --git a/src/test/ui/explain.stdout b/src/test/ui/explain.stdout
index 62f1a7f98ea..ef1d866c3ff 100644
--- a/src/test/ui/explain.stdout
+++ b/src/test/ui/explain.stdout
@@ -47,8 +47,8 @@ unsafe {
 ```
 
 Here, transmute is being used to convert the types of the fn arguments.
-This pattern is incorrect because, because the type of `foo` is a function
-**item** (`typeof(foo)`), which is zero-sized, and the target type (`fn()`)
+This pattern is incorrect because the type of `foo` is a function **item**
+(`typeof(foo)`), which is zero-sized, and the target type (`fn()`)
 is a function pointer, which is not zero-sized.
 This pattern should be rewritten. There are a few possible ways to do this:
 
diff --git a/src/test/ui/issues/issue-102964.rs b/src/test/ui/issues/issue-102964.rs
new file mode 100644
index 00000000000..43ff2360076
--- /dev/null
+++ b/src/test/ui/issues/issue-102964.rs
@@ -0,0 +1,10 @@
+use std::rc::Rc;
+type Foo<'a, T> = &'a dyn Fn(&T);
+type RcFoo<'a, T> = Rc<Foo<'a, T>>;
+
+fn bar_function<T>(function: Foo<T>) -> RcFoo<T> {
+    //~^ ERROR mismatched types
+    let rc = Rc::new(function);
+}
+
+fn main() {}
diff --git a/src/test/ui/issues/issue-102964.stderr b/src/test/ui/issues/issue-102964.stderr
new file mode 100644
index 00000000000..4504039097b
--- /dev/null
+++ b/src/test/ui/issues/issue-102964.stderr
@@ -0,0 +1,19 @@
+error[E0308]: mismatched types
+  --> $DIR/issue-102964.rs:5:41
+   |
+LL | fn bar_function<T>(function: Foo<T>) -> RcFoo<T> {
+   |    ------------                         ^^^^^^^^ expected struct `Rc`, found `()`
+   |    |
+   |    implicitly returns `()` as its body has no tail or `return` expression
+   |
+   = note: expected struct `Rc<&dyn for<'a> Fn(&'a T)>`
+           found unit type `()`
+help: consider returning the local binding `rc`
+   |
+LL ~     let rc = Rc::new(function);
+LL +     rc
+   |
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0308`.
diff --git a/src/test/ui/issues/issue-29746.rs b/src/test/ui/issues/issue-29746.rs
index 428cc637f55..3470a7e09ad 100644
--- a/src/test/ui/issues/issue-29746.rs
+++ b/src/test/ui/issues/issue-29746.rs
@@ -7,7 +7,7 @@ macro_rules! zip {
         zip!([$($rest),*], $a.zip($b), (x,y), [x,y])
     };
 
-    // Intermediate steps to build the zipped expression, the match pattern, and
+    // Intermediate steps to build the zipped expression, the match pattern
     //  and the output tuple of the closure, using macro hygiene to repeatedly
     //  introduce new variables named 'x'.
     ([$a:expr, $($rest:expr),*], $zip:expr, $pat:pat, [$($flat:expr),*]) => {
diff --git a/src/test/ui/issues/issue-75907.rs b/src/test/ui/issues/issue-75907.rs
index 1534b6d07de..6da99cf6435 100644
--- a/src/test/ui/issues/issue-75907.rs
+++ b/src/test/ui/issues/issue-75907.rs
@@ -1,4 +1,4 @@
-// Test for for diagnostic improvement issue #75907
+// Test for diagnostic improvement issue #75907
 
 mod foo {
     pub(crate) struct Foo(u8);
diff --git a/src/test/ui/issues/issue-75907_b.rs b/src/test/ui/issues/issue-75907_b.rs
index e3074778233..fdfc5907c16 100644
--- a/src/test/ui/issues/issue-75907_b.rs
+++ b/src/test/ui/issues/issue-75907_b.rs
@@ -1,4 +1,4 @@
-// Test for for diagnostic improvement issue #75907, extern crate
+// Test for diagnostic improvement issue #75907, extern crate
 // aux-build:issue-75907.rs
 
 extern crate issue_75907 as a;
diff --git a/src/test/ui/lint/lint-stability-deprecated.rs b/src/test/ui/lint/lint-stability-deprecated.rs
index bdc66e83083..74c35083e60 100644
--- a/src/test/ui/lint/lint-stability-deprecated.rs
+++ b/src/test/ui/lint/lint-stability-deprecated.rs
@@ -130,7 +130,7 @@ mod cross_crate {
         let _ = UnstableTupleStruct (1);
         let _ = StableTupleStruct (1);
 
-        // At the moment, the lint checker only checks stability in
+        // At the moment, the lint checker only checks stability
         // in the arguments of macros.
         // Eventually, we will want to lint the contents of the
         // macro in the module *defining* it. Also, stability levels
diff --git a/src/test/ui/proc-macro/meta-macro-hygiene.rs b/src/test/ui/proc-macro/meta-macro-hygiene.rs
index 62968ea54e0..70b8d8da19b 100644
--- a/src/test/ui/proc-macro/meta-macro-hygiene.rs
+++ b/src/test/ui/proc-macro/meta-macro-hygiene.rs
@@ -19,8 +19,8 @@ macro_rules! produce_it {
         // `print_def_site!` will respan the `$crate` identifier
         // with `Span::def_site()`. This should cause it to resolve
         // relative to `meta_macro`, *not* `make_macro` (despite
-        // the fact that that `print_def_site` is produced by
-        // a `macro_rules!` macro in `make_macro`).
+        // the fact that `print_def_site` is produced by a
+        // `macro_rules!` macro in `make_macro`).
         meta_macro::print_def_site!($crate::dummy!());
     }
 }
diff --git a/src/test/ui/proc-macro/meta-macro-hygiene.stdout b/src/test/ui/proc-macro/meta-macro-hygiene.stdout
index 2494af1208f..6b7b0c819cc 100644
--- a/src/test/ui/proc-macro/meta-macro-hygiene.stdout
+++ b/src/test/ui/proc-macro/meta-macro-hygiene.stdout
@@ -35,8 +35,8 @@ macro_rules! produce_it
         // `print_def_site!` will respan the `$crate` identifier
         // with `Span::def_site()`. This should cause it to resolve
         // relative to `meta_macro`, *not* `make_macro` (despite
-        // the fact that that `print_def_site` is produced by
-        // a `macro_rules!` macro in `make_macro`).
+        // the fact that `print_def_site` is produced by a
+        // `macro_rules!` macro in `make_macro`).
     }
 }
 
diff --git a/src/test/ui/type/type-check/assignment-in-if.rs b/src/test/ui/type/type-check/assignment-in-if.rs
index 3a7845096fd..ada250df246 100644
--- a/src/test/ui/type/type-check/assignment-in-if.rs
+++ b/src/test/ui/type/type-check/assignment-in-if.rs
@@ -53,4 +53,10 @@ fn main() {
         //~| ERROR mismatched types
         println!("{}", x);
     }
+
+    if x = 1 && x == 1 {
+        //~^ ERROR mismatched types
+        //~| ERROR mismatched types
+        println!("{}", x);
+    }
 }
diff --git a/src/test/ui/type/type-check/assignment-in-if.stderr b/src/test/ui/type/type-check/assignment-in-if.stderr
index 166f2293777..8ab08e25e30 100644
--- a/src/test/ui/type/type-check/assignment-in-if.stderr
+++ b/src/test/ui/type/type-check/assignment-in-if.stderr
@@ -104,6 +104,23 @@ help: you might have meant to compare for equality
 LL |     if x == x && x == x && x == x {
    |                               +
 
-error: aborting due to 11 previous errors
+error[E0308]: mismatched types
+  --> $DIR/assignment-in-if.rs:57:12
+   |
+LL |     if x = 1 && x == 1 {
+   |            ^ expected `bool`, found integer
+
+error[E0308]: mismatched types
+  --> $DIR/assignment-in-if.rs:57:8
+   |
+LL |     if x = 1 && x == 1 {
+   |        ^^^^^^^^^^^^^^^ expected `bool`, found `()`
+   |
+help: you might have meant to compare for equality
+   |
+LL |     if x == 1 && x == 1 {
+   |           +
+
+error: aborting due to 13 previous errors
 
 For more information about this error, try `rustc --explain E0308`.
diff --git a/src/tools/tidy/src/features.rs b/src/tools/tidy/src/features.rs
index d8b3903b98e..f10ecf5f201 100644
--- a/src/tools/tidy/src/features.rs
+++ b/src/tools/tidy/src/features.rs
@@ -10,7 +10,7 @@
 //! * Language features in a group are sorted by feature name.
 
 use crate::walk::{filter_dirs, walk, walk_many};
-use std::collections::HashMap;
+use std::collections::hash_map::{Entry, HashMap};
 use std::fmt;
 use std::fs;
 use std::num::NonZeroU32;
@@ -280,13 +280,14 @@ fn test_filen_gate(filen_underscore: &str, features: &mut Features) -> bool {
 }
 
 pub fn collect_lang_features(base_compiler_path: &Path, bad: &mut bool) -> Features {
-    let mut all = collect_lang_features_in(base_compiler_path, "active.rs", bad);
-    all.extend(collect_lang_features_in(base_compiler_path, "accepted.rs", bad));
-    all.extend(collect_lang_features_in(base_compiler_path, "removed.rs", bad));
-    all
+    let mut features = Features::new();
+    collect_lang_features_in(&mut features, base_compiler_path, "active.rs", bad);
+    collect_lang_features_in(&mut features, base_compiler_path, "accepted.rs", bad);
+    collect_lang_features_in(&mut features, base_compiler_path, "removed.rs", bad);
+    features
 }
 
-fn collect_lang_features_in(base: &Path, file: &str, bad: &mut bool) -> Features {
+fn collect_lang_features_in(features: &mut Features, base: &Path, file: &str, bad: &mut bool) {
     let path = base.join("rustc_feature").join("src").join(file);
     let contents = t!(fs::read_to_string(&path));
 
@@ -298,135 +299,145 @@ fn collect_lang_features_in(base: &Path, file: &str, bad: &mut bool) -> Features
     let mut in_feature_group = false;
     let mut prev_names = vec![];
 
-    contents
-        .lines()
-        .zip(1..)
-        .filter_map(|(line, line_number)| {
-            let line = line.trim();
-
-            // Within -start and -end, the tracking issue can be omitted.
-            match line {
-                "// no-tracking-issue-start" => {
-                    next_feature_omits_tracking_issue = true;
-                    return None;
-                }
-                "// no-tracking-issue-end" => {
-                    next_feature_omits_tracking_issue = false;
-                    return None;
-                }
-                _ => {}
+    let lines = contents.lines().zip(1..);
+    for (line, line_number) in lines {
+        let line = line.trim();
+
+        // Within -start and -end, the tracking issue can be omitted.
+        match line {
+            "// no-tracking-issue-start" => {
+                next_feature_omits_tracking_issue = true;
+                continue;
             }
+            "// no-tracking-issue-end" => {
+                next_feature_omits_tracking_issue = false;
+                continue;
+            }
+            _ => {}
+        }
 
-            if line.starts_with(FEATURE_GROUP_START_PREFIX) {
-                if in_feature_group {
-                    tidy_error!(
-                        bad,
-                        "{}:{}: \
+        if line.starts_with(FEATURE_GROUP_START_PREFIX) {
+            if in_feature_group {
+                tidy_error!(
+                    bad,
+                    "{}:{}: \
                         new feature group is started without ending the previous one",
-                        path.display(),
-                        line_number,
-                    );
-                }
-
-                in_feature_group = true;
-                prev_names = vec![];
-                return None;
-            } else if line.starts_with(FEATURE_GROUP_END_PREFIX) {
-                in_feature_group = false;
-                prev_names = vec![];
-                return None;
+                    path.display(),
+                    line_number,
+                );
             }
 
-            let mut parts = line.split(',');
-            let level = match parts.next().map(|l| l.trim().trim_start_matches('(')) {
-                Some("active") => Status::Unstable,
-                Some("incomplete") => Status::Unstable,
-                Some("removed") => Status::Removed,
-                Some("accepted") => Status::Stable,
-                _ => return None,
-            };
-            let name = parts.next().unwrap().trim();
-
-            let since_str = parts.next().unwrap().trim().trim_matches('"');
-            let since = match since_str.parse() {
-                Ok(since) => Some(since),
-                Err(err) => {
-                    tidy_error!(
-                        bad,
-                        "{}:{}: failed to parse since: {} ({:?})",
-                        path.display(),
-                        line_number,
-                        since_str,
-                        err,
-                    );
-                    None
-                }
-            };
-            if in_feature_group {
-                if prev_names.last() > Some(&name) {
-                    // This assumes the user adds the feature name at the end of the list, as we're
-                    // not looking ahead.
-                    let correct_index = match prev_names.binary_search(&name) {
-                        Ok(_) => {
-                            // This only occurs when the feature name has already been declared.
-                            tidy_error!(
-                                bad,
-                                "{}:{}: duplicate feature {}",
-                                path.display(),
-                                line_number,
-                                name,
-                            );
-                            // skip any additional checks for this line
-                            return None;
-                        }
-                        Err(index) => index,
-                    };
+            in_feature_group = true;
+            prev_names = vec![];
+            continue;
+        } else if line.starts_with(FEATURE_GROUP_END_PREFIX) {
+            in_feature_group = false;
+            prev_names = vec![];
+            continue;
+        }
 
-                    let correct_placement = if correct_index == 0 {
-                        "at the beginning of the feature group".to_owned()
-                    } else if correct_index == prev_names.len() {
-                        // I don't believe this is reachable given the above assumption, but it
-                        // doesn't hurt to be safe.
-                        "at the end of the feature group".to_owned()
-                    } else {
-                        format!(
-                            "between {} and {}",
-                            prev_names[correct_index - 1],
-                            prev_names[correct_index],
-                        )
-                    };
+        let mut parts = line.split(',');
+        let level = match parts.next().map(|l| l.trim().trim_start_matches('(')) {
+            Some("active") => Status::Unstable,
+            Some("incomplete") => Status::Unstable,
+            Some("removed") => Status::Removed,
+            Some("accepted") => Status::Stable,
+            _ => continue,
+        };
+        let name = parts.next().unwrap().trim();
+
+        let since_str = parts.next().unwrap().trim().trim_matches('"');
+        let since = match since_str.parse() {
+            Ok(since) => Some(since),
+            Err(err) => {
+                tidy_error!(
+                    bad,
+                    "{}:{}: failed to parse since: {} ({:?})",
+                    path.display(),
+                    line_number,
+                    since_str,
+                    err,
+                );
+                None
+            }
+        };
+        if in_feature_group {
+            if prev_names.last() > Some(&name) {
+                // This assumes the user adds the feature name at the end of the list, as we're
+                // not looking ahead.
+                let correct_index = match prev_names.binary_search(&name) {
+                    Ok(_) => {
+                        // This only occurs when the feature name has already been declared.
+                        tidy_error!(
+                            bad,
+                            "{}:{}: duplicate feature {}",
+                            path.display(),
+                            line_number,
+                            name,
+                        );
+                        // skip any additional checks for this line
+                        continue;
+                    }
+                    Err(index) => index,
+                };
 
-                    tidy_error!(
-                        bad,
-                        "{}:{}: feature {} is not sorted by feature name (should be {})",
-                        path.display(),
-                        line_number,
-                        name,
-                        correct_placement,
-                    );
-                }
-                prev_names.push(name);
+                let correct_placement = if correct_index == 0 {
+                    "at the beginning of the feature group".to_owned()
+                } else if correct_index == prev_names.len() {
+                    // I don't believe this is reachable given the above assumption, but it
+                    // doesn't hurt to be safe.
+                    "at the end of the feature group".to_owned()
+                } else {
+                    format!(
+                        "between {} and {}",
+                        prev_names[correct_index - 1],
+                        prev_names[correct_index],
+                    )
+                };
+
+                tidy_error!(
+                    bad,
+                    "{}:{}: feature {} is not sorted by feature name (should be {})",
+                    path.display(),
+                    line_number,
+                    name,
+                    correct_placement,
+                );
             }
+            prev_names.push(name);
+        }
 
-            let issue_str = parts.next().unwrap().trim();
-            let tracking_issue = if issue_str.starts_with("None") {
-                if level == Status::Unstable && !next_feature_omits_tracking_issue {
-                    tidy_error!(
-                        bad,
-                        "{}:{}: no tracking issue for feature {}",
-                        path.display(),
-                        line_number,
-                        name,
-                    );
-                }
-                None
-            } else {
-                let s = issue_str.split('(').nth(1).unwrap().split(')').next().unwrap();
-                Some(s.parse().unwrap())
-            };
-            Some((name.to_owned(), Feature { level, since, has_gate_test: false, tracking_issue }))
-        })
-        .collect()
+        let issue_str = parts.next().unwrap().trim();
+        let tracking_issue = if issue_str.starts_with("None") {
+            if level == Status::Unstable && !next_feature_omits_tracking_issue {
+                tidy_error!(
+                    bad,
+                    "{}:{}: no tracking issue for feature {}",
+                    path.display(),
+                    line_number,
+                    name,
+                );
+            }
+            None
+        } else {
+            let s = issue_str.split('(').nth(1).unwrap().split(')').next().unwrap();
+            Some(s.parse().unwrap())
+        };
+        match features.entry(name.to_owned()) {
+            Entry::Occupied(e) => {
+                tidy_error!(
+                    bad,
+                    "{}:{} feature {name} already specified with status '{}'",
+                    path.display(),
+                    line_number,
+                    e.get().level,
+                );
+            }
+            Entry::Vacant(e) => {
+                e.insert(Feature { level, since, has_gate_test: false, tracking_issue });
+            }
+        }
+    }
 }
 
 fn get_and_check_lib_features(