From acd08168c803932a377a24202cb09593be338740 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Tue, 11 Nov 2025 18:11:20 -0500 Subject: [PATCH] unwoof --- .../satisfied_by_all_typevars.md | 120 +-- crates/ty_python_semantic/src/types.rs | 693 ++++++++---------- .../ty_python_semantic/src/types/call/bind.rs | 91 ++- crates/ty_python_semantic/src/types/class.rs | 74 +- .../src/types/constraints.rs | 110 +-- .../ty_python_semantic/src/types/display.rs | 3 - .../ty_python_semantic/src/types/function.rs | 8 +- .../ty_python_semantic/src/types/generics.rs | 49 +- .../ty_python_semantic/src/types/instance.rs | 35 +- .../ty_python_semantic/src/types/newtype.rs | 26 +- .../src/types/protocol_class.rs | 41 +- .../src/types/signatures.rs | 137 ++-- .../src/types/subclass_of.rs | 18 +- crates/ty_python_semantic/src/types/tuple.rs | 46 +- .../ty_extensions/ty_extensions.pyi | 6 - 15 files changed, 612 insertions(+), 845 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/type_properties/satisfied_by_all_typevars.md b/crates/ty_python_semantic/resources/mdtest/type_properties/satisfied_by_all_typevars.md index 346b22a130..865cfa8395 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_properties/satisfied_by_all_typevars.md +++ b/crates/ty_python_semantic/resources/mdtest/type_properties/satisfied_by_all_typevars.md @@ -60,29 +60,29 @@ class Sub(Base): ... class Unrelated: ... def unbounded[T](): - static_assert(ConstraintSet.always().with_inferable(T).satisfied_by_all_typevars()) + static_assert(ConstraintSet.always().satisfied_by_all_typevars(inferable=tuple[T])) static_assert(ConstraintSet.always().satisfied_by_all_typevars()) - static_assert(not ConstraintSet.never().with_inferable(T).satisfied_by_all_typevars()) + static_assert(not ConstraintSet.never().satisfied_by_all_typevars(inferable=tuple[T])) static_assert(not ConstraintSet.never().satisfied_by_all_typevars()) # (T = Never) is a valid specialization, which satisfies (T ≤ Unrelated). - static_assert(ConstraintSet.range(Never, T, Unrelated).with_inferable(T).satisfied_by_all_typevars()) + static_assert(ConstraintSet.range(Never, T, Unrelated).satisfied_by_all_typevars(inferable=tuple[T])) # (T = Base) is a valid specialization, which does not satisfy (T ≤ Unrelated). static_assert(not ConstraintSet.range(Never, T, Unrelated).satisfied_by_all_typevars()) # (T = Base) is a valid specialization, which satisfies (T ≤ Super). - static_assert(ConstraintSet.range(Never, T, Super).with_inferable(T).satisfied_by_all_typevars()) + static_assert(ConstraintSet.range(Never, T, Super).satisfied_by_all_typevars(inferable=tuple[T])) # (T = Unrelated) is a valid specialization, which does not satisfy (T ≤ Super). static_assert(not ConstraintSet.range(Never, T, Super).satisfied_by_all_typevars()) # (T = Base) is a valid specialization, which satisfies (T ≤ Base). - static_assert(ConstraintSet.range(Never, T, Base).with_inferable(T).satisfied_by_all_typevars()) + static_assert(ConstraintSet.range(Never, T, Base).satisfied_by_all_typevars(inferable=tuple[T])) # (T = Unrelated) is a valid specialization, which does not satisfy (T ≤ Base). static_assert(not ConstraintSet.range(Never, T, Base).satisfied_by_all_typevars()) # (T = Sub) is a valid specialization, which satisfies (T ≤ Sub). - static_assert(ConstraintSet.range(Never, T, Sub).with_inferable(T).satisfied_by_all_typevars()) + static_assert(ConstraintSet.range(Never, T, Sub).satisfied_by_all_typevars(inferable=tuple[T])) # (T = Unrelated) is a valid specialization, which does not satisfy (T ≤ Sub). static_assert(not ConstraintSet.range(Never, T, Sub).satisfied_by_all_typevars()) ``` @@ -106,38 +106,38 @@ class Sub(Base): ... class Unrelated: ... def bounded[T: Base](): - static_assert(ConstraintSet.always().with_inferable(T).satisfied_by_all_typevars()) + static_assert(ConstraintSet.always().satisfied_by_all_typevars(inferable=tuple[T])) static_assert(ConstraintSet.always().satisfied_by_all_typevars()) - static_assert(not ConstraintSet.never().with_inferable(T).satisfied_by_all_typevars()) + static_assert(not ConstraintSet.never().satisfied_by_all_typevars(inferable=tuple[T])) static_assert(not ConstraintSet.never().satisfied_by_all_typevars()) # (T = Base) is a valid specialization, which satisfies (T ≤ Super). - static_assert(ConstraintSet.range(Never, T, Super).with_inferable(T).satisfied_by_all_typevars()) + static_assert(ConstraintSet.range(Never, T, Super).satisfied_by_all_typevars(inferable=tuple[T])) # Every valid specialization satisfies (T ≤ Base). Since (Base ≤ Super), every valid # specialization also satisfies (T ≤ Super). static_assert(ConstraintSet.range(Never, T, Super).satisfied_by_all_typevars()) # (T = Base) is a valid specialization, which satisfies (T ≤ Base). - static_assert(ConstraintSet.range(Never, T, Base).with_inferable(T).satisfied_by_all_typevars()) + static_assert(ConstraintSet.range(Never, T, Base).satisfied_by_all_typevars(inferable=tuple[T])) # Every valid specialization satisfies (T ≤ Base). static_assert(ConstraintSet.range(Never, T, Base).satisfied_by_all_typevars()) # (T = Sub) is a valid specialization, which satisfies (T ≤ Sub). - static_assert(ConstraintSet.range(Never, T, Sub).with_inferable(T).satisfied_by_all_typevars()) + static_assert(ConstraintSet.range(Never, T, Sub).satisfied_by_all_typevars(inferable=tuple[T])) # (T = Base) is a valid specialization, which does not satisfy (T ≤ Sub). static_assert(not ConstraintSet.range(Never, T, Sub).satisfied_by_all_typevars()) # (T = Never) is a valid specialization, which satisfies (T ≤ Unrelated). constraints = ConstraintSet.range(Never, T, Unrelated) - static_assert(constraints.with_inferable(T).satisfied_by_all_typevars()) + static_assert(constraints.satisfied_by_all_typevars(inferable=tuple[T])) # (T = Base) is a valid specialization, which does not satisfy (T ≤ Unrelated). static_assert(not constraints.satisfied_by_all_typevars()) # Never is the only type that satisfies both (T ≤ Base) and (T ≤ Unrelated). So there is no # valid specialization that satisfies (T ≤ Unrelated ∧ T ≠ Never). constraints = constraints & ~ConstraintSet.range(Never, T, Never) - static_assert(not constraints.with_inferable(T).satisfied_by_all_typevars()) + static_assert(not constraints.satisfied_by_all_typevars(inferable=tuple[T])) static_assert(not constraints.satisfied_by_all_typevars()) ``` @@ -153,15 +153,15 @@ the constraint set. from typing import Any def bounded_by_gradual[T: Any](): - static_assert(ConstraintSet.always().with_inferable(T).satisfied_by_all_typevars()) + static_assert(ConstraintSet.always().satisfied_by_all_typevars(inferable=tuple[T])) static_assert(ConstraintSet.always().satisfied_by_all_typevars()) - static_assert(not ConstraintSet.never().with_inferable(T).satisfied_by_all_typevars()) + static_assert(not ConstraintSet.never().satisfied_by_all_typevars(inferable=tuple[T])) static_assert(not ConstraintSet.never().satisfied_by_all_typevars()) # If we choose Base as the materialization for the upper bound, then (T = Base) is a valid # specialization, which satisfies (T ≤ Base). - static_assert(ConstraintSet.range(Never, T, Base).with_inferable(T).satisfied_by_all_typevars()) + static_assert(ConstraintSet.range(Never, T, Base).satisfied_by_all_typevars(inferable=tuple[T])) # We are free to choose any materialization of the upper bound, and only have to show that the # constraint set holds for that one materialization. Having chosen one materialization, we then # have to show that the constraint set holds for all valid specializations of that @@ -173,7 +173,7 @@ def bounded_by_gradual[T: Any](): # If we choose Unrelated as the materialization, then (T = Unrelated) is a valid specialization, # which satisfies (T ≤ Unrelated). constraints = ConstraintSet.range(Never, T, Unrelated) - static_assert(constraints.with_inferable(T).satisfied_by_all_typevars()) + static_assert(constraints.satisfied_by_all_typevars(inferable=tuple[T])) # If we choose Never as the materialization, then (T = Never) is the only valid specialization, # which satisfies (T ≤ Unrelated). static_assert(constraints.satisfied_by_all_typevars()) @@ -181,7 +181,7 @@ def bounded_by_gradual[T: Any](): # If we choose Unrelated as the materialization, then (T = Unrelated) is a valid specialization, # which satisfies (T ≤ Unrelated ∧ T ≠ Never). constraints = constraints & ~ConstraintSet.range(Never, T, Never) - static_assert(constraints.with_inferable(T).satisfied_by_all_typevars()) + static_assert(constraints.satisfied_by_all_typevars(inferable=tuple[T])) # There is no upper bound that we can choose to satisfy this constraint set in non-inferable # position. (T = Never) will be a valid assignment no matter what, and that does not satisfy # (T ≤ Unrelated ∧ T ≠ Never). @@ -196,15 +196,15 @@ restrictive variance (i.e., invariance), but we get the same results for other v ```py def bounded_by_gradual[T: list[Any]](): - static_assert(ConstraintSet.always().with_inferable(T).satisfied_by_all_typevars()) + static_assert(ConstraintSet.always().satisfied_by_all_typevars(inferable=tuple[T])) static_assert(ConstraintSet.always().satisfied_by_all_typevars()) - static_assert(not ConstraintSet.never().with_inferable(T).satisfied_by_all_typevars()) + static_assert(not ConstraintSet.never().satisfied_by_all_typevars(inferable=tuple[T])) static_assert(not ConstraintSet.never().satisfied_by_all_typevars()) # If we choose list[Base] as the materialization of the upper bound, then (T = list[Base]) is a # valid specialization, which satisfies (T ≤ list[Base]). - static_assert(ConstraintSet.range(Never, T, list[Base]).with_inferable(T).satisfied_by_all_typevars()) + static_assert(ConstraintSet.range(Never, T, list[Base]).satisfied_by_all_typevars(inferable=tuple[T])) # If we choose Base as the materialization, then all valid specializations must satisfy # (T ≤ list[Base]). # We are free to choose any materialization of the upper bound, and only have to show that the @@ -217,7 +217,7 @@ def bounded_by_gradual[T: list[Any]](): # If we choose Unrelated as the materialization, then (T = list[Unrelated]) is a valid # specialization, which satisfies (T ≤ list[Unrelated]). constraints = ConstraintSet.range(Never, T, list[Unrelated]) - static_assert(constraints.with_inferable(T).satisfied_by_all_typevars()) + static_assert(constraints.satisfied_by_all_typevars(inferable=tuple[T])) # If we choose Unrelated as the materialization, then all valid specializations must satisfy # (T ≤ list[Unrelated]). static_assert(constraints.satisfied_by_all_typevars()) @@ -225,7 +225,7 @@ def bounded_by_gradual[T: list[Any]](): # If we choose Unrelated as the materialization, then (T = list[Unrelated]) is a valid # specialization, which satisfies (T ≤ list[Unrelated] ∧ T ≠ Never). constraints = constraints & ~ConstraintSet.range(Never, T, Never) - static_assert(constraints.with_inferable(T).satisfied_by_all_typevars()) + static_assert(constraints.satisfied_by_all_typevars(inferable=tuple[T])) # There is no upper bound that we can choose to satisfy this constraint set in non-inferable # position. (T = Never) will be a valid assignment no matter what, and that does not satisfy # (T ≤ list[Unrelated] ∧ T ≠ Never). @@ -251,61 +251,61 @@ class Sub(Base): ... class Unrelated: ... def constrained[T: (Base, Unrelated)](): - static_assert(ConstraintSet.always().with_inferable(T).satisfied_by_all_typevars()) + static_assert(ConstraintSet.always().satisfied_by_all_typevars(inferable=tuple[T])) static_assert(ConstraintSet.always().satisfied_by_all_typevars()) - static_assert(not ConstraintSet.never().with_inferable(T).satisfied_by_all_typevars()) + static_assert(not ConstraintSet.never().satisfied_by_all_typevars(inferable=tuple[T])) static_assert(not ConstraintSet.never().satisfied_by_all_typevars()) # (T = Unrelated) is a valid specialization, which satisfies (T ≤ Unrelated). - static_assert(ConstraintSet.range(Never, T, Unrelated).with_inferable(T).satisfied_by_all_typevars()) + static_assert(ConstraintSet.range(Never, T, Unrelated).satisfied_by_all_typevars(inferable=tuple[T])) # (T = Base) is a valid specialization, which does not satisfy (T ≤ Unrelated). static_assert(not ConstraintSet.range(Never, T, Unrelated).satisfied_by_all_typevars()) # (T = Base) is a valid specialization, which satisfies (T ≤ Super). - static_assert(ConstraintSet.range(Never, T, Super).with_inferable(T).satisfied_by_all_typevars()) + static_assert(ConstraintSet.range(Never, T, Super).satisfied_by_all_typevars(inferable=tuple[T])) # (T = Unrelated) is a valid specialization, which does not satisfy (T ≤ Super). static_assert(not ConstraintSet.range(Never, T, Super).satisfied_by_all_typevars()) # (T = Base) is a valid specialization, which satisfies (T ≤ Base). - static_assert(ConstraintSet.range(Never, T, Base).with_inferable(T).satisfied_by_all_typevars()) + static_assert(ConstraintSet.range(Never, T, Base).satisfied_by_all_typevars(inferable=tuple[T])) # (T = Unrelated) is a valid specialization, which does not satisfy (T ≤ Base). static_assert(not ConstraintSet.range(Never, T, Base).satisfied_by_all_typevars()) # Neither (T = Base) nor (T = Unrelated) satisfy (T ≤ Sub). - static_assert(not ConstraintSet.range(Never, T, Sub).with_inferable(T).satisfied_by_all_typevars()) + static_assert(not ConstraintSet.range(Never, T, Sub).satisfied_by_all_typevars(inferable=tuple[T])) static_assert(not ConstraintSet.range(Never, T, Sub).satisfied_by_all_typevars()) # (T = Base) and (T = Unrelated) both satisfy (T ≤ Super ∨ T ≤ Unrelated). constraints = ConstraintSet.range(Never, T, Super) | ConstraintSet.range(Never, T, Unrelated) - static_assert(constraints.with_inferable(T).satisfied_by_all_typevars()) + static_assert(constraints.satisfied_by_all_typevars(inferable=tuple[T])) static_assert(constraints.satisfied_by_all_typevars()) # (T = Base) and (T = Unrelated) both satisfy (T ≤ Base ∨ T ≤ Unrelated). constraints = ConstraintSet.range(Never, T, Base) | ConstraintSet.range(Never, T, Unrelated) - static_assert(constraints.with_inferable(T).satisfied_by_all_typevars()) + static_assert(constraints.satisfied_by_all_typevars(inferable=tuple[T])) static_assert(constraints.satisfied_by_all_typevars()) # (T = Unrelated) is a valid specialization, which satisfies (T ≤ Sub ∨ T ≤ Unrelated). constraints = ConstraintSet.range(Never, T, Sub) | ConstraintSet.range(Never, T, Unrelated) - static_assert(constraints.with_inferable(T).satisfied_by_all_typevars()) + static_assert(constraints.satisfied_by_all_typevars(inferable=tuple[T])) # (T = Base) is a valid specialization, which does not satisfy (T ≤ Sub ∨ T ≤ Unrelated). static_assert(not constraints.satisfied_by_all_typevars()) # (T = Unrelated) is a valid specialization, which satisfies (T = Super ∨ T = Unrelated). constraints = ConstraintSet.range(Super, T, Super) | ConstraintSet.range(Unrelated, T, Unrelated) - static_assert(constraints.with_inferable(T).satisfied_by_all_typevars()) + static_assert(constraints.satisfied_by_all_typevars(inferable=tuple[T])) # (T = Base) is a valid specialization, which does not satisfy (T = Super ∨ T = Unrelated). static_assert(not constraints.satisfied_by_all_typevars()) # (T = Base) and (T = Unrelated) both satisfy (T = Base ∨ T = Unrelated). constraints = ConstraintSet.range(Base, T, Base) | ConstraintSet.range(Unrelated, T, Unrelated) - static_assert(constraints.with_inferable(T).satisfied_by_all_typevars()) + static_assert(constraints.satisfied_by_all_typevars(inferable=tuple[T])) static_assert(constraints.satisfied_by_all_typevars()) # (T = Unrelated) is a valid specialization, which satisfies (T = Sub ∨ T = Unrelated). constraints = ConstraintSet.range(Sub, T, Sub) | ConstraintSet.range(Unrelated, T, Unrelated) - static_assert(constraints.with_inferable(T).satisfied_by_all_typevars()) + static_assert(constraints.satisfied_by_all_typevars(inferable=tuple[T])) # (T = Base) is a valid specialization, which does not satisfy (T = Sub ∨ T = Unrelated). static_assert(not constraints.satisfied_by_all_typevars()) ``` @@ -322,50 +322,50 @@ satisfy the constraint set. from typing import Any def constrained_by_gradual[T: (Base, Any)](): - static_assert(ConstraintSet.always().with_inferable(T).satisfied_by_all_typevars()) + static_assert(ConstraintSet.always().satisfied_by_all_typevars(inferable=tuple[T])) static_assert(ConstraintSet.always().satisfied_by_all_typevars()) - static_assert(not ConstraintSet.never().with_inferable(T).satisfied_by_all_typevars()) + static_assert(not ConstraintSet.never().satisfied_by_all_typevars(inferable=tuple[T])) static_assert(not ConstraintSet.never().satisfied_by_all_typevars()) # If we choose Unrelated as the materialization of the gradual constraint, then (T = Unrelated) # is a valid specialization, which satisfies (T ≤ Unrelated). - static_assert(ConstraintSet.range(Never, T, Unrelated).with_inferable(T).satisfied_by_all_typevars()) + static_assert(ConstraintSet.range(Never, T, Unrelated).satisfied_by_all_typevars(inferable=tuple[T])) # No matter which materialization we choose, (T = Base) is a valid specialization, which does # not satisfy (T ≤ Unrelated). static_assert(not ConstraintSet.range(Never, T, Unrelated).satisfied_by_all_typevars()) # If we choose Super as the materialization, then (T = Super) is a valid specialization, which # satisfies (T ≤ Super). - static_assert(ConstraintSet.range(Never, T, Super).with_inferable(T).satisfied_by_all_typevars()) + static_assert(ConstraintSet.range(Never, T, Super).satisfied_by_all_typevars(inferable=tuple[T])) # If we choose Never as the materialization, then (T = Base) and (T = Never) are the only valid # specializations, both of which satisfy (T ≤ Super). static_assert(ConstraintSet.range(Never, T, Super).satisfied_by_all_typevars()) # If we choose Base as the materialization, then (T = Base) is a valid specialization, which # satisfies (T ≤ Base). - static_assert(ConstraintSet.range(Never, T, Base).with_inferable(T).satisfied_by_all_typevars()) + static_assert(ConstraintSet.range(Never, T, Base).satisfied_by_all_typevars(inferable=tuple[T])) # If we choose Never as the materialization, then (T = Base) and (T = Never) are the only valid # specializations, both of which satisfy (T ≤ Base). static_assert(ConstraintSet.range(Never, T, Base).satisfied_by_all_typevars()) def constrained_by_two_gradual[T: (Any, Any)](): - static_assert(ConstraintSet.always().with_inferable(T).satisfied_by_all_typevars()) + static_assert(ConstraintSet.always().satisfied_by_all_typevars(inferable=tuple[T])) static_assert(ConstraintSet.always().satisfied_by_all_typevars()) - static_assert(not ConstraintSet.never().with_inferable(T).satisfied_by_all_typevars()) + static_assert(not ConstraintSet.never().satisfied_by_all_typevars(inferable=tuple[T])) static_assert(not ConstraintSet.never().satisfied_by_all_typevars()) # If we choose Unrelated as the materialization of either constraint, then (T = Unrelated) is a # valid specialization, which satisfies (T ≤ Unrelated). - static_assert(ConstraintSet.range(Never, T, Unrelated).with_inferable(T).satisfied_by_all_typevars()) + static_assert(ConstraintSet.range(Never, T, Unrelated).satisfied_by_all_typevars(inferable=tuple[T])) # If we choose Unrelated as the materialization of both constraints, then (T = Unrelated) is the # only valid specialization, which satisfies (T ≤ Unrelated). static_assert(ConstraintSet.range(Never, T, Unrelated).satisfied_by_all_typevars()) # If we choose Base as the materialization of either constraint, then (T = Base) is a valid # specialization, which satisfies (T ≤ Base). - static_assert(ConstraintSet.range(Never, T, Base).with_inferable(T).satisfied_by_all_typevars()) + static_assert(ConstraintSet.range(Never, T, Base).satisfied_by_all_typevars(inferable=tuple[T])) # If we choose Never as the materialization of both constraints, then (T = Never) is the only # valid specialization, which satisfies (T ≤ Base). static_assert(ConstraintSet.range(Never, T, Base).satisfied_by_all_typevars()) @@ -379,35 +379,35 @@ restrictive variance (i.e., invariance), but we get the same results for other v ```py def constrained_by_gradual[T: (list[Base], list[Any])](): - static_assert(ConstraintSet.always().with_inferable(T).satisfied_by_all_typevars()) + static_assert(ConstraintSet.always().satisfied_by_all_typevars(inferable=tuple[T])) static_assert(ConstraintSet.always().satisfied_by_all_typevars()) - static_assert(not ConstraintSet.never().with_inferable(T).satisfied_by_all_typevars()) + static_assert(not ConstraintSet.never().satisfied_by_all_typevars(inferable=tuple[T])) static_assert(not ConstraintSet.never().satisfied_by_all_typevars()) # No matter which materialization we choose, every valid specialization will be of the form # (T = list[X]). Because Unrelated is final, it is disjoint from all lists. There is therefore # no materialization or specialization that satisfies (T ≤ Unrelated). - static_assert(not ConstraintSet.range(Never, T, Unrelated).with_inferable(T).satisfied_by_all_typevars()) + static_assert(not ConstraintSet.range(Never, T, Unrelated).satisfied_by_all_typevars(inferable=tuple[T])) static_assert(not ConstraintSet.range(Never, T, Unrelated).satisfied_by_all_typevars()) # If we choose list[Super] as the materialization, then (T = list[Super]) is a valid # specialization, which satisfies (T ≤ list[Super]). - static_assert(ConstraintSet.range(Never, T, list[Super]).with_inferable(T).satisfied_by_all_typevars()) + static_assert(ConstraintSet.range(Never, T, list[Super]).satisfied_by_all_typevars(inferable=tuple[T])) # No matter which materialization we choose, (T = list[Base]) is a valid specialization, which # does not satisfy (T ≤ list[Super]). static_assert(not ConstraintSet.range(Never, T, list[Super]).satisfied_by_all_typevars()) # If we choose list[Base] as the materialization, then (T = list[Base]) is a valid # specialization, which satisfies (T ≤ list[Base]). - static_assert(ConstraintSet.range(Never, T, list[Base]).with_inferable(T).satisfied_by_all_typevars()) + static_assert(ConstraintSet.range(Never, T, list[Base]).satisfied_by_all_typevars(inferable=tuple[T])) # If we choose list[Base] as the materialization, then all valid specializations must satisfy # (T ≤ list[Base]). static_assert(ConstraintSet.range(Never, T, list[Base]).satisfied_by_all_typevars()) # If we choose list[Sub] as the materialization, then (T = list[Sub]) is a valid specialization, # which # satisfies (T ≤ list[Sub]). - static_assert(ConstraintSet.range(Never, T, list[Sub]).with_inferable(T).satisfied_by_all_typevars()) + static_assert(ConstraintSet.range(Never, T, list[Sub]).satisfied_by_all_typevars(inferable=tuple[T])) # No matter which materialization we choose, (T = list[Base]) is a valid specialization, which # does not satisfy (T ≤ list[Sub]). static_assert(not ConstraintSet.range(Never, T, list[Sub]).satisfied_by_all_typevars()) @@ -415,7 +415,7 @@ def constrained_by_gradual[T: (list[Base], list[Any])](): # If we choose list[Unrelated] as the materialization, then (T = list[Unrelated]) is a valid # specialization, which satisfies (T ≤ list[Unrelated]). constraints = ConstraintSet.range(Never, T, list[Unrelated]) - static_assert(constraints.with_inferable(T).satisfied_by_all_typevars()) + static_assert(constraints.satisfied_by_all_typevars(inferable=tuple[T])) # No matter which materialization we choose, (T = list[Base]) is a valid specialization, which # does not satisfy (T ≤ list[Unrelated]). static_assert(not constraints.satisfied_by_all_typevars()) @@ -423,42 +423,42 @@ def constrained_by_gradual[T: (list[Base], list[Any])](): # If we choose list[Unrelated] as the materialization, then (T = list[Unrelated]) is a valid # specialization, which satisfies (T ≤ list[Unrelated] ∧ T ≠ Never). constraints = constraints & ~ConstraintSet.range(Never, T, Never) - static_assert(constraints.with_inferable(T).satisfied_by_all_typevars()) + static_assert(constraints.satisfied_by_all_typevars(inferable=tuple[T])) # There is no materialization that we can choose to satisfy this constraint set in non-inferable # position. (T = Never) will be a valid assignment no matter what, and that does not satisfy # (T ≤ list[Unrelated] ∧ T ≠ Never). static_assert(not constraints.satisfied_by_all_typevars()) def constrained_by_two_gradual[T: (list[Any], list[Any])](): - static_assert(ConstraintSet.always().with_inferable(T).satisfied_by_all_typevars()) + static_assert(ConstraintSet.always().satisfied_by_all_typevars(inferable=tuple[T])) static_assert(ConstraintSet.always().satisfied_by_all_typevars()) - static_assert(not ConstraintSet.never().with_inferable(T).satisfied_by_all_typevars()) + static_assert(not ConstraintSet.never().satisfied_by_all_typevars(inferable=tuple[T])) static_assert(not ConstraintSet.never().satisfied_by_all_typevars()) # No matter which materialization we choose, every valid specialization will be of the form # (T = list[X]). Because Unrelated is final, it is disjoint from all lists. There is therefore # no materialization or specialization that satisfies (T ≤ Unrelated). - static_assert(not ConstraintSet.range(Never, T, Unrelated).with_inferable(T).satisfied_by_all_typevars()) + static_assert(not ConstraintSet.range(Never, T, Unrelated).satisfied_by_all_typevars(inferable=tuple[T])) static_assert(not ConstraintSet.range(Never, T, Unrelated).satisfied_by_all_typevars()) # If we choose list[Super] as the materialization, then (T = list[Super]) is a valid # specialization, which satisfies (T ≤ list[Super]). - static_assert(ConstraintSet.range(Never, T, list[Super]).with_inferable(T).satisfied_by_all_typevars()) + static_assert(ConstraintSet.range(Never, T, list[Super]).satisfied_by_all_typevars(inferable=tuple[T])) # No matter which materialization we choose, (T = list[Base]) is a valid specialization, which # does not satisfy (T ≤ list[Super]). static_assert(ConstraintSet.range(Never, T, list[Super]).satisfied_by_all_typevars()) # If we choose list[Base] as the materialization, then (T = list[Base]) is a valid # specialization, which satisfies (T ≤ list[Base]). - static_assert(ConstraintSet.range(Never, T, list[Base]).with_inferable(T).satisfied_by_all_typevars()) + static_assert(ConstraintSet.range(Never, T, list[Base]).satisfied_by_all_typevars(inferable=tuple[T])) # If we choose Base as the materialization, then all valid specializations must satisfy # (T ≤ list[Base]). static_assert(ConstraintSet.range(Never, T, list[Base]).satisfied_by_all_typevars()) # If we choose list[Sub] as the materialization, then (T = list[Sub]) is a valid specialization, # which satisfies (T ≤ list[Sub]). - static_assert(ConstraintSet.range(Never, T, list[Sub]).with_inferable(T).satisfied_by_all_typevars()) + static_assert(ConstraintSet.range(Never, T, list[Sub]).satisfied_by_all_typevars(inferable=tuple[T])) # No matter which materialization we choose, (T = list[Base]) is a valid specialization, which # does not satisfy (T ≤ list[Sub]). static_assert(ConstraintSet.range(Never, T, list[Sub]).satisfied_by_all_typevars()) @@ -466,7 +466,7 @@ def constrained_by_two_gradual[T: (list[Any], list[Any])](): # If we choose list[Unrelated] as the materialization, then (T = list[Unrelated]) is a valid # specialization, which satisfies (T ≤ list[Unrelated]). constraints = ConstraintSet.range(Never, T, list[Unrelated]) - static_assert(constraints.with_inferable(T).satisfied_by_all_typevars()) + static_assert(constraints.satisfied_by_all_typevars(inferable=tuple[T])) # No matter which materialization we choose, (T = list[Base]) is a valid specialization, which # does not satisfy (T ≤ list[Unrelated]). static_assert(constraints.satisfied_by_all_typevars()) @@ -474,7 +474,7 @@ def constrained_by_two_gradual[T: (list[Any], list[Any])](): # If we choose list[Unrelated] as the materialization, then (T = list[Unrelated]) is a valid # specialization, which satisfies (T ≤ list[Unrelated] ∧ T ≠ Never). constraints = constraints & ~ConstraintSet.range(Never, T, Never) - static_assert(constraints.with_inferable(T).satisfied_by_all_typevars()) + static_assert(constraints.satisfied_by_all_typevars(inferable=tuple[T])) # There is no constraint that we can choose to satisfy this constraint set in non-inferable # position. (T = Never) will be a valid assignment no matter what, and that does not satisfy # (T ≤ list[Unrelated] ∧ T ≠ Never). diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index d6930d28f1..0082e3401a 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -202,9 +202,9 @@ pub(crate) type ApplyTypeMappingVisitor<'db> = TypeTransformer<'db, TypeMapping< pub(crate) type HasRelationToVisitor<'db> = CycleDetector, (Type<'db>, Type<'db>, TypeRelation<'db>), ConstraintSet<'db>>; -impl<'db> HasRelationToVisitor<'db> { - fn from_inferable(inferable: InferableTypeVars<'db>) -> Self { - HasRelationToVisitor::new(ConstraintSet::always(inferable)) +impl Default for HasRelationToVisitor<'_> { + fn default() -> Self { + HasRelationToVisitor::new(ConstraintSet::from(true)) } } @@ -214,9 +214,9 @@ pub(crate) type IsDisjointVisitor<'db> = PairVisitor<'db, IsDisjoint, Constraint #[derive(Debug)] pub(crate) struct IsDisjoint; -impl<'db> IsDisjointVisitor<'db> { - fn from_inferable(inferable: InferableTypeVars<'db>) -> Self { - IsDisjointVisitor::new(ConstraintSet::never(inferable)) +impl Default for IsDisjointVisitor<'_> { + fn default() -> Self { + IsDisjointVisitor::new(ConstraintSet::from(false)) } } @@ -226,9 +226,9 @@ pub(crate) type IsEquivalentVisitor<'db> = PairVisitor<'db, IsEquivalent, Constr #[derive(Debug)] pub(crate) struct IsEquivalent; -impl<'db> IsEquivalentVisitor<'db> { - fn from_inferable(inferable: InferableTypeVars<'db>) -> Self { - IsEquivalentVisitor::new(ConstraintSet::always(inferable)) +impl Default for IsEquivalentVisitor<'_> { + fn default() -> Self { + IsEquivalentVisitor::new(ConstraintSet::from(true)) } } @@ -550,12 +550,7 @@ impl<'db> PropertyInstanceType<'db> { other: Self, inferable: InferableTypeVars<'db>, ) -> ConstraintSet<'db> { - self.is_equivalent_to_impl( - db, - other, - inferable, - &IsEquivalentVisitor::from_inferable(inferable), - ) + self.is_equivalent_to_impl(db, other, inferable, &IsEquivalentVisitor::default()) } fn is_equivalent_to_impl( @@ -567,27 +562,27 @@ impl<'db> PropertyInstanceType<'db> { ) -> ConstraintSet<'db> { let getter_equivalence = if let Some(getter) = self.getter(db) { let Some(other_getter) = other.getter(db) else { - return ConstraintSet::never(inferable); + return ConstraintSet::from(false); }; getter.is_equivalent_to_impl(db, other_getter, inferable, visitor) } else { if other.getter(db).is_some() { - return ConstraintSet::never(inferable); + return ConstraintSet::from(false); } - ConstraintSet::always(inferable) + ConstraintSet::from(true) }; let setter_equivalence = || { if let Some(setter) = self.setter(db) { let Some(other_setter) = other.setter(db) else { - return ConstraintSet::never(inferable); + return ConstraintSet::from(false); }; setter.is_equivalent_to_impl(db, other_setter, inferable, visitor) } else { if other.setter(db).is_some() { - return ConstraintSet::never(inferable); + return ConstraintSet::from(false); } - ConstraintSet::always(inferable) + ConstraintSet::from(true) } }; @@ -1632,11 +1627,12 @@ impl<'db> Type<'db> { db: &'db dyn Db, target: Type<'db>, constraints: ConstraintSet<'db>, + inferable: InferableTypeVars<'db>, ) -> ConstraintSet<'db> { self.has_relation_to( db, target, - constraints.inferable, + inferable, TypeRelation::ConstraintImplication(constraints), ) } @@ -1684,8 +1680,8 @@ impl<'db> Type<'db> { target, inferable, relation, - &HasRelationToVisitor::from_inferable(inferable), - &IsDisjointVisitor::from_inferable(inferable), + &HasRelationToVisitor::default(), + &IsDisjointVisitor::default(), ) } @@ -1704,7 +1700,7 @@ impl<'db> Type<'db> { // Note that we could do a full equivalence check here, but that would be both expensive // and unnecessary. This early return is only an optimisation. if (!relation.is_subtyping() || self.subtyping_is_always_reflexive()) && self == target { - return ConstraintSet::always(inferable); + return ConstraintSet::from(true); } // Handle constraint implication first. If either `self` or `target` is a typevar, check @@ -1718,22 +1714,22 @@ impl<'db> Type<'db> { match (self, target) { // Everything is a subtype of `object`. (_, Type::NominalInstance(instance)) if instance.is_object() => { - ConstraintSet::always(inferable) + ConstraintSet::from(true) } (_, Type::ProtocolInstance(target)) if target.is_equivalent_to_object(db) => { - ConstraintSet::always(inferable) + ConstraintSet::from(true) } // `Never` is the bottom type, the empty set. // It is a subtype of all other types. - (Type::Never, _) => ConstraintSet::always(inferable), + (Type::Never, _) => ConstraintSet::from(true), // In some specific situations, `Any`/`Unknown`/`@Todo` can be simplified out of unions and intersections, // but this is not true for divergent types (and moving this case any lower down appears to cause // "too many cycle iterations" panics). (Type::Dynamic(DynamicType::Divergent(_)), _) | (_, Type::Dynamic(DynamicType::Divergent(_))) => { - ConstraintSet::from_bool(relation.is_assignability(), inferable) + ConstraintSet::from(relation.is_assignability()) } (Type::TypeAlias(self_alias), _) => { @@ -1768,18 +1764,16 @@ impl<'db> Type<'db> { (Type::KnownInstance(KnownInstanceType::Field(field)), right) if relation.is_assignability() => { - field - .default_type(db) - .when_none_or(inferable, |default_type| { - default_type.has_relation_to_impl( - db, - right, - inferable, - relation, - relation_visitor, - disjointness_visitor, - ) - }) + field.default_type(db).when_none_or(|default_type| { + default_type.has_relation_to_impl( + db, + right, + inferable, + relation, + relation_visitor, + disjointness_visitor, + ) + }) } // Dynamic is only a subtype of `object` and only a supertype of `Never`; both were @@ -1795,37 +1789,31 @@ impl<'db> Type<'db> { !matches!(dynamic, DynamicType::Divergent(_)), "DynamicType::Divergent should have been handled in an earlier branch" ); - ConstraintSet::from_bool( - match relation { - TypeRelation::Subtyping | TypeRelation::ConstraintImplication(_) => false, - TypeRelation::Assignability => true, - TypeRelation::Redundancy => match target { - Type::Dynamic(_) => true, - Type::Union(union) => union.elements(db).iter().any(Type::is_dynamic), - _ => false, - }, - }, - inferable, - ) - } - (_, Type::Dynamic(_)) => ConstraintSet::from_bool( - match relation { + ConstraintSet::from(match relation { TypeRelation::Subtyping | TypeRelation::ConstraintImplication(_) => false, TypeRelation::Assignability => true, - TypeRelation::Redundancy => match self { + TypeRelation::Redundancy => match target { Type::Dynamic(_) => true, - Type::Intersection(intersection) => { - // If a `Divergent` type is involved, it must not be eliminated. - intersection - .positive(db) - .iter() - .any(Type::is_non_divergent_dynamic) - } + Type::Union(union) => union.elements(db).iter().any(Type::is_dynamic), _ => false, }, + }) + } + (_, Type::Dynamic(_)) => ConstraintSet::from(match relation { + TypeRelation::Subtyping | TypeRelation::ConstraintImplication(_) => false, + TypeRelation::Assignability => true, + TypeRelation::Redundancy => match self { + Type::Dynamic(_) => true, + Type::Intersection(intersection) => { + // If a `Divergent` type is involved, it must not be eliminated. + intersection + .positive(db) + .iter() + .any(Type::is_non_divergent_dynamic) + } + _ => false, }, - inferable, - ), + }), // In general, a TypeVar `T` is not a subtype of a type `S` unless one of the two conditions is satisfied: // 1. `T` is a bound TypeVar and `T`'s upper bound is a subtype of `S`. @@ -1839,19 +1827,19 @@ impl<'db> Type<'db> { if !bound_typevar.is_inferable(db, inferable) && union.elements(db).contains(&self) => { - ConstraintSet::always(inferable) + ConstraintSet::from(true) } (Type::Intersection(intersection), Type::TypeVar(bound_typevar)) if !bound_typevar.is_inferable(db, inferable) && intersection.positive(db).contains(&target) => { - ConstraintSet::always(inferable) + ConstraintSet::from(true) } (Type::Intersection(intersection), Type::TypeVar(bound_typevar)) if !bound_typevar.is_inferable(db, inferable) && intersection.negative(db).contains(&target) => { - ConstraintSet::never(inferable) + ConstraintSet::from(false) } // Two identical typevars must always solve to the same type, so they are always @@ -1863,7 +1851,7 @@ impl<'db> Type<'db> { if !lhs_bound_typevar.is_inferable(db, inferable) && lhs_bound_typevar.is_same_typevar_as(db, rhs_bound_typevar) => { - ConstraintSet::always(inferable) + ConstraintSet::from(true) } // A fully static typevar is a subtype of its upper bound, and to something similar to @@ -1884,10 +1872,8 @@ impl<'db> Type<'db> { relation_visitor, disjointness_visitor, ), - Some(TypeVarBoundOrConstraints::Constraints(constraints)) => constraints - .elements(db) - .iter() - .when_all(db, inferable, |constraint| { + Some(TypeVarBoundOrConstraints::Constraints(constraints)) => { + constraints.elements(db).iter().when_all(db, |constraint| { constraint.has_relation_to_impl( db, target, @@ -1896,7 +1882,8 @@ impl<'db> Type<'db> { relation_visitor, disjointness_visitor, ) - }), + }) + } } } @@ -1908,8 +1895,8 @@ impl<'db> Type<'db> { && !bound_typevar .typevar(db) .constraints(db) - .when_some_and(inferable, |constraints| { - constraints.iter().when_all(db, inferable, |constraint| { + .when_some_and(|constraints| { + constraints.iter().when_all(db, |constraint| { self.has_relation_to_impl( db, *constraint, @@ -1929,8 +1916,8 @@ impl<'db> Type<'db> { bound_typevar .typevar(db) .constraints(db) - .when_some_and(inferable, |constraints| { - constraints.iter().when_all(db, inferable, |constraint| { + .when_some_and(|constraints| { + constraints.iter().when_all(db, |constraint| { self.has_relation_to_impl( db, *constraint, @@ -1949,43 +1936,33 @@ impl<'db> Type<'db> { // TODO: record the unification constraints - ConstraintSet::always(inferable) + ConstraintSet::from(true) } // `Never` is the bottom type, the empty set. - (_, Type::Never) => ConstraintSet::never(inferable), + (_, Type::Never) => ConstraintSet::from(false), - (Type::Union(union), _) => { - union - .elements(db) - .iter() - .when_all(db, inferable, |&elem_ty| { - elem_ty.has_relation_to_impl( - db, - target, - inferable, - relation, - relation_visitor, - disjointness_visitor, - ) - }) - } + (Type::Union(union), _) => union.elements(db).iter().when_all(db, |&elem_ty| { + elem_ty.has_relation_to_impl( + db, + target, + inferable, + relation, + relation_visitor, + disjointness_visitor, + ) + }), - (_, Type::Union(union)) => { - union - .elements(db) - .iter() - .when_any(db, inferable, |&elem_ty| { - self.has_relation_to_impl( - db, - elem_ty, - inferable, - relation, - relation_visitor, - disjointness_visitor, - ) - }) - } + (_, Type::Union(union)) => union.elements(db).iter().when_any(db, |&elem_ty| { + self.has_relation_to_impl( + db, + elem_ty, + inferable, + relation, + relation_visitor, + disjointness_visitor, + ) + }), // If both sides are intersections we need to handle the right side first // (A & B & C) is a subtype of (A & B) because the left is a subtype of both A and B, @@ -1993,7 +1970,7 @@ impl<'db> Type<'db> { (_, Type::Intersection(intersection)) => intersection .positive(db) .iter() - .when_all(db, inferable, |&pos_ty| { + .when_all(db, |&pos_ty| { self.has_relation_to_impl( db, pos_ty, @@ -2023,40 +2000,34 @@ impl<'db> Type<'db> { | TypeRelation::ConstraintImplication(_) => self, TypeRelation::Assignability => self.bottom_materialization(db), }; - intersection - .negative(db) - .iter() - .when_all(db, inferable, |&neg_ty| { - let neg_ty = match relation { - TypeRelation::Subtyping - | TypeRelation::Redundancy - | TypeRelation::ConstraintImplication(_) => neg_ty, - TypeRelation::Assignability => neg_ty.bottom_materialization(db), - }; - self_ty.is_disjoint_from_impl( - db, - neg_ty, - inferable, - disjointness_visitor, - relation_visitor, - ) - }) + intersection.negative(db).iter().when_all(db, |&neg_ty| { + let neg_ty = match relation { + TypeRelation::Subtyping + | TypeRelation::Redundancy + | TypeRelation::ConstraintImplication(_) => neg_ty, + TypeRelation::Assignability => neg_ty.bottom_materialization(db), + }; + self_ty.is_disjoint_from_impl( + db, + neg_ty, + inferable, + disjointness_visitor, + relation_visitor, + ) + }) }), (Type::Intersection(intersection), _) => { - intersection - .positive(db) - .iter() - .when_any(db, inferable, |&elem_ty| { - elem_ty.has_relation_to_impl( - db, - target, - inferable, - relation, - relation_visitor, - disjointness_visitor, - ) - }) + intersection.positive(db).iter().when_any(db, |&elem_ty| { + elem_ty.has_relation_to_impl( + db, + target, + inferable, + relation, + relation_visitor, + disjointness_visitor, + ) + }) } // Other than the special cases checked above, no other types are a subtype of a @@ -2065,7 +2036,7 @@ impl<'db> Type<'db> { // bound. This is true even if the bound is a final class, since the typevar can still // be specialized to `Never`.) (_, Type::TypeVar(bound_typevar)) if !bound_typevar.is_inferable(db, inferable) => { - ConstraintSet::never(inferable) + ConstraintSet::from(false) } (_, Type::TypeVar(typevar)) @@ -2086,47 +2057,40 @@ impl<'db> Type<'db> { { // TODO: record the unification constraints - typevar - .typevar(db) - .upper_bound(db) - .when_none_or(inferable, |bound| { - self.has_relation_to_impl( - db, - bound, - inferable, - relation, - relation_visitor, - disjointness_visitor, - ) - }) + typevar.typevar(db).upper_bound(db).when_none_or(|bound| { + self.has_relation_to_impl( + db, + bound, + inferable, + relation, + relation_visitor, + disjointness_visitor, + ) + }) } // TODO: Infer specializations here (_, Type::TypeVar(bound_typevar)) if bound_typevar.is_inferable(db, inferable) => { - ConstraintSet::never(inferable) + ConstraintSet::from(false) } (Type::TypeVar(bound_typevar), _) => { // All inferable cases should have been handled above assert!(!bound_typevar.is_inferable(db, inferable)); - ConstraintSet::never(inferable) + ConstraintSet::from(false) } (Type::TypedDict(_), _) => { // TODO: Implement assignability and subtyping for TypedDict - ConstraintSet::from_bool(relation.is_assignability(), inferable) + ConstraintSet::from(relation.is_assignability()) } // A non-`TypedDict` cannot subtype a `TypedDict` - (_, Type::TypedDict(_)) => ConstraintSet::never(inferable), + (_, Type::TypedDict(_)) => ConstraintSet::from(false), // Note that the definition of `Type::AlwaysFalsy` depends on the return value of `__bool__`. // If `__bool__` always returns True or False, it can be treated as a subtype of `AlwaysTruthy` or `AlwaysFalsy`, respectively. - (left, Type::AlwaysFalsy) => { - ConstraintSet::from_bool(left.bool(db).is_always_false(), inferable) - } - (left, Type::AlwaysTruthy) => { - ConstraintSet::from_bool(left.bool(db).is_always_true(), inferable) - } + (left, Type::AlwaysFalsy) => ConstraintSet::from(left.bool(db).is_always_false()), + (left, Type::AlwaysTruthy) => ConstraintSet::from(left.bool(db).is_always_true()), // Currently, the only supertype of `AlwaysFalsy` and `AlwaysTruthy` is the universal set (object instance). (Type::AlwaysFalsy | Type::AlwaysTruthy, _) => { target.when_equivalent_to(db, Type::object(), inferable) @@ -2186,7 +2150,7 @@ impl<'db> Type<'db> { | Type::FunctionLiteral(_) | Type::ModuleLiteral(_) | Type::EnumLiteral(_), - ) => ConstraintSet::never(inferable), + ) => ConstraintSet::from(false), (Type::Callable(self_callable), Type::Callable(other_callable)) => relation_visitor .visit((self, target, relation), || { @@ -2201,17 +2165,16 @@ impl<'db> Type<'db> { }), (_, Type::Callable(_)) => relation_visitor.visit((self, target, relation), || { - self.try_upcast_to_callable(db) - .when_some_and(inferable, |callable| { - callable.has_relation_to_impl( - db, - target, - inferable, - relation, - relation_visitor, - disjointness_visitor, - ) - }) + self.try_upcast_to_callable(db).when_some_and(|callable| { + callable.has_relation_to_impl( + db, + target, + inferable, + relation, + relation_visitor, + disjointness_visitor, + ) + }) }), (_, Type::ProtocolInstance(protocol)) => { @@ -2228,22 +2191,22 @@ impl<'db> Type<'db> { } // A protocol instance can never be a subtype of a nominal type, with the *sole* exception of `object`. - (Type::ProtocolInstance(_), _) => ConstraintSet::never(inferable), + (Type::ProtocolInstance(_), _) => ConstraintSet::from(false), // All `StringLiteral` types are a subtype of `LiteralString`. - (Type::StringLiteral(_), Type::LiteralString) => ConstraintSet::always(inferable), + (Type::StringLiteral(_), Type::LiteralString) => ConstraintSet::from(true), // An instance is a subtype of an enum literal, if it is an instance of the enum class // and the enum has only one member. (Type::NominalInstance(_), Type::EnumLiteral(target_enum_literal)) => { if target_enum_literal.enum_class_instance(db) != self { - return ConstraintSet::never(inferable); + return ConstraintSet::from(false); } - ConstraintSet::from_bool( - is_single_member_enum(db, target_enum_literal.enum_class(db)), - inferable, - ) + ConstraintSet::from(is_single_member_enum( + db, + target_enum_literal.enum_class(db), + )) } // Except for the special `LiteralString` case above, @@ -2259,7 +2222,7 @@ impl<'db> Type<'db> { | Type::EnumLiteral(_) | Type::FunctionLiteral(_), _, - ) => (self.literal_fallback_instance(db)).when_some_and(inferable, |instance| { + ) => (self.literal_fallback_instance(db)).when_some_and(|instance| { instance.has_relation_to_impl( db, target, @@ -2304,7 +2267,7 @@ impl<'db> Type<'db> { (Type::DataclassDecorator(_) | Type::DataclassTransformer(_), _) => { // TODO: Implement subtyping using an equivalent `Callable` type. - ConstraintSet::never(inferable) + ConstraintSet::from(false) } // `TypeIs` is invariant. @@ -2353,7 +2316,7 @@ impl<'db> Type<'db> { ) } - (Type::Callable(_), _) => ConstraintSet::never(inferable), + (Type::Callable(_), _) => ConstraintSet::from(false), (Type::BoundSuper(_), Type::BoundSuper(_)) => { self.when_equivalent_to(db, target, inferable) @@ -2382,9 +2345,7 @@ impl<'db> Type<'db> { disjointness_visitor, ) }) - .unwrap_or_else(|| { - ConstraintSet::from_bool(relation.is_assignability(), inferable) - }), + .unwrap_or_else(|| ConstraintSet::from(relation.is_assignability())), (Type::GenericAlias(alias), Type::SubclassOf(target_subclass_ty)) => target_subclass_ty .subclass_of() .into_class() @@ -2398,9 +2359,7 @@ impl<'db> Type<'db> { disjointness_visitor, ) }) - .unwrap_or_else(|| { - ConstraintSet::from_bool(relation.is_assignability(), inferable) - }), + .unwrap_or_else(|| ConstraintSet::from(relation.is_assignability())), // This branch asks: given two types `type[T]` and `type[S]`, is `type[T]` a subtype of `type[S]`? (Type::SubclassOf(self_subclass_ty), Type::SubclassOf(target_subclass_ty)) => { @@ -2451,19 +2410,16 @@ impl<'db> Type<'db> { disjointness_visitor, ) .or(db, || { - ConstraintSet::from_bool(relation.is_assignability(), inferable).and( - db, - || { - other.has_relation_to_impl( - db, - KnownClass::Type.to_instance(db), - inferable, - relation, - relation_visitor, - disjointness_visitor, - ) - }, - ) + ConstraintSet::from(relation.is_assignability()).and(db, || { + other.has_relation_to_impl( + db, + KnownClass::Type.to_instance(db), + inferable, + relation, + relation_visitor, + disjointness_visitor, + ) + }) }) } @@ -2539,7 +2495,7 @@ impl<'db> Type<'db> { } (Type::NewTypeInstance(self_newtype), Type::NewTypeInstance(target_newtype)) => { - self_newtype.has_relation_to_impl(db, target_newtype, inferable) + self_newtype.has_relation_to_impl(db, target_newtype) } ( @@ -2575,8 +2531,8 @@ impl<'db> Type<'db> { // Other than the special cases enumerated above, nominal-instance types, and // newtype-instance types are never subtypes of any other variants - (Type::NominalInstance(_), _) => ConstraintSet::never(inferable), - (Type::NewTypeInstance(_), _) => ConstraintSet::never(inferable), + (Type::NominalInstance(_), _) => ConstraintSet::from(false), + (Type::NewTypeInstance(_), _) => ConstraintSet::from(false), } } @@ -2603,12 +2559,7 @@ impl<'db> Type<'db> { other: Type<'db>, inferable: InferableTypeVars<'db>, ) -> ConstraintSet<'db> { - self.is_equivalent_to_impl( - db, - other, - inferable, - &IsEquivalentVisitor::from_inferable(inferable), - ) + self.is_equivalent_to_impl(db, other, inferable, &IsEquivalentVisitor::default()) } pub(crate) fn is_equivalent_to_impl( @@ -2619,7 +2570,7 @@ impl<'db> Type<'db> { visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { if self == other { - return ConstraintSet::always(inferable); + return ConstraintSet::from(true); } match (self, other) { @@ -2627,17 +2578,17 @@ impl<'db> Type<'db> { // which prevents `Divergent` from being eliminated during union reduction. (Type::Dynamic(_), Type::Dynamic(DynamicType::Divergent(_))) | (Type::Dynamic(DynamicType::Divergent(_)), Type::Dynamic(_)) => { - ConstraintSet::never(inferable) + ConstraintSet::from(false) } - (Type::Dynamic(_), Type::Dynamic(_)) => ConstraintSet::always(inferable), + (Type::Dynamic(_), Type::Dynamic(_)) => ConstraintSet::from(true), (Type::SubclassOf(first), Type::SubclassOf(second)) => { match (first.subclass_of(), second.subclass_of()) { - (first, second) if first == second => ConstraintSet::always(inferable), + (first, second) if first == second => ConstraintSet::from(true), (SubclassOfInner::Dynamic(_), SubclassOfInner::Dynamic(_)) => { - ConstraintSet::always(inferable) + ConstraintSet::from(true) } - _ => ConstraintSet::never(inferable), + _ => ConstraintSet::from(false), } } @@ -2656,10 +2607,7 @@ impl<'db> Type<'db> { } (Type::NewTypeInstance(self_newtype), Type::NewTypeInstance(other_newtype)) => { - ConstraintSet::from_bool( - self_newtype.is_equivalent_to_impl(db, other_newtype), - inferable, - ) + ConstraintSet::from(self_newtype.is_equivalent_to_impl(db, other_newtype)) } (Type::NominalInstance(first), Type::NominalInstance(second)) => { @@ -2692,29 +2640,23 @@ impl<'db> Type<'db> { } (Type::ProtocolInstance(protocol), nominal @ Type::NominalInstance(n)) | (nominal @ Type::NominalInstance(n), Type::ProtocolInstance(protocol)) => { - ConstraintSet::from_bool( - n.is_object() && protocol.normalized(db) == nominal, - inferable, - ) + ConstraintSet::from(n.is_object() && protocol.normalized(db) == nominal) } // An instance of an enum class is equivalent to an enum literal of that class, // if that enum has only has one member. (Type::NominalInstance(instance), Type::EnumLiteral(literal)) | (Type::EnumLiteral(literal), Type::NominalInstance(instance)) => { if literal.enum_class_instance(db) != Type::NominalInstance(instance) { - return ConstraintSet::never(inferable); + return ConstraintSet::from(false); } - ConstraintSet::from_bool( - is_single_member_enum(db, instance.class_literal(db)), - inferable, - ) + ConstraintSet::from(is_single_member_enum(db, instance.class_literal(db))) } (Type::PropertyInstance(left), Type::PropertyInstance(right)) => { left.is_equivalent_to_impl(db, right, inferable, visitor) } - _ => ConstraintSet::never(inferable), + _ => ConstraintSet::from(false), } } @@ -2748,8 +2690,8 @@ impl<'db> Type<'db> { db, other, inferable, - &IsDisjointVisitor::from_inferable(inferable), - &HasRelationToVisitor::from_inferable(inferable), + &IsDisjointVisitor::default(), + &HasRelationToVisitor::default(), ) } @@ -2769,30 +2711,27 @@ impl<'db> Type<'db> { disjointness_visitor: &IsDisjointVisitor<'db>, relation_visitor: &HasRelationToVisitor<'db>, ) -> ConstraintSet<'db> { - protocol - .interface(db) - .members(db) - .when_any(db, inferable, |member| { - other - .member(db, member.name()) - .place - .ignore_possibly_undefined() - .when_none_or(inferable, |attribute_type| { - member.has_disjoint_type_from( - db, - attribute_type, - inferable, - disjointness_visitor, - relation_visitor, - ) - }) - }) + protocol.interface(db).members(db).when_any(db, |member| { + other + .member(db, member.name()) + .place + .ignore_possibly_undefined() + .when_none_or(|attribute_type| { + member.has_disjoint_type_from( + db, + attribute_type, + inferable, + disjointness_visitor, + relation_visitor, + ) + }) + }) } match (self, other) { - (Type::Never, _) | (_, Type::Never) => ConstraintSet::always(inferable), + (Type::Never, _) | (_, Type::Never) => ConstraintSet::from(true), - (Type::Dynamic(_), _) | (_, Type::Dynamic(_)) => ConstraintSet::never(inferable), + (Type::Dynamic(_), _) | (_, Type::Dynamic(_)) => ConstraintSet::from(false), (Type::TypeAlias(alias), _) => { let self_alias_ty = alias.value_type(db); @@ -2822,7 +2761,7 @@ impl<'db> Type<'db> { (Type::TypedDict(_), _) | (_, Type::TypedDict(_)) => { // TODO: Implement disjointness for TypedDict - ConstraintSet::never(inferable) + ConstraintSet::from(false) } // A typevar is never disjoint from itself, since all occurrences of the typevar must @@ -2833,7 +2772,7 @@ impl<'db> Type<'db> { if !self_bound_typevar.is_inferable(db, inferable) && self_bound_typevar.is_same_typevar_as(db, other_bound_typevar) => { - ConstraintSet::never(inferable) + ConstraintSet::from(false) } (tvar @ Type::TypeVar(bound_typevar), Type::Intersection(intersection)) @@ -2841,7 +2780,7 @@ impl<'db> Type<'db> { if !bound_typevar.is_inferable(db, inferable) && intersection.negative(db).contains(&tvar) => { - ConstraintSet::always(inferable) + ConstraintSet::from(true) } // An unbounded typevar is never disjoint from any other type, since it might be @@ -2852,7 +2791,7 @@ impl<'db> Type<'db> { if !bound_typevar.is_inferable(db, inferable) => { match bound_typevar.typevar(db).bound_or_constraints(db) { - None => ConstraintSet::never(inferable), + None => ConstraintSet::from(false), Some(TypeVarBoundOrConstraints::UpperBound(bound)) => bound .is_disjoint_from_impl( db, @@ -2861,10 +2800,8 @@ impl<'db> Type<'db> { disjointness_visitor, relation_visitor, ), - Some(TypeVarBoundOrConstraints::Constraints(constraints)) => constraints - .elements(db) - .iter() - .when_all(db, inferable, |constraint| { + Some(TypeVarBoundOrConstraints::Constraints(constraints)) => { + constraints.elements(db).iter().when_all(db, |constraint| { constraint.is_disjoint_from_impl( db, other, @@ -2872,15 +2809,16 @@ impl<'db> Type<'db> { disjointness_visitor, relation_visitor, ) - }), + }) + } } } // TODO: Infer specializations here - (Type::TypeVar(_), _) | (_, Type::TypeVar(_)) => ConstraintSet::never(inferable), + (Type::TypeVar(_), _) | (_, Type::TypeVar(_)) => ConstraintSet::from(false), (Type::Union(union), other) | (other, Type::Union(union)) => { - union.elements(db).iter().when_all(db, inferable, |e| { + union.elements(db).iter().when_all(db, |e| { e.is_disjoint_from_impl( db, other, @@ -2899,7 +2837,7 @@ impl<'db> Type<'db> { self_intersection .positive(db) .iter() - .when_any(db, inferable, |p| { + .when_any(db, |p| { p.is_disjoint_from_impl( db, other, @@ -2909,18 +2847,15 @@ impl<'db> Type<'db> { ) }) .or(db, || { - other_intersection - .positive(db) - .iter() - .when_any(db, inferable, |p| { - p.is_disjoint_from_impl( - db, - self, - inferable, - disjointness_visitor, - relation_visitor, - ) - }) + other_intersection.positive(db).iter().when_any(db, |p| { + p.is_disjoint_from_impl( + db, + self, + inferable, + disjointness_visitor, + relation_visitor, + ) + }) }) }) } @@ -2931,7 +2866,7 @@ impl<'db> Type<'db> { intersection .positive(db) .iter() - .when_any(db, inferable, |p| { + .when_any(db, |p| { p.is_disjoint_from_impl( db, non_intersection, @@ -2942,19 +2877,16 @@ impl<'db> Type<'db> { }) // A & B & Not[C] is disjoint from C .or(db, || { - intersection - .negative(db) - .iter() - .when_any(db, inferable, |&neg_ty| { - non_intersection.has_relation_to_impl( - db, - neg_ty, - inferable, - TypeRelation::Subtyping, - relation_visitor, - disjointness_visitor, - ) - }) + intersection.negative(db).iter().when_any(db, |&neg_ty| { + non_intersection.has_relation_to_impl( + db, + neg_ty, + inferable, + TypeRelation::Subtyping, + relation_visitor, + disjointness_visitor, + ) + }) }) }) } @@ -2990,7 +2922,7 @@ impl<'db> Type<'db> { | Type::GenericAlias(..) | Type::SpecialForm(..) | Type::KnownInstance(..)), - ) => ConstraintSet::from_bool(left != right, inferable), + ) => ConstraintSet::from(left != right), ( Type::SubclassOf(_), @@ -3019,16 +2951,16 @@ impl<'db> Type<'db> { | Type::WrapperDescriptor(..) | Type::ModuleLiteral(..), Type::SubclassOf(_), - ) => ConstraintSet::always(inferable), + ) => ConstraintSet::from(true), (Type::AlwaysTruthy, ty) | (ty, Type::AlwaysTruthy) => { // `Truthiness::Ambiguous` may include `AlwaysTrue` as a subset, so it's not guaranteed to be disjoint. // Thus, they are only disjoint if `ty.bool() == AlwaysFalse`. - ConstraintSet::from_bool(ty.bool(db).is_always_false(), inferable) + ConstraintSet::from(ty.bool(db).is_always_false()) } (Type::AlwaysFalsy, ty) | (ty, Type::AlwaysFalsy) => { // Similarly, they are only disjoint if `ty.bool() == AlwaysTrue`. - ConstraintSet::from_bool(ty.bool(db).is_always_true(), inferable) + ConstraintSet::from(ty.bool(db).is_always_true()) } (Type::ProtocolInstance(left), Type::ProtocolInstance(right)) => disjointness_visitor @@ -3149,29 +3081,25 @@ impl<'db> Type<'db> { (Type::ProtocolInstance(protocol), other) | (other, Type::ProtocolInstance(protocol)) => { disjointness_visitor.visit((self, other), || { - protocol - .interface(db) - .members(db) - .when_any(db, inferable, |member| { - match other.member(db, member.name()).place { - Place::Defined(attribute_type, _, _) => member - .has_disjoint_type_from( - db, - attribute_type, - inferable, - disjointness_visitor, - relation_visitor, - ), - Place::Undefined => ConstraintSet::never(inferable), - } - }) + protocol.interface(db).members(db).when_any(db, |member| { + match other.member(db, member.name()).place { + Place::Defined(attribute_type, _, _) => member.has_disjoint_type_from( + db, + attribute_type, + inferable, + disjointness_visitor, + relation_visitor, + ), + Place::Undefined => ConstraintSet::from(false), + } + }) }) } (Type::SubclassOf(subclass_of_ty), Type::ClassLiteral(class_b)) | (Type::ClassLiteral(class_b), Type::SubclassOf(subclass_of_ty)) => { match subclass_of_ty.subclass_of() { - SubclassOfInner::Dynamic(_) => ConstraintSet::never(inferable), + SubclassOfInner::Dynamic(_) => ConstraintSet::from(false), SubclassOfInner::Class(class_a) => { class_b.when_subclass_of(db, None, class_a).negate(db) } @@ -3181,7 +3109,7 @@ impl<'db> Type<'db> { (Type::SubclassOf(subclass_of_ty), Type::GenericAlias(alias_b)) | (Type::GenericAlias(alias_b), Type::SubclassOf(subclass_of_ty)) => { match subclass_of_ty.subclass_of() { - SubclassOfInner::Dynamic(_) => ConstraintSet::never(inferable), + SubclassOfInner::Dynamic(_) => ConstraintSet::from(false), SubclassOfInner::Class(class_a) => ClassType::from(alias_b) .when_subclass_of(db, class_a, inferable) .negate(db), @@ -3218,18 +3146,12 @@ impl<'db> Type<'db> { (Type::SpecialForm(special_form), Type::NominalInstance(instance)) | (Type::NominalInstance(instance), Type::SpecialForm(special_form)) => { - ConstraintSet::from_bool( - !special_form.is_instance_of(db, instance.class(db)), - inferable, - ) + ConstraintSet::from(!special_form.is_instance_of(db, instance.class(db))) } (Type::KnownInstance(known_instance), Type::NominalInstance(instance)) | (Type::NominalInstance(instance), Type::KnownInstance(known_instance)) => { - ConstraintSet::from_bool( - !known_instance.is_instance_of(db, instance.class(db)), - inferable, - ) + ConstraintSet::from(!known_instance.is_instance_of(db, instance.class(db))) } (Type::BooleanLiteral(..) | Type::TypeIs(_), Type::NominalInstance(instance)) @@ -3242,7 +3164,7 @@ impl<'db> Type<'db> { } (Type::BooleanLiteral(..) | Type::TypeIs(_), _) - | (_, Type::BooleanLiteral(..) | Type::TypeIs(_)) => ConstraintSet::always(inferable), + | (_, Type::BooleanLiteral(..) | Type::TypeIs(_)) => ConstraintSet::from(true), (Type::IntLiteral(..), Type::NominalInstance(instance)) | (Type::NominalInstance(instance), Type::IntLiteral(..)) => { @@ -3253,12 +3175,10 @@ impl<'db> Type<'db> { .negate(db) } - (Type::IntLiteral(..), _) | (_, Type::IntLiteral(..)) => { - ConstraintSet::always(inferable) - } + (Type::IntLiteral(..), _) | (_, Type::IntLiteral(..)) => ConstraintSet::from(true), (Type::StringLiteral(..), Type::LiteralString) - | (Type::LiteralString, Type::StringLiteral(..)) => ConstraintSet::never(inferable), + | (Type::LiteralString, Type::StringLiteral(..)) => ConstraintSet::from(false), (Type::StringLiteral(..) | Type::LiteralString, Type::NominalInstance(instance)) | (Type::NominalInstance(instance), Type::StringLiteral(..) | Type::LiteralString) => { @@ -3269,8 +3189,8 @@ impl<'db> Type<'db> { .negate(db) } - (Type::LiteralString, Type::LiteralString) => ConstraintSet::never(inferable), - (Type::LiteralString, _) | (_, Type::LiteralString) => ConstraintSet::always(inferable), + (Type::LiteralString, Type::LiteralString) => ConstraintSet::from(false), + (Type::LiteralString, _) | (_, Type::LiteralString) => ConstraintSet::from(true), (Type::BytesLiteral(..), Type::NominalInstance(instance)) | (Type::NominalInstance(instance), Type::BytesLiteral(..)) => { @@ -3295,9 +3215,7 @@ impl<'db> Type<'db> { ) .negate(db) } - (Type::EnumLiteral(..), _) | (_, Type::EnumLiteral(..)) => { - ConstraintSet::always(inferable) - } + (Type::EnumLiteral(..), _) | (_, Type::EnumLiteral(..)) => ConstraintSet::from(true), // A class-literal type `X` is always disjoint from an instance type `Y`, // unless the type expressing "all instances of `Z`" is a subtype of of `Y`, @@ -3368,7 +3286,7 @@ impl<'db> Type<'db> { // No two callable types are ever disjoint because // `(*args: object, **kwargs: object) -> Never` is a subtype of all fully static // callable types. - ConstraintSet::never(inferable) + ConstraintSet::from(false) } (Type::Callable(_), Type::StringLiteral(_) | Type::BytesLiteral(_)) @@ -3376,7 +3294,7 @@ impl<'db> Type<'db> { // A callable type is disjoint from other literal types. For example, // `Type::StringLiteral` must be an instance of exactly `str`, not a subclass // of `str`, and `str` is not callable. The same applies to other literal types. - ConstraintSet::always(inferable) + ConstraintSet::from(true) } (Type::Callable(_), Type::SpecialForm(special_form)) @@ -3385,7 +3303,7 @@ impl<'db> Type<'db> { // that are callable (like TypedDict and collection constructors). // Most special forms are type constructors/annotations (like `typing.Literal`, // `typing.Union`, etc.) that are subscripted, not called. - ConstraintSet::from_bool(!special_form.is_callable(), inferable) + ConstraintSet::from(!special_form.is_callable()) } ( @@ -3403,7 +3321,7 @@ impl<'db> Type<'db> { ) .place .ignore_possibly_undefined() - .when_none_or(inferable, |dunder_call| { + .when_none_or(|dunder_call| { dunder_call .has_relation_to_impl( db, @@ -3425,7 +3343,7 @@ impl<'db> Type<'db> { Type::Callable(_) | Type::DataclassDecorator(_) | Type::DataclassTransformer(_), ) => { // TODO: Implement disjointness for general callable type with other types - ConstraintSet::never(inferable) + ConstraintSet::from(false) } (Type::ModuleLiteral(..), other @ Type::NominalInstance(..)) @@ -3452,7 +3370,7 @@ impl<'db> Type<'db> { }), (Type::NewTypeInstance(left), Type::NewTypeInstance(right)) => { - left.is_disjoint_from_impl(db, right, inferable) + left.is_disjoint_from_impl(db, right) } (Type::NewTypeInstance(newtype), other) | (other, Type::NewTypeInstance(newtype)) => { Type::instance(db, newtype.base_class_type(db)).is_disjoint_from_impl( @@ -4400,14 +4318,6 @@ impl<'db> Type<'db> { )) .into() } - Type::KnownInstance(KnownInstanceType::ConstraintSet(tracked)) - if name == "with_inferable" => - { - Place::bound(Type::KnownBoundMethod( - KnownBoundMethodType::ConstraintSetWithInferable(tracked), - )) - .into() - } Type::KnownInstance(KnownInstanceType::ConstraintSet(tracked)) if name == "implies_subtype_of" => { @@ -4959,7 +4869,7 @@ impl<'db> Type<'db> { Type::KnownInstance(KnownInstanceType::ConstraintSet(tracked_set)) => { let constraints = tracked_set.constraints(db); - Truthiness::from(constraints.satisfied_by_all_typevars(db)) + Truthiness::from(constraints.is_always_satisfied(db)) } Type::FunctionLiteral(_) @@ -7253,7 +7163,6 @@ impl<'db> Type<'db> { | KnownBoundMethodType::ConstraintSetRange | KnownBoundMethodType::ConstraintSetAlways | KnownBoundMethodType::ConstraintSetNever - | KnownBoundMethodType::ConstraintSetWithInferable(_) | KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_) | KnownBoundMethodType::ConstraintSetSatisfies(_) | KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(_) @@ -7414,7 +7323,6 @@ impl<'db> Type<'db> { | KnownBoundMethodType::ConstraintSetRange | KnownBoundMethodType::ConstraintSetAlways | KnownBoundMethodType::ConstraintSetNever - | KnownBoundMethodType::ConstraintSetWithInferable(_) | KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_) | KnownBoundMethodType::ConstraintSetSatisfies(_) | KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(_), @@ -10811,7 +10719,7 @@ impl<'db> CallableType<'db> { disjointness_visitor: &IsDisjointVisitor<'db>, ) -> ConstraintSet<'db> { if other.is_function_like(db) && !self.is_function_like(db) { - return ConstraintSet::never(inferable); + return ConstraintSet::from(false); } self.signatures(db).has_relation_to_impl( db, @@ -10834,14 +10742,10 @@ impl<'db> CallableType<'db> { visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { if self == other { - return ConstraintSet::always(inferable); + return ConstraintSet::from(true); } - ConstraintSet::from_bool( - self.is_function_like(db) == other.is_function_like(db), - inferable, - ) - .and(db, || { + ConstraintSet::from(self.is_function_like(db) == other.is_function_like(db)).and(db, || { self.signatures(db) .is_equivalent_to_impl(db, other.signatures(db), inferable, visitor) }) @@ -10877,7 +10781,6 @@ pub enum KnownBoundMethodType<'db> { ConstraintSetRange, ConstraintSetAlways, ConstraintSetNever, - ConstraintSetWithInferable(TrackedConstraintSet<'db>), ConstraintSetImpliesSubtypeOf(TrackedConstraintSet<'db>), ConstraintSetSatisfies(TrackedConstraintSet<'db>), ConstraintSetSatisfiedByAllTypeVars(TrackedConstraintSet<'db>), @@ -10908,7 +10811,6 @@ pub(super) fn walk_method_wrapper_type<'db, V: visitor::TypeVisitor<'db> + ?Size | KnownBoundMethodType::ConstraintSetRange | KnownBoundMethodType::ConstraintSetAlways | KnownBoundMethodType::ConstraintSetNever - | KnownBoundMethodType::ConstraintSetWithInferable(_) | KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_) | KnownBoundMethodType::ConstraintSetSatisfies(_) | KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(_) => {} @@ -10960,7 +10862,7 @@ impl<'db> KnownBoundMethodType<'db> { ) => self_property.when_equivalent_to(db, other_property, inferable), (KnownBoundMethodType::StrStartswith(_), KnownBoundMethodType::StrStartswith(_)) => { - ConstraintSet::from_bool(self == other, inferable) + ConstraintSet::from(self == other) } (KnownBoundMethodType::PathOpen, KnownBoundMethodType::PathOpen) @@ -10976,10 +10878,6 @@ impl<'db> KnownBoundMethodType<'db> { KnownBoundMethodType::ConstraintSetNever, KnownBoundMethodType::ConstraintSetNever, ) - | ( - KnownBoundMethodType::ConstraintSetWithInferable(_), - KnownBoundMethodType::ConstraintSetWithInferable(_), - ) | ( KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_), KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_), @@ -10991,7 +10889,7 @@ impl<'db> KnownBoundMethodType<'db> { | ( KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(_), KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(_), - ) => ConstraintSet::always(inferable), + ) => ConstraintSet::from(true), ( KnownBoundMethodType::FunctionTypeDunderGet(_) @@ -11003,7 +10901,6 @@ impl<'db> KnownBoundMethodType<'db> { | KnownBoundMethodType::ConstraintSetRange | KnownBoundMethodType::ConstraintSetAlways | KnownBoundMethodType::ConstraintSetNever - | KnownBoundMethodType::ConstraintSetWithInferable(_) | KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_) | KnownBoundMethodType::ConstraintSetSatisfies(_) | KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(_), @@ -11016,11 +10913,10 @@ impl<'db> KnownBoundMethodType<'db> { | KnownBoundMethodType::ConstraintSetRange | KnownBoundMethodType::ConstraintSetAlways | KnownBoundMethodType::ConstraintSetNever - | KnownBoundMethodType::ConstraintSetWithInferable(_) | KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_) | KnownBoundMethodType::ConstraintSetSatisfies(_) | KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(_), - ) => ConstraintSet::never(inferable), + ) => ConstraintSet::from(false), } } @@ -11052,7 +10948,7 @@ impl<'db> KnownBoundMethodType<'db> { ) => self_property.is_equivalent_to_impl(db, other_property, inferable, visitor), (KnownBoundMethodType::StrStartswith(_), KnownBoundMethodType::StrStartswith(_)) => { - ConstraintSet::from_bool(self == other, inferable) + ConstraintSet::from(self == other) } (KnownBoundMethodType::PathOpen, KnownBoundMethodType::PathOpen) @@ -11067,13 +10963,9 @@ impl<'db> KnownBoundMethodType<'db> { | ( KnownBoundMethodType::ConstraintSetNever, KnownBoundMethodType::ConstraintSetNever, - ) => ConstraintSet::always(inferable), + ) => ConstraintSet::from(true), ( - KnownBoundMethodType::ConstraintSetWithInferable(left_constraints), - KnownBoundMethodType::ConstraintSetWithInferable(right_constraints), - ) - | ( KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(left_constraints), KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(right_constraints), ) @@ -11098,7 +10990,6 @@ impl<'db> KnownBoundMethodType<'db> { | KnownBoundMethodType::ConstraintSetRange | KnownBoundMethodType::ConstraintSetAlways | KnownBoundMethodType::ConstraintSetNever - | KnownBoundMethodType::ConstraintSetWithInferable(_) | KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_) | KnownBoundMethodType::ConstraintSetSatisfies(_) | KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(_), @@ -11111,11 +11002,10 @@ impl<'db> KnownBoundMethodType<'db> { | KnownBoundMethodType::ConstraintSetRange | KnownBoundMethodType::ConstraintSetAlways | KnownBoundMethodType::ConstraintSetNever - | KnownBoundMethodType::ConstraintSetWithInferable(_) | KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_) | KnownBoundMethodType::ConstraintSetSatisfies(_) | KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(_), - ) => ConstraintSet::never(inferable), + ) => ConstraintSet::from(false), } } @@ -11138,7 +11028,6 @@ impl<'db> KnownBoundMethodType<'db> { | KnownBoundMethodType::ConstraintSetRange | KnownBoundMethodType::ConstraintSetAlways | KnownBoundMethodType::ConstraintSetNever - | KnownBoundMethodType::ConstraintSetWithInferable(_) | KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_) | KnownBoundMethodType::ConstraintSetSatisfies(_) | KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(_) => self, @@ -11157,7 +11046,6 @@ impl<'db> KnownBoundMethodType<'db> { KnownBoundMethodType::ConstraintSetRange | KnownBoundMethodType::ConstraintSetAlways | KnownBoundMethodType::ConstraintSetNever - | KnownBoundMethodType::ConstraintSetWithInferable(_) | KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_) | KnownBoundMethodType::ConstraintSetSatisfies(_) | KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(_) => { @@ -11288,15 +11176,6 @@ impl<'db> KnownBoundMethodType<'db> { ))) } - KnownBoundMethodType::ConstraintSetWithInferable(_) => { - Either::Right(std::iter::once(Signature::new( - Parameters::new([Parameter::variadic(Name::new_static("inferable")) - .type_form() - .with_annotated_type(Type::any())]), - Some(KnownClass::ConstraintSet.to_instance(db)), - ))) - } - KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_) => { Either::Right(std::iter::once(Signature::new( Parameters::new([ @@ -11321,7 +11200,13 @@ impl<'db> KnownBoundMethodType<'db> { KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(_) => { Either::Right(std::iter::once(Signature::new( - Parameters::empty(), + Parameters::new([Parameter::keyword_only(Name::new_static("inferable")) + .type_form() + .with_annotated_type(UnionType::from_elements( + db, + [Type::homogeneous_tuple(db, Type::any()), Type::none(db)], + )) + .with_default_type(Type::none(db))]), Some(KnownClass::Bool.to_instance(db)), ))) } @@ -12084,27 +11969,27 @@ impl<'db> UnionType<'db> { self, db: &'db dyn Db, other: Self, - inferable: InferableTypeVars<'db>, + _inferable: InferableTypeVars<'db>, _visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { if self == other { - return ConstraintSet::always(inferable); + return ConstraintSet::from(true); } let self_elements = self.elements(db); let other_elements = other.elements(db); if self_elements.len() != other_elements.len() { - return ConstraintSet::never(inferable); + return ConstraintSet::from(false); } let sorted_self = self.normalized(db); if sorted_self == Type::Union(other) { - return ConstraintSet::always(inferable); + return ConstraintSet::from(true); } - ConstraintSet::from_bool(sorted_self == other.normalized(db), inferable) + ConstraintSet::from(sorted_self == other.normalized(db)) } } @@ -12186,34 +12071,34 @@ impl<'db> IntersectionType<'db> { self, db: &'db dyn Db, other: Self, - inferable: InferableTypeVars<'db>, + _inferable: InferableTypeVars<'db>, _visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { if self == other { - return ConstraintSet::always(inferable); + return ConstraintSet::from(true); } let self_positive = self.positive(db); let other_positive = other.positive(db); if self_positive.len() != other_positive.len() { - return ConstraintSet::never(inferable); + return ConstraintSet::from(false); } let self_negative = self.negative(db); let other_negative = other.negative(db); if self_negative.len() != other_negative.len() { - return ConstraintSet::never(inferable); + return ConstraintSet::from(false); } let sorted_self = self.normalized(db); if sorted_self == other { - return ConstraintSet::always(inferable); + return ConstraintSet::from(true); } - ConstraintSet::from_bool(sorted_self == other.normalized(db), inferable) + ConstraintSet::from(sorted_self == other.normalized(db)) } /// Returns an iterator over the positive elements of the intersection. If diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index 1f62b0a8f2..1314f94230 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -36,9 +36,9 @@ use crate::types::tuple::{TupleLength, TupleType}; use crate::types::{ BoundMethodType, BoundTypeVarIdentity, ClassLiteral, DataclassFlags, DataclassParams, FieldInstance, KnownBoundMethodType, KnownClass, KnownInstanceType, MemberLookupPolicy, - PropertyInstanceType, SpecialFormType, TrackedConstraintSet, TypeAliasType, TypeContext, - UnionBuilder, UnionType, WrapperDescriptorKind, enums, ide_support, infer_isolated_expression, - todo_type, + NominalInstanceType, PropertyInstanceType, SpecialFormType, TrackedConstraintSet, + TypeAliasType, TypeContext, UnionBuilder, UnionType, WrapperDescriptorKind, enums, ide_support, + infer_isolated_expression, todo_type, }; use ruff_db::diagnostic::{Annotation, Diagnostic, SubDiagnostic, SubDiagnosticSeverity}; use ruff_python_ast::{self as ast, ArgOrKeyword, PythonVersion}; @@ -181,7 +181,7 @@ impl<'db> Bindings<'db> { } } - self.evaluate_known_cases(db, argument_types, dataclass_field_specifiers); + self.evaluate_known_cases(db, dataclass_field_specifiers); // In order of precedence: // @@ -300,12 +300,7 @@ impl<'db> Bindings<'db> { /// Evaluates the return type of certain known callables, where we have special-case logic to /// determine the return type in a way that isn't directly expressible in the type system. - fn evaluate_known_cases( - &mut self, - db: &'db dyn Db, - argument_types: &CallArguments<'_, 'db>, - dataclass_field_specifiers: &[Type<'db>], - ) { + fn evaluate_known_cases(&mut self, db: &'db dyn Db, dataclass_field_specifiers: &[Type<'db>]) { let to_bool = |ty: &Option>, default: bool| -> bool { if let Some(Type::BooleanLiteral(value)) = ty { *value @@ -1147,13 +1142,7 @@ impl<'db> Bindings<'db> { else { return; }; - let constraints = ConstraintSet::range( - db, - *lower, - *typevar, - *upper, - InferableTypeVars::none(), - ); + let constraints = ConstraintSet::range(db, *lower, *typevar, *upper); let tracked = TrackedConstraintSet::new(db, constraints); overload.set_return_type(Type::KnownInstance( KnownInstanceType::ConstraintSet(tracked), @@ -1164,7 +1153,7 @@ impl<'db> Bindings<'db> { if !overload.parameter_types().is_empty() { return; } - let constraints = ConstraintSet::always(InferableTypeVars::none()); + let constraints = ConstraintSet::from(true); let tracked = TrackedConstraintSet::new(db, constraints); overload.set_return_type(Type::KnownInstance( KnownInstanceType::ConstraintSet(tracked), @@ -1175,40 +1164,13 @@ impl<'db> Bindings<'db> { if !overload.parameter_types().is_empty() { return; } - let constraints = ConstraintSet::never(InferableTypeVars::none()); + let constraints = ConstraintSet::from(false); let tracked = TrackedConstraintSet::new(db, constraints); overload.set_return_type(Type::KnownInstance( KnownInstanceType::ConstraintSet(tracked), )); } - Type::KnownBoundMethod(KnownBoundMethodType::ConstraintSetWithInferable( - tracked, - )) => { - let mut any_invalid = false; - let inferable = InferableTypeVars::from_bound_typevars( - db, - overload - .arguments_for_parameter(argument_types, 0) - .filter_map(|(_, ty)| { - let identity = ty - .as_typevar() - .map(|bound_typevar| bound_typevar.identity(db)); - any_invalid |= identity.is_none(); - identity - }), - ); - if any_invalid { - continue; - } - - let result = tracked.constraints(db).with_inferable(inferable); - let tracked = TrackedConstraintSet::new(db, result); - overload.set_return_type(Type::KnownInstance( - KnownInstanceType::ConstraintSet(tracked), - )); - } - Type::KnownBoundMethod( KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(tracked), ) => { @@ -1216,7 +1178,12 @@ impl<'db> Bindings<'db> { continue; }; - let result = ty_a.when_subtype_of_given(db, *ty_b, tracked.constraints(db)); + let result = ty_a.when_subtype_of_given( + db, + *ty_b, + tracked.constraints(db), + InferableTypeVars::none(), + ); let tracked = TrackedConstraintSet::new(db, result); overload.set_return_type(Type::KnownInstance( KnownInstanceType::ConstraintSet(tracked), @@ -1246,7 +1213,35 @@ impl<'db> Bindings<'db> { Type::KnownBoundMethod( KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(tracked), ) => { - let result = tracked.constraints(db).satisfied_by_all_typevars(db); + let extract_inferable = |instance: &NominalInstanceType<'db>| { + if instance.has_known_class(db, KnownClass::NoneType) { + // Caller explicitly passed None, so no typevars are inferable. + return Some(InferableTypeVars::none()); + } + Some(InferableTypeVars::from_bound_typevars( + db, + instance.tuple_spec(db)?.fixed_elements().filter_map(|ty| { + ty.as_typevar() + .map(|bound_typevar| bound_typevar.identity(db)) + }), + )) + }; + + let inferable = match overload.parameter_types() { + // Caller did not provide argument, so no typevars are inferable. + [None] => InferableTypeVars::none(), + [Some(Type::NominalInstance(instance))] => { + match extract_inferable(instance) { + Some(inferable) => inferable, + None => continue, + } + } + _ => continue, + }; + + let result = tracked + .constraints(db) + .satisfied_by_all_typevars(db, inferable); overload.set_return_type(Type::BooleanLiteral(result)); } diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index eee8788b83..7dace10d6b 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -531,8 +531,8 @@ impl<'db> ClassType<'db> { other, inferable, TypeRelation::Subtyping, - &HasRelationToVisitor::from_inferable(inferable), - &IsDisjointVisitor::from_inferable(inferable), + &HasRelationToVisitor::default(), + &IsDisjointVisitor::default(), ) } @@ -545,48 +545,45 @@ impl<'db> ClassType<'db> { relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, ) -> ConstraintSet<'db> { - self.iter_mro(db).when_any(db, inferable, |base| { + self.iter_mro(db).when_any(db, |base| { match base { ClassBase::Dynamic(_) => match relation { TypeRelation::Subtyping | TypeRelation::Redundancy | TypeRelation::ConstraintImplication(_) => { - ConstraintSet::from_bool(other.is_object(db), inferable) - } - TypeRelation::Assignability => { - ConstraintSet::from_bool(!other.is_final(db), inferable) + ConstraintSet::from(other.is_object(db)) } + TypeRelation::Assignability => ConstraintSet::from(!other.is_final(db)), }, // Protocol and Generic are not represented by a ClassType. - ClassBase::Protocol | ClassBase::Generic => ConstraintSet::never(inferable), + ClassBase::Protocol | ClassBase::Generic => ConstraintSet::from(false), ClassBase::Class(base) => match (base, other) { (ClassType::NonGeneric(base), ClassType::NonGeneric(other)) => { - ConstraintSet::from_bool(base == other, inferable) + ConstraintSet::from(base == other) } (ClassType::Generic(base), ClassType::Generic(other)) => { - ConstraintSet::from_bool(base.origin(db) == other.origin(db), inferable) - .and(db, || { - base.specialization(db).has_relation_to_impl( - db, - other.specialization(db), - inferable, - relation, - relation_visitor, - disjointness_visitor, - ) - }) + ConstraintSet::from(base.origin(db) == other.origin(db)).and(db, || { + base.specialization(db).has_relation_to_impl( + db, + other.specialization(db), + inferable, + relation, + relation_visitor, + disjointness_visitor, + ) + }) } (ClassType::Generic(_), ClassType::NonGeneric(_)) | (ClassType::NonGeneric(_), ClassType::Generic(_)) => { - ConstraintSet::never(inferable) + ConstraintSet::from(false) } }, ClassBase::TypedDict => { // TODO: Implement subclassing and assignability for TypedDicts. - ConstraintSet::always(inferable) + ConstraintSet::from(true) } } }) @@ -600,28 +597,26 @@ impl<'db> ClassType<'db> { visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { if self == other { - return ConstraintSet::always(inferable); + return ConstraintSet::from(true); } match (self, other) { // A non-generic class is never equivalent to a generic class. // Two non-generic classes are only equivalent if they are equal (handled above). (ClassType::NonGeneric(_), _) | (_, ClassType::NonGeneric(_)) => { - ConstraintSet::never(inferable) + ConstraintSet::from(false) } - (ClassType::Generic(this), ClassType::Generic(other)) => ConstraintSet::from_bool( - this.origin(db) == other.origin(db), - inferable, - ) - .and(db, || { - this.specialization(db).is_equivalent_to_impl( - db, - other.specialization(db), - inferable, - visitor, - ) - }), + (ClassType::Generic(this), ClassType::Generic(other)) => { + ConstraintSet::from(this.origin(db) == other.origin(db)).and(db, || { + this.specialization(db).is_equivalent_to_impl( + db, + other.specialization(db), + inferable, + visitor, + ) + }) + } } } @@ -1780,10 +1775,7 @@ impl<'db> ClassLiteral<'db> { specialization: Option>, other: ClassType<'db>, ) -> ConstraintSet<'db> { - ConstraintSet::from_bool( - self.is_subclass_of(db, specialization, other), - InferableTypeVars::none(), - ) + ConstraintSet::from(self.is_subclass_of(db, specialization, other)) } /// Return `true` if this class constitutes a typed dict specification (inherits from @@ -4701,7 +4693,7 @@ impl KnownClass { db: &'db dyn Db, other: ClassType<'db>, ) -> ConstraintSet<'db> { - ConstraintSet::from_bool(self.is_subclass_of(db, other), InferableTypeVars::none()) + ConstraintSet::from(self.is_subclass_of(db, other)) } /// Return the module in which we should look up the definition for this class diff --git a/crates/ty_python_semantic/src/types/constraints.rs b/crates/ty_python_semantic/src/types/constraints.rs index 9e6bfc82e9..6b35e2eb0c 100644 --- a/crates/ty_python_semantic/src/types/constraints.rs +++ b/crates/ty_python_semantic/src/types/constraints.rs @@ -74,41 +74,25 @@ use crate::types::{ pub(crate) trait OptionConstraintsExtension { /// Returns a constraint set that is always satisfiable if the option is `None`; otherwise /// applies a function to determine under what constraints the value inside of it holds. - fn when_none_or<'db>( - self, - inferable: InferableTypeVars<'db>, - f: impl FnOnce(T) -> ConstraintSet<'db>, - ) -> ConstraintSet<'db>; + fn when_none_or<'db>(self, f: impl FnOnce(T) -> ConstraintSet<'db>) -> ConstraintSet<'db>; /// Returns a constraint set that is never satisfiable if the option is `None`; otherwise /// applies a function to determine under what constraints the value inside of it holds. - fn when_some_and<'db>( - self, - inferable: InferableTypeVars<'db>, - f: impl FnOnce(T) -> ConstraintSet<'db>, - ) -> ConstraintSet<'db>; + fn when_some_and<'db>(self, f: impl FnOnce(T) -> ConstraintSet<'db>) -> ConstraintSet<'db>; } impl OptionConstraintsExtension for Option { - fn when_none_or<'db>( - self, - inferable: InferableTypeVars<'db>, - f: impl FnOnce(T) -> ConstraintSet<'db>, - ) -> ConstraintSet<'db> { + fn when_none_or<'db>(self, f: impl FnOnce(T) -> ConstraintSet<'db>) -> ConstraintSet<'db> { match self { Some(value) => f(value), - None => ConstraintSet::always(inferable), + None => ConstraintSet::always(), } } - fn when_some_and<'db>( - self, - inferable: InferableTypeVars<'db>, - f: impl FnOnce(T) -> ConstraintSet<'db>, - ) -> ConstraintSet<'db> { + fn when_some_and<'db>(self, f: impl FnOnce(T) -> ConstraintSet<'db>) -> ConstraintSet<'db> { match self { Some(value) => f(value), - None => ConstraintSet::never(inferable), + None => ConstraintSet::never(), } } } @@ -123,7 +107,6 @@ pub(crate) trait IteratorConstraintsExtension { fn when_any<'db>( self, db: &'db dyn Db, - inferable: InferableTypeVars<'db>, f: impl FnMut(T) -> ConstraintSet<'db>, ) -> ConstraintSet<'db>; @@ -135,7 +118,6 @@ pub(crate) trait IteratorConstraintsExtension { fn when_all<'db>( self, db: &'db dyn Db, - inferable: InferableTypeVars<'db>, f: impl FnMut(T) -> ConstraintSet<'db>, ) -> ConstraintSet<'db>; } @@ -147,10 +129,9 @@ where fn when_any<'db>( self, db: &'db dyn Db, - inferable: InferableTypeVars<'db>, mut f: impl FnMut(T) -> ConstraintSet<'db>, ) -> ConstraintSet<'db> { - let mut result = ConstraintSet::never(inferable); + let mut result = ConstraintSet::never(); for child in self { if result.union(db, f(child)).is_always_satisfied(db) { return result; @@ -162,10 +143,9 @@ where fn when_all<'db>( self, db: &'db dyn Db, - inferable: InferableTypeVars<'db>, mut f: impl FnMut(T) -> ConstraintSet<'db>, ) -> ConstraintSet<'db> { - let mut result = ConstraintSet::always(inferable); + let mut result = ConstraintSet::always(); for child in self { if result.intersect(db, f(child)).is_never_satisfied(db) { return result; @@ -184,34 +164,18 @@ where pub struct ConstraintSet<'db> { /// The BDD representing this constraint set node: Node<'db>, - - /// The typevars that were inferable when constructing this constraint set - pub(crate) inferable: InferableTypeVars<'db>, } impl<'db> ConstraintSet<'db> { - pub(crate) fn never(inferable: InferableTypeVars<'db>) -> Self { + fn never() -> Self { Self { node: Node::AlwaysFalse, - inferable, } } - pub(crate) fn always(inferable: InferableTypeVars<'db>) -> Self { + fn always() -> Self { Self { node: Node::AlwaysTrue, - inferable, - } - } - - pub(crate) fn from_bool(b: bool, inferable: InferableTypeVars<'db>) -> Self { - Self { - node: if b { - Node::AlwaysTrue - } else { - Node::AlwaysFalse - }, - inferable, } } @@ -221,7 +185,6 @@ impl<'db> ConstraintSet<'db> { typevar: BoundTypeVarInstance<'db>, lower: Type<'db>, upper: Type<'db>, - inferable: InferableTypeVars<'db>, relation: TypeRelation<'db>, ) -> Self { let (lower, upper) = match relation { @@ -239,14 +202,6 @@ impl<'db> ConstraintSet<'db> { Self { node: ConstrainedTypeVar::new_node(db, typevar, lower, upper), - inferable, - } - } - - pub(crate) fn with_inferable(self, inferable: InferableTypeVars<'db>) -> Self { - Self { - node: self.node, - inferable, } } @@ -271,7 +226,6 @@ impl<'db> ConstraintSet<'db> { ) -> Self { Self { node: self.node.when_subtype_of_given(db, lhs, rhs), - inferable: self.inferable, } } @@ -289,8 +243,12 @@ impl<'db> ConstraintSet<'db> { /// since the constraint set cannot be affected by any typevars that it does not mention. That /// means that those additional typevars trivially satisfy the constraint set, regardless of /// whether they are inferable or not. - pub(crate) fn satisfied_by_all_typevars(self, db: &'db dyn Db) -> bool { - self.node.satisfied_by_all_typevars(db, self.inferable) + pub(crate) fn satisfied_by_all_typevars( + self, + db: &'db dyn Db, + inferable: InferableTypeVars<'db>, + ) -> bool { + self.node.satisfied_by_all_typevars(db, inferable) } /// Updates this constraint set to hold the union of itself and another constraint set. @@ -301,7 +259,6 @@ impl<'db> ConstraintSet<'db> { /// only mention typevars that are inferable in the lhs, or which both sides consider /// non-inferable. pub(crate) fn union(&mut self, db: &'db dyn Db, other: Self) -> Self { - let other = other.reduce_inferable(db, self.inferable); self.node = self.node.or(db, other.node); *self } @@ -314,7 +271,6 @@ impl<'db> ConstraintSet<'db> { /// only mention typevars that are inferable in the lhs, or which both sides consider /// non-inferable. pub(crate) fn intersect(&mut self, db: &'db dyn Db, other: Self) -> Self { - let other = other.reduce_inferable(db, self.inferable); self.node = self.node.and(db, other.node); *self } @@ -323,7 +279,6 @@ impl<'db> ConstraintSet<'db> { pub(crate) fn negate(self, db: &'db dyn Db) -> Self { Self { node: self.node.negate(db), - inferable: self.inferable, } } @@ -355,7 +310,6 @@ impl<'db> ConstraintSet<'db> { pub(crate) fn iff(self, db: &'db dyn Db, other: Self) -> Self { ConstraintSet { node: self.node.iff(db, other.node), - inferable: self.inferable.merge(db, other.inferable), } } @@ -364,14 +318,14 @@ impl<'db> ConstraintSet<'db> { /// away. (That is, those typevars will be removed from the constraint set, and the constraint /// set will return true whenever there was _any_ specialization of those typevars that /// returned true before.) + /// XXX: fix docs pub(crate) fn reduce_inferable( self, db: &'db dyn Db, - inferable: InferableTypeVars<'db>, + to_remove: impl IntoIterator>, ) -> Self { - let to_abstract = self.inferable.subtract(db, inferable); - let node = self.node.exists(db, to_abstract); - Self { node, inferable } + let node = self.node.exists(db, to_remove); + Self { node } } pub(crate) fn range( @@ -379,16 +333,8 @@ impl<'db> ConstraintSet<'db> { lower: Type<'db>, typevar: BoundTypeVarInstance<'db>, upper: Type<'db>, - inferable: InferableTypeVars<'db>, ) -> Self { - Self::constrain_typevar( - db, - typevar, - lower, - upper, - inferable, - TypeRelation::Assignability, - ) + Self::constrain_typevar(db, typevar, lower, upper, TypeRelation::Assignability) } pub(crate) fn display(self, db: &'db dyn Db) -> impl Display { @@ -396,6 +342,12 @@ impl<'db> ConstraintSet<'db> { } } +impl From for ConstraintSet<'_> { + fn from(b: bool) -> Self { + if b { Self::always() } else { Self::never() } + } +} + impl<'db> BoundTypeVarInstance<'db> { /// Returns whether this typevar can be the lower or upper bound of another typevar in a /// constraint set. @@ -2230,10 +2182,10 @@ mod tests { let u = BoundTypeVarInstance::synthetic(&db, "U", TypeVarVariance::Invariant); let bool_type = KnownClass::Bool.to_instance(&db); let str_type = KnownClass::Str.to_instance(&db); - let t_str = ConstraintSet::range(&db, str_type, t, str_type, InferableTypeVars::none()); - let t_bool = ConstraintSet::range(&db, bool_type, t, bool_type, InferableTypeVars::none()); - let u_str = ConstraintSet::range(&db, str_type, u, str_type, InferableTypeVars::none()); - let u_bool = ConstraintSet::range(&db, bool_type, u, bool_type, InferableTypeVars::none()); + let t_str = ConstraintSet::range(&db, str_type, t, str_type); + let t_bool = ConstraintSet::range(&db, bool_type, t, bool_type); + let u_str = ConstraintSet::range(&db, str_type, u, str_type); + let u_bool = ConstraintSet::range(&db, bool_type, u, bool_type); let constraints = (t_str.or(&db, || t_bool)).and(&db, || u_str.or(&db, || u_bool)); let actual = constraints.node.display_graph(&db, &"").to_string(); assert_eq!(actual, expected); diff --git a/crates/ty_python_semantic/src/types/display.rs b/crates/ty_python_semantic/src/types/display.rs index 595d194bdc..b8a8a05ac4 100644 --- a/crates/ty_python_semantic/src/types/display.rs +++ b/crates/ty_python_semantic/src/types/display.rs @@ -532,9 +532,6 @@ impl Display for DisplayRepresentation<'_> { Type::KnownBoundMethod(KnownBoundMethodType::ConstraintSetNever) => { f.write_str("bound method `ConstraintSet.never`") } - Type::KnownBoundMethod(KnownBoundMethodType::ConstraintSetWithInferable(_)) => { - f.write_str("bound method `ConstraintSet.with_inferable`") - } Type::KnownBoundMethod(KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_)) => { f.write_str("bound method `ConstraintSet.implies_subtype_of`") } diff --git a/crates/ty_python_semantic/src/types/function.rs b/crates/ty_python_semantic/src/types/function.rs index b6b76fbd9a..8903e24698 100644 --- a/crates/ty_python_semantic/src/types/function.rs +++ b/crates/ty_python_semantic/src/types/function.rs @@ -986,11 +986,11 @@ impl<'db> FunctionType<'db> { | TypeRelation::ConstraintImplication(_) ) && self.normalized(db) == other.normalized(db) { - return ConstraintSet::always(inferable); + return ConstraintSet::from(true); } if self.literal(db) != other.literal(db) { - return ConstraintSet::never(inferable); + return ConstraintSet::from(false); } let self_signature = self.signature(db); @@ -1013,10 +1013,10 @@ impl<'db> FunctionType<'db> { visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { if self.normalized(db) == other.normalized(db) { - return ConstraintSet::always(inferable); + return ConstraintSet::from(true); } if self.literal(db) != other.literal(db) { - return ConstraintSet::never(inferable); + return ConstraintSet::from(false); } let self_signature = self.signature(db); let other_signature = other.signature(db); diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index 247833911b..de1ea713db 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -2,7 +2,7 @@ use std::cell::RefCell; use std::collections::hash_map::Entry; use std::fmt::Display; -use itertools::{Either, EitherOrBoth, Itertools}; +use itertools::Itertools; use ruff_python_ast as ast; use rustc_hash::FxHashMap; use smallvec::{SmallVec, smallvec}; @@ -119,7 +119,7 @@ pub(crate) fn typing_self<'db>( .map(Type::TypeVar) } -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, get_size2::GetSize, salsa::Update)] +#[derive(Clone, Copy, Debug)] pub(crate) struct InferableTypeVars<'db> { inner: Option>, } @@ -168,36 +168,19 @@ impl<'db> InferableTypeVars<'db> { (None, None) => self, (Some(_), None) => self, (None, Some(_)) => other, - (Some(self_inner), Some(other_inner)) if self_inner == other_inner => self, (Some(self_inner), Some(other_inner)) => InferableTypeVars { inner: Some(self_inner.merge(db, other_inner)), }, } } - /// Returns an iterator of the typevars that are in this inferable set but not in another. - pub(crate) fn subtract( + pub(crate) fn iter( self, db: &'db dyn Db, - other: Self, ) -> impl Iterator> + 'db { - let (self_inner, other_inner) = match (self.inner, other.inner) { - (None, _) => return Either::Left(Either::Left(std::iter::empty())), - (Some(self_inner), None) => { - return Either::Left(Either::Right(self_inner.inferable(db).into_iter().copied())); - } - (Some(self_inner), Some(other_inner)) => (self_inner, other_inner), - }; - let self_inferable = self_inner.inferable(db).into_iter().copied(); - let other_inferable = other_inner.inferable(db).into_iter().copied(); - Either::Right( - self_inferable - .merge_join_by(other_inferable, BoundTypeVarIdentity::cmp) - .filter_map(|merged| match merged { - EitherOrBoth::Left(bound_typevar) => Some(bound_typevar), - EitherOrBoth::Right(_) | EitherOrBoth::Both(_, _) => None, - }), - ) + self.inner + .into_iter() + .flat_map(|inner| inner.inferable(db).iter().copied()) } // Keep this around for debugging purposes @@ -770,12 +753,12 @@ fn is_subtype_in_invariant_position<'db>( // This should be removed and properly handled in the respective // `(Type::TypeVar(_), _) | (_, Type::TypeVar(_))` branch of // `Type::has_relation_to_impl`. Right now, we cannot generally - // return `ConstraintSet::always(inferable)` from that branch, as that + // return `ConstraintSet::from(true)` from that branch, as that // leads to union simplification, which means that we lose track // of type variables without recording the constraints under which // the relation holds. if matches!(base, Type::TypeVar(_)) || matches!(derived, Type::TypeVar(_)) { - return ConstraintSet::always(inferable); + return ConstraintSet::from(true); } derived.has_relation_to_impl( @@ -1189,7 +1172,7 @@ impl<'db> Specialization<'db> { ) -> ConstraintSet<'db> { let generic_context = self.generic_context(db); if generic_context != other.generic_context(db) { - return ConstraintSet::never(inferable); + return ConstraintSet::from(false); } if let (Some(self_tuple), Some(other_tuple)) = (self.tuple_inner(db), other.tuple_inner(db)) @@ -1207,7 +1190,7 @@ impl<'db> Specialization<'db> { let self_materialization_kind = self.materialization_kind(db); let other_materialization_kind = other.materialization_kind(db); - let mut result = ConstraintSet::always(inferable); + let mut result = ConstraintSet::from(true); for ((bound_typevar, self_type), other_type) in (generic_context.variables(db)) .zip(self.types(db)) .zip(other.types(db)) @@ -1246,7 +1229,7 @@ impl<'db> Specialization<'db> { relation_visitor, disjointness_visitor, ), - TypeVarVariance::Bivariant => ConstraintSet::always(inferable), + TypeVarVariance::Bivariant => ConstraintSet::from(true), }; if result.intersect(db, compatible).is_never_satisfied(db) { return result; @@ -1264,14 +1247,14 @@ impl<'db> Specialization<'db> { visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { if self.materialization_kind(db) != other.materialization_kind(db) { - return ConstraintSet::never(inferable); + return ConstraintSet::from(false); } let generic_context = self.generic_context(db); if generic_context != other.generic_context(db) { - return ConstraintSet::never(inferable); + return ConstraintSet::from(false); } - let mut result = ConstraintSet::always(inferable); + let mut result = ConstraintSet::from(true); for ((bound_typevar, self_type), other_type) in (generic_context.variables(db)) .zip(self.types(db)) .zip(other.types(db)) @@ -1288,7 +1271,7 @@ impl<'db> Specialization<'db> { | TypeVarVariance::Contravariant => { self_type.is_equivalent_to_impl(db, *other_type, inferable, visitor) } - TypeVarVariance::Bivariant => ConstraintSet::always(inferable), + TypeVarVariance::Bivariant => ConstraintSet::from(true), }; if result.intersect(db, compatible).is_never_satisfied(db) { return result; @@ -1296,7 +1279,7 @@ impl<'db> Specialization<'db> { } match (self.tuple_inner(db), other.tuple_inner(db)) { - (Some(_), None) | (None, Some(_)) => return ConstraintSet::never(inferable), + (Some(_), None) | (None, Some(_)) => return ConstraintSet::from(false), (None, None) => {} (Some(self_tuple), Some(other_tuple)) => { let compatible = diff --git a/crates/ty_python_semantic/src/types/instance.rs b/crates/ty_python_semantic/src/types/instance.rs index 54acaac9ae..e1410ca070 100644 --- a/crates/ty_python_semantic/src/types/instance.rs +++ b/crates/ty_python_semantic/src/types/instance.rs @@ -141,7 +141,7 @@ impl<'db> Type<'db> { .inner .interface(db) .members(db) - .when_all(db, inferable, |member| { + .when_all(db, |member| { member.is_satisfied_by( db, self, @@ -161,7 +161,7 @@ impl<'db> Type<'db> { // recognise `str` as a subtype of `Container[str]`. structurally_satisfied.or(db, || { let Some(nominal_instance) = protocol.as_nominal_type() else { - return ConstraintSet::never(inferable); + return ConstraintSet::from(false); }; // if `self` and `other` are *both* protocols, we also need to treat `self` as if it @@ -371,7 +371,7 @@ impl<'db> NominalInstanceType<'db> { disjointness_visitor: &IsDisjointVisitor<'db>, ) -> ConstraintSet<'db> { match (self.0, other.0) { - (_, NominalInstanceInner::Object) => ConstraintSet::always(inferable), + (_, NominalInstanceInner::Object) => ConstraintSet::from(true), ( NominalInstanceInner::ExactTuple(tuple1), NominalInstanceInner::ExactTuple(tuple2), @@ -407,12 +407,12 @@ impl<'db> NominalInstanceType<'db> { NominalInstanceInner::ExactTuple(tuple2), ) => tuple1.is_equivalent_to_impl(db, tuple2, inferable, visitor), (NominalInstanceInner::Object, NominalInstanceInner::Object) => { - ConstraintSet::always(inferable) + ConstraintSet::from(true) } (NominalInstanceInner::NonTuple(class1), NominalInstanceInner::NonTuple(class2)) => { class1.is_equivalent_to_impl(db, class2, inferable, visitor) } - _ => ConstraintSet::never(inferable), + _ => ConstraintSet::from(false), } } @@ -425,9 +425,9 @@ impl<'db> NominalInstanceType<'db> { relation_visitor: &HasRelationToVisitor<'db>, ) -> ConstraintSet<'db> { if self.is_object() || other.is_object() { - return ConstraintSet::never(inferable); + return ConstraintSet::from(false); } - let mut result = ConstraintSet::never(inferable); + let mut result = ConstraintSet::from(false); if let Some(self_spec) = self.tuple_spec(db) { if let Some(other_spec) = other.tuple_spec(db) { let compatible = self_spec.is_disjoint_from_impl( @@ -443,10 +443,7 @@ impl<'db> NominalInstanceType<'db> { } } result.or(db, || { - ConstraintSet::from_bool( - !(self.class(db)).could_coexist_in_mro_with(db, other.class(db)), - inferable, - ) + ConstraintSet::from(!(self.class(db)).could_coexist_in_mro_with(db, other.class(db))) }) } @@ -671,8 +668,8 @@ impl<'db> ProtocolInstanceType<'db> { protocol, InferableTypeVars::none(), TypeRelation::Subtyping, - &HasRelationToVisitor::from_inferable(InferableTypeVars::none()), - &IsDisjointVisitor::from_inferable(InferableTypeVars::none()), + &HasRelationToVisitor::default(), + &IsDisjointVisitor::default(), ) .is_always_satisfied(db) } @@ -722,17 +719,17 @@ impl<'db> ProtocolInstanceType<'db> { self, db: &'db dyn Db, other: Self, - inferable: InferableTypeVars<'db>, + _inferable: InferableTypeVars<'db>, _visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { if self == other { - return ConstraintSet::always(inferable); + return ConstraintSet::from(true); } let self_normalized = self.normalized(db); if self_normalized == Type::ProtocolInstance(other) { - return ConstraintSet::always(inferable); + return ConstraintSet::from(true); } - ConstraintSet::from_bool(self_normalized == other.normalized(db), inferable) + ConstraintSet::from(self_normalized == other.normalized(db)) } /// Return `true` if this protocol type is disjoint from the protocol `other`. @@ -744,10 +741,10 @@ impl<'db> ProtocolInstanceType<'db> { self, _db: &'db dyn Db, _other: Self, - inferable: InferableTypeVars<'db>, + _inferable: InferableTypeVars<'db>, _visitor: &IsDisjointVisitor<'db>, ) -> ConstraintSet<'db> { - ConstraintSet::never(inferable) + ConstraintSet::from(false) } pub(crate) fn instance_member(self, db: &'db dyn Db, name: &str) -> PlaceAndQualifiers<'db> { diff --git a/crates/ty_python_semantic/src/types/newtype.rs b/crates/ty_python_semantic/src/types/newtype.rs index 8e51c50a53..fe08fa7bee 100644 --- a/crates/ty_python_semantic/src/types/newtype.rs +++ b/crates/ty_python_semantic/src/types/newtype.rs @@ -3,7 +3,6 @@ use std::collections::BTreeSet; use crate::Db; use crate::semantic_index::definition::{Definition, DefinitionKind}; use crate::types::constraints::ConstraintSet; -use crate::types::generics::InferableTypeVars; use crate::types::{ClassType, Type, definition_expression_type, visitor}; use ruff_db::parsed::parsed_module; use ruff_python_ast as ast; @@ -116,37 +115,26 @@ impl<'db> NewType<'db> { // Since a regular class can't inherit from a newtype, the only way for one newtype to be a // subtype of another is to have the other in its chain of newtype bases. Once we reach the // base class, we don't have to keep looking. - pub(crate) fn has_relation_to_impl( - self, - db: &'db dyn Db, - other: Self, - inferable: InferableTypeVars<'db>, - ) -> ConstraintSet<'db> { + pub(crate) fn has_relation_to_impl(self, db: &'db dyn Db, other: Self) -> ConstraintSet<'db> { if self.is_equivalent_to_impl(db, other) { - return ConstraintSet::always(inferable); + return ConstraintSet::from(true); } for base in self.iter_bases(db) { if let NewTypeBase::NewType(base_newtype) = base { if base_newtype.is_equivalent_to_impl(db, other) { - return ConstraintSet::always(inferable); + return ConstraintSet::from(true); } } } - ConstraintSet::never(inferable) + ConstraintSet::from(false) } - pub(crate) fn is_disjoint_from_impl( - self, - db: &'db dyn Db, - other: Self, - inferable: InferableTypeVars<'db>, - ) -> ConstraintSet<'db> { + pub(crate) fn is_disjoint_from_impl(self, db: &'db dyn Db, other: Self) -> ConstraintSet<'db> { // Two NewTypes are disjoint if they're not equal and neither inherits from the other. // NewTypes have single inheritance, and a regular class can't inherit from a NewType, so // it's not possible for some third type to multiply-inherit from both. - let mut self_not_subtype_of_other = - self.has_relation_to_impl(db, other, inferable).negate(db); - let other_not_subtype_of_self = other.has_relation_to_impl(db, self, inferable).negate(db); + let mut self_not_subtype_of_other = self.has_relation_to_impl(db, other).negate(db); + let other_not_subtype_of_self = other.has_relation_to_impl(db, self).negate(db); self_not_subtype_of_other.intersect(db, other_not_subtype_of_self) } diff --git a/crates/ty_python_semantic/src/types/protocol_class.rs b/crates/ty_python_semantic/src/types/protocol_class.rs index 36af560b18..31acf47416 100644 --- a/crates/ty_python_semantic/src/types/protocol_class.rs +++ b/crates/ty_python_semantic/src/types/protocol_class.rs @@ -238,14 +238,13 @@ impl<'db> ProtocolInterface<'db> { relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, ) -> ConstraintSet<'db> { - other.members(db).when_all(db, inferable, |other_member| { - self.member_by_name(db, other_member.name).when_some_and( - inferable, - |our_member| match (our_member.kind, other_member.kind) { + other.members(db).when_all(db, |other_member| { + self.member_by_name(db, other_member.name) + .when_some_and(|our_member| match (our_member.kind, other_member.kind) { // Method members are always immutable; // they can never be subtypes of/assignable to mutable attribute members. (ProtocolMemberKind::Method(_), ProtocolMemberKind::Other(_)) => { - ConstraintSet::never(inferable) + ConstraintSet::from(false) } // A property member can only be a subtype of an attribute member @@ -253,16 +252,15 @@ impl<'db> ProtocolInterface<'db> { // // TODO: this should also consider the types of the members on both sides. (ProtocolMemberKind::Property(property), ProtocolMemberKind::Other(_)) => { - ConstraintSet::from_bool( + ConstraintSet::from( property.getter(db).is_some() && property.setter(db).is_some(), - inferable, ) } // A `@property` member can never be a subtype of a method member, as it is not necessarily // accessible on the meta-type, whereas a method member must be. (ProtocolMemberKind::Property(_), ProtocolMemberKind::Method(_)) => { - ConstraintSet::never(inferable) + ConstraintSet::from(false) } // But an attribute member *can* be a subtype of a method member, @@ -270,9 +268,8 @@ impl<'db> ProtocolInterface<'db> { ( ProtocolMemberKind::Other(our_type), ProtocolMemberKind::Method(other_type), - ) => ConstraintSet::from_bool( + ) => ConstraintSet::from( our_member.qualifiers.contains(TypeQualifiers::CLASS_VAR), - inferable, ) .and(db, || { our_type.has_relation_to_impl( @@ -327,9 +324,8 @@ impl<'db> ProtocolInterface<'db> { | ProtocolMemberKind::Method(_) | ProtocolMemberKind::Other(_), ProtocolMemberKind::Property(_), - ) => ConstraintSet::always(inferable), - }, - ) + ) => ConstraintSet::from(true), + }) }) } @@ -619,7 +615,7 @@ impl<'a, 'db> ProtocolMember<'a, 'db> { match &self.kind { // TODO: implement disjointness for property/method members as well as attribute members ProtocolMemberKind::Property(_) | ProtocolMemberKind::Method(_) => { - ConstraintSet::never(inferable) + ConstraintSet::from(false) } ProtocolMemberKind::Other(ty) => ty.is_disjoint_from_impl( db, @@ -655,7 +651,7 @@ impl<'a, 'db> ProtocolMember<'a, 'db> { // complex interaction between `__new__`, `__init__` and metaclass `__call__`. let attribute_type = if self.name == "__call__" { let Some(attribute_type) = other.try_upcast_to_callable(db) else { - return ConstraintSet::never(inferable); + return ConstraintSet::from(false); }; attribute_type } else { @@ -669,7 +665,7 @@ impl<'a, 'db> ProtocolMember<'a, 'db> { ) .place else { - return ConstraintSet::never(inferable); + return ConstraintSet::from(false); }; attribute_type }; @@ -684,18 +680,15 @@ impl<'a, 'db> ProtocolMember<'a, 'db> { ) } // TODO: consider the types of the attribute on `other` for property members - ProtocolMemberKind::Property(_) => ConstraintSet::from_bool( - matches!( - other.member(db, self.name).place, - Place::Defined(_, _, Definedness::AlwaysDefined) - ), - inferable, - ), + ProtocolMemberKind::Property(_) => ConstraintSet::from(matches!( + other.member(db, self.name).place, + Place::Defined(_, _, Definedness::AlwaysDefined) + )), ProtocolMemberKind::Other(member_type) => { let Place::Defined(attribute_type, _, Definedness::AlwaysDefined) = other.member(db, self.name).place else { - return ConstraintSet::never(inferable); + return ConstraintSet::from(false); }; member_type .has_relation_to_impl( diff --git a/crates/ty_python_semantic/src/types/signatures.rs b/crates/ty_python_semantic/src/types/signatures.rs index 19eee55f08..03bc5d194d 100644 --- a/crates/ty_python_semantic/src/types/signatures.rs +++ b/crates/ty_python_semantic/src/types/signatures.rs @@ -224,8 +224,8 @@ impl<'db> CallableSignature<'db> { other, inferable, TypeRelation::Subtyping, - &HasRelationToVisitor::from_inferable(inferable), - &IsDisjointVisitor::from_inferable(inferable), + &HasRelationToVisitor::default(), + &IsDisjointVisitor::default(), ) } @@ -274,49 +274,43 @@ impl<'db> CallableSignature<'db> { } // `self` is possibly overloaded while `other` is definitely not overloaded. - (_, [_]) => self_signatures - .iter() - .when_any(db, inferable, |self_signature| { - Self::has_relation_to_inner( - db, - std::slice::from_ref(self_signature), - other_signatures, - inferable, - relation, - relation_visitor, - disjointness_visitor, - ) - }), + (_, [_]) => self_signatures.iter().when_any(db, |self_signature| { + Self::has_relation_to_inner( + db, + std::slice::from_ref(self_signature), + other_signatures, + inferable, + relation, + relation_visitor, + disjointness_visitor, + ) + }), // `self` is definitely not overloaded while `other` is possibly overloaded. - ([_], _) => other_signatures - .iter() - .when_all(db, inferable, |other_signature| { - Self::has_relation_to_inner( - db, - self_signatures, - std::slice::from_ref(other_signature), - inferable, - relation, - relation_visitor, - disjointness_visitor, - ) - }), + ([_], _) => other_signatures.iter().when_all(db, |other_signature| { + Self::has_relation_to_inner( + db, + self_signatures, + std::slice::from_ref(other_signature), + inferable, + relation, + relation_visitor, + disjointness_visitor, + ) + }), // `self` is definitely overloaded while `other` is possibly overloaded. - (_, _) => other_signatures - .iter() - .when_all(db, inferable, |other_signature| { - Self::has_relation_to_inner( - db, - self_signatures, - std::slice::from_ref(other_signature), - inferable, - relation, - relation_visitor, - disjointness_visitor, - ) - }), + (_, _) => other_signatures.iter().when_all(db, |other_signature| { + Self::has_relation_to_inner( + db, + self_signatures, + std::slice::from_ref(other_signature), + inferable, + relation, + relation_visitor, + disjointness_visitor, + ) + }), } } @@ -338,7 +332,7 @@ impl<'db> CallableSignature<'db> { } (_, _) => { if self == other { - return ConstraintSet::always(inferable); + return ConstraintSet::from(true); } self.is_subtype_of_impl(db, other, inferable) .and(db, || other.is_subtype_of_impl(db, self, inferable)) @@ -651,7 +645,7 @@ impl<'db> Signature<'db> { inferable: InferableTypeVars<'db>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { - let mut result = ConstraintSet::always(inferable); + let mut result = ConstraintSet::from(true); let mut check_types = |self_type: Option>, other_type: Option>| { let self_type = self_type.unwrap_or(Type::unknown()); let other_type = other_type.unwrap_or(Type::unknown()); @@ -664,11 +658,11 @@ impl<'db> Signature<'db> { }; if self.parameters.is_gradual() != other.parameters.is_gradual() { - return ConstraintSet::never(inferable); + return ConstraintSet::from(false); } if self.parameters.len() != other.parameters.len() { - return ConstraintSet::never(inferable); + return ConstraintSet::from(false); } if !check_types(self.return_ty, other.return_ty) { @@ -716,7 +710,7 @@ impl<'db> Signature<'db> { (ParameterKind::KeywordVariadic { .. }, ParameterKind::KeywordVariadic { .. }) => {} - _ => return ConstraintSet::never(inferable), + _ => return ConstraintSet::from(false), } if !check_types( @@ -740,9 +734,17 @@ impl<'db> Signature<'db> { relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, ) -> ConstraintSet<'db> { - // If this callable is generic, then `inner` will add all of our typevars to the - // `inferable` set, since we only need to find one specialization that causes the check to - // succeed. + // If either signature is generic, their typevars should also be considered inferable when + // checking whether the signatures are equivalent, since we only need to find one + // specialization that causes the check to succeed. + let self_inferable = self.inferable_typevars(db); + let other_inferable = other.inferable_typevars(db); + let new_inferable = InferableTypeVars::none() + .merge(db, self_inferable) + .merge(db, other_inferable); + let inferable = inferable.merge(db, new_inferable); + + // `inner` will create a constraint set that references these newly inferable typevars. let when = self.has_relation_to_inner( db, other, @@ -756,7 +758,7 @@ impl<'db> Signature<'db> { // we produce, we reduce it back down to the inferable set that the caller asked about. // If we introduced new inferable typevars, those will be existentially quantified away // before returning. - when.reduce_inferable(db, inferable) + when.reduce_inferable(db, new_inferable.iter(db)) } fn has_relation_to_inner( @@ -829,12 +831,7 @@ impl<'db> Signature<'db> { } } - // The typevars in self and other should also be considered inferable when checking whether - // two signatures are equivalent. - let inferable = inferable.merge(db, self.inferable_typevars(db)); - let inferable = inferable.merge(db, other.inferable_typevars(db)); - - let mut result = ConstraintSet::always(inferable); + let mut result = ConstraintSet::from(true); let mut check_types = |type1: Option>, type2: Option>| { let type1 = type1.unwrap_or(Type::unknown()); let type2 = type2.unwrap_or(Type::unknown()); @@ -870,13 +867,13 @@ impl<'db> Signature<'db> { .keyword_variadic() .is_some_and(|(_, param)| param.annotated_type().is_some_and(|ty| ty.is_object())) { - return ConstraintSet::always(inferable); + return ConstraintSet::from(true); } // If either of the parameter lists is gradual (`...`), then it is assignable to and from // any other parameter list, but not a subtype or supertype of any other parameter list. if self.parameters.is_gradual() || other.parameters.is_gradual() { - return ConstraintSet::from_bool(relation.is_assignability(), inferable); + return ConstraintSet::from(relation.is_assignability()); } let mut parameters = ParametersZip { @@ -914,7 +911,7 @@ impl<'db> Signature<'db> { // `other`, then the non-variadic parameters in `self` must have a default // value. if default_type.is_none() { - return ConstraintSet::never(inferable); + return ConstraintSet::from(false); } } ParameterKind::Variadic { .. } | ParameterKind::KeywordVariadic { .. } => { @@ -926,7 +923,7 @@ impl<'db> Signature<'db> { EitherOrBoth::Right(_) => { // If there are more parameters in `other` than in `self`, then `self` is not a // subtype of `other`. - return ConstraintSet::never(inferable); + return ConstraintSet::from(false); } EitherOrBoth::Both(self_parameter, other_parameter) => { @@ -946,7 +943,7 @@ impl<'db> Signature<'db> { }, ) => { if self_default.is_none() && other_default.is_some() { - return ConstraintSet::never(inferable); + return ConstraintSet::from(false); } if !check_types( other_parameter.annotated_type(), @@ -967,11 +964,11 @@ impl<'db> Signature<'db> { }, ) => { if self_name != other_name { - return ConstraintSet::never(inferable); + return ConstraintSet::from(false); } // The following checks are the same as positional-only parameters. if self_default.is_none() && other_default.is_some() { - return ConstraintSet::never(inferable); + return ConstraintSet::from(false); } if !check_types( other_parameter.annotated_type(), @@ -1056,7 +1053,7 @@ impl<'db> Signature<'db> { break; } - _ => return ConstraintSet::never(inferable), + _ => return ConstraintSet::from(false), } } } @@ -1090,7 +1087,7 @@ impl<'db> Signature<'db> { // previous loop. They cannot be matched against any parameter in `other` which // only contains keyword-only and keyword-variadic parameters so the subtype // relation is invalid. - return ConstraintSet::never(inferable); + return ConstraintSet::from(false); } ParameterKind::Variadic { .. } => {} } @@ -1117,7 +1114,7 @@ impl<'db> Signature<'db> { .. } => { if self_default.is_none() && other_default.is_some() { - return ConstraintSet::never(inferable); + return ConstraintSet::from(false); } if !check_types( other_parameter.annotated_type(), @@ -1138,14 +1135,14 @@ impl<'db> Signature<'db> { return result; } } else { - return ConstraintSet::never(inferable); + return ConstraintSet::from(false); } } ParameterKind::KeywordVariadic { .. } => { let Some(self_keyword_variadic_type) = self_keyword_variadic else { // For a `self <: other` relationship, if `other` has a keyword variadic // parameter, `self` must also have a keyword variadic parameter. - return ConstraintSet::never(inferable); + return ConstraintSet::from(false); }; if !check_types(other_parameter.annotated_type(), self_keyword_variadic_type) { return result; @@ -1153,7 +1150,7 @@ impl<'db> Signature<'db> { } _ => { // This can only occur in case of a syntax error. - return ConstraintSet::never(inferable); + return ConstraintSet::from(false); } } } @@ -1162,7 +1159,7 @@ impl<'db> Signature<'db> { // optional otherwise the subtype relation is invalid. for (_, self_parameter) in self_keywords { if self_parameter.default_type().is_none() { - return ConstraintSet::never(inferable); + return ConstraintSet::from(false); } } diff --git a/crates/ty_python_semantic/src/types/subclass_of.rs b/crates/ty_python_semantic/src/types/subclass_of.rs index ce3977bf39..3ff8cadf5c 100644 --- a/crates/ty_python_semantic/src/types/subclass_of.rs +++ b/crates/ty_python_semantic/src/types/subclass_of.rs @@ -143,16 +143,13 @@ impl<'db> SubclassOfType<'db> { ) -> ConstraintSet<'db> { match (self.subclass_of, other.subclass_of) { (SubclassOfInner::Dynamic(_), SubclassOfInner::Dynamic(_)) => { - ConstraintSet::from_bool(!relation.is_subtyping(), inferable) + ConstraintSet::from(!relation.is_subtyping()) } (SubclassOfInner::Dynamic(_), SubclassOfInner::Class(other_class)) => { - ConstraintSet::from_bool( - other_class.is_object(db) || relation.is_assignability(), - inferable, - ) + ConstraintSet::from(other_class.is_object(db) || relation.is_assignability()) } (SubclassOfInner::Class(_), SubclassOfInner::Dynamic(_)) => { - ConstraintSet::from_bool(relation.is_assignability(), inferable) + ConstraintSet::from(relation.is_assignability()) } // For example, `type[bool]` describes all possible runtime subclasses of the class `bool`, @@ -177,18 +174,15 @@ impl<'db> SubclassOfType<'db> { self, db: &'db dyn Db, other: Self, - inferable: InferableTypeVars<'db>, + _inferable: InferableTypeVars<'db>, _visitor: &IsDisjointVisitor<'db>, ) -> ConstraintSet<'db> { match (self.subclass_of, other.subclass_of) { (SubclassOfInner::Dynamic(_), _) | (_, SubclassOfInner::Dynamic(_)) => { - ConstraintSet::never(inferable) + ConstraintSet::from(false) } (SubclassOfInner::Class(self_class), SubclassOfInner::Class(other_class)) => { - ConstraintSet::from_bool( - !self_class.could_coexist_in_mro_with(db, other_class), - inferable, - ) + ConstraintSet::from(!self_class.could_coexist_in_mro_with(db, other_class)) } } } diff --git a/crates/ty_python_semantic/src/types/tuple.rs b/crates/ty_python_semantic/src/types/tuple.rs index 6a439dfb80..43d0873546 100644 --- a/crates/ty_python_semantic/src/types/tuple.rs +++ b/crates/ty_python_semantic/src/types/tuple.rs @@ -448,8 +448,8 @@ impl<'db> FixedLengthTuple> { ) -> ConstraintSet<'db> { match other { Tuple::Fixed(other) => { - ConstraintSet::from_bool(self.0.len() == other.0.len(), inferable).and(db, || { - (self.0.iter().zip(&other.0)).when_all(db, inferable, |(self_ty, other_ty)| { + ConstraintSet::from(self.0.len() == other.0.len()).and(db, || { + (self.0.iter().zip(&other.0)).when_all(db, |(self_ty, other_ty)| { self_ty.has_relation_to_impl( db, *other_ty, @@ -465,11 +465,11 @@ impl<'db> FixedLengthTuple> { Tuple::Variable(other) => { // This tuple must have enough elements to match up with the other tuple's prefix // and suffix, and each of those elements must pairwise satisfy the relation. - let mut result = ConstraintSet::always(inferable); + let mut result = ConstraintSet::from(true); let mut self_iter = self.0.iter(); for other_ty in &other.prefix { let Some(self_ty) = self_iter.next() else { - return ConstraintSet::never(inferable); + return ConstraintSet::from(false); }; let element_constraints = self_ty.has_relation_to_impl( db, @@ -488,7 +488,7 @@ impl<'db> FixedLengthTuple> { } for other_ty in other.suffix.iter().rev() { let Some(self_ty) = self_iter.next_back() else { - return ConstraintSet::never(inferable); + return ConstraintSet::from(false); }; let element_constraints = self_ty.has_relation_to_impl( db, @@ -509,7 +509,7 @@ impl<'db> FixedLengthTuple> { // In addition, any remaining elements in this tuple must satisfy the // variable-length portion of the other tuple. result.and(db, || { - self_iter.when_all(db, inferable, |self_ty| { + self_iter.when_all(db, |self_ty| { self_ty.has_relation_to_impl( db, other.variable, @@ -531,10 +531,10 @@ impl<'db> FixedLengthTuple> { inferable: InferableTypeVars<'db>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { - ConstraintSet::from_bool(self.0.len() == other.0.len(), inferable).and(db, || { + ConstraintSet::from(self.0.len() == other.0.len()).and(db, || { (self.0.iter()) .zip(&other.0) - .when_all(db, inferable, |(self_ty, other_ty)| { + .when_all(db, |(self_ty, other_ty)| { self_ty.is_equivalent_to_impl(db, *other_ty, inferable, visitor) }) }) @@ -816,17 +816,17 @@ impl<'db> VariableLengthTuple> { // possible lengths. This means that `tuple[Any, ...]` can match any tuple of any // length. if !relation.is_assignability() || !self.variable.is_dynamic() { - return ConstraintSet::never(inferable); + return ConstraintSet::from(false); } // In addition, the other tuple must have enough elements to match up with this // tuple's prefix and suffix, and each of those elements must pairwise satisfy the // relation. - let mut result = ConstraintSet::always(inferable); + let mut result = ConstraintSet::from(true); let mut other_iter = other.elements().copied(); for self_ty in self.prenormalized_prefix_elements(db, None) { let Some(other_ty) = other_iter.next() else { - return ConstraintSet::never(inferable); + return ConstraintSet::from(false); }; let element_constraints = self_ty.has_relation_to_impl( db, @@ -846,7 +846,7 @@ impl<'db> VariableLengthTuple> { let suffix: Vec<_> = self.prenormalized_suffix_elements(db, None).collect(); for self_ty in suffix.iter().rev() { let Some(other_ty) = other_iter.next_back() else { - return ConstraintSet::never(inferable); + return ConstraintSet::from(false); }; let element_constraints = self_ty.has_relation_to_impl( db, @@ -882,7 +882,7 @@ impl<'db> VariableLengthTuple> { // The overlapping parts of the prefixes and suffixes must satisfy the relation. // Any remaining parts must satisfy the relation with the other tuple's // variable-length part. - let mut result = ConstraintSet::always(inferable); + let mut result = ConstraintSet::from(true); let pairwise = (self.prenormalized_prefix_elements(db, self_prenormalize_variable)) .zip_longest( other.prenormalized_prefix_elements(db, other_prenormalize_variable), @@ -908,7 +908,7 @@ impl<'db> VariableLengthTuple> { EitherOrBoth::Right(_) => { // The rhs has a required element that the lhs is not guaranteed to // provide. - return ConstraintSet::never(inferable); + return ConstraintSet::from(false); } }; if result @@ -947,7 +947,7 @@ impl<'db> VariableLengthTuple> { EitherOrBoth::Right(_) => { // The rhs has a required element that the lhs is not guaranteed to // provide. - return ConstraintSet::never(inferable); + return ConstraintSet::from(false); } }; if result @@ -985,24 +985,24 @@ impl<'db> VariableLengthTuple> { .and(db, || { (self.prenormalized_prefix_elements(db, None)) .zip_longest(other.prenormalized_prefix_elements(db, None)) - .when_all(db, inferable, |pair| match pair { + .when_all(db, |pair| match pair { EitherOrBoth::Both(self_ty, other_ty) => { self_ty.is_equivalent_to_impl(db, other_ty, inferable, visitor) } EitherOrBoth::Left(_) | EitherOrBoth::Right(_) => { - ConstraintSet::never(inferable) + ConstraintSet::from(false) } }) }) .and(db, || { (self.prenormalized_suffix_elements(db, None)) .zip_longest(other.prenormalized_suffix_elements(db, None)) - .when_all(db, inferable, |pair| match pair { + .when_all(db, |pair| match pair { EitherOrBoth::Both(self_ty, other_ty) => { self_ty.is_equivalent_to_impl(db, other_ty, inferable, visitor) } EitherOrBoth::Left(_) | EitherOrBoth::Right(_) => { - ConstraintSet::never(inferable) + ConstraintSet::from(false) } }) }) @@ -1230,7 +1230,7 @@ impl<'db> Tuple> { self_tuple.is_equivalent_to_impl(db, other_tuple, inferable, visitor) } (Tuple::Fixed(_), Tuple::Variable(_)) | (Tuple::Variable(_), Tuple::Fixed(_)) => { - ConstraintSet::never(inferable) + ConstraintSet::from(false) } } } @@ -1247,10 +1247,10 @@ impl<'db> Tuple> { let (self_min, self_max) = self.len().size_hint(); let (other_min, other_max) = other.len().size_hint(); if self_max.is_some_and(|max| max < other_min) { - return ConstraintSet::always(inferable); + return ConstraintSet::from(true); } if other_max.is_some_and(|max| max < self_min) { - return ConstraintSet::always(inferable); + return ConstraintSet::from(true); } // If any of the required elements are pairwise disjoint, the tuples are disjoint as well. @@ -1266,7 +1266,7 @@ impl<'db> Tuple> { where 'db: 's, { - (a.into_iter().zip(b)).when_any(db, inferable, |(self_element, other_element)| { + (a.into_iter().zip(b)).when_any(db, |(self_element, other_element)| { self_element.is_disjoint_from_impl( db, *other_element, diff --git a/crates/ty_vendored/ty_extensions/ty_extensions.pyi b/crates/ty_vendored/ty_extensions/ty_extensions.pyi index 85ef11535a..744bd5af37 100644 --- a/crates/ty_vendored/ty_extensions/ty_extensions.pyi +++ b/crates/ty_vendored/ty_extensions/ty_extensions.pyi @@ -59,12 +59,6 @@ class ConstraintSet: def never() -> Self: """Returns a constraint set that is never satisfied""" - def with_inferable(self, *inferable: Any) -> Self: - """ - Returns a copy of this constraint set with some typevars marked as being - in inferable position. - """ - def implies_subtype_of(self, ty: Any, of: Any) -> Self: """ Returns a constraint set that is satisfied when `ty` is a `subtype`_ of