ruff/crates
Douglas Creager 45842cc034
[ty] Fix non-determinism in `ConstraintSet.specialize_constrained` (#21744)
This fixes a non-determinism that we were seeing in the constraint set
tests in https://github.com/astral-sh/ruff/pull/21715.

In this test, we create the following constraint set, and then try to
create a specialization from it:

```
(T@constrained_by_gradual_list = list[Base])
  ∨
(Bottom[list[Any]] ≤ T@constrained_by_gradual_list ≤ Top[list[Any]])
```

That is, `T` is either specifically `list[Base]`, or it's any `list`.
Our current heuristics say that, absent other restrictions, we should
specialize `T` to the more specific type (`list[Base]`).

In the correct test output, we end up creating a BDD that looks like
this:

```
(T@constrained_by_gradual_list = list[Base])
┡━₁ always
└─₀ (Bottom[list[Any]] ≤ T@constrained_by_gradual_list ≤ Top[list[Any]])
    ┡━₁ always
    └─₀ never
```

In the incorrect output, the BDD looks like this:

```
(Bottom[list[Any]] ≤ T@constrained_by_gradual_list ≤ Top[list[Any]])
┡━₁ always
└─₀ never
```

The difference is the ordering of the two individual constraints. Both
constraints appear in the first BDD, but the second BDD only contains `T
is any list`. If we were to force the second BDD to contain both
constraints, it would look like this:

```
(Bottom[list[Any]] ≤ T@constrained_by_gradual_list ≤ Top[list[Any]])
┡━₁ always
└─₀ (T@constrained_by_gradual_list = list[Base])
    ┡━₁ always
    └─₀ never
```

This is the standard shape for an OR of two constraints. However! Those
two constraints are not independent of each other! If `T` is
specifically `list[Base]`, then it's definitely also "any `list`". From
that, we can infer the contrapositive: that if `T` is not any list, then
it cannot be `list[Base]` specifically. When we encounter impossible
situations like that, we prune that path in the BDD, and treat it as
`false`. That rewrites the second BDD to the following:

```
(Bottom[list[Any]] ≤ T@constrained_by_gradual_list ≤ Top[list[Any]])
┡━₁ always
└─₀ (T@constrained_by_gradual_list = list[Base])
    ┡━₁ never   <-- IMPOSSIBLE, rewritten to never
    └─₀ never
```

We then would see that that BDD node is redundant, since both of its
outgoing edges point at the `never` node. Our BDDs are _reduced_, which
means we have to remove that redundant node, resulting in the BDD we saw
above:

```
(Bottom[list[Any]] ≤ T@constrained_by_gradual_list ≤ Top[list[Any]])
┡━₁ always
└─₀ never       <-- redundant node removed
```

The end result is that we were "forgetting" about the `T = list[Base]`
constraint, but only for some BDD variable orderings.

To fix this, I'm leaning in to the fact that our BDDs really do need to
"remember" all of the constraints that they were created with. Some
combinations might not be possible, but we now have the sequent map,
which is quite good at detecting and pruning those.

So now our BDDs are _quasi-reduced_, which just means that redundant
nodes are allowed. (At first I was worried that allowing redundant nodes
would be an unsound "fix the glitch". But it turns out they're real!
[This](https://ieeexplore.ieee.org/abstract/document/130209) is the
paper that introduces them, though it's very difficult to read. Knuth
mentions them in §7.1.4 of
[TAOCP](https://course.khoury.northeastern.edu/csu690/ssl/bdd-knuth.pdf),
and [this paper](https://par.nsf.gov/servlets/purl/10128966) has a nice
short summary of them in §2.)

While we're here, I've added a bunch of `debug` and `trace` level log
messages to the constraint set implementation. I was getting tired of
having to add these by hands over and over. To enable them, just set
`TY_LOG` in your environment, e.g.

```sh
env TY_LOG=ty_python_semantic::types::constraints::SequentMap=trace ty check ...
```

[Note, this has an `internal` label because are still not using
`specialize_constrained` in anything user-facing yet.]
2025-12-03 10:19:39 -05:00
..
ruff Bump 0.14.7 (#21684) 2025-11-28 14:34:27 -06:00
ruff_annotate_snippets Only render hyperlinks for terminals known to support them (#21519) 2025-11-19 10:02:58 +01:00
ruff_benchmark Move `Token`, `TokenKind` and `Tokens` to `ruff-python-ast` (#21760) 2025-12-02 20:10:46 +01:00
ruff_cache Switch to Rust 2024 edition (#18129) 2025-05-16 13:25:28 +02:00
ruff_db [ty] Enable LRU collection for parsed module (#21749) 2025-12-03 12:16:18 +01:00
ruff_dev Update Rust toolchain to 1.91 (#21179) 2025-11-01 01:50:58 +00:00
ruff_diagnostics [ty] Add code action to ignore diagnostic on the current line (#21595) 2025-11-29 15:41:54 +01:00
ruff_formatter [ty] Use "cannot" consistently over "can not" (#21255) 2025-11-03 10:38:20 -05:00
ruff_graph `analyze`: Add option to skip over imports in `TYPE_CHECKING` blocks (#21472) 2025-11-16 12:30:24 +00:00
ruff_index Update Rust toolchain to 1.88 and MSRV to 1.86 (#19011) 2025-06-28 20:24:00 +02:00
ruff_linter [syntax-error] Default type parameter followed by non-default type parameter (#21657) 2025-12-03 12:01:31 +05:30
ruff_macros Document when a rule was added (#21035) 2025-10-23 14:48:41 -04:00
ruff_memory_usage [ty] Enable LRU collection for parsed module (#21749) 2025-12-03 12:16:18 +01:00
ruff_notebook [ty] Respect notebook cell boundaries when adding an auto import (#21322) 2025-11-13 18:58:08 +01:00
ruff_options_metadata Update Rust toolchain to 1.89 (#19807) 2025-08-07 18:21:50 +02:00
ruff_python_ast Add token based `parenthesized_ranges` implementation (#21738) 2025-12-03 08:15:17 +00:00
ruff_python_ast_integration_tests Add token based `parenthesized_ranges` implementation (#21738) 2025-12-03 08:15:17 +00:00
ruff_python_codegen Move `Token`, `TokenKind` and `Tokens` to `ruff-python-ast` (#21760) 2025-12-02 20:10:46 +01:00
ruff_python_formatter Move `Token`, `TokenKind` and `Tokens` to `ruff-python-ast` (#21760) 2025-12-02 20:10:46 +01:00
ruff_python_importer Move `Token`, `TokenKind` and `Tokens` to `ruff-python-ast` (#21760) 2025-12-02 20:10:46 +01:00
ruff_python_index Move `Token`, `TokenKind` and `Tokens` to `ruff-python-ast` (#21760) 2025-12-02 20:10:46 +01:00
ruff_python_literal Switch to Rust 2024 edition (#18129) 2025-05-16 13:25:28 +02:00
ruff_python_parser [syntax-error] Default type parameter followed by non-default type parameter (#21657) 2025-12-03 12:01:31 +05:30
ruff_python_semantic Add rule to detect unnecessary class properties (#21535) 2025-11-26 09:31:22 +01:00
ruff_python_stdlib [`ruff`] Extend FA102 with listed PEP 585-compatible APIs (#20659) 2025-10-03 09:45:32 -04:00
ruff_python_trivia Handle t-string prefixes in `SimpleTokenizer` (#20578) 2025-09-25 14:33:37 -05:00
ruff_python_trivia_integration_tests Handle t-string prefixes in `SimpleTokenizer` (#20578) 2025-09-25 14:33:37 -05:00
ruff_server Set severity for non-rule diagnostics (#21559) 2025-11-21 17:42:35 +01:00
ruff_source_file Move diff rendering to `ruff_db` (#20006) 2025-08-21 09:47:00 -04:00
ruff_text_size [ty] Fix subtraction overflow bug 2025-11-21 15:07:37 -05:00
ruff_wasm Bump 0.14.7 (#21684) 2025-11-28 14:34:27 -06:00
ruff_workspace `analyze`: Add option to skip over imports in `TYPE_CHECKING` blocks (#21472) 2025-11-16 12:30:24 +00:00
ty Add token based `parenthesized_ranges` implementation (#21738) 2025-12-03 08:15:17 +00:00
ty_combine [ty] Disallow std::env and io methods in most ty crates (#20046) 2025-08-22 11:13:47 -07:00
ty_completion_eval [ty] Exclude `typing_extensions` from completions unless it's really available 2025-12-01 11:24:16 -05:00
ty_ide [ty] Improve `@override`, `@final` and Liskov checks in cases where there are multiple reachable definitions (#21767) 2025-12-03 12:51:36 +00:00
ty_project [ty] Enable LRU collection for parsed module (#21749) 2025-12-03 12:16:18 +01:00
ty_python_semantic [ty] Fix non-determinism in `ConstraintSet.specialize_constrained` (#21744) 2025-12-03 10:19:39 -05:00
ty_server [ty] Fix auto-import code action to handle pre-existing import 2025-12-01 14:20:47 -05:00
ty_static [ty] improve base conda distinction from child conda (#20675) 2025-10-03 13:56:06 +00:00
ty_test [ty] add tests for workspaces (#21741) 2025-12-02 06:43:41 -05:00
ty_vendored [ty] Create a specialization from a constraint set (#21414) 2025-11-19 14:20:33 -05:00
ty_wasm [ty] Add code action to ignore diagnostic on the current line (#21595) 2025-11-29 15:41:54 +01:00