diff --git a/Cargo.lock b/Cargo.lock index e41193896d3fd..20c2340aea9c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -62,12 +62,6 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" -[[package]] -name = "assert-unchecked" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7330592adf847ee2e3513587b4db2db410a0d751378654e7e993d9adcbe5c795" - [[package]] name = "async-trait" version = "0.1.88" @@ -1548,9 +1542,9 @@ name = "oxc_allocator" version = "0.60.0" dependencies = [ "allocator-api2", - "assert-unchecked", "bumpalo", "hashbrown 0.15.2", + "oxc_data_structures", "oxc_estree", "rustc-hash", "serde", @@ -1718,7 +1712,6 @@ dependencies = [ name = "oxc_data_structures" version = "0.60.0" dependencies = [ - "assert-unchecked", "ropey", ] @@ -1953,7 +1946,6 @@ dependencies = [ name = "oxc_parser" version = "0.60.0" dependencies = [ - "assert-unchecked", "bitflags 2.9.0", "cow-utils", "memchr", @@ -1962,6 +1954,7 @@ dependencies = [ "oxc_allocator", "oxc_ast", "oxc_ast_visit", + "oxc_data_structures", "oxc_diagnostics", "oxc_ecmascript", "oxc_regular_expression", @@ -2073,7 +2066,6 @@ dependencies = [ name = "oxc_semantic" version = "0.60.0" dependencies = [ - "assert-unchecked", "insta", "itertools", "oxc_allocator", @@ -2128,12 +2120,12 @@ dependencies = [ name = "oxc_syntax" version = "0.60.0" dependencies = [ - "assert-unchecked", "bitflags 2.9.0", "cow-utils", "nonmax", "oxc_allocator", "oxc_ast_macros", + "oxc_data_structures", "oxc_estree", "oxc_index", "oxc_span", diff --git a/Cargo.toml b/Cargo.toml index 1f800673bb01a..f10b8a4359aeb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -158,7 +158,6 @@ oxc_sourcemap = "3" # allocator-api2 = "0.2.21" -assert-unchecked = "0.1.2" base64 = "0.22.1" bitflags = "2.8.0" bpaf = "0.9.16" diff --git a/crates/oxc_allocator/Cargo.toml b/crates/oxc_allocator/Cargo.toml index 42f2e298957f3..c8315f8e5b299 100644 --- a/crates/oxc_allocator/Cargo.toml +++ b/crates/oxc_allocator/Cargo.toml @@ -19,10 +19,10 @@ workspace = true doctest = false [dependencies] +oxc_data_structures = { workspace = true, features = ["assert_unchecked"] } oxc_estree = { workspace = true, optional = true } allocator-api2 = { workspace = true } -assert-unchecked = { workspace = true } bumpalo = { workspace = true, features = ["allocator-api2", "collections"] } hashbrown = { workspace = true, default-features = false, features = ["inline-more", "allocator-api2"] } rustc-hash = { workspace = true } diff --git a/crates/oxc_allocator/src/string.rs b/crates/oxc_allocator/src/string.rs index be3506fa39b09..9d2abd13d4ebd 100644 --- a/crates/oxc_allocator/src/string.rs +++ b/crates/oxc_allocator/src/string.rs @@ -13,11 +13,12 @@ use std::{ ptr, }; -use assert_unchecked::assert_unchecked; use bumpalo::collections::String as BumpaloString; pub use simdutf8::basic::Utf8Error; use simdutf8::basic::from_utf8; +use oxc_data_structures::assert_unchecked; + use crate::{Allocator, Vec}; /// Arena String. diff --git a/crates/oxc_data_structures/Cargo.toml b/crates/oxc_data_structures/Cargo.toml index 7f240c2918aaa..a4b7d3fe128e7 100644 --- a/crates/oxc_data_structures/Cargo.toml +++ b/crates/oxc_data_structures/Cargo.toml @@ -18,16 +18,17 @@ workspace = true [lib] test = true +# Doctests must be enabled for this crate as they are used to run compilation failure tests doctest = true [dependencies] -assert-unchecked = { workspace = true, optional = true } ropey = { workspace = true, optional = true } [features] default = [] -all = ["code_buffer", "inline_string", "rope", "stack"] -code_buffer = ["dep:assert-unchecked"] +all = ["assert_unchecked", "code_buffer", "inline_string", "rope", "stack"] +assert_unchecked = [] +code_buffer = ["assert_unchecked"] inline_string = [] rope = ["dep:ropey"] -stack = ["dep:assert-unchecked"] +stack = ["assert_unchecked"] diff --git a/crates/oxc_data_structures/src/assert_unchecked.rs b/crates/oxc_data_structures/src/assert_unchecked.rs new file mode 100644 index 0000000000000..e9ddd75adbbb1 --- /dev/null +++ b/crates/oxc_data_structures/src/assert_unchecked.rs @@ -0,0 +1,102 @@ +//! `assert_unchecked!` macro. + +/// Macro that makes a soundness promise to the compiler that this condition always holds. +/// +/// This is wrapper around [`std::hint::assert_unchecked`] which performs a standard safe `assert!` +/// in debug builds, and accepts a format string for the error in that case. +/// +/// See documentation for [`std::hint::assert_unchecked`] for further details. +/// +/// This macro can be used in const context. +/// +/// # SAFETY +/// It is undefined behaviour if the condition evaluates to `false`. +/// +/// # Examples +/// ``` +/// // Requires `assert_unchecked` feature +/// use oxc_data_structures::assert_unchecked; +/// +/// /// Read first byte from a `&[u8]`, without bounds checks. +/// /// +/// /// # SAFETY +/// /// Caller must ensure `slice` is not empty. +/// unsafe fn first_unchecked(slice: &[u8]) -> u8 { +/// // SAFETY: Caller guarantees `slice` is not empty +/// unsafe { +/// assert_unchecked!(!slice.is_empty(), "Oh no. Slice is empty."); +/// } +/// // Compiler elides the bounds check here. https://godbolt.org/z/xaGK8GzbG +/// slice[0] +/// } +/// ``` +#[macro_export] +macro_rules! assert_unchecked { + ($cond:expr) => (assert_unchecked!($cond,)); + ($cond:expr, $($arg:tt)*) => ({ + #[cfg(debug_assertions)] + { + const unsafe fn __needs_unsafe() {} + __needs_unsafe(); + assert!($cond, $($arg)*); + } + #[cfg(not(debug_assertions))] + { + std::hint::assert_unchecked($cond); + } + }) +} + +/** +Doctest to ensure macro cannot be used without `unsafe {}`. +```compile_fail +use oxc_data_structures::assert_unchecked; +assert_unchecked!(true); +``` +*/ +const _MACRO_CANNOT_BE_USED_WITHOUT_UNSAFE: () = (); + +#[cfg(test)] +#[expect(clippy::undocumented_unsafe_blocks)] +mod test { + mod pass { + use crate::assert_unchecked; + + #[test] + fn plain() { + unsafe { assert_unchecked!(0 == 0) }; + } + + #[test] + fn string_literal() { + unsafe { assert_unchecked!(0 == 0, "String literal") }; + } + + #[test] + fn fmt_string() { + unsafe { assert_unchecked!(0 == 0, "Format str {} {:?}", 123, 456) }; + } + } + + // Cannot test failing assertions in release mode as it'd be undefined behavior! + #[cfg(debug_assertions)] + mod fail { + #[test] + #[should_panic(expected = "assertion failed: 0 == 1")] + fn plain() { + unsafe { assert_unchecked!(0 == 1) }; + } + + #[test] + #[should_panic(expected = "String literal")] + fn string_literal() { + unsafe { assert_unchecked!(0 == 1, "String literal") }; + } + + #[test] + #[should_panic(expected = "Format str: 123 456")] + fn fmt_string() { + unsafe { assert_unchecked!(0 == 1, "Format str: {} {:?}", 123, 456) }; + } + } +} diff --git a/crates/oxc_data_structures/src/code_buffer.rs b/crates/oxc_data_structures/src/code_buffer.rs index ab6f2d06d4270..b46c789cebb48 100644 --- a/crates/oxc_data_structures/src/code_buffer.rs +++ b/crates/oxc_data_structures/src/code_buffer.rs @@ -2,7 +2,7 @@ use std::iter; -use assert_unchecked::assert_unchecked; +use crate::assert_unchecked; /// A string builder for constructing source code. /// diff --git a/crates/oxc_data_structures/src/lib.rs b/crates/oxc_data_structures/src/lib.rs index f4e47146dd173..76309e61864db 100644 --- a/crates/oxc_data_structures/src/lib.rs +++ b/crates/oxc_data_structures/src/lib.rs @@ -2,11 +2,17 @@ #![warn(missing_docs)] +#[cfg(feature = "assert_unchecked")] +mod assert_unchecked; + #[cfg(feature = "code_buffer")] pub mod code_buffer; + #[cfg(feature = "inline_string")] pub mod inline_string; + #[cfg(feature = "rope")] pub mod rope; + #[cfg(feature = "stack")] pub mod stack; diff --git a/crates/oxc_data_structures/src/stack/common.rs b/crates/oxc_data_structures/src/stack/common.rs index ba3286362d269..005403a6f42eb 100644 --- a/crates/oxc_data_structures/src/stack/common.rs +++ b/crates/oxc_data_structures/src/stack/common.rs @@ -5,7 +5,7 @@ use std::{ slice, }; -use assert_unchecked::assert_unchecked; +use crate::assert_unchecked; use super::StackCapacity; diff --git a/crates/oxc_parser/Cargo.toml b/crates/oxc_parser/Cargo.toml index bb37450cd9844..475dfeeeab0b6 100644 --- a/crates/oxc_parser/Cargo.toml +++ b/crates/oxc_parser/Cargo.toml @@ -21,13 +21,13 @@ doctest = false [dependencies] oxc_allocator = { workspace = true } oxc_ast = { workspace = true } +oxc_data_structures = { workspace = true, features = ["assert_unchecked"] } oxc_diagnostics = { workspace = true } oxc_ecmascript = { workspace = true } oxc_regular_expression = { workspace = true } oxc_span = { workspace = true } oxc_syntax = { workspace = true } -assert-unchecked = { workspace = true } bitflags = { workspace = true } cow-utils = { workspace = true } num-bigint = { workspace = true } diff --git a/crates/oxc_parser/src/lexer/byte_handlers.rs b/crates/oxc_parser/src/lexer/byte_handlers.rs index 3057e3b7339ac..f87a9062c08f3 100644 --- a/crates/oxc_parser/src/lexer/byte_handlers.rs +++ b/crates/oxc_parser/src/lexer/byte_handlers.rs @@ -108,7 +108,7 @@ macro_rules! byte_handler { /// fn SPS(lexer: &mut Lexer) { /// // SAFETY: This macro is only used for ASCII characters /// unsafe { -/// use assert_unchecked::assert_unchecked; +/// use oxc_data_structures::assert_unchecked; /// assert_unchecked!(!lexer.source.is_eof()); /// assert_unchecked!(lexer.source.peek_byte_unchecked() < 128); /// } @@ -125,7 +125,7 @@ macro_rules! ascii_byte_handler { byte_handler!($id($lex) { // SAFETY: This macro is only used for ASCII characters unsafe { - use assert_unchecked::assert_unchecked; + use oxc_data_structures::assert_unchecked; assert_unchecked!(!$lex.source.is_eof()); assert_unchecked!($lex.source.peek_byte_unchecked() < 128); } diff --git a/crates/oxc_semantic/Cargo.toml b/crates/oxc_semantic/Cargo.toml index f5ca7c83da87e..587d19e2ae003 100644 --- a/crates/oxc_semantic/Cargo.toml +++ b/crates/oxc_semantic/Cargo.toml @@ -23,14 +23,13 @@ oxc_allocator = { workspace = true } oxc_ast = { workspace = true } oxc_ast_visit = { workspace = true } oxc_cfg = { workspace = true } -oxc_data_structures = { workspace = true, features = ["stack"] } +oxc_data_structures = { workspace = true, features = ["assert_unchecked", "stack"] } oxc_diagnostics = { workspace = true } oxc_ecmascript = { workspace = true } oxc_index = { workspace = true } oxc_span = { workspace = true } oxc_syntax = { workspace = true } -assert-unchecked = { workspace = true } itertools = { workspace = true } phf = { workspace = true, features = ["macros"] } rustc-hash = { workspace = true } diff --git a/crates/oxc_semantic/src/unresolved_stack.rs b/crates/oxc_semantic/src/unresolved_stack.rs index 344802c8ac223..934cba4530058 100644 --- a/crates/oxc_semantic/src/unresolved_stack.rs +++ b/crates/oxc_semantic/src/unresolved_stack.rs @@ -1,6 +1,6 @@ -use assert_unchecked::assert_unchecked; use rustc_hash::FxHashMap; +use oxc_data_structures::assert_unchecked; use oxc_span::Atom; use oxc_syntax::reference::ReferenceId; diff --git a/crates/oxc_syntax/Cargo.toml b/crates/oxc_syntax/Cargo.toml index 03851b1ac5d9b..68b4bcdcd43b2 100644 --- a/crates/oxc_syntax/Cargo.toml +++ b/crates/oxc_syntax/Cargo.toml @@ -22,11 +22,11 @@ doctest = false [dependencies] oxc_allocator = { workspace = true } oxc_ast_macros = { workspace = true } +oxc_data_structures = { workspace = true, features = ["assert_unchecked"] } oxc_estree = { workspace = true } oxc_index = { workspace = true } oxc_span = { workspace = true } -assert-unchecked = { workspace = true } bitflags = { workspace = true } cow-utils = { workspace = true } nonmax = { workspace = true } diff --git a/crates/oxc_syntax/src/identifier.rs b/crates/oxc_syntax/src/identifier.rs index 1685dd9c0984e..983a1c4eca300 100644 --- a/crates/oxc_syntax/src/identifier.rs +++ b/crates/oxc_syntax/src/identifier.rs @@ -1,7 +1,9 @@ #![expect(missing_docs)] // fixme -use assert_unchecked::assert_unchecked; + use unicode_id_start::{is_id_continue_unicode, is_id_start_unicode}; +use oxc_data_structures::assert_unchecked; + pub const EOF: char = '\0'; // 11.1 Unicode Format-Control Characters