{"record":{"id":"8eb337b9cfc10b41","repo":"astral-sh/ruff","slug":"clause-vector-should-not-be-empty","errorCode":null,"errorMessage":"clause vector should not be empty","messagePattern":"clause vector should not be empty","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/ty_python_semantic/src/types/constraints.rs","lineNumber":7137,"sourceCode":"        check_solutions_for_constraint_orderings(\n            db,\n            &[t, u],\n            &atoms,\n            |storage| {\n                let [t_int, t_str, bytes_u] = atoms.map(|atom| atom.node(db, &env, storage));\n                t_int\n                    .or(storage, t_str)\n                    .negate(storage)\n                    .or(storage, bytes_u)\n            },\n            // A satisfied alternative must not infer `T` from unrelated positive decisions\n            // made earlier in a TDD path.\n            [\"never=false always=false merged=[U=bytes] paths=[; U=bytes]\"],\n        );\n    }\n\n    #[test]\n    fn constraint_ordering_preserves_independent_concrete_solutions() {\n        let db = setup_db();\n        let db = &db;\n        let env = db.program_environment();\n        let t = create_typevar(db, \"T\");\n        let u = create_typevar(db, \"U\");\n        let int = KnownClass::Int.to_instance(db, &env);\n        let str = KnownClass::Str.to_instance(db, &env);\n        let atoms = [\n            PermutedConstraint(\n                t,\n                ConstraintBound::missing_lower(),\n                ConstraintBound::Evidence(int),\n            ),\n            PermutedConstraint(\n                t,\n                ConstraintBound::missing_lower(),\n                ConstraintBound::Evidence(str),\n            ),","sourceCodeStart":7119,"sourceCodeEnd":7155,"githubUrl":"https://github.com/astral-sh/ruff/blob/15f3fe6b15a5f00172f34b0f542f8ea277f5a586/crates/ty_python_semantic/src/types/constraints.rs#L7119-L7155","documentation":"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.","triggerScenarios":"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`.","commonSituations":"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.","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."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Embedders: per-file panic isolation keeps one ICE from killing a batch run\nuse std::panic::{catch_unwind, AssertUnwindSafe};\n\nif catch_unwind(AssertUnwindSafe(|| run_check(db, file))).is_err() {\n    mark_file_errored(file); // continue with remaining files\n}","preventionTips":["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."],"tags":["ty","constraint-solver","bdd","dnf","backtracking","panic","ice","rust"],"backgroundTag":"compiler-internal-error","analyzedSha":"15f3fe6b15a5f00172f34b0f542f8ea277f5a586","analyzedAt":"2026-08-20T16:33:49.445Z","contentChangedAt":"2026-08-20T16:33:49.445Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}