Skip to content

Commit 766ed5b

Browse files
[ty] Some more simplifications when rendering constraint sets (#21009)
This PR adds another useful simplification when rendering constraint sets: `T = int` instead of `T = int ∧ T ≠ str`. (The "smaller" constraint `T = int` implies the "larger" constraint `T ≠ str`. Constraint set clauses are intersections, and if one constraint in a clause implies another, we can throw away the "larger" constraint.) While we're here, we also normalize the bounds of a constraint, so that we equate e.g. `T ≤ int | str` with `T ≤ str | int`, and change the ordering of BDD variables so that all constraints with the same typevar are ordered adjacent to each other. Lastly, we also add a new `display_graph` helper method that prints out the full graph structure of a BDD. --------- Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
1 parent 81c1d36 commit 766ed5b

File tree

6 files changed

+314
-23
lines changed

6 files changed

+314
-23
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/ty_python_semantic/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,9 @@ ty_vendored = { workspace = true }
6363
anyhow = { workspace = true }
6464
dir-test = { workspace = true }
6565
glob = { workspace = true }
66+
indoc = { workspace = true }
6667
insta = { workspace = true }
68+
pretty_assertions = { workspace = true }
6769
tempfile = { workspace = true }
6870
quickcheck = { version = "1.0.3", default-features = false }
6971
quickcheck_macros = { version = "1.0.0" }

crates/ty_python_semantic/resources/mdtest/type_properties/constraints.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -601,3 +601,40 @@ def _[T, U]() -> None:
601601
# revealed: ty_extensions.ConstraintSet[always]
602602
reveal_type(~union | union)
603603
```
604+
605+
## Other simplifications
606+
607+
When displaying a constraint set, we transform the internal BDD representation into a DNF formula
608+
(i.e., the logical OR of several clauses, each of which is the logical AND of several constraints).
609+
This section contains several examples that show that we simplify the DNF formula as much as we can
610+
before displaying it.
611+
612+
```py
613+
from ty_extensions import range_constraint
614+
615+
def f[T, U]():
616+
t1 = range_constraint(str, T, str)
617+
t2 = range_constraint(bool, T, bool)
618+
u1 = range_constraint(str, U, str)
619+
u2 = range_constraint(bool, U, bool)
620+
621+
# revealed: ty_extensions.ConstraintSet[(T@f = bool) ∨ (T@f = str)]
622+
reveal_type(t1 | t2)
623+
# revealed: ty_extensions.ConstraintSet[(U@f = bool) ∨ (U@f = str)]
624+
reveal_type(u1 | u2)
625+
# revealed: ty_extensions.ConstraintSet[((T@f = bool) ∧ (U@f = bool)) ∨ ((T@f = bool) ∧ (U@f = str)) ∨ ((T@f = str) ∧ (U@f = bool)) ∨ ((T@f = str) ∧ (U@f = str))]
626+
reveal_type((t1 | t2) & (u1 | u2))
627+
```
628+
629+
The lower and upper bounds of a constraint are normalized, so that we equate unions and
630+
intersections whose elements appear in different orders.
631+
632+
```py
633+
from typing import Never
634+
635+
def f[T]():
636+
# revealed: ty_extensions.ConstraintSet[(T@f ≤ int | str)]
637+
reveal_type(range_constraint(Never, T, str | int))
638+
# revealed: ty_extensions.ConstraintSet[(T@f ≤ int | str)]
639+
reveal_type(range_constraint(Never, T, int | str))
640+
```

crates/ty_python_semantic/src/semantic_index/definition.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,12 @@ use crate::unpack::{Unpack, UnpackPosition};
2222
/// because a new scope gets inserted before the `Definition` or a new place is inserted
2323
/// before this `Definition`. However, the ID can be considered stable and it is okay to use
2424
/// `Definition` in cross-module` salsa queries or as a field on other salsa tracked structs.
25+
///
26+
/// # Ordering
27+
/// Ordering is based on the definition's salsa-assigned id and not on its values.
28+
/// The id may change between runs, or when the definition was garbage collected and recreated.
2529
#[salsa::tracked(debug, heap_size=ruff_memory_usage::heap_size)]
30+
#[derive(Ord, PartialOrd)]
2631
pub struct Definition<'db> {
2732
/// The file in which the definition occurs.
2833
pub file: File,

crates/ty_python_semantic/src/types.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8461,7 +8461,9 @@ fn lazy_bound_or_constraints_cycle_initial<'db>(
84618461
}
84628462

84638463
/// Where a type variable is bound and usable.
8464-
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, salsa::Update, get_size2::GetSize)]
8464+
#[derive(
8465+
Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, salsa::Update, get_size2::GetSize,
8466+
)]
84658467
pub enum BindingContext<'db> {
84668468
/// The definition of the generic class, function, or type alias that binds this typevar.
84678469
Definition(Definition<'db>),
@@ -8495,7 +8497,9 @@ impl<'db> BindingContext<'db> {
84958497
/// independent of the typevar's bounds or constraints. Two bound typevars have the same identity
84968498
/// if they represent the same logical typevar bound in the same context, even if their bounds
84978499
/// have been materialized differently.
8498-
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, get_size2::GetSize, salsa::Update)]
8500+
#[derive(
8501+
Debug, Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd, get_size2::GetSize, salsa::Update,
8502+
)]
84998503
pub struct BoundTypeVarIdentity<'db> {
85008504
pub(crate) identity: TypeVarIdentity<'db>,
85018505
pub(crate) binding_context: BindingContext<'db>,

0 commit comments

Comments
 (0)