astral-sh/ruff · error

non-terminal BDD should have source_order

Error message

non-terminal BDD should have source_order

What it means

An `OwnedConstraintSet` pairs its BDD root node with a source-order tree. In this routine the set's constraints are transformed with a closure and the set is rebuilt; a non-terminal root must always carry `source_order`. The expect fires when the transformed set kept a non-terminal BDD but its `source_order` is None - a combine path that dropped the source order.

Source

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

        self.constraint_cache.extend(
            compacted
                .constraint_indices
                .iter_ones()
                .zip(compacted.constraints.iter().copied())
                .map(|(old_index, constraint)| (constraint, ConstraintId::from_usize(old_index))),
        );
        self.node_cache.extend(
            compacted
                .node_indices
                .iter_ones()
                .zip(compacted.nodes.iter().copied())
                .map(|(old_index, node)| (node, NodeId::from_usize(old_index))),
        );
        self.source_order_cache.extend(
            compacted
                .source_orders
                .iter()
                .copied()
                .enumerate()
                .map(|(index, source_order)| (source_order, SourceOrderId::from_usize(index))),
        );
    }

    // 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()

View on GitHub (pinned to 15f3fe6b15)

Solutions

  1. Audit every operation that constructs `OwnedConstraintSet` to thread `source_order` for non-terminal roots; only terminal roots may set it to None.
  2. Reproduce with the smallest inference case (few typevars, few constraints) and run the ty_python_semantic constraint tests.
  3. Use `debug_assert!` in constructors to catch None-source-order/non-terminal combinations at creation time.
  4. Report upstream with the reproducer if stock 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()); // degrade to trivially-true set

Prevention

When it happens

Trigger: Calling this map/compact routine on an owned constraint set where the closure `f` returns a constraint set whose node is non-terminal while `source_order` is None, e.g. a quantifier/combination operation that threads the BDD but forgets the source-order sidecar.

Common situations: Modifying ty's constraint solver (new combine/quantify operations on `OwnedConstraintSet`); typically surfaces in inference mdtests with multiple typevars and ordering-sensitive fixtures.

Related errors


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