astral-sh/ruff · critical
clause vector should not be empty
Error message
clause vector should not be empty
What it means
Internal assertion in `SatisfiedClause::pop` (crates/ty_python_semantic/src/types/constraints.rs:7038). `SatisfiedClause` is one conjunction in the DNF representation that ty builds while enumerating all variable assignments satisfying a BDD; the `Searcher` in `satisfied_clauses` (constraints.rs:3274-3292) keeps strictly paired `push(constraint)`/`visit`/`pop()` steps for each of an interior node's three branches (if_true, if_uncertain, if_false) during depth-first traversal and backtracking. The `expect` asserts that push/pop bookkeeping stays symmetric; a panic means the traversal popped a constraint it never pushed, an internal solver bug unrelated to the Python code being checked.
Source
Thrown at crates/ty_python_semantic/src/types/constraints.rs:7137
check_solutions_for_constraint_orderings(
db,
&[t, u],
&atoms,
|storage| {
let [t_int, t_str, bytes_u] = atoms.map(|atom| atom.node(db, &env, storage));
t_int
.or(storage, t_str)
.negate(storage)
.or(storage, bytes_u)
},
// A satisfied alternative must not infer `T` from unrelated positive decisions
// made earlier in a TDD path.
["never=false always=false merged=[U=bytes] paths=[; U=bytes]"],
);
}
#[test]
fn constraint_ordering_preserves_independent_concrete_solutions() {
let db = setup_db();
let db = &db;
let env = db.program_environment();
let t = create_typevar(db, "T");
let u = create_typevar(db, "U");
let int = KnownClass::Int.to_instance(db, &env);
let str = KnownClass::Str.to_instance(db, &env);
let atoms = [
PermutedConstraint(
t,
ConstraintBound::missing_lower(),
ConstraintBound::Evidence(int),
),
PermutedConstraint(
t,
ConstraintBound::missing_lower(),
ConstraintBound::Evidence(str),
),View on GitHub (pinned to 15f3fe6b15)
Solutions
- If hit while developing ty: diff recent changes to `satisfied_clauses`/`SatisfiedClause` in crates/ty_python_semantic/src/types/constraints.rs and restore the push/pop pairing on every branch (including early returns and `continue`s).
- Run the solver unit tests: `INSTA_FORCE_PASS=1 INSTA_UPDATE=always MDTEST_UPDATE_SNAPSHOTS=1 cargo nextest run -p ty_python_semantic` and add a test covering the edited branch shape.
- As an end user: report with `RUST_BACKTRACE=1 ty check <file>` output, the file, and `ty --version`; downgrade to the last working build meanwhile.
- Consider replacing the `Vec` backtracking with a persistent/cons-list passed by value so a desync cannot occur structurally.
Defensive patterns
Strategy: try-catch
Try / catch
// Embedders: per-file panic isolation keeps one ICE from killing a batch run
use std::panic::{catch_unwind, AssertUnwindSafe};
if catch_unwind(AssertUnwindSafe(|| run_check(db, file))).is_err() {
mark_file_errored(file); // continue with remaining files
} Prevention
- ty developers: keep `push`/`visit`/`pop` strictly paired on all three branches in `satisfied_clauses` (constraints.rs:3274-3292); guard any new control flow so a branch never pops without pushing.
- Cover new node shapes with unit tests that call `satisfied_clauses` and compare the DNF output.
- Users: report with backtrace; this assert is defensive and unreachable with the current traversal, so any hit is a bug worth filing.
When it happens
Trigger: Only reachable on code paths that enumerate DNF clauses of a BDD via `satisfied_clauses` (also used by the BDD `display` output, where an empty clause renders as "always"). It fires if someone edits the `Searcher` traversal so a branch pushes conditionally but pops unconditionally — or skips a push while keeping the pop — desynchronizing `current_clause`.
Common situations: Practically unreachable with the current strictly-paired traversal; it exists as a defensive invariant. It would surface during ty development after modifying the DNF enumeration or after a BDD rewrite produced a node shape the Searcher did not anticipate. Users on released ty builds essentially never see it.
Related errors
- every BDD constraint should have a source-order entry
- `parsed_module` should have assigned a node index
- non-terminal BDD should have source_order
- node should be non-terminal
- extra use-def data should have been retained
AI-assisted analysis of astral-sh/ruff@15f3fe6b15 (2026-08-20).
Data as JSON: /api/errors/8eb337b9cfc10b41.
Report an issue: GitHub.