From 7c75afde6abb630ad177ddd4d841162f89639846 Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud <7252775+indietyp@users.noreply.github.com> Date: Mon, 8 Jun 2026 11:52:56 +0200 Subject: [PATCH 1/2] feat: remove read module chore: add new dependency chore: format feat: error module feat: introduce hashql_eval interner chore: checkpoint feat: checkpoint feat: checkpoint chore: remove old value module feat: checkpoint feat: checkpoint feat: checkpoint feat: checkpoint feat: checkpoint chore: checkpoint feat: move entity query into its own modul fix: query request feat: checkpoint (it compiles!) feat: checkpoint feat: checkpoint feat: checkpoint fix: issue around cached thunking feat: covariance for opaque inners fix: cfgattr serde chore: remove graph module --- libs/@local/hashql/core/Cargo.toml | 1 - .../hashql/core/src/collections/pool.rs | 1 + .../graph/algorithms/dominators/frontier.rs | 1 + libs/@local/hashql/core/src/heap/mod.rs | 3 +- libs/@local/hashql/core/src/heap/pool.rs | 434 ++++++++++++------ libs/@local/hashql/core/src/heap/scratch.rs | 5 +- libs/@local/hashql/core/src/id/bit_vec/mod.rs | 2 + .../hashql/core/src/module/namespace.rs | 1 + .../hashql/core/src/pretty/formatter.rs | 1 + libs/@local/hashql/core/src/span/mod.rs | 18 +- libs/@local/hashql/core/src/span/table.rs | 84 ++-- libs/@local/hashql/diagnostics/src/issues.rs | 79 ++++ libs/@local/hashql/diagnostics/src/lib.rs | 2 +- .../hashql/diagnostics/src/source/span.rs | 2 + libs/@local/hashql/diagnostics/src/status.rs | 145 +++++- libs/@local/hashql/syntax-jexpr/src/error.rs | 21 +- .../hashql/syntax-jexpr/src/lexer/error.rs | 18 +- .../hashql/syntax-jexpr/src/lexer/mod.rs | 19 +- .../syntax-jexpr/src/lexer/syntax_kind_set.rs | 2 + .../syntax-jexpr/src/parser/array/error.rs | 19 +- .../syntax-jexpr/src/parser/array/visit.rs | 6 +- .../syntax-jexpr/src/parser/complex/mod.rs | 6 +- .../hashql/syntax-jexpr/src/parser/error.rs | 10 +- .../syntax-jexpr/src/parser/object/error.rs | 43 +- .../syntax-jexpr/src/parser/object/visit.rs | 10 +- .../hashql/syntax-jexpr/src/parser/state.rs | 2 + .../syntax-jexpr/src/parser/string/error.rs | 6 +- libs/@local/hashql/syntax-jexpr/src/test.rs | 6 +- 28 files changed, 672 insertions(+), 275 deletions(-) diff --git a/libs/@local/hashql/core/Cargo.toml b/libs/@local/hashql/core/Cargo.toml index c1c1f638fc5..5cef818ed51 100644 --- a/libs/@local/hashql/core/Cargo.toml +++ b/libs/@local/hashql/core/Cargo.toml @@ -32,7 +32,6 @@ lexical = { workspace = true, features = ["parse-integers", "parse- memchr = { workspace = true } rapidfuzz = { workspace = true } roaring = { workspace = true, features = ["std"] } -rpds = { workspace = true, features = ["std"] } serde = { workspace = true, optional = true, features = ["alloc", "derive"] } simple-mermaid = { workspace = true } tracing = { workspace = true } diff --git a/libs/@local/hashql/core/src/collections/pool.rs b/libs/@local/hashql/core/src/collections/pool.rs index 410fdfc6e16..2b9e730a1f4 100644 --- a/libs/@local/hashql/core/src/collections/pool.rs +++ b/libs/@local/hashql/core/src/collections/pool.rs @@ -344,6 +344,7 @@ where /// let pool = VecPool::::new(15); /// assert_eq!(pool.capacity(), 15); /// ``` + #[inline] pub const fn capacity(&self) -> usize { self.capacity } diff --git a/libs/@local/hashql/core/src/graph/algorithms/dominators/frontier.rs b/libs/@local/hashql/core/src/graph/algorithms/dominators/frontier.rs index c00f04189f8..bf411ca8b46 100644 --- a/libs/@local/hashql/core/src/graph/algorithms/dominators/frontier.rs +++ b/libs/@local/hashql/core/src/graph/algorithms/dominators/frontier.rs @@ -177,6 +177,7 @@ where } /// Returns the underlying [`RowRef`] if the frontier is non-empty. + #[inline] #[must_use] pub const fn as_inner(&self) -> Option> { self.inner diff --git a/libs/@local/hashql/core/src/heap/mod.rs b/libs/@local/hashql/core/src/heap/mod.rs index 68360ad9803..525c955fc70 100644 --- a/libs/@local/hashql/core/src/heap/mod.rs +++ b/libs/@local/hashql/core/src/heap/mod.rs @@ -111,7 +111,7 @@ pub use self::{ clone::{CloneIn, TryCloneIn}, convert::{FromIn, IntoIn}, iter::{CollectIn, FromIteratorIn}, - pool::{ScratchPool, ScratchPoolGuard}, + pool::{HeapPool, HeapPoolGuard, ScratchPool, ScratchPoolGuard}, scratch::Scratch, transfer::TransferInto, }; @@ -288,6 +288,7 @@ impl Heap { } impl Default for Heap { + #[inline] fn default() -> Self { Self::new() } diff --git a/libs/@local/hashql/core/src/heap/pool.rs b/libs/@local/hashql/core/src/heap/pool.rs index 7a7b7c64a6f..c0ffe494b68 100644 --- a/libs/@local/hashql/core/src/heap/pool.rs +++ b/libs/@local/hashql/core/src/heap/pool.rs @@ -1,201 +1,359 @@ -//! Pool of scratch allocators for parallel bump allocation. +//! Allocator pools for parallel bump allocation. //! -//! [`ScratchPool`] enables bump allocation across multiple threads. Each thread -//! borrows its own [`ScratchPoolGuard`] via [`get`](ScratchPool::get), which provides -//! an independent bump allocator. +//! [`ScratchPool`] provides thread-safe scratch allocation. Each thread borrows +//! its own [`ScratchPoolGuard`] via [`get`](ScratchPool::get), which derefs to +//! [`Scratch`](super::Scratch). //! -//! # Usage -//! -//! ``` -//! # #![feature(allocator_api)] -//! use hashql_core::heap::ScratchPool; -//! -//! let pool = ScratchPool::new(); -//! -//! let guard = pool.get(); -//! let mut vec: Vec = Vec::new_in(&guard); -//! vec.push(42); -//! ``` +//! [`HeapPool`] provides thread-safe heap allocation with symbol interning. +//! Each thread borrows its own [`HeapPoolGuard`] via [`get`](HeapPool::get), +//! which derefs to [`Heap`](super::Heap). -use core::{alloc, mem, ptr}; +use core::{ + mem::ManuallyDrop, + ops::{Deref, DerefMut}, +}; +use std::sync::nonpoison::Mutex; -use bump_scope::{BumpBox, BumpPool, BumpPoolGuard}; +use super::{Heap, ResetAllocator as _, Scratch}; -use super::{AllocatorScope, BumpAllocator, allocator::Checkpoint}; - -/// A pool of scratch allocators for parallel bump allocation. -/// -/// Unlike [`Scratch`](super::Scratch) which is `!Sync`, `ScratchPool` can be shared -/// across threads. Each thread obtains its own [`ScratchPoolGuard`] via [`get`](Self::get), -/// which provides an independent bump allocator. -/// -/// # Example +/// A pool of [`Scratch`] allocators for parallel bump allocation. /// -/// ``` -/// # #![feature(allocator_api)] -/// use hashql_core::heap::ScratchPool; +/// Each thread obtains its own [`Scratch`] via [`get`](Self::get). The +/// allocator is returned to the pool and reset when the guard is dropped. /// -/// let pool = ScratchPool::new(); -/// let guard = pool.get(); -/// -/// let mut vec: Vec = Vec::new_in(&guard); -/// vec.push(1); -/// vec.push(2); -/// ``` -pub struct ScratchPool(BumpPool); +/// By default the pool is unbounded. Use [`bounded`](Self::bounded) to cap +/// the number of retained allocators; excess allocators are dropped instead +/// of returned. +pub struct ScratchPool { + pool: Mutex>, + max_size: usize, +} impl ScratchPool { - /// Creates a new empty scratch pool. + /// Creates a new unbounded scratch pool. #[must_use] #[inline] - pub fn new() -> Self { - Self(BumpPool::new()) + pub const fn new() -> Self { + Self { + pool: Mutex::new(Vec::new()), + max_size: usize::MAX, + } } - /// Borrows an allocator from the pool. + /// Creates a new scratch pool that retains at most `max_size` allocators. /// - /// Each call may reuse a previously returned allocator or create a new one. + /// When a guard is dropped and the pool is already at capacity, the + /// allocator is dropped instead of returned to the pool. + #[must_use] #[inline] - pub fn get(&self) -> ScratchPoolGuard<'_> { - ScratchPoolGuard(self.0.get()) + pub const fn bounded(max_size: usize) -> Self { + Self { + pool: Mutex::new(Vec::new()), + max_size, + } } - /// Resets all allocators in the pool, freeing all allocations at once. - /// - /// The pool retains its current capacity. - /// - /// # Panics + /// Borrows a [`Scratch`] from the pool. /// - /// All [`ScratchPoolGuard`]s must have been dropped before calling this method. + /// Reuses a previously returned allocator if available, otherwise creates + /// a new one. The allocator is reset and returned to the pool when the + /// guard is dropped. #[inline] - pub fn reset(&mut self) { - self.0.reset(); + pub fn get(&self) -> ScratchPoolGuard<'_> { + let scratch = self.pool.lock().pop().unwrap_or_default(); + + ScratchPoolGuard { + pool: self, + scratch: ManuallyDrop::new(scratch), + } } } impl Default for ScratchPool { + #[inline] fn default() -> Self { Self::new() } } -/// A borrowed allocator from a [`ScratchPool`]. +/// A borrowed [`Scratch`] from a [`ScratchPool`]. /// -/// Implements [`BumpAllocator`] and [`Allocator`](alloc::Allocator), so it can be -/// used anywhere a bump allocator is expected. -pub struct ScratchPoolGuard<'pool>(BumpPoolGuard<'pool>); +/// Derefs to [`Scratch`], so it can be used anywhere a `&Scratch` is expected. +/// On drop, the allocator is reset and returned to the pool for reuse. +pub struct ScratchPoolGuard<'pool> { + pool: &'pool ScratchPool, + scratch: ManuallyDrop, +} -impl BumpAllocator for ScratchPoolGuard<'_> { - type Checkpoint = Checkpoint; - type Scoped<'scope> = AllocatorScope<'scope>; +impl Deref for ScratchPoolGuard<'_> { + type Target = Scratch; #[inline] - fn scoped(&mut self, func: impl FnOnce(Self::Scoped<'_>) -> T) -> T { - self.0.scoped(|scope| func(AllocatorScope(scope))) + fn deref(&self) -> &Scratch { + &self.scratch } +} +impl DerefMut for ScratchPoolGuard<'_> { #[inline] - fn checkpoint(&self) -> Self::Checkpoint { - Checkpoint(self.0.checkpoint()) + fn deref_mut(&mut self) -> &mut Scratch { + &mut self.scratch + } +} + +#[expect(unsafe_code, reason = "ManuallyDrop::take in Drop")] +impl Drop for ScratchPoolGuard<'_> { + fn drop(&mut self) { + // SAFETY: This is the `Drop` impl, so `drop` is called exactly once. + // After `take`, `self.scratch` is logically moved out and will not be + // read or dropped again. + let mut scratch = unsafe { ManuallyDrop::take(&mut self.scratch) }; + scratch.reset(); + + let mut scratches = self.pool.pool.lock(); + + if scratches.len() < self.pool.max_size { + scratches.push(scratch); + } } +} +/// A pool of [`Heap`] allocators for parallel allocation with symbol interning. +/// +/// Each thread obtains its own [`Heap`] via [`get`](Self::get). The heap is +/// returned to the pool and reset when the guard is dropped. +/// +/// By default the pool is unbounded. Use [`bounded`](Self::bounded) to cap +/// the number of retained heaps; excess heaps are dropped instead of returned. +pub struct HeapPool { + pool: Mutex>, + max_size: usize, +} + +impl HeapPool { + /// Creates a new unbounded heap pool. + #[must_use] #[inline] - unsafe fn rollback(&self, checkpoint: Self::Checkpoint) { - // SAFETY: The same safety preconditions apply. - unsafe { self.0.reset_to(checkpoint.0) } + pub const fn new() -> Self { + Self { + pool: Mutex::new(Vec::new()), + max_size: usize::MAX, + } } + /// Creates a new heap pool that retains at most `max_size` heaps. + /// + /// When a guard is dropped and the pool is already at capacity, the heap + /// is dropped instead of returned to the pool. + #[must_use] #[inline] - fn try_allocate_slice_copy(&self, slice: &[T]) -> Result<&mut [T], alloc::AllocError> { - self.0 - .try_alloc_slice_copy(slice) - .map(BumpBox::leak) - .map_err(|_err| alloc::AllocError) + pub const fn bounded(max_size: usize) -> Self { + Self { + pool: Mutex::new(Vec::new()), + max_size, + } } + /// Borrows a [`Heap`] from the pool. + /// + /// Reuses a previously returned heap if available, otherwise creates a new + /// one. The heap is reset and returned to the pool when the guard is + /// dropped. #[inline] - fn try_allocate_slice_uninit( - &self, - len: usize, - ) -> Result<&mut [mem::MaybeUninit], alloc::AllocError> { - const { - assert!( - !core::mem::needs_drop::(), - "Cannot allocate a type that needs drop" - ); - }; - - self.0 - .try_alloc_uninit_slice(len) - .map(BumpBox::leak) - .map_err(|_err| alloc::AllocError) + pub fn get(&self) -> HeapPoolGuard<'_> { + let heap = self.pool.lock().pop().unwrap_or_default(); + + HeapPoolGuard { + pool: self, + heap: ManuallyDrop::new(heap), + } } } -// SAFETY: Delegates to bump_scope via the internal BumpPoolGuard. -#[expect(unsafe_code, reason = "proxy to bump")] -unsafe impl alloc::Allocator for ScratchPoolGuard<'_> { +impl Default for HeapPool { #[inline] - fn allocate(&self, layout: alloc::Layout) -> Result, alloc::AllocError> { - bump_scope::alloc::Allocator::allocate(&*self.0, layout).map_err(|_err| alloc::AllocError) + fn default() -> Self { + Self::new() } +} + +/// A borrowed [`Heap`] from a [`HeapPool`]. +/// +/// Derefs to [`Heap`], so it can be used anywhere a `&Heap` is expected. +/// On drop, the heap is reset and returned to the pool for reuse. +pub struct HeapPoolGuard<'pool> { + pool: &'pool HeapPool, + heap: ManuallyDrop, +} + +impl Deref for HeapPoolGuard<'_> { + type Target = Heap; #[inline] - fn allocate_zeroed( - &self, - layout: alloc::Layout, - ) -> Result, alloc::AllocError> { - bump_scope::alloc::Allocator::allocate_zeroed(&*self.0, layout) - .map_err(|_err| alloc::AllocError) + fn deref(&self) -> &Heap { + &self.heap } +} +impl DerefMut for HeapPoolGuard<'_> { #[inline] - unsafe fn deallocate(&self, ptr: ptr::NonNull, layout: alloc::Layout) { - // SAFETY: Caller upholds Allocator contract. - unsafe { - bump_scope::alloc::Allocator::deallocate(&*self.0, ptr, layout); - } + fn deref_mut(&mut self) -> &mut Heap { + &mut self.heap } +} - #[inline] - unsafe fn grow( - &self, - ptr: ptr::NonNull, - old_layout: alloc::Layout, - new_layout: alloc::Layout, - ) -> Result, alloc::AllocError> { - // SAFETY: Caller upholds Allocator contract. - unsafe { - bump_scope::alloc::Allocator::grow(&*self.0, ptr, old_layout, new_layout) - .map_err(|_err| alloc::AllocError) +#[expect(unsafe_code, reason = "ManuallyDrop::take in Drop")] +impl Drop for HeapPoolGuard<'_> { + fn drop(&mut self) { + // SAFETY: This is the `Drop` impl, so `drop` is called exactly once. + // After `take`, `self.heap` is logically moved out and will not be + // read or dropped again. + let mut heap = unsafe { ManuallyDrop::take(&mut self.heap) }; + heap.reset(); + + let mut heaps = self.pool.pool.lock(); + + if heaps.len() < self.pool.max_size { + heaps.push(heap); } } +} - #[inline] - unsafe fn grow_zeroed( - &self, - ptr: ptr::NonNull, - old_layout: alloc::Layout, - new_layout: alloc::Layout, - ) -> Result, alloc::AllocError> { - // SAFETY: Caller upholds Allocator contract. - unsafe { - bump_scope::alloc::Allocator::grow_zeroed(&*self.0, ptr, old_layout, new_layout) - .map_err(|_err| alloc::AllocError) - } +const _: () = { + const fn assert_send() {} + const fn assert_sync() {} + + assert_send::(); + assert_sync::(); + assert_send::>(); + + assert_send::(); + assert_sync::(); + assert_send::>(); +}; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn scratch_pool_reuses_allocators() { + let pool = ScratchPool::new(); + + let guard = pool.get(); + let mut vec: Vec = Vec::new_in(&*guard); + vec.push(42); + assert_eq!(vec[0], 42); + drop(vec); + drop(guard); + + // Second get reuses the allocator. + let guard = pool.get(); + let mut vec: Vec = Vec::new_in(&*guard); + vec.push(99); + assert_eq!(vec[0], 99); + drop(vec); + drop(guard); + + assert_eq!(pool.pool.lock().len(), 1); } - #[inline] - unsafe fn shrink( - &self, - ptr: ptr::NonNull, - old_layout: alloc::Layout, - new_layout: alloc::Layout, - ) -> Result, alloc::AllocError> { - // SAFETY: Caller upholds Allocator contract. - unsafe { - bump_scope::alloc::Allocator::shrink(&*self.0, ptr, old_layout, new_layout) - .map_err(|_err| alloc::AllocError) - } + #[test] + fn scratch_pool_grows_under_concurrent_demand() { + let pool = ScratchPool::new(); + + let guard1 = pool.get(); + let guard2 = pool.get(); + + let mut vec1: Vec = Vec::new_in(&*guard1); + let mut vec2: Vec = Vec::new_in(&*guard2); + vec1.push(1); + vec2.push(2); + assert_eq!(vec1[0], 1); + assert_eq!(vec2[0], 2); + + drop(vec1); + drop(vec2); + drop(guard1); + drop(guard2); + + assert_eq!(pool.pool.lock().len(), 2); + } + + #[test] + fn scratch_pool_bounded_drops_excess() { + let pool = ScratchPool::bounded(1); + + let guard1 = pool.get(); + let guard2 = pool.get(); + drop(guard1); + drop(guard2); + + assert_eq!(pool.pool.lock().len(), 1); + } + + #[test] + fn heap_pool_reuses_heaps() { + let pool = HeapPool::new(); + + // First get: creates a new heap. + let guard = pool.get(); + let sym1 = guard.intern_symbol("hello"); + assert_eq!(sym1.as_str(), "hello"); + drop(guard); + + // Second get: should reuse the heap (pool has one to hand back). + // The returned heap was reset, so the runtime symbol is gone, + // but interning the same string still works (gets a fresh allocation). + let guard = pool.get(); + let sym2 = guard.intern_symbol("hello"); + assert_eq!(sym2.as_str(), "hello"); + drop(guard); + + // Pool should have exactly one heap in it now. + assert_eq!(pool.pool.lock().len(), 1); + } + + #[test] + fn heap_pool_bounded_drops_excess() { + let pool = HeapPool::bounded(1); + + // Borrow two heaps simultaneously. + let guard1 = pool.get(); + let guard2 = pool.get(); + guard1.intern_symbol("a"); + guard2.intern_symbol("b"); + + // Return both. Only one should be retained. + drop(guard1); + drop(guard2); + assert_eq!(pool.pool.lock().len(), 1); + + // A new get still works (reuses the one retained heap). + let guard = pool.get(); + assert_eq!(pool.pool.lock().len(), 0); + drop(guard); + assert_eq!(pool.pool.lock().len(), 1); + } + + #[test] + fn heap_pool_grows_under_concurrent_demand() { + let pool = HeapPool::new(); + + // Borrow two heaps simultaneously. + let guard1 = pool.get(); + let guard2 = pool.get(); + + // Both are independent: interning in one doesn't affect the other. + let sym1 = guard1.intern_symbol("aaa"); + let sym2 = guard2.intern_symbol("bbb"); + assert_eq!(sym1.as_str(), "aaa"); + assert_eq!(sym2.as_str(), "bbb"); + + drop(guard1); + drop(guard2); + + // Both heaps returned to pool. + assert_eq!(pool.pool.lock().len(), 2); } } diff --git a/libs/@local/hashql/core/src/heap/scratch.rs b/libs/@local/hashql/core/src/heap/scratch.rs index ce87a754e75..53f69d2aafd 100644 --- a/libs/@local/hashql/core/src/heap/scratch.rs +++ b/libs/@local/hashql/core/src/heap/scratch.rs @@ -33,16 +33,16 @@ pub struct Scratch { impl Scratch { /// Creates a new scratch allocator. - #[must_use] #[inline] + #[must_use] pub fn new() -> Self { Self { inner: Allocator::new(), } } - #[must_use] #[inline] + #[must_use] pub fn with_capacity(capacity: usize) -> Self { Self { inner: Allocator::with_capacity(capacity), @@ -51,6 +51,7 @@ impl Scratch { } impl Default for Scratch { + #[inline] fn default() -> Self { Self::new() } diff --git a/libs/@local/hashql/core/src/id/bit_vec/mod.rs b/libs/@local/hashql/core/src/id/bit_vec/mod.rs index bbc332ca59d..030dc3fe579 100644 --- a/libs/@local/hashql/core/src/id/bit_vec/mod.rs +++ b/libs/@local/hashql/core/src/id/bit_vec/mod.rs @@ -108,6 +108,7 @@ pub struct DenseBitSet { impl DenseBitSet { /// Gets the domain size. + #[inline] #[must_use] pub const fn domain_size(&self) -> usize { self.domain_size @@ -581,6 +582,7 @@ enum Chunk { const _: () = assert!(size_of::() == 16); impl ChunkedBitSet { + #[inline] #[must_use] pub const fn domain_size(&self) -> usize { self.domain_size diff --git a/libs/@local/hashql/core/src/module/namespace.rs b/libs/@local/hashql/core/src/module/namespace.rs index f2570859aad..607c489b555 100644 --- a/libs/@local/hashql/core/src/module/namespace.rs +++ b/libs/@local/hashql/core/src/module/namespace.rs @@ -60,6 +60,7 @@ impl<'env, 'heap> ModuleNamespace<'env, 'heap> { } } + #[inline] #[must_use] pub const fn registry(&self) -> &'env ModuleRegistry<'heap> { self.registry diff --git a/libs/@local/hashql/core/src/pretty/formatter.rs b/libs/@local/hashql/core/src/pretty/formatter.rs index dfaf182b4e5..621658edb3a 100644 --- a/libs/@local/hashql/core/src/pretty/formatter.rs +++ b/libs/@local/hashql/core/src/pretty/formatter.rs @@ -35,6 +35,7 @@ impl FormatterOptions { } impl Default for FormatterOptions { + #[inline] fn default() -> Self { Self { indent: 4 } } diff --git a/libs/@local/hashql/core/src/span/mod.rs b/libs/@local/hashql/core/src/span/mod.rs index 875140f8d5e..3cd0a8b39f6 100644 --- a/libs/@local/hashql/core/src/span/mod.rs +++ b/libs/@local/hashql/core/src/span/mod.rs @@ -275,6 +275,13 @@ pub use self::table::SpanTable; /// Some(TextRange::new(0.into(), 10.into())) /// ); /// ``` +#[cfg_attr( + feature = "serde", + expect( + clippy::unsafe_derive_deserialize, + reason = "id() is safe to use with serde" + ) +)] #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct SpanId(u32); @@ -311,8 +318,10 @@ impl SpanId { SourceId::new_unchecked(self.0 >> Self::SOURCE_OFFSET) } - pub(crate) const fn id(self) -> u32 { - self.0 & Self::ID_MASK + #[expect(unsafe_code)] + pub(crate) const fn id(self) -> LocalSpanId { + // SAFETY: The mask ensures that the value does not exceed the maximum span index. + unsafe { LocalSpanId::new_unchecked(self.0 & Self::ID_MASK) } } } @@ -322,6 +331,11 @@ impl Display for SpanId { } } +crate::id::newtype! { + #[id(crate = crate)] + pub(crate) struct LocalSpanId(u32 is 0..=SpanId::MAX_ID) +} + /// Determines how multiple ancestor spans should be combined during span resolution. /// /// When a span has multiple ancestors (parent spans), [`SpanResolutionMode`] controls diff --git a/libs/@local/hashql/core/src/span/table.rs b/libs/@local/hashql/core/src/span/table.rs index 100e53e3678..a98f76ffad2 100644 --- a/libs/@local/hashql/core/src/span/table.rs +++ b/libs/@local/hashql/core/src/span/table.rs @@ -4,6 +4,7 @@ use hashql_diagnostics::source::{SourceId, SourceSpan}; use text_size::{TextRange, TextSize}; use super::{Span, SpanAncestors, SpanAncestorsMut, SpanId, SpanResolutionMode}; +use crate::id::{Id as _, bit_vec::DenseBitSet}; #[derive(Debug)] struct SpanEntry { @@ -244,7 +245,7 @@ impl SpanTable { return false; } - let index = span.id() as usize; + let index = span.id().as_usize(); let Some(element) = self.spans.get_mut(index) else { return false; @@ -265,8 +266,7 @@ impl SpanTable { return None; } - let index = span.id() as usize; - + let index = span.id().as_usize(); self.spans.get(index) } @@ -487,34 +487,14 @@ impl SpanTable { self.absolute_impl(span, 0) } - /// Computes all ancestor spans for a given span in linearized order. - /// - /// Returns a flattened list of all spans that are ancestors of the given span, - /// traversing the entire ancestor tree and removing duplicates. The result - /// includes both direct ancestors and ancestors-of-ancestors. - /// - /// # Returns - /// - /// A [`Vec`] containing all unique ancestor spans. The order is - /// determined by the traversal algorithm and may not be hierarchical. + /// Returns an iterator over the given span and all of its ancestors. /// - /// # Algorithm - /// - /// Uses a depth-first search with a stack to traverse the ancestor tree: - /// 1. Start with the given span - /// 2. For each span, add its direct ancestors to the result and stack - /// 3. Continue until all ancestors are processed - /// 4. Duplicates are automatically filtered during traversal - /// - /// # Performance - /// - /// Time complexity is O(n) where n is the total number of unique ancestors. - /// Space complexity is also O(n) for the result vector and traversal stack. + /// The first element yielded is `span` itself, followed by its transitive + /// ancestors. Each [`SpanId`] appears at most once. Spans that cannot be + /// resolved in this table are skipped. /// /// # Examples /// - /// Simple hierarchy: - /// /// ```rust /// use hashql_core::span::{SpanAncestors, SpanTable, TextRange}; /// use hashql_diagnostics::source::SourceId; @@ -541,14 +521,15 @@ impl SpanTable { /// }; /// let child_id = table.insert(child, SpanAncestors::union(&[parent_id])); /// - /// let ancestors = table.ancestors(child_id); - /// // Result contains both parent_id and grandparent_id - /// assert_eq!(ancestors.len(), 2); + /// let ancestors: Vec<_> = table.ancestors(child_id).into_iter().collect(); + /// // First element is the span itself, followed by its ancestors + /// assert_eq!(ancestors[0], child_id); /// assert!(ancestors.contains(&parent_id)); /// assert!(ancestors.contains(&grandparent_id)); + /// assert_eq!(ancestors.len(), 3); /// ``` /// - /// Complex hierarchy with multiple branches: + /// Diamond-shaped hierarchy with shared ancestors: /// /// ```rust /// use hashql_core::span::{SpanAncestors, SpanTable, TextRange}; @@ -560,7 +541,6 @@ impl SpanTable { /// # } /// let mut table = SpanTable::new(SourceId::new_unchecked(0)); /// - /// // Create diamond-shaped hierarchy /// let root = MySpan { /// range: TextRange::new(0.into(), 100.into()), /// }; @@ -581,36 +561,32 @@ impl SpanTable { /// }; /// let merge_id = table.insert(merge, SpanAncestors::union(&[left_id, right_id])); /// - /// let ancestors = table.ancestors(merge_id); - /// // Result contains all unique ancestors: left_id, right_id, root_id - /// assert_eq!(ancestors.len(), 3); + /// let ancestors: Vec<_> = table.ancestors(merge_id).into_iter().collect(); + /// // Contains merge_id, left_id, right_id, and root_id (deduplicated) + /// assert_eq!(ancestors.len(), 4); + /// assert_eq!(ancestors[0], merge_id); /// ``` #[must_use] - pub fn ancestors(&self, span: SpanId) -> Vec { - let mut ancestors = Vec::new(); + pub fn ancestors(&self, span: SpanId) -> impl IntoIterator { let mut stack = vec![span]; + let mut visited = DenseBitSet::new_empty(self.spans.len()); - while let Some(current) = stack.pop() { - let Some(entry) = self.get_entry(current) else { - continue; - }; - - let direct_ancestors = &self.ancestors[entry.ancestors.clone()]; - - if direct_ancestors.is_empty() { - continue; - } + core::iter::from_fn(move || { + loop { + let span = stack.pop()?; + let Some(entry) = self.get_entry(span) else { + continue; + }; - for &ancestor in direct_ancestors { - if ancestors.contains(&ancestor) { + if !visited.insert(span.id()) { continue; } - ancestors.push(ancestor); - stack.push(ancestor); - } - } + let ancestors = &self.ancestors[entry.ancestors.clone()]; + stack.extend(ancestors); - ancestors + return Some(span); + } + }) } } diff --git a/libs/@local/hashql/diagnostics/src/issues.rs b/libs/@local/hashql/diagnostics/src/issues.rs index 065f8ded9c4..da862a1e2a2 100644 --- a/libs/@local/hashql/diagnostics/src/issues.rs +++ b/libs/@local/hashql/diagnostics/src/issues.rs @@ -145,6 +145,41 @@ impl DiagnosticIssues { } } + /// Transforms the span type of all diagnostics in the collection. + /// + /// Applies the function to every span in labels, suggestions, and patches. + /// This is useful when converting between span representations, such as + /// resolving relative spans to absolute positions for rendering. + /// + /// # Examples + /// + /// ``` + /// use hashql_diagnostics::{Diagnostic, DiagnosticIssues, Label, Severity}; + /// # use hashql_diagnostics::category::TerminalDiagnosticCategory; + /// # const CATEGORY: TerminalDiagnosticCategory = TerminalDiagnosticCategory { + /// # id: "example", name: "Example" + /// # }; + /// + /// let mut issues = DiagnosticIssues::new(); + /// issues.push( + /// Diagnostic::new(CATEGORY, Severity::Warning).primary(Label::new(10..15, "warning here")), + /// ); + /// + /// // Convert Range spans to string descriptions + /// let converted = issues.map_spans(|range| format!("{}..{}", range.start, range.end)); + /// assert_eq!(converted.len(), 1); + /// ``` + pub fn map_spans(self, mut func: impl FnMut(S) -> S2) -> DiagnosticIssues { + DiagnosticIssues { + diagnostics: self + .diagnostics + .into_iter() + .map(|diagnostic| diagnostic.map_spans(&mut func)) + .collect(), + critical: self.critical, + } + } + /// Converts to a collection with type-erased categories. /// /// When combining diagnostics from different compilation phases that use @@ -231,6 +266,7 @@ impl DiagnosticIssues { /// ); /// assert_eq!(issues.critical(), 1); // Warnings are not critical /// ``` + #[inline] #[must_use] pub const fn critical(&self) -> usize { self.critical @@ -804,12 +840,55 @@ impl IntoIterator for DiagnosticIssues { } } +impl From>> for DiagnosticIssues +where + K: SeverityKind, +{ + fn from(diagnostics: Vec>) -> Self { + let critical = diagnostics + .iter() + .filter(|diagnostic| diagnostic.severity.is_critical()) + .count(); + + Self { + diagnostics, + critical, + } + } +} + impl Default for DiagnosticIssues { fn default() -> Self { Self::new() } } +#[cfg(feature = "serde")] +impl serde::Serialize for DiagnosticIssues +where + C: serde::Serialize, + S: serde::Serialize, + K: serde::Serialize, +{ + fn serialize(&self, serializer: Ser) -> Result { + self.diagnostics.serialize(serializer) + } +} + +#[cfg(feature = "serde")] +impl<'de, C, S, K> serde::Deserialize<'de> for DiagnosticIssues +where + C: serde::Deserialize<'de>, + S: serde::Deserialize<'de>, + K: serde::Deserialize<'de> + SeverityKind, +{ + fn deserialize>(deserializer: De) -> Result { + let inner = Vec::deserialize(deserializer)?; + + Ok(Self::from(inner)) + } +} + /// A trait for collecting diagnostic information from fallible operations. /// /// `DiagnosticSink` provides a way to process [`Result`] values that may contain diff --git a/libs/@local/hashql/diagnostics/src/lib.rs b/libs/@local/hashql/diagnostics/src/lib.rs index 83e416cf5f3..28f03988520 100644 --- a/libs/@local/hashql/diagnostics/src/lib.rs +++ b/libs/@local/hashql/diagnostics/src/lib.rs @@ -36,5 +36,5 @@ pub use self::{ issues::{DiagnosticIssues, DiagnosticSink}, severity::Severity, source::{Source, Sources}, - status::{Failure, Status, StatusExt, Success}, + status::{Failure, IntoStatus, Status, StatusExt, Success}, }; diff --git a/libs/@local/hashql/diagnostics/src/source/span.rs b/libs/@local/hashql/diagnostics/src/source/span.rs index 02f1a0b955d..b3a5a4b7870 100644 --- a/libs/@local/hashql/diagnostics/src/source/span.rs +++ b/libs/@local/hashql/diagnostics/src/source/span.rs @@ -166,12 +166,14 @@ impl SourceSpan { } /// Returns the source file identifier for this span. + #[inline] #[must_use] pub const fn source(&self) -> SourceId { self.source } /// Returns the text range within the source file. + #[inline] #[must_use] pub const fn range(&self) -> TextRange { self.range diff --git a/libs/@local/hashql/diagnostics/src/status.rs b/libs/@local/hashql/diagnostics/src/status.rs index 29f9d3d15e8..f2ad9092801 100644 --- a/libs/@local/hashql/diagnostics/src/status.rs +++ b/libs/@local/hashql/diagnostics/src/status.rs @@ -40,16 +40,14 @@ use crate::{ /// assert_eq!(result.advisories.critical(), 0); /// ``` #[derive(Debug)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Success { pub value: T, pub advisories: DiagnosticIssues, } impl Success { - /// Transforms the value in the success result while preserving diagnostics. - /// - /// This method applies a function to the success value, creating a new [`Success`] with - /// the transformed value and the same advisory diagnostics. + /// Transforms the value while preserving advisory diagnostics. /// /// # Examples /// @@ -168,6 +166,7 @@ impl Success { /// assert_eq!(error.secondary.len(), 1); /// ``` #[derive(Debug)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct Failure { // boxed to reduce memory footprint pub primary: Box>, @@ -175,6 +174,27 @@ pub struct Failure { } impl Failure { + /// Creates a [`Failure`] from a primary critical diagnostic. + /// + /// The resulting failure has no secondary diagnostics. + /// + /// # Examples + /// + /// ``` + /// use hashql_diagnostics::{Diagnostic, Failure, Label, Severity}; + /// # use hashql_diagnostics::category::TerminalDiagnosticCategory; + /// # const CATEGORY: TerminalDiagnosticCategory = TerminalDiagnosticCategory { + /// # id: "error", name: "Error" + /// # }; + /// + /// let critical = Diagnostic::new(CATEGORY, Severity::Error) + /// .primary(Label::new(10..15, "error here")) + /// .specialize() + /// .expect_err("should be critical"); + /// + /// let failure = Failure::new(critical); + /// assert!(failure.secondary.is_empty()); + /// ``` pub fn new(primary: Diagnostic) -> Self { Self { primary: Box::new(primary), @@ -503,6 +523,34 @@ pub trait StatusExt: sealed::Sealed { #[expect(clippy::missing_errors_doc, reason = "This is a trait on Result")] fn map_category(self, func: impl FnMut(C) -> C2) -> Status; + /// Transforms the span type of all diagnostics in the status. + /// + /// Applies the function to every span in labels, suggestions, and patches + /// across both success advisories and failure diagnostics. This is useful + /// when converting between span representations at compilation phase + /// boundaries, such as resolving relative spans to absolute positions. + /// + /// # Examples + /// + /// ``` + /// use hashql_diagnostics::{Diagnostic, Label, Severity, Status, StatusExt as _}; + /// # use hashql_diagnostics::category::TerminalDiagnosticCategory; + /// # const CATEGORY: TerminalDiagnosticCategory = TerminalDiagnosticCategory { + /// # id: "example", name: "Example" + /// # }; + /// + /// let mut result = Status::success(42); + /// result.push_diagnostic( + /// Diagnostic::new(CATEGORY, Severity::Warning).primary(Label::new(10..15, "warning here")), + /// ); + /// + /// // Convert Range spans to (start, end) tuples + /// let converted = result.map_spans(|range| (range.start, range.end)); + /// assert!(converted.is_ok()); + /// ``` + #[expect(clippy::missing_errors_doc, reason = "This is a trait on Result")] + fn map_spans(self, func: impl FnMut(S) -> S2) -> Status; + /// Converts to a result with type-erased diagnostic categories. /// /// When combining diagnostics from different compilation phases that use different category @@ -809,6 +857,19 @@ impl StatusExt for Status { } } + fn map_spans(self, mut func: impl FnMut(S) -> S2) -> Status { + match self { + Ok(Success { value, advisories }) => Ok(Success { + value, + advisories: advisories.map_spans(func), + }), + Err(Failure { primary, secondary }) => Err(Failure { + primary: Box::new(primary.map_spans(&mut func)), + secondary: secondary.map_spans(func), + }), + } + } + fn boxed<'category>(self) -> Self::Boxed<'category> where C: DiagnosticCategory + 'category, @@ -925,3 +986,79 @@ impl StatusExt for Status { } } } + +/// Conversion into a [`Status`] from result types that carry either a value or +/// a critical diagnostic, but have no accumulated advisories or secondary +/// diagnostics. +/// +/// This is useful at boundaries where a fallible operation returns +/// `Result>` and the caller needs to feed that +/// into a [`Status`]-based pipeline. +pub trait IntoStatus { + /// Converts into a [`Status`]. + /// + /// The resulting status has no advisories on success and no secondary + /// diagnostics on failure. + /// + /// # Errors + /// + /// Returns [`Failure`] when the source value represents a critical + /// diagnostic. + /// + /// # Examples + /// + /// Successful conversion: + /// + /// ``` + /// use hashql_diagnostics::{IntoStatus, Status, StatusExt as _}; + /// + /// let result: Result = Ok(42); + /// let status: Status<_, (), ()> = result.into_status(); + /// + /// match status { + /// Ok(success) => { + /// assert_eq!(success.value, 42); + /// assert!(success.advisories.is_empty()); + /// } + /// Err(_) => panic!("should be successful"), + /// } + /// ``` + /// + /// Failed conversion: + /// + /// ``` + /// use hashql_diagnostics::{ + /// Diagnostic, IntoStatus, Label, Severity, Status, StatusExt as _, severity::SeverityKind, + /// }; + /// # use hashql_diagnostics::category::TerminalDiagnosticCategory; + /// # const CATEGORY: TerminalDiagnosticCategory = TerminalDiagnosticCategory { + /// # id: "example", name: "Example" + /// # }; + /// + /// let diagnostic = Diagnostic::new(CATEGORY, Severity::Error) + /// .primary(Label::new(10..15, "something went wrong")) + /// .specialize() + /// .expect_err("should be critical"); + /// + /// let result: Result = Err(diagnostic); + /// let status: Status = result.into_status(); + /// + /// match status { + /// Ok(_) => panic!("should be a failure"), + /// Err(failure) => { + /// assert!(failure.primary.severity.is_critical()); + /// assert!(failure.secondary.is_empty()); + /// } + /// } + /// ``` + fn into_status(self) -> Status; +} + +impl IntoStatus for Result> { + fn into_status(self) -> Status { + match self { + Ok(value) => Status::success(value), + Err(primary) => Status::failure(primary), + } + } +} diff --git a/libs/@local/hashql/syntax-jexpr/src/error.rs b/libs/@local/hashql/syntax-jexpr/src/error.rs index 068c09189ad..0647bf69d96 100644 --- a/libs/@local/hashql/syntax-jexpr/src/error.rs +++ b/libs/@local/hashql/syntax-jexpr/src/error.rs @@ -1,11 +1,15 @@ use alloc::borrow::Cow; use hashql_core::span::SpanId; -use hashql_diagnostics::{Diagnostic, category::DiagnosticCategory}; +use hashql_diagnostics::{ + Diagnostic, + category::DiagnosticCategory, + severity::{Critical, SeverityKind}, +}; use crate::parser::error::ParserDiagnosticCategory; -pub type JExprDiagnostic = Diagnostic; +pub type JExprDiagnostic = Diagnostic; #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum JExprDiagnosticCategory { @@ -29,7 +33,7 @@ impl DiagnosticCategory for JExprDiagnosticCategory { } /// Extension trait for changing diagnostic categories in results. -pub(crate) trait ResultExt { +pub(crate) trait ResultExt { type Ok; type DiagnosticCategory; type Span; @@ -38,10 +42,15 @@ pub(crate) trait ResultExt { fn change_category( self, category: impl FnOnce(Self::DiagnosticCategory) -> C, - ) -> Result>; + ) -> Result> + where + K: SeverityKind; } -impl ResultExt for Result> { +impl ResultExt for Result> +where + K: SeverityKind, +{ type DiagnosticCategory = C; type Ok = T; type Span = S; @@ -49,7 +58,7 @@ impl ResultExt for Result> { fn change_category( self, category: impl FnOnce(Self::DiagnosticCategory) -> D, - ) -> Result> { + ) -> Result> { self.map_err(|diagnostic| diagnostic.map_category(category)) } } diff --git a/libs/@local/hashql/syntax-jexpr/src/lexer/error.rs b/libs/@local/hashql/syntax-jexpr/src/lexer/error.rs index 365fd51e802..49d6eece405 100644 --- a/libs/@local/hashql/syntax-jexpr/src/lexer/error.rs +++ b/libs/@local/hashql/syntax-jexpr/src/lexer/error.rs @@ -5,7 +5,7 @@ use hashql_diagnostics::{ Diagnostic, Label, category::{DiagnosticCategory, TerminalDiagnosticCategory}, diagnostic::Message, - severity::Severity, + severity::Critical, }; use text_size::TextRange; @@ -16,7 +16,7 @@ use super::{ }; use crate::lexer::syntax_kind_set::Conjunction; -pub(crate) type LexerDiagnostic = Diagnostic; +pub(crate) type LexerDiagnostic = Diagnostic; const INVALID_STRING: TerminalDiagnosticCategory = TerminalDiagnosticCategory { id: "invalid-string", @@ -85,7 +85,7 @@ const CONTROL_CHAR_HELP: &str = pub(crate) fn from_hifijson_str_error( error: &hifijson::str::Error, span: SpanId, -) -> Diagnostic { +) -> LexerDiagnostic { let (message, help) = match error { hifijson::str::Error::Control => ( "Control character not allowed", @@ -102,7 +102,7 @@ pub(crate) fn from_hifijson_str_error( ), }; - let mut diagnostic = Diagnostic::new(LexerDiagnosticCategory::InvalidString, Severity::Error) + let mut diagnostic = Diagnostic::new(LexerDiagnosticCategory::InvalidString, Critical::ERROR) .primary(Label::new(span, message)); if let Some(help) = help { @@ -123,7 +123,7 @@ pub(crate) fn unexpected_eof(span: SpanId, expected: SyntaxKindSet) -> LexerDiag ) }; - let mut diagnostic = Diagnostic::new(LexerDiagnosticCategory::UnexpectedEof, Severity::Error) + let mut diagnostic = Diagnostic::new(LexerDiagnosticCategory::UnexpectedEof, Critical::ERROR) .primary(Label::new(span, label)); // Provide specific help based on what was expected @@ -165,7 +165,7 @@ pub(crate) fn unexpected_token( ) }; - let mut diagnostic = Diagnostic::new(LexerDiagnosticCategory::UnexpectedToken, Severity::Error) + let mut diagnostic = Diagnostic::new(LexerDiagnosticCategory::UnexpectedToken, Critical::ERROR) .primary(Label::new(span, label)); // Provide specific help based on common syntax errors @@ -197,7 +197,7 @@ const INVALID_NUMBER_HELP: &str = "JSON numbers must contain digits and follow t `[-]digits[.digits][(e|E)[+|-]digits]`"; pub(crate) fn from_number_error(error: ParseNumberErrorKind, span: SpanId) -> LexerDiagnostic { - let mut diagnostic = Diagnostic::new(LexerDiagnosticCategory::InvalidNumber, Severity::Error) + let mut diagnostic = Diagnostic::new(LexerDiagnosticCategory::InvalidNumber, Critical::ERROR) .primary(Label::new(span, error.to_string())); diagnostic.add_message(Message::help(INVALID_NUMBER_HELP)); @@ -210,7 +210,7 @@ const UNRECOGNIZED_CHAR_HELP: &str = "J-Expr only supports standard JSON syntax. pub(crate) fn from_unrecognized_character_error(span: SpanId) -> LexerDiagnostic { let mut diagnostic = - Diagnostic::new(LexerDiagnosticCategory::InvalidCharacter, Severity::Error) + Diagnostic::new(LexerDiagnosticCategory::InvalidCharacter, Critical::ERROR) .primary(Label::new(span, "Unrecognized character")); diagnostic.add_message(Message::help(UNRECOGNIZED_CHAR_HELP)); @@ -222,7 +222,7 @@ const INVALID_UTF8_HELP: &str = "J-Expr requires valid UTF-8 encoded input. Chec characters or ensure your source is properly encoded as UTF-8."; pub(crate) fn from_invalid_utf8_error(span: SpanId) -> LexerDiagnostic { - let mut diagnostic = Diagnostic::new(LexerDiagnosticCategory::InvalidUtf8, Severity::Error) + let mut diagnostic = Diagnostic::new(LexerDiagnosticCategory::InvalidUtf8, Critical::ERROR) .primary(Label::new(span, "Invalid UTF-8 byte sequence")); diagnostic.add_message(Message::help(INVALID_UTF8_HELP)); diff --git a/libs/@local/hashql/syntax-jexpr/src/lexer/mod.rs b/libs/@local/hashql/syntax-jexpr/src/lexer/mod.rs index a2b4d0a4851..b664b6a9449 100644 --- a/libs/@local/hashql/syntax-jexpr/src/lexer/mod.rs +++ b/libs/@local/hashql/syntax-jexpr/src/lexer/mod.rs @@ -1,12 +1,11 @@ -use hashql_core::span::{SpanAncestors, SpanId, SpanTable}; -use hashql_diagnostics::Diagnostic; +use hashql_core::span::{SpanAncestors, SpanTable}; use logos::SpannedIter; use text_size::{TextRange, TextSize}; pub(crate) use self::number::Number; use self::{ error::{ - LexerDiagnosticCategory, LexerError, from_hifijson_str_error, from_invalid_utf8_error, + LexerDiagnostic, LexerError, from_hifijson_str_error, from_invalid_utf8_error, from_number_error, from_unrecognized_character_error, }, number::ParseNumberError, @@ -63,7 +62,7 @@ impl<'source> Lexer<'source> { pub(crate) fn advance( &mut self, context: &mut LexerContext, - ) -> Option, Diagnostic>> { + ) -> Option, LexerDiagnostic>> { let (kind, span) = self.inner.next()?; let span = { @@ -131,18 +130,22 @@ impl<'source> Lexer<'source> { #[cfg(test)] mod test { #![expect(clippy::non_ascii_literal)] - use hashql_core::span::{SpanId, SpanTable}; - use hashql_diagnostics::{Diagnostic, source::SourceId}; + use hashql_core::span::SpanTable; + use hashql_diagnostics::source::SourceId; use insta::{assert_snapshot, with_settings}; use text_size::TextRange; - use super::{Lexer, LexerContext, error::LexerDiagnosticCategory, token::Token}; + use super::{ + Lexer, LexerContext, + error::{LexerDiagnostic, LexerDiagnosticCategory}, + token::Token, + }; use crate::{span::Span, test::render_diagnostic}; fn parse<'source>( source: &'source str, spans: &mut SpanTable, - ) -> Result>, Diagnostic> { + ) -> Result>, LexerDiagnostic> { let mut lexer = Lexer::new(source.as_bytes()); let mut tokens = Vec::new(); diff --git a/libs/@local/hashql/syntax-jexpr/src/lexer/syntax_kind_set.rs b/libs/@local/hashql/syntax-jexpr/src/lexer/syntax_kind_set.rs index 405991a2acd..d0a313a2915 100644 --- a/libs/@local/hashql/syntax-jexpr/src/lexer/syntax_kind_set.rs +++ b/libs/@local/hashql/syntax-jexpr/src/lexer/syntax_kind_set.rs @@ -186,6 +186,7 @@ impl FromIterator for SyntaxKindSet { } impl From for SyntaxKindSet { + #[inline] fn from(kind: SyntaxKind) -> Self { Self::from_kind(kind) } @@ -225,6 +226,7 @@ impl BitXor for SyntaxKindSet { } impl Default for SyntaxKindSet { + #[inline] fn default() -> Self { Self::EMPTY } diff --git a/libs/@local/hashql/syntax-jexpr/src/parser/array/error.rs b/libs/@local/hashql/syntax-jexpr/src/parser/array/error.rs index 275c5a19a39..8cab0e3855d 100644 --- a/libs/@local/hashql/syntax-jexpr/src/parser/array/error.rs +++ b/libs/@local/hashql/syntax-jexpr/src/parser/array/error.rs @@ -5,13 +5,13 @@ use hashql_diagnostics::{ Diagnostic, Label, category::{DiagnosticCategory, TerminalDiagnosticCategory}, diagnostic::Message, - severity::Severity, + severity::Critical, }; use winnow::error::ContextError; use crate::{lexer::error::LexerDiagnosticCategory, span::Span}; -pub(crate) type ArrayDiagnostic = Diagnostic; +pub(crate) type ArrayDiagnostic = Diagnostic; const LEADING_COMMA: TerminalDiagnosticCategory = TerminalDiagnosticCategory { id: "leading-comma", @@ -118,6 +118,7 @@ impl DiagnosticCategory for ArrayDiagnosticCategory { } impl From for ArrayDiagnosticCategory { + #[inline] fn from(value: LexerDiagnosticCategory) -> Self { Self::Lexer(value) } @@ -131,7 +132,7 @@ const EMPTY_NOTE: &str = r##"Valid examples: "##; pub(crate) fn empty(span: SpanId) -> ArrayDiagnostic { - let mut diagnostic = Diagnostic::new(ArrayDiagnosticCategory::Empty, Severity::Error) + let mut diagnostic = Diagnostic::new(ArrayDiagnosticCategory::Empty, Critical::ERROR) .primary(Label::new(span, "Empty array not allowed")); diagnostic.add_message(Message::help(EMPTY_HELP)); @@ -146,7 +147,7 @@ const TRAILING_COMMA_HELP: &str = "J-Expr does not support trailing commas in ar pub(crate) fn trailing_commas(spans: &[SpanId]) -> ArrayDiagnostic { let (&first, rest) = spans.split_first().expect("spans must be non-empty"); - let mut diagnostic = Diagnostic::new(ArrayDiagnosticCategory::TrailingComma, Severity::Error) + let mut diagnostic = Diagnostic::new(ArrayDiagnosticCategory::TrailingComma, Critical::ERROR) .primary(Label::new(first, "Remove this trailing comma")); for &span in rest { @@ -166,7 +167,7 @@ const LEADING_COMMA_HELP: &str = pub(crate) fn leading_commas(spans: &[SpanId]) -> ArrayDiagnostic { let (&first, rest) = spans.split_first().expect("spans must be non-empty"); - let mut diagnostic = Diagnostic::new(ArrayDiagnosticCategory::LeadingComma, Severity::Error) + let mut diagnostic = Diagnostic::new(ArrayDiagnosticCategory::LeadingComma, Critical::ERROR) .primary(Label::new(first, "Remove this leading comma")); for &span in rest { @@ -187,7 +188,7 @@ pub(crate) fn consecutive_commas(spans: &[SpanId]) -> ArrayDiagnostic { let (&first, rest) = spans.split_first().expect("spans must be non-empty"); let mut diagnostic = - Diagnostic::new(ArrayDiagnosticCategory::ConsecutiveComma, Severity::Error) + Diagnostic::new(ArrayDiagnosticCategory::ConsecutiveComma, Critical::ERROR) .primary(Label::new(first, "Remove this extra comma")); for &span in rest { @@ -214,7 +215,7 @@ pub(crate) fn labeled_argument_missing_prefix( ) -> ArrayDiagnostic { let mut diagnostic = Diagnostic::new( ArrayDiagnosticCategory::LabeledArgumentMissingPrefix, - Severity::Error, + Critical::ERROR, ) .primary(Label::new(span, "Missing ':' prefix")); @@ -236,7 +237,7 @@ pub(crate) fn labeled_arguments_length_mismatch( ) -> ArrayDiagnostic { let diagnostic = Diagnostic::new( ArrayDiagnosticCategory::LabeledArgumentLengthMismatch, - Severity::Error, + Critical::ERROR, ); let mut diagnostic = if count == 0 { @@ -283,7 +284,7 @@ pub(crate) fn labeled_argument_invalid_identifier( ) -> ArrayDiagnostic { let mut diagnostic = Diagnostic::new( ArrayDiagnosticCategory::LabeledArgumentInvalidIdentifier, - Severity::Error, + Critical::ERROR, ) .primary(Label::new(label_span, "Invalid labeled argument name")); diff --git a/libs/@local/hashql/syntax-jexpr/src/parser/array/visit.rs b/libs/@local/hashql/syntax-jexpr/src/parser/array/visit.rs index 04bf4d0553f..3c224a13c37 100644 --- a/libs/@local/hashql/syntax-jexpr/src/parser/array/visit.rs +++ b/libs/@local/hashql/syntax-jexpr/src/parser/array/visit.rs @@ -1,5 +1,5 @@ use hashql_core::span::SpanId; -use hashql_diagnostics::Diagnostic; +use hashql_diagnostics::{Diagnostic, severity::Critical}; use text_size::TextRange; use crate::{ @@ -38,8 +38,8 @@ pub(crate) fn visit_array<'arena, 'source, 'spans, C>( token: Token<'source>, mut on_item: impl FnMut( &mut ParserState<'arena, 'source, 'spans>, - ) -> Result<(), Diagnostic>, -) -> Result> + ) -> Result<(), Diagnostic>, +) -> Result> where C: From, { diff --git a/libs/@local/hashql/syntax-jexpr/src/parser/complex/mod.rs b/libs/@local/hashql/syntax-jexpr/src/parser/complex/mod.rs index eb3f1e93bf2..0dca8159f52 100644 --- a/libs/@local/hashql/syntax-jexpr/src/parser/complex/mod.rs +++ b/libs/@local/hashql/syntax-jexpr/src/parser/complex/mod.rs @@ -1,5 +1,5 @@ use hashql_core::span::SpanId; -use hashql_diagnostics::Diagnostic; +use hashql_diagnostics::{Diagnostic, severity::Critical}; use crate::{ ParserState, @@ -21,8 +21,8 @@ pub(crate) fn verify_no_repeat( &mut ParserState<'_, '_, '_>, Vec, VerifyState, - ) -> Diagnostic, -) -> Result<(), Diagnostic> + ) -> Diagnostic, +) -> Result<(), Diagnostic> where C: From, { diff --git a/libs/@local/hashql/syntax-jexpr/src/parser/error.rs b/libs/@local/hashql/syntax-jexpr/src/parser/error.rs index f3ead4f6158..43f4041fc9d 100644 --- a/libs/@local/hashql/syntax-jexpr/src/parser/error.rs +++ b/libs/@local/hashql/syntax-jexpr/src/parser/error.rs @@ -5,7 +5,7 @@ use hashql_diagnostics::{ Diagnostic, Label, category::{DiagnosticCategory, TerminalDiagnosticCategory}, diagnostic::Message, - severity::Severity, + severity::Critical, }; use super::{ @@ -14,7 +14,7 @@ use super::{ }; use crate::lexer::error::LexerDiagnosticCategory; -pub(crate) type ParserDiagnostic = Diagnostic; +pub(crate) type ParserDiagnostic = Diagnostic; const EXPECTED_LANGUAGE_ITEM: TerminalDiagnosticCategory = TerminalDiagnosticCategory { id: "unexpected-token", @@ -76,24 +76,28 @@ impl DiagnosticCategory for ParserDiagnosticCategory { } impl From for ParserDiagnosticCategory { + #[inline] fn from(value: LexerDiagnosticCategory) -> Self { Self::Lexer(value) } } impl From for ParserDiagnosticCategory { + #[inline] fn from(value: StringDiagnosticCategory) -> Self { Self::String(value) } } impl From for ParserDiagnosticCategory { + #[inline] fn from(value: ArrayDiagnosticCategory) -> Self { Self::Array(value) } } impl From for ParserDiagnosticCategory { + #[inline] fn from(value: ObjectDiagnosticCategory) -> Self { Self::Object(value) } @@ -103,7 +107,7 @@ const EXPECTED_EOF_HELP: &str = "Remove this token or check for missing delimiters in the preceding expression"; pub(crate) fn expected_eof(span: SpanId) -> ParserDiagnostic { - let mut diagnostic = Diagnostic::new(ParserDiagnosticCategory::ExpectedEof, Severity::Error) + let mut diagnostic = Diagnostic::new(ParserDiagnosticCategory::ExpectedEof, Critical::ERROR) .primary(Label::new(span, "Extra content after expression")); diagnostic.add_message(Message::help(EXPECTED_EOF_HELP)); diff --git a/libs/@local/hashql/syntax-jexpr/src/parser/object/error.rs b/libs/@local/hashql/syntax-jexpr/src/parser/object/error.rs index 60b6e7f3ad3..8b9174b6299 100644 --- a/libs/@local/hashql/syntax-jexpr/src/parser/object/error.rs +++ b/libs/@local/hashql/syntax-jexpr/src/parser/object/error.rs @@ -5,7 +5,7 @@ use hashql_diagnostics::{ Diagnostic, Label, category::{DiagnosticCategory, TerminalDiagnosticCategory}, diagnostic::Message, - severity::Severity, + severity::Critical, }; use winnow::error::ContextError; @@ -14,7 +14,7 @@ use crate::{ span::Span, }; -pub(crate) type ObjectDiagnostic = Diagnostic; +pub(crate) type ObjectDiagnostic = Diagnostic; const LEADING_COMMA: TerminalDiagnosticCategory = TerminalDiagnosticCategory { id: "leading-comma", @@ -230,6 +230,7 @@ impl DiagnosticCategory for ObjectDiagnosticCategory { } impl From for ObjectDiagnosticCategory { + #[inline] fn from(value: LexerDiagnosticCategory) -> Self { Self::Lexer(value) } @@ -249,7 +250,7 @@ Empty objects don't have semantic meaning in J-Expr. "##; pub(crate) fn empty(span: SpanId) -> ObjectDiagnostic { - let mut diagnostic = Diagnostic::new(ObjectDiagnosticCategory::Empty, Severity::Error) + let mut diagnostic = Diagnostic::new(ObjectDiagnosticCategory::Empty, Critical::ERROR) .primary(Label::new(span, "Add required fields to this object")); diagnostic.add_message(Message::help(EMPTY_HELP)); @@ -263,7 +264,7 @@ const TRAILING_COMMA_HELP: &str = r#"J-Expr does not support trailing commas in pub(crate) fn trailing_commas(spans: &[SpanId]) -> ObjectDiagnostic { let (&first, rest) = spans.split_first().expect("Should have at least one span"); - let mut diagnostic = Diagnostic::new(ObjectDiagnosticCategory::TrailingComma, Severity::Error) + let mut diagnostic = Diagnostic::new(ObjectDiagnosticCategory::TrailingComma, Critical::ERROR) .primary(Label::new(first, "Remove this trailing comma")); for &span in rest { @@ -281,7 +282,7 @@ const LEADING_COMMA_HELP: &str = r#"J-Expr does not support leading commas in ob pub(crate) fn leading_commas(spans: &[SpanId]) -> ObjectDiagnostic { let (&first, rest) = spans.split_first().expect("Should have at least one span"); - let mut diagnostic = Diagnostic::new(ObjectDiagnosticCategory::LeadingComma, Severity::Error) + let mut diagnostic = Diagnostic::new(ObjectDiagnosticCategory::LeadingComma, Critical::ERROR) .primary(Label::new(first, "Remove this leading comma")); for &span in rest { @@ -300,7 +301,7 @@ const CONSECUTIVE_COMMA_HELP: &str = r#"J-Expr requires exactly one comma betwee pub(crate) fn consecutive_commas(spans: &[SpanId]) -> ObjectDiagnostic { let (&first, rest) = spans.split_first().expect("Should have at least one span"); let mut diagnostic = - Diagnostic::new(ObjectDiagnosticCategory::ConsecutiveComma, Severity::Error) + Diagnostic::new(ObjectDiagnosticCategory::ConsecutiveComma, Critical::ERROR) .primary(Label::new(first, "Remove this extra comma")); for &span in rest { @@ -319,7 +320,7 @@ const CONSECUTIVE_COLON_HELP: &str = r#"J-Expr requires exactly one colon betwee pub(crate) fn consecutive_colons(spans: &[SpanId]) -> ObjectDiagnostic { let (&first, rest) = spans.split_first().expect("Expected at least one span"); let mut diagnostic = - Diagnostic::new(ObjectDiagnosticCategory::ConsecutiveColon, Severity::Error) + Diagnostic::new(ObjectDiagnosticCategory::ConsecutiveColon, Critical::ERROR) .primary(Label::new(first, "Remove this extra colon")); for &span in rest { @@ -337,8 +338,8 @@ pub(crate) fn unknown_key( span: SpanId, key: impl AsRef, expected: &[&'static str], -) -> Diagnostic { - let mut diagnostic = Diagnostic::new(ObjectDiagnosticCategory::UnknownKey, Severity::Error) +) -> ObjectDiagnostic { + let mut diagnostic = Diagnostic::new(ObjectDiagnosticCategory::UnknownKey, Critical::ERROR) .primary(Label::new( span, if expected.is_empty() { @@ -385,7 +386,7 @@ pub(crate) fn unknown_key( pub(crate) fn orphaned_type(span: SpanId) -> ObjectDiagnostic { let mut diagnostic = - Diagnostic::new(ObjectDiagnosticCategory::OrphanedType, Severity::Error).primary( + Diagnostic::new(ObjectDiagnosticCategory::OrphanedType, Critical::ERROR).primary( Label::new(span, "Add a primary construct to use with #type"), ); @@ -416,7 +417,7 @@ pub(crate) fn duplicate_key( duplicate_span: SpanId, key: impl AsRef, ) -> ObjectDiagnostic { - let mut diagnostic = Diagnostic::new(ObjectDiagnosticCategory::DuplicateKey, Severity::Error) + let mut diagnostic = Diagnostic::new(ObjectDiagnosticCategory::DuplicateKey, Critical::ERROR) .primary(Label::new(duplicate_span, "Duplicate key")); diagnostic.labels.push(Label::new( @@ -440,7 +441,7 @@ pub(crate) fn struct_key_expected_identifier( ) -> ObjectDiagnostic { let mut diagnostic = Diagnostic::new( ObjectDiagnosticCategory::StructKeyExpectedIdentifier, - Severity::Error, + Critical::ERROR, ) .primary(Label::new(key_span, "Invalid struct field key")); @@ -459,7 +460,7 @@ pub(crate) fn struct_key_expected_identifier( pub(crate) fn dict_entry_expected_array(span: SpanId, found: SyntaxKind) -> ObjectDiagnostic { let mut diagnostic = Diagnostic::new( ObjectDiagnosticCategory::DictEntryExpectedArray, - Severity::Error, + Critical::ERROR, ) .primary(Label::new(span, "Expected an array for dictionary entry")); @@ -482,7 +483,7 @@ const DICT_ENTRY_FORMAT_NOTE: &str = pub(crate) fn dict_entry_too_few_items(span: SpanId, found: usize) -> ObjectDiagnostic { let mut diagnostic = Diagnostic::new( ObjectDiagnosticCategory::DictEntryTooFewItems, - Severity::Error, + Critical::ERROR, ) .primary(Label::new( span, @@ -508,7 +509,7 @@ pub(crate) fn dict_entry_too_many_items(excess_element_spans: &[SpanId]) -> Obje let mut diagnostic = Diagnostic::new( ObjectDiagnosticCategory::DictEntryTooManyItems, - Severity::Error, + Critical::ERROR, ) .primary(Label::new(first, "Remove this element")); @@ -532,7 +533,7 @@ pub(crate) fn dict_entry_too_many_items(excess_element_spans: &[SpanId]) -> Obje pub(crate) fn tuple_expected_array(span: SpanId, found: SyntaxKind) -> ObjectDiagnostic { let mut diagnostic = Diagnostic::new( ObjectDiagnosticCategory::TupleExpectedArray, - Severity::Error, + Critical::ERROR, ) .primary(Label::new(span, "Expected an array here")); @@ -553,7 +554,7 @@ pub(crate) fn tuple_expected_array(span: SpanId, found: SyntaxKind) -> ObjectDia pub(crate) fn list_expected_array(span: SpanId, found: SyntaxKind) -> ObjectDiagnostic { let mut diagnostic = - Diagnostic::new(ObjectDiagnosticCategory::ListExpectedArray, Severity::Error) + Diagnostic::new(ObjectDiagnosticCategory::ListExpectedArray, Critical::ERROR) .primary(Label::new(span, "Expected an array here")); // More specific help with clear guidance @@ -574,7 +575,7 @@ pub(crate) fn list_expected_array(span: SpanId, found: SyntaxKind) -> ObjectDiag pub(crate) fn struct_expected_object(span: SpanId, found: SyntaxKind) -> ObjectDiagnostic { let mut diagnostic = Diagnostic::new( ObjectDiagnosticCategory::StructExpectedObject, - Severity::Error, + Critical::ERROR, ) .primary(Label::new(span, "Expected an object here")); @@ -596,7 +597,7 @@ pub(crate) fn struct_expected_object(span: SpanId, found: SyntaxKind) -> ObjectD pub(crate) fn dict_expected_format(span: SpanId, found: SyntaxKind) -> ObjectDiagnostic { let mut diagnostic = Diagnostic::new( ObjectDiagnosticCategory::DictExpectedFormat, - Severity::Error, + Critical::ERROR, ) .primary(Label::new( span, @@ -622,7 +623,7 @@ pub(crate) fn dict_expected_format(span: SpanId, found: SyntaxKind) -> ObjectDia pub(crate) fn type_expected_string(span: SpanId, found: SyntaxKind) -> ObjectDiagnostic { let mut diagnostic = Diagnostic::new( ObjectDiagnosticCategory::TypeExpectedString, - Severity::Error, + Critical::ERROR, ) .primary(Label::new(span, "Invalid type specification")); @@ -642,7 +643,7 @@ pub(crate) fn type_expected_string(span: SpanId, found: SyntaxKind) -> ObjectDia pub(crate) fn literal_expected_primitive(span: SpanId, found: SyntaxKind) -> ObjectDiagnostic { let mut diagnostic = Diagnostic::new( ObjectDiagnosticCategory::LiteralExpectedPrimitive, - Severity::Error, + Critical::ERROR, ) .primary(Label::new(span, "Invalid literal value in this context")); diff --git a/libs/@local/hashql/syntax-jexpr/src/parser/object/visit.rs b/libs/@local/hashql/syntax-jexpr/src/parser/object/visit.rs index a31534e8c63..bf2d7a4ebb1 100644 --- a/libs/@local/hashql/syntax-jexpr/src/parser/object/visit.rs +++ b/libs/@local/hashql/syntax-jexpr/src/parser/object/visit.rs @@ -1,7 +1,7 @@ use alloc::borrow::Cow; use hashql_core::span::SpanId; -use hashql_diagnostics::Diagnostic; +use hashql_diagnostics::{Diagnostic, severity::Critical}; use text_size::TextRange; use crate::{ @@ -34,8 +34,8 @@ pub(crate) fn visit_object_entry<'arena, 'source, 'spans, C>( on_item: &mut impl FnMut( &mut ParserState<'arena, 'source, 'spans>, Key<'source>, - ) -> Result<(), Diagnostic>, -) -> Result<(), Diagnostic> + ) -> Result<(), Diagnostic>, +) -> Result<(), Diagnostic> where C: From, { @@ -88,8 +88,8 @@ pub(crate) fn visit_object<'arena, 'source, 'spans, C>( mut on_item: impl FnMut( &mut ParserState<'arena, 'source, 'spans>, Key<'source>, - ) -> Result<(), Diagnostic>, -) -> Result> + ) -> Result<(), Diagnostic>, +) -> Result> where C: From, { diff --git a/libs/@local/hashql/syntax-jexpr/src/parser/state.rs b/libs/@local/hashql/syntax-jexpr/src/parser/state.rs index cd917bd0886..2f1b0c410e6 100644 --- a/libs/@local/hashql/syntax-jexpr/src/parser/state.rs +++ b/libs/@local/hashql/syntax-jexpr/src/parser/state.rs @@ -182,12 +182,14 @@ impl Expected { } impl From for Expected { + #[inline] fn from(value: SyntaxKindSet) -> Self { Self::Validate(value) } } impl From for Expected { + #[inline] fn from(value: SyntaxKind) -> Self { Self::Validate(SyntaxKindSet::from(value)) } diff --git a/libs/@local/hashql/syntax-jexpr/src/parser/string/error.rs b/libs/@local/hashql/syntax-jexpr/src/parser/string/error.rs index 9a723b446df..f6ab12b4faa 100644 --- a/libs/@local/hashql/syntax-jexpr/src/parser/string/error.rs +++ b/libs/@local/hashql/syntax-jexpr/src/parser/string/error.rs @@ -6,14 +6,14 @@ use hashql_diagnostics::{ Diagnostic, Label, category::{DiagnosticCategory, TerminalDiagnosticCategory}, diagnostic::Message, - severity::Severity, + severity::Critical, }; use text_size::{TextRange, TextSize}; use winnow::error::{ContextError, StrContext}; use crate::span::Span; -pub(crate) type StringDiagnostic = Diagnostic; +pub(crate) type StringDiagnostic = Diagnostic; const INVALID_EXPR: TerminalDiagnosticCategory = TerminalDiagnosticCategory { id: "invalid-expression", @@ -117,7 +117,7 @@ pub(crate) fn invalid_expr( let (label, expected) = convert_parse_error(spans, parent, (offset, error)); let mut diagnostic = - Diagnostic::new(StringDiagnosticCategory::InvalidExpression, Severity::Error) + Diagnostic::new(StringDiagnosticCategory::InvalidExpression, Critical::ERROR) .primary(label); if let Some(expected) = expected { diff --git a/libs/@local/hashql/syntax-jexpr/src/test.rs b/libs/@local/hashql/syntax-jexpr/src/test.rs index 0575d3c3ff3..793ebb2ea42 100644 --- a/libs/@local/hashql/syntax-jexpr/src/test.rs +++ b/libs/@local/hashql/syntax-jexpr/src/test.rs @@ -3,18 +3,20 @@ use hashql_diagnostics::{ Diagnostic, category::DiagnosticCategory, diagnostic::render::{ColorDepth, Format, RenderOptions}, + severity::SeverityKind, source::{Source, Sources}, }; use crate::span::Span; -pub(crate) fn render_diagnostic( +pub(crate) fn render_diagnostic( source: &str, - diagnostic: &Diagnostic, + diagnostic: &Diagnostic, mut spans: &SpanTable, ) -> String where C: DiagnosticCategory, + K: SeverityKind, { let mut sources = Sources::new(); sources.push(Source::new(source)); From d854dc55fb48418f67bdb0ce774d58a1971b1ad2 Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud <7252775+indietyp@users.noreply.github.com> Date: Mon, 8 Jun 2026 11:57:50 +0200 Subject: [PATCH 2/2] fix: split --- .../graph/api/src/rest/entity_query_request.rs | 15 +-------------- libs/@local/hashql/compiletest/src/pipeline.rs | 1 + libs/@local/hashql/core/Cargo.toml | 1 + libs/@local/hashql/core/src/heap/pool.rs | 4 ++-- libs/@local/hashql/core/src/lib.rs | 8 +++++--- 5 files changed, 10 insertions(+), 19 deletions(-) diff --git a/libs/@local/graph/api/src/rest/entity_query_request.rs b/libs/@local/graph/api/src/rest/entity_query_request.rs index 63b025672c7..4d96c3bdb4b 100644 --- a/libs/@local/graph/api/src/rest/entity_query_request.rs +++ b/libs/@local/graph/api/src/rest/entity_query_request.rs @@ -51,7 +51,6 @@ use hashql_diagnostics::{ DiagnosticIssues, Failure, Severity, Status, StatusExt as _, Success, category::{DiagnosticCategory, canonical_category_id}, diagnostic::render::{Format, RenderOptions}, - severity::Critical, source::{DiagnosticSpan, Source, SourceId, Sources}, }; use hashql_eval::{ @@ -340,19 +339,7 @@ impl<'q> EntityQuery<'q> { let mut ast = parser .parse_expr(query.get().as_bytes()) .map_err(|diagnostic| { - Failure::new( - diagnostic - .map_category(HashQLDiagnosticCategory::JExpr) - .map_severity(|severity| { - Critical::try_new(severity).unwrap_or_else(|| { - tracing::error!( - ?severity, - "JExpr returned an error of non-critical severity" - ); - Critical::ERROR - }) - }), - ) + Failure::new(diagnostic.map_category(HashQLDiagnosticCategory::JExpr)) })?; let mut env = Environment::new(heap); diff --git a/libs/@local/hashql/compiletest/src/pipeline.rs b/libs/@local/hashql/compiletest/src/pipeline.rs index 49fe6b68dac..8b18e299072 100644 --- a/libs/@local/hashql/compiletest/src/pipeline.rs +++ b/libs/@local/hashql/compiletest/src/pipeline.rs @@ -130,6 +130,7 @@ impl<'heap> Pipeline<'heap> { parser .parse_expr(content.as_ref()) + .map_err(Diagnostic::generalize) .map_err(Diagnostic::boxed) } diff --git a/libs/@local/hashql/core/Cargo.toml b/libs/@local/hashql/core/Cargo.toml index 5cef818ed51..8a6b5b68221 100644 --- a/libs/@local/hashql/core/Cargo.toml +++ b/libs/@local/hashql/core/Cargo.toml @@ -32,6 +32,7 @@ lexical = { workspace = true, features = ["parse-integers", "parse- memchr = { workspace = true } rapidfuzz = { workspace = true } roaring = { workspace = true, features = ["std"] } +rpds = { workspace = true } serde = { workspace = true, optional = true, features = ["alloc", "derive"] } simple-mermaid = { workspace = true } tracing = { workspace = true } diff --git a/libs/@local/hashql/core/src/heap/pool.rs b/libs/@local/hashql/core/src/heap/pool.rs index c0ffe494b68..3b441a8999d 100644 --- a/libs/@local/hashql/core/src/heap/pool.rs +++ b/libs/@local/hashql/core/src/heap/pool.rs @@ -2,11 +2,11 @@ //! //! [`ScratchPool`] provides thread-safe scratch allocation. Each thread borrows //! its own [`ScratchPoolGuard`] via [`get`](ScratchPool::get), which derefs to -//! [`Scratch`](super::Scratch). +//! [`Scratch`]. //! //! [`HeapPool`] provides thread-safe heap allocation with symbol interning. //! Each thread borrows its own [`HeapPoolGuard`] via [`get`](HeapPool::get), -//! which derefs to [`Heap`](super::Heap). +//! which derefs to [`Heap`]. use core::{ mem::ManuallyDrop, diff --git a/libs/@local/hashql/core/src/lib.rs b/libs/@local/hashql/core/src/lib.rs index 30bcbe06294..3e5b5ffbeba 100644 --- a/libs/@local/hashql/core/src/lib.rs +++ b/libs/@local/hashql/core/src/lib.rs @@ -26,17 +26,19 @@ const_range, const_trait_impl, extend_one, + extern_types, get_disjoint_mut_helpers, + get_mut_unchecked, iter_intersperse, iter_map_windows, iter_next_chunk, + nonpoison_mutex, slice_partition_dedup, slice_swap_unchecked, step_trait, + sync_nonpoison, try_trait_v2, - variant_count, - get_mut_unchecked, - extern_types + variant_count )] extern crate alloc;