upstream stdx changes
diff --git a/Cargo.lock b/Cargo.lock
index b6a32d5..0d509f5 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1083,9 +1083,9 @@
 
 [[package]]
 name = "jod-thread"
-version = "0.1.2"
+version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b23360e99b8717f20aaa4598f5a6541efbe30630039fbc7706cf954a87947ae"
+checksum = "a037eddb7d28de1d0fc42411f501b53b75838d313908078d6698d064f3029b24"
 
 [[package]]
 name = "kqueue"
diff --git a/crates/stdx/Cargo.toml b/crates/stdx/Cargo.toml
index 7603330..7bda106 100644
--- a/crates/stdx/Cargo.toml
+++ b/crates/stdx/Cargo.toml
@@ -13,7 +13,7 @@
 
 [dependencies]
 backtrace = { version = "0.3.74", optional = true }
-jod-thread = "0.1.2"
+jod-thread = "1.0.0"
 crossbeam-channel.workspace = true
 itertools.workspace = true
 tracing.workspace = true
diff --git a/crates/stdx/src/anymap.rs b/crates/stdx/src/anymap.rs
index 0c39419..f55698e 100644
--- a/crates/stdx/src/anymap.rs
+++ b/crates/stdx/src/anymap.rs
@@ -1,4 +1,5 @@
 //! This file is a port of only the necessary features from <https://github.com/chris-morgan/anymap> version 1.0.0-beta.2 for use within rust-analyzer.
+//!
 //! Copyright © 2014–2022 Chris Morgan.
 //! COPYING: <https://github.com/chris-morgan/anymap/blob/master/COPYING>
 //! Note that the license is changed from Blue Oak Model 1.0.0 or MIT or Apache-2.0 to MIT OR Apache-2.0
@@ -20,14 +21,14 @@
 
 use core::hash::Hasher;
 
-/// A hasher designed to eke a little more speed out, given `TypeId`’s known characteristics.
+/// A hasher designed to eke a little more speed out, given `TypeId`'s known characteristics.
 ///
-/// Specifically, this is a no-op hasher that expects to be fed a u64’s worth of
+/// Specifically, this is a no-op hasher that expects to be fed a u64's worth of
 /// randomly-distributed bits. It works well for `TypeId` (eliminating start-up time, so that my
-/// get_missing benchmark is ~30ns rather than ~900ns, and being a good deal faster after that, so
-/// that my insert_and_get_on_260_types benchmark is ~12μs instead of ~21.5μs), but will
+/// `get_missing` benchmark is ~30ns rather than ~900ns, and being a good deal faster after that, so
+/// that my `insert_and_get_on_260_types` benchmark is ~12μs instead of ~21.5μs), but will
 /// panic in debug mode and always emit zeros in release mode for any other sorts of inputs, so
-/// yeah, don’t use it! 😀
+/// yeah, don't use it! 😀
 #[derive(Default)]
 pub struct TypeIdHasher {
     value: u64,
@@ -36,9 +37,9 @@
 impl Hasher for TypeIdHasher {
     #[inline]
     fn write(&mut self, bytes: &[u8]) {
-        // This expects to receive exactly one 64-bit value, and there’s no realistic chance of
-        // that changing, but I don’t want to depend on something that isn’t expressly part of the
-        // contract for safety. But I’m OK with release builds putting everything in one bucket
+        // This expects to receive exactly one 64-bit value, and there's no realistic chance of
+        // that changing, but I don't want to depend on something that isn't expressly part of the
+        // contract for safety. But I'm OK with release builds putting everything in one bucket
         // if it *did* change (and debug builds panicking).
         debug_assert_eq!(bytes.len(), 8);
         let _ = bytes.try_into().map(|array| self.value = u64::from_ne_bytes(array));
@@ -59,7 +60,7 @@
 /// Raw access to the underlying `HashMap`.
 ///
 /// This alias is provided for convenience because of the ugly third generic parameter.
-#[allow(clippy::disallowed_types)] // Uses a custom hasher
+#[expect(clippy::disallowed_types, reason = "Uses a custom hasher")]
 pub type RawMap<A> = hash_map::HashMap<TypeId, Box<A>, BuildHasherDefault<TypeIdHasher>>;
 
 /// A collection containing zero or one values for any given type and allowing convenient,
@@ -73,19 +74,20 @@
 ///
 /// Cumulatively, there are thus six forms of map:
 ///
-/// - <code>[Map]&lt;dyn [core::any::Any]&gt;</code>,
+/// - `[Map]<dyn [core::any::Any]>`,
 ///   also spelled [`AnyMap`] for convenience.
-/// - <code>[Map]&lt;dyn [core::any::Any] + Send&gt;</code>
-/// - <code>[Map]&lt;dyn [core::any::Any] + Send + Sync&gt;</code>
+/// - `[Map]<dyn [core::any::Any] + Send>`
+/// - `[Map]<dyn [core::any::Any] + Send + Sync>`
 ///
 /// ## Example
 ///
-/// (Here using the [`AnyMap`] convenience alias; the first line could use
-/// <code>[anymap::Map][Map]::&lt;[core::any::Any]&gt;::new()</code> instead if desired.)
+/// (Here, the [`AnyMap`] convenience alias is used;
+/// the first line could use `[anymap::Map][Map]::<[core::any::Any]>::new()`
+/// instead if desired.)
 ///
 /// ```
 /// # use stdx::anymap;
-#[doc = "let mut data = anymap::AnyMap::new();"]
+/// let mut data = anymap::AnyMap::new();
 /// assert_eq!(data.get(), None::<&i32>);
 /// ```
 ///
@@ -95,11 +97,11 @@
     raw: RawMap<A>,
 }
 
-/// The most common type of `Map`: just using `Any`; <code>[Map]&lt;dyn [Any]&gt;</code>.
+/// The most common type of `Map`: just using `Any`; `[Map]<dyn [Any]>`.
 ///
 /// Why is this a separate type alias rather than a default value for `Map<A>`?
-/// `Map::new()` doesn’t seem to be happy to infer that it should go with the default
-/// value. It’s a bit sad, really. Ah well, I guess this approach will do.
+/// `Map::new()` doesn't seem to be happy to infer that it should go with the default
+/// value. It's a bit sad, really. Ah well, I guess this approach will do.
 pub type AnyMap = Map<dyn Any>;
 
 impl<A: ?Sized + Downcast> Default for Map<A> {
@@ -113,6 +115,7 @@
     /// Returns a reference to the value stored in the collection for the type `T`,
     /// if it exists.
     #[inline]
+    #[must_use]
     pub fn get<T: IntoBox<A>>(&self) -> Option<&T> {
         self.raw.get(&TypeId::of::<T>()).map(|any| unsafe { any.downcast_ref_unchecked::<T>() })
     }
@@ -132,30 +135,30 @@
 }
 
 /// A view into a single occupied location in an `Map`.
-pub struct OccupiedEntry<'a, A: ?Sized + Downcast, V: 'a> {
-    inner: hash_map::OccupiedEntry<'a, TypeId, Box<A>>,
+pub struct OccupiedEntry<'map, A: ?Sized + Downcast, V: 'map> {
+    inner: hash_map::OccupiedEntry<'map, TypeId, Box<A>>,
     type_: PhantomData<V>,
 }
 
 /// A view into a single empty location in an `Map`.
-pub struct VacantEntry<'a, A: ?Sized + Downcast, V: 'a> {
-    inner: hash_map::VacantEntry<'a, TypeId, Box<A>>,
+pub struct VacantEntry<'map, A: ?Sized + Downcast, V: 'map> {
+    inner: hash_map::VacantEntry<'map, TypeId, Box<A>>,
     type_: PhantomData<V>,
 }
 
 /// A view into a single location in an `Map`, which may be vacant or occupied.
-pub enum Entry<'a, A: ?Sized + Downcast, V> {
+pub enum Entry<'map, A: ?Sized + Downcast, V> {
     /// An occupied Entry
-    Occupied(OccupiedEntry<'a, A, V>),
+    Occupied(OccupiedEntry<'map, A, V>),
     /// A vacant Entry
-    Vacant(VacantEntry<'a, A, V>),
+    Vacant(VacantEntry<'map, A, V>),
 }
 
-impl<'a, A: ?Sized + Downcast, V: IntoBox<A>> Entry<'a, A, V> {
+impl<'map, A: ?Sized + Downcast, V: IntoBox<A>> Entry<'map, A, V> {
     /// Ensures a value is in the entry by inserting the result of the default function if
     /// empty, and returns a mutable reference to the value in the entry.
     #[inline]
-    pub fn or_insert_with<F: FnOnce() -> V>(self, default: F) -> &'a mut V {
+    pub fn or_insert_with<F: FnOnce() -> V>(self, default: F) -> &'map mut V {
         match self {
             Entry::Occupied(inner) => inner.into_mut(),
             Entry::Vacant(inner) => inner.insert(default()),
@@ -163,20 +166,21 @@
     }
 }
 
-impl<'a, A: ?Sized + Downcast, V: IntoBox<A>> OccupiedEntry<'a, A, V> {
-    /// Converts the OccupiedEntry into a mutable reference to the value in the entry
+impl<'map, A: ?Sized + Downcast, V: IntoBox<A>> OccupiedEntry<'map, A, V> {
+    /// Converts the `OccupiedEntry` into a mutable reference to the value in the entry
     /// with a lifetime bound to the collection itself
     #[inline]
-    pub fn into_mut(self) -> &'a mut V {
+    #[must_use]
+    pub fn into_mut(self) -> &'map mut V {
         unsafe { self.inner.into_mut().downcast_mut_unchecked() }
     }
 }
 
-impl<'a, A: ?Sized + Downcast, V: IntoBox<A>> VacantEntry<'a, A, V> {
-    /// Sets the value of the entry with the VacantEntry's key,
+impl<'map, A: ?Sized + Downcast, V: IntoBox<A>> VacantEntry<'map, A, V> {
+    /// Sets the value of the entry with the `VacantEntry`'s key,
     /// and returns a mutable reference to it
     #[inline]
-    pub fn insert(self, value: V) -> &'a mut V {
+    pub fn insert(self, value: V) -> &'map mut V {
         unsafe { self.inner.insert(value.into_box()).downcast_mut_unchecked() }
     }
 }
@@ -201,14 +205,13 @@
     #[test]
     fn type_id_hasher() {
         use core::any::TypeId;
-        use core::hash::Hash;
+        use core::hash::Hash as _;
         fn verify_hashing_with(type_id: TypeId) {
             let mut hasher = TypeIdHasher::default();
             type_id.hash(&mut hasher);
-            // SAFETY: u64 is valid for all bit patterns.
-            let _ = hasher.finish();
+            _ = hasher.finish();
         }
-        // Pick a variety of types, just to demonstrate it’s all sane. Normal, zero-sized, unsized, &c.
+        // Pick a variety of types, just to demonstrate it's all sane. Normal, zero-sized, unsized, &c.
         verify_hashing_with(TypeId::of::<usize>());
         verify_hashing_with(TypeId::of::<()>());
         verify_hashing_with(TypeId::of::<str>());
@@ -220,34 +223,34 @@
 /// Methods for downcasting from an `Any`-like trait object.
 ///
 /// This should only be implemented on trait objects for subtraits of `Any`, though you can
-/// implement it for other types and it’ll work fine, so long as your implementation is correct.
+/// implement it for other types and it'll work fine, so long as your implementation is correct.
 pub trait Downcast {
     /// Gets the `TypeId` of `self`.
     fn type_id(&self) -> TypeId;
 
     // Note the bound through these downcast methods is 'static, rather than the inexpressible
     // concept of Self-but-as-a-trait (where Self is `dyn Trait`). This is sufficient, exceeding
-    // TypeId’s requirements. Sure, you *can* do CloneAny.downcast_unchecked::<NotClone>() and the
-    // type system won’t protect you, but that doesn’t introduce any unsafety: the method is
+    // TypeId's requirements. Sure, you *can* do CloneAny.downcast_unchecked::<NotClone>() and the
+    // type system won't protect you, but that doesn't introduce any unsafety: the method is
     // already unsafe because you can specify the wrong type, and if this were exposing safe
     // downcasting, CloneAny.downcast::<NotClone>() would just return an error, which is just as
     // correct.
     //
-    // Now in theory we could also add T: ?Sized, but that doesn’t play nicely with the common
-    // implementation, so I’m doing without it.
+    // Now in theory we could also add T: ?Sized, but that doesn't play nicely with the common
+    // implementation, so I'm doing without it.
 
     /// Downcast from `&Any` to `&T`, without checking the type matches.
     ///
     /// # Safety
     ///
-    /// The caller must ensure that `T` matches the trait object, on pain of *undefined behaviour*.
+    /// The caller must ensure that `T` matches the trait object, on pain of *undefined behavior*.
     unsafe fn downcast_ref_unchecked<T: 'static>(&self) -> &T;
 
     /// Downcast from `&mut Any` to `&mut T`, without checking the type matches.
     ///
     /// # Safety
     ///
-    /// The caller must ensure that `T` matches the trait object, on pain of *undefined behaviour*.
+    /// The caller must ensure that `T` matches the trait object, on pain of *undefined behavior*.
     unsafe fn downcast_mut_unchecked<T: 'static>(&mut self) -> &mut T;
 }
 
@@ -267,12 +270,12 @@
 
             #[inline]
             unsafe fn downcast_ref_unchecked<T: 'static>(&self) -> &T {
-                unsafe { &*(self as *const Self as *const T) }
+                unsafe { &*std::ptr::from_ref::<Self>(self).cast::<T>() }
             }
 
             #[inline]
             unsafe fn downcast_mut_unchecked<T: 'static>(&mut self) -> &mut T {
-                unsafe { &mut *(self as *mut Self as *mut T) }
+                unsafe { &mut *std::ptr::from_mut::<Self>(self).cast::<T>() }
             }
         }
 
diff --git a/crates/stdx/src/lib.rs b/crates/stdx/src/lib.rs
index 982be40..9a292ea 100644
--- a/crates/stdx/src/lib.rs
+++ b/crates/stdx/src/lib.rs
@@ -17,7 +17,7 @@
 pub use itertools;
 
 #[inline(always)]
-pub fn is_ci() -> bool {
+pub const fn is_ci() -> bool {
     option_env!("CI").is_some()
 }
 
@@ -26,14 +26,14 @@
 }
 
 #[must_use]
-#[allow(clippy::print_stderr)]
+#[expect(clippy::print_stderr, reason = "only visible to developers")]
 pub fn timeit(label: &'static str) -> impl Drop {
     let start = Instant::now();
-    defer(move || eprintln!("{}: {:.2?}", label, start.elapsed()))
+    defer(move || eprintln!("{}: {:.2}", label, start.elapsed().as_nanos()))
 }
 
 /// Prints backtrace to stderr, useful for debugging.
-#[allow(clippy::print_stderr)]
+#[expect(clippy::print_stderr, reason = "only visible to developers")]
 pub fn print_backtrace() {
     #[cfg(feature = "backtrace")]
     eprintln!("{:?}", backtrace::Backtrace::new());
@@ -126,6 +126,7 @@
 }
 
 // Taken from rustc.
+#[must_use]
 pub fn to_camel_case(ident: &str) -> String {
     ident
         .trim_matches('_')
@@ -156,7 +157,7 @@
 
             camel_cased_component
         })
-        .fold((String::new(), None), |(acc, prev): (_, Option<String>), next| {
+        .fold((String::new(), None), |(mut acc, prev): (_, Option<String>), next| {
             // separate two components with an underscore if their boundary cannot
             // be distinguished using an uppercase/lowercase case distinction
             let join = prev
@@ -166,16 +167,20 @@
                     Some(!char_has_case(l) && !char_has_case(f))
                 })
                 .unwrap_or(false);
-            (acc + if join { "_" } else { "" } + &next, Some(next))
+            acc.push_str(if join { "_" } else { "" });
+            acc.push_str(&next);
+            (acc, Some(next))
         })
         .0
 }
 
 // Taken from rustc.
-pub fn char_has_case(c: char) -> bool {
+#[must_use]
+pub const fn char_has_case(c: char) -> bool {
     c.is_lowercase() || c.is_uppercase()
 }
 
+#[must_use]
 pub fn is_upper_snake_case(s: &str) -> bool {
     s.chars().all(|c| c.is_uppercase() || c == '_' || c.is_numeric())
 }
@@ -188,6 +193,7 @@
     *buf = buf.replace(from, to);
 }
 
+#[must_use]
 pub fn trim_indent(mut text: &str) -> String {
     if text.starts_with('\n') {
         text = &text[1..];
@@ -249,8 +255,8 @@
 
 impl Drop for JodChild {
     fn drop(&mut self) {
-        let _ = self.0.kill();
-        let _ = self.0.wait();
+        _ = self.0.kill();
+        _ = self.0.wait();
     }
 }
 
@@ -259,12 +265,11 @@
         command.spawn().map(Self)
     }
 
+    #[must_use]
+    #[cfg(not(target_arch = "wasm32"))]
     pub fn into_inner(self) -> std::process::Child {
-        if cfg!(target_arch = "wasm32") {
-            panic!("no processes on wasm");
-        }
         // SAFETY: repr transparent, except on WASM
-        unsafe { std::mem::transmute::<JodChild, std::process::Child>(self) }
+        unsafe { std::mem::transmute::<Self, std::process::Child>(self) }
     }
 }
 
diff --git a/crates/stdx/src/non_empty_vec.rs b/crates/stdx/src/non_empty_vec.rs
index 342194c..faa322d 100644
--- a/crates/stdx/src/non_empty_vec.rs
+++ b/crates/stdx/src/non_empty_vec.rs
@@ -8,8 +8,8 @@
 
 impl<T> NonEmptyVec<T> {
     #[inline]
-    pub fn new(first: T) -> Self {
-        NonEmptyVec { first, rest: Vec::new() }
+    pub const fn new(first: T) -> Self {
+        Self { first, rest: Vec::new() }
     }
 
     #[inline]
@@ -24,7 +24,7 @@
 
     #[inline]
     pub fn push(&mut self, value: T) {
-        self.rest.push(value)
+        self.rest.push(value);
     }
 
     #[inline]
diff --git a/crates/stdx/src/panic_context.rs b/crates/stdx/src/panic_context.rs
index a35d50b..b220451 100644
--- a/crates/stdx/src/panic_context.rs
+++ b/crates/stdx/src/panic_context.rs
@@ -16,7 +16,7 @@
 }
 
 pub fn enter(frame: String) -> PanicContext {
-    #[allow(clippy::print_stderr)]
+    #[expect(clippy::print_stderr, reason = "already panicking anyway")]
     fn set_hook() {
         let default_hook = panic::take_hook();
         panic::set_hook(Box::new(move |panic_info| {
diff --git a/crates/stdx/src/process.rs b/crates/stdx/src/process.rs
index 3b3955c..2efeed4 100644
--- a/crates/stdx/src/process.rs
+++ b/crates/stdx/src/process.rs
@@ -54,6 +54,9 @@
     Ok((stdout, stderr))
 }
 
+/// # Panics
+///
+/// Panics if `cmd` is not configured to have `stdout` and `stderr` as `piped`.
 pub fn spawn_with_streaming_output(
     mut cmd: Command,
     on_stdout_line: &mut dyn FnMut(&str),
diff --git a/crates/stdx/src/rand.rs b/crates/stdx/src/rand.rs
index 115a073..e028990 100644
--- a/crates/stdx/src/rand.rs
+++ b/crates/stdx/src/rand.rs
@@ -1,8 +1,7 @@
-//! We don't use `rand`, as that's too many things for us.
+//! We don't use `rand` because that is too many things for us.
 //!
-//! We currently use oorandom instead, but it's missing these two utilities.
-//! Perhaps we should switch to `fastrand`, or our own small PRNG, it's not like
-//! we need anything more complicated than xor-shift.
+//! `oorandom` is used instead, but it's missing these two utilities.
+//! Switching to `fastrand` or our own small PRNG may be good because only xor-shift is needed.
 
 pub fn shuffle<T>(slice: &mut [T], mut rand_index: impl FnMut(usize) -> usize) {
     let mut remaining = slice.len() - 1;
diff --git a/crates/stdx/src/thread.rs b/crates/stdx/src/thread.rs
index e577eb4..6c742fe 100644
--- a/crates/stdx/src/thread.rs
+++ b/crates/stdx/src/thread.rs
@@ -1,12 +1,12 @@
 //! A utility module for working with threads that automatically joins threads upon drop
-//! and abstracts over operating system quality of service (QoS) APIs
+//! and abstracts over operating system quality of service (`QoS`) APIs
 //! through the concept of a “thread intent”.
 //!
 //! The intent of a thread is frozen at thread creation time,
 //! i.e. there is no API to change the intent of a thread once it has been spawned.
 //!
 //! As a system, rust-analyzer should have the property that
-//! old manual scheduling APIs are replaced entirely by QoS.
+//! old manual scheduling APIs are replaced entirely by `QoS`.
 //! To maintain this invariant, we panic when it is clear that
 //! old scheduling APIs have been used.
 //!
@@ -23,10 +23,12 @@
 pub use intent::ThreadIntent;
 pub use pool::Pool;
 
+/// # Panics
+///
+/// Panics if failed to spawn the thread.
 pub fn spawn<F, T>(intent: ThreadIntent, f: F) -> JoinHandle<T>
 where
-    F: FnOnce() -> T,
-    F: Send + 'static,
+    F: (FnOnce() -> T) + Send + 'static,
     T: Send + 'static,
 {
     Builder::new(intent).spawn(f).expect("failed to spawn thread")
@@ -39,26 +41,29 @@
 }
 
 impl Builder {
-    pub fn new(intent: ThreadIntent) -> Builder {
-        Builder { intent, inner: jod_thread::Builder::new(), allow_leak: false }
+    #[must_use]
+    pub fn new(intent: ThreadIntent) -> Self {
+        Self { intent, inner: jod_thread::Builder::new(), allow_leak: false }
     }
 
-    pub fn name(self, name: String) -> Builder {
-        Builder { inner: self.inner.name(name), ..self }
+    #[must_use]
+    pub fn name(self, name: String) -> Self {
+        Self { inner: self.inner.name(name), ..self }
     }
 
-    pub fn stack_size(self, size: usize) -> Builder {
-        Builder { inner: self.inner.stack_size(size), ..self }
+    #[must_use]
+    pub fn stack_size(self, size: usize) -> Self {
+        Self { inner: self.inner.stack_size(size), ..self }
     }
 
-    pub fn allow_leak(self, b: bool) -> Builder {
-        Builder { allow_leak: b, ..self }
+    #[must_use]
+    pub fn allow_leak(self, allow_leak: bool) -> Self {
+        Self { allow_leak, ..self }
     }
 
     pub fn spawn<F, T>(self, f: F) -> std::io::Result<JoinHandle<T>>
     where
-        F: FnOnce() -> T,
-        F: Send + 'static,
+        F: (FnOnce() -> T) + Send + 'static,
         T: Send + 'static,
     {
         let inner_handle = self.inner.spawn(move || {
@@ -78,6 +83,10 @@
 }
 
 impl<T> JoinHandle<T> {
+    /// # Panics
+    ///
+    /// Panics if there is no thread to join.
+    #[must_use]
     pub fn join(mut self) -> T {
         self.inner.take().unwrap().join()
     }
@@ -95,6 +104,7 @@
     }
 }
 
+#[expect(clippy::min_ident_chars, reason = "trait impl")]
 impl<T> fmt::Debug for JoinHandle<T> {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         f.pad("JoinHandle { .. }")
diff --git a/crates/stdx/src/thread/intent.rs b/crates/stdx/src/thread/intent.rs
index 7b65db3..1203bfc 100644
--- a/crates/stdx/src/thread/intent.rs
+++ b/crates/stdx/src/thread/intent.rs
@@ -1,9 +1,9 @@
-//! An opaque façade around platform-specific QoS APIs.
+//! An opaque façade around platform-specific `QoS` APIs.
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
 // Please maintain order from least to most priority for the derived `Ord` impl.
 pub enum ThreadIntent {
-    /// Any thread which does work that isn’t in the critical path of the user typing
+    /// Any thread which does work that isn't in the critical path of the user typing
     /// (e.g. processing Go To Definition).
     Worker,
 
@@ -34,6 +34,7 @@
 
 const IS_QOS_AVAILABLE: bool = imp::IS_QOS_AVAILABLE;
 
+#[expect(clippy::semicolon_if_nothing_returned, reason = "thin wrapper")]
 fn set_current_thread_qos_class(class: QoSClass) {
     imp::set_current_thread_qos_class(class)
 }
@@ -63,7 +64,7 @@
         ///
         /// * **You do not care about how long it takes for work to finish.**
         /// * **You do not care about work being deferred temporarily.**
-        ///   (e.g. if the device’s battery is in a critical state)
+        ///   (e.g. if the device's battery is in a critical state)
         ///
         /// Examples:
         ///
@@ -84,7 +85,7 @@
         /// All other work is prioritized over background tasks.
         Background,
 
-        /// TLDR: tasks that don’t block using your app
+        /// TLDR: tasks that don't block using your app
         ///
         /// Contract:
         ///
@@ -110,7 +111,7 @@
         /// for tasks using this class.
         ///
         /// This QoS class provides a balance between
-        /// performance, responsiveness and efficiency.
+        /// performance, responsiveness, and efficiency.
         Utility,
 
         /// TLDR: tasks that block using your app
@@ -126,10 +127,10 @@
         /// * in a video editor:
         ///   opening a saved project
         /// * in a browser:
-        ///   loading a list of the user’s bookmarks and top sites
+        ///   loading a list of the user's bookmarks and top sites
         ///   when a new tab is created
         /// * in a collaborative word processor:
-        ///   running a search on the document’s content
+        ///   running a search on the document's content
         ///
         /// Use this QoS class for tasks which were initiated by the user
         /// and block the usage of your app while they are in progress.
@@ -208,7 +209,7 @@
             }
 
             _ => {
-                // `pthread_set_qos_class_self_np`’s documentation
+                // `pthread_set_qos_class_self_np`'s documentation
                 // does not mention any other errors.
                 unreachable!("`pthread_set_qos_class_self_np` returned unexpected error {errno}")
             }
@@ -223,7 +224,7 @@
         };
 
         if code != 0 {
-            // `pthread_get_qos_class_np`’s documentation states that
+            // `pthread_get_qos_class_np`'s documentation states that
             // an error value is placed into errno if the return code is not zero.
             // However, it never states what errors are possible.
             // Inspecting the source[0] shows that, as of this writing, it always returns zero.
diff --git a/crates/stdx/src/thread/pool.rs b/crates/stdx/src/thread/pool.rs
index 0efff38..074cd74 100644
--- a/crates/stdx/src/thread/pool.rs
+++ b/crates/stdx/src/thread/pool.rs
@@ -38,7 +38,11 @@
 }
 
 impl Pool {
-    pub fn new(threads: usize) -> Pool {
+    /// # Panics
+    ///
+    /// Panics if job panics
+    #[must_use]
+    pub fn new(threads: usize) -> Self {
         const STACK_SIZE: usize = 8 * 1024 * 1024;
         const INITIAL_INTENT: ThreadIntent = ThreadIntent::Worker;
 
@@ -63,7 +67,7 @@
                             }
                             extant_tasks.fetch_add(1, Ordering::SeqCst);
                             // discard the panic, we should've logged the backtrace already
-                            _ = panic::catch_unwind(job.f);
+                            drop(panic::catch_unwind(job.f));
                             extant_tasks.fetch_sub(1, Ordering::SeqCst);
                         }
                     }
@@ -73,9 +77,12 @@
             handles.push(handle);
         }
 
-        Pool { _handles: handles.into_boxed_slice(), extant_tasks, job_sender }
+        Self { _handles: handles.into_boxed_slice(), extant_tasks, job_sender }
     }
 
+    /// # Panics
+    ///
+    /// Panics if job panics
     pub fn spawn<F>(&self, intent: ThreadIntent, f: F)
     where
         F: FnOnce() + Send + UnwindSafe + 'static,
@@ -84,14 +91,20 @@
             if cfg!(debug_assertions) {
                 intent.assert_is_used_on_current_thread();
             }
-            f()
+            f();
         });
 
         let job = Job { requested_intent: intent, f };
         self.job_sender.send(job).unwrap();
     }
 
+    #[must_use]
     pub fn len(&self) -> usize {
         self.extant_tasks.load(Ordering::SeqCst)
     }
+
+    #[must_use]
+    pub fn is_empty(&self) -> bool {
+        self.len() == 0
+    }
 }