mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 13:24:57 +00:00
[ty] Introduce a representation for the top/bottom materialization of an invariant generic (#20076)
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks-instrumented (push) Blocked by required conditions
CI / benchmarks-walltime (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks-instrumented (push) Blocked by required conditions
CI / benchmarks-walltime (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run
Part of #994. This adds a new field to the Specialization struct to record when we're dealing with the top or bottom materialization of an invariant generic. It also implements subtyping and assignability for these objects. Next planned steps after this is done are to implement other operations on top/bottom materializations; probably attribute access is an important one. --------- Co-authored-by: Carl Meyer <carl@astral.sh>
This commit is contained in:
parent
af259faed5
commit
18eaa659c1
10 changed files with 696 additions and 191 deletions
|
@ -47,7 +47,7 @@ The invariant position is replaced with an unresolved type variable.
|
||||||
|
|
||||||
```py
|
```py
|
||||||
def _(top_list: Top[list[Any]]):
|
def _(top_list: Top[list[Any]]):
|
||||||
reveal_type(top_list) # revealed: list[T_all]
|
reveal_type(top_list) # revealed: Top[list[Any]]
|
||||||
```
|
```
|
||||||
|
|
||||||
### Bottom materialization
|
### Bottom materialization
|
||||||
|
@ -75,7 +75,7 @@ type variable.
|
||||||
|
|
||||||
```py
|
```py
|
||||||
def _(bottom_list: Bottom[list[Any]]):
|
def _(bottom_list: Bottom[list[Any]]):
|
||||||
reveal_type(bottom_list) # revealed: list[T_all]
|
reveal_type(bottom_list) # revealed: Bottom[list[Any]]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Fully static types
|
## Fully static types
|
||||||
|
@ -230,14 +230,14 @@ def _(
|
||||||
top_aiu: Top[LTAnyIntUnknown],
|
top_aiu: Top[LTAnyIntUnknown],
|
||||||
bottom_aiu: Bottom[LTAnyIntUnknown],
|
bottom_aiu: Bottom[LTAnyIntUnknown],
|
||||||
):
|
):
|
||||||
reveal_type(top_ai) # revealed: list[tuple[T_all, int]]
|
reveal_type(top_ai) # revealed: Top[list[tuple[Any, int]]]
|
||||||
reveal_type(bottom_ai) # revealed: list[tuple[T_all, int]]
|
reveal_type(bottom_ai) # revealed: Bottom[list[tuple[Any, int]]]
|
||||||
|
|
||||||
reveal_type(top_su) # revealed: list[tuple[str, T_all]]
|
reveal_type(top_su) # revealed: Top[list[tuple[str, Unknown]]]
|
||||||
reveal_type(bottom_su) # revealed: list[tuple[str, T_all]]
|
reveal_type(bottom_su) # revealed: Bottom[list[tuple[str, Unknown]]]
|
||||||
|
|
||||||
reveal_type(top_aiu) # revealed: list[tuple[T_all, int, T_all]]
|
reveal_type(top_aiu) # revealed: Top[list[tuple[Any, int, Unknown]]]
|
||||||
reveal_type(bottom_aiu) # revealed: list[tuple[T_all, int, T_all]]
|
reveal_type(bottom_aiu) # revealed: Bottom[list[tuple[Any, int, Unknown]]]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Union
|
## Union
|
||||||
|
@ -286,14 +286,14 @@ def _(
|
||||||
top_aiu: Top[list[Any | int | Unknown]],
|
top_aiu: Top[list[Any | int | Unknown]],
|
||||||
bottom_aiu: Bottom[list[Any | int | Unknown]],
|
bottom_aiu: Bottom[list[Any | int | Unknown]],
|
||||||
):
|
):
|
||||||
reveal_type(top_ai) # revealed: list[T_all | int]
|
reveal_type(top_ai) # revealed: Top[list[Any | int]]
|
||||||
reveal_type(bottom_ai) # revealed: list[T_all | int]
|
reveal_type(bottom_ai) # revealed: Bottom[list[Any | int]]
|
||||||
|
|
||||||
reveal_type(top_su) # revealed: list[str | T_all]
|
reveal_type(top_su) # revealed: Top[list[str | Unknown]]
|
||||||
reveal_type(bottom_su) # revealed: list[str | T_all]
|
reveal_type(bottom_su) # revealed: Bottom[list[str | Unknown]]
|
||||||
|
|
||||||
reveal_type(top_aiu) # revealed: list[T_all | int]
|
reveal_type(top_aiu) # revealed: Top[list[Any | int]]
|
||||||
reveal_type(bottom_aiu) # revealed: list[T_all | int]
|
reveal_type(bottom_aiu) # revealed: Bottom[list[Any | int]]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Intersection
|
## Intersection
|
||||||
|
@ -320,8 +320,10 @@ def _(
|
||||||
top: Top[Intersection[list[Any], list[int]]],
|
top: Top[Intersection[list[Any], list[int]]],
|
||||||
bottom: Bottom[Intersection[list[Any], list[int]]],
|
bottom: Bottom[Intersection[list[Any], list[int]]],
|
||||||
):
|
):
|
||||||
reveal_type(top) # revealed: list[T_all] & list[int]
|
# Top[list[Any] & list[int]] = Top[list[Any]] & list[int] = list[int]
|
||||||
reveal_type(bottom) # revealed: list[T_all] & list[int]
|
reveal_type(top) # revealed: list[int]
|
||||||
|
# Bottom[list[Any] & list[int]] = Bottom[list[Any]] & list[int] = Bottom[list[Any]]
|
||||||
|
reveal_type(bottom) # revealed: Bottom[list[Any]]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Negation (via `Not`)
|
## Negation (via `Not`)
|
||||||
|
@ -366,8 +368,8 @@ static_assert(is_equivalent_to(Bottom[type[int | Any]], type[int]))
|
||||||
|
|
||||||
# Here, `T` has an upper bound of `type`
|
# Here, `T` has an upper bound of `type`
|
||||||
def _(top: Top[list[type[Any]]], bottom: Bottom[list[type[Any]]]):
|
def _(top: Top[list[type[Any]]], bottom: Bottom[list[type[Any]]]):
|
||||||
reveal_type(top) # revealed: list[T_all]
|
reveal_type(top) # revealed: Top[list[type[Any]]]
|
||||||
reveal_type(bottom) # revealed: list[T_all]
|
reveal_type(bottom) # revealed: Bottom[list[type[Any]]]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Type variables
|
## Type variables
|
||||||
|
@ -427,8 +429,8 @@ class GenericContravariant(Generic[T_contra]):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _(top: Top[GenericInvariant[Any]], bottom: Bottom[GenericInvariant[Any]]):
|
def _(top: Top[GenericInvariant[Any]], bottom: Bottom[GenericInvariant[Any]]):
|
||||||
reveal_type(top) # revealed: GenericInvariant[T_all]
|
reveal_type(top) # revealed: Top[GenericInvariant[Any]]
|
||||||
reveal_type(bottom) # revealed: GenericInvariant[T_all]
|
reveal_type(bottom) # revealed: Bottom[GenericInvariant[Any]]
|
||||||
|
|
||||||
static_assert(is_equivalent_to(Top[GenericCovariant[Any]], GenericCovariant[object]))
|
static_assert(is_equivalent_to(Top[GenericCovariant[Any]], GenericCovariant[object]))
|
||||||
static_assert(is_equivalent_to(Bottom[GenericCovariant[Any]], GenericCovariant[Never]))
|
static_assert(is_equivalent_to(Bottom[GenericCovariant[Any]], GenericCovariant[Never]))
|
||||||
|
@ -448,8 +450,8 @@ type CovariantCallable = Callable[[GenericCovariant[Any]], None]
|
||||||
type ContravariantCallable = Callable[[GenericContravariant[Any]], None]
|
type ContravariantCallable = Callable[[GenericContravariant[Any]], None]
|
||||||
|
|
||||||
def invariant(top: Top[InvariantCallable], bottom: Bottom[InvariantCallable]) -> None:
|
def invariant(top: Top[InvariantCallable], bottom: Bottom[InvariantCallable]) -> None:
|
||||||
reveal_type(top) # revealed: (GenericInvariant[T_all], /) -> None
|
reveal_type(top) # revealed: (Bottom[GenericInvariant[Any]], /) -> None
|
||||||
reveal_type(bottom) # revealed: (GenericInvariant[T_all], /) -> None
|
reveal_type(bottom) # revealed: (Top[GenericInvariant[Any]], /) -> None
|
||||||
|
|
||||||
def covariant(top: Top[CovariantCallable], bottom: Bottom[CovariantCallable]) -> None:
|
def covariant(top: Top[CovariantCallable], bottom: Bottom[CovariantCallable]) -> None:
|
||||||
reveal_type(top) # revealed: (GenericCovariant[Never], /) -> None
|
reveal_type(top) # revealed: (GenericCovariant[Never], /) -> None
|
||||||
|
@ -492,3 +494,207 @@ def _(
|
||||||
bottom_1: Bottom[1], # error: [invalid-type-form]
|
bottom_1: Bottom[1], # error: [invalid-type-form]
|
||||||
): ...
|
): ...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Nested use
|
||||||
|
|
||||||
|
`Top[T]` and `Bottom[T]` are always fully static types. Therefore, they have only one
|
||||||
|
materialization (themselves) and applying `Top` or `Bottom` again does nothing.
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import Any
|
||||||
|
from ty_extensions import Top, Bottom, static_assert, is_equivalent_to
|
||||||
|
|
||||||
|
static_assert(is_equivalent_to(Top[Top[list[Any]]], Top[list[Any]]))
|
||||||
|
static_assert(is_equivalent_to(Bottom[Top[list[Any]]], Top[list[Any]]))
|
||||||
|
|
||||||
|
static_assert(is_equivalent_to(Bottom[Bottom[list[Any]]], Bottom[list[Any]]))
|
||||||
|
static_assert(is_equivalent_to(Top[Bottom[list[Any]]], Bottom[list[Any]]))
|
||||||
|
```
|
||||||
|
|
||||||
|
## Subtyping
|
||||||
|
|
||||||
|
Any `list[T]` is a subtype of `Top[list[Any]]`, but with more restrictive gradual types, not all
|
||||||
|
other specializations are subtypes.
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import Any, Literal
|
||||||
|
from ty_extensions import is_subtype_of, static_assert, Top, Intersection, Bottom
|
||||||
|
|
||||||
|
# None and Top
|
||||||
|
static_assert(is_subtype_of(list[int], Top[list[Any]]))
|
||||||
|
static_assert(not is_subtype_of(Top[list[Any]], list[int]))
|
||||||
|
static_assert(is_subtype_of(list[bool], Top[list[Intersection[int, Any]]]))
|
||||||
|
static_assert(is_subtype_of(list[int], Top[list[Intersection[int, Any]]]))
|
||||||
|
static_assert(not is_subtype_of(list[int | str], Top[list[Intersection[int, Any]]]))
|
||||||
|
static_assert(not is_subtype_of(list[object], Top[list[Intersection[int, Any]]]))
|
||||||
|
static_assert(not is_subtype_of(list[str], Top[list[Intersection[int, Any]]]))
|
||||||
|
static_assert(not is_subtype_of(list[str | bool], Top[list[Intersection[int, Any]]]))
|
||||||
|
|
||||||
|
# Top and Top
|
||||||
|
static_assert(is_subtype_of(Top[list[int | Any]], Top[list[Any]]))
|
||||||
|
static_assert(not is_subtype_of(Top[list[Any]], Top[list[int | Any]]))
|
||||||
|
static_assert(is_subtype_of(Top[list[Intersection[int, Any]]], Top[list[Any]]))
|
||||||
|
static_assert(not is_subtype_of(Top[list[Any]], Top[list[Intersection[int, Any]]]))
|
||||||
|
static_assert(not is_subtype_of(Top[list[Intersection[int, Any]]], Top[list[int | Any]]))
|
||||||
|
static_assert(not is_subtype_of(Top[list[int | Any]], Top[list[Intersection[int, Any]]]))
|
||||||
|
static_assert(not is_subtype_of(Top[list[str | Any]], Top[list[int | Any]]))
|
||||||
|
static_assert(is_subtype_of(Top[list[str | int | Any]], Top[list[int | Any]]))
|
||||||
|
static_assert(not is_subtype_of(Top[list[int | Any]], Top[list[str | int | Any]]))
|
||||||
|
|
||||||
|
# Bottom and Top
|
||||||
|
static_assert(is_subtype_of(Bottom[list[Any]], Top[list[Any]]))
|
||||||
|
static_assert(is_subtype_of(Bottom[list[Any]], Top[list[int | Any]]))
|
||||||
|
static_assert(is_subtype_of(Bottom[list[int | Any]], Top[list[Any]]))
|
||||||
|
static_assert(is_subtype_of(Bottom[list[int | Any]], Top[list[int | str]]))
|
||||||
|
static_assert(is_subtype_of(Bottom[list[Intersection[int, Any]]], Top[list[Intersection[str, Any]]]))
|
||||||
|
static_assert(not is_subtype_of(Bottom[list[Intersection[int, bool | Any]]], Bottom[list[Intersection[str, Literal["x"] | Any]]]))
|
||||||
|
|
||||||
|
# None and None
|
||||||
|
static_assert(not is_subtype_of(list[int], list[Any]))
|
||||||
|
static_assert(not is_subtype_of(list[Any], list[int]))
|
||||||
|
static_assert(is_subtype_of(list[int], list[int]))
|
||||||
|
static_assert(not is_subtype_of(list[int], list[object]))
|
||||||
|
static_assert(not is_subtype_of(list[object], list[int]))
|
||||||
|
|
||||||
|
# Top and None
|
||||||
|
static_assert(not is_subtype_of(Top[list[Any]], list[Any]))
|
||||||
|
static_assert(not is_subtype_of(Top[list[Any]], list[int]))
|
||||||
|
static_assert(is_subtype_of(Top[list[int]], list[int]))
|
||||||
|
|
||||||
|
# Bottom and None
|
||||||
|
static_assert(is_subtype_of(Bottom[list[Any]], list[object]))
|
||||||
|
static_assert(is_subtype_of(Bottom[list[int | Any]], list[str | int]))
|
||||||
|
static_assert(not is_subtype_of(Bottom[list[str | Any]], list[Intersection[int, bool | Any]]))
|
||||||
|
|
||||||
|
# None and Bottom
|
||||||
|
static_assert(not is_subtype_of(list[int], Bottom[list[Any]]))
|
||||||
|
static_assert(not is_subtype_of(list[int], Bottom[list[int | Any]]))
|
||||||
|
static_assert(is_subtype_of(list[int], Bottom[list[int]]))
|
||||||
|
|
||||||
|
# Top and Bottom
|
||||||
|
static_assert(not is_subtype_of(Top[list[Any]], Bottom[list[Any]]))
|
||||||
|
static_assert(not is_subtype_of(Top[list[int | Any]], Bottom[list[int | Any]]))
|
||||||
|
static_assert(is_subtype_of(Top[list[int]], Bottom[list[int]]))
|
||||||
|
|
||||||
|
# Bottom and Bottom
|
||||||
|
static_assert(is_subtype_of(Bottom[list[Any]], Bottom[list[int | str | Any]]))
|
||||||
|
static_assert(is_subtype_of(Bottom[list[int | Any]], Bottom[list[int | str | Any]]))
|
||||||
|
static_assert(is_subtype_of(Bottom[list[bool | Any]], Bottom[list[int | Any]]))
|
||||||
|
static_assert(not is_subtype_of(Bottom[list[int | Any]], Bottom[list[bool | Any]]))
|
||||||
|
static_assert(not is_subtype_of(Bottom[list[int | Any]], Bottom[list[Any]]))
|
||||||
|
```
|
||||||
|
|
||||||
|
## Assignability
|
||||||
|
|
||||||
|
### General
|
||||||
|
|
||||||
|
Assignability is the same as subtyping for top and bottom materializations, because those are fully
|
||||||
|
static types, but some gradual types are assignable even if they are not subtypes.
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import Any, Literal
|
||||||
|
from ty_extensions import is_assignable_to, static_assert, Top, Intersection, Bottom
|
||||||
|
|
||||||
|
# None and Top
|
||||||
|
static_assert(is_assignable_to(list[Any], Top[list[Any]]))
|
||||||
|
static_assert(is_assignable_to(list[int], Top[list[Any]]))
|
||||||
|
static_assert(not is_assignable_to(Top[list[Any]], list[int]))
|
||||||
|
static_assert(is_assignable_to(list[bool], Top[list[Intersection[int, Any]]]))
|
||||||
|
static_assert(is_assignable_to(list[int], Top[list[Intersection[int, Any]]]))
|
||||||
|
static_assert(is_assignable_to(list[Any], Top[list[Intersection[int, Any]]]))
|
||||||
|
static_assert(not is_assignable_to(list[int | str], Top[list[Intersection[int, Any]]]))
|
||||||
|
static_assert(not is_assignable_to(list[object], Top[list[Intersection[int, Any]]]))
|
||||||
|
static_assert(not is_assignable_to(list[str], Top[list[Intersection[int, Any]]]))
|
||||||
|
static_assert(not is_assignable_to(list[str | bool], Top[list[Intersection[int, Any]]]))
|
||||||
|
|
||||||
|
# Top and Top
|
||||||
|
static_assert(is_assignable_to(Top[list[int | Any]], Top[list[Any]]))
|
||||||
|
static_assert(not is_assignable_to(Top[list[Any]], Top[list[int | Any]]))
|
||||||
|
static_assert(is_assignable_to(Top[list[Intersection[int, Any]]], Top[list[Any]]))
|
||||||
|
static_assert(not is_assignable_to(Top[list[Any]], Top[list[Intersection[int, Any]]]))
|
||||||
|
static_assert(not is_assignable_to(Top[list[Intersection[int, Any]]], Top[list[int | Any]]))
|
||||||
|
static_assert(not is_assignable_to(Top[list[int | Any]], Top[list[Intersection[int, Any]]]))
|
||||||
|
static_assert(not is_assignable_to(Top[list[str | Any]], Top[list[int | Any]]))
|
||||||
|
static_assert(is_assignable_to(Top[list[str | int | Any]], Top[list[int | Any]]))
|
||||||
|
static_assert(not is_assignable_to(Top[list[int | Any]], Top[list[str | int | Any]]))
|
||||||
|
|
||||||
|
# Bottom and Top
|
||||||
|
static_assert(is_assignable_to(Bottom[list[Any]], Top[list[Any]]))
|
||||||
|
static_assert(is_assignable_to(Bottom[list[Any]], Top[list[int | Any]]))
|
||||||
|
static_assert(is_assignable_to(Bottom[list[int | Any]], Top[list[Any]]))
|
||||||
|
static_assert(is_assignable_to(Bottom[list[Intersection[int, Any]]], Top[list[Intersection[str, Any]]]))
|
||||||
|
static_assert(
|
||||||
|
not is_assignable_to(Bottom[list[Intersection[int, bool | Any]]], Bottom[list[Intersection[str, Literal["x"] | Any]]])
|
||||||
|
)
|
||||||
|
|
||||||
|
# None and None
|
||||||
|
static_assert(is_assignable_to(list[int], list[Any]))
|
||||||
|
static_assert(is_assignable_to(list[Any], list[int]))
|
||||||
|
static_assert(is_assignable_to(list[int], list[int]))
|
||||||
|
static_assert(not is_assignable_to(list[int], list[object]))
|
||||||
|
static_assert(not is_assignable_to(list[object], list[int]))
|
||||||
|
|
||||||
|
# Top and None
|
||||||
|
static_assert(is_assignable_to(Top[list[Any]], list[Any]))
|
||||||
|
static_assert(not is_assignable_to(Top[list[Any]], list[int]))
|
||||||
|
static_assert(is_assignable_to(Top[list[int]], list[int]))
|
||||||
|
|
||||||
|
# Bottom and None
|
||||||
|
static_assert(is_assignable_to(Bottom[list[Any]], list[object]))
|
||||||
|
static_assert(is_assignable_to(Bottom[list[int | Any]], Top[list[str | int]]))
|
||||||
|
static_assert(not is_assignable_to(Bottom[list[str | Any]], list[Intersection[int, bool | Any]]))
|
||||||
|
|
||||||
|
# None and Bottom
|
||||||
|
static_assert(is_assignable_to(list[Any], Bottom[list[Any]]))
|
||||||
|
static_assert(not is_assignable_to(list[int], Bottom[list[Any]]))
|
||||||
|
static_assert(not is_assignable_to(list[int], Bottom[list[int | Any]]))
|
||||||
|
static_assert(is_assignable_to(list[int], Bottom[list[int]]))
|
||||||
|
|
||||||
|
# Top and Bottom
|
||||||
|
static_assert(not is_assignable_to(Top[list[Any]], Bottom[list[Any]]))
|
||||||
|
static_assert(not is_assignable_to(Top[list[int | Any]], Bottom[list[int | Any]]))
|
||||||
|
static_assert(is_assignable_to(Top[list[int]], Bottom[list[int]]))
|
||||||
|
|
||||||
|
# Bottom and Bottom
|
||||||
|
static_assert(is_assignable_to(Bottom[list[Any]], Bottom[list[int | str | Any]]))
|
||||||
|
static_assert(is_assignable_to(Bottom[list[int | Any]], Bottom[list[int | str | Any]]))
|
||||||
|
static_assert(is_assignable_to(Bottom[list[bool | Any]], Bottom[list[int | Any]]))
|
||||||
|
static_assert(not is_assignable_to(Bottom[list[int | Any]], Bottom[list[bool | Any]]))
|
||||||
|
static_assert(not is_assignable_to(Bottom[list[int | Any]], Bottom[list[Any]]))
|
||||||
|
```
|
||||||
|
|
||||||
|
### Subclasses with different variance
|
||||||
|
|
||||||
|
We need to take special care when an invariant class inherits from a covariant or contravariant one.
|
||||||
|
This comes up frequently in practice because `list` (invariant) inherits from `Sequence` and a
|
||||||
|
number of other covariant ABCs, but we'll use a synthetic example.
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import Generic, TypeVar, Any
|
||||||
|
from ty_extensions import static_assert, is_assignable_to, is_equivalent_to, Top
|
||||||
|
|
||||||
|
class A:
|
||||||
|
pass
|
||||||
|
|
||||||
|
class B(A):
|
||||||
|
pass
|
||||||
|
|
||||||
|
T_co = TypeVar("T_co", covariant=True)
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
class CovariantBase(Generic[T_co]):
|
||||||
|
def get(self) -> T_co:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
class InvariantChild(CovariantBase[T]):
|
||||||
|
def push(self, obj: T) -> None: ...
|
||||||
|
|
||||||
|
static_assert(is_assignable_to(InvariantChild[A], CovariantBase[A]))
|
||||||
|
static_assert(is_assignable_to(InvariantChild[B], CovariantBase[A]))
|
||||||
|
static_assert(not is_assignable_to(InvariantChild[A], CovariantBase[B]))
|
||||||
|
static_assert(not is_assignable_to(InvariantChild[B], InvariantChild[A]))
|
||||||
|
static_assert(is_equivalent_to(Top[CovariantBase[Any]], CovariantBase[object]))
|
||||||
|
static_assert(is_assignable_to(InvariantChild[Any], CovariantBase[A]))
|
||||||
|
|
||||||
|
static_assert(not is_assignable_to(Top[InvariantChild[Any]], CovariantBase[A]))
|
||||||
|
```
|
||||||
|
|
|
@ -195,6 +195,34 @@ pub(crate) struct IsEquivalent;
|
||||||
pub(crate) type NormalizedVisitor<'db> = TypeTransformer<'db, Normalized>;
|
pub(crate) type NormalizedVisitor<'db> = TypeTransformer<'db, Normalized>;
|
||||||
pub(crate) struct Normalized;
|
pub(crate) struct Normalized;
|
||||||
|
|
||||||
|
/// How a generic type has been specialized.
|
||||||
|
///
|
||||||
|
/// This matters only if there is at least one invariant type parameter.
|
||||||
|
/// For example, we represent `Top[list[Any]]` as a `GenericAlias` with
|
||||||
|
/// `MaterializationKind` set to Top, which we denote as `Top[list[Any]]`.
|
||||||
|
/// A type `Top[list[T]]` includes all fully static list types `list[U]` where `U` is
|
||||||
|
/// a supertype of `Bottom[T]` and a subtype of `Top[T]`.
|
||||||
|
///
|
||||||
|
/// Similarly, there is `Bottom[list[Any]]`.
|
||||||
|
/// This type is harder to make sense of in a set-theoretic framework, but
|
||||||
|
/// it is a subtype of all materializations of `list[Any]`.
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, get_size2::GetSize)]
|
||||||
|
pub enum MaterializationKind {
|
||||||
|
Top,
|
||||||
|
Bottom,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MaterializationKind {
|
||||||
|
/// Flip the materialization type: `Top` becomes `Bottom` and vice versa.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn flip(self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::Top => Self::Bottom,
|
||||||
|
Self::Bottom => Self::Top,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The descriptor protocol distinguishes two kinds of descriptors. Non-data descriptors
|
/// The descriptor protocol distinguishes two kinds of descriptors. Non-data descriptors
|
||||||
/// define a `__get__` method, while data descriptors additionally define a `__set__`
|
/// define a `__get__` method, while data descriptors additionally define a `__set__`
|
||||||
/// method or a `__delete__` method. This enum is used to categorize attributes into two
|
/// method or a `__delete__` method. This enum is used to categorize attributes into two
|
||||||
|
@ -489,11 +517,13 @@ impl<'db> PropertyInstanceType<'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
fn materialize(self, db: &'db dyn Db, materialization_kind: MaterializationKind) -> Self {
|
||||||
Self::new(
|
Self::new(
|
||||||
db,
|
db,
|
||||||
self.getter(db).map(|ty| ty.materialize(db, variance)),
|
self.getter(db)
|
||||||
self.setter(db).map(|ty| ty.materialize(db, variance)),
|
.map(|ty| ty.materialize(db, materialization_kind)),
|
||||||
|
self.setter(db)
|
||||||
|
.map(|ty| ty.materialize(db, materialization_kind)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -738,14 +768,14 @@ impl<'db> Type<'db> {
|
||||||
/// most general form of the type that is fully static.
|
/// most general form of the type that is fully static.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) fn top_materialization(&self, db: &'db dyn Db) -> Type<'db> {
|
pub(crate) fn top_materialization(&self, db: &'db dyn Db) -> Type<'db> {
|
||||||
self.materialize(db, TypeVarVariance::Covariant)
|
self.materialize(db, MaterializationKind::Top)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the bottom materialization (or lower bound materialization) of this type, which is
|
/// Returns the bottom materialization (or lower bound materialization) of this type, which is
|
||||||
/// the most specific form of the type that is fully static.
|
/// the most specific form of the type that is fully static.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) fn bottom_materialization(&self, db: &'db dyn Db) -> Type<'db> {
|
pub(crate) fn bottom_materialization(&self, db: &'db dyn Db) -> Type<'db> {
|
||||||
self.materialize(db, TypeVarVariance::Contravariant)
|
self.materialize(db, MaterializationKind::Bottom)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If this type is an instance type where the class has a tuple spec, returns the tuple spec.
|
/// If this type is an instance type where the class has a tuple spec, returns the tuple spec.
|
||||||
|
@ -780,29 +810,11 @@ impl<'db> Type<'db> {
|
||||||
/// - In covariant position, it's replaced with `object`
|
/// - In covariant position, it's replaced with `object`
|
||||||
/// - In contravariant position, it's replaced with `Never`
|
/// - In contravariant position, it's replaced with `Never`
|
||||||
/// - In invariant position, it's replaced with an unresolved type variable
|
/// - In invariant position, it's replaced with an unresolved type variable
|
||||||
fn materialize(&self, db: &'db dyn Db, variance: TypeVarVariance) -> Type<'db> {
|
fn materialize(&self, db: &'db dyn Db, materialization_kind: MaterializationKind) -> Type<'db> {
|
||||||
match self {
|
match self {
|
||||||
Type::Dynamic(_) => match variance {
|
Type::Dynamic(_) => match materialization_kind {
|
||||||
// TODO: For an invariant position, e.g. `list[Any]`, it should be replaced with an
|
MaterializationKind::Top => Type::object(db),
|
||||||
// existential type representing "all lists, containing any type." We currently
|
MaterializationKind::Bottom => Type::Never,
|
||||||
// represent this by replacing `Any` in invariant position with an unresolved type
|
|
||||||
// variable.
|
|
||||||
TypeVarVariance::Invariant => Type::TypeVar(BoundTypeVarInstance::new(
|
|
||||||
db,
|
|
||||||
TypeVarInstance::new(
|
|
||||||
db,
|
|
||||||
Name::new_static("T_all"),
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
Some(variance),
|
|
||||||
None,
|
|
||||||
TypeVarKind::Pep695,
|
|
||||||
),
|
|
||||||
BindingContext::Synthetic,
|
|
||||||
)),
|
|
||||||
TypeVarVariance::Covariant => Type::object(db),
|
|
||||||
TypeVarVariance::Contravariant => Type::Never,
|
|
||||||
TypeVarVariance::Bivariant => unreachable!(),
|
|
||||||
},
|
},
|
||||||
|
|
||||||
Type::Never
|
Type::Never
|
||||||
|
@ -825,7 +837,7 @@ impl<'db> Type<'db> {
|
||||||
| Type::BoundSuper(_) => *self,
|
| Type::BoundSuper(_) => *self,
|
||||||
|
|
||||||
Type::PropertyInstance(property_instance) => {
|
Type::PropertyInstance(property_instance) => {
|
||||||
Type::PropertyInstance(property_instance.materialize(db, variance))
|
Type::PropertyInstance(property_instance.materialize(db, materialization_kind))
|
||||||
}
|
}
|
||||||
|
|
||||||
Type::FunctionLiteral(_) | Type::BoundMethod(_) => {
|
Type::FunctionLiteral(_) | Type::BoundMethod(_) => {
|
||||||
|
@ -834,14 +846,16 @@ impl<'db> Type<'db> {
|
||||||
*self
|
*self
|
||||||
}
|
}
|
||||||
|
|
||||||
Type::NominalInstance(instance) => instance.materialize(db, variance),
|
Type::NominalInstance(instance) => instance.materialize(db, materialization_kind),
|
||||||
Type::GenericAlias(generic_alias) => {
|
Type::GenericAlias(generic_alias) => {
|
||||||
Type::GenericAlias(generic_alias.materialize(db, variance))
|
Type::GenericAlias(generic_alias.materialize(db, materialization_kind))
|
||||||
}
|
}
|
||||||
Type::Callable(callable_type) => {
|
Type::Callable(callable_type) => {
|
||||||
Type::Callable(callable_type.materialize(db, variance))
|
Type::Callable(callable_type.materialize(db, materialization_kind))
|
||||||
|
}
|
||||||
|
Type::SubclassOf(subclass_of_type) => {
|
||||||
|
subclass_of_type.materialize(db, materialization_kind)
|
||||||
}
|
}
|
||||||
Type::SubclassOf(subclass_of_type) => subclass_of_type.materialize(db, variance),
|
|
||||||
Type::ProtocolInstance(protocol_instance_type) => {
|
Type::ProtocolInstance(protocol_instance_type) => {
|
||||||
// TODO: Add tests for this once subtyping/assignability is implemented for
|
// TODO: Add tests for this once subtyping/assignability is implemented for
|
||||||
// protocols. It _might_ require changing the logic here because:
|
// protocols. It _might_ require changing the logic here because:
|
||||||
|
@ -850,35 +864,45 @@ impl<'db> Type<'db> {
|
||||||
// > read-only property members, and method members, on protocols act covariantly;
|
// > read-only property members, and method members, on protocols act covariantly;
|
||||||
// > write-only property members act contravariantly; and read/write attribute
|
// > write-only property members act contravariantly; and read/write attribute
|
||||||
// > members on protocols act invariantly
|
// > members on protocols act invariantly
|
||||||
Type::ProtocolInstance(protocol_instance_type.materialize(db, variance))
|
Type::ProtocolInstance(protocol_instance_type.materialize(db, materialization_kind))
|
||||||
|
}
|
||||||
|
Type::Union(union_type) => {
|
||||||
|
union_type.map(db, |ty| ty.materialize(db, materialization_kind))
|
||||||
}
|
}
|
||||||
Type::Union(union_type) => union_type.map(db, |ty| ty.materialize(db, variance)),
|
|
||||||
Type::Intersection(intersection_type) => IntersectionBuilder::new(db)
|
Type::Intersection(intersection_type) => IntersectionBuilder::new(db)
|
||||||
.positive_elements(
|
.positive_elements(
|
||||||
intersection_type
|
intersection_type
|
||||||
.positive(db)
|
.positive(db)
|
||||||
.iter()
|
.iter()
|
||||||
.map(|ty| ty.materialize(db, variance)),
|
.map(|ty| ty.materialize(db, materialization_kind)),
|
||||||
)
|
)
|
||||||
.negative_elements(
|
.negative_elements(
|
||||||
intersection_type
|
intersection_type
|
||||||
.negative(db)
|
.negative(db)
|
||||||
.iter()
|
.iter()
|
||||||
.map(|ty| ty.materialize(db, variance.flip())),
|
.map(|ty| ty.materialize(db, materialization_kind.flip())),
|
||||||
)
|
)
|
||||||
.build(),
|
.build(),
|
||||||
Type::TypeVar(bound_typevar) => Type::TypeVar(bound_typevar.materialize(db, variance)),
|
Type::TypeVar(bound_typevar) => {
|
||||||
|
Type::TypeVar(bound_typevar.materialize(db, materialization_kind))
|
||||||
|
}
|
||||||
Type::NonInferableTypeVar(bound_typevar) => {
|
Type::NonInferableTypeVar(bound_typevar) => {
|
||||||
Type::NonInferableTypeVar(bound_typevar.materialize(db, variance))
|
Type::NonInferableTypeVar(bound_typevar.materialize(db, materialization_kind))
|
||||||
}
|
}
|
||||||
Type::TypeIs(type_is) => {
|
Type::TypeIs(type_is) => {
|
||||||
type_is.with_type(db, type_is.return_type(db).materialize(db, variance))
|
// TODO(jelle): this seems wrong, should be invariant?
|
||||||
|
type_is.with_type(
|
||||||
|
db,
|
||||||
|
type_is
|
||||||
|
.return_type(db)
|
||||||
|
.materialize(db, materialization_kind),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
Type::TypedDict(_) => {
|
Type::TypedDict(_) => {
|
||||||
// TODO: Materialization of gradual TypedDicts
|
// TODO: Materialization of gradual TypedDicts
|
||||||
*self
|
*self
|
||||||
}
|
}
|
||||||
Type::TypeAlias(alias) => alias.value_type(db).materialize(db, variance),
|
Type::TypeAlias(alias) => alias.value_type(db).materialize(db, materialization_kind),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6637,6 +6661,13 @@ impl<'db> TypeMapping<'_, 'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn materialization_kind(&self, db: &'db dyn Db) -> Option<MaterializationKind> {
|
||||||
|
match self {
|
||||||
|
TypeMapping::Specialization(specialization) => specialization.materialization_kind(db),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Singleton types that are heavily special-cased by ty. Despite its name,
|
/// Singleton types that are heavily special-cased by ty. Despite its name,
|
||||||
|
@ -7321,29 +7352,35 @@ impl<'db> TypeVarInstance<'db> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
fn materialize(self, db: &'db dyn Db, materialization_kind: MaterializationKind) -> Self {
|
||||||
Self::new(
|
Self::new(
|
||||||
db,
|
db,
|
||||||
self.name(db),
|
self.name(db),
|
||||||
self.definition(db),
|
self.definition(db),
|
||||||
self._bound_or_constraints(db)
|
self._bound_or_constraints(db)
|
||||||
.and_then(|bound_or_constraints| match bound_or_constraints {
|
.and_then(|bound_or_constraints| match bound_or_constraints {
|
||||||
TypeVarBoundOrConstraintsEvaluation::Eager(bound_or_constraints) => {
|
TypeVarBoundOrConstraintsEvaluation::Eager(bound_or_constraints) => Some(
|
||||||
Some(bound_or_constraints.materialize(db, variance).into())
|
bound_or_constraints
|
||||||
}
|
.materialize(db, materialization_kind)
|
||||||
|
.into(),
|
||||||
|
),
|
||||||
TypeVarBoundOrConstraintsEvaluation::LazyUpperBound => self
|
TypeVarBoundOrConstraintsEvaluation::LazyUpperBound => self
|
||||||
.lazy_bound(db)
|
.lazy_bound(db)
|
||||||
.map(|bound| bound.materialize(db, variance).into()),
|
.map(|bound| bound.materialize(db, materialization_kind).into()),
|
||||||
TypeVarBoundOrConstraintsEvaluation::LazyConstraints => self
|
TypeVarBoundOrConstraintsEvaluation::LazyConstraints => {
|
||||||
.lazy_constraints(db)
|
self.lazy_constraints(db).map(|constraints| {
|
||||||
.map(|constraints| constraints.materialize(db, variance).into()),
|
constraints.materialize(db, materialization_kind).into()
|
||||||
|
})
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
self.explicit_variance(db),
|
self.explicit_variance(db),
|
||||||
self._default(db).and_then(|default| match default {
|
self._default(db).and_then(|default| match default {
|
||||||
TypeVarDefaultEvaluation::Eager(ty) => Some(ty.materialize(db, variance).into()),
|
TypeVarDefaultEvaluation::Eager(ty) => {
|
||||||
|
Some(ty.materialize(db, materialization_kind).into())
|
||||||
|
}
|
||||||
TypeVarDefaultEvaluation::Lazy => self
|
TypeVarDefaultEvaluation::Lazy => self
|
||||||
.lazy_default(db)
|
.lazy_default(db)
|
||||||
.map(|ty| ty.materialize(db, variance).into()),
|
.map(|ty| ty.materialize(db, materialization_kind).into()),
|
||||||
}),
|
}),
|
||||||
self.kind(db),
|
self.kind(db),
|
||||||
)
|
)
|
||||||
|
@ -7505,10 +7542,10 @@ impl<'db> BoundTypeVarInstance<'db> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
fn materialize(self, db: &'db dyn Db, materialization_kind: MaterializationKind) -> Self {
|
||||||
Self::new(
|
Self::new(
|
||||||
db,
|
db,
|
||||||
self.typevar(db).materialize(db, variance),
|
self.typevar(db).materialize(db, materialization_kind),
|
||||||
self.binding_context(db),
|
self.binding_context(db),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -7585,10 +7622,10 @@ impl<'db> TypeVarBoundOrConstraints<'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
fn materialize(self, db: &'db dyn Db, materialization_kind: MaterializationKind) -> Self {
|
||||||
match self {
|
match self {
|
||||||
TypeVarBoundOrConstraints::UpperBound(bound) => {
|
TypeVarBoundOrConstraints::UpperBound(bound) => {
|
||||||
TypeVarBoundOrConstraints::UpperBound(bound.materialize(db, variance))
|
TypeVarBoundOrConstraints::UpperBound(bound.materialize(db, materialization_kind))
|
||||||
}
|
}
|
||||||
TypeVarBoundOrConstraints::Constraints(constraints) => {
|
TypeVarBoundOrConstraints::Constraints(constraints) => {
|
||||||
TypeVarBoundOrConstraints::Constraints(UnionType::new(
|
TypeVarBoundOrConstraints::Constraints(UnionType::new(
|
||||||
|
@ -7596,7 +7633,7 @@ impl<'db> TypeVarBoundOrConstraints<'db> {
|
||||||
constraints
|
constraints
|
||||||
.elements(db)
|
.elements(db)
|
||||||
.iter()
|
.iter()
|
||||||
.map(|ty| ty.materialize(db, variance))
|
.map(|ty| ty.materialize(db, materialization_kind))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.into_boxed_slice(),
|
.into_boxed_slice(),
|
||||||
))
|
))
|
||||||
|
@ -8838,10 +8875,10 @@ impl<'db> CallableType<'db> {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
fn materialize(self, db: &'db dyn Db, materialization_kind: MaterializationKind) -> Self {
|
||||||
CallableType::new(
|
CallableType::new(
|
||||||
db,
|
db,
|
||||||
self.signatures(db).materialize(db, variance),
|
self.signatures(db).materialize(db, materialization_kind),
|
||||||
self.is_function_like(db),
|
self.is_function_like(db),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,10 +31,10 @@ use crate::types::typed_dict::typed_dict_params_from_class_def;
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
ApplyTypeMappingVisitor, Binding, BoundSuperError, BoundSuperType, CallableType,
|
ApplyTypeMappingVisitor, Binding, BoundSuperError, BoundSuperType, CallableType,
|
||||||
DataclassParams, DeprecatedInstance, HasRelationToVisitor, IsEquivalentVisitor,
|
DataclassParams, DeprecatedInstance, HasRelationToVisitor, IsEquivalentVisitor,
|
||||||
KnownInstanceType, ManualPEP695TypeAliasType, NormalizedVisitor, PropertyInstanceType,
|
KnownInstanceType, ManualPEP695TypeAliasType, MaterializationKind, NormalizedVisitor,
|
||||||
StringLiteralType, TypeAliasType, TypeMapping, TypeRelation, TypeVarBoundOrConstraints,
|
PropertyInstanceType, StringLiteralType, TypeAliasType, TypeMapping, TypeRelation,
|
||||||
TypeVarInstance, TypeVarKind, TypedDictParams, VarianceInferable, declaration_type,
|
TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind, TypedDictParams, VarianceInferable,
|
||||||
infer_definition_types, todo_type,
|
declaration_type, infer_definition_types, todo_type,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
Db, FxIndexMap, FxOrderSet, Program,
|
Db, FxIndexMap, FxOrderSet, Program,
|
||||||
|
@ -272,11 +272,16 @@ impl<'db> GenericAlias<'db> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
pub(super) fn materialize(
|
||||||
|
self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
materialization_kind: MaterializationKind,
|
||||||
|
) -> Self {
|
||||||
Self::new(
|
Self::new(
|
||||||
db,
|
db,
|
||||||
self.origin(db),
|
self.origin(db),
|
||||||
self.specialization(db).materialize(db, variance),
|
self.specialization(db)
|
||||||
|
.materialize(db, materialization_kind),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -404,10 +409,14 @@ impl<'db> ClassType<'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
pub(super) fn materialize(
|
||||||
|
self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
materialization_kind: MaterializationKind,
|
||||||
|
) -> Self {
|
||||||
match self {
|
match self {
|
||||||
Self::NonGeneric(_) => self,
|
Self::NonGeneric(_) => self,
|
||||||
Self::Generic(generic) => Self::Generic(generic.materialize(db, variance)),
|
Self::Generic(generic) => Self::Generic(generic.materialize(db, materialization_kind)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,8 +16,8 @@ use crate::types::generics::{GenericContext, Specialization};
|
||||||
use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signature};
|
use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signature};
|
||||||
use crate::types::tuple::TupleSpec;
|
use crate::types::tuple::TupleSpec;
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
CallableType, IntersectionType, KnownClass, MethodWrapperKind, Protocol, StringLiteralType,
|
CallableType, IntersectionType, KnownClass, MaterializationKind, MethodWrapperKind, Protocol,
|
||||||
SubclassOfInner, Type, UnionType, WrapperDescriptorKind,
|
StringLiteralType, SubclassOfInner, Type, UnionType, WrapperDescriptorKind,
|
||||||
};
|
};
|
||||||
use ruff_db::parsed::parsed_module;
|
use ruff_db::parsed::parsed_module;
|
||||||
|
|
||||||
|
@ -614,14 +614,25 @@ impl Display for DisplayGenericAlias<'_> {
|
||||||
if let Some(tuple) = self.specialization.tuple(self.db) {
|
if let Some(tuple) = self.specialization.tuple(self.db) {
|
||||||
tuple.display_with(self.db, self.settings).fmt(f)
|
tuple.display_with(self.db, self.settings).fmt(f)
|
||||||
} else {
|
} else {
|
||||||
|
let prefix = match self.specialization.materialization_kind(self.db) {
|
||||||
|
None => "",
|
||||||
|
Some(MaterializationKind::Top) => "Top[",
|
||||||
|
Some(MaterializationKind::Bottom) => "Bottom[",
|
||||||
|
};
|
||||||
|
let suffix = match self.specialization.materialization_kind(self.db) {
|
||||||
|
None => "",
|
||||||
|
Some(_) => "]",
|
||||||
|
};
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"{origin}{specialization}",
|
"{prefix}{origin}{specialization}{suffix}",
|
||||||
|
prefix = prefix,
|
||||||
origin = self.origin.name(self.db),
|
origin = self.origin.name(self.db),
|
||||||
specialization = self.specialization.display_short(
|
specialization = self.specialization.display_short(
|
||||||
self.db,
|
self.db,
|
||||||
TupleSpecialization::from_class(self.db, self.origin)
|
TupleSpecialization::from_class(self.db, self.origin)
|
||||||
),
|
),
|
||||||
|
suffix = suffix,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,9 +16,9 @@ use crate::types::signatures::{Parameter, Parameters, Signature};
|
||||||
use crate::types::tuple::{TupleSpec, TupleType, walk_tuple_type};
|
use crate::types::tuple::{TupleSpec, TupleType, walk_tuple_type};
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
ApplyTypeMappingVisitor, BoundTypeVarInstance, HasRelationToVisitor, IsEquivalentVisitor,
|
ApplyTypeMappingVisitor, BoundTypeVarInstance, HasRelationToVisitor, IsEquivalentVisitor,
|
||||||
KnownClass, KnownInstanceType, NormalizedVisitor, Type, TypeMapping, TypeRelation,
|
KnownClass, KnownInstanceType, MaterializationKind, NormalizedVisitor, Type, TypeMapping,
|
||||||
TypeVarBoundOrConstraints, TypeVarInstance, TypeVarVariance, UnionType, binding_type,
|
TypeRelation, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarVariance, UnionType,
|
||||||
declaration_type,
|
binding_type, declaration_type,
|
||||||
};
|
};
|
||||||
use crate::{Db, FxOrderSet};
|
use crate::{Db, FxOrderSet};
|
||||||
|
|
||||||
|
@ -244,6 +244,7 @@ impl<'db> GenericContext<'db> {
|
||||||
db,
|
db,
|
||||||
self,
|
self,
|
||||||
partial.types(db),
|
partial.types(db),
|
||||||
|
None,
|
||||||
Some(TupleType::homogeneous(db, Type::unknown())),
|
Some(TupleType::homogeneous(db, Type::unknown())),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
@ -304,7 +305,7 @@ impl<'db> GenericContext<'db> {
|
||||||
types: Box<[Type<'db>]>,
|
types: Box<[Type<'db>]>,
|
||||||
) -> Specialization<'db> {
|
) -> Specialization<'db> {
|
||||||
assert!(self.variables(db).len() == types.len());
|
assert!(self.variables(db).len() == types.len());
|
||||||
Specialization::new(db, self, types, None)
|
Specialization::new(db, self, types, None, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a specialization of this generic context for the `tuple` class.
|
/// Creates a specialization of this generic context for the `tuple` class.
|
||||||
|
@ -314,7 +315,7 @@ impl<'db> GenericContext<'db> {
|
||||||
element_type: Type<'db>,
|
element_type: Type<'db>,
|
||||||
tuple: TupleType<'db>,
|
tuple: TupleType<'db>,
|
||||||
) -> Specialization<'db> {
|
) -> Specialization<'db> {
|
||||||
Specialization::new(db, self, Box::from([element_type]), Some(tuple))
|
Specialization::new(db, self, Box::from([element_type]), None, Some(tuple))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a specialization of this generic context. Panics if the length of `types` does not
|
/// Creates a specialization of this generic context. Panics if the length of `types` does not
|
||||||
|
@ -360,7 +361,7 @@ impl<'db> GenericContext<'db> {
|
||||||
expanded[idx] = default;
|
expanded[idx] = default;
|
||||||
}
|
}
|
||||||
|
|
||||||
Specialization::new(db, self, expanded.into_boxed_slice(), None)
|
Specialization::new(db, self, expanded.into_boxed_slice(), None, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &NormalizedVisitor<'db>) -> Self {
|
pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &NormalizedVisitor<'db>) -> Self {
|
||||||
|
@ -407,6 +408,14 @@ pub struct Specialization<'db> {
|
||||||
pub(crate) generic_context: GenericContext<'db>,
|
pub(crate) generic_context: GenericContext<'db>,
|
||||||
#[returns(deref)]
|
#[returns(deref)]
|
||||||
pub(crate) types: Box<[Type<'db>]>,
|
pub(crate) types: Box<[Type<'db>]>,
|
||||||
|
/// The materialization kind of the specialization. For example, given an invariant
|
||||||
|
/// generic type `A`, `Top[A[Any]]` is a supertype of all materializations of `A[Any]`,
|
||||||
|
/// and is represented here with `Some(MaterializationKind::Top)`. Similarly,
|
||||||
|
/// `Bottom[A[Any]]` is a subtype of all materializations of `A[Any]`, and is represented
|
||||||
|
/// with `Some(MaterializationKind::Bottom)`.
|
||||||
|
/// The `materialization_kind` field may be non-`None` only if the specialization contains
|
||||||
|
/// dynamic types in invariant positions.
|
||||||
|
pub(crate) materialization_kind: Option<MaterializationKind>,
|
||||||
|
|
||||||
/// For specializations of `tuple`, we also store more detailed information about the tuple's
|
/// For specializations of `tuple`, we also store more detailed information about the tuple's
|
||||||
/// elements, above what the class's (single) typevar can represent.
|
/// elements, above what the class's (single) typevar can represent.
|
||||||
|
@ -430,6 +439,114 @@ pub(super) fn walk_specialization<'db, V: super::visitor::TypeVisitor<'db> + ?Si
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_subtype_in_invariant_position<'db, C: Constraints<'db>>(
|
||||||
|
db: &'db dyn Db,
|
||||||
|
derived_type: &Type<'db>,
|
||||||
|
derived_materialization: MaterializationKind,
|
||||||
|
base_type: &Type<'db>,
|
||||||
|
base_materialization: MaterializationKind,
|
||||||
|
) -> C {
|
||||||
|
let derived_top = derived_type.top_materialization(db);
|
||||||
|
let derived_bottom = derived_type.bottom_materialization(db);
|
||||||
|
let base_top = base_type.top_materialization(db);
|
||||||
|
let base_bottom = base_type.bottom_materialization(db);
|
||||||
|
match (derived_materialization, base_materialization) {
|
||||||
|
// `Derived` is a subtype of `Base` if the range of materializations covered by `Derived`
|
||||||
|
// is a subset of the range covered by `Base`.
|
||||||
|
(MaterializationKind::Top, MaterializationKind::Top) => C::from_bool(
|
||||||
|
db,
|
||||||
|
base_bottom.is_subtype_of(db, derived_bottom)
|
||||||
|
&& derived_top.is_subtype_of(db, base_top),
|
||||||
|
),
|
||||||
|
// One bottom is a subtype of another if it covers a strictly larger set of materializations.
|
||||||
|
(MaterializationKind::Bottom, MaterializationKind::Bottom) => C::from_bool(
|
||||||
|
db,
|
||||||
|
derived_bottom.is_subtype_of(db, base_bottom)
|
||||||
|
&& base_top.is_subtype_of(db, derived_top),
|
||||||
|
),
|
||||||
|
// The bottom materialization of `Derived` is a subtype of the top materialization
|
||||||
|
// of `Base` if there is some type that is both within the
|
||||||
|
// range of types covered by derived and within the range covered by base, because if such a type
|
||||||
|
// exists, it's a subtype of `Top[base]` and a supertype of `Bottom[derived]`.
|
||||||
|
(MaterializationKind::Bottom, MaterializationKind::Top) => C::from_bool(
|
||||||
|
db,
|
||||||
|
(base_bottom.is_subtype_of(db, derived_bottom)
|
||||||
|
&& derived_bottom.is_subtype_of(db, base_top))
|
||||||
|
|| (base_bottom.is_subtype_of(db, derived_top)
|
||||||
|
&& derived_top.is_subtype_of(db, base_top)
|
||||||
|
|| (base_top.is_subtype_of(db, derived_top)
|
||||||
|
&& derived_bottom.is_subtype_of(db, base_top))),
|
||||||
|
),
|
||||||
|
// A top materialization is a subtype of a bottom materialization only if both original
|
||||||
|
// un-materialized types are the same fully static type.
|
||||||
|
(MaterializationKind::Top, MaterializationKind::Bottom) => C::from_bool(
|
||||||
|
db,
|
||||||
|
derived_top.is_subtype_of(db, base_bottom)
|
||||||
|
&& base_top.is_subtype_of(db, derived_bottom),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether two types encountered in an invariant position
|
||||||
|
/// have a relation (subtyping or assignability), taking into account
|
||||||
|
/// that the two types may come from a top or bottom materialization.
|
||||||
|
fn has_relation_in_invariant_position<'db, C: Constraints<'db>>(
|
||||||
|
db: &'db dyn Db,
|
||||||
|
derived_type: &Type<'db>,
|
||||||
|
derived_materialization: Option<MaterializationKind>,
|
||||||
|
base_type: &Type<'db>,
|
||||||
|
base_materialization: Option<MaterializationKind>,
|
||||||
|
relation: TypeRelation,
|
||||||
|
) -> C {
|
||||||
|
match (derived_materialization, base_materialization, relation) {
|
||||||
|
// Top and bottom materializations are fully static types, so subtyping
|
||||||
|
// is the same as assignability.
|
||||||
|
(Some(derived_mat), Some(base_mat), _) => {
|
||||||
|
is_subtype_in_invariant_position(db, derived_type, derived_mat, base_type, base_mat)
|
||||||
|
}
|
||||||
|
// Subtyping between invariant type parameters without a top/bottom materialization involved
|
||||||
|
// is equivalence
|
||||||
|
(None, None, TypeRelation::Subtyping) => {
|
||||||
|
C::from_bool(db, derived_type.is_equivalent_to(db, *base_type))
|
||||||
|
}
|
||||||
|
(None, None, TypeRelation::Assignability) => C::from_bool(
|
||||||
|
db,
|
||||||
|
derived_type.is_assignable_to(db, *base_type)
|
||||||
|
&& base_type.is_assignable_to(db, *derived_type),
|
||||||
|
),
|
||||||
|
// For gradual types, A <: B (subtyping) is defined as Top[A] <: Bottom[B]
|
||||||
|
(None, Some(base_mat), TypeRelation::Subtyping) => is_subtype_in_invariant_position(
|
||||||
|
db,
|
||||||
|
derived_type,
|
||||||
|
MaterializationKind::Top,
|
||||||
|
base_type,
|
||||||
|
base_mat,
|
||||||
|
),
|
||||||
|
(Some(derived_mat), None, TypeRelation::Subtyping) => is_subtype_in_invariant_position(
|
||||||
|
db,
|
||||||
|
derived_type,
|
||||||
|
derived_mat,
|
||||||
|
base_type,
|
||||||
|
MaterializationKind::Bottom,
|
||||||
|
),
|
||||||
|
// And A <~ B (assignability) is Bottom[A] <: Top[B]
|
||||||
|
(None, Some(base_mat), TypeRelation::Assignability) => is_subtype_in_invariant_position(
|
||||||
|
db,
|
||||||
|
derived_type,
|
||||||
|
MaterializationKind::Bottom,
|
||||||
|
base_type,
|
||||||
|
base_mat,
|
||||||
|
),
|
||||||
|
(Some(derived_mat), None, TypeRelation::Assignability) => is_subtype_in_invariant_position(
|
||||||
|
db,
|
||||||
|
derived_type,
|
||||||
|
derived_mat,
|
||||||
|
base_type,
|
||||||
|
MaterializationKind::Top,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'db> Specialization<'db> {
|
impl<'db> Specialization<'db> {
|
||||||
/// Returns the tuple spec for a specialization of the `tuple` class.
|
/// Returns the tuple spec for a specialization of the `tuple` class.
|
||||||
pub(crate) fn tuple(self, db: &'db dyn Db) -> Option<&'db TupleSpec<'db>> {
|
pub(crate) fn tuple(self, db: &'db dyn Db) -> Option<&'db TupleSpec<'db>> {
|
||||||
|
@ -481,15 +598,61 @@ impl<'db> Specialization<'db> {
|
||||||
type_mapping: &TypeMapping<'a, 'db>,
|
type_mapping: &TypeMapping<'a, 'db>,
|
||||||
visitor: &ApplyTypeMappingVisitor<'db>,
|
visitor: &ApplyTypeMappingVisitor<'db>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
// TODO it seems like this should be possible to do in a much simpler way in
|
||||||
|
// `Self::apply_specialization`; just apply the type mapping to create the new
|
||||||
|
// specialization, then materialize the new specialization appropriately, if the type
|
||||||
|
// mapping is a materialization. But this doesn't work; see discussion in
|
||||||
|
// https://github.com/astral-sh/ruff/pull/20076
|
||||||
|
let applied_materialization_kind = type_mapping.materialization_kind(db);
|
||||||
|
let mut has_dynamic_invariant_typevar = false;
|
||||||
let types: Box<[_]> = self
|
let types: Box<[_]> = self
|
||||||
.types(db)
|
.generic_context(db)
|
||||||
.iter()
|
.variables(db)
|
||||||
.map(|ty| ty.apply_type_mapping_impl(db, type_mapping, visitor))
|
.into_iter()
|
||||||
|
.zip(self.types(db))
|
||||||
|
.map(|(bound_typevar, vartype)| {
|
||||||
|
let ty = vartype.apply_type_mapping_impl(db, type_mapping, visitor);
|
||||||
|
match (applied_materialization_kind, bound_typevar.variance(db)) {
|
||||||
|
(None, _) => ty,
|
||||||
|
(Some(_), TypeVarVariance::Bivariant) =>
|
||||||
|
// With bivariance, all specializations are subtypes of each other,
|
||||||
|
// so any materialization is acceptable.
|
||||||
|
{
|
||||||
|
ty.materialize(db, MaterializationKind::Top)
|
||||||
|
}
|
||||||
|
(Some(materialization_kind), TypeVarVariance::Covariant) => {
|
||||||
|
ty.materialize(db, materialization_kind)
|
||||||
|
}
|
||||||
|
(Some(materialization_kind), TypeVarVariance::Contravariant) => {
|
||||||
|
ty.materialize(db, materialization_kind.flip())
|
||||||
|
}
|
||||||
|
(Some(_), TypeVarVariance::Invariant) => {
|
||||||
|
let top_materialization = ty.materialize(db, MaterializationKind::Top);
|
||||||
|
if !ty.is_equivalent_to(db, top_materialization) {
|
||||||
|
has_dynamic_invariant_typevar = true;
|
||||||
|
}
|
||||||
|
ty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let tuple_inner = self
|
let tuple_inner = self
|
||||||
.tuple_inner(db)
|
.tuple_inner(db)
|
||||||
.and_then(|tuple| tuple.apply_type_mapping_impl(db, type_mapping, visitor));
|
.and_then(|tuple| tuple.apply_type_mapping_impl(db, type_mapping, visitor));
|
||||||
Specialization::new(db, self.generic_context(db), types, tuple_inner)
|
let new_materialization_kind = if has_dynamic_invariant_typevar {
|
||||||
|
self.materialization_kind(db)
|
||||||
|
.or(applied_materialization_kind)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
Specialization::new(
|
||||||
|
db,
|
||||||
|
self.generic_context(db),
|
||||||
|
types,
|
||||||
|
new_materialization_kind,
|
||||||
|
tuple_inner,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Applies an optional specialization to this specialization.
|
/// Applies an optional specialization to this specialization.
|
||||||
|
@ -527,7 +690,8 @@ impl<'db> Specialization<'db> {
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
// TODO: Combine the tuple specs too
|
// TODO: Combine the tuple specs too
|
||||||
Specialization::new(db, self.generic_context(db), types, None)
|
// TODO(jelle): specialization type?
|
||||||
|
Specialization::new(db, self.generic_context(db), types, None, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &NormalizedVisitor<'db>) -> Self {
|
pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &NormalizedVisitor<'db>) -> Self {
|
||||||
|
@ -540,25 +704,68 @@ impl<'db> Specialization<'db> {
|
||||||
.tuple_inner(db)
|
.tuple_inner(db)
|
||||||
.and_then(|tuple| tuple.normalized_impl(db, visitor));
|
.and_then(|tuple| tuple.normalized_impl(db, visitor));
|
||||||
let context = self.generic_context(db).normalized_impl(db, visitor);
|
let context = self.generic_context(db).normalized_impl(db, visitor);
|
||||||
Self::new(db, context, types, tuple_inner)
|
Self::new(
|
||||||
|
db,
|
||||||
|
context,
|
||||||
|
types,
|
||||||
|
self.materialization_kind(db),
|
||||||
|
tuple_inner,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
pub(super) fn materialize(
|
||||||
|
self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
materialization_kind: MaterializationKind,
|
||||||
|
) -> Self {
|
||||||
|
// The top and bottom materializations are fully static types already, so materializing them
|
||||||
|
// further does nothing.
|
||||||
|
if self.materialization_kind(db).is_some() {
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
let mut has_dynamic_invariant_typevar = false;
|
||||||
let types: Box<[_]> = self
|
let types: Box<[_]> = self
|
||||||
.generic_context(db)
|
.generic_context(db)
|
||||||
.variables(db)
|
.variables(db)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.zip(self.types(db))
|
.zip(self.types(db))
|
||||||
.map(|(bound_typevar, vartype)| {
|
.map(|(bound_typevar, vartype)| {
|
||||||
let variance = bound_typevar.variance_with_polarity(db, variance);
|
match bound_typevar.variance(db) {
|
||||||
vartype.materialize(db, variance)
|
TypeVarVariance::Bivariant => {
|
||||||
|
// With bivariance, all specializations are subtypes of each other,
|
||||||
|
// so any materialization is acceptable.
|
||||||
|
vartype.materialize(db, MaterializationKind::Top)
|
||||||
|
}
|
||||||
|
TypeVarVariance::Covariant => vartype.materialize(db, materialization_kind),
|
||||||
|
TypeVarVariance::Contravariant => {
|
||||||
|
vartype.materialize(db, materialization_kind.flip())
|
||||||
|
}
|
||||||
|
TypeVarVariance::Invariant => {
|
||||||
|
let top_materialization = vartype.materialize(db, MaterializationKind::Top);
|
||||||
|
if !vartype.is_equivalent_to(db, top_materialization) {
|
||||||
|
has_dynamic_invariant_typevar = true;
|
||||||
|
}
|
||||||
|
*vartype
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
let tuple_inner = self.tuple_inner(db).and_then(|tuple| {
|
let tuple_inner = self.tuple_inner(db).and_then(|tuple| {
|
||||||
// Tuples are immutable, so tuple element types are always in covariant position.
|
// Tuples are immutable, so tuple element types are always in covariant position.
|
||||||
tuple.materialize(db, variance)
|
tuple.materialize(db, materialization_kind)
|
||||||
});
|
});
|
||||||
Specialization::new(db, self.generic_context(db), types, tuple_inner)
|
let new_materialization_kind = if has_dynamic_invariant_typevar {
|
||||||
|
Some(materialization_kind)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
Specialization::new(
|
||||||
|
db,
|
||||||
|
self.generic_context(db),
|
||||||
|
types,
|
||||||
|
new_materialization_kind,
|
||||||
|
tuple_inner,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn has_relation_to_impl<C: Constraints<'db>>(
|
pub(crate) fn has_relation_to_impl<C: Constraints<'db>>(
|
||||||
|
@ -578,12 +785,20 @@ impl<'db> Specialization<'db> {
|
||||||
return self_tuple.has_relation_to_impl(db, other_tuple, relation, visitor);
|
return self_tuple.has_relation_to_impl(db, other_tuple, relation, visitor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let self_materialization_kind = self.materialization_kind(db);
|
||||||
|
let other_materialization_kind = other.materialization_kind(db);
|
||||||
|
|
||||||
let mut result = C::always_satisfiable(db);
|
let mut result = C::always_satisfiable(db);
|
||||||
for ((bound_typevar, self_type), other_type) in (generic_context.variables(db).into_iter())
|
for ((bound_typevar, self_type), other_type) in (generic_context.variables(db).into_iter())
|
||||||
.zip(self.types(db))
|
.zip(self.types(db))
|
||||||
.zip(other.types(db))
|
.zip(other.types(db))
|
||||||
{
|
{
|
||||||
if self_type.is_dynamic() || other_type.is_dynamic() {
|
// As an optimization, we can return early if either type is dynamic, unless
|
||||||
|
// we're dealing with a top or bottom materialization.
|
||||||
|
if other_materialization_kind.is_none()
|
||||||
|
&& self_materialization_kind.is_none()
|
||||||
|
&& (self_type.is_dynamic() || other_type.is_dynamic())
|
||||||
|
{
|
||||||
match relation {
|
match relation {
|
||||||
TypeRelation::Assignability => continue,
|
TypeRelation::Assignability => continue,
|
||||||
TypeRelation::Subtyping => return C::unsatisfiable(db),
|
TypeRelation::Subtyping => return C::unsatisfiable(db),
|
||||||
|
@ -597,14 +812,14 @@ impl<'db> Specialization<'db> {
|
||||||
// - invariant: verify that self_type <: other_type AND other_type <: self_type
|
// - invariant: verify that self_type <: other_type AND other_type <: self_type
|
||||||
// - bivariant: skip, can't make subtyping/assignability false
|
// - bivariant: skip, can't make subtyping/assignability false
|
||||||
let compatible = match bound_typevar.variance(db) {
|
let compatible = match bound_typevar.variance(db) {
|
||||||
TypeVarVariance::Invariant => match relation {
|
TypeVarVariance::Invariant => has_relation_in_invariant_position(
|
||||||
TypeRelation::Subtyping => self_type.when_equivalent_to(db, *other_type),
|
db,
|
||||||
TypeRelation::Assignability => C::from_bool(
|
self_type,
|
||||||
db,
|
self_materialization_kind,
|
||||||
self_type.is_assignable_to(db, *other_type)
|
other_type,
|
||||||
&& other_type.is_assignable_to(db, *self_type),
|
other_materialization_kind,
|
||||||
),
|
relation,
|
||||||
},
|
),
|
||||||
TypeVarVariance::Covariant => {
|
TypeVarVariance::Covariant => {
|
||||||
self_type.has_relation_to_impl(db, *other_type, relation, visitor)
|
self_type.has_relation_to_impl(db, *other_type, relation, visitor)
|
||||||
}
|
}
|
||||||
|
@ -627,6 +842,9 @@ impl<'db> Specialization<'db> {
|
||||||
other: Specialization<'db>,
|
other: Specialization<'db>,
|
||||||
visitor: &IsEquivalentVisitor<'db, C>,
|
visitor: &IsEquivalentVisitor<'db, C>,
|
||||||
) -> C {
|
) -> C {
|
||||||
|
if self.materialization_kind(db) != other.materialization_kind(db) {
|
||||||
|
return C::unsatisfiable(db);
|
||||||
|
}
|
||||||
let generic_context = self.generic_context(db);
|
let generic_context = self.generic_context(db);
|
||||||
if generic_context != other.generic_context(db) {
|
if generic_context != other.generic_context(db) {
|
||||||
return C::unsatisfiable(db);
|
return C::unsatisfiable(db);
|
||||||
|
|
|
@ -13,7 +13,8 @@ use crate::types::protocol_class::walk_protocol_interface;
|
||||||
use crate::types::tuple::{TupleSpec, TupleType};
|
use crate::types::tuple::{TupleSpec, TupleType};
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
ApplyTypeMappingVisitor, ClassBase, HasRelationToVisitor, IsDisjointVisitor,
|
ApplyTypeMappingVisitor, ClassBase, HasRelationToVisitor, IsDisjointVisitor,
|
||||||
IsEquivalentVisitor, NormalizedVisitor, TypeMapping, TypeRelation, VarianceInferable,
|
IsEquivalentVisitor, MaterializationKind, NormalizedVisitor, TypeMapping, TypeRelation,
|
||||||
|
VarianceInferable,
|
||||||
};
|
};
|
||||||
use crate::{Db, FxOrderSet};
|
use crate::{Db, FxOrderSet};
|
||||||
|
|
||||||
|
@ -259,11 +260,17 @@ impl<'db> NominalInstanceType<'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Type<'db> {
|
pub(super) fn materialize(
|
||||||
|
self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
materialization_kind: MaterializationKind,
|
||||||
|
) -> Type<'db> {
|
||||||
match self.0 {
|
match self.0 {
|
||||||
NominalInstanceInner::ExactTuple(tuple) => Type::tuple(tuple.materialize(db, variance)),
|
NominalInstanceInner::ExactTuple(tuple) => {
|
||||||
|
Type::tuple(tuple.materialize(db, materialization_kind))
|
||||||
|
}
|
||||||
NominalInstanceInner::NonTuple(class) => {
|
NominalInstanceInner::NonTuple(class) => {
|
||||||
Type::non_tuple_instance(class.materialize(db, variance))
|
Type::non_tuple_instance(class.materialize(db, materialization_kind))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -577,12 +584,16 @@ impl<'db> ProtocolInstanceType<'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
pub(super) fn materialize(
|
||||||
|
self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
materialization_kind: MaterializationKind,
|
||||||
|
) -> Self {
|
||||||
match self.inner {
|
match self.inner {
|
||||||
// TODO: This should also materialize via `class.materialize(db, variance)`
|
// TODO: This should also materialize via `class.materialize(db, variance)`
|
||||||
Protocol::FromClass(class) => Self::from_class(class),
|
Protocol::FromClass(class) => Self::from_class(class),
|
||||||
Protocol::Synthesized(synthesized) => {
|
Protocol::Synthesized(synthesized) => {
|
||||||
Self::synthesized(synthesized.materialize(db, variance))
|
Self::synthesized(synthesized.materialize(db, materialization_kind))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -668,8 +679,8 @@ mod synthesized_protocol {
|
||||||
use crate::semantic_index::definition::Definition;
|
use crate::semantic_index::definition::Definition;
|
||||||
use crate::types::protocol_class::ProtocolInterface;
|
use crate::types::protocol_class::ProtocolInterface;
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
ApplyTypeMappingVisitor, BoundTypeVarInstance, NormalizedVisitor, TypeMapping,
|
ApplyTypeMappingVisitor, BoundTypeVarInstance, MaterializationKind, NormalizedVisitor,
|
||||||
TypeVarVariance, VarianceInferable,
|
TypeMapping, TypeVarVariance, VarianceInferable,
|
||||||
};
|
};
|
||||||
use crate::{Db, FxOrderSet};
|
use crate::{Db, FxOrderSet};
|
||||||
|
|
||||||
|
@ -696,8 +707,12 @@ mod synthesized_protocol {
|
||||||
Self(interface.normalized_impl(db, visitor))
|
Self(interface.normalized_impl(db, visitor))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
pub(super) fn materialize(
|
||||||
Self(self.0.materialize(db, variance))
|
self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
materialization_kind: MaterializationKind,
|
||||||
|
) -> Self {
|
||||||
|
Self(self.0.materialize(db, materialization_kind))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn apply_type_mapping_impl<'a>(
|
pub(super) fn apply_type_mapping_impl<'a>(
|
||||||
|
|
|
@ -18,8 +18,9 @@ use crate::{
|
||||||
semantic_index::{definition::Definition, use_def_map},
|
semantic_index::{definition::Definition, use_def_map},
|
||||||
types::{
|
types::{
|
||||||
BoundTypeVarInstance, CallableType, ClassBase, ClassLiteral, HasRelationToVisitor,
|
BoundTypeVarInstance, CallableType, ClassBase, ClassLiteral, HasRelationToVisitor,
|
||||||
IsDisjointVisitor, KnownFunction, NormalizedVisitor, PropertyInstanceType, Signature, Type,
|
IsDisjointVisitor, KnownFunction, MaterializationKind, NormalizedVisitor,
|
||||||
TypeMapping, TypeQualifiers, TypeRelation, VarianceInferable,
|
PropertyInstanceType, Signature, Type, TypeMapping, TypeQualifiers, TypeRelation,
|
||||||
|
VarianceInferable,
|
||||||
constraints::{Constraints, IteratorConstraintsExtension},
|
constraints::{Constraints, IteratorConstraintsExtension},
|
||||||
signatures::{Parameter, Parameters},
|
signatures::{Parameter, Parameters},
|
||||||
},
|
},
|
||||||
|
@ -255,12 +256,16 @@ impl<'db> ProtocolInterface<'db> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
pub(super) fn materialize(
|
||||||
|
self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
materialization_kind: MaterializationKind,
|
||||||
|
) -> Self {
|
||||||
Self::new(
|
Self::new(
|
||||||
db,
|
db,
|
||||||
self.inner(db)
|
self.inner(db)
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(name, data)| (name.clone(), data.materialize(db, variance)))
|
.map(|(name, data)| (name.clone(), data.materialize(db, materialization_kind)))
|
||||||
.collect::<BTreeMap<_, _>>(),
|
.collect::<BTreeMap<_, _>>(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -365,9 +370,9 @@ impl<'db> ProtocolMemberData<'db> {
|
||||||
.find_legacy_typevars(db, binding_context, typevars);
|
.find_legacy_typevars(db, binding_context, typevars);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn materialize(&self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
fn materialize(&self, db: &'db dyn Db, materialization_kind: MaterializationKind) -> Self {
|
||||||
Self {
|
Self {
|
||||||
kind: self.kind.materialize(db, variance),
|
kind: self.kind.materialize(db, materialization_kind),
|
||||||
qualifiers: self.qualifiers,
|
qualifiers: self.qualifiers,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -470,16 +475,16 @@ impl<'db> ProtocolMemberKind<'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
fn materialize(self, db: &'db dyn Db, materialization_kind: MaterializationKind) -> Self {
|
||||||
match self {
|
match self {
|
||||||
ProtocolMemberKind::Method(callable) => {
|
ProtocolMemberKind::Method(callable) => {
|
||||||
ProtocolMemberKind::Method(callable.materialize(db, variance))
|
ProtocolMemberKind::Method(callable.materialize(db, materialization_kind))
|
||||||
}
|
}
|
||||||
ProtocolMemberKind::Property(property) => {
|
ProtocolMemberKind::Property(property) => {
|
||||||
ProtocolMemberKind::Property(property.materialize(db, variance))
|
ProtocolMemberKind::Property(property.materialize(db, materialization_kind))
|
||||||
}
|
}
|
||||||
ProtocolMemberKind::Other(ty) => {
|
ProtocolMemberKind::Other(ty) => {
|
||||||
ProtocolMemberKind::Other(ty.materialize(db, variance))
|
ProtocolMemberKind::Other(ty.materialize(db, materialization_kind))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,8 @@ use crate::types::constraints::{Constraints, IteratorConstraintsExtension};
|
||||||
use crate::types::generics::{GenericContext, walk_generic_context};
|
use crate::types::generics::{GenericContext, walk_generic_context};
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
BindingContext, BoundTypeVarInstance, HasRelationToVisitor, IsEquivalentVisitor, KnownClass,
|
BindingContext, BoundTypeVarInstance, HasRelationToVisitor, IsEquivalentVisitor, KnownClass,
|
||||||
NormalizedVisitor, TypeMapping, TypeRelation, VarianceInferable, todo_type,
|
MaterializationKind, NormalizedVisitor, TypeMapping, TypeRelation, VarianceInferable,
|
||||||
|
todo_type,
|
||||||
};
|
};
|
||||||
use crate::{Db, FxOrderSet};
|
use crate::{Db, FxOrderSet};
|
||||||
use ruff_python_ast::{self as ast, name::Name};
|
use ruff_python_ast::{self as ast, name::Name};
|
||||||
|
@ -57,11 +58,15 @@ impl<'db> CallableSignature<'db> {
|
||||||
self.overloads.iter()
|
self.overloads.iter()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn materialize(&self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
pub(super) fn materialize(
|
||||||
|
&self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
materialization_kind: MaterializationKind,
|
||||||
|
) -> Self {
|
||||||
Self::from_overloads(
|
Self::from_overloads(
|
||||||
self.overloads
|
self.overloads
|
||||||
.iter()
|
.iter()
|
||||||
.map(|signature| signature.materialize(db, variance)),
|
.map(|signature| signature.materialize(db, materialization_kind)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -405,17 +410,17 @@ impl<'db> Signature<'db> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn materialize(&self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
fn materialize(&self, db: &'db dyn Db, materialization_kind: MaterializationKind) -> Self {
|
||||||
Self {
|
Self {
|
||||||
generic_context: self.generic_context,
|
generic_context: self.generic_context,
|
||||||
inherited_generic_context: self.inherited_generic_context,
|
inherited_generic_context: self.inherited_generic_context,
|
||||||
definition: self.definition,
|
definition: self.definition,
|
||||||
// Parameters are at contravariant position, so the variance is flipped.
|
// Parameters are at contravariant position, so the variance is flipped.
|
||||||
parameters: self.parameters.materialize(db, variance.flip()),
|
parameters: self.parameters.materialize(db, materialization_kind.flip()),
|
||||||
return_ty: Some(
|
return_ty: Some(
|
||||||
self.return_ty
|
self.return_ty
|
||||||
.unwrap_or(Type::unknown())
|
.unwrap_or(Type::unknown())
|
||||||
.materialize(db, variance),
|
.materialize(db, materialization_kind),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1063,13 +1068,13 @@ impl<'db> Parameters<'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn materialize(&self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
fn materialize(&self, db: &'db dyn Db, materialization_kind: MaterializationKind) -> Self {
|
||||||
if self.is_gradual {
|
if self.is_gradual {
|
||||||
Parameters::object(db)
|
Parameters::object(db)
|
||||||
} else {
|
} else {
|
||||||
Parameters::new(
|
Parameters::new(
|
||||||
self.iter()
|
self.iter()
|
||||||
.map(|parameter| parameter.materialize(db, variance)),
|
.map(|parameter| parameter.materialize(db, materialization_kind)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1395,12 +1400,12 @@ impl<'db> Parameter<'db> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn materialize(&self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
fn materialize(&self, db: &'db dyn Db, materialization_kind: MaterializationKind) -> Self {
|
||||||
Self {
|
Self {
|
||||||
annotated_type: Some(
|
annotated_type: Some(
|
||||||
self.annotated_type
|
self.annotated_type
|
||||||
.unwrap_or(Type::unknown())
|
.unwrap_or(Type::unknown())
|
||||||
.materialize(db, variance),
|
.materialize(db, materialization_kind),
|
||||||
),
|
),
|
||||||
kind: self.kind.clone(),
|
kind: self.kind.clone(),
|
||||||
form: self.form,
|
form: self.form,
|
||||||
|
|
|
@ -1,17 +1,15 @@
|
||||||
use ruff_python_ast::name::Name;
|
|
||||||
|
|
||||||
use crate::place::PlaceAndQualifiers;
|
use crate::place::PlaceAndQualifiers;
|
||||||
use crate::semantic_index::definition::Definition;
|
use crate::semantic_index::definition::Definition;
|
||||||
use crate::types::constraints::Constraints;
|
use crate::types::constraints::Constraints;
|
||||||
use crate::types::variance::VarianceInferable;
|
use crate::types::variance::VarianceInferable;
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
ApplyTypeMappingVisitor, BindingContext, BoundTypeVarInstance, ClassType, DynamicType,
|
ApplyTypeMappingVisitor, BoundTypeVarInstance, ClassType, DynamicType, HasRelationToVisitor,
|
||||||
HasRelationToVisitor, IsDisjointVisitor, KnownClass, MemberLookupPolicy, NormalizedVisitor,
|
IsDisjointVisitor, KnownClass, MaterializationKind, MemberLookupPolicy, NormalizedVisitor,
|
||||||
SpecialFormType, Type, TypeMapping, TypeRelation, TypeVarInstance,
|
SpecialFormType, Type, TypeMapping, TypeRelation,
|
||||||
};
|
};
|
||||||
use crate::{Db, FxOrderSet};
|
use crate::{Db, FxOrderSet};
|
||||||
|
|
||||||
use super::{TypeVarBoundOrConstraints, TypeVarKind, TypeVarVariance};
|
use super::TypeVarVariance;
|
||||||
|
|
||||||
/// A type that represents `type[C]`, i.e. the class object `C` and class objects that are subclasses of `C`.
|
/// A type that represents `type[C]`, i.e. the class object `C` and class objects that are subclasses of `C`.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Update, get_size2::GetSize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Update, get_size2::GetSize)]
|
||||||
|
@ -81,34 +79,15 @@ impl<'db> SubclassOfType<'db> {
|
||||||
subclass_of.is_dynamic()
|
subclass_of.is_dynamic()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Type<'db> {
|
pub(super) fn materialize(
|
||||||
|
self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
materialization_kind: MaterializationKind,
|
||||||
|
) -> Type<'db> {
|
||||||
match self.subclass_of {
|
match self.subclass_of {
|
||||||
SubclassOfInner::Dynamic(_) => match variance {
|
SubclassOfInner::Dynamic(_) => match materialization_kind {
|
||||||
TypeVarVariance::Covariant => KnownClass::Type.to_instance(db),
|
MaterializationKind::Top => KnownClass::Type.to_instance(db),
|
||||||
TypeVarVariance::Contravariant => Type::Never,
|
MaterializationKind::Bottom => Type::Never,
|
||||||
TypeVarVariance::Invariant => {
|
|
||||||
// We need to materialize this to `type[T]` but that isn't representable so
|
|
||||||
// we instead use a type variable with an upper bound of `type`.
|
|
||||||
Type::NonInferableTypeVar(BoundTypeVarInstance::new(
|
|
||||||
db,
|
|
||||||
TypeVarInstance::new(
|
|
||||||
db,
|
|
||||||
Name::new_static("T_all"),
|
|
||||||
None,
|
|
||||||
Some(
|
|
||||||
TypeVarBoundOrConstraints::UpperBound(
|
|
||||||
KnownClass::Type.to_instance(db),
|
|
||||||
)
|
|
||||||
.into(),
|
|
||||||
),
|
|
||||||
Some(variance),
|
|
||||||
None,
|
|
||||||
TypeVarKind::Pep695,
|
|
||||||
),
|
|
||||||
BindingContext::Synthetic,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
TypeVarVariance::Bivariant => unreachable!(),
|
|
||||||
},
|
},
|
||||||
SubclassOfInner::Class(_) => Type::SubclassOf(self),
|
SubclassOfInner::Class(_) => Type::SubclassOf(self),
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ use crate::types::class::{ClassType, KnownClass};
|
||||||
use crate::types::constraints::{Constraints, IteratorConstraintsExtension};
|
use crate::types::constraints::{Constraints, IteratorConstraintsExtension};
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
ApplyTypeMappingVisitor, BoundTypeVarInstance, HasRelationToVisitor, IsDisjointVisitor,
|
ApplyTypeMappingVisitor, BoundTypeVarInstance, HasRelationToVisitor, IsDisjointVisitor,
|
||||||
IsEquivalentVisitor, NormalizedVisitor, Type, TypeMapping, TypeRelation, TypeVarVariance,
|
IsEquivalentVisitor, MaterializationKind, NormalizedVisitor, Type, TypeMapping, TypeRelation,
|
||||||
UnionBuilder, UnionType,
|
UnionBuilder, UnionType,
|
||||||
};
|
};
|
||||||
use crate::util::subscript::{Nth, OutOfBoundsError, PyIndex, PySlice, StepSizeZeroError};
|
use crate::util::subscript::{Nth, OutOfBoundsError, PyIndex, PySlice, StepSizeZeroError};
|
||||||
|
@ -228,8 +228,12 @@ impl<'db> TupleType<'db> {
|
||||||
TupleType::new(db, &self.tuple(db).normalized_impl(db, visitor))
|
TupleType::new(db, &self.tuple(db).normalized_impl(db, visitor))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Option<Self> {
|
pub(crate) fn materialize(
|
||||||
TupleType::new(db, &self.tuple(db).materialize(db, variance))
|
self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
materialization_kind: MaterializationKind,
|
||||||
|
) -> Option<Self> {
|
||||||
|
TupleType::new(db, &self.tuple(db).materialize(db, materialization_kind))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn apply_type_mapping_impl<'a>(
|
pub(crate) fn apply_type_mapping_impl<'a>(
|
||||||
|
@ -389,8 +393,12 @@ impl<'db> FixedLengthTuple<Type<'db>> {
|
||||||
Self::from_elements(self.0.iter().map(|ty| ty.normalized_impl(db, visitor)))
|
Self::from_elements(self.0.iter().map(|ty| ty.normalized_impl(db, visitor)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn materialize(&self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
fn materialize(&self, db: &'db dyn Db, materialization_kind: MaterializationKind) -> Self {
|
||||||
Self::from_elements(self.0.iter().map(|ty| ty.materialize(db, variance)))
|
Self::from_elements(
|
||||||
|
self.0
|
||||||
|
.iter()
|
||||||
|
.map(|ty| ty.materialize(db, materialization_kind)),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_type_mapping_impl<'a>(
|
fn apply_type_mapping_impl<'a>(
|
||||||
|
@ -703,11 +711,19 @@ impl<'db> VariableLengthTuple<Type<'db>> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn materialize(&self, db: &'db dyn Db, variance: TypeVarVariance) -> TupleSpec<'db> {
|
fn materialize(
|
||||||
|
&self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
materialization_kind: MaterializationKind,
|
||||||
|
) -> TupleSpec<'db> {
|
||||||
Self::mixed(
|
Self::mixed(
|
||||||
self.prefix.iter().map(|ty| ty.materialize(db, variance)),
|
self.prefix
|
||||||
self.variable.materialize(db, variance),
|
.iter()
|
||||||
self.suffix.iter().map(|ty| ty.materialize(db, variance)),
|
.map(|ty| ty.materialize(db, materialization_kind)),
|
||||||
|
self.variable.materialize(db, materialization_kind),
|
||||||
|
self.suffix
|
||||||
|
.iter()
|
||||||
|
.map(|ty| ty.materialize(db, materialization_kind)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1058,10 +1074,14 @@ impl<'db> Tuple<Type<'db>> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn materialize(&self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
pub(crate) fn materialize(
|
||||||
|
&self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
materialization_kind: MaterializationKind,
|
||||||
|
) -> Self {
|
||||||
match self {
|
match self {
|
||||||
Tuple::Fixed(tuple) => Tuple::Fixed(tuple.materialize(db, variance)),
|
Tuple::Fixed(tuple) => Tuple::Fixed(tuple.materialize(db, materialization_kind)),
|
||||||
Tuple::Variable(tuple) => tuple.materialize(db, variance),
|
Tuple::Variable(tuple) => tuple.materialize(db, materialization_kind),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue