astral-sh/ruff · error
non-terminal constraint set should have a source_order
Error message
non-terminal constraint set should have a source_order
What it means
The source-order counterpart of error 156 during a merge: if the incoming `OwnedConstraintSet` has a non-terminal root, it must also carry a `source_order` tree so the merged builder can map it. The expect fires when the incoming set has a non-terminal node but `source_order` is None.
Source
Thrown at crates/ty_python_semantic/src/types/constraints.rs:1715
if old_node.is_terminal() {
return old_node;
}
if let Some(remapped) = cache.get(&old_node) {
return *remapped;
}
let old_node_index = inner.retained_node_index(old_node);
let old_interior = inner.nodes[old_node_index];
let if_true = rebuild_node(storage, inner, constraints, cache, old_interior.if_true);
let if_uncertain = rebuild_node(
storage,
inner,
constraints,
cache,
old_interior.if_uncertain,
);
let if_false = rebuild_node(storage, inner, constraints, cache, old_interior.if_false);
let old_constraint_index = inner.retained_constraint_index(old_interior.constraint);
let (condition, _) = constraints[old_constraint_index];
let remapped = condition.ite_uncertain(storage, if_true, if_uncertain, if_false);
cache.insert(old_node, remapped);
remapped
}
if other.node.is_terminal() {
return (other.node, None);
}
let inner = other
.inner
.as_ref()
.expect("storage-free owned constraint sets must have terminal roots");
// Restore the saved order of referenced typevars before rebuilding constraints. A stored
// `T <= U` can have `U` as its subject and `T` as its lower bound. Interning that subject
// first would reverse the original typevar order, causing successive loads to alternateView on GitHub (pinned to 15f3fe6b15)
Solutions
- Make the owned-set constructor enforce the pairing: non-terminal node implies both Some(inner) and Some(source_order).
- Check the producer path that built the incoming set (e.g. quantifier elimination) threads source_order like it threads the node.
- Add a regression test merging the output of every combine operation; run the ty_python_semantic suite.
- Report upstream with the fixture if unmodified ty panics.
Defensive patterns
Strategy: try-catch
Try / catch
let merged = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
builder.extend(db, env, other)
}))
.unwrap_or_else(|_| builder.clone_set()); Prevention
- Contributors: the triple (node, inner, source_order) must stay consistent for non-terminal roots; check producers, not the merge.
- Keep quantifier/combine operations covered by unit tests that merge their outputs.
- Users: pin versions and report reachable panics with reproducers.
When it happens
Trigger: Merging an owned constraint set constructed non-terminal with source_order None - the same inconsistent-construction family as 152 and 156, surfacing at merge time instead of rebuild time.
Common situations: Changes to how owned sets are produced (quantification, caching into Salsa, partial moves out of a set); constraint mdtests with multiple merged sets after a solver refactor.
Related errors
- storage-free owned constraint sets must have terminal roots
- every TDD constraint should have a source order
- non-terminal BDD should have source_order
- typevar should be interned before ordering
- node should be non-terminal
AI-assisted analysis of astral-sh/ruff@15f3fe6b15 (2026-08-20).
Data as JSON: /api/errors/55225bb5d76e9eea.
Report an issue: GitHub.