astral-sh/ruff · error

node should be non-terminal

Error message

node should be non-terminal

What it means

During the same owned-set rebuild, every visited non-terminal BDD node must have a support id in storage (`node_support_id`). The traversal loop already skips terminal nodes, so this expect fires only when storage's interior-node data is inconsistent with its node list - corrupted or partially compacted constraint storage, not a user-code condition.

Source

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

    }

    // This is a separate method from `ensure_overlay_identity_caches` because it requires a `db`.
    fn ensure_overlay_typevar_identity_cache(&mut self, db: &'db dyn Db) {
        let Some(compacted) = &self.compacted else {
            return;
        };
        if !self.typevar_cache.is_empty() {
            return;
        }

        self.typevar_cache.extend(
            compacted
                .typevars
                .iter_enumerated()
                .map(|(id, typevar)| (typevar.identity(db), id)),
        );
    }

    fn adjusted_node_id(&self, id: NodeId) -> NodeId {
        if let Some(compacted) = &self.compacted {
            return id + compacted.node_indices.len();
        }
        id
    }

    fn adjusted_constraint_id(&self, id: ConstraintId) -> ConstraintId {
        if let Some(compacted) = &self.compacted {
            return id + compacted.constraint_indices.len();
        }
        id
    }

    fn adjusted_support_id(&self, id: SupportId) -> SupportId {
        if let Some(compacted) = &self.compacted {
            return id + compacted.support_indices.len();
        }

View on GitHub (pinned to 15f3fe6b15)

Solutions

  1. Check the compaction/filter steps keep `nodes`, `supports`, and `node_supports` consistent (the used_nodes/used_supports bookkeeping below this expect).
  2. Add a storage self-check (debug assert) that every interior node has a support id after each mutation phase.
  3. Minimize the failing inference fixture and run `cargo nextest run -p ty_python_semantic`.
  4. Report upstream with the fixture if unmodified ty panics.
Defensive patterns

Strategy: try-catch

Try / catch

let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
    owned_set.map(db, f)
}))
.unwrap_or_else(|_| OwnedConstraintSet::terminal());

Prevention

When it happens

Trigger: Running the rebuild over a storage whose node list contains interior nodes without recorded support ids - e.g. a compaction step that filtered nodes but not their support entries, or a hand-built storage in tests.

Common situations: Contributions to the constraint-set compaction/filtering code; adding new node kinds to the BDD; usually hit via mdtests after editing `constraints.rs` rather than in released builds.

Related errors


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