astral-sh/ruff · error

accumulator should not be empty

Error message

accumulator should not be empty

What it means

This routine combines nodes in a balanced tree using a SmallVec accumulator keyed by depth (the comment above it diagrams the a/0, ab/1, abcd/2 shape). The `pop().expect(...)` is guarded by `last().is_some_and(...)`, so it is logically unreachable: it exists to satisfy the Option returned by pop. Hitting it means the accumulator's depth invariants were broken elsewhere in the combining logic.

Source

Thrown at crates/ty_python_semantic/src/types/constraints.rs:3002

    /// rather than by a union assembled from several alternatives. Exhausting the shared
    /// traversal budget also returns false, leaving the original branch unchanged.
    fn is_covered_by(
        self,
        storage: &ConstraintSetStorage<'_>,
        other: Self,
        remaining_visits: &mut usize,
    ) -> bool {
        if self == other || self == ALWAYS_FALSE || other == ALWAYS_TRUE {
            return true;
        }
        let Some(remaining) = remaining_visits.checked_sub(1) else {
            return false;
        };
        *remaining_visits = remaining;
        let (Node::Interior(left), Node::Interior(right)) = (self.node(), other.node()) else {
            return false;
        };
        let left = storage.interior_node_data(left.node());
        let right = storage.interior_node_data(right.node());
        match left.constraint.ordering().cmp(&right.constraint.ordering()) {
            Ordering::Less => {
                left.if_true.is_covered_by(storage, other, remaining_visits)
                    && left
                        .if_uncertain
                        .is_covered_by(storage, other, remaining_visits)
                    && left
                        .if_false
                        .is_covered_by(storage, other, remaining_visits)
            }
            Ordering::Equal => {
                left.if_uncertain
                    .is_covered_by(storage, other, remaining_visits)
                    && (left
                        .if_true
                        .is_covered_by(storage, right.if_true, remaining_visits)
                        || left.if_true.is_covered_by(

View on GitHub (pinned to 15f3fe6b15)

Solutions

  1. Check the depth type (u8) cannot wrap for realistic constraint counts, and that every path through the loop either pops or pushes consistently.
  2. Replace the expect with a `debug_assert!` plus a `continue`-style recovery only after finding the actual invariant break; do not silence it blindly.
  3. Minimize the failing constraint workload into a unit test in constraints.rs.
  4. Report upstream with the reproducer if stock ty panics.
Defensive patterns

Strategy: try-catch

Try / catch

let combined = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
    combine_all(db, env, nodes)
}))
.unwrap_or_else(|_| (one, None)); // degrade to the identity element

Prevention

When it happens

Trigger: A change to the depth bookkeeping - e.g. depth overflow of the u8 counter at 256+ combinations, combine returning early without popping, or mutations of the accumulator between the guard and the pop - makes the while-guard see an element pop cannot return.

Common situations: Editing the tree-combination code in constraints.rs (operator folding over many nodes); inference mdtests with hundreds of constraints after such edits.

Related errors


AI-assisted analysis of astral-sh/ruff@15f3fe6b15 (2026-08-20). Data as JSON: /api/errors/06703c39e16e58ee. Report an issue: GitHub.