diff --git a/Cargo.lock b/Cargo.lock index fc3680ce27..b4e97261d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -22,9 +22,6 @@ name = "always-assert" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1078fa1ce1e34b1872d8611ad921196d76bdd7027e949fbe31231abde201892" -dependencies = [ - "tracing", -] [[package]] name = "anyhow" @@ -1922,13 +1919,13 @@ dependencies = [ name = "stdx" version = "0.0.0" dependencies = [ - "always-assert", "backtrace", "crossbeam-channel", "itertools", "jod-thread", "libc", "miow", + "tracing", "windows-sys 0.59.0", ] diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml index b8ce2b7430..64c8afdc1f 100644 --- a/crates/rust-analyzer/Cargo.toml +++ b/crates/rust-analyzer/Cargo.toml @@ -94,7 +94,7 @@ syntax-bridge.workspace = true [features] jemalloc = ["jemallocator", "profile/jemalloc"] -force-always-assert = ["always-assert/force"] +force-always-assert = ["stdx/force-always-assert"] sysroot-abi = [] in-rust-tree = [ "sysroot-abi", diff --git a/crates/stdx/Cargo.toml b/crates/stdx/Cargo.toml index 1ebb48c577..62c32d68e6 100644 --- a/crates/stdx/Cargo.toml +++ b/crates/stdx/Cargo.toml @@ -14,11 +14,11 @@ doctest = false [dependencies] backtrace = { version = "0.3.67", optional = true } -always-assert = { version = "0.2.0", features = ["tracing"] } jod-thread = "0.1.2" libc.workspace = true crossbeam-channel.workspace = true itertools.workspace = true +tracing.workspace = true # Think twice before adding anything here [target.'cfg(windows)'.dependencies] @@ -28,6 +28,7 @@ windows-sys = { version = "0.59", features = ["Win32_Foundation"] } [features] # Uncomment to enable for the whole crate graph # default = [ "backtrace" ] +force-always-assert = [] [lints] workspace = true diff --git a/crates/stdx/src/assert.rs b/crates/stdx/src/assert.rs new file mode 100644 index 0000000000..91c279798c --- /dev/null +++ b/crates/stdx/src/assert.rs @@ -0,0 +1,115 @@ +// Vendored from https://github.com/matklad/always-assert/commit/4cf564eea6fcf18b30c3c3483a611004dc03afbb +//! Recoverable assertions, inspired by [the use of `assert()` in +//! SQLite](https://www.sqlite.org/assert.html). +//! +//! `never!` and `always!` return the actual value of the condition if +//! `debug_assertions` are disabled. +//! +//! Use them when terminating on assertion failure is worse than continuing. +//! +//! One example would be a critical application like a database: +//! +//! ```ignore +//! use stdx::never; +//! +//! fn apply_transaction(&mut self, tx: Transaction) -> Result<(), TransactionAborted> { +//! let delta = self.compute_delta(&tx); +//! +//! if never!(!self.check_internal_invariant(&delta)) { +//! // Ok, something in this transaction messed up our internal state. +//! // This really shouldn't be happening, and this signifies a bug. +//! // Luckily, we can recover by just rejecting the transaction. +//! return abort_transaction(tx); +//! } +//! self.commit(delta); +//! Ok(()) +//! } +//! ``` +//! +//! Another example is assertions about non-critical functionality in usual apps +//! +//! ```ignore +//! use stdx::never; +//! +//! let english_message = "super app installed!" +//! let mut local_message = localize(english_message); +//! if never!(local_message.is_empty(), "missing localization for {}", english_message) { +//! // We localized all the messages but this one slipper through the cracks? +//! // Better to show the english one then than to fail outright; +//! local_message = english_message; +//! } +//! println!("{}", local_message); +//! ``` + +/// Asserts that the condition is always true and returns its actual value. +/// +/// If the condition is true does nothing and and evaluates to true. +/// +/// If the condition is false: +/// * panics if `force` feature or `debug_assertions` are enabled, +/// * logs an error if the `tracing` feature is enabled, +/// * evaluates to false. +/// +/// Accepts `format!` style arguments. +#[macro_export] +macro_rules! always { + ($cond:expr) => { + $crate::always!($cond, "assertion failed: {}", stringify!($cond)) + }; + + ($cond:expr, $fmt:literal $($arg:tt)*) => {{ + let cond = $cond; + if cfg!(debug_assertions) || $crate::assert::__FORCE { + assert!(cond, $fmt $($arg)*); + } + if !cond { + $crate::assert::__tracing_error!($fmt $($arg)*); + } + cond + }}; +} + +/// Asserts that the condition is never true and returns its actual value. +/// +/// If the condition is false does nothing and and evaluates to false. +/// +/// If the condition is true: +/// * panics if `force` feature or `debug_assertions` are enabled, +/// * logs an error if the `tracing` feature is enabled, +/// * evaluates to true. +/// +/// Accepts `format!` style arguments. +/// +/// Empty condition is equivalent to false: +/// +/// ```ignore +/// never!("oups") ~= unreachable!("oups") +/// ``` +#[macro_export] +macro_rules! never { + (true $($tt:tt)*) => { $crate::never!((true) $($tt)*) }; + (false $($tt:tt)*) => { $crate::never!((false) $($tt)*) }; + () => { $crate::never!("assertion failed: entered unreachable code") }; + ($fmt:literal $(, $($arg:tt)*)?) => {{ + if cfg!(debug_assertions) || $crate::assert::__FORCE { + unreachable!($fmt $(, $($arg)*)?); + } + $crate::assert::__tracing_error!($fmt $(, $($arg)*)?); + }}; + + ($cond:expr) => {{ + let cond = !$crate::always!(!$cond); + cond + }}; + + ($cond:expr, $fmt:literal $($arg:tt)*) => {{ + let cond = !$crate::always!(!$cond, $fmt $($arg)*); + cond + }}; +} + +#[doc(hidden)] +pub use tracing::error as __tracing_error; + +#[doc(hidden)] +pub const __FORCE: bool = cfg!(feature = "force-always-assert"); diff --git a/crates/stdx/src/lib.rs b/crates/stdx/src/lib.rs index 04c2153abf..8313e1871f 100644 --- a/crates/stdx/src/lib.rs +++ b/crates/stdx/src/lib.rs @@ -4,8 +4,10 @@ use std::io as sio; use std::process::Command; use std::{cmp::Ordering, ops, time::Instant}; -pub mod anymap; mod macros; + +pub mod anymap; +pub mod assert; pub mod non_empty_vec; pub mod panic_context; pub mod process; @@ -13,7 +15,6 @@ pub mod rand; pub mod thin_vec; pub mod thread; -pub use always_assert::{always, never}; pub use itertools; #[inline(always)]