[red-knot] More exhaustive disjointness tests (#15606)
## Summary Mostly just brings the structure/format of the disjointness-tests closer to what we have for `is_subtype_of` etc.
This commit is contained in:
@@ -1,85 +1,60 @@
|
||||
# Tests for disjointness
|
||||
# Disjointness relation
|
||||
|
||||
If two types can be disjoint, it means that it is known that no possible runtime object could ever
|
||||
inhabit both types simultaneously.
|
||||
Two types `S` and `T` are disjoint if their intersection `S & T` is empty (equivalent to `Never`).
|
||||
This means that it is known that no possible runtime object inhabits both types simultaneously.
|
||||
|
||||
## `Never`
|
||||
|
||||
`Never` is disjoint from every type, including itself.
|
||||
## Basic builtin types
|
||||
|
||||
```py
|
||||
from typing_extensions import Never
|
||||
from knot_extensions import is_disjoint_from, static_assert
|
||||
from typing_extensions import Literal, LiteralString, Any
|
||||
from knot_extensions import Intersection, Not, TypeOf, is_disjoint_from, static_assert
|
||||
|
||||
static_assert(is_disjoint_from(Never, Never))
|
||||
static_assert(is_disjoint_from(Never, None))
|
||||
static_assert(is_disjoint_from(Never, int))
|
||||
```
|
||||
static_assert(is_disjoint_from(bool, str))
|
||||
static_assert(not is_disjoint_from(bool, bool))
|
||||
static_assert(not is_disjoint_from(bool, int))
|
||||
static_assert(not is_disjoint_from(bool, object))
|
||||
|
||||
## `None`
|
||||
static_assert(not is_disjoint_from(Any, bool))
|
||||
static_assert(not is_disjoint_from(Any, Any))
|
||||
|
||||
```py
|
||||
from typing_extensions import Literal
|
||||
from knot_extensions import AlwaysFalsy, AlwaysTruthy, is_disjoint_from, static_assert
|
||||
|
||||
static_assert(is_disjoint_from(None, Literal[True]))
|
||||
static_assert(is_disjoint_from(None, Literal[1]))
|
||||
static_assert(is_disjoint_from(None, Literal["test"]))
|
||||
static_assert(is_disjoint_from(None, Literal[b"test"]))
|
||||
static_assert(is_disjoint_from(None, LiteralString))
|
||||
static_assert(is_disjoint_from(None, int))
|
||||
static_assert(is_disjoint_from(None, type[object]))
|
||||
static_assert(is_disjoint_from(None, AlwaysTruthy))
|
||||
|
||||
static_assert(not is_disjoint_from(None, None))
|
||||
static_assert(not is_disjoint_from(None, object))
|
||||
static_assert(not is_disjoint_from(None, AlwaysFalsy))
|
||||
```
|
||||
|
||||
## Basic
|
||||
|
||||
```py
|
||||
from typing_extensions import Literal, LiteralString
|
||||
from knot_extensions import AlwaysFalsy, AlwaysTruthy, Intersection, Not, TypeOf, is_disjoint_from, static_assert
|
||||
|
||||
static_assert(is_disjoint_from(Literal[True], Literal[False]))
|
||||
static_assert(is_disjoint_from(Literal[True], Literal[1]))
|
||||
static_assert(is_disjoint_from(Literal[False], Literal[0]))
|
||||
|
||||
static_assert(is_disjoint_from(Literal[1], Literal[2]))
|
||||
|
||||
static_assert(is_disjoint_from(Literal["a"], Literal["b"]))
|
||||
|
||||
static_assert(is_disjoint_from(Literal[b"a"], LiteralString))
|
||||
static_assert(is_disjoint_from(Literal[b"a"], Literal[b"b"]))
|
||||
static_assert(is_disjoint_from(Literal[b"a"], Literal["a"]))
|
||||
|
||||
static_assert(is_disjoint_from(type[object], TypeOf[Literal]))
|
||||
static_assert(is_disjoint_from(type[str], LiteralString))
|
||||
|
||||
static_assert(is_disjoint_from(AlwaysFalsy, AlwaysTruthy))
|
||||
|
||||
static_assert(not is_disjoint_from(Any, int))
|
||||
|
||||
static_assert(not is_disjoint_from(Literal[True], Literal[True]))
|
||||
static_assert(not is_disjoint_from(Literal[False], Literal[False]))
|
||||
static_assert(not is_disjoint_from(Literal[True], bool))
|
||||
static_assert(not is_disjoint_from(Literal[True], int))
|
||||
|
||||
static_assert(not is_disjoint_from(Literal[1], Literal[1]))
|
||||
|
||||
static_assert(not is_disjoint_from(Literal["a"], Literal["a"]))
|
||||
static_assert(not is_disjoint_from(Literal["a"], LiteralString))
|
||||
static_assert(not is_disjoint_from(Literal["a"], str))
|
||||
|
||||
static_assert(not is_disjoint_from(int, int))
|
||||
static_assert(not is_disjoint_from(LiteralString, LiteralString))
|
||||
|
||||
static_assert(not is_disjoint_from(str, LiteralString))
|
||||
static_assert(not is_disjoint_from(str, type))
|
||||
static_assert(not is_disjoint_from(str, type[Any]))
|
||||
static_assert(not is_disjoint_from(str, AlwaysFalsy))
|
||||
static_assert(not is_disjoint_from(str, AlwaysTruthy))
|
||||
```
|
||||
|
||||
## Class hierarchies
|
||||
|
||||
```py
|
||||
from knot_extensions import is_disjoint_from, static_assert, Intersection, is_subtype_of
|
||||
from typing import final
|
||||
|
||||
class A: ...
|
||||
class B1(A): ...
|
||||
class B2(A): ...
|
||||
|
||||
# B1 and B2 are subclasses of A, so they are not disjoint from A:
|
||||
static_assert(not is_disjoint_from(A, B1))
|
||||
static_assert(not is_disjoint_from(A, B2))
|
||||
|
||||
# The two subclasses B1 and B2 are also not disjoint ...
|
||||
static_assert(not is_disjoint_from(B1, B2))
|
||||
|
||||
# ... because they could share a common subclass ...
|
||||
class C(B1, B2): ...
|
||||
|
||||
# ... which lies in their intersection:
|
||||
static_assert(is_subtype_of(C, Intersection[B1, B2]))
|
||||
|
||||
# However, if a class is marked final, it can not be subclassed ...
|
||||
@final
|
||||
class FinalSubclass(A): ...
|
||||
|
||||
static_assert(not is_disjoint_from(FinalSubclass, A))
|
||||
|
||||
# ... which makes it disjoint from B1, B2:
|
||||
static_assert(is_disjoint_from(B1, FinalSubclass))
|
||||
static_assert(is_disjoint_from(B2, FinalSubclass))
|
||||
```
|
||||
|
||||
## Tuple types
|
||||
@@ -108,22 +83,124 @@ static_assert(not is_disjoint_from(tuple[Literal[1], Literal[2]], tuple[Literal[
|
||||
|
||||
```py
|
||||
from typing_extensions import Literal
|
||||
from knot_extensions import AlwaysFalsy, AlwaysTruthy, Intersection, is_disjoint_from, static_assert
|
||||
from knot_extensions import Intersection, is_disjoint_from, static_assert
|
||||
|
||||
static_assert(is_disjoint_from(Literal[1, 2], Literal[3]))
|
||||
static_assert(is_disjoint_from(Literal[1, 2], Literal[3, 4]))
|
||||
static_assert(is_disjoint_from(Literal[1, 2], AlwaysFalsy))
|
||||
|
||||
static_assert(is_disjoint_from(Intersection[int, Literal[1]], Literal[2]))
|
||||
|
||||
static_assert(not is_disjoint_from(Literal[1, 2], Literal[2]))
|
||||
static_assert(not is_disjoint_from(Literal[1, 2], Literal[2, 3]))
|
||||
static_assert(not is_disjoint_from(Literal[0, 1], AlwaysTruthy))
|
||||
|
||||
static_assert(not is_disjoint_from(Intersection[int, Literal[2]], Literal[2]))
|
||||
```
|
||||
|
||||
## Class, module and function literals
|
||||
## Intersections
|
||||
|
||||
```py
|
||||
from typing_extensions import Literal, final
|
||||
from knot_extensions import Intersection, is_disjoint_from, static_assert
|
||||
|
||||
@final
|
||||
class P: ...
|
||||
|
||||
@final
|
||||
class Q: ...
|
||||
|
||||
@final
|
||||
class R: ...
|
||||
|
||||
# For three pairwise disjoint classes ...
|
||||
static_assert(is_disjoint_from(P, Q))
|
||||
static_assert(is_disjoint_from(P, R))
|
||||
static_assert(is_disjoint_from(Q, R))
|
||||
|
||||
# ... their intersections are also disjoint:
|
||||
static_assert(is_disjoint_from(Intersection[P, Q], R))
|
||||
static_assert(is_disjoint_from(Intersection[P, R], Q))
|
||||
static_assert(is_disjoint_from(Intersection[Q, R], P))
|
||||
|
||||
# On the other hand, for non-disjoint classes ...
|
||||
class X: ...
|
||||
class Y: ...
|
||||
class Z: ...
|
||||
|
||||
static_assert(not is_disjoint_from(X, Y))
|
||||
static_assert(not is_disjoint_from(X, Z))
|
||||
static_assert(not is_disjoint_from(Y, Z))
|
||||
|
||||
# ... their intersections are also not disjoint:
|
||||
static_assert(not is_disjoint_from(Intersection[X, Y], Z))
|
||||
static_assert(not is_disjoint_from(Intersection[X, Z], Y))
|
||||
static_assert(not is_disjoint_from(Intersection[Y, Z], X))
|
||||
```
|
||||
|
||||
## Special types
|
||||
|
||||
### `Never`
|
||||
|
||||
`Never` is disjoint from every type, including itself.
|
||||
|
||||
```py
|
||||
from typing_extensions import Never
|
||||
from knot_extensions import is_disjoint_from, static_assert
|
||||
|
||||
static_assert(is_disjoint_from(Never, Never))
|
||||
static_assert(is_disjoint_from(Never, None))
|
||||
static_assert(is_disjoint_from(Never, int))
|
||||
static_assert(is_disjoint_from(Never, object))
|
||||
```
|
||||
|
||||
### `None`
|
||||
|
||||
```py
|
||||
from typing_extensions import Literal
|
||||
from knot_extensions import is_disjoint_from, static_assert
|
||||
|
||||
static_assert(is_disjoint_from(None, Literal[True]))
|
||||
static_assert(is_disjoint_from(None, Literal[1]))
|
||||
static_assert(is_disjoint_from(None, Literal["test"]))
|
||||
static_assert(is_disjoint_from(None, Literal[b"test"]))
|
||||
static_assert(is_disjoint_from(None, LiteralString))
|
||||
static_assert(is_disjoint_from(None, int))
|
||||
static_assert(is_disjoint_from(None, type[object]))
|
||||
|
||||
static_assert(not is_disjoint_from(None, None))
|
||||
static_assert(not is_disjoint_from(None, int | None))
|
||||
static_assert(not is_disjoint_from(None, object))
|
||||
```
|
||||
|
||||
### Literals
|
||||
|
||||
```py
|
||||
from typing_extensions import Literal, LiteralString
|
||||
from knot_extensions import TypeOf, is_disjoint_from, static_assert
|
||||
|
||||
static_assert(is_disjoint_from(Literal[True], Literal[False]))
|
||||
static_assert(is_disjoint_from(Literal[True], Literal[1]))
|
||||
static_assert(is_disjoint_from(Literal[False], Literal[0]))
|
||||
|
||||
static_assert(is_disjoint_from(Literal[1], Literal[2]))
|
||||
|
||||
static_assert(is_disjoint_from(Literal["a"], Literal["b"]))
|
||||
|
||||
static_assert(is_disjoint_from(Literal[b"a"], LiteralString))
|
||||
static_assert(is_disjoint_from(Literal[b"a"], Literal[b"b"]))
|
||||
static_assert(is_disjoint_from(Literal[b"a"], Literal["a"]))
|
||||
|
||||
static_assert(is_disjoint_from(type[object], TypeOf[Literal]))
|
||||
static_assert(is_disjoint_from(type[str], LiteralString))
|
||||
|
||||
static_assert(not is_disjoint_from(Literal[True], Literal[True]))
|
||||
static_assert(not is_disjoint_from(Literal[False], Literal[False]))
|
||||
static_assert(not is_disjoint_from(Literal[True], bool))
|
||||
static_assert(not is_disjoint_from(Literal[True], int))
|
||||
|
||||
static_assert(not is_disjoint_from(Literal[1], Literal[1]))
|
||||
|
||||
static_assert(not is_disjoint_from(Literal["a"], Literal["a"]))
|
||||
static_assert(not is_disjoint_from(Literal["a"], LiteralString))
|
||||
static_assert(not is_disjoint_from(Literal["a"], str))
|
||||
```
|
||||
|
||||
### Class, module and function literals
|
||||
|
||||
```py
|
||||
from types import ModuleType, FunctionType
|
||||
@@ -132,9 +209,21 @@ from knot_extensions import TypeOf, is_disjoint_from, static_assert
|
||||
class A: ...
|
||||
class B: ...
|
||||
|
||||
static_assert(is_disjoint_from(TypeOf[A], TypeOf[B]))
|
||||
static_assert(is_disjoint_from(TypeOf[A], type[B]))
|
||||
static_assert(not is_disjoint_from(type[A], TypeOf[A]))
|
||||
type LiteralA = TypeOf[A]
|
||||
type LiteralB = TypeOf[B]
|
||||
|
||||
# Class literals for different classes are always disjoint.
|
||||
# They are singleton types that only contain the class object itself.
|
||||
static_assert(is_disjoint_from(LiteralA, LiteralB))
|
||||
|
||||
# The class A is a subclass of A, so A is not disjoint from type[A]:
|
||||
static_assert(not is_disjoint_from(LiteralA, type[A]))
|
||||
|
||||
# The class A is disjoint from type[B] because it's not a subclass of B:
|
||||
static_assert(is_disjoint_from(LiteralA, type[B]))
|
||||
|
||||
# However, type[A] is not disjoint from type[B], as there could be
|
||||
# classes that inherit from both A and B:
|
||||
static_assert(not is_disjoint_from(type[A], type[B]))
|
||||
|
||||
import random
|
||||
@@ -152,7 +241,23 @@ static_assert(not is_disjoint_from(TypeOf[f], FunctionType))
|
||||
static_assert(not is_disjoint_from(TypeOf[f], object))
|
||||
```
|
||||
|
||||
## Instance types versus `type[T]` types
|
||||
### `AlwaysTruthy` and `AlwaysFalsy`
|
||||
|
||||
```py
|
||||
from knot_extensions import AlwaysFalsy, AlwaysTruthy, is_disjoint_from, static_assert
|
||||
|
||||
static_assert(is_disjoint_from(None, AlwaysTruthy))
|
||||
static_assert(not is_disjoint_from(None, AlwaysFalsy))
|
||||
|
||||
static_assert(is_disjoint_from(AlwaysFalsy, AlwaysTruthy))
|
||||
static_assert(not is_disjoint_from(str, AlwaysFalsy))
|
||||
static_assert(not is_disjoint_from(str, AlwaysTruthy))
|
||||
|
||||
static_assert(is_disjoint_from(Literal[1, 2], AlwaysFalsy))
|
||||
static_assert(not is_disjoint_from(Literal[0, 1], AlwaysTruthy))
|
||||
```
|
||||
|
||||
### Instance types versus `type[T]` types
|
||||
|
||||
An instance type is disjoint from a `type[T]` type if the instance type is `@final` and the class of
|
||||
the instance type is not a subclass of `T`'s metaclass.
|
||||
@@ -185,7 +290,7 @@ static_assert(not is_disjoint_from(Meta2, type[UsesMeta2]))
|
||||
static_assert(is_disjoint_from(Meta1, type[UsesMeta2]))
|
||||
```
|
||||
|
||||
## `type[T]` versus `type[S]`
|
||||
### `type[T]` versus `type[S]`
|
||||
|
||||
By the same token, `type[T]` is disjoint from `type[S]` if the metaclass of `T` is disjoint from the
|
||||
metaclass of `S`.
|
||||
|
||||
Reference in New Issue
Block a user