Skip to content

Commit 1a58e99

Browse files
committed
feat(data_structures): add is_exhausted method to NonEmptyStack and SparseStack (#13672)
When using stacks, typically you create a stack at start of AST traversal, and then `debug_assert!` that the stack has been emptied again at the end of traversal, to make sure that the stack has not got out of sync - that every `push` is followed by a corresponding `pop`. With `NonEmptyStack` and `SparseStack` it's a little confusing how to do this, because these stacks can never be empty. `stack.is_empty()` is not correct. `stack.len() == 1` is correct, but it's unclear when reading that code what exactly this is testing. Add `is_exhausted` methods to `NonEmptyStack` and `SparseStack` to fulfil this role. Naming: `is_exhausted` is perhaps not the best name, but I couldn't come up with a better one. I considered `is_emptied`, which is more descriptive, but I think it's too close to `is_empty`. (prompted by #13632)
1 parent e29e6c0 commit 1a58e99

File tree

2 files changed

+81
-0
lines changed

2 files changed

+81
-0
lines changed

crates/oxc_data_structures/src/stack/non_empty.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,10 @@ impl<T> NonEmptyStack<T> {
354354
}
355355

356356
/// Get if stack is empty. Always returns `false`.
357+
///
358+
/// Probably you want [`is_exhausted`] instead.
359+
///
360+
/// [`is_exhausted`]: Self::is_exhausted
357361
#[expect(clippy::unused_self)]
358362
#[inline]
359363
pub fn is_empty(&self) -> bool {
@@ -362,6 +366,22 @@ impl<T> NonEmptyStack<T> {
362366
false
363367
}
364368

369+
/// Get if stack is back in its initial state.
370+
/// i.e. Every `push` has been followed by a corresponding `pop`.
371+
///
372+
/// [`NonEmptyStack`] is never empty, so this is the closest thing to `is_empty` that makes sense.
373+
///
374+
/// If this method returns `true`, the stack only contains the initial element,
375+
/// and calling [`pop`] will panic.
376+
///
377+
/// Equivalent to `stack.len() == 1`.
378+
///
379+
/// [`pop`]: Self::pop
380+
#[inline]
381+
pub fn is_exhausted(&self) -> bool {
382+
self.cursor == self.start
383+
}
384+
365385
/// Get capacity.
366386
#[inline]
367387
pub fn capacity(&self) -> usize {
@@ -460,6 +480,22 @@ mod tests {
460480
NonEmptyStack::with_capacity(0, 10u64);
461481
}
462482

483+
#[test]
484+
fn is_exhausted() {
485+
let mut stack = NonEmptyStack::new(0u64);
486+
assert!(stack.is_exhausted());
487+
488+
stack.push(10);
489+
assert!(!stack.is_exhausted());
490+
stack.push(20);
491+
assert!(!stack.is_exhausted());
492+
493+
stack.pop();
494+
assert!(!stack.is_exhausted());
495+
stack.pop();
496+
assert!(stack.is_exhausted());
497+
}
498+
463499
#[test]
464500
fn push_then_pop() {
465501
let mut stack = NonEmptyStack::new(10u64);

crates/oxc_data_structures/src/stack/sparse.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,22 @@ impl<T> SparseStack<T> {
249249
self.has_values.len()
250250
}
251251

252+
/// Get if stack is back in its initial state.
253+
/// i.e. Every `push` has been followed by a corresponding `pop`.
254+
///
255+
/// [`SparseStack`] is never empty, so this is the closest thing to `is_empty` that makes sense.
256+
///
257+
/// If this method returns `true`, the stack only contains the initial element,
258+
/// and calling [`pop`] will panic.
259+
///
260+
/// Equivalent to `stack.len() == 1`.
261+
///
262+
/// [`pop`]: Self::pop
263+
#[inline]
264+
pub fn is_exhausted(&self) -> bool {
265+
self.has_values.is_exhausted()
266+
}
267+
252268
/// Get number of filled entries on the stack.
253269
#[inline]
254270
pub fn filled_len(&self) -> usize {
@@ -285,3 +301,32 @@ impl<T> SparseStack<T> {
285301
self.values.as_mut_slice()
286302
}
287303
}
304+
305+
#[cfg(test)]
306+
mod tests {
307+
use super::*;
308+
309+
#[test]
310+
fn is_exhausted() {
311+
let mut stack = SparseStack::<u64>::new();
312+
assert!(stack.is_exhausted());
313+
314+
stack.push(None);
315+
assert!(!stack.is_exhausted());
316+
stack.push(None);
317+
assert!(!stack.is_exhausted());
318+
stack.pop();
319+
assert!(!stack.is_exhausted());
320+
stack.pop();
321+
assert!(stack.is_exhausted());
322+
323+
stack.push(Some(10));
324+
assert!(!stack.is_exhausted());
325+
stack.push(Some(20));
326+
assert!(!stack.is_exhausted());
327+
stack.pop();
328+
assert!(!stack.is_exhausted());
329+
stack.pop();
330+
assert!(stack.is_exhausted());
331+
}
332+
}

0 commit comments

Comments
 (0)