mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-30 23:27:27 +00:00
[ty] Homogeneous and mixed tuples (#18600)
Some checks are pending
CI / cargo fuzz build (push) Blocked by required conditions
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 / 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 / cargo fuzz build (push) Blocked by required conditions
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 / 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
We already had support for homogeneous tuples (`tuple[int, ...]`). This PR extends this to also support mixed tuples (`tuple[str, str, *tuple[int, ...], str str]`). A mixed tuple consists of a fixed-length (possibly empty) prefix and suffix, and a variable-length portion in the middle. Every element of the variable-length portion must be of the same type. A homogeneous tuple is then just a mixed tuple with an empty prefix and suffix. The new data representation uses different Rust types for a fixed-length (aka heterogeneous) tuple. Another option would have been to use the `VariableLengthTuple` representation for all tuples, and to wrap the "variable + suffix" portion in an `Option`. I don't think that would simplify the method implementations much, though, since we would still have a 2×2 case analysis for most of them. One wrinkle is that the definition of the `tuple` class in the typeshed has a single typevar, and canonically represents a homogeneous tuple. When getting the class of a tuple instance, that means that we have to summarize our detailed mixed tuple type information into its "homogeneous supertype". (We were already doing this for heterogeneous types.) A similar thing happens when concatenating two mixed tuples: the variable-length portion and suffix of the LHS, and the prefix and variable-length portion of the RHS, all get unioned into the variable-length portion of the result. The LHS prefix and RHS suffix carry through unchanged. --------- Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
This commit is contained in:
parent
d9266284df
commit
ea812d0813
32 changed files with 2432 additions and 758 deletions
|
@ -58,7 +58,7 @@ reveal_type(c) # revealed: tuple[str, int]
|
|||
reveal_type(d) # revealed: tuple[tuple[str, str], tuple[int, int]]
|
||||
reveal_type(e) # revealed: tuple[str, ...]
|
||||
|
||||
reveal_type(f) # revealed: @Todo(PEP 646)
|
||||
reveal_type(f) # revealed: tuple[str, *tuple[int, ...], bytes]
|
||||
reveal_type(g) # revealed: @Todo(PEP 646)
|
||||
|
||||
reveal_type(h) # revealed: tuple[list[int], list[int]]
|
||||
|
|
|
@ -1722,7 +1722,7 @@ d = True
|
|||
reveal_type(d.__class__) # revealed: <class 'bool'>
|
||||
|
||||
e = (42, 42)
|
||||
reveal_type(e.__class__) # revealed: <class 'tuple'>
|
||||
reveal_type(e.__class__) # revealed: <class 'tuple[Literal[42], Literal[42]]'>
|
||||
|
||||
def f(a: int, b: typing_extensions.LiteralString, c: int | str, d: type[str]):
|
||||
reveal_type(a.__class__) # revealed: type[int]
|
||||
|
|
|
@ -17,6 +17,32 @@ def _(x: tuple[int, str], y: tuple[None, tuple[int]]):
|
|||
|
||||
```py
|
||||
def _(x: tuple[int, ...], y: tuple[str, ...]):
|
||||
reveal_type(x + x) # revealed: tuple[int, ...]
|
||||
reveal_type(x + y) # revealed: tuple[int | str, ...]
|
||||
reveal_type(x + (1, 2)) # revealed: tuple[int, ...]
|
||||
reveal_type((1, 2) + x) # revealed: tuple[Literal[1], Literal[2], *tuple[int, ...]]
|
||||
reveal_type(x + (3, 4)) # revealed: tuple[*tuple[int, ...], Literal[3], Literal[4]]
|
||||
reveal_type((1, 2) + x + (3, 4)) # revealed: tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[3], Literal[4]]
|
||||
reveal_type((1, 2) + y + (3, 4) + x) # revealed: tuple[Literal[1], Literal[2], *tuple[int | str, ...]]
|
||||
```
|
||||
|
||||
We get the same results even when we use a legacy type alias, even though this involves first
|
||||
inferring the `tuple[...]` expression as a value form. (Doing so gives a generic alias of the
|
||||
`tuple` type, but as a special case, we include the full detailed tuple element specification in
|
||||
specializations of `tuple`.)
|
||||
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
OneTwo = tuple[Literal[1], Literal[2]]
|
||||
ThreeFour = tuple[Literal[3], Literal[4]]
|
||||
IntTuple = tuple[int, ...]
|
||||
StrTuple = tuple[str, ...]
|
||||
|
||||
def _(one_two: OneTwo, x: IntTuple, y: StrTuple, three_four: ThreeFour):
|
||||
reveal_type(x + x) # revealed: tuple[int, ...]
|
||||
reveal_type(x + y) # revealed: tuple[int | str, ...]
|
||||
reveal_type(one_two + x) # revealed: tuple[Literal[1], Literal[2], *tuple[int, ...]]
|
||||
reveal_type(x + three_four) # revealed: tuple[*tuple[int, ...], Literal[3], Literal[4]]
|
||||
reveal_type(one_two + x + three_four) # revealed: tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[3], Literal[4]]
|
||||
reveal_type(one_two + y + three_four + x) # revealed: tuple[Literal[1], Literal[2], *tuple[int | str, ...]]
|
||||
```
|
||||
|
|
|
@ -130,6 +130,44 @@ reveal_type(takes_in_protocol(ExplicitSub())) # revealed: int
|
|||
reveal_type(takes_in_protocol(ExplicitGenericSub[str]())) # revealed: str
|
||||
```
|
||||
|
||||
## Inferring tuple parameter types
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
def takes_mixed_tuple_suffix(x: tuple[int, bytes, *tuple[str, ...], T, int]) -> T:
|
||||
return x[-2]
|
||||
|
||||
# TODO: revealed: Literal[True]
|
||||
reveal_type(takes_mixed_tuple_suffix((1, b"foo", "bar", "baz", True, 42))) # revealed: Unknown
|
||||
|
||||
def takes_mixed_tuple_prefix(x: tuple[int, T, *tuple[str, ...], bool, int]) -> T:
|
||||
return x[1]
|
||||
|
||||
# TODO: revealed: Literal[b"foo"]
|
||||
reveal_type(takes_mixed_tuple_prefix((1, b"foo", "bar", "baz", True, 42))) # revealed: Unknown
|
||||
|
||||
def takes_fixed_tuple(x: tuple[T, int]) -> T:
|
||||
return x[0]
|
||||
|
||||
reveal_type(takes_fixed_tuple((True, 42))) # revealed: Literal[True]
|
||||
|
||||
def takes_homogeneous_tuple(x: tuple[T, ...]) -> T:
|
||||
return x[0]
|
||||
|
||||
# TODO: revealed: Literal[42]
|
||||
reveal_type(takes_homogeneous_tuple((42,))) # revealed: Unknown
|
||||
# TODO: revealed: Literal[42, 43]
|
||||
reveal_type(takes_homogeneous_tuple((42, 43))) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Inferring a bound typevar
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
|
|
@ -125,6 +125,35 @@ reveal_type(takes_in_protocol(ExplicitSub())) # revealed: int
|
|||
reveal_type(takes_in_protocol(ExplicitGenericSub[str]())) # revealed: str
|
||||
```
|
||||
|
||||
## Inferring tuple parameter types
|
||||
|
||||
```py
|
||||
def takes_mixed_tuple_suffix[T](x: tuple[int, bytes, *tuple[str, ...], T, int]) -> T:
|
||||
return x[-2]
|
||||
|
||||
# TODO: revealed: Literal[True]
|
||||
reveal_type(takes_mixed_tuple_suffix((1, b"foo", "bar", "baz", True, 42))) # revealed: Unknown
|
||||
|
||||
def takes_mixed_tuple_prefix[T](x: tuple[int, T, *tuple[str, ...], bool, int]) -> T:
|
||||
return x[1]
|
||||
|
||||
# TODO: revealed: Literal[b"foo"]
|
||||
reveal_type(takes_mixed_tuple_prefix((1, b"foo", "bar", "baz", True, 42))) # revealed: Unknown
|
||||
|
||||
def takes_fixed_tuple[T](x: tuple[T, int]) -> T:
|
||||
return x[0]
|
||||
|
||||
reveal_type(takes_fixed_tuple((True, 42))) # revealed: Literal[True]
|
||||
|
||||
def takes_homogeneous_tuple[T](x: tuple[T, ...]) -> T:
|
||||
return x[0]
|
||||
|
||||
# TODO: revealed: Literal[42]
|
||||
reveal_type(takes_homogeneous_tuple((42,))) # revealed: Unknown
|
||||
# TODO: revealed: Literal[42, 43]
|
||||
reveal_type(takes_homogeneous_tuple((42, 43))) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Inferring a bound typevar
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
|
|
@ -192,10 +192,9 @@ def _(t1: tuple[int | None, int | None], t2: tuple[int, int] | tuple[None, None]
|
|||
reveal_type(t1[1]) # revealed: int | None
|
||||
|
||||
if t2[0] is not None:
|
||||
reveal_type(t2[0]) # revealed: int
|
||||
# TODO: should be int
|
||||
reveal_type(t2[0]) # revealed: Unknown & ~None
|
||||
# TODO: should be int
|
||||
reveal_type(t2[1]) # revealed: Unknown
|
||||
reveal_type(t2[1]) # revealed: int | None
|
||||
```
|
||||
|
||||
### String subscript
|
||||
|
|
|
@ -215,12 +215,12 @@ def _(a: tuple[str, int] | tuple[int, str], c: C[Any]):
|
|||
# TODO: Should be `tuple[int, str]`
|
||||
reveal_type(a) # revealed: tuple[str, int] | tuple[int, str]
|
||||
# TODO: Should be `str`
|
||||
reveal_type(a[1]) # revealed: Unknown
|
||||
reveal_type(a[1]) # revealed: str | int
|
||||
|
||||
if reveal_type(is_int(a[0])): # revealed: TypeIs[int @ a[0]]
|
||||
# TODO: Should be `tuple[int, str]`
|
||||
reveal_type(a) # revealed: tuple[str, int] | tuple[int, str]
|
||||
reveal_type(a[0]) # revealed: Unknown & int
|
||||
reveal_type(a[0]) # revealed: int
|
||||
|
||||
# TODO: Should be `TypeGuard[str @ c.v]`
|
||||
if reveal_type(guard_str(c.v)): # revealed: @Todo(`TypeGuard[]` special form)
|
||||
|
|
|
@ -69,8 +69,64 @@ def _(m: int, n: int):
|
|||
t[::0] # error: [zero-stepsize-in-slice]
|
||||
|
||||
tuple_slice = t[m:n]
|
||||
# TODO: Should be `tuple[Literal[1, 'a', b"b"] | None, ...]`
|
||||
reveal_type(tuple_slice) # revealed: tuple[Unknown, ...]
|
||||
reveal_type(tuple_slice) # revealed: tuple[Literal[1, "a", b"b"] | None, ...]
|
||||
```
|
||||
|
||||
## Slices of homogeneous and mixed tuples
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.11"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
def homogeneous(t: tuple[str, ...]) -> None:
|
||||
reveal_type(t[0]) # revealed: str
|
||||
reveal_type(t[1]) # revealed: str
|
||||
reveal_type(t[2]) # revealed: str
|
||||
reveal_type(t[3]) # revealed: str
|
||||
|
||||
reveal_type(t[-1]) # revealed: str
|
||||
reveal_type(t[-2]) # revealed: str
|
||||
reveal_type(t[-3]) # revealed: str
|
||||
reveal_type(t[-4]) # revealed: str
|
||||
|
||||
def mixed(s: tuple[str, ...]) -> None:
|
||||
t = (1, 2, 3) + s + (8, 9, 10)
|
||||
|
||||
reveal_type(t[0]) # revealed: Literal[1]
|
||||
reveal_type(t[1]) # revealed: Literal[2]
|
||||
reveal_type(t[2]) # revealed: Literal[3]
|
||||
reveal_type(t[3]) # revealed: str | Literal[8]
|
||||
reveal_type(t[4]) # revealed: str | Literal[8, 9]
|
||||
reveal_type(t[5]) # revealed: str | Literal[8, 9, 10]
|
||||
|
||||
reveal_type(t[-1]) # revealed: Literal[10]
|
||||
reveal_type(t[-2]) # revealed: Literal[9]
|
||||
reveal_type(t[-3]) # revealed: Literal[8]
|
||||
reveal_type(t[-4]) # revealed: Literal[3] | str
|
||||
reveal_type(t[-5]) # revealed: Literal[2, 3] | str
|
||||
reveal_type(t[-6]) # revealed: Literal[1, 2, 3] | str
|
||||
```
|
||||
|
||||
## `tuple` as generic alias
|
||||
|
||||
For tuple instances, we can track more detailed information about the length and element types of
|
||||
the tuple. This information carries over to the generic alias that the tuple is an instance of.
|
||||
|
||||
```py
|
||||
def _(a: tuple, b: tuple[int], c: tuple[int, str], d: tuple[int, ...]) -> None:
|
||||
reveal_type(a) # revealed: tuple[Unknown, ...]
|
||||
reveal_type(b) # revealed: tuple[int]
|
||||
reveal_type(c) # revealed: tuple[int, str]
|
||||
reveal_type(d) # revealed: tuple[int, ...]
|
||||
|
||||
reveal_type(tuple) # revealed: <class 'tuple'>
|
||||
reveal_type(tuple[int]) # revealed: <class 'tuple[int]'>
|
||||
reveal_type(tuple[int, str]) # revealed: <class 'tuple[int, str]'>
|
||||
reveal_type(tuple[int, ...]) # revealed: <class 'tuple[int, ...]'>
|
||||
```
|
||||
|
||||
## Inheritance
|
||||
|
@ -83,8 +139,13 @@ python-version = "3.9"
|
|||
```py
|
||||
class A(tuple[int, str]): ...
|
||||
|
||||
# revealed: tuple[<class 'A'>, <class 'tuple[@Todo(Generic tuple specializations), ...]'>, <class 'Sequence[@Todo(Generic tuple specializations)]'>, <class 'Reversible[@Todo(Generic tuple specializations)]'>, <class 'Collection[@Todo(Generic tuple specializations)]'>, <class 'Iterable[@Todo(Generic tuple specializations)]'>, <class 'Container[@Todo(Generic tuple specializations)]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
# revealed: tuple[<class 'A'>, <class 'tuple[int, str]'>, <class 'Sequence[int | str]'>, <class 'Reversible[int | str]'>, <class 'Collection[int | str]'>, <class 'Iterable[int | str]'>, <class 'Container[int | str]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
reveal_type(A.__mro__)
|
||||
|
||||
class C(tuple): ...
|
||||
|
||||
# revealed: tuple[<class 'C'>, <class 'tuple[Unknown, ...]'>, <class 'Sequence[Unknown]'>, <class 'Reversible[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
reveal_type(C.__mro__)
|
||||
```
|
||||
|
||||
## `typing.Tuple`
|
||||
|
@ -109,9 +170,19 @@ def _(c: Tuple, d: Tuple[int, A], e: Tuple[Any, ...]):
|
|||
Inheriting from `Tuple` results in a MRO with `builtins.tuple` and `typing.Generic`. `Tuple` itself
|
||||
is not a class.
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.9"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing import Tuple
|
||||
|
||||
class A(Tuple[int, str]): ...
|
||||
|
||||
# revealed: tuple[<class 'A'>, <class 'tuple[int, str]'>, <class 'Sequence[int | str]'>, <class 'Reversible[int | str]'>, <class 'Collection[int | str]'>, <class 'Iterable[int | str]'>, <class 'Container[int | str]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
reveal_type(A.__mro__)
|
||||
|
||||
class C(Tuple): ...
|
||||
|
||||
# revealed: tuple[<class 'C'>, <class 'tuple[Unknown, ...]'>, <class 'Sequence[Unknown]'>, <class 'Reversible[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
|
|
|
@ -16,6 +16,28 @@ def _(p: P, q: Q):
|
|||
assert_type((p, q), tuple[P, Q])
|
||||
```
|
||||
|
||||
## Instantiating tuples
|
||||
|
||||
Like all classes, tuples can be instantiated by invoking the `tuple` class. When instantiating a
|
||||
specialization of `tuple` we (TODO: should) check that the values passed in match the element types
|
||||
defined in the specialization.
|
||||
|
||||
```py
|
||||
# TODO: revealed: tuple[()]
|
||||
reveal_type(tuple()) # revealed: tuple[Unknown, ...]
|
||||
# TODO: revealed: tuple[Literal[1]]
|
||||
reveal_type(tuple([1])) # revealed: tuple[Unknown, ...]
|
||||
reveal_type(tuple[int]([1])) # revealed: tuple[int]
|
||||
# TODO: error for invalid arguments
|
||||
reveal_type(tuple[int, str]([1])) # revealed: tuple[int, str]
|
||||
|
||||
reveal_type(().__class__()) # revealed: tuple[()]
|
||||
# TODO: error for invalid arguments
|
||||
reveal_type((1,).__class__()) # revealed: tuple[Literal[1]]
|
||||
# TODO: error for invalid arguments
|
||||
reveal_type((1, 2).__class__()) # revealed: tuple[Literal[1], Literal[2]]
|
||||
```
|
||||
|
||||
## Subtyping relationships
|
||||
|
||||
The type `tuple[S1, S2]` is a subtype of `tuple[T1, T2]` if and only if `S1` is a subtype of `T1`
|
||||
|
@ -60,10 +82,7 @@ class AnotherEmptyTuple(tuple[()]): ...
|
|||
|
||||
static_assert(not is_equivalent_to(AnotherEmptyTuple, tuple[()]))
|
||||
|
||||
# TODO: These should not be errors
|
||||
# error: [static-assert-error]
|
||||
static_assert(is_subtype_of(AnotherEmptyTuple, tuple[()]))
|
||||
# error: [static-assert-error]
|
||||
static_assert(is_assignable_to(AnotherEmptyTuple, tuple[()]))
|
||||
```
|
||||
|
||||
|
@ -158,8 +177,6 @@ class NotAlwaysTruthyTuple(tuple[int]):
|
|||
def __bool__(self) -> bool:
|
||||
return False
|
||||
|
||||
# TODO: This assignment should be allowed
|
||||
# error: [invalid-assignment]
|
||||
t: tuple[int] = NotAlwaysTruthyTuple((1,))
|
||||
```
|
||||
|
||||
|
|
|
@ -303,6 +303,11 @@ static_assert(not is_assignable_to(tuple[Any, Literal[2]], tuple[int, str]))
|
|||
|
||||
## Assignability of heterogeneous tuple types to homogeneous tuple types
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
While a homogeneous tuple type is not assignable to any heterogeneous tuple types, a heterogeneous
|
||||
tuple type can be assignable to a homogeneous tuple type, and homogeneous tuple types can be
|
||||
assignable to `Sequence`:
|
||||
|
@ -312,6 +317,11 @@ from typing import Literal, Any, Sequence
|
|||
from ty_extensions import static_assert, is_assignable_to, Not, AlwaysFalsy
|
||||
|
||||
static_assert(is_assignable_to(tuple[Literal[1], Literal[2]], tuple[Literal[1, 2], ...]))
|
||||
static_assert(is_assignable_to(tuple[Literal[1], Literal[2]], tuple[Literal[1], *tuple[Literal[2], ...]]))
|
||||
static_assert(is_assignable_to(tuple[Literal[1], Literal[2]], tuple[*tuple[Literal[1], ...], Literal[2]]))
|
||||
static_assert(is_assignable_to(tuple[Literal[1], Literal[2]], tuple[Literal[1], *tuple[str, ...], Literal[2]]))
|
||||
static_assert(is_assignable_to(tuple[Literal[1], Literal[2]], tuple[Literal[1], Literal[2], *tuple[str, ...]]))
|
||||
static_assert(is_assignable_to(tuple[Literal[1], Literal[2]], tuple[*tuple[str, ...], Literal[1], Literal[2]]))
|
||||
static_assert(is_assignable_to(tuple[Literal[1], Literal[2]], tuple[int, ...]))
|
||||
static_assert(is_assignable_to(tuple[Literal[1], Literal[2]], tuple[int | str, ...]))
|
||||
static_assert(is_assignable_to(tuple[Literal[1], Literal[2]], tuple[Any, ...]))
|
||||
|
@ -330,6 +340,218 @@ static_assert(is_assignable_to(tuple[()], Sequence[int]))
|
|||
static_assert(not is_assignable_to(tuple[int, int], tuple[str, ...]))
|
||||
```
|
||||
|
||||
## Assignability of two mixed tuple types
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing import Literal, Any, Sequence
|
||||
from ty_extensions import static_assert, is_assignable_to, Not, AlwaysFalsy
|
||||
|
||||
static_assert(
|
||||
is_assignable_to(
|
||||
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||
)
|
||||
)
|
||||
static_assert(
|
||||
is_assignable_to(
|
||||
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[10]],
|
||||
)
|
||||
)
|
||||
static_assert(
|
||||
is_assignable_to(
|
||||
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||
tuple[Literal[1], Literal[2], *tuple[int, ...]],
|
||||
)
|
||||
)
|
||||
|
||||
static_assert(
|
||||
is_assignable_to(
|
||||
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||
tuple[Literal[1], *tuple[int, ...], Literal[9], Literal[10]],
|
||||
)
|
||||
)
|
||||
static_assert(
|
||||
is_assignable_to(
|
||||
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||
tuple[Literal[1], *tuple[int, ...], Literal[10]],
|
||||
)
|
||||
)
|
||||
static_assert(
|
||||
is_assignable_to(
|
||||
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||
tuple[Literal[1], *tuple[int, ...]],
|
||||
)
|
||||
)
|
||||
|
||||
static_assert(
|
||||
is_assignable_to(
|
||||
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||
tuple[*tuple[int, ...], Literal[9], Literal[10]],
|
||||
)
|
||||
)
|
||||
static_assert(
|
||||
is_assignable_to(
|
||||
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||
tuple[*tuple[int, ...], Literal[10]],
|
||||
)
|
||||
)
|
||||
static_assert(
|
||||
is_assignable_to(
|
||||
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||
tuple[*tuple[int, ...]],
|
||||
)
|
||||
)
|
||||
|
||||
static_assert(
|
||||
not is_assignable_to(
|
||||
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[10]],
|
||||
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||
)
|
||||
)
|
||||
static_assert(
|
||||
not is_assignable_to(
|
||||
tuple[Literal[1], Literal[2], *tuple[int, ...]],
|
||||
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||
)
|
||||
)
|
||||
|
||||
static_assert(
|
||||
not is_assignable_to(
|
||||
tuple[Literal[1], *tuple[int, ...], Literal[9], Literal[10]],
|
||||
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||
)
|
||||
)
|
||||
static_assert(
|
||||
not is_assignable_to(
|
||||
tuple[Literal[1], *tuple[int, ...], Literal[10]],
|
||||
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||
)
|
||||
)
|
||||
static_assert(
|
||||
not is_assignable_to(
|
||||
tuple[Literal[1], *tuple[int, ...]],
|
||||
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||
)
|
||||
)
|
||||
|
||||
static_assert(
|
||||
not is_assignable_to(
|
||||
tuple[*tuple[int, ...], Literal[9], Literal[10]],
|
||||
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||
)
|
||||
)
|
||||
static_assert(
|
||||
not is_assignable_to(
|
||||
tuple[*tuple[int, ...], Literal[10]],
|
||||
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||
)
|
||||
)
|
||||
static_assert(
|
||||
not is_assignable_to(
|
||||
tuple[*tuple[int, ...]],
|
||||
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
## Assignability of the gradual tuple
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
As a [special case][gradual tuple], `tuple[Any, ...]` is a [gradual][gradual form] tuple type, which
|
||||
is assignable to every tuple of any length.
|
||||
|
||||
```py
|
||||
from typing import Any
|
||||
from ty_extensions import static_assert, is_assignable_to
|
||||
|
||||
static_assert(is_assignable_to(tuple[Any, ...], tuple[Any, ...]))
|
||||
static_assert(is_assignable_to(tuple[Any, ...], tuple[Any]))
|
||||
static_assert(is_assignable_to(tuple[Any, ...], tuple[Any, Any]))
|
||||
static_assert(is_assignable_to(tuple[Any, ...], tuple[int, ...]))
|
||||
static_assert(is_assignable_to(tuple[Any, ...], tuple[int]))
|
||||
static_assert(is_assignable_to(tuple[Any, ...], tuple[int, int]))
|
||||
```
|
||||
|
||||
This also applies when `tuple[Any, ...]` is unpacked into a mixed tuple.
|
||||
|
||||
```py
|
||||
static_assert(is_assignable_to(tuple[int, *tuple[Any, ...]], tuple[int, *tuple[Any, ...]]))
|
||||
static_assert(is_assignable_to(tuple[int, *tuple[Any, ...]], tuple[Any, ...]))
|
||||
static_assert(is_assignable_to(tuple[int, *tuple[Any, ...]], tuple[Any]))
|
||||
static_assert(is_assignable_to(tuple[int, *tuple[Any, ...]], tuple[Any, Any]))
|
||||
static_assert(is_assignable_to(tuple[int, *tuple[Any, ...]], tuple[int, *tuple[int, ...]]))
|
||||
static_assert(is_assignable_to(tuple[int, *tuple[Any, ...]], tuple[int, ...]))
|
||||
static_assert(is_assignable_to(tuple[int, *tuple[Any, ...]], tuple[int]))
|
||||
static_assert(is_assignable_to(tuple[int, *tuple[Any, ...]], tuple[int, int]))
|
||||
|
||||
static_assert(is_assignable_to(tuple[*tuple[Any, ...], int], tuple[*tuple[Any, ...], int]))
|
||||
static_assert(is_assignable_to(tuple[*tuple[Any, ...], int], tuple[Any, ...]))
|
||||
static_assert(is_assignable_to(tuple[*tuple[Any, ...], int], tuple[Any]))
|
||||
static_assert(is_assignable_to(tuple[*tuple[Any, ...], int], tuple[Any, Any]))
|
||||
static_assert(is_assignable_to(tuple[*tuple[Any, ...], int], tuple[*tuple[int, ...], int]))
|
||||
static_assert(is_assignable_to(tuple[*tuple[Any, ...], int], tuple[int, ...]))
|
||||
static_assert(is_assignable_to(tuple[*tuple[Any, ...], int], tuple[int]))
|
||||
static_assert(is_assignable_to(tuple[*tuple[Any, ...], int], tuple[int, int]))
|
||||
|
||||
static_assert(is_assignable_to(tuple[int, *tuple[Any, ...], int], tuple[int, *tuple[Any, ...], int]))
|
||||
static_assert(is_assignable_to(tuple[int, *tuple[Any, ...], int], tuple[Any, ...]))
|
||||
static_assert(not is_assignable_to(tuple[int, *tuple[Any, ...], int], tuple[Any]))
|
||||
static_assert(is_assignable_to(tuple[int, *tuple[Any, ...], int], tuple[Any, Any]))
|
||||
static_assert(is_assignable_to(tuple[int, *tuple[Any, ...], int], tuple[int, *tuple[int, ...], int]))
|
||||
static_assert(is_assignable_to(tuple[int, *tuple[Any, ...], int], tuple[int, ...]))
|
||||
static_assert(not is_assignable_to(tuple[int, *tuple[Any, ...], int], tuple[int]))
|
||||
static_assert(is_assignable_to(tuple[int, *tuple[Any, ...], int], tuple[int, int]))
|
||||
```
|
||||
|
||||
The same is not true of fully static tuple types, since an unbounded homogeneous tuple is defined to
|
||||
be the _union_ of all tuple lengths, not the _gradual choice_ of them.
|
||||
|
||||
```py
|
||||
static_assert(is_assignable_to(tuple[int, ...], tuple[Any, ...]))
|
||||
static_assert(not is_assignable_to(tuple[int, ...], tuple[Any]))
|
||||
static_assert(not is_assignable_to(tuple[int, ...], tuple[Any, Any]))
|
||||
static_assert(is_assignable_to(tuple[int, ...], tuple[int, ...]))
|
||||
static_assert(not is_assignable_to(tuple[int, ...], tuple[int]))
|
||||
static_assert(not is_assignable_to(tuple[int, ...], tuple[int, int]))
|
||||
|
||||
static_assert(is_assignable_to(tuple[int, *tuple[int, ...]], tuple[int, *tuple[Any, ...]]))
|
||||
static_assert(is_assignable_to(tuple[int, *tuple[int, ...]], tuple[Any, ...]))
|
||||
static_assert(not is_assignable_to(tuple[int, *tuple[int, ...]], tuple[Any]))
|
||||
static_assert(not is_assignable_to(tuple[int, *tuple[int, ...]], tuple[Any, Any]))
|
||||
static_assert(is_assignable_to(tuple[int, *tuple[int, ...]], tuple[int, *tuple[int, ...]]))
|
||||
static_assert(is_assignable_to(tuple[int, *tuple[int, ...]], tuple[int, ...]))
|
||||
static_assert(not is_assignable_to(tuple[int, *tuple[int, ...]], tuple[int]))
|
||||
static_assert(not is_assignable_to(tuple[int, *tuple[int, ...]], tuple[int, int]))
|
||||
|
||||
static_assert(is_assignable_to(tuple[*tuple[int, ...], int], tuple[*tuple[Any, ...], int]))
|
||||
static_assert(is_assignable_to(tuple[*tuple[int, ...], int], tuple[Any, ...]))
|
||||
static_assert(not is_assignable_to(tuple[*tuple[int, ...], int], tuple[Any]))
|
||||
static_assert(not is_assignable_to(tuple[*tuple[int, ...], int], tuple[Any, Any]))
|
||||
static_assert(is_assignable_to(tuple[*tuple[int, ...], int], tuple[*tuple[int, ...], int]))
|
||||
static_assert(is_assignable_to(tuple[*tuple[int, ...], int], tuple[int, ...]))
|
||||
static_assert(not is_assignable_to(tuple[*tuple[int, ...], int], tuple[int]))
|
||||
static_assert(not is_assignable_to(tuple[*tuple[int, ...], int], tuple[int, int]))
|
||||
|
||||
static_assert(is_assignable_to(tuple[int, *tuple[int, ...], int], tuple[int, *tuple[Any, ...], int]))
|
||||
static_assert(is_assignable_to(tuple[int, *tuple[int, ...], int], tuple[Any, ...]))
|
||||
static_assert(not is_assignable_to(tuple[int, *tuple[int, ...], int], tuple[Any]))
|
||||
static_assert(not is_assignable_to(tuple[int, *tuple[int, ...], int], tuple[Any, Any]))
|
||||
static_assert(is_assignable_to(tuple[int, *tuple[int, ...], int], tuple[int, *tuple[int, ...], int]))
|
||||
static_assert(is_assignable_to(tuple[int, *tuple[int, ...], int], tuple[int, ...]))
|
||||
static_assert(not is_assignable_to(tuple[int, *tuple[int, ...], int], tuple[int]))
|
||||
static_assert(not is_assignable_to(tuple[int, *tuple[int, ...], int], tuple[int, int]))
|
||||
```
|
||||
|
||||
## Union types
|
||||
|
||||
```py
|
||||
|
@ -828,8 +1050,8 @@ sets of possible materializations -- if they represent the same sets of possible
|
|||
sets of sets of possible runtime objects). By this principle `int | Any` is gradually equivalent to
|
||||
`Unknown | int`, since they have exactly the same sets of posisble materializations. But
|
||||
`bool | Any` is not equivalent to `int`, since there are many possible materializations of
|
||||
`bool | Any` that are not assignable to `int`. It is therefore *not* necessary for `X` to be
|
||||
gradually equivalent to `Y` in order for `Foo[X]` to be assignable to `Foo[Y]`; it is *only*
|
||||
`bool | Any` that are not assignable to `int`. It is therefore _not_ necessary for `X` to be
|
||||
gradually equivalent to `Y` in order for `Foo[X]` to be assignable to `Foo[Y]`; it is _only_
|
||||
necessary for `X` and `Y` to be mutually assignable.
|
||||
|
||||
```py
|
||||
|
@ -887,4 +1109,6 @@ static_assert(not is_assignable_to(TypeGuard[Unknown], str)) # error: [static-a
|
|||
static_assert(not is_assignable_to(TypeIs[Any], str))
|
||||
```
|
||||
|
||||
[gradual form]: https://typing.python.org/en/latest/spec/glossary.html#term-gradual-form
|
||||
[gradual tuple]: https://typing.python.org/en/latest/spec/tuples.html#tuple-type-form
|
||||
[typing documentation]: https://typing.python.org/en/latest/spec/concepts.html#the-assignable-to-or-consistent-subtyping-relation
|
||||
|
|
|
@ -48,8 +48,7 @@ static_assert(not is_fully_static(Any | str))
|
|||
static_assert(not is_fully_static(str | Unknown))
|
||||
static_assert(not is_fully_static(Intersection[Any, Not[LiteralString]]))
|
||||
|
||||
# TODO: should pass
|
||||
static_assert(not is_fully_static(tuple[Any, ...])) # error: [static-assert-error]
|
||||
static_assert(not is_fully_static(tuple[Any, ...]))
|
||||
|
||||
static_assert(not is_fully_static(tuple[int, Any]))
|
||||
static_assert(not is_fully_static(type[Any]))
|
||||
|
|
|
@ -159,6 +159,11 @@ from typing import Literal, Any, Sequence
|
|||
from ty_extensions import static_assert, is_subtype_of, Not, AlwaysFalsy
|
||||
|
||||
static_assert(is_subtype_of(tuple[Literal[1], Literal[2]], tuple[Literal[1, 2], ...]))
|
||||
static_assert(is_subtype_of(tuple[Literal[1], Literal[2]], tuple[Literal[1], *tuple[Literal[2], ...]]))
|
||||
static_assert(is_subtype_of(tuple[Literal[1], Literal[2]], tuple[*tuple[Literal[1], ...], Literal[2]]))
|
||||
static_assert(is_subtype_of(tuple[Literal[1], Literal[2]], tuple[Literal[1], *tuple[str, ...], Literal[2]]))
|
||||
static_assert(is_subtype_of(tuple[Literal[1], Literal[2]], tuple[Literal[1], Literal[2], *tuple[str, ...]]))
|
||||
static_assert(is_subtype_of(tuple[Literal[1], Literal[2]], tuple[*tuple[str, ...], Literal[1], Literal[2]]))
|
||||
static_assert(is_subtype_of(tuple[Literal[1], Literal[2]], tuple[int, ...]))
|
||||
static_assert(is_subtype_of(tuple[Literal[1], Literal[2]], tuple[int | str, ...]))
|
||||
static_assert(is_subtype_of(tuple[Literal[1], Literal[2]], tuple[Not[AlwaysFalsy], ...]))
|
||||
|
@ -177,6 +182,215 @@ static_assert(not is_subtype_of(tuple[int, ...], Sequence[Any]))
|
|||
static_assert(not is_subtype_of(tuple[Any, ...], Sequence[int]))
|
||||
```
|
||||
|
||||
## Subtyping of two mixed tuple types
|
||||
|
||||
```py
|
||||
from typing import Literal, Any, Sequence
|
||||
from ty_extensions import static_assert, is_subtype_of, Not, AlwaysFalsy
|
||||
|
||||
static_assert(
|
||||
is_subtype_of(
|
||||
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||
)
|
||||
)
|
||||
static_assert(
|
||||
is_subtype_of(
|
||||
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[10]],
|
||||
)
|
||||
)
|
||||
static_assert(
|
||||
is_subtype_of(
|
||||
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||
tuple[Literal[1], Literal[2], *tuple[int, ...]],
|
||||
)
|
||||
)
|
||||
|
||||
static_assert(
|
||||
is_subtype_of(
|
||||
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||
tuple[Literal[1], *tuple[int, ...], Literal[9], Literal[10]],
|
||||
)
|
||||
)
|
||||
static_assert(
|
||||
is_subtype_of(
|
||||
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||
tuple[Literal[1], *tuple[int, ...], Literal[10]],
|
||||
)
|
||||
)
|
||||
static_assert(
|
||||
is_subtype_of(
|
||||
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||
tuple[Literal[1], *tuple[int, ...]],
|
||||
)
|
||||
)
|
||||
|
||||
static_assert(
|
||||
is_subtype_of(
|
||||
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||
tuple[*tuple[int, ...], Literal[9], Literal[10]],
|
||||
)
|
||||
)
|
||||
static_assert(
|
||||
is_subtype_of(
|
||||
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||
tuple[*tuple[int, ...], Literal[10]],
|
||||
)
|
||||
)
|
||||
static_assert(
|
||||
is_subtype_of(
|
||||
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||
tuple[*tuple[int, ...]],
|
||||
)
|
||||
)
|
||||
|
||||
static_assert(
|
||||
not is_subtype_of(
|
||||
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[10]],
|
||||
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||
)
|
||||
)
|
||||
static_assert(
|
||||
not is_subtype_of(
|
||||
tuple[Literal[1], Literal[2], *tuple[int, ...]],
|
||||
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||
)
|
||||
)
|
||||
|
||||
static_assert(
|
||||
not is_subtype_of(
|
||||
tuple[Literal[1], *tuple[int, ...], Literal[9], Literal[10]],
|
||||
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||
)
|
||||
)
|
||||
static_assert(
|
||||
not is_subtype_of(
|
||||
tuple[Literal[1], *tuple[int, ...], Literal[10]],
|
||||
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||
)
|
||||
)
|
||||
static_assert(
|
||||
not is_subtype_of(
|
||||
tuple[Literal[1], *tuple[int, ...]],
|
||||
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||
)
|
||||
)
|
||||
|
||||
static_assert(
|
||||
not is_subtype_of(
|
||||
tuple[*tuple[int, ...], Literal[9], Literal[10]],
|
||||
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||
)
|
||||
)
|
||||
static_assert(
|
||||
not is_subtype_of(
|
||||
tuple[*tuple[int, ...], Literal[10]],
|
||||
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||
)
|
||||
)
|
||||
static_assert(
|
||||
not is_subtype_of(
|
||||
tuple[*tuple[int, ...]],
|
||||
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
## Subtyping of the gradual tuple
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
As a [special case][gradual tuple], `tuple[Any, ...]` is a [gradual][gradual form] tuple type.
|
||||
However, the special-case behavior of assignability does not also apply to subtyping, since gradual
|
||||
types to not participate in subtyping.
|
||||
|
||||
```py
|
||||
from typing import Any
|
||||
from ty_extensions import static_assert, is_subtype_of
|
||||
|
||||
static_assert(not is_subtype_of(tuple[Any, ...], tuple[Any, ...]))
|
||||
static_assert(not is_subtype_of(tuple[Any, ...], tuple[Any]))
|
||||
static_assert(not is_subtype_of(tuple[Any, ...], tuple[Any, Any]))
|
||||
static_assert(not is_subtype_of(tuple[Any, ...], tuple[int, ...]))
|
||||
static_assert(not is_subtype_of(tuple[Any, ...], tuple[int]))
|
||||
static_assert(not is_subtype_of(tuple[Any, ...], tuple[int, int]))
|
||||
```
|
||||
|
||||
Subtyping also does not apply when `tuple[Any, ...]` is unpacked into a mixed tuple.
|
||||
|
||||
```py
|
||||
static_assert(not is_subtype_of(tuple[int, *tuple[Any, ...]], tuple[int, *tuple[Any, ...]]))
|
||||
static_assert(not is_subtype_of(tuple[int, *tuple[Any, ...]], tuple[Any, ...]))
|
||||
static_assert(not is_subtype_of(tuple[int, *tuple[Any, ...]], tuple[Any]))
|
||||
static_assert(not is_subtype_of(tuple[int, *tuple[Any, ...]], tuple[Any, Any]))
|
||||
static_assert(not is_subtype_of(tuple[int, *tuple[Any, ...]], tuple[int, *tuple[int, ...]]))
|
||||
static_assert(not is_subtype_of(tuple[int, *tuple[Any, ...]], tuple[int, ...]))
|
||||
static_assert(not is_subtype_of(tuple[int, *tuple[Any, ...]], tuple[int]))
|
||||
static_assert(not is_subtype_of(tuple[int, *tuple[Any, ...]], tuple[int, int]))
|
||||
|
||||
static_assert(not is_subtype_of(tuple[*tuple[Any, ...], int], tuple[*tuple[Any, ...], int]))
|
||||
static_assert(not is_subtype_of(tuple[*tuple[Any, ...], int], tuple[Any, ...]))
|
||||
static_assert(not is_subtype_of(tuple[*tuple[Any, ...], int], tuple[Any]))
|
||||
static_assert(not is_subtype_of(tuple[*tuple[Any, ...], int], tuple[Any, Any]))
|
||||
static_assert(not is_subtype_of(tuple[*tuple[Any, ...], int], tuple[*tuple[int, ...], int]))
|
||||
static_assert(not is_subtype_of(tuple[*tuple[Any, ...], int], tuple[int, ...]))
|
||||
static_assert(not is_subtype_of(tuple[*tuple[Any, ...], int], tuple[int]))
|
||||
static_assert(not is_subtype_of(tuple[*tuple[Any, ...], int], tuple[int, int]))
|
||||
|
||||
static_assert(not is_subtype_of(tuple[int, *tuple[Any, ...], int], tuple[int, *tuple[Any, ...], int]))
|
||||
static_assert(not is_subtype_of(tuple[int, *tuple[Any, ...], int], tuple[Any, ...]))
|
||||
static_assert(not is_subtype_of(tuple[int, *tuple[Any, ...], int], tuple[Any]))
|
||||
static_assert(not is_subtype_of(tuple[int, *tuple[Any, ...], int], tuple[Any, Any]))
|
||||
static_assert(not is_subtype_of(tuple[int, *tuple[Any, ...], int], tuple[int, *tuple[int, ...], int]))
|
||||
static_assert(not is_subtype_of(tuple[int, *tuple[Any, ...], int], tuple[int, ...]))
|
||||
static_assert(not is_subtype_of(tuple[int, *tuple[Any, ...], int], tuple[int]))
|
||||
static_assert(not is_subtype_of(tuple[int, *tuple[Any, ...], int], tuple[int, int]))
|
||||
```
|
||||
|
||||
Subtyping does apply to unbounded homogeneous tuples of a fully static type. However, such tuples
|
||||
are defined to be the _union_ of all tuple lengths, not the _gradual choice_ of them, so no
|
||||
variable-length tuples are a subtyping of _any_ fixed-length tuple.
|
||||
|
||||
```py
|
||||
static_assert(not is_subtype_of(tuple[int, ...], tuple[Any, ...]))
|
||||
static_assert(not is_subtype_of(tuple[int, ...], tuple[Any]))
|
||||
static_assert(not is_subtype_of(tuple[int, ...], tuple[Any, Any]))
|
||||
static_assert(is_subtype_of(tuple[int, ...], tuple[int, ...]))
|
||||
static_assert(not is_subtype_of(tuple[int, ...], tuple[int]))
|
||||
static_assert(not is_subtype_of(tuple[int, ...], tuple[int, int]))
|
||||
|
||||
static_assert(not is_subtype_of(tuple[int, *tuple[int, ...]], tuple[int, *tuple[Any, ...]]))
|
||||
static_assert(not is_subtype_of(tuple[int, *tuple[int, ...]], tuple[Any, ...]))
|
||||
static_assert(not is_subtype_of(tuple[int, *tuple[int, ...]], tuple[Any]))
|
||||
static_assert(not is_subtype_of(tuple[int, *tuple[int, ...]], tuple[Any, Any]))
|
||||
static_assert(is_subtype_of(tuple[int, *tuple[int, ...]], tuple[int, *tuple[int, ...]]))
|
||||
static_assert(is_subtype_of(tuple[int, *tuple[int, ...]], tuple[int, ...]))
|
||||
static_assert(not is_subtype_of(tuple[int, *tuple[int, ...]], tuple[int]))
|
||||
static_assert(not is_subtype_of(tuple[int, *tuple[int, ...]], tuple[int, int]))
|
||||
|
||||
static_assert(not is_subtype_of(tuple[*tuple[int, ...], int], tuple[*tuple[Any, ...], int]))
|
||||
static_assert(not is_subtype_of(tuple[*tuple[int, ...], int], tuple[Any, ...]))
|
||||
static_assert(not is_subtype_of(tuple[*tuple[int, ...], int], tuple[Any]))
|
||||
static_assert(not is_subtype_of(tuple[*tuple[int, ...], int], tuple[Any, Any]))
|
||||
static_assert(is_subtype_of(tuple[*tuple[int, ...], int], tuple[*tuple[int, ...], int]))
|
||||
static_assert(is_subtype_of(tuple[*tuple[int, ...], int], tuple[int, ...]))
|
||||
static_assert(not is_subtype_of(tuple[*tuple[int, ...], int], tuple[int]))
|
||||
static_assert(not is_subtype_of(tuple[*tuple[int, ...], int], tuple[int, int]))
|
||||
|
||||
static_assert(not is_subtype_of(tuple[int, *tuple[int, ...], int], tuple[int, *tuple[Any, ...], int]))
|
||||
static_assert(not is_subtype_of(tuple[int, *tuple[int, ...], int], tuple[Any, ...]))
|
||||
static_assert(not is_subtype_of(tuple[int, *tuple[int, ...], int], tuple[Any]))
|
||||
static_assert(not is_subtype_of(tuple[int, *tuple[int, ...], int], tuple[Any, Any]))
|
||||
static_assert(is_subtype_of(tuple[int, *tuple[int, ...], int], tuple[int, *tuple[int, ...], int]))
|
||||
static_assert(is_subtype_of(tuple[int, *tuple[int, ...], int], tuple[int, ...]))
|
||||
static_assert(not is_subtype_of(tuple[int, *tuple[int, ...], int], tuple[int]))
|
||||
static_assert(not is_subtype_of(tuple[int, *tuple[int, ...], int], tuple[int, int]))
|
||||
```
|
||||
|
||||
## Union types
|
||||
|
||||
```py
|
||||
|
@ -1639,5 +1853,7 @@ static_assert(is_subtype_of(CallableTypeOf[overload_ab], CallableTypeOf[overload
|
|||
static_assert(is_subtype_of(CallableTypeOf[overload_ba], CallableTypeOf[overload_ab]))
|
||||
```
|
||||
|
||||
[gradual form]: https://typing.python.org/en/latest/spec/glossary.html#term-gradual-form
|
||||
[gradual tuple]: https://typing.python.org/en/latest/spec/tuples.html#tuple-type-form
|
||||
[special case for float and complex]: https://typing.python.org/en/latest/spec/special-types.html#special-cases-for-float-and-complex
|
||||
[typing documentation]: https://typing.python.org/en/latest/spec/concepts.html#subtype-supertype-and-type-equivalence
|
||||
|
|
|
@ -7,6 +7,7 @@ cpython # too many cycle iterations
|
|||
hydpy # too many iterations
|
||||
ibis # too many iterations
|
||||
jax # too many iterations
|
||||
mypy # too many iterations (self-recursive type alias)
|
||||
packaging # too many iterations
|
||||
pandas # slow (9s)
|
||||
pandera # stack overflow
|
||||
|
|
|
@ -60,7 +60,6 @@ mkdocs
|
|||
mkosi
|
||||
mongo-python-driver
|
||||
more-itertools
|
||||
mypy
|
||||
mypy-protobuf
|
||||
mypy_primer
|
||||
nionutils
|
||||
|
|
|
@ -51,6 +51,7 @@ use crate::types::infer::infer_unpack_types;
|
|||
use crate::types::mro::{Mro, MroError, MroIterator};
|
||||
pub(crate) use crate::types::narrow::infer_narrowing_constraint;
|
||||
use crate::types::signatures::{Parameter, ParameterForm, Parameters};
|
||||
use crate::types::tuple::{TupleSpec, TupleType};
|
||||
pub use crate::util::diagnostics::add_inferred_python_version_hint_to_diagnostic;
|
||||
use crate::{Db, FxOrderSet, Module, Program};
|
||||
pub(crate) use class::{ClassLiteral, ClassType, GenericAlias, KnownClass};
|
||||
|
@ -78,6 +79,7 @@ mod slots;
|
|||
mod special_form;
|
||||
mod string_annotation;
|
||||
mod subclass_of;
|
||||
mod tuple;
|
||||
mod type_ordering;
|
||||
mod unpacker;
|
||||
|
||||
|
@ -543,8 +545,10 @@ pub enum Type<'db> {
|
|||
LiteralString,
|
||||
/// A bytes literal
|
||||
BytesLiteral(BytesLiteralType<'db>),
|
||||
/// A heterogeneous tuple type, with elements of the given types in source order.
|
||||
// TODO: Support variable length homogeneous tuple type like `tuple[int, ...]`.
|
||||
/// An instance of the builtin `tuple` class.
|
||||
/// TODO: Consider removing this in favor of `NominalInstance`. This is currently stored as a
|
||||
/// separate variant partly for historical reasons, and partly to allow us to easily
|
||||
/// distinguish tuples since they occur so often.
|
||||
Tuple(TupleType<'db>),
|
||||
/// An instance of a typevar in a generic class or function. When the generic class or function
|
||||
/// is specialized, we will replace this typevar with its specialization.
|
||||
|
@ -720,13 +724,7 @@ impl<'db> Type<'db> {
|
|||
.map(|ty| ty.materialize(db, variance.flip())),
|
||||
)
|
||||
.build(),
|
||||
Type::Tuple(tuple_type) => TupleType::from_elements(
|
||||
db,
|
||||
tuple_type
|
||||
.elements(db)
|
||||
.iter()
|
||||
.map(|ty| ty.materialize(db, variance)),
|
||||
),
|
||||
Type::Tuple(tuple_type) => Type::tuple(db, tuple_type.materialize(db, variance)),
|
||||
Type::TypeVar(type_var) => Type::TypeVar(type_var.materialize(db, variance)),
|
||||
Type::TypeIs(type_is) => {
|
||||
type_is.with_type(db, type_is.return_type(db).materialize(db, variance))
|
||||
|
@ -770,8 +768,8 @@ impl<'db> Type<'db> {
|
|||
Self::Tuple(tuple) => TupleType::from_elements(
|
||||
db,
|
||||
tuple
|
||||
.elements(db)
|
||||
.iter()
|
||||
.tuple(db)
|
||||
.all_elements()
|
||||
.map(|ty| ty.replace_self_reference(db, class)),
|
||||
),
|
||||
|
||||
|
@ -881,8 +879,8 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
|
||||
Self::Tuple(tuple) => tuple
|
||||
.elements(db)
|
||||
.iter()
|
||||
.tuple(db)
|
||||
.all_elements()
|
||||
.any(|ty| ty.any_over_type(db, type_fn)),
|
||||
|
||||
Self::Union(union) => union
|
||||
|
@ -1076,13 +1074,6 @@ impl<'db> Type<'db> {
|
|||
.expect("Expected a Type::IntLiteral variant")
|
||||
}
|
||||
|
||||
pub const fn into_tuple(self) -> Option<TupleType<'db>> {
|
||||
match self {
|
||||
Type::Tuple(tuple_type) => Some(tuple_type),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn is_boolean_literal(&self) -> bool {
|
||||
matches!(self, Type::BooleanLiteral(..))
|
||||
}
|
||||
|
@ -1141,7 +1132,7 @@ impl<'db> Type<'db> {
|
|||
match self {
|
||||
Type::Union(union) => Type::Union(union.normalized(db)),
|
||||
Type::Intersection(intersection) => Type::Intersection(intersection.normalized(db)),
|
||||
Type::Tuple(tuple) => Type::Tuple(tuple.normalized(db)),
|
||||
Type::Tuple(tuple) => Type::tuple(db, tuple.normalized(db)),
|
||||
Type::Callable(callable) => Type::Callable(callable.normalized(db)),
|
||||
Type::ProtocolInstance(protocol) => protocol.normalized(db),
|
||||
Type::NominalInstance(instance) => Type::NominalInstance(instance.normalized(db)),
|
||||
|
@ -1441,27 +1432,23 @@ impl<'db> Type<'db> {
|
|||
false
|
||||
}
|
||||
|
||||
// A fully static heterogeneous tuple type `A` is a subtype of a fully static heterogeneous tuple type `B`
|
||||
// iff the two tuple types have the same number of elements and each element-type in `A` is a subtype
|
||||
// of the element-type at the same index in `B`. (Now say that 5 times fast.)
|
||||
//
|
||||
// For example: `tuple[bool, bool]` is a subtype of `tuple[int, int]`,
|
||||
// but `tuple[bool, bool, bool]` is not a subtype of `tuple[int, int]`
|
||||
(Type::Tuple(self_tuple), Type::Tuple(target_tuple)) => {
|
||||
let self_elements = self_tuple.elements(db);
|
||||
let target_elements = target_tuple.elements(db);
|
||||
self_elements.len() == target_elements.len()
|
||||
&& self_elements.iter().zip(target_elements).all(
|
||||
|(self_element, target_element)| {
|
||||
self_element.has_relation_to(db, *target_element, relation)
|
||||
},
|
||||
)
|
||||
self_tuple.has_relation_to(db, target_tuple, relation)
|
||||
}
|
||||
|
||||
// `tuple[A, B, C]` is a subtype of `tuple[A | B | C, ...]`
|
||||
(Type::Tuple(tuple), _) => tuple
|
||||
.homogeneous_supertype(db)
|
||||
.has_relation_to(db, target, relation),
|
||||
(Type::Tuple(self_tuple), Type::NominalInstance(target_instance)) => {
|
||||
self_tuple.to_class_type(db).is_some_and(|self_class| {
|
||||
self_class.has_relation_to(db, target_instance.class, relation)
|
||||
})
|
||||
}
|
||||
(Type::NominalInstance(self_instance), Type::Tuple(target_tuple)) => {
|
||||
target_tuple.to_class_type(db).is_some_and(|target_class| {
|
||||
self_instance
|
||||
.class
|
||||
.has_relation_to(db, target_class, relation)
|
||||
})
|
||||
}
|
||||
(Type::Tuple(_), _) => false,
|
||||
|
||||
(Type::BoundSuper(_), Type::BoundSuper(_)) => relation.are_equivalent(db, self, target),
|
||||
(Type::BoundSuper(_), _) => KnownClass::Super
|
||||
|
@ -1961,14 +1948,15 @@ impl<'db> Type<'db> {
|
|||
!known_instance.is_instance_of(db, instance.class)
|
||||
}
|
||||
|
||||
(
|
||||
known_instance_ty @ (Type::SpecialForm(_) | Type::KnownInstance(_)),
|
||||
Type::Tuple(tuple),
|
||||
)
|
||||
| (
|
||||
Type::Tuple(tuple),
|
||||
known_instance_ty @ (Type::SpecialForm(_) | Type::KnownInstance(_)),
|
||||
) => known_instance_ty.is_disjoint_from(db, tuple.homogeneous_supertype(db)),
|
||||
(Type::SpecialForm(special_form), Type::Tuple(tuple))
|
||||
| (Type::Tuple(tuple), Type::SpecialForm(special_form)) => tuple
|
||||
.to_class_type(db)
|
||||
.is_some_and(|tuple_class| !special_form.is_instance_of(db, tuple_class)),
|
||||
|
||||
(Type::KnownInstance(known_instance), Type::Tuple(tuple))
|
||||
| (Type::Tuple(tuple), Type::KnownInstance(known_instance)) => tuple
|
||||
.to_class_type(db)
|
||||
.is_some_and(|tuple_class| !known_instance.is_instance_of(db, tuple_class)),
|
||||
|
||||
(Type::BooleanLiteral(..) | Type::TypeIs(_), Type::NominalInstance(instance))
|
||||
| (Type::NominalInstance(instance), Type::BooleanLiteral(..) | Type::TypeIs(_)) => {
|
||||
|
@ -2113,18 +2101,14 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
|
||||
(Type::Tuple(tuple), Type::Tuple(other_tuple)) => {
|
||||
let self_elements = tuple.elements(db);
|
||||
let other_elements = other_tuple.elements(db);
|
||||
self_elements.len() != other_elements.len()
|
||||
|| self_elements
|
||||
.iter()
|
||||
.zip(other_elements)
|
||||
.any(|(e1, e2)| e1.is_disjoint_from(db, *e2))
|
||||
tuple.is_disjoint_from(db, other_tuple)
|
||||
}
|
||||
|
||||
(Type::Tuple(tuple), instance @ Type::NominalInstance(_))
|
||||
| (instance @ Type::NominalInstance(_), Type::Tuple(tuple)) => {
|
||||
instance.is_disjoint_from(db, tuple.homogeneous_supertype(db))
|
||||
(Type::Tuple(tuple), Type::NominalInstance(instance))
|
||||
| (Type::NominalInstance(instance), Type::Tuple(tuple)) => {
|
||||
tuple.to_class_type(db).is_some_and(|tuple_class| {
|
||||
instance.is_disjoint_from_nominal_instance_of_class(db, tuple_class)
|
||||
})
|
||||
}
|
||||
|
||||
(Type::PropertyInstance(_), other) | (other, Type::PropertyInstance(_)) => {
|
||||
|
@ -2203,10 +2187,7 @@ impl<'db> Type<'db> {
|
|||
// containing gradual forms such as `tuple[Any, ...]`.
|
||||
// Conversely, make sure to return `true` for homogeneous tuples such as
|
||||
// `tuple[int, ...]`, once we add support for them.
|
||||
Type::Tuple(tuple) => tuple
|
||||
.elements(db)
|
||||
.iter()
|
||||
.all(|elem| elem.is_fully_static(db)),
|
||||
Type::Tuple(tuple) => tuple.is_fully_static(db),
|
||||
Type::Callable(callable) => callable.is_fully_static(db),
|
||||
Type::TypeIs(type_is) => type_is.return_type(db).is_fully_static(db),
|
||||
}
|
||||
|
@ -2379,11 +2360,7 @@ impl<'db> Type<'db> {
|
|||
false
|
||||
}
|
||||
|
||||
Type::Tuple(tuple) => tuple
|
||||
.elements(db)
|
||||
.iter()
|
||||
.all(|elem| elem.is_single_valued(db)),
|
||||
|
||||
Type::Tuple(tuple) => tuple.is_single_valued(db),
|
||||
Type::NominalInstance(instance) => instance.is_single_valued(db),
|
||||
|
||||
Type::BoundSuper(_) => {
|
||||
|
@ -2629,7 +2606,10 @@ impl<'db> Type<'db> {
|
|||
KnownClass::Str.to_instance(db).instance_member(db, name)
|
||||
}
|
||||
Type::BytesLiteral(_) => KnownClass::Bytes.to_instance(db).instance_member(db, name),
|
||||
Type::Tuple(tuple) => tuple.homogeneous_supertype(db).instance_member(db, name),
|
||||
Type::Tuple(tuple) => tuple
|
||||
.to_class_type(db)
|
||||
.map(|class| class.instance_member(db, name))
|
||||
.unwrap_or(Place::Unbound.into()),
|
||||
|
||||
Type::AlwaysTruthy | Type::AlwaysFalsy => Type::object(db).instance_member(db, name),
|
||||
Type::ModuleLiteral(_) => KnownClass::ModuleType
|
||||
|
@ -3474,7 +3454,7 @@ impl<'db> Type<'db> {
|
|||
Type::BooleanLiteral(bool) => Truthiness::from(*bool),
|
||||
Type::StringLiteral(str) => Truthiness::from(!str.value(db).is_empty()),
|
||||
Type::BytesLiteral(bytes) => Truthiness::from(!bytes.value(db).is_empty()),
|
||||
Type::Tuple(items) => Truthiness::from(!items.elements(db).is_empty()),
|
||||
Type::Tuple(tuple) => Truthiness::from(!tuple.tuple(db).is_empty()),
|
||||
};
|
||||
|
||||
Ok(truthiness)
|
||||
|
@ -3505,7 +3485,11 @@ impl<'db> Type<'db> {
|
|||
let usize_len = match self {
|
||||
Type::BytesLiteral(bytes) => Some(bytes.python_len(db)),
|
||||
Type::StringLiteral(string) => Some(string.python_len(db)),
|
||||
Type::Tuple(tuple) => Some(tuple.len(db)),
|
||||
Type::Tuple(tuple) => match tuple.tuple(db) {
|
||||
TupleSpec::Fixed(tuple) => Some(tuple.len()),
|
||||
TupleSpec::Variable(_) => None,
|
||||
},
|
||||
|
||||
_ => None,
|
||||
};
|
||||
|
||||
|
@ -3705,10 +3689,7 @@ impl<'db> Type<'db> {
|
|||
db,
|
||||
[
|
||||
KnownClass::Str.to_instance(db),
|
||||
KnownClass::Tuple.to_specialized_instance(
|
||||
db,
|
||||
[KnownClass::Str.to_instance(db)],
|
||||
),
|
||||
TupleType::homogeneous(db, KnownClass::Str.to_instance(db)),
|
||||
],
|
||||
)),
|
||||
Parameter::positional_only(Some(Name::new_static("start")))
|
||||
|
@ -4022,10 +4003,10 @@ impl<'db> Type<'db> {
|
|||
Parameter::positional_only(Some(Name::new_static("name")))
|
||||
.with_annotated_type(str_instance),
|
||||
Parameter::positional_only(Some(Name::new_static("bases")))
|
||||
.with_annotated_type(
|
||||
KnownClass::Tuple
|
||||
.to_specialized_instance(db, [type_instance]),
|
||||
),
|
||||
.with_annotated_type(TupleType::homogeneous(
|
||||
db,
|
||||
type_instance,
|
||||
)),
|
||||
Parameter::positional_only(Some(Name::new_static("dict")))
|
||||
.with_annotated_type(
|
||||
KnownClass::Dict.to_specialized_instance(
|
||||
|
@ -4173,16 +4154,16 @@ impl<'db> Type<'db> {
|
|||
.with_annotated_type(Type::any())
|
||||
.type_form(),
|
||||
Parameter::keyword_only(Name::new_static("type_params"))
|
||||
.with_annotated_type(KnownClass::Tuple.to_specialized_instance(
|
||||
.with_annotated_type(TupleType::homogeneous(
|
||||
db,
|
||||
[UnionType::from_elements(
|
||||
UnionType::from_elements(
|
||||
db,
|
||||
[
|
||||
KnownClass::TypeVar.to_instance(db),
|
||||
KnownClass::ParamSpec.to_instance(db),
|
||||
KnownClass::TypeVarTuple.to_instance(db),
|
||||
],
|
||||
)],
|
||||
),
|
||||
))
|
||||
.with_default_type(TupleType::empty(db)),
|
||||
]),
|
||||
|
@ -4476,7 +4457,10 @@ impl<'db> Type<'db> {
|
|||
/// ```
|
||||
fn try_iterate(self, db: &'db dyn Db) -> Result<Type<'db>, IterationError<'db>> {
|
||||
if let Type::Tuple(tuple_type) = self {
|
||||
return Ok(UnionType::from_elements(db, tuple_type.elements(db)));
|
||||
return Ok(UnionType::from_elements(
|
||||
db,
|
||||
tuple_type.tuple(db).all_elements(),
|
||||
));
|
||||
}
|
||||
|
||||
if let Type::GenericAlias(alias) = self {
|
||||
|
@ -4639,20 +4623,20 @@ impl<'db> Type<'db> {
|
|||
// have the class's typevars still in the method signature when we attempt to call it. To
|
||||
// do this, we instead use the _identity_ specialization, which maps each of the class's
|
||||
// generic typevars to itself.
|
||||
let (generic_origin, generic_context, self_type) = match self {
|
||||
Type::ClassLiteral(class) => match class.generic_context(db) {
|
||||
Some(generic_context) => {
|
||||
let specialization = generic_context.identity_specialization(db);
|
||||
(
|
||||
let (generic_origin, generic_context, self_type) =
|
||||
match self {
|
||||
Type::ClassLiteral(class) => match class.generic_context(db) {
|
||||
Some(generic_context) => (
|
||||
Some(class),
|
||||
Some(generic_context),
|
||||
Type::GenericAlias(GenericAlias::new(db, class, specialization)),
|
||||
)
|
||||
}
|
||||
Type::from(class.apply_specialization(db, |_| {
|
||||
generic_context.identity_specialization(db)
|
||||
})),
|
||||
),
|
||||
_ => (None, None, self),
|
||||
},
|
||||
_ => (None, None, self),
|
||||
},
|
||||
_ => (None, None, self),
|
||||
};
|
||||
};
|
||||
|
||||
// As of now we do not model custom `__call__` on meta-classes, so the code below
|
||||
// only deals with interplay between `__new__` and `__init__` methods.
|
||||
|
@ -4775,11 +4759,7 @@ impl<'db> Type<'db> {
|
|||
.map(|specialization| {
|
||||
Type::instance(
|
||||
db,
|
||||
ClassType::Generic(GenericAlias::new(
|
||||
db,
|
||||
generic_origin,
|
||||
specialization,
|
||||
)),
|
||||
generic_origin.apply_specialization(db, |_| specialization),
|
||||
)
|
||||
})
|
||||
.unwrap_or(instance_ty);
|
||||
|
@ -4966,7 +4946,7 @@ impl<'db> Type<'db> {
|
|||
|
||||
// We treat `typing.Type` exactly the same as `builtins.type`:
|
||||
SpecialFormType::Type => Ok(KnownClass::Type.to_instance(db)),
|
||||
SpecialFormType::Tuple => Ok(KnownClass::Tuple.to_instance(db)),
|
||||
SpecialFormType::Tuple => Ok(TupleType::homogeneous(db, Type::unknown())),
|
||||
|
||||
// Legacy `typing` aliases
|
||||
SpecialFormType::List => Ok(KnownClass::List.to_instance(db)),
|
||||
|
@ -5189,7 +5169,10 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
Type::Callable(_) | Type::DataclassTransformer(_) => KnownClass::Type.to_instance(db),
|
||||
Type::ModuleLiteral(_) => KnownClass::ModuleType.to_class_literal(db),
|
||||
Type::Tuple(_) => KnownClass::Tuple.to_class_literal(db),
|
||||
Type::Tuple(tuple) => tuple
|
||||
.to_class_type(db)
|
||||
.map(Type::from)
|
||||
.unwrap_or_else(Type::unknown),
|
||||
|
||||
Type::TypeVar(typevar) => match typevar.bound_or_constraints(db) {
|
||||
None => KnownClass::Type.to_instance(db),
|
||||
|
@ -5343,12 +5326,7 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
builder.build()
|
||||
}
|
||||
Type::Tuple(tuple) => TupleType::from_elements(
|
||||
db,
|
||||
tuple
|
||||
.iter(db)
|
||||
.map(|ty| ty.apply_type_mapping(db, type_mapping)),
|
||||
),
|
||||
Type::Tuple(tuple) => Type::Tuple(tuple.apply_type_mapping(db, type_mapping)),
|
||||
|
||||
Type::TypeIs(type_is) => type_is.with_type(db, type_is.return_type(db).apply_type_mapping(db, type_mapping)),
|
||||
|
||||
|
@ -5439,10 +5417,9 @@ impl<'db> Type<'db> {
|
|||
negative.find_legacy_typevars(db, typevars);
|
||||
}
|
||||
}
|
||||
|
||||
Type::Tuple(tuple) => {
|
||||
for element in tuple.iter(db) {
|
||||
element.find_legacy_typevars(db, typevars);
|
||||
}
|
||||
tuple.find_legacy_typevars(db, typevars);
|
||||
}
|
||||
|
||||
Type::GenericAlias(alias) => {
|
||||
|
@ -8134,89 +8111,6 @@ impl<'db> BytesLiteralType<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
/// # Ordering
|
||||
/// Ordering is based on the tuple's salsa-assigned id and not on its elements.
|
||||
/// The id may change between runs, or when the tuple was garbage collected and recreated.
|
||||
#[salsa::interned(debug)]
|
||||
#[derive(PartialOrd, Ord)]
|
||||
pub struct TupleType<'db> {
|
||||
#[returns(deref)]
|
||||
elements: Box<[Type<'db>]>,
|
||||
}
|
||||
|
||||
impl<'db> TupleType<'db> {
|
||||
fn homogeneous_supertype(self, db: &'db dyn Db) -> Type<'db> {
|
||||
KnownClass::Tuple
|
||||
.to_specialized_instance(db, [UnionType::from_elements(db, self.elements(db))])
|
||||
}
|
||||
|
||||
pub(crate) fn empty(db: &'db dyn Db) -> Type<'db> {
|
||||
Type::Tuple(TupleType::new(db, Box::<[Type<'db>]>::from([])))
|
||||
}
|
||||
|
||||
pub(crate) fn from_elements<T: Into<Type<'db>>>(
|
||||
db: &'db dyn Db,
|
||||
types: impl IntoIterator<Item = T>,
|
||||
) -> Type<'db> {
|
||||
let mut elements = vec![];
|
||||
|
||||
for ty in types {
|
||||
let ty = ty.into();
|
||||
if ty.is_never() {
|
||||
return Type::Never;
|
||||
}
|
||||
elements.push(ty);
|
||||
}
|
||||
|
||||
Type::Tuple(Self::new(db, elements.into_boxed_slice()))
|
||||
}
|
||||
|
||||
/// Return a normalized version of `self`.
|
||||
///
|
||||
/// See [`Type::normalized`] for more details.
|
||||
#[must_use]
|
||||
pub(crate) fn normalized(self, db: &'db dyn Db) -> Self {
|
||||
let elements: Box<[Type<'db>]> = self
|
||||
.elements(db)
|
||||
.iter()
|
||||
.map(|ty| ty.normalized(db))
|
||||
.collect();
|
||||
TupleType::new(db, elements)
|
||||
}
|
||||
|
||||
pub(crate) fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
|
||||
let self_elements = self.elements(db);
|
||||
let other_elements = other.elements(db);
|
||||
self_elements.len() == other_elements.len()
|
||||
&& self_elements
|
||||
.iter()
|
||||
.zip(other_elements)
|
||||
.all(|(self_ty, other_ty)| self_ty.is_equivalent_to(db, *other_ty))
|
||||
}
|
||||
|
||||
pub(crate) fn is_gradual_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
|
||||
let self_elements = self.elements(db);
|
||||
let other_elements = other.elements(db);
|
||||
self_elements.len() == other_elements.len()
|
||||
&& self_elements
|
||||
.iter()
|
||||
.zip(other_elements)
|
||||
.all(|(self_ty, other_ty)| self_ty.is_gradual_equivalent_to(db, *other_ty))
|
||||
}
|
||||
|
||||
pub fn get(&self, db: &'db dyn Db, index: usize) -> Option<Type<'db>> {
|
||||
self.elements(db).get(index).copied()
|
||||
}
|
||||
|
||||
pub fn len(&self, db: &'db dyn Db) -> usize {
|
||||
self.elements(db).len()
|
||||
}
|
||||
|
||||
pub fn iter(&self, db: &'db dyn Db) -> impl Iterator<Item = Type<'db>> + 'db + '_ {
|
||||
self.elements(db).iter().copied()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) enum BoundSuperError<'db> {
|
||||
InvalidPivotClassType {
|
||||
|
|
|
@ -4,7 +4,8 @@ use std::ops::{Deref, DerefMut};
|
|||
use itertools::{Either, Itertools};
|
||||
|
||||
use crate::Db;
|
||||
use crate::types::{KnownClass, TupleType};
|
||||
use crate::types::KnownClass;
|
||||
use crate::types::tuple::{TupleSpec, TupleType};
|
||||
|
||||
use super::Type;
|
||||
|
||||
|
@ -210,11 +211,15 @@ fn expand_type<'db>(db: &'db dyn Db, ty: Type<'db>) -> Option<Vec<Type<'db>>> {
|
|||
Type::BooleanLiteral(false),
|
||||
])
|
||||
}
|
||||
Type::Tuple(tuple) => {
|
||||
Type::Tuple(tuple_type) => {
|
||||
// Note: This should only account for tuples of known length, i.e., `tuple[bool, ...]`
|
||||
// should not be expanded here.
|
||||
let tuple = tuple_type.tuple(db);
|
||||
if !matches!(tuple, TupleSpec::Fixed(_)) {
|
||||
return None;
|
||||
}
|
||||
let expanded = tuple
|
||||
.iter(db)
|
||||
.all_elements()
|
||||
.map(|element| {
|
||||
if let Some(expanded) = expand_type(db, element) {
|
||||
Either::Left(expanded.into_iter())
|
||||
|
@ -242,7 +247,8 @@ fn expand_type<'db>(db: &'db dyn Db, ty: Type<'db>) -> Option<Vec<Type<'db>>> {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::db::tests::setup_db;
|
||||
use crate::types::{KnownClass, TupleType, Type, UnionType};
|
||||
use crate::types::tuple::TupleType;
|
||||
use crate::types::{KnownClass, Type, UnionType};
|
||||
|
||||
use super::expand_type;
|
||||
|
||||
|
@ -308,7 +314,6 @@ mod tests {
|
|||
TupleType::from_elements(&db, [false_ty, bytes_ty]),
|
||||
];
|
||||
let expanded = expand_type(&db, tuple_type2).unwrap();
|
||||
assert_eq!(expanded.len(), expected_types.len());
|
||||
assert_eq!(expanded, expected_types);
|
||||
|
||||
// Mixed set of elements where some can be expanded while others cannot be.
|
||||
|
@ -328,7 +333,16 @@ mod tests {
|
|||
TupleType::from_elements(&db, [false_ty, int_ty, bytes_ty, str_ty]),
|
||||
];
|
||||
let expanded = expand_type(&db, tuple_type3).unwrap();
|
||||
assert_eq!(expanded.len(), expected_types.len());
|
||||
assert_eq!(expanded, expected_types);
|
||||
|
||||
// Variable-length tuples are not expanded.
|
||||
let variable_length_tuple = TupleType::mixed(
|
||||
&db,
|
||||
[bool_ty],
|
||||
int_ty,
|
||||
[UnionType::from_elements(&db, [str_ty, bytes_ty]), str_ty],
|
||||
);
|
||||
let expanded = expand_type(&db, variable_length_tuple);
|
||||
assert!(expanded.is_none());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,9 +27,10 @@ use crate::types::function::{
|
|||
};
|
||||
use crate::types::generics::{Specialization, SpecializationBuilder, SpecializationError};
|
||||
use crate::types::signatures::{Parameter, ParameterForm};
|
||||
use crate::types::tuple::TupleType;
|
||||
use crate::types::{
|
||||
BoundMethodType, ClassLiteral, DataclassParams, KnownClass, KnownInstanceType,
|
||||
MethodWrapperKind, PropertyInstanceType, SpecialFormType, TupleType, TypeMapping, UnionType,
|
||||
MethodWrapperKind, PropertyInstanceType, SpecialFormType, TypeMapping, UnionType,
|
||||
WrapperDescriptorKind, ide_support, todo_type,
|
||||
};
|
||||
use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, SubDiagnostic};
|
||||
|
|
|
@ -12,6 +12,7 @@ use crate::semantic_index::definition::{Definition, DefinitionState};
|
|||
use crate::types::function::{DataclassTransformerParams, KnownFunction};
|
||||
use crate::types::generics::{GenericContext, Specialization};
|
||||
use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signature};
|
||||
use crate::types::tuple::TupleType;
|
||||
use crate::types::{
|
||||
CallableType, DataclassParams, KnownInstanceType, TypeMapping, TypeRelation, TypeVarInstance,
|
||||
};
|
||||
|
@ -30,8 +31,8 @@ use crate::{
|
|||
place_table, semantic_index, use_def_map,
|
||||
},
|
||||
types::{
|
||||
CallArgumentTypes, CallError, CallErrorKind, MetaclassCandidate, TupleType, UnionBuilder,
|
||||
UnionType, definition_expression_type,
|
||||
CallArgumentTypes, CallError, CallErrorKind, MetaclassCandidate, UnionBuilder, UnionType,
|
||||
definition_expression_type,
|
||||
},
|
||||
};
|
||||
use indexmap::IndexSet;
|
||||
|
@ -203,6 +204,8 @@ impl<'db> GenericAlias<'db> {
|
|||
db: &'db dyn Db,
|
||||
typevars: &mut FxOrderSet<TypeVarInstance<'db>>,
|
||||
) {
|
||||
// A tuple's specialization will include all of its element types, so we don't need to also
|
||||
// look in `self.tuple`.
|
||||
self.specialization(db).find_legacy_typevars(db, typevars);
|
||||
}
|
||||
}
|
||||
|
@ -761,58 +764,46 @@ impl<'db> ClassLiteral<'db> {
|
|||
index.expect_single_definition(body_scope.node(db).expect_class(&module))
|
||||
}
|
||||
|
||||
pub(crate) fn apply_specialization(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
f: impl FnOnce(GenericContext<'db>) -> Specialization<'db>,
|
||||
) -> ClassType<'db> {
|
||||
match self.generic_context(db) {
|
||||
None => ClassType::NonGeneric(self),
|
||||
Some(generic_context) => {
|
||||
let specialization = f(generic_context);
|
||||
ClassType::Generic(GenericAlias::new(db, self, specialization))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn apply_optional_specialization(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
specialization: Option<Specialization<'db>>,
|
||||
) -> ClassType<'db> {
|
||||
match (self.generic_context(db), specialization) {
|
||||
(None, _) => ClassType::NonGeneric(self),
|
||||
(Some(generic_context), None) => {
|
||||
let specialization = generic_context.default_specialization(db);
|
||||
ClassType::Generic(GenericAlias::new(db, self, specialization))
|
||||
}
|
||||
(Some(_), Some(specialization)) => {
|
||||
ClassType::Generic(GenericAlias::new(db, self, specialization))
|
||||
}
|
||||
}
|
||||
self.apply_specialization(db, |generic_context| {
|
||||
specialization.unwrap_or_else(|| generic_context.default_specialization(db))
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the default specialization of this class. For non-generic classes, the class is
|
||||
/// returned unchanged. For a non-specialized generic class, we return a generic alias that
|
||||
/// applies the default specialization to the class's typevars.
|
||||
pub(crate) fn default_specialization(self, db: &'db dyn Db) -> ClassType<'db> {
|
||||
match self.generic_context(db) {
|
||||
None => ClassType::NonGeneric(self),
|
||||
Some(generic_context) => {
|
||||
let specialization = generic_context.default_specialization(db);
|
||||
ClassType::Generic(GenericAlias::new(db, self, specialization))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a specialization of this class with a `@Todo`-type
|
||||
pub(crate) fn todo_specialization(self, db: &'db dyn Db, todo: &'static str) -> ClassType<'db> {
|
||||
match self.generic_context(db) {
|
||||
None => ClassType::NonGeneric(self),
|
||||
Some(generic_context) => {
|
||||
let specialization = generic_context.todo_specialization(db, todo);
|
||||
ClassType::Generic(GenericAlias::new(db, self, specialization))
|
||||
}
|
||||
}
|
||||
self.apply_specialization(db, |generic_context| {
|
||||
generic_context.default_specialization(db)
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the unknown specialization of this class. For non-generic classes, the class is
|
||||
/// returned unchanged. For a non-specialized generic class, we return a generic alias that
|
||||
/// maps each of the class's typevars to `Unknown`.
|
||||
pub(crate) fn unknown_specialization(self, db: &'db dyn Db) -> ClassType<'db> {
|
||||
match self.generic_context(db) {
|
||||
None => ClassType::NonGeneric(self),
|
||||
Some(generic_context) => {
|
||||
let specialization = generic_context.unknown_specialization(db);
|
||||
ClassType::Generic(GenericAlias::new(db, self, specialization))
|
||||
}
|
||||
}
|
||||
self.apply_specialization(db, |generic_context| {
|
||||
generic_context.unknown_specialization(db)
|
||||
})
|
||||
}
|
||||
|
||||
/// Return an iterator over the inferred types of this class's *explicit* bases.
|
||||
|
@ -2448,22 +2439,20 @@ impl<'db> KnownClass {
|
|||
.unwrap_or_else(Type::unknown)
|
||||
}
|
||||
|
||||
/// Lookup a [`KnownClass`] in typeshed and return a [`Type`]
|
||||
/// representing all possible instances of the generic class with a specialization.
|
||||
/// Lookup a generic [`KnownClass`] in typeshed and return a [`Type`]
|
||||
/// representing a specialization of that class.
|
||||
///
|
||||
/// If the class cannot be found in typeshed, or if you provide a specialization with the wrong
|
||||
/// number of types, a debug-level log message will be emitted stating this.
|
||||
pub(crate) fn to_specialized_instance(
|
||||
pub(crate) fn to_specialized_class_type(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
specialization: impl IntoIterator<Item = Type<'db>>,
|
||||
) -> Type<'db> {
|
||||
) -> Option<ClassType<'db>> {
|
||||
let Type::ClassLiteral(class_literal) = self.to_class_literal(db) else {
|
||||
return Type::unknown();
|
||||
};
|
||||
let Some(generic_context) = class_literal.generic_context(db) else {
|
||||
return Type::instance(db, ClassType::NonGeneric(class_literal));
|
||||
return None;
|
||||
};
|
||||
let generic_context = class_literal.generic_context(db)?;
|
||||
|
||||
let types = specialization.into_iter().collect::<Box<[_]>>();
|
||||
if types.len() != generic_context.len(db) {
|
||||
|
@ -2477,21 +2466,32 @@ impl<'db> KnownClass {
|
|||
self.display(db)
|
||||
);
|
||||
}
|
||||
return Type::instance(db, class_literal.default_specialization(db));
|
||||
return Some(class_literal.default_specialization(db));
|
||||
}
|
||||
|
||||
let specialization = generic_context.specialize(db, types);
|
||||
Type::instance(
|
||||
db,
|
||||
ClassType::Generic(GenericAlias::new(db, class_literal, specialization)),
|
||||
)
|
||||
Some(class_literal.apply_specialization(db, |_| generic_context.specialize(db, types)))
|
||||
}
|
||||
|
||||
/// Lookup a [`KnownClass`] in typeshed and return a [`Type`]
|
||||
/// representing all possible instances of the generic class with a specialization.
|
||||
///
|
||||
/// If the class cannot be found in typeshed, or if you provide a specialization with the wrong
|
||||
/// number of types, a debug-level log message will be emitted stating this.
|
||||
pub(crate) fn to_specialized_instance(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
specialization: impl IntoIterator<Item = Type<'db>>,
|
||||
) -> Type<'db> {
|
||||
self.to_specialized_class_type(db, specialization)
|
||||
.and_then(|class_type| Type::from(class_type).to_instance(db))
|
||||
.unwrap_or_else(Type::unknown)
|
||||
}
|
||||
|
||||
/// Attempt to lookup a [`KnownClass`] in typeshed and return a [`Type`] representing that class-literal.
|
||||
///
|
||||
/// Return an error if the symbol cannot be found in the expected typeshed module,
|
||||
/// or if the symbol is not a class definition, or if the symbol is possibly unbound.
|
||||
pub(crate) fn try_to_class_literal(
|
||||
fn try_to_class_literal_without_logging(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
) -> Result<ClassLiteral<'db>, KnownClassLookupError<'db>> {
|
||||
|
@ -2511,14 +2511,13 @@ impl<'db> KnownClass {
|
|||
/// Lookup a [`KnownClass`] in typeshed and return a [`Type`] representing that class-literal.
|
||||
///
|
||||
/// If the class cannot be found in typeshed, a debug-level log message will be emitted stating this.
|
||||
pub(crate) fn to_class_literal(self, db: &'db dyn Db) -> Type<'db> {
|
||||
pub(crate) fn try_to_class_literal(self, db: &'db dyn Db) -> Option<ClassLiteral<'db>> {
|
||||
// a cache of the `KnownClass`es that we have already failed to lookup in typeshed
|
||||
// (and therefore that we've already logged a warning for)
|
||||
static MESSAGES: LazyLock<Mutex<FxHashSet<KnownClass>>> = LazyLock::new(Mutex::default);
|
||||
|
||||
self.try_to_class_literal(db)
|
||||
.map(Type::ClassLiteral)
|
||||
.unwrap_or_else(|lookup_error| {
|
||||
self.try_to_class_literal_without_logging(db)
|
||||
.or_else(|lookup_error| {
|
||||
if MESSAGES.lock().unwrap().insert(self) {
|
||||
if matches!(
|
||||
lookup_error,
|
||||
|
@ -2535,12 +2534,22 @@ impl<'db> KnownClass {
|
|||
|
||||
match lookup_error {
|
||||
KnownClassLookupError::ClassPossiblyUnbound { class_literal, .. } => {
|
||||
class_literal.into()
|
||||
Ok(class_literal)
|
||||
}
|
||||
KnownClassLookupError::ClassNotFound { .. }
|
||||
| KnownClassLookupError::SymbolNotAClass { .. } => Type::unknown(),
|
||||
| KnownClassLookupError::SymbolNotAClass { .. } => Err(()),
|
||||
}
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
|
||||
/// Lookup a [`KnownClass`] in typeshed and return a [`Type`] representing that class-literal.
|
||||
///
|
||||
/// If the class cannot be found in typeshed, a debug-level log message will be emitted stating this.
|
||||
pub(crate) fn to_class_literal(self, db: &'db dyn Db) -> Type<'db> {
|
||||
self.try_to_class_literal(db)
|
||||
.map(Type::ClassLiteral)
|
||||
.unwrap_or_else(Type::unknown)
|
||||
}
|
||||
|
||||
/// Lookup a [`KnownClass`] in typeshed and return a [`Type`]
|
||||
|
@ -2557,7 +2566,7 @@ impl<'db> KnownClass {
|
|||
/// Return `true` if this symbol can be resolved to a class definition `class` in typeshed,
|
||||
/// *and* `class` is a subclass of `other`.
|
||||
pub(super) fn is_subclass_of(self, db: &'db dyn Db, other: ClassType<'db>) -> bool {
|
||||
self.try_to_class_literal(db)
|
||||
self.try_to_class_literal_without_logging(db)
|
||||
.is_ok_and(|class| class.is_subclass_of(db, None, other))
|
||||
}
|
||||
|
||||
|
|
|
@ -68,6 +68,7 @@ impl<'db> ClassBase<'db> {
|
|||
if literal.is_known(db, KnownClass::Any) {
|
||||
Some(Self::Dynamic(DynamicType::Any))
|
||||
} else if literal.is_known(db, KnownClass::NamedTuple) {
|
||||
// TODO: Figure out the tuple spec for the named tuple
|
||||
Self::try_from_type(db, KnownClass::Tuple.to_class_literal(db))
|
||||
} else {
|
||||
Some(Self::Class(literal.default_specialization(db)))
|
||||
|
|
|
@ -14,6 +14,7 @@ use crate::types::string_annotation::{
|
|||
IMPLICIT_CONCATENATED_STRING_TYPE_ANNOTATION, INVALID_SYNTAX_IN_FORWARD_ANNOTATION,
|
||||
RAW_STRING_TYPE_ANNOTATION,
|
||||
};
|
||||
use crate::types::tuple::TupleType;
|
||||
use crate::types::{SpecialFormType, Type, protocol_class::ProtocolClassLiteral};
|
||||
use crate::{Db, Module, ModuleName, Program, declare_lint};
|
||||
use itertools::Itertools;
|
||||
|
@ -1606,7 +1607,7 @@ pub(super) fn report_index_out_of_bounds(
|
|||
kind: &'static str,
|
||||
node: AnyNodeRef,
|
||||
tuple_ty: Type,
|
||||
length: usize,
|
||||
length: impl std::fmt::Display,
|
||||
index: i64,
|
||||
) {
|
||||
let Some(builder) = context.report_lint(&INDEX_OUT_OF_BOUNDS, node) else {
|
||||
|
@ -2120,7 +2121,7 @@ pub(crate) fn report_invalid_or_unsupported_base(
|
|||
return;
|
||||
}
|
||||
|
||||
let tuple_of_types = KnownClass::Tuple.to_specialized_instance(db, [instance_of_type]);
|
||||
let tuple_of_types = TupleType::homogeneous(db, instance_of_type);
|
||||
|
||||
let explain_mro_entries = |diagnostic: &mut LintDiagnosticGuard| {
|
||||
diagnostic.info(
|
||||
|
|
|
@ -10,6 +10,7 @@ use crate::types::class::{ClassLiteral, ClassType, GenericAlias};
|
|||
use crate::types::function::{FunctionType, OverloadLiteral};
|
||||
use crate::types::generics::{GenericContext, Specialization};
|
||||
use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signature};
|
||||
use crate::types::tuple::TupleSpec;
|
||||
use crate::types::{
|
||||
CallableType, IntersectionType, KnownClass, MethodWrapperKind, Protocol, StringLiteralType,
|
||||
SubclassOfInner, Type, TypeVarBoundOrConstraints, TypeVarInstance, UnionType,
|
||||
|
@ -190,16 +191,7 @@ impl Display for DisplayRepresentation<'_> {
|
|||
|
||||
escape.bytes_repr(TripleQuotes::No).write(f)
|
||||
}
|
||||
Type::Tuple(tuple) => {
|
||||
f.write_str("tuple[")?;
|
||||
let elements = tuple.elements(self.db);
|
||||
if elements.is_empty() {
|
||||
f.write_str("()")?;
|
||||
} else {
|
||||
elements.display(self.db).fmt(f)?;
|
||||
}
|
||||
f.write_str("]")
|
||||
}
|
||||
Type::Tuple(specialization) => specialization.tuple(self.db).display(self.db).fmt(f),
|
||||
Type::TypeVar(typevar) => f.write_str(typevar.name(self.db)),
|
||||
Type::AlwaysTruthy => f.write_str("AlwaysTruthy"),
|
||||
Type::AlwaysFalsy => f.write_str("AlwaysFalsy"),
|
||||
|
@ -224,6 +216,67 @@ impl Display for DisplayRepresentation<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'db> TupleSpec<'db> {
|
||||
pub(crate) fn display(&'db self, db: &'db dyn Db) -> DisplayTuple<'db> {
|
||||
DisplayTuple { tuple: self, db }
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct DisplayTuple<'db> {
|
||||
tuple: &'db TupleSpec<'db>,
|
||||
db: &'db dyn Db,
|
||||
}
|
||||
|
||||
impl Display for DisplayTuple<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
f.write_str("tuple[")?;
|
||||
match self.tuple {
|
||||
TupleSpec::Fixed(tuple) => {
|
||||
let elements = tuple.elements_slice();
|
||||
if elements.is_empty() {
|
||||
f.write_str("()")?;
|
||||
} else {
|
||||
elements.display(self.db).fmt(f)?;
|
||||
}
|
||||
}
|
||||
|
||||
// Decoder key for which snippets of text need to be included depending on whether
|
||||
// the tuple contains a prefix and/or suffix:
|
||||
//
|
||||
// tuple[ yyy, ... ]
|
||||
// tuple[xxx, *tuple[yyy, ...] ]
|
||||
// tuple[xxx, *tuple[yyy, ...], zzz]
|
||||
// tuple[ *tuple[yyy, ...], zzz]
|
||||
// PPPPPPPPPPPP P
|
||||
// SSSSSSS SSSSSS
|
||||
//
|
||||
// (Anything that appears above only a P is included only if there's a prefix; anything
|
||||
// above only an S is included only if there's a suffix; anything about both a P and an
|
||||
// S is included if there is either a prefix or a suffix. The initial `tuple[` and
|
||||
// trailing `]` are printed elsewhere. The `yyy, ...` is printed no matter what.)
|
||||
TupleSpec::Variable(tuple) => {
|
||||
if !tuple.prefix.is_empty() {
|
||||
tuple.prefix.display(self.db).fmt(f)?;
|
||||
f.write_str(", ")?;
|
||||
}
|
||||
if !tuple.prefix.is_empty() || !tuple.suffix.is_empty() {
|
||||
f.write_str("*tuple[")?;
|
||||
}
|
||||
tuple.variable.display(self.db).fmt(f)?;
|
||||
f.write_str(", ...")?;
|
||||
if !tuple.prefix.is_empty() || !tuple.suffix.is_empty() {
|
||||
f.write_str("]")?;
|
||||
}
|
||||
if !tuple.suffix.is_empty() {
|
||||
f.write_str(", ")?;
|
||||
tuple.suffix.display(self.db).fmt(f)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
f.write_str("]")
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> OverloadLiteral<'db> {
|
||||
// Not currently used, but useful for debugging.
|
||||
#[expect(dead_code)]
|
||||
|
@ -307,15 +360,19 @@ pub(crate) struct DisplayGenericAlias<'db> {
|
|||
|
||||
impl Display for DisplayGenericAlias<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{origin}{specialization}",
|
||||
origin = self.origin.name(self.db),
|
||||
specialization = self.specialization.display_short(
|
||||
self.db,
|
||||
TupleSpecialization::from_class(self.db, self.origin)
|
||||
),
|
||||
)
|
||||
if self.origin.is_known(self.db, KnownClass::Tuple) {
|
||||
self.specialization.tuple(self.db).display(self.db).fmt(f)
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
"{origin}{specialization}",
|
||||
origin = self.origin.name(self.db),
|
||||
specialization = self.specialization.display_short(
|
||||
self.db,
|
||||
TupleSpecialization::from_class(self.db, self.origin)
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,9 +8,10 @@ use crate::types::class::ClassType;
|
|||
use crate::types::class_base::ClassBase;
|
||||
use crate::types::instance::{NominalInstanceType, Protocol, ProtocolInstanceType};
|
||||
use crate::types::signatures::{Parameter, Parameters, Signature};
|
||||
use crate::types::tuple::{TupleSpec, TupleType};
|
||||
use crate::types::{
|
||||
KnownInstanceType, Type, TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarInstance,
|
||||
TypeVarVariance, UnionType, declaration_type, todo_type,
|
||||
TypeVarVariance, UnionType, declaration_type,
|
||||
};
|
||||
use crate::{Db, FxOrderSet};
|
||||
|
||||
|
@ -143,20 +144,6 @@ impl<'db> GenericContext<'db> {
|
|||
self.specialize_partial(db, &vec![None; self.variables(db).len()])
|
||||
}
|
||||
|
||||
#[allow(unused_variables)] // Only unused in release builds
|
||||
pub(crate) fn todo_specialization(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
todo: &'static str,
|
||||
) -> Specialization<'db> {
|
||||
let types = self
|
||||
.variables(db)
|
||||
.iter()
|
||||
.map(|typevar| typevar.default_ty(db).unwrap_or(todo_type!(todo)))
|
||||
.collect();
|
||||
self.specialize(db, types)
|
||||
}
|
||||
|
||||
pub(crate) fn identity_specialization(self, db: &'db dyn Db) -> Specialization<'db> {
|
||||
let types = self
|
||||
.variables(db)
|
||||
|
@ -185,7 +172,17 @@ impl<'db> GenericContext<'db> {
|
|||
types: Box<[Type<'db>]>,
|
||||
) -> Specialization<'db> {
|
||||
assert!(self.variables(db).len() == types.len());
|
||||
Specialization::new(db, self, types)
|
||||
Specialization::new(db, self, types, None)
|
||||
}
|
||||
|
||||
/// Creates a specialization of this generic context for the `tuple` class.
|
||||
pub(crate) fn specialize_tuple(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
tuple: TupleType<'db>,
|
||||
) -> Specialization<'db> {
|
||||
let element_type = UnionType::from_elements(db, tuple.tuple(db).all_elements());
|
||||
Specialization::new(db, self, Box::from([element_type]), Some(tuple))
|
||||
}
|
||||
|
||||
/// Creates a specialization of this generic context. Panics if the length of `types` does not
|
||||
|
@ -230,7 +227,7 @@ impl<'db> GenericContext<'db> {
|
|||
expanded[idx] = default;
|
||||
}
|
||||
|
||||
Specialization::new(db, self, expanded.into_boxed_slice())
|
||||
Specialization::new(db, self, expanded.into_boxed_slice(), None)
|
||||
}
|
||||
|
||||
pub(crate) fn normalized(self, db: &'db dyn Db) -> Self {
|
||||
|
@ -273,9 +270,24 @@ pub struct Specialization<'db> {
|
|||
pub(crate) generic_context: GenericContext<'db>,
|
||||
#[returns(deref)]
|
||||
pub(crate) types: Box<[Type<'db>]>,
|
||||
|
||||
/// For specializations of `tuple`, we also store more detailed information about the tuple's
|
||||
/// elements, above what the class's (single) typevar can represent.
|
||||
tuple_inner: Option<TupleType<'db>>,
|
||||
}
|
||||
|
||||
impl<'db> Specialization<'db> {
|
||||
/// Returns the tuple spec for a specialization of the `tuple` class.
|
||||
pub(crate) fn tuple(self, db: &'db dyn Db) -> &'db TupleSpec<'db> {
|
||||
if let Some(tuple) = self.tuple_inner(db).map(|tuple_type| tuple_type.tuple(db)) {
|
||||
return tuple;
|
||||
}
|
||||
if let [element_type] = self.types(db) {
|
||||
return TupleType::new(db, TupleSpec::homogeneous(*element_type)).tuple(db);
|
||||
}
|
||||
TupleType::new(db, TupleSpec::homogeneous(Type::unknown())).tuple(db)
|
||||
}
|
||||
|
||||
/// Returns the type that a typevar is mapped to, or None if the typevar isn't part of this
|
||||
/// mapping.
|
||||
pub(crate) fn get(self, db: &'db dyn Db, typevar: TypeVarInstance<'db>) -> Option<Type<'db>> {
|
||||
|
@ -313,7 +325,10 @@ impl<'db> Specialization<'db> {
|
|||
.iter()
|
||||
.map(|ty| ty.apply_type_mapping(db, type_mapping))
|
||||
.collect();
|
||||
Specialization::new(db, self.generic_context(db), types)
|
||||
let tuple_inner = self
|
||||
.tuple_inner(db)
|
||||
.map(|tuple| tuple.apply_type_mapping(db, type_mapping));
|
||||
Specialization::new(db, self.generic_context(db), types, tuple_inner)
|
||||
}
|
||||
|
||||
/// Applies an optional specialization to this specialization.
|
||||
|
@ -350,12 +365,14 @@ impl<'db> Specialization<'db> {
|
|||
_ => UnionType::from_elements(db, [self_type, other_type]),
|
||||
})
|
||||
.collect();
|
||||
Specialization::new(db, self.generic_context(db), types)
|
||||
// TODO: Combine the tuple specs too
|
||||
Specialization::new(db, self.generic_context(db), types, None)
|
||||
}
|
||||
|
||||
pub(crate) fn normalized(self, db: &'db dyn Db) -> Self {
|
||||
let types: Box<[_]> = self.types(db).iter().map(|ty| ty.normalized(db)).collect();
|
||||
Self::new(db, self.generic_context(db), types)
|
||||
let tuple_inner = self.tuple_inner(db).map(|tuple| tuple.normalized(db));
|
||||
Self::new(db, self.generic_context(db), types, tuple_inner)
|
||||
}
|
||||
|
||||
pub(super) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
||||
|
@ -374,7 +391,11 @@ impl<'db> Specialization<'db> {
|
|||
vartype.materialize(db, variance)
|
||||
})
|
||||
.collect();
|
||||
Specialization::new(db, self.generic_context(db), types)
|
||||
let tuple_inner = self.tuple_inner(db).map(|tuple| {
|
||||
// Tuples are immutable, so tuple element types are always in covariant position.
|
||||
tuple.materialize(db, variance)
|
||||
});
|
||||
Specialization::new(db, self.generic_context(db), types, tuple_inner)
|
||||
}
|
||||
|
||||
pub(crate) fn has_relation_to(
|
||||
|
@ -388,6 +409,11 @@ impl<'db> Specialization<'db> {
|
|||
return false;
|
||||
}
|
||||
|
||||
if let (Some(self_tuple), Some(other_tuple)) = (self.tuple_inner(db), other.tuple_inner(db))
|
||||
{
|
||||
return self_tuple.has_relation_to(db, other_tuple, relation);
|
||||
}
|
||||
|
||||
for ((typevar, self_type), other_type) in (generic_context.variables(db).into_iter())
|
||||
.zip(self.types(db))
|
||||
.zip(other.types(db))
|
||||
|
@ -570,7 +596,8 @@ impl<'db> SpecializationBuilder<'db> {
|
|||
.unwrap_or(variable.default_ty(self.db).unwrap_or(Type::unknown()))
|
||||
})
|
||||
.collect();
|
||||
Specialization::new(self.db, generic_context, types)
|
||||
// TODO Infer the tuple spec for a tuple type
|
||||
Specialization::new(self.db, generic_context, types, None)
|
||||
}
|
||||
|
||||
fn add_type_mapping(&mut self, typevar: TypeVarInstance<'db>, ty: Type<'db>) {
|
||||
|
@ -641,14 +668,19 @@ impl<'db> SpecializationBuilder<'db> {
|
|||
}
|
||||
|
||||
(Type::Tuple(formal_tuple), Type::Tuple(actual_tuple)) => {
|
||||
let formal_elements = formal_tuple.elements(self.db);
|
||||
let actual_elements = actual_tuple.elements(self.db);
|
||||
if formal_elements.len() == actual_elements.len() {
|
||||
for (formal_element, actual_element) in
|
||||
formal_elements.iter().zip(actual_elements)
|
||||
{
|
||||
self.infer(*formal_element, *actual_element)?;
|
||||
let formal_tuple = formal_tuple.tuple(self.db);
|
||||
let actual_tuple = actual_tuple.tuple(self.db);
|
||||
match (formal_tuple, actual_tuple) {
|
||||
(TupleSpec::Fixed(formal_tuple), TupleSpec::Fixed(actual_tuple)) => {
|
||||
if formal_tuple.len() == actual_tuple.len() {
|
||||
for (formal_element, actual_element) in formal_tuple.elements().zip(actual_tuple.elements()) {
|
||||
self.infer(formal_element, actual_element)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Infer specializations of variable-length tuples
|
||||
(TupleSpec::Variable(_), _) | (_, TupleSpec::Variable(_)) => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -117,11 +117,16 @@ impl AllMembers {
|
|||
| Type::KnownInstance(_)
|
||||
| Type::TypeVar(_)
|
||||
| Type::BoundSuper(_)
|
||||
| Type::TypeIs(_) => {
|
||||
if let Type::ClassLiteral(class_literal) = ty.to_meta_type(db) {
|
||||
| Type::TypeIs(_) => match ty.to_meta_type(db) {
|
||||
Type::ClassLiteral(class_literal) => {
|
||||
self.extend_with_class_members(db, class_literal);
|
||||
}
|
||||
}
|
||||
Type::GenericAlias(generic_alias) => {
|
||||
let class_literal = generic_alias.origin(db);
|
||||
self.extend_with_class_members(db, class_literal);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
|
||||
Type::ModuleLiteral(literal) => {
|
||||
self.extend_with_type(db, KnownClass::ModuleType.to_instance(db));
|
||||
|
|
|
@ -95,15 +95,16 @@ use crate::types::function::{
|
|||
use crate::types::generics::GenericContext;
|
||||
use crate::types::mro::MroErrorKind;
|
||||
use crate::types::signatures::{CallableSignature, Signature};
|
||||
use crate::types::tuple::{TupleSpec, TupleType};
|
||||
use crate::types::unpacker::{UnpackResult, Unpacker};
|
||||
use crate::types::{
|
||||
BareTypeAliasType, CallDunderError, CallableType, ClassLiteral, ClassType, DataclassParams,
|
||||
DynamicType, GenericAlias, IntersectionBuilder, IntersectionType, KnownClass,
|
||||
KnownInstanceType, LintDiagnosticGuard, MemberLookupPolicy, MetaclassCandidate,
|
||||
PEP695TypeAliasType, Parameter, ParameterForm, Parameters, SpecialFormType, StringLiteralType,
|
||||
SubclassOfType, Truthiness, TupleType, Type, TypeAliasType, TypeAndQualifiers,
|
||||
TypeArrayDisplay, TypeIsType, TypeQualifiers, TypeVarBoundOrConstraints, TypeVarInstance,
|
||||
TypeVarKind, TypeVarVariance, UnionBuilder, UnionType, binding_type, todo_type,
|
||||
DynamicType, IntersectionBuilder, IntersectionType, KnownClass, KnownInstanceType,
|
||||
LintDiagnosticGuard, MemberLookupPolicy, MetaclassCandidate, PEP695TypeAliasType, Parameter,
|
||||
ParameterForm, Parameters, SpecialFormType, StringLiteralType, SubclassOfType, Truthiness,
|
||||
Type, TypeAliasType, TypeAndQualifiers, TypeArrayDisplay, TypeIsType, TypeQualifiers,
|
||||
TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind, TypeVarVariance, UnionBuilder,
|
||||
UnionType, binding_type, todo_type,
|
||||
};
|
||||
use crate::unpack::{Unpack, UnpackPosition};
|
||||
use crate::util::subscript::{PyIndex, PySlice};
|
||||
|
@ -2443,7 +2444,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
todo_type!("PEP 646")
|
||||
} else {
|
||||
let annotated_type = self.file_expression_type(annotation);
|
||||
KnownClass::Tuple.to_specialized_instance(self.db(), [annotated_type])
|
||||
TupleType::homogeneous(self.db(), annotated_type)
|
||||
};
|
||||
|
||||
self.add_declaration_with_binding(
|
||||
|
@ -2455,7 +2456,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
self.add_binding(
|
||||
parameter.into(),
|
||||
definition,
|
||||
KnownClass::Tuple.to_specialized_instance(self.db(), [Type::unknown()]),
|
||||
TupleType::homogeneous(self.db(), Type::unknown()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -2832,7 +2833,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
// it will actually be the type of the generic parameters to `BaseExceptionGroup` or `ExceptionGroup`.
|
||||
let symbol_ty = if let Type::Tuple(tuple) = node_ty {
|
||||
let mut builder = UnionBuilder::new(self.db());
|
||||
for element in tuple.elements(self.db()).iter().copied() {
|
||||
for element in tuple.tuple(self.db()).all_elements() {
|
||||
builder = builder.add(
|
||||
if element.is_assignable_to(self.db(), type_base_exception) {
|
||||
element.to_instance(self.db()).expect(
|
||||
|
@ -2855,7 +2856,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
)
|
||||
} else if node_ty.is_assignable_to(
|
||||
self.db(),
|
||||
KnownClass::Tuple.to_specialized_instance(self.db(), [type_base_exception]),
|
||||
TupleType::homogeneous(self.db(), type_base_exception),
|
||||
) {
|
||||
extract_tuple_specialization(self.db(), node_ty)
|
||||
.unwrap_or_else(|| KnownClass::BaseException.to_instance(self.db()))
|
||||
|
@ -2865,7 +2866,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
self.db(),
|
||||
[
|
||||
type_base_exception,
|
||||
KnownClass::Tuple.to_specialized_instance(self.db(), [type_base_exception]),
|
||||
TupleType::homogeneous(self.db(), type_base_exception),
|
||||
],
|
||||
),
|
||||
) {
|
||||
|
@ -3698,9 +3699,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
ast::Expr::List(ast::ExprList { elts, .. })
|
||||
| ast::Expr::Tuple(ast::ExprTuple { elts, .. }) => {
|
||||
let mut assigned_tys = match assigned_ty {
|
||||
Some(Type::Tuple(tuple)) => {
|
||||
Either::Left(tuple.elements(self.db()).iter().copied())
|
||||
}
|
||||
Some(Type::Tuple(tuple)) => Either::Left(tuple.tuple(self.db()).all_elements()),
|
||||
Some(_) | None => Either::Right(std::iter::empty()),
|
||||
};
|
||||
|
||||
|
@ -6940,6 +6939,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
(Type::BooleanLiteral(b1), Type::BooleanLiteral(b2), ast::Operator::BitXor) => {
|
||||
Some(Type::BooleanLiteral(b1 ^ b2))
|
||||
}
|
||||
|
||||
(Type::BooleanLiteral(b1), Type::BooleanLiteral(_) | Type::IntLiteral(_), op) => self
|
||||
.infer_binary_expression_type(
|
||||
node,
|
||||
|
@ -6956,19 +6956,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
Type::IntLiteral(i64::from(b2)),
|
||||
op,
|
||||
),
|
||||
(Type::Tuple(lhs), Type::Tuple(rhs), ast::Operator::Add) => {
|
||||
// Note: this only works on heterogeneous tuples.
|
||||
let lhs_elements = lhs.elements(self.db());
|
||||
let rhs_elements = rhs.elements(self.db());
|
||||
|
||||
Some(TupleType::from_elements(
|
||||
(Type::Tuple(lhs), Type::Tuple(rhs), ast::Operator::Add) => Some(Type::tuple(
|
||||
self.db(),
|
||||
TupleType::new(
|
||||
self.db(),
|
||||
lhs_elements
|
||||
.iter()
|
||||
.copied()
|
||||
.chain(rhs_elements.iter().copied()),
|
||||
))
|
||||
}
|
||||
lhs.tuple(self.db()).concat(self.db(), rhs.tuple(self.db())),
|
||||
),
|
||||
)),
|
||||
|
||||
// We've handled all of the special cases that we support for literals, so we need to
|
||||
// fall back on looking for dunder methods on one of the operand types.
|
||||
|
@ -7425,19 +7420,20 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
// tuples.
|
||||
//
|
||||
// Ref: https://github.com/astral-sh/ruff/pull/18251#discussion_r2115909311
|
||||
if tuple.len(self.db()) > 1 << 12 {
|
||||
let (minimum_length, _) = tuple.tuple(self.db()).size_hint();
|
||||
if minimum_length > 1 << 12 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut definitely_true = false;
|
||||
let mut definitely_false = true;
|
||||
for element in tuple.elements(self.db()) {
|
||||
for element in tuple.tuple(self.db()).all_elements() {
|
||||
if element.is_string_literal() {
|
||||
if literal == *element {
|
||||
if literal == element {
|
||||
definitely_true = true;
|
||||
definitely_false = false;
|
||||
}
|
||||
} else if !literal.is_disjoint_from(self.db(), *element) {
|
||||
} else if !literal.is_disjoint_from(self.db(), element) {
|
||||
definitely_false = false;
|
||||
}
|
||||
}
|
||||
|
@ -7697,12 +7693,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
)
|
||||
}
|
||||
(Type::Tuple(lhs), Type::Tuple(rhs)) => {
|
||||
// Note: This only works on heterogeneous tuple types.
|
||||
let lhs_elements = lhs.elements(self.db());
|
||||
let rhs_elements = rhs.elements(self.db());
|
||||
let lhs_tuple = lhs.tuple(self.db());
|
||||
let rhs_tuple = rhs.tuple(self.db());
|
||||
|
||||
let mut tuple_rich_comparison =
|
||||
|op| self.infer_tuple_rich_comparison(lhs_elements, op, rhs_elements, range);
|
||||
|op| self.infer_tuple_rich_comparison(lhs_tuple, op, rhs_tuple, range);
|
||||
|
||||
match op {
|
||||
ast::CmpOp::Eq => tuple_rich_comparison(RichCompareOperator::Eq),
|
||||
|
@ -7712,14 +7707,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
ast::CmpOp::Gt => tuple_rich_comparison(RichCompareOperator::Gt),
|
||||
ast::CmpOp::GtE => tuple_rich_comparison(RichCompareOperator::Ge),
|
||||
ast::CmpOp::In | ast::CmpOp::NotIn => {
|
||||
let mut eq_count = 0usize;
|
||||
let mut not_eq_count = 0usize;
|
||||
let mut any_eq = false;
|
||||
let mut any_ambiguous = false;
|
||||
|
||||
for ty in rhs_elements {
|
||||
for ty in rhs_tuple.all_elements() {
|
||||
let eq_result = self.infer_binary_type_comparison(
|
||||
Type::Tuple(lhs),
|
||||
ast::CmpOp::Eq,
|
||||
*ty,
|
||||
ty,
|
||||
range,
|
||||
).expect("infer_binary_type_comparison should never return None for `CmpOp::Eq`");
|
||||
|
||||
|
@ -7729,16 +7724,16 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
// for different union variants. Instead, this is just for us to
|
||||
// evaluate a possibly truthy value to `false` or `true`.
|
||||
ty => match ty.bool(self.db()) {
|
||||
Truthiness::AlwaysTrue => eq_count += 1,
|
||||
Truthiness::AlwaysFalse => not_eq_count += 1,
|
||||
Truthiness::Ambiguous => (),
|
||||
Truthiness::AlwaysTrue => any_eq = true,
|
||||
Truthiness::AlwaysFalse => (),
|
||||
Truthiness::Ambiguous => any_ambiguous = true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if eq_count >= 1 {
|
||||
if any_eq {
|
||||
Ok(Type::BooleanLiteral(op.is_in()))
|
||||
} else if not_eq_count == rhs_elements.len() {
|
||||
} else if !any_ambiguous {
|
||||
Ok(Type::BooleanLiteral(op.is_not_in()))
|
||||
} else {
|
||||
Ok(KnownClass::Bool.to_instance(self.db()))
|
||||
|
@ -7914,13 +7909,21 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
/// see `<https://github.com/python/cpython/blob/9d6366b60d01305fc5e45100e0cd13e358aa397d/Objects/tupleobject.c#L637>`
|
||||
fn infer_tuple_rich_comparison(
|
||||
&mut self,
|
||||
left: &[Type<'db>],
|
||||
left: &TupleSpec<'db>,
|
||||
op: RichCompareOperator,
|
||||
right: &[Type<'db>],
|
||||
right: &TupleSpec<'db>,
|
||||
range: TextRange,
|
||||
) -> Result<Type<'db>, CompareUnsupportedError<'db>> {
|
||||
let left_iter = left.iter().copied();
|
||||
let right_iter = right.iter().copied();
|
||||
// If either tuple is variable length, we can make no assumptions about the relative
|
||||
// lengths of the tuples, and therefore neither about how they compare lexicographically.
|
||||
// TODO: Consider comparing the prefixes of the tuples, since that could give a comparison
|
||||
// result regardless of how long the variable-length tuple is.
|
||||
let (TupleSpec::Fixed(left), TupleSpec::Fixed(right)) = (left, right) else {
|
||||
return Ok(Type::unknown());
|
||||
};
|
||||
|
||||
let left_iter = left.elements();
|
||||
let right_iter = right.elements();
|
||||
|
||||
let mut builder = UnionBuilder::new(self.db());
|
||||
|
||||
|
@ -8052,11 +8055,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
// special cases, too.
|
||||
if let Type::ClassLiteral(class) = value_ty {
|
||||
if class.is_known(self.db(), KnownClass::Tuple) {
|
||||
self.infer_expression(slice);
|
||||
// TODO heterogeneous and homogeneous tuples in value expressions
|
||||
return Type::from(
|
||||
class.todo_specialization(self.db(), "Generic tuple specializations"),
|
||||
);
|
||||
return self
|
||||
.infer_tuple_type_expression(slice)
|
||||
.to_meta_type(self.db());
|
||||
}
|
||||
if let Some(generic_context) = class.generic_context(self.db()) {
|
||||
return self.infer_explicit_class_specialization(
|
||||
|
@ -8067,6 +8068,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
);
|
||||
}
|
||||
}
|
||||
if let Type::SpecialForm(SpecialFormType::Tuple) = value_ty {
|
||||
return self
|
||||
.infer_tuple_type_expression(slice)
|
||||
.to_meta_type(self.db());
|
||||
}
|
||||
|
||||
let slice_ty = self.infer_expression(slice);
|
||||
let result_ty = self.infer_subscript_expression_types(value, value_ty, slice_ty);
|
||||
|
@ -8113,9 +8119,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
.matching_overloads()
|
||||
.next()
|
||||
.expect("valid bindings should have matching overload");
|
||||
let specialization =
|
||||
generic_context.specialize_partial(self.db(), overload.parameter_types());
|
||||
Type::from(GenericAlias::new(self.db(), generic_class, specialization))
|
||||
Type::from(generic_class.apply_specialization(self.db(), |_| {
|
||||
generic_context.specialize_partial(self.db(), overload.parameter_types())
|
||||
}))
|
||||
}
|
||||
|
||||
fn infer_subscript_expression_types(
|
||||
|
@ -8137,18 +8143,19 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
|
||||
// Ex) Given `("a", "b", "c", "d")[1]`, return `"b"`
|
||||
(Type::Tuple(tuple_ty), Type::IntLiteral(int), _) if i32::try_from(int).is_ok() => {
|
||||
let elements = tuple_ty.elements(self.db());
|
||||
elements
|
||||
.iter()
|
||||
.py_index(i32::try_from(int).expect("checked in branch arm"))
|
||||
.copied()
|
||||
let tuple = tuple_ty.tuple(self.db());
|
||||
tuple
|
||||
.py_index(
|
||||
self.db(),
|
||||
i32::try_from(int).expect("checked in branch arm"),
|
||||
)
|
||||
.unwrap_or_else(|_| {
|
||||
report_index_out_of_bounds(
|
||||
&self.context,
|
||||
"tuple",
|
||||
value_node.into(),
|
||||
value_ty,
|
||||
elements.len(),
|
||||
tuple.display_minimum_length(),
|
||||
int,
|
||||
);
|
||||
Type::unknown()
|
||||
|
@ -8156,9 +8163,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
}
|
||||
// Ex) Given `("a", 1, Null)[0:2]`, return `("a", 1)`
|
||||
(Type::Tuple(tuple_ty), _, Some(SliceLiteral { start, stop, step })) => {
|
||||
let elements = tuple_ty.elements(self.db());
|
||||
let TupleSpec::Fixed(tuple) = tuple_ty.tuple(self.db()) else {
|
||||
return todo_type!("slice into variable-length tuple");
|
||||
};
|
||||
|
||||
if let Ok(new_elements) = elements.py_slice(start, stop, step) {
|
||||
if let Ok(new_elements) = tuple.py_slice(self.db(), start, stop, step) {
|
||||
TupleType::from_elements(self.db(), new_elements)
|
||||
} else {
|
||||
report_slice_step_size_zero(&self.context, value_node.into());
|
||||
|
@ -8170,9 +8179,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
if i32::try_from(int).is_ok() =>
|
||||
{
|
||||
let literal_value = literal_ty.value(self.db());
|
||||
literal_value
|
||||
.chars()
|
||||
.py_index(i32::try_from(int).expect("checked in branch arm"))
|
||||
(&mut literal_value.chars())
|
||||
.py_index(
|
||||
self.db(),
|
||||
i32::try_from(int).expect("checked in branch arm"),
|
||||
)
|
||||
.map(|ch| Type::string_literal(self.db(), &ch.to_string()))
|
||||
.unwrap_or_else(|_| {
|
||||
report_index_out_of_bounds(
|
||||
|
@ -8192,7 +8203,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
|
||||
let chars: Vec<_> = literal_value.chars().collect();
|
||||
|
||||
if let Ok(new_chars) = chars.py_slice(start, stop, step) {
|
||||
if let Ok(new_chars) = chars.py_slice(self.db(), start, stop, step) {
|
||||
let literal: String = new_chars.collect();
|
||||
Type::string_literal(self.db(), &literal)
|
||||
} else {
|
||||
|
@ -8206,8 +8217,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
{
|
||||
let literal_value = literal_ty.value(self.db());
|
||||
literal_value
|
||||
.iter()
|
||||
.py_index(i32::try_from(int).expect("checked in branch arm"))
|
||||
.py_index(
|
||||
self.db(),
|
||||
i32::try_from(int).expect("checked in branch arm"),
|
||||
)
|
||||
.map(|byte| Type::IntLiteral((*byte).into()))
|
||||
.unwrap_or_else(|_| {
|
||||
report_index_out_of_bounds(
|
||||
|
@ -8225,7 +8238,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
(Type::BytesLiteral(literal_ty), _, Some(SliceLiteral { start, stop, step })) => {
|
||||
let literal_value = literal_ty.value(self.db());
|
||||
|
||||
if let Ok(new_bytes) = literal_value.py_slice(start, stop, step) {
|
||||
if let Ok(new_bytes) = literal_value.py_slice(self.db(), start, stop, step) {
|
||||
let new_bytes: Vec<u8> = new_bytes.copied().collect();
|
||||
Type::bytes_literal(self.db(), &new_bytes)
|
||||
} else {
|
||||
|
@ -8243,14 +8256,19 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
value_ty,
|
||||
Type::IntLiteral(i64::from(bool)),
|
||||
),
|
||||
(Type::SpecialForm(SpecialFormType::Protocol), Type::Tuple(typevars), _) => self
|
||||
.legacy_generic_class_context(
|
||||
(Type::SpecialForm(SpecialFormType::Protocol), Type::Tuple(typevars), _) => {
|
||||
let TupleSpec::Fixed(typevars) = typevars.tuple(self.db()) else {
|
||||
// TODO: emit a diagnostic
|
||||
return Type::unknown();
|
||||
};
|
||||
self.legacy_generic_class_context(
|
||||
value_node,
|
||||
typevars.elements(self.db()),
|
||||
typevars.elements_slice(),
|
||||
LegacyGenericBase::Protocol,
|
||||
)
|
||||
.map(|context| Type::KnownInstance(KnownInstanceType::SubscriptedProtocol(context)))
|
||||
.unwrap_or_else(Type::unknown),
|
||||
.unwrap_or_else(Type::unknown)
|
||||
}
|
||||
(Type::SpecialForm(SpecialFormType::Protocol), typevar, _) => self
|
||||
.legacy_generic_class_context(
|
||||
value_node,
|
||||
|
@ -8263,14 +8281,19 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
// TODO: emit a diagnostic
|
||||
todo_type!("doubly-specialized typing.Protocol")
|
||||
}
|
||||
(Type::SpecialForm(SpecialFormType::Generic), Type::Tuple(typevars), _) => self
|
||||
.legacy_generic_class_context(
|
||||
(Type::SpecialForm(SpecialFormType::Generic), Type::Tuple(typevars), _) => {
|
||||
let TupleSpec::Fixed(typevars) = typevars.tuple(self.db()) else {
|
||||
// TODO: emit a diagnostic
|
||||
return Type::unknown();
|
||||
};
|
||||
self.legacy_generic_class_context(
|
||||
value_node,
|
||||
typevars.elements(self.db()),
|
||||
typevars.elements_slice(),
|
||||
LegacyGenericBase::Generic,
|
||||
)
|
||||
.map(|context| Type::KnownInstance(KnownInstanceType::SubscriptedGeneric(context)))
|
||||
.unwrap_or_else(Type::unknown),
|
||||
.unwrap_or_else(Type::unknown)
|
||||
}
|
||||
(Type::SpecialForm(SpecialFormType::Generic), typevar, _) => self
|
||||
.legacy_generic_class_context(
|
||||
value_node,
|
||||
|
@ -9167,10 +9190,23 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
|||
todo_type!("ellipsis literal in type expression")
|
||||
}
|
||||
|
||||
ast::Expr::Starred(starred) => {
|
||||
self.infer_starred_expression(starred);
|
||||
todo_type!("PEP 646")
|
||||
}
|
||||
ast::Expr::Starred(starred) => self.infer_starred_type_expression(starred),
|
||||
}
|
||||
}
|
||||
|
||||
fn infer_starred_type_expression(&mut self, starred: &ast::ExprStarred) -> Type<'db> {
|
||||
let ast::ExprStarred {
|
||||
range: _,
|
||||
node_index: _,
|
||||
value,
|
||||
ctx: _,
|
||||
} = starred;
|
||||
|
||||
let starred_type = self.infer_type_expression(value);
|
||||
if let Type::Tuple(_) = starred_type {
|
||||
starred_type
|
||||
} else {
|
||||
todo_type!("PEP 646")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9225,7 +9261,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
|||
}
|
||||
|
||||
match element {
|
||||
ast::Expr::Starred(_) => true,
|
||||
ast::Expr::Starred(_) => !matches!(element_ty, Type::Tuple(_)),
|
||||
ast::Expr::Subscript(ast::ExprSubscript { value, .. }) => {
|
||||
let value_ty = if builder.deferred_state.in_string_annotation() {
|
||||
// Using `.expression_type` does not work in string annotations, because
|
||||
|
@ -9246,13 +9282,13 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
|||
ast::Expr::Tuple(elements) => {
|
||||
if let [element, ellipsis @ ast::Expr::EllipsisLiteral(_)] = &*elements.elts {
|
||||
self.infer_expression(ellipsis);
|
||||
let result = KnownClass::Tuple
|
||||
.to_specialized_instance(self.db(), [self.infer_type_expression(element)]);
|
||||
let result =
|
||||
TupleType::homogeneous(self.db(), self.infer_type_expression(element));
|
||||
self.store_expression_type(tuple_slice, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
let mut element_types = Vec::with_capacity(elements.len());
|
||||
let mut element_types = TupleSpec::with_capacity(elements.len());
|
||||
|
||||
// Whether to infer `Todo` for the whole tuple
|
||||
// (see docstring for `element_could_alter_type_of_whole_tuple`)
|
||||
|
@ -9262,13 +9298,22 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
|||
let element_ty = self.infer_type_expression(element);
|
||||
return_todo |=
|
||||
element_could_alter_type_of_whole_tuple(element, element_ty, self);
|
||||
element_types.push(element_ty);
|
||||
if let ast::Expr::Starred(_) = element {
|
||||
if let Type::Tuple(inner_tuple) = element_ty {
|
||||
element_types =
|
||||
element_types.concat(self.db(), inner_tuple.tuple(self.db()));
|
||||
} else {
|
||||
// TODO: emit a diagnostic
|
||||
}
|
||||
} else {
|
||||
element_types.push(element_ty);
|
||||
}
|
||||
}
|
||||
|
||||
let ty = if return_todo {
|
||||
todo_type!("PEP 646")
|
||||
} else {
|
||||
TupleType::from_elements(self.db(), element_types)
|
||||
Type::tuple(self.db(), TupleType::new(self.db(), element_types))
|
||||
};
|
||||
|
||||
// Here, we store the type for the inner `int, str` tuple-expression,
|
||||
|
|
|
@ -5,6 +5,7 @@ use std::marker::PhantomData;
|
|||
use super::protocol_class::ProtocolInterface;
|
||||
use super::{ClassType, KnownClass, SubclassOfType, Type, TypeVarVariance};
|
||||
use crate::place::{Boundness, Place, PlaceAndQualifiers};
|
||||
use crate::types::tuple::TupleType;
|
||||
use crate::types::{ClassLiteral, DynamicType, TypeMapping, TypeRelation, TypeVarInstance};
|
||||
use crate::{Db, FxOrderSet};
|
||||
|
||||
|
@ -12,12 +13,18 @@ pub(super) use synthesized_protocol::SynthesizedProtocolType;
|
|||
|
||||
impl<'db> Type<'db> {
|
||||
pub(crate) fn instance(db: &'db dyn Db, class: ClassType<'db>) -> Self {
|
||||
if class.is_known(db, KnownClass::Any) {
|
||||
Self::Dynamic(DynamicType::Any)
|
||||
} else if class.class_literal(db).0.is_protocol(db) {
|
||||
Self::ProtocolInstance(ProtocolInstanceType::from_class(class))
|
||||
} else {
|
||||
Self::NominalInstance(NominalInstanceType::from_class(class))
|
||||
match (class, class.known(db)) {
|
||||
(_, Some(KnownClass::Any)) => Self::Dynamic(DynamicType::Any),
|
||||
(ClassType::NonGeneric(_), Some(KnownClass::Tuple)) => {
|
||||
TupleType::homogeneous(db, Type::unknown())
|
||||
}
|
||||
(ClassType::Generic(alias), Some(KnownClass::Tuple)) => {
|
||||
Self::tuple(db, TupleType::new(db, alias.specialization(db).tuple(db)))
|
||||
}
|
||||
_ if class.class_literal(db).0.is_protocol(db) => {
|
||||
Self::ProtocolInstance(ProtocolInstanceType::from_class(class))
|
||||
}
|
||||
_ => Self::NominalInstance(NominalInstanceType::from_class(class)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -98,11 +105,24 @@ impl<'db> NominalInstanceType<'db> {
|
|||
}
|
||||
|
||||
pub(super) fn is_disjoint_from(self, db: &'db dyn Db, other: Self) -> bool {
|
||||
if self.class.is_final(db) && !self.class.is_subclass_of(db, other.class) {
|
||||
self.is_disjoint_from_nominal_instance_of_class(db, other.class)
|
||||
}
|
||||
|
||||
// Note that this method only exists so that we can check disjointness between nominal
|
||||
// instances of `tuple` and some other class. Tuples are currently represented by the
|
||||
// `Type::Tuple` variant, not `Type::NominalInstance`. We have a TODO to try to remove the
|
||||
// dedicated `Tuple` variant in favor of `NominalInstance`; if we can do that, then we won't
|
||||
// need this method, and its logic can be subsumed into `is_disjoint_from`.
|
||||
pub(super) fn is_disjoint_from_nominal_instance_of_class(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
other_class: ClassType,
|
||||
) -> bool {
|
||||
if self.class.is_final(db) && !self.class.is_subclass_of(db, other_class) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if other.class.is_final(db) && !other.class.is_subclass_of(db, self.class) {
|
||||
if other_class.is_final(db) && !other_class.is_subclass_of(db, self.class) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -116,7 +136,7 @@ impl<'db> NominalInstanceType<'db> {
|
|||
if self_metaclass == type_type {
|
||||
return false;
|
||||
}
|
||||
let other_metaclass = other.class.metaclass_instance_type(db);
|
||||
let other_metaclass = other_class.metaclass_instance_type(db);
|
||||
if other_metaclass == type_type {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -175,8 +175,8 @@ impl ClassInfoConstraintFunction {
|
|||
match classinfo {
|
||||
Type::Tuple(tuple) => {
|
||||
let mut builder = UnionBuilder::new(db);
|
||||
for element in tuple.elements(db) {
|
||||
builder = builder.add(self.generate_constraint(db, *element)?);
|
||||
for element in tuple.tuple(db).all_elements() {
|
||||
builder = builder.add(self.generate_constraint(db, element)?);
|
||||
}
|
||||
Some(builder.build())
|
||||
}
|
||||
|
@ -540,7 +540,7 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
|
|||
match rhs_ty {
|
||||
Type::Tuple(rhs_tuple) => Some(UnionType::from_elements(
|
||||
self.db,
|
||||
rhs_tuple.elements(self.db),
|
||||
rhs_tuple.tuple(self.db).all_elements(),
|
||||
)),
|
||||
|
||||
Type::StringLiteral(string_literal) => Some(UnionType::from_elements(
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
use crate::db::tests::TestDb;
|
||||
use crate::place::{builtins_symbol, known_module_symbol};
|
||||
use crate::types::tuple::TupleType;
|
||||
use crate::types::{
|
||||
BoundMethodType, CallableType, IntersectionBuilder, KnownClass, Parameter, Parameters,
|
||||
Signature, SpecialFormType, SubclassOfType, TupleType, Type, UnionType,
|
||||
Signature, SpecialFormType, SubclassOfType, Type, UnionType,
|
||||
};
|
||||
use crate::{Db, KnownModule};
|
||||
use hashbrown::HashSet;
|
||||
|
|
|
@ -36,7 +36,7 @@ impl SlotsKind {
|
|||
match slots_ty {
|
||||
// __slots__ = ("a", "b")
|
||||
Type::Tuple(tuple) => {
|
||||
if tuple.elements(db).is_empty() {
|
||||
if tuple.tuple(db).is_empty() {
|
||||
Self::Empty
|
||||
} else {
|
||||
Self::NotEmpty
|
||||
|
|
868
crates/ty_python_semantic/src/types/tuple.rs
Normal file
868
crates/ty_python_semantic/src/types/tuple.rs
Normal file
|
@ -0,0 +1,868 @@
|
|||
//! Types describing fixed- and variable-length tuples.
|
||||
//!
|
||||
//! At runtime, a Python tuple is a fixed-length immutable list of values. There is no restriction
|
||||
//! on the types of the elements of a tuple value. In the type system, we want to model both
|
||||
//! "heterogeneous" tuples that have elements of a fixed sequence of specific types, and
|
||||
//! "homogeneous" tuples that have an unknown number of elements of the same single type. And in
|
||||
//! fact, we want to model tuples that are a combination of the two ("mixed" tuples), with a
|
||||
//! heterogeneous prefix and/or suffix, and a homogeneous portion of unknown length in between
|
||||
//! those.
|
||||
//!
|
||||
//! The description of which elements can appear in a `tuple` is called a [`TupleSpec`]. Other
|
||||
//! things besides `tuple` instances can be described by a tuple spec — for instance, the targets
|
||||
//! of an unpacking assignment. A `tuple` specialization that includes `Never` as one of its
|
||||
//! fixed-length elements cannot be instantiated. We reduce the entire `tuple` type down to
|
||||
//! `Never`. The same is not true of tuple specs in general. (That means that it is [`TupleType`]
|
||||
//! that adds that "collapse `Never`" behavior, whereas [`TupleSpec`] allows you to add any element
|
||||
//! types, including `Never`.)
|
||||
|
||||
use itertools::Either;
|
||||
|
||||
use crate::types::class::{ClassType, KnownClass};
|
||||
use crate::types::{Type, TypeMapping, TypeRelation, TypeVarInstance, TypeVarVariance, UnionType};
|
||||
use crate::util::subscript::{Nth, OutOfBoundsError, PyIndex, PySlice, StepSizeZeroError};
|
||||
use crate::{Db, FxOrderSet};
|
||||
|
||||
/// # Ordering
|
||||
/// Ordering is based on the tuple's salsa-assigned id and not on its elements.
|
||||
/// The id may change between runs, or when the tuple was garbage collected and recreated.
|
||||
#[salsa::interned(debug)]
|
||||
#[derive(PartialOrd, Ord)]
|
||||
pub struct TupleType<'db> {
|
||||
#[returns(ref)]
|
||||
pub(crate) tuple: TupleSpec<'db>,
|
||||
}
|
||||
|
||||
impl<'db> Type<'db> {
|
||||
pub(crate) fn tuple(db: &'db dyn Db, tuple: TupleType<'db>) -> Self {
|
||||
// If a fixed-length (i.e., mandatory) element of the tuple is `Never`, then it's not
|
||||
// possible to instantiate the tuple as a whole. (This is not true of the variable-length
|
||||
// portion of the tuple, since it can contain no elements.)
|
||||
if tuple.tuple(db).fixed_elements().any(|ty| ty.is_never()) {
|
||||
return Type::Never;
|
||||
}
|
||||
Self::Tuple(tuple)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> TupleType<'db> {
|
||||
pub(crate) fn empty(db: &'db dyn Db) -> Type<'db> {
|
||||
Type::tuple(
|
||||
db,
|
||||
TupleType::new(db, TupleSpec::from(FixedLengthTupleSpec::empty())),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn from_elements(
|
||||
db: &'db dyn Db,
|
||||
types: impl IntoIterator<Item = impl Into<Type<'db>>>,
|
||||
) -> Type<'db> {
|
||||
Type::tuple(
|
||||
db,
|
||||
TupleType::new(
|
||||
db,
|
||||
TupleSpec::from(FixedLengthTupleSpec::from_elements(types)),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn mixed(
|
||||
db: &'db dyn Db,
|
||||
prefix: impl IntoIterator<Item = impl Into<Type<'db>>>,
|
||||
variable: Type<'db>,
|
||||
suffix: impl IntoIterator<Item = impl Into<Type<'db>>>,
|
||||
) -> Type<'db> {
|
||||
Type::tuple(
|
||||
db,
|
||||
TupleType::new(
|
||||
db,
|
||||
TupleSpec::from(VariableLengthTupleSpec::mixed(prefix, variable, suffix)),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn homogeneous(db: &'db dyn Db, element: Type<'db>) -> Type<'db> {
|
||||
Type::tuple(db, TupleType::new(db, TupleSpec::homogeneous(element)))
|
||||
}
|
||||
|
||||
pub(crate) fn to_class_type(self, db: &'db dyn Db) -> Option<ClassType<'db>> {
|
||||
KnownClass::Tuple
|
||||
.try_to_class_literal(db)
|
||||
.and_then(|class_literal| match class_literal.generic_context(db) {
|
||||
None => Some(ClassType::NonGeneric(class_literal)),
|
||||
Some(generic_context) if generic_context.variables(db).len() != 1 => None,
|
||||
Some(generic_context) => Some(
|
||||
class_literal
|
||||
.apply_specialization(db, |_| generic_context.specialize_tuple(db, self)),
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
/// Return a normalized version of `self`.
|
||||
///
|
||||
/// See [`Type::normalized`] for more details.
|
||||
#[must_use]
|
||||
pub(crate) fn normalized(self, db: &'db dyn Db) -> Self {
|
||||
TupleType::new(db, self.tuple(db).normalized(db))
|
||||
}
|
||||
|
||||
pub(crate) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
||||
TupleType::new(db, self.tuple(db).materialize(db, variance))
|
||||
}
|
||||
|
||||
pub(crate) fn apply_type_mapping<'a>(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
type_mapping: &TypeMapping<'a, 'db>,
|
||||
) -> Self {
|
||||
TupleType::new(db, self.tuple(db).apply_type_mapping(db, type_mapping))
|
||||
}
|
||||
|
||||
pub(crate) fn find_legacy_typevars(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
typevars: &mut FxOrderSet<TypeVarInstance<'db>>,
|
||||
) {
|
||||
self.tuple(db).find_legacy_typevars(db, typevars);
|
||||
}
|
||||
|
||||
pub(crate) fn has_relation_to(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
other: Self,
|
||||
relation: TypeRelation,
|
||||
) -> bool {
|
||||
self.tuple(db)
|
||||
.has_relation_to(db, other.tuple(db), relation)
|
||||
}
|
||||
|
||||
pub(crate) fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
|
||||
self.tuple(db).is_equivalent_to(db, other.tuple(db))
|
||||
}
|
||||
|
||||
pub(crate) fn is_gradual_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
|
||||
self.tuple(db).is_gradual_equivalent_to(db, other.tuple(db))
|
||||
}
|
||||
|
||||
pub(crate) fn is_disjoint_from(self, db: &'db dyn Db, other: Self) -> bool {
|
||||
self.tuple(db).is_disjoint_from(db, other.tuple(db))
|
||||
}
|
||||
|
||||
pub(crate) fn is_fully_static(self, db: &'db dyn Db) -> bool {
|
||||
self.tuple(db).is_fully_static(db)
|
||||
}
|
||||
|
||||
pub(crate) fn is_single_valued(self, db: &'db dyn Db) -> bool {
|
||||
self.tuple(db).is_single_valued(db)
|
||||
}
|
||||
}
|
||||
|
||||
/// A fixed-length tuple spec.
|
||||
///
|
||||
/// Tuple specs are used for more than just `tuple` instances, so they allow `Never` to appear as a
|
||||
/// fixed-length element type. [`TupleType`] adds that additional invariant (since a tuple that
|
||||
/// must contain an element that can't be instantiated, can't be instantiated itself).
|
||||
#[derive(Clone, Debug, Default, Eq, Hash, PartialEq, salsa::Update)]
|
||||
pub struct FixedLengthTupleSpec<'db>(Vec<Type<'db>>);
|
||||
|
||||
impl<'db> FixedLengthTupleSpec<'db> {
|
||||
pub(crate) fn empty() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub(crate) fn with_capacity(capacity: usize) -> Self {
|
||||
Self(Vec::with_capacity(capacity))
|
||||
}
|
||||
|
||||
pub(crate) fn from_elements(elements: impl IntoIterator<Item = impl Into<Type<'db>>>) -> Self {
|
||||
Self(elements.into_iter().map(Into::into).collect())
|
||||
}
|
||||
|
||||
pub(crate) fn elements_slice(&self) -> &[Type<'db>] {
|
||||
&self.0
|
||||
}
|
||||
|
||||
pub(crate) fn elements(&self) -> impl Iterator<Item = Type<'db>> + '_ {
|
||||
self.0.iter().copied()
|
||||
}
|
||||
|
||||
/// Returns the length of this tuple.
|
||||
pub(crate) fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
|
||||
fn concat(&self, other: &TupleSpec<'db>) -> TupleSpec<'db> {
|
||||
match other {
|
||||
TupleSpec::Fixed(other) => {
|
||||
let mut elements = Vec::with_capacity(self.0.len() + other.0.len());
|
||||
elements.extend_from_slice(&self.0);
|
||||
elements.extend_from_slice(&other.0);
|
||||
TupleSpec::Fixed(FixedLengthTupleSpec(elements))
|
||||
}
|
||||
|
||||
TupleSpec::Variable(other) => {
|
||||
let mut prefix = Vec::with_capacity(self.0.len() + other.prefix.len());
|
||||
prefix.extend_from_slice(&self.0);
|
||||
prefix.extend_from_slice(&other.prefix);
|
||||
TupleSpec::Variable(VariableLengthTupleSpec {
|
||||
prefix,
|
||||
variable: other.variable,
|
||||
suffix: other.suffix.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn push(&mut self, element: Type<'db>) {
|
||||
self.0.push(element);
|
||||
}
|
||||
|
||||
pub(crate) fn extend_from_slice(&mut self, elements: &[Type<'db>]) {
|
||||
self.0.extend_from_slice(elements);
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn normalized(&self, db: &'db dyn Db) -> Self {
|
||||
Self(self.0.iter().map(|ty| ty.normalized(db)).collect())
|
||||
}
|
||||
|
||||
fn materialize(&self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
||||
Self(
|
||||
self.0
|
||||
.iter()
|
||||
.map(|ty| ty.materialize(db, variance))
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
fn apply_type_mapping<'a>(&self, db: &'db dyn Db, type_mapping: &TypeMapping<'a, 'db>) -> Self {
|
||||
Self(
|
||||
self.0
|
||||
.iter()
|
||||
.map(|ty| ty.apply_type_mapping(db, type_mapping))
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
fn find_legacy_typevars(
|
||||
&self,
|
||||
db: &'db dyn Db,
|
||||
typevars: &mut FxOrderSet<TypeVarInstance<'db>>,
|
||||
) {
|
||||
for ty in &self.0 {
|
||||
ty.find_legacy_typevars(db, typevars);
|
||||
}
|
||||
}
|
||||
|
||||
fn has_relation_to(
|
||||
&self,
|
||||
db: &'db dyn Db,
|
||||
other: &TupleSpec<'db>,
|
||||
relation: TypeRelation,
|
||||
) -> bool {
|
||||
match other {
|
||||
TupleSpec::Fixed(other) => {
|
||||
self.0.len() == other.0.len()
|
||||
&& (self.0.iter())
|
||||
.zip(&other.0)
|
||||
.all(|(self_ty, other_ty)| self_ty.has_relation_to(db, *other_ty, relation))
|
||||
}
|
||||
|
||||
TupleSpec::Variable(other) => {
|
||||
// This tuple must have enough elements to match up with the other tuple's prefix
|
||||
// and suffix, and each of those elements must pairwise satisfy the relation.
|
||||
let mut self_iter = self.0.iter();
|
||||
for other_ty in &other.prefix {
|
||||
let Some(self_ty) = self_iter.next() else {
|
||||
return false;
|
||||
};
|
||||
if !self_ty.has_relation_to(db, *other_ty, relation) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for other_ty in other.suffix.iter().rev() {
|
||||
let Some(self_ty) = self_iter.next_back() else {
|
||||
return false;
|
||||
};
|
||||
if !self_ty.has_relation_to(db, *other_ty, relation) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// In addition, any remaining elements in this tuple must satisfy the
|
||||
// variable-length portion of the other tuple.
|
||||
self_iter.all(|self_ty| self_ty.has_relation_to(db, other.variable, relation))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_equivalent_to(&self, db: &'db dyn Db, other: &Self) -> bool {
|
||||
self.0.len() == other.0.len()
|
||||
&& (self.0.iter())
|
||||
.zip(&other.0)
|
||||
.all(|(self_ty, other_ty)| self_ty.is_equivalent_to(db, *other_ty))
|
||||
}
|
||||
|
||||
fn is_gradual_equivalent_to(&self, db: &'db dyn Db, other: &Self) -> bool {
|
||||
self.0.len() == other.0.len()
|
||||
&& (self.0.iter())
|
||||
.zip(&other.0)
|
||||
.all(|(self_ty, other_ty)| self_ty.is_gradual_equivalent_to(db, *other_ty))
|
||||
}
|
||||
|
||||
fn is_disjoint_from(&self, db: &'db dyn Db, other: &Self) -> bool {
|
||||
self.0.len() != other.0.len()
|
||||
|| (self.0.iter())
|
||||
.zip(&other.0)
|
||||
.any(|(self_ty, other_ty)| self_ty.is_disjoint_from(db, *other_ty))
|
||||
}
|
||||
|
||||
fn is_fully_static(&self, db: &'db dyn Db) -> bool {
|
||||
self.0.iter().all(|ty| ty.is_fully_static(db))
|
||||
}
|
||||
|
||||
fn is_single_valued(&self, db: &'db dyn Db) -> bool {
|
||||
self.0.iter().all(|ty| ty.is_single_valued(db))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> PyIndex<'db> for &FixedLengthTupleSpec<'db> {
|
||||
type Item = Type<'db>;
|
||||
|
||||
fn py_index(self, db: &'db dyn Db, index: i32) -> Result<Self::Item, OutOfBoundsError> {
|
||||
self.0.as_slice().py_index(db, index).copied()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> PySlice<'db> for FixedLengthTupleSpec<'db> {
|
||||
type Item = Type<'db>;
|
||||
|
||||
fn py_slice(
|
||||
&'db self,
|
||||
db: &'db dyn Db,
|
||||
start: Option<i32>,
|
||||
stop: Option<i32>,
|
||||
step: Option<i32>,
|
||||
) -> Result<impl Iterator<Item = &'db Self::Item>, StepSizeZeroError> {
|
||||
self.0.py_slice(db, start, stop, step)
|
||||
}
|
||||
}
|
||||
|
||||
/// A variable-length tuple spec.
|
||||
///
|
||||
/// The tuple spec can contain a fixed-length heterogeneous prefix and/or suffix. All of the
|
||||
/// elements of the variable-length portion must be of the same type.
|
||||
///
|
||||
/// Tuple specs are used for more than just `tuple` instances, so they allow `Never` to appear as a
|
||||
/// fixed-length element type. [`TupleType`] adds that additional invariant (since a tuple that
|
||||
/// must contain an element that can't be instantiated, can't be instantiated itself).
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq, salsa::Update)]
|
||||
pub struct VariableLengthTupleSpec<'db> {
|
||||
pub(crate) prefix: Vec<Type<'db>>,
|
||||
pub(crate) variable: Type<'db>,
|
||||
pub(crate) suffix: Vec<Type<'db>>,
|
||||
}
|
||||
|
||||
impl<'db> VariableLengthTupleSpec<'db> {
|
||||
/// Creates a new tuple spec containing zero or more elements of a given type, with no prefix
|
||||
/// or suffix.
|
||||
fn homogeneous(ty: Type<'db>) -> Self {
|
||||
Self {
|
||||
prefix: vec![],
|
||||
variable: ty,
|
||||
suffix: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn mixed(
|
||||
prefix: impl IntoIterator<Item = impl Into<Type<'db>>>,
|
||||
variable: Type<'db>,
|
||||
suffix: impl IntoIterator<Item = impl Into<Type<'db>>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
prefix: prefix.into_iter().map(Into::into).collect(),
|
||||
variable,
|
||||
suffix: suffix.into_iter().map(Into::into).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn fixed_elements(&self) -> impl Iterator<Item = Type<'db>> + '_ {
|
||||
(self.prefix.iter().copied()).chain(self.suffix.iter().copied())
|
||||
}
|
||||
|
||||
fn all_elements(&self) -> impl Iterator<Item = Type<'db>> + '_ {
|
||||
(self.prefix.iter().copied())
|
||||
.chain(std::iter::once(self.variable))
|
||||
.chain(self.suffix.iter().copied())
|
||||
}
|
||||
|
||||
/// Returns the minimum length of this tuple.
|
||||
pub(crate) fn minimum_length(&self) -> usize {
|
||||
self.prefix.len() + self.suffix.len()
|
||||
}
|
||||
|
||||
fn concat(&self, db: &'db dyn Db, other: &TupleSpec<'db>) -> TupleSpec<'db> {
|
||||
match other {
|
||||
TupleSpec::Fixed(other) => {
|
||||
let mut suffix = Vec::with_capacity(self.suffix.len() + other.0.len());
|
||||
suffix.extend_from_slice(&self.suffix);
|
||||
suffix.extend_from_slice(&other.0);
|
||||
TupleSpec::Variable(VariableLengthTupleSpec {
|
||||
prefix: self.prefix.clone(),
|
||||
variable: self.variable,
|
||||
suffix,
|
||||
})
|
||||
}
|
||||
|
||||
TupleSpec::Variable(other) => {
|
||||
let variable = UnionType::from_elements(
|
||||
db,
|
||||
(self.suffix.iter().copied())
|
||||
.chain([self.variable, other.variable])
|
||||
.chain(other.prefix.iter().copied()),
|
||||
);
|
||||
TupleSpec::Variable(VariableLengthTupleSpec {
|
||||
prefix: self.prefix.clone(),
|
||||
variable,
|
||||
suffix: other.suffix.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn push(&mut self, element: Type<'db>) {
|
||||
self.suffix.push(element);
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn normalized(&self, db: &'db dyn Db) -> Self {
|
||||
Self {
|
||||
prefix: self.prefix.iter().map(|ty| ty.normalized(db)).collect(),
|
||||
variable: self.variable.normalized(db),
|
||||
suffix: self.suffix.iter().map(|ty| ty.normalized(db)).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn materialize(&self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
||||
Self {
|
||||
prefix: self
|
||||
.prefix
|
||||
.iter()
|
||||
.map(|ty| ty.materialize(db, variance))
|
||||
.collect(),
|
||||
variable: self.variable.materialize(db, variance),
|
||||
suffix: self
|
||||
.suffix
|
||||
.iter()
|
||||
.map(|ty| ty.materialize(db, variance))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_type_mapping<'a>(&self, db: &'db dyn Db, type_mapping: &TypeMapping<'a, 'db>) -> Self {
|
||||
Self {
|
||||
prefix: self
|
||||
.prefix
|
||||
.iter()
|
||||
.map(|ty| ty.apply_type_mapping(db, type_mapping))
|
||||
.collect(),
|
||||
variable: self.variable.apply_type_mapping(db, type_mapping),
|
||||
suffix: self
|
||||
.suffix
|
||||
.iter()
|
||||
.map(|ty| ty.apply_type_mapping(db, type_mapping))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn find_legacy_typevars(
|
||||
&self,
|
||||
db: &'db dyn Db,
|
||||
typevars: &mut FxOrderSet<TypeVarInstance<'db>>,
|
||||
) {
|
||||
for ty in &self.prefix {
|
||||
ty.find_legacy_typevars(db, typevars);
|
||||
}
|
||||
self.variable.find_legacy_typevars(db, typevars);
|
||||
for ty in &self.suffix {
|
||||
ty.find_legacy_typevars(db, typevars);
|
||||
}
|
||||
}
|
||||
|
||||
fn has_relation_to(
|
||||
&self,
|
||||
db: &'db dyn Db,
|
||||
other: &TupleSpec<'db>,
|
||||
relation: TypeRelation,
|
||||
) -> bool {
|
||||
match other {
|
||||
TupleSpec::Fixed(other) => {
|
||||
// The `...` length specifier of a variable-length tuple type is interpreted
|
||||
// differently depending on the type of the variable-length elements.
|
||||
//
|
||||
// It typically represents the _union_ of all possible lengths. That means that a
|
||||
// variable-length tuple type is not a subtype of _any_ fixed-length tuple type.
|
||||
//
|
||||
// However, as a special case, if the variable-length portion of the tuple is `Any`
|
||||
// (or any other dynamic type), then the `...` is the _gradual choice_ of all
|
||||
// possible lengths. This means that `tuple[Any, ...]` can match any tuple of any
|
||||
// length.
|
||||
if relation == TypeRelation::Subtyping || !matches!(self.variable, Type::Dynamic(_))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// In addition, the other tuple must have enough elements to match up with this
|
||||
// tuple's prefix and suffix, and each of those elements must pairwise satisfy the
|
||||
// relation.
|
||||
let mut other_iter = other.0.iter();
|
||||
for self_ty in &self.prefix {
|
||||
let Some(other_ty) = other_iter.next() else {
|
||||
return false;
|
||||
};
|
||||
if !self_ty.has_relation_to(db, *other_ty, relation) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for self_ty in self.suffix.iter().rev() {
|
||||
let Some(other_ty) = other_iter.next_back() else {
|
||||
return false;
|
||||
};
|
||||
if !self_ty.has_relation_to(db, *other_ty, relation) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
TupleSpec::Variable(other) => {
|
||||
// The overlapping parts of the prefixes and suffixes must satisfy the relation.
|
||||
let mut self_prefix = self.prefix.iter();
|
||||
let mut other_prefix = other.prefix.iter();
|
||||
let prefixes_match = (&mut self_prefix)
|
||||
.zip(&mut other_prefix)
|
||||
.all(|(self_ty, other_ty)| self_ty.has_relation_to(db, *other_ty, relation));
|
||||
if !prefixes_match {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut self_suffix = self.suffix.iter().rev();
|
||||
let mut other_suffix = other.suffix.iter().rev();
|
||||
let suffixes_match = (&mut self_suffix)
|
||||
.zip(&mut other_suffix)
|
||||
.all(|(self_ty, other_ty)| self_ty.has_relation_to(db, *other_ty, relation));
|
||||
if !suffixes_match {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Any remaining parts of either prefix or suffix must satisfy the relation with
|
||||
// the other tuple's variable-length portion.
|
||||
let prefix_matches_variable = self_prefix
|
||||
.all(|self_ty| self_ty.has_relation_to(db, other.variable, relation));
|
||||
if !prefix_matches_variable {
|
||||
return false;
|
||||
}
|
||||
let prefix_matches_variable = other_prefix
|
||||
.all(|other_ty| self.variable.has_relation_to(db, *other_ty, relation));
|
||||
if !prefix_matches_variable {
|
||||
return false;
|
||||
}
|
||||
|
||||
let suffix_matches_variable = self_suffix
|
||||
.all(|self_ty| self_ty.has_relation_to(db, other.variable, relation));
|
||||
if !suffix_matches_variable {
|
||||
return false;
|
||||
}
|
||||
let suffix_matches_variable = other_suffix
|
||||
.all(|other_ty| self.variable.has_relation_to(db, *other_ty, relation));
|
||||
if !suffix_matches_variable {
|
||||
return false;
|
||||
}
|
||||
|
||||
// And lastly, the variable-length portions must satisfy the relation.
|
||||
self.variable.has_relation_to(db, other.variable, relation)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_equivalent_to(&self, db: &'db dyn Db, other: &Self) -> bool {
|
||||
self.prefix.len() == other.prefix.len()
|
||||
&& self.suffix.len() == other.suffix.len()
|
||||
&& self.variable.is_equivalent_to(db, other.variable)
|
||||
&& (self.prefix.iter())
|
||||
.zip(&other.prefix)
|
||||
.all(|(self_ty, other_ty)| self_ty.is_equivalent_to(db, *other_ty))
|
||||
&& (self.suffix.iter())
|
||||
.zip(&other.suffix)
|
||||
.all(|(self_ty, other_ty)| self_ty.is_equivalent_to(db, *other_ty))
|
||||
}
|
||||
|
||||
fn is_gradual_equivalent_to(&self, db: &'db dyn Db, other: &Self) -> bool {
|
||||
self.prefix.len() == other.prefix.len()
|
||||
&& self.suffix.len() == other.suffix.len()
|
||||
&& self.variable.is_gradual_equivalent_to(db, other.variable)
|
||||
&& (self.prefix.iter())
|
||||
.zip(&other.prefix)
|
||||
.all(|(self_ty, other_ty)| self_ty.is_gradual_equivalent_to(db, *other_ty))
|
||||
&& (self.suffix.iter())
|
||||
.zip(&other.suffix)
|
||||
.all(|(self_ty, other_ty)| self_ty.is_gradual_equivalent_to(db, *other_ty))
|
||||
}
|
||||
|
||||
fn is_fully_static(&self, db: &'db dyn Db) -> bool {
|
||||
self.variable.is_fully_static(db)
|
||||
&& self.prefix.iter().all(|ty| ty.is_fully_static(db))
|
||||
&& self.suffix.iter().all(|ty| ty.is_fully_static(db))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> PyIndex<'db> for &VariableLengthTupleSpec<'db> {
|
||||
type Item = Type<'db>;
|
||||
|
||||
fn py_index(self, db: &'db dyn Db, index: i32) -> Result<Self::Item, OutOfBoundsError> {
|
||||
match Nth::from_index(index) {
|
||||
Nth::FromStart(index) => {
|
||||
if let Some(element) = self.prefix.get(index) {
|
||||
// index is small enough that it lands in the prefix of the tuple.
|
||||
return Ok(*element);
|
||||
}
|
||||
|
||||
// index is large enough that it lands past the prefix. The tuple can always be
|
||||
// large enough that it lands in the variable-length portion. It might also be
|
||||
// small enough to land in the suffix.
|
||||
let index_past_prefix = index - self.prefix.len() + 1;
|
||||
Ok(UnionType::from_elements(
|
||||
db,
|
||||
std::iter::once(self.variable)
|
||||
.chain(self.suffix.iter().copied().take(index_past_prefix)),
|
||||
))
|
||||
}
|
||||
|
||||
Nth::FromEnd(index_from_end) => {
|
||||
if index_from_end < self.suffix.len() {
|
||||
// index is small enough that it lands in the suffix of the tuple.
|
||||
return Ok(self.suffix[self.suffix.len() - index_from_end - 1]);
|
||||
}
|
||||
|
||||
// index is large enough that it lands past the suffix. The tuple can always be
|
||||
// large enough that it lands in the variable-length portion. It might also be
|
||||
// small enough to land in the prefix.
|
||||
let index_past_suffix = index_from_end - self.suffix.len() + 1;
|
||||
Ok(UnionType::from_elements(
|
||||
db,
|
||||
(self.prefix.iter().rev().copied())
|
||||
.take(index_past_suffix)
|
||||
.rev()
|
||||
.chain(std::iter::once(self.variable)),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A tuple spec that might be fixed- or variable-length.
|
||||
///
|
||||
/// Tuple specs are used for more than just `tuple` instances, so they allow `Never` to appear as a
|
||||
/// fixed-length element type. [`TupleType`] adds that additional invariant (since a tuple that
|
||||
/// must contain an element that can't be instantiated, can't be instantiated itself).
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq, salsa::Update)]
|
||||
pub enum TupleSpec<'db> {
|
||||
Fixed(FixedLengthTupleSpec<'db>),
|
||||
Variable(VariableLengthTupleSpec<'db>),
|
||||
}
|
||||
|
||||
impl<'db> TupleSpec<'db> {
|
||||
pub(crate) fn with_capacity(capacity: usize) -> Self {
|
||||
TupleSpec::Fixed(FixedLengthTupleSpec::with_capacity(capacity))
|
||||
}
|
||||
|
||||
pub(crate) fn homogeneous(element: Type<'db>) -> Self {
|
||||
TupleSpec::from(VariableLengthTupleSpec::homogeneous(element))
|
||||
}
|
||||
|
||||
/// Returns an iterator of all of the fixed-length element types of this tuple.
|
||||
pub(crate) fn fixed_elements(&self) -> impl Iterator<Item = Type<'db>> + '_ {
|
||||
match self {
|
||||
TupleSpec::Fixed(tuple) => Either::Left(tuple.elements()),
|
||||
TupleSpec::Variable(tuple) => Either::Right(tuple.fixed_elements()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an iterator of all of the element types of this tuple. Does not deduplicate the
|
||||
/// elements, and does not distinguish between fixed- and variable-length elements.
|
||||
pub(crate) fn all_elements(&self) -> impl Iterator<Item = Type<'db>> + '_ {
|
||||
match self {
|
||||
TupleSpec::Fixed(tuple) => Either::Left(tuple.elements()),
|
||||
TupleSpec::Variable(tuple) => Either::Right(tuple.all_elements()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn display_minimum_length(&self) -> String {
|
||||
match self {
|
||||
TupleSpec::Fixed(tuple) => tuple.len().to_string(),
|
||||
TupleSpec::Variable(tuple) => format!("at least {}", tuple.minimum_length()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the minimum and maximum length of this tuple. (The maximum length will be `None`
|
||||
/// for a tuple with a variable-length portion.)
|
||||
pub(crate) fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
match self {
|
||||
TupleSpec::Fixed(tuple) => {
|
||||
let len = tuple.len();
|
||||
(len, Some(len))
|
||||
}
|
||||
TupleSpec::Variable(tuple) => (tuple.minimum_length(), None),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_empty(&self) -> bool {
|
||||
match self {
|
||||
TupleSpec::Fixed(tuple) => tuple.is_empty(),
|
||||
TupleSpec::Variable(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Concatenates another tuple to the end of this tuple, returning a new tuple.
|
||||
pub(crate) fn concat(&self, db: &'db dyn Db, other: &Self) -> Self {
|
||||
match self {
|
||||
TupleSpec::Fixed(tuple) => tuple.concat(other),
|
||||
TupleSpec::Variable(tuple) => tuple.concat(db, other),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn push(&mut self, element: Type<'db>) {
|
||||
match self {
|
||||
TupleSpec::Fixed(tuple) => tuple.push(element),
|
||||
TupleSpec::Variable(tuple) => tuple.push(element),
|
||||
}
|
||||
}
|
||||
|
||||
fn normalized(&self, db: &'db dyn Db) -> Self {
|
||||
match self {
|
||||
TupleSpec::Fixed(tuple) => TupleSpec::Fixed(tuple.normalized(db)),
|
||||
TupleSpec::Variable(tuple) => TupleSpec::Variable(tuple.normalized(db)),
|
||||
}
|
||||
}
|
||||
|
||||
fn materialize(&self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
||||
match self {
|
||||
TupleSpec::Fixed(tuple) => TupleSpec::Fixed(tuple.materialize(db, variance)),
|
||||
TupleSpec::Variable(tuple) => TupleSpec::Variable(tuple.materialize(db, variance)),
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_type_mapping<'a>(&self, db: &'db dyn Db, type_mapping: &TypeMapping<'a, 'db>) -> Self {
|
||||
match self {
|
||||
TupleSpec::Fixed(tuple) => TupleSpec::Fixed(tuple.apply_type_mapping(db, type_mapping)),
|
||||
TupleSpec::Variable(tuple) => {
|
||||
TupleSpec::Variable(tuple.apply_type_mapping(db, type_mapping))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn find_legacy_typevars(
|
||||
&self,
|
||||
db: &'db dyn Db,
|
||||
typevars: &mut FxOrderSet<TypeVarInstance<'db>>,
|
||||
) {
|
||||
match self {
|
||||
TupleSpec::Fixed(tuple) => tuple.find_legacy_typevars(db, typevars),
|
||||
TupleSpec::Variable(tuple) => tuple.find_legacy_typevars(db, typevars),
|
||||
}
|
||||
}
|
||||
|
||||
fn has_relation_to(&self, db: &'db dyn Db, other: &Self, relation: TypeRelation) -> bool {
|
||||
match self {
|
||||
TupleSpec::Fixed(self_tuple) => self_tuple.has_relation_to(db, other, relation),
|
||||
TupleSpec::Variable(self_tuple) => self_tuple.has_relation_to(db, other, relation),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_equivalent_to(&self, db: &'db dyn Db, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(TupleSpec::Fixed(self_tuple), TupleSpec::Fixed(other_tuple)) => {
|
||||
self_tuple.is_equivalent_to(db, other_tuple)
|
||||
}
|
||||
(TupleSpec::Variable(self_tuple), TupleSpec::Variable(other_tuple)) => {
|
||||
self_tuple.is_equivalent_to(db, other_tuple)
|
||||
}
|
||||
(TupleSpec::Fixed(_), TupleSpec::Variable(_))
|
||||
| (TupleSpec::Variable(_), TupleSpec::Fixed(_)) => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_gradual_equivalent_to(&self, db: &'db dyn Db, other: &TupleSpec<'db>) -> bool {
|
||||
match (self, other) {
|
||||
(TupleSpec::Fixed(self_tuple), TupleSpec::Fixed(other_tuple)) => {
|
||||
self_tuple.is_gradual_equivalent_to(db, other_tuple)
|
||||
}
|
||||
(TupleSpec::Variable(self_tuple), TupleSpec::Variable(other_tuple)) => {
|
||||
self_tuple.is_gradual_equivalent_to(db, other_tuple)
|
||||
}
|
||||
(TupleSpec::Fixed(_), TupleSpec::Variable(_))
|
||||
| (TupleSpec::Variable(_), TupleSpec::Fixed(_)) => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_disjoint_from(&self, db: &'db dyn Db, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(TupleSpec::Fixed(self_tuple), TupleSpec::Fixed(other_tuple)) => {
|
||||
self_tuple.is_disjoint_from(db, other_tuple)
|
||||
}
|
||||
// Two pure homogeneous tuples `tuple[A, ...]` and `tuple[B, ...]` can never be
|
||||
// disjoint even if A and B are disjoint, because `tuple[()]` would be assignable to
|
||||
// both.
|
||||
// TODO: Consider checking for disjointness between the tuples' prefixes and suffixes.
|
||||
(TupleSpec::Variable(_), TupleSpec::Variable(_)) => false,
|
||||
// TODO: Consider checking for disjointness between the fixed-length tuple and the
|
||||
// variable-length tuple's prefix/suffix.
|
||||
(TupleSpec::Fixed(_), TupleSpec::Variable(_))
|
||||
| (TupleSpec::Variable(_), TupleSpec::Fixed(_)) => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_fully_static(&self, db: &'db dyn Db) -> bool {
|
||||
match self {
|
||||
TupleSpec::Fixed(tuple) => tuple.is_fully_static(db),
|
||||
TupleSpec::Variable(tuple) => tuple.is_fully_static(db),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_single_valued(&self, db: &'db dyn Db) -> bool {
|
||||
match self {
|
||||
TupleSpec::Fixed(tuple) => tuple.is_single_valued(db),
|
||||
TupleSpec::Variable(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> From<FixedLengthTupleSpec<'db>> for TupleSpec<'db> {
|
||||
fn from(tuple: FixedLengthTupleSpec<'db>) -> Self {
|
||||
TupleSpec::Fixed(tuple)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> From<VariableLengthTupleSpec<'db>> for TupleSpec<'db> {
|
||||
fn from(tuple: VariableLengthTupleSpec<'db>) -> Self {
|
||||
TupleSpec::Variable(tuple)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> PyIndex<'db> for &TupleSpec<'db> {
|
||||
type Item = Type<'db>;
|
||||
|
||||
fn py_index(self, db: &'db dyn Db, index: i32) -> Result<Self::Item, OutOfBoundsError> {
|
||||
match self {
|
||||
TupleSpec::Fixed(tuple) => tuple.py_index(db, index),
|
||||
TupleSpec::Variable(tuple) => tuple.py_index(db, index),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,12 +9,13 @@ use ruff_python_ast::{self as ast, AnyNodeRef};
|
|||
use crate::Db;
|
||||
use crate::semantic_index::ast_ids::{HasScopedExpressionId, ScopedExpressionId};
|
||||
use crate::semantic_index::place::ScopeId;
|
||||
use crate::types::{Type, TypeCheckDiagnostics, infer_expression_types};
|
||||
use crate::types::tuple::{FixedLengthTupleSpec, TupleSpec, TupleType};
|
||||
use crate::types::{Type, TypeCheckDiagnostics, infer_expression_types, todo_type};
|
||||
use crate::unpack::{UnpackKind, UnpackValue};
|
||||
|
||||
use super::context::InferContext;
|
||||
use super::diagnostic::INVALID_ASSIGNMENT;
|
||||
use super::{KnownClass, TupleType, UnionType};
|
||||
use super::{KnownClass, UnionType};
|
||||
|
||||
/// Unpacks the value expression type to their respective targets.
|
||||
pub(crate) struct Unpacker<'db, 'ast> {
|
||||
|
@ -152,53 +153,55 @@ impl<'db, 'ast> Unpacker<'db, 'ast> {
|
|||
_ => ty,
|
||||
};
|
||||
|
||||
if let Some(tuple_ty) = ty.into_tuple() {
|
||||
let tuple_ty_elements =
|
||||
self.tuple_ty_elements(target, elts, tuple_ty, value_expr);
|
||||
if let Type::Tuple(tuple_ty) = ty {
|
||||
let tuple = self.tuple_ty_elements(target, elts, tuple_ty, value_expr);
|
||||
|
||||
let length_mismatch =
|
||||
match elts.len().cmp(&tuple_ty_elements.len()) {
|
||||
Ordering::Less => {
|
||||
if let Some(builder) =
|
||||
self.context.report_lint(&INVALID_ASSIGNMENT, target)
|
||||
{
|
||||
let mut diag =
|
||||
builder.into_diagnostic("Too many values to unpack");
|
||||
diag.set_primary_message(format_args!(
|
||||
"Expected {}",
|
||||
elts.len(),
|
||||
));
|
||||
diag.annotate(self.context.secondary(value_expr).message(
|
||||
format_args!("Got {}", tuple_ty_elements.len()),
|
||||
));
|
||||
}
|
||||
true
|
||||
let length_mismatch = match elts.len().cmp(&tuple.len()) {
|
||||
Ordering::Less => {
|
||||
if let Some(builder) =
|
||||
self.context.report_lint(&INVALID_ASSIGNMENT, target)
|
||||
{
|
||||
let mut diag =
|
||||
builder.into_diagnostic("Too many values to unpack");
|
||||
diag.set_primary_message(format_args!(
|
||||
"Expected {}",
|
||||
elts.len(),
|
||||
));
|
||||
diag.annotate(
|
||||
self.context
|
||||
.secondary(value_expr)
|
||||
.message(format_args!("Got {}", tuple.len())),
|
||||
);
|
||||
}
|
||||
Ordering::Greater => {
|
||||
if let Some(builder) =
|
||||
self.context.report_lint(&INVALID_ASSIGNMENT, target)
|
||||
{
|
||||
let mut diag =
|
||||
builder.into_diagnostic("Not enough values to unpack");
|
||||
diag.set_primary_message(format_args!(
|
||||
"Expected {}",
|
||||
elts.len(),
|
||||
));
|
||||
diag.annotate(self.context.secondary(value_expr).message(
|
||||
format_args!("Got {}", tuple_ty_elements.len()),
|
||||
));
|
||||
}
|
||||
true
|
||||
true
|
||||
}
|
||||
Ordering::Greater => {
|
||||
if let Some(builder) =
|
||||
self.context.report_lint(&INVALID_ASSIGNMENT, target)
|
||||
{
|
||||
let mut diag =
|
||||
builder.into_diagnostic("Not enough values to unpack");
|
||||
diag.set_primary_message(format_args!(
|
||||
"Expected {}",
|
||||
elts.len(),
|
||||
));
|
||||
diag.annotate(
|
||||
self.context
|
||||
.secondary(value_expr)
|
||||
.message(format_args!("Got {}", tuple.len())),
|
||||
);
|
||||
}
|
||||
Ordering::Equal => false,
|
||||
};
|
||||
true
|
||||
}
|
||||
Ordering::Equal => false,
|
||||
};
|
||||
|
||||
for (index, ty) in tuple_ty_elements.iter().enumerate() {
|
||||
for (index, ty) in tuple.elements().enumerate() {
|
||||
if let Some(element_types) = target_types.get_mut(index) {
|
||||
if length_mismatch {
|
||||
element_types.push(Type::unknown());
|
||||
} else {
|
||||
element_types.push(*ty);
|
||||
element_types.push(ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -248,24 +251,36 @@ impl<'db, 'ast> Unpacker<'db, 'ast> {
|
|||
targets: &[ast::Expr],
|
||||
tuple_ty: TupleType<'db>,
|
||||
value_expr: AnyNodeRef<'_>,
|
||||
) -> Cow<'_, [Type<'db>]> {
|
||||
) -> Cow<'_, FixedLengthTupleSpec<'db>> {
|
||||
let TupleSpec::Fixed(tuple) = tuple_ty.tuple(self.db()) else {
|
||||
let todo = todo_type!("Unpack variable-length tuple");
|
||||
return Cow::Owned(FixedLengthTupleSpec::from_elements(targets.iter().map(
|
||||
|target| {
|
||||
if target.is_starred_expr() {
|
||||
KnownClass::List.to_specialized_instance(self.db(), [todo])
|
||||
} else {
|
||||
todo
|
||||
}
|
||||
},
|
||||
)));
|
||||
};
|
||||
|
||||
// If there is a starred expression, it will consume all of the types at that location.
|
||||
let Some(starred_index) = targets.iter().position(ast::Expr::is_starred_expr) else {
|
||||
// Otherwise, the types will be unpacked 1-1 to the targets.
|
||||
return Cow::Borrowed(tuple_ty.elements(self.db()));
|
||||
return Cow::Borrowed(tuple);
|
||||
};
|
||||
|
||||
if tuple_ty.len(self.db()) >= targets.len() - 1 {
|
||||
if tuple.len() >= targets.len() - 1 {
|
||||
// This branch is only taken when there are enough elements in the tuple type to
|
||||
// combine for the starred expression. So, the arithmetic and indexing operations are
|
||||
// safe to perform.
|
||||
let mut element_types = Vec::with_capacity(targets.len());
|
||||
let mut element_types = FixedLengthTupleSpec::with_capacity(targets.len());
|
||||
let tuple_elements = tuple.elements_slice();
|
||||
|
||||
// Insert all the elements before the starred expression.
|
||||
element_types.extend_from_slice(
|
||||
// SAFETY: Safe because of the length check above.
|
||||
&tuple_ty.elements(self.db())[..starred_index],
|
||||
);
|
||||
// SAFETY: Safe because of the length check above.
|
||||
element_types.extend_from_slice(&tuple_elements[..starred_index]);
|
||||
|
||||
// The number of target expressions that are remaining after the starred expression.
|
||||
// For example, in `(a, *b, c, d) = ...`, the index of starred element `b` is 1 and the
|
||||
|
@ -276,11 +291,10 @@ impl<'db, 'ast> Unpacker<'db, 'ast> {
|
|||
// expression, in an exclusive manner. For example, in `(a, *b, c) = (1, 2, 3, 4)`, the
|
||||
// starred expression `b` will consume the elements `Literal[2]` and `Literal[3]` and
|
||||
// the index value would be 3.
|
||||
let starred_end_index = tuple_ty.len(self.db()) - remaining;
|
||||
let starred_end_index = tuple.len() - remaining;
|
||||
|
||||
// SAFETY: Safe because of the length check above.
|
||||
let starred_element_types =
|
||||
&tuple_ty.elements(self.db())[starred_index..starred_end_index];
|
||||
let starred_element_types = &tuple_elements[starred_index..starred_end_index];
|
||||
|
||||
element_types.push(KnownClass::List.to_specialized_instance(
|
||||
self.db(),
|
||||
|
@ -292,10 +306,8 @@ impl<'db, 'ast> Unpacker<'db, 'ast> {
|
|||
));
|
||||
|
||||
// Insert the types remaining that aren't consumed by the starred expression.
|
||||
element_types.extend_from_slice(
|
||||
// SAFETY: Safe because of the length check above.
|
||||
&tuple_ty.elements(self.db())[starred_end_index..],
|
||||
);
|
||||
// SAFETY: Safe because of the length check above.
|
||||
element_types.extend_from_slice(&tuple_elements[starred_end_index..]);
|
||||
|
||||
Cow::Owned(element_types)
|
||||
} else {
|
||||
|
@ -305,22 +317,19 @@ impl<'db, 'ast> Unpacker<'db, 'ast> {
|
|||
diag.annotate(
|
||||
self.context
|
||||
.secondary(value_expr)
|
||||
.message(format_args!("Got {}", tuple_ty.len(self.db()))),
|
||||
.message(format_args!("Got {}", tuple.len())),
|
||||
);
|
||||
}
|
||||
|
||||
Cow::Owned(
|
||||
targets
|
||||
.iter()
|
||||
.map(|target| {
|
||||
if target.is_starred_expr() {
|
||||
KnownClass::List.to_specialized_instance(self.db(), [Type::unknown()])
|
||||
} else {
|
||||
Type::unknown()
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
Cow::Owned(FixedLengthTupleSpec::from_elements(targets.iter().map(
|
||||
|target| {
|
||||
if target.is_starred_expr() {
|
||||
KnownClass::List.to_specialized_instance(self.db(), [Type::unknown()])
|
||||
} else {
|
||||
Type::unknown()
|
||||
}
|
||||
},
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,13 +4,15 @@
|
|||
|
||||
use itertools::Either;
|
||||
|
||||
use crate::Db;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub(crate) struct OutOfBoundsError;
|
||||
|
||||
pub(crate) trait PyIndex {
|
||||
type Item;
|
||||
pub(crate) trait PyIndex<'db> {
|
||||
type Item: 'db;
|
||||
|
||||
fn py_index(&mut self, index: i32) -> Result<Self::Item, OutOfBoundsError>;
|
||||
fn py_index(self, db: &'db dyn Db, index: i32) -> Result<Self::Item, OutOfBoundsError>;
|
||||
}
|
||||
|
||||
fn from_nonnegative_i32(index: i32) -> usize {
|
||||
|
@ -39,13 +41,13 @@ enum Position {
|
|||
AfterEnd,
|
||||
}
|
||||
|
||||
enum Nth {
|
||||
pub(crate) enum Nth {
|
||||
FromStart(usize),
|
||||
FromEnd(usize),
|
||||
}
|
||||
|
||||
impl Nth {
|
||||
fn from_index(index: i32) -> Self {
|
||||
pub(crate) fn from_index(index: i32) -> Self {
|
||||
if index >= 0 {
|
||||
Nth::FromStart(from_nonnegative_i32(index))
|
||||
} else {
|
||||
|
@ -75,13 +77,26 @@ impl Nth {
|
|||
}
|
||||
}
|
||||
|
||||
impl<I, T> PyIndex for T
|
||||
impl<'db, T> PyIndex<'db> for &'db [T] {
|
||||
type Item = &'db T;
|
||||
|
||||
fn py_index(self, _db: &'db dyn Db, index: i32) -> Result<&'db T, OutOfBoundsError> {
|
||||
match Nth::from_index(index) {
|
||||
Nth::FromStart(nth) => self.get(nth).ok_or(OutOfBoundsError),
|
||||
Nth::FromEnd(nth_rev) => (self.len().checked_sub(nth_rev + 1))
|
||||
.map(|idx| &self[idx])
|
||||
.ok_or(OutOfBoundsError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db, I: 'db, T> PyIndex<'db> for &mut T
|
||||
where
|
||||
T: DoubleEndedIterator<Item = I>,
|
||||
{
|
||||
type Item = I;
|
||||
|
||||
fn py_index(&mut self, index: i32) -> Result<I, OutOfBoundsError> {
|
||||
fn py_index(self, _db: &'db dyn Db, index: i32) -> Result<I, OutOfBoundsError> {
|
||||
match Nth::from_index(index) {
|
||||
Nth::FromStart(nth) => self.nth(nth).ok_or(OutOfBoundsError),
|
||||
Nth::FromEnd(nth_rev) => self.nth_back(nth_rev).ok_or(OutOfBoundsError),
|
||||
|
@ -92,32 +107,28 @@ where
|
|||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub(crate) struct StepSizeZeroError;
|
||||
|
||||
pub(crate) trait PySlice {
|
||||
type Item;
|
||||
pub(crate) trait PySlice<'db> {
|
||||
type Item: 'db;
|
||||
|
||||
fn py_slice(
|
||||
&self,
|
||||
&'db self,
|
||||
db: &'db dyn Db,
|
||||
start: Option<i32>,
|
||||
stop: Option<i32>,
|
||||
step: Option<i32>,
|
||||
) -> Result<
|
||||
Either<impl Iterator<Item = &Self::Item>, impl Iterator<Item = &Self::Item>>,
|
||||
StepSizeZeroError,
|
||||
>;
|
||||
) -> Result<impl Iterator<Item = &'db Self::Item>, StepSizeZeroError>;
|
||||
}
|
||||
|
||||
impl<T> PySlice for [T] {
|
||||
impl<'db, T: 'db> PySlice<'db> for [T] {
|
||||
type Item = T;
|
||||
|
||||
fn py_slice(
|
||||
&self,
|
||||
&'db self,
|
||||
_db: &'db dyn Db,
|
||||
start: Option<i32>,
|
||||
stop: Option<i32>,
|
||||
step_int: Option<i32>,
|
||||
) -> Result<
|
||||
Either<impl Iterator<Item = &Self::Item>, impl Iterator<Item = &Self::Item>>,
|
||||
StepSizeZeroError,
|
||||
> {
|
||||
) -> Result<impl Iterator<Item = &'db Self::Item>, StepSizeZeroError> {
|
||||
let step_int = step_int.unwrap_or(1);
|
||||
if step_int == 0 {
|
||||
return Err(StepSizeZeroError);
|
||||
|
@ -194,6 +205,8 @@ impl<T> PySlice for [T] {
|
|||
#[cfg(test)]
|
||||
#[expect(clippy::redundant_clone)]
|
||||
mod tests {
|
||||
use crate::Db;
|
||||
use crate::db::tests::setup_db;
|
||||
use crate::util::subscript::{OutOfBoundsError, StepSizeZeroError};
|
||||
|
||||
use super::{PyIndex, PySlice};
|
||||
|
@ -201,302 +214,387 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn py_index_empty() {
|
||||
let db = setup_db();
|
||||
let iter = std::iter::empty::<char>();
|
||||
|
||||
assert_eq!(iter.clone().py_index(0), Err(OutOfBoundsError));
|
||||
assert_eq!(iter.clone().py_index(1), Err(OutOfBoundsError));
|
||||
assert_eq!(iter.clone().py_index(-1), Err(OutOfBoundsError));
|
||||
assert_eq!(iter.clone().py_index(i32::MIN), Err(OutOfBoundsError));
|
||||
assert_eq!(iter.clone().py_index(i32::MAX), Err(OutOfBoundsError));
|
||||
assert_eq!(iter.clone().py_index(&db, 0), Err(OutOfBoundsError));
|
||||
assert_eq!(iter.clone().py_index(&db, 1), Err(OutOfBoundsError));
|
||||
assert_eq!(iter.clone().py_index(&db, -1), Err(OutOfBoundsError));
|
||||
assert_eq!(iter.clone().py_index(&db, i32::MIN), Err(OutOfBoundsError));
|
||||
assert_eq!(iter.clone().py_index(&db, i32::MAX), Err(OutOfBoundsError));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn py_index_single_element() {
|
||||
let db = setup_db();
|
||||
let iter = ['a'].into_iter();
|
||||
|
||||
assert_eq!(iter.clone().py_index(0), Ok('a'));
|
||||
assert_eq!(iter.clone().py_index(1), Err(OutOfBoundsError));
|
||||
assert_eq!(iter.clone().py_index(-1), Ok('a'));
|
||||
assert_eq!(iter.clone().py_index(-2), Err(OutOfBoundsError));
|
||||
assert_eq!(iter.clone().py_index(&db, 0), Ok('a'));
|
||||
assert_eq!(iter.clone().py_index(&db, 1), Err(OutOfBoundsError));
|
||||
assert_eq!(iter.clone().py_index(&db, -1), Ok('a'));
|
||||
assert_eq!(iter.clone().py_index(&db, -2), Err(OutOfBoundsError));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn py_index_more_elements() {
|
||||
let db = setup_db();
|
||||
let iter = ['a', 'b', 'c', 'd', 'e'].into_iter();
|
||||
|
||||
assert_eq!(iter.clone().py_index(0), Ok('a'));
|
||||
assert_eq!(iter.clone().py_index(1), Ok('b'));
|
||||
assert_eq!(iter.clone().py_index(4), Ok('e'));
|
||||
assert_eq!(iter.clone().py_index(5), Err(OutOfBoundsError));
|
||||
assert_eq!(iter.clone().py_index(&db, 0), Ok('a'));
|
||||
assert_eq!(iter.clone().py_index(&db, 1), Ok('b'));
|
||||
assert_eq!(iter.clone().py_index(&db, 4), Ok('e'));
|
||||
assert_eq!(iter.clone().py_index(&db, 5), Err(OutOfBoundsError));
|
||||
|
||||
assert_eq!(iter.clone().py_index(-1), Ok('e'));
|
||||
assert_eq!(iter.clone().py_index(-2), Ok('d'));
|
||||
assert_eq!(iter.clone().py_index(-5), Ok('a'));
|
||||
assert_eq!(iter.clone().py_index(-6), Err(OutOfBoundsError));
|
||||
assert_eq!(iter.clone().py_index(&db, -1), Ok('e'));
|
||||
assert_eq!(iter.clone().py_index(&db, -2), Ok('d'));
|
||||
assert_eq!(iter.clone().py_index(&db, -5), Ok('a'));
|
||||
assert_eq!(iter.clone().py_index(&db, -6), Err(OutOfBoundsError));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn py_index_uses_full_index_range() {
|
||||
let db = setup_db();
|
||||
let iter = 0..=u32::MAX;
|
||||
|
||||
// u32::MAX - |i32::MIN| + 1 = 2^32 - 1 - 2^31 + 1 = 2^31
|
||||
assert_eq!(iter.clone().py_index(i32::MIN), Ok(2u32.pow(31)));
|
||||
assert_eq!(iter.clone().py_index(-2), Ok(u32::MAX - 2 + 1));
|
||||
assert_eq!(iter.clone().py_index(-1), Ok(u32::MAX - 1 + 1));
|
||||
assert_eq!(iter.clone().py_index(&db, i32::MIN), Ok(2u32.pow(31)));
|
||||
assert_eq!(iter.clone().py_index(&db, -2), Ok(u32::MAX - 2 + 1));
|
||||
assert_eq!(iter.clone().py_index(&db, -1), Ok(u32::MAX - 1 + 1));
|
||||
|
||||
assert_eq!(iter.clone().py_index(0), Ok(0));
|
||||
assert_eq!(iter.clone().py_index(1), Ok(1));
|
||||
assert_eq!(iter.clone().py_index(i32::MAX), Ok(i32::MAX as u32));
|
||||
assert_eq!(iter.clone().py_index(&db, 0), Ok(0));
|
||||
assert_eq!(iter.clone().py_index(&db, 1), Ok(1));
|
||||
assert_eq!(iter.clone().py_index(&db, i32::MAX), Ok(i32::MAX as u32));
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn assert_eq_slice<const N: usize, const M: usize>(
|
||||
db: &dyn Db,
|
||||
input: &[char; N],
|
||||
start: Option<i32>,
|
||||
stop: Option<i32>,
|
||||
step: Option<i32>,
|
||||
expected: &[char; M],
|
||||
) {
|
||||
assert_equal(input.py_slice(start, stop, step).unwrap(), expected.iter());
|
||||
assert_equal(
|
||||
input.py_slice(db, start, stop, step).unwrap(),
|
||||
expected.iter(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn py_slice_empty_input() {
|
||||
let db = setup_db();
|
||||
let input = [];
|
||||
|
||||
assert_eq_slice(&input, None, None, None, &[]);
|
||||
assert_eq_slice(&input, Some(0), None, None, &[]);
|
||||
assert_eq_slice(&input, None, Some(0), None, &[]);
|
||||
assert_eq_slice(&input, Some(0), Some(0), None, &[]);
|
||||
assert_eq_slice(&input, Some(-5), Some(-5), None, &[]);
|
||||
assert_eq_slice(&input, None, None, Some(-1), &[]);
|
||||
assert_eq_slice(&input, None, None, Some(2), &[]);
|
||||
assert_eq_slice(&db, &input, None, None, None, &[]);
|
||||
assert_eq_slice(&db, &input, Some(0), None, None, &[]);
|
||||
assert_eq_slice(&db, &input, None, Some(0), None, &[]);
|
||||
assert_eq_slice(&db, &input, Some(0), Some(0), None, &[]);
|
||||
assert_eq_slice(&db, &input, Some(-5), Some(-5), None, &[]);
|
||||
assert_eq_slice(&db, &input, None, None, Some(-1), &[]);
|
||||
assert_eq_slice(&db, &input, None, None, Some(2), &[]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn py_slice_single_element_input() {
|
||||
let db = setup_db();
|
||||
let input = ['a'];
|
||||
|
||||
assert_eq_slice(&input, None, None, None, &['a']);
|
||||
assert_eq_slice(&db, &input, None, None, None, &['a']);
|
||||
|
||||
assert_eq_slice(&input, Some(0), None, None, &['a']);
|
||||
assert_eq_slice(&input, None, Some(0), None, &[]);
|
||||
assert_eq_slice(&input, Some(0), Some(0), None, &[]);
|
||||
assert_eq_slice(&input, Some(0), Some(1), None, &['a']);
|
||||
assert_eq_slice(&input, Some(0), Some(2), None, &['a']);
|
||||
assert_eq_slice(&db, &input, Some(0), None, None, &['a']);
|
||||
assert_eq_slice(&db, &input, None, Some(0), None, &[]);
|
||||
assert_eq_slice(&db, &input, Some(0), Some(0), None, &[]);
|
||||
assert_eq_slice(&db, &input, Some(0), Some(1), None, &['a']);
|
||||
assert_eq_slice(&db, &input, Some(0), Some(2), None, &['a']);
|
||||
|
||||
assert_eq_slice(&input, Some(-1), None, None, &['a']);
|
||||
assert_eq_slice(&input, Some(-1), Some(-1), None, &[]);
|
||||
assert_eq_slice(&input, Some(-1), Some(0), None, &[]);
|
||||
assert_eq_slice(&input, Some(-1), Some(1), None, &['a']);
|
||||
assert_eq_slice(&input, Some(-1), Some(2), None, &['a']);
|
||||
assert_eq_slice(&input, None, Some(-1), None, &[]);
|
||||
assert_eq_slice(&db, &input, Some(-1), None, None, &['a']);
|
||||
assert_eq_slice(&db, &input, Some(-1), Some(-1), None, &[]);
|
||||
assert_eq_slice(&db, &input, Some(-1), Some(0), None, &[]);
|
||||
assert_eq_slice(&db, &input, Some(-1), Some(1), None, &['a']);
|
||||
assert_eq_slice(&db, &input, Some(-1), Some(2), None, &['a']);
|
||||
assert_eq_slice(&db, &input, None, Some(-1), None, &[]);
|
||||
|
||||
assert_eq_slice(&input, Some(-2), None, None, &['a']);
|
||||
assert_eq_slice(&input, Some(-2), Some(-1), None, &[]);
|
||||
assert_eq_slice(&input, Some(-2), Some(0), None, &[]);
|
||||
assert_eq_slice(&input, Some(-2), Some(1), None, &['a']);
|
||||
assert_eq_slice(&input, Some(-2), Some(2), None, &['a']);
|
||||
assert_eq_slice(&db, &input, Some(-2), None, None, &['a']);
|
||||
assert_eq_slice(&db, &input, Some(-2), Some(-1), None, &[]);
|
||||
assert_eq_slice(&db, &input, Some(-2), Some(0), None, &[]);
|
||||
assert_eq_slice(&db, &input, Some(-2), Some(1), None, &['a']);
|
||||
assert_eq_slice(&db, &input, Some(-2), Some(2), None, &['a']);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn py_slice_nonnegative_indices() {
|
||||
let db = setup_db();
|
||||
let input = ['a', 'b', 'c', 'd', 'e'];
|
||||
|
||||
assert_eq_slice(&input, None, Some(0), None, &[]);
|
||||
assert_eq_slice(&input, None, Some(1), None, &['a']);
|
||||
assert_eq_slice(&input, None, Some(4), None, &['a', 'b', 'c', 'd']);
|
||||
assert_eq_slice(&input, None, Some(5), None, &['a', 'b', 'c', 'd', 'e']);
|
||||
assert_eq_slice(&input, None, Some(6), None, &['a', 'b', 'c', 'd', 'e']);
|
||||
assert_eq_slice(&input, None, None, None, &['a', 'b', 'c', 'd', 'e']);
|
||||
assert_eq_slice(&db, &input, None, Some(0), None, &[]);
|
||||
assert_eq_slice(&db, &input, None, Some(1), None, &['a']);
|
||||
assert_eq_slice(&db, &input, None, Some(4), None, &['a', 'b', 'c', 'd']);
|
||||
assert_eq_slice(&db, &input, None, Some(5), None, &['a', 'b', 'c', 'd', 'e']);
|
||||
assert_eq_slice(&db, &input, None, Some(6), None, &['a', 'b', 'c', 'd', 'e']);
|
||||
assert_eq_slice(&db, &input, None, None, None, &['a', 'b', 'c', 'd', 'e']);
|
||||
|
||||
assert_eq_slice(&input, Some(0), Some(0), None, &[]);
|
||||
assert_eq_slice(&input, Some(0), Some(1), None, &['a']);
|
||||
assert_eq_slice(&input, Some(0), Some(4), None, &['a', 'b', 'c', 'd']);
|
||||
assert_eq_slice(&input, Some(0), Some(5), None, &['a', 'b', 'c', 'd', 'e']);
|
||||
assert_eq_slice(&input, Some(0), Some(6), None, &['a', 'b', 'c', 'd', 'e']);
|
||||
assert_eq_slice(&input, Some(0), None, None, &['a', 'b', 'c', 'd', 'e']);
|
||||
assert_eq_slice(&db, &input, Some(0), Some(0), None, &[]);
|
||||
assert_eq_slice(&db, &input, Some(0), Some(1), None, &['a']);
|
||||
assert_eq_slice(&db, &input, Some(0), Some(4), None, &['a', 'b', 'c', 'd']);
|
||||
assert_eq_slice(
|
||||
&db,
|
||||
&input,
|
||||
Some(0),
|
||||
Some(5),
|
||||
None,
|
||||
&['a', 'b', 'c', 'd', 'e'],
|
||||
);
|
||||
assert_eq_slice(
|
||||
&db,
|
||||
&input,
|
||||
Some(0),
|
||||
Some(6),
|
||||
None,
|
||||
&['a', 'b', 'c', 'd', 'e'],
|
||||
);
|
||||
assert_eq_slice(&db, &input, Some(0), None, None, &['a', 'b', 'c', 'd', 'e']);
|
||||
|
||||
assert_eq_slice(&input, Some(1), Some(0), None, &[]);
|
||||
assert_eq_slice(&input, Some(1), Some(1), None, &[]);
|
||||
assert_eq_slice(&input, Some(1), Some(2), None, &['b']);
|
||||
assert_eq_slice(&input, Some(1), Some(4), None, &['b', 'c', 'd']);
|
||||
assert_eq_slice(&input, Some(1), Some(5), None, &['b', 'c', 'd', 'e']);
|
||||
assert_eq_slice(&input, Some(1), Some(6), None, &['b', 'c', 'd', 'e']);
|
||||
assert_eq_slice(&input, Some(1), None, None, &['b', 'c', 'd', 'e']);
|
||||
assert_eq_slice(&db, &input, Some(1), Some(0), None, &[]);
|
||||
assert_eq_slice(&db, &input, Some(1), Some(1), None, &[]);
|
||||
assert_eq_slice(&db, &input, Some(1), Some(2), None, &['b']);
|
||||
assert_eq_slice(&db, &input, Some(1), Some(4), None, &['b', 'c', 'd']);
|
||||
assert_eq_slice(&db, &input, Some(1), Some(5), None, &['b', 'c', 'd', 'e']);
|
||||
assert_eq_slice(&db, &input, Some(1), Some(6), None, &['b', 'c', 'd', 'e']);
|
||||
assert_eq_slice(&db, &input, Some(1), None, None, &['b', 'c', 'd', 'e']);
|
||||
|
||||
assert_eq_slice(&input, Some(4), Some(0), None, &[]);
|
||||
assert_eq_slice(&input, Some(4), Some(4), None, &[]);
|
||||
assert_eq_slice(&input, Some(4), Some(5), None, &['e']);
|
||||
assert_eq_slice(&input, Some(4), Some(6), None, &['e']);
|
||||
assert_eq_slice(&input, Some(4), None, None, &['e']);
|
||||
assert_eq_slice(&db, &input, Some(4), Some(0), None, &[]);
|
||||
assert_eq_slice(&db, &input, Some(4), Some(4), None, &[]);
|
||||
assert_eq_slice(&db, &input, Some(4), Some(5), None, &['e']);
|
||||
assert_eq_slice(&db, &input, Some(4), Some(6), None, &['e']);
|
||||
assert_eq_slice(&db, &input, Some(4), None, None, &['e']);
|
||||
|
||||
assert_eq_slice(&input, Some(5), Some(0), None, &[]);
|
||||
assert_eq_slice(&input, Some(5), Some(5), None, &[]);
|
||||
assert_eq_slice(&input, Some(5), Some(6), None, &[]);
|
||||
assert_eq_slice(&input, Some(5), None, None, &[]);
|
||||
assert_eq_slice(&db, &input, Some(5), Some(0), None, &[]);
|
||||
assert_eq_slice(&db, &input, Some(5), Some(5), None, &[]);
|
||||
assert_eq_slice(&db, &input, Some(5), Some(6), None, &[]);
|
||||
assert_eq_slice(&db, &input, Some(5), None, None, &[]);
|
||||
|
||||
assert_eq_slice(&input, Some(6), Some(0), None, &[]);
|
||||
assert_eq_slice(&input, Some(6), Some(6), None, &[]);
|
||||
assert_eq_slice(&input, Some(6), None, None, &[]);
|
||||
assert_eq_slice(&db, &input, Some(6), Some(0), None, &[]);
|
||||
assert_eq_slice(&db, &input, Some(6), Some(6), None, &[]);
|
||||
assert_eq_slice(&db, &input, Some(6), None, None, &[]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn py_slice_negative_indices() {
|
||||
let db = setup_db();
|
||||
let input = ['a', 'b', 'c', 'd', 'e'];
|
||||
|
||||
assert_eq_slice(&input, Some(-6), None, None, &['a', 'b', 'c', 'd', 'e']);
|
||||
assert_eq_slice(&input, Some(-6), Some(-1), None, &['a', 'b', 'c', 'd']);
|
||||
assert_eq_slice(&input, Some(-6), Some(-4), None, &['a']);
|
||||
assert_eq_slice(&input, Some(-6), Some(-5), None, &[]);
|
||||
assert_eq_slice(&input, Some(-6), Some(-6), None, &[]);
|
||||
assert_eq_slice(&input, Some(-6), Some(-10), None, &[]);
|
||||
assert_eq_slice(
|
||||
&db,
|
||||
&input,
|
||||
Some(-6),
|
||||
None,
|
||||
None,
|
||||
&['a', 'b', 'c', 'd', 'e'],
|
||||
);
|
||||
assert_eq_slice(&db, &input, Some(-6), Some(-1), None, &['a', 'b', 'c', 'd']);
|
||||
assert_eq_slice(&db, &input, Some(-6), Some(-4), None, &['a']);
|
||||
assert_eq_slice(&db, &input, Some(-6), Some(-5), None, &[]);
|
||||
assert_eq_slice(&db, &input, Some(-6), Some(-6), None, &[]);
|
||||
assert_eq_slice(&db, &input, Some(-6), Some(-10), None, &[]);
|
||||
|
||||
assert_eq_slice(&input, Some(-5), None, None, &['a', 'b', 'c', 'd', 'e']);
|
||||
assert_eq_slice(&input, Some(-5), Some(-1), None, &['a', 'b', 'c', 'd']);
|
||||
assert_eq_slice(&input, Some(-5), Some(-4), None, &['a']);
|
||||
assert_eq_slice(&input, Some(-5), Some(-5), None, &[]);
|
||||
assert_eq_slice(&input, Some(-5), Some(-6), None, &[]);
|
||||
assert_eq_slice(&input, Some(-5), Some(-10), None, &[]);
|
||||
assert_eq_slice(
|
||||
&db,
|
||||
&input,
|
||||
Some(-5),
|
||||
None,
|
||||
None,
|
||||
&['a', 'b', 'c', 'd', 'e'],
|
||||
);
|
||||
assert_eq_slice(&db, &input, Some(-5), Some(-1), None, &['a', 'b', 'c', 'd']);
|
||||
assert_eq_slice(&db, &input, Some(-5), Some(-4), None, &['a']);
|
||||
assert_eq_slice(&db, &input, Some(-5), Some(-5), None, &[]);
|
||||
assert_eq_slice(&db, &input, Some(-5), Some(-6), None, &[]);
|
||||
assert_eq_slice(&db, &input, Some(-5), Some(-10), None, &[]);
|
||||
|
||||
assert_eq_slice(&input, Some(-4), None, None, &['b', 'c', 'd', 'e']);
|
||||
assert_eq_slice(&input, Some(-4), Some(-1), None, &['b', 'c', 'd']);
|
||||
assert_eq_slice(&input, Some(-4), Some(-3), None, &['b']);
|
||||
assert_eq_slice(&input, Some(-4), Some(-4), None, &[]);
|
||||
assert_eq_slice(&input, Some(-4), Some(-10), None, &[]);
|
||||
assert_eq_slice(&db, &input, Some(-4), None, None, &['b', 'c', 'd', 'e']);
|
||||
assert_eq_slice(&db, &input, Some(-4), Some(-1), None, &['b', 'c', 'd']);
|
||||
assert_eq_slice(&db, &input, Some(-4), Some(-3), None, &['b']);
|
||||
assert_eq_slice(&db, &input, Some(-4), Some(-4), None, &[]);
|
||||
assert_eq_slice(&db, &input, Some(-4), Some(-10), None, &[]);
|
||||
|
||||
assert_eq_slice(&input, Some(-1), None, None, &['e']);
|
||||
assert_eq_slice(&input, Some(-1), Some(-1), None, &[]);
|
||||
assert_eq_slice(&input, Some(-1), Some(-10), None, &[]);
|
||||
assert_eq_slice(&db, &input, Some(-1), None, None, &['e']);
|
||||
assert_eq_slice(&db, &input, Some(-1), Some(-1), None, &[]);
|
||||
assert_eq_slice(&db, &input, Some(-1), Some(-10), None, &[]);
|
||||
|
||||
assert_eq_slice(&input, None, Some(-1), None, &['a', 'b', 'c', 'd']);
|
||||
assert_eq_slice(&input, None, Some(-4), None, &['a']);
|
||||
assert_eq_slice(&input, None, Some(-5), None, &[]);
|
||||
assert_eq_slice(&input, None, Some(-6), None, &[]);
|
||||
assert_eq_slice(&db, &input, None, Some(-1), None, &['a', 'b', 'c', 'd']);
|
||||
assert_eq_slice(&db, &input, None, Some(-4), None, &['a']);
|
||||
assert_eq_slice(&db, &input, None, Some(-5), None, &[]);
|
||||
assert_eq_slice(&db, &input, None, Some(-6), None, &[]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn py_slice_mixed_positive_negative_indices() {
|
||||
let db = setup_db();
|
||||
let input = ['a', 'b', 'c', 'd', 'e'];
|
||||
|
||||
assert_eq_slice(&input, Some(0), Some(-1), None, &['a', 'b', 'c', 'd']);
|
||||
assert_eq_slice(&input, Some(1), Some(-1), None, &['b', 'c', 'd']);
|
||||
assert_eq_slice(&input, Some(3), Some(-1), None, &['d']);
|
||||
assert_eq_slice(&input, Some(4), Some(-1), None, &[]);
|
||||
assert_eq_slice(&input, Some(5), Some(-1), None, &[]);
|
||||
assert_eq_slice(&db, &input, Some(0), Some(-1), None, &['a', 'b', 'c', 'd']);
|
||||
assert_eq_slice(&db, &input, Some(1), Some(-1), None, &['b', 'c', 'd']);
|
||||
assert_eq_slice(&db, &input, Some(3), Some(-1), None, &['d']);
|
||||
assert_eq_slice(&db, &input, Some(4), Some(-1), None, &[]);
|
||||
assert_eq_slice(&db, &input, Some(5), Some(-1), None, &[]);
|
||||
|
||||
assert_eq_slice(&input, Some(0), Some(-4), None, &['a']);
|
||||
assert_eq_slice(&input, Some(1), Some(-4), None, &[]);
|
||||
assert_eq_slice(&input, Some(3), Some(-4), None, &[]);
|
||||
assert_eq_slice(&db, &input, Some(0), Some(-4), None, &['a']);
|
||||
assert_eq_slice(&db, &input, Some(1), Some(-4), None, &[]);
|
||||
assert_eq_slice(&db, &input, Some(3), Some(-4), None, &[]);
|
||||
|
||||
assert_eq_slice(&input, Some(0), Some(-5), None, &[]);
|
||||
assert_eq_slice(&input, Some(1), Some(-5), None, &[]);
|
||||
assert_eq_slice(&input, Some(3), Some(-5), None, &[]);
|
||||
assert_eq_slice(&db, &input, Some(0), Some(-5), None, &[]);
|
||||
assert_eq_slice(&db, &input, Some(1), Some(-5), None, &[]);
|
||||
assert_eq_slice(&db, &input, Some(3), Some(-5), None, &[]);
|
||||
|
||||
assert_eq_slice(&input, Some(0), Some(-6), None, &[]);
|
||||
assert_eq_slice(&input, Some(1), Some(-6), None, &[]);
|
||||
assert_eq_slice(&db, &input, Some(0), Some(-6), None, &[]);
|
||||
assert_eq_slice(&db, &input, Some(1), Some(-6), None, &[]);
|
||||
|
||||
assert_eq_slice(&input, Some(-6), Some(6), None, &['a', 'b', 'c', 'd', 'e']);
|
||||
assert_eq_slice(&input, Some(-6), Some(5), None, &['a', 'b', 'c', 'd', 'e']);
|
||||
assert_eq_slice(&input, Some(-6), Some(4), None, &['a', 'b', 'c', 'd']);
|
||||
assert_eq_slice(&input, Some(-6), Some(1), None, &['a']);
|
||||
assert_eq_slice(&input, Some(-6), Some(0), None, &[]);
|
||||
assert_eq_slice(
|
||||
&db,
|
||||
&input,
|
||||
Some(-6),
|
||||
Some(6),
|
||||
None,
|
||||
&['a', 'b', 'c', 'd', 'e'],
|
||||
);
|
||||
assert_eq_slice(
|
||||
&db,
|
||||
&input,
|
||||
Some(-6),
|
||||
Some(5),
|
||||
None,
|
||||
&['a', 'b', 'c', 'd', 'e'],
|
||||
);
|
||||
assert_eq_slice(&db, &input, Some(-6), Some(4), None, &['a', 'b', 'c', 'd']);
|
||||
assert_eq_slice(&db, &input, Some(-6), Some(1), None, &['a']);
|
||||
assert_eq_slice(&db, &input, Some(-6), Some(0), None, &[]);
|
||||
|
||||
assert_eq_slice(&input, Some(-5), Some(6), None, &['a', 'b', 'c', 'd', 'e']);
|
||||
assert_eq_slice(&input, Some(-5), Some(5), None, &['a', 'b', 'c', 'd', 'e']);
|
||||
assert_eq_slice(&input, Some(-5), Some(4), None, &['a', 'b', 'c', 'd']);
|
||||
assert_eq_slice(&input, Some(-5), Some(1), None, &['a']);
|
||||
assert_eq_slice(&input, Some(-5), Some(0), None, &[]);
|
||||
assert_eq_slice(
|
||||
&db,
|
||||
&input,
|
||||
Some(-5),
|
||||
Some(6),
|
||||
None,
|
||||
&['a', 'b', 'c', 'd', 'e'],
|
||||
);
|
||||
assert_eq_slice(
|
||||
&db,
|
||||
&input,
|
||||
Some(-5),
|
||||
Some(5),
|
||||
None,
|
||||
&['a', 'b', 'c', 'd', 'e'],
|
||||
);
|
||||
assert_eq_slice(&db, &input, Some(-5), Some(4), None, &['a', 'b', 'c', 'd']);
|
||||
assert_eq_slice(&db, &input, Some(-5), Some(1), None, &['a']);
|
||||
assert_eq_slice(&db, &input, Some(-5), Some(0), None, &[]);
|
||||
|
||||
assert_eq_slice(&input, Some(-4), Some(6), None, &['b', 'c', 'd', 'e']);
|
||||
assert_eq_slice(&input, Some(-4), Some(5), None, &['b', 'c', 'd', 'e']);
|
||||
assert_eq_slice(&input, Some(-4), Some(4), None, &['b', 'c', 'd']);
|
||||
assert_eq_slice(&input, Some(-4), Some(2), None, &['b']);
|
||||
assert_eq_slice(&input, Some(-4), Some(1), None, &[]);
|
||||
assert_eq_slice(&input, Some(-4), Some(0), None, &[]);
|
||||
assert_eq_slice(&db, &input, Some(-4), Some(6), None, &['b', 'c', 'd', 'e']);
|
||||
assert_eq_slice(&db, &input, Some(-4), Some(5), None, &['b', 'c', 'd', 'e']);
|
||||
assert_eq_slice(&db, &input, Some(-4), Some(4), None, &['b', 'c', 'd']);
|
||||
assert_eq_slice(&db, &input, Some(-4), Some(2), None, &['b']);
|
||||
assert_eq_slice(&db, &input, Some(-4), Some(1), None, &[]);
|
||||
assert_eq_slice(&db, &input, Some(-4), Some(0), None, &[]);
|
||||
|
||||
assert_eq_slice(&input, Some(-1), Some(6), None, &['e']);
|
||||
assert_eq_slice(&input, Some(-1), Some(5), None, &['e']);
|
||||
assert_eq_slice(&input, Some(-1), Some(4), None, &[]);
|
||||
assert_eq_slice(&input, Some(-1), Some(1), None, &[]);
|
||||
assert_eq_slice(&db, &input, Some(-1), Some(6), None, &['e']);
|
||||
assert_eq_slice(&db, &input, Some(-1), Some(5), None, &['e']);
|
||||
assert_eq_slice(&db, &input, Some(-1), Some(4), None, &[]);
|
||||
assert_eq_slice(&db, &input, Some(-1), Some(1), None, &[]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn py_slice_step_forward() {
|
||||
let db = setup_db();
|
||||
// indices: 0 1 2 3 4 5 6
|
||||
let input = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];
|
||||
|
||||
// Step size zero is invalid:
|
||||
assert!(matches!(
|
||||
input.py_slice(None, None, Some(0)),
|
||||
input.py_slice(&db, None, None, Some(0)),
|
||||
Err(StepSizeZeroError)
|
||||
));
|
||||
assert!(matches!(
|
||||
input.py_slice(Some(0), Some(5), Some(0)),
|
||||
input.py_slice(&db, Some(0), Some(5), Some(0)),
|
||||
Err(StepSizeZeroError)
|
||||
));
|
||||
assert!(matches!(
|
||||
input.py_slice(Some(0), Some(0), Some(0)),
|
||||
input.py_slice(&db, Some(0), Some(0), Some(0)),
|
||||
Err(StepSizeZeroError)
|
||||
));
|
||||
|
||||
assert_eq_slice(&input, Some(0), Some(8), Some(2), &['a', 'c', 'e', 'g']);
|
||||
assert_eq_slice(&input, Some(0), Some(7), Some(2), &['a', 'c', 'e', 'g']);
|
||||
assert_eq_slice(&input, Some(0), Some(6), Some(2), &['a', 'c', 'e']);
|
||||
assert_eq_slice(&input, Some(0), Some(5), Some(2), &['a', 'c', 'e']);
|
||||
assert_eq_slice(&input, Some(0), Some(4), Some(2), &['a', 'c']);
|
||||
assert_eq_slice(&input, Some(0), Some(3), Some(2), &['a', 'c']);
|
||||
assert_eq_slice(&input, Some(0), Some(2), Some(2), &['a']);
|
||||
assert_eq_slice(&input, Some(0), Some(1), Some(2), &['a']);
|
||||
assert_eq_slice(&input, Some(0), Some(0), Some(2), &[]);
|
||||
assert_eq_slice(&input, Some(1), Some(5), Some(2), &['b', 'd']);
|
||||
assert_eq_slice(
|
||||
&db,
|
||||
&input,
|
||||
Some(0),
|
||||
Some(8),
|
||||
Some(2),
|
||||
&['a', 'c', 'e', 'g'],
|
||||
);
|
||||
assert_eq_slice(
|
||||
&db,
|
||||
&input,
|
||||
Some(0),
|
||||
Some(7),
|
||||
Some(2),
|
||||
&['a', 'c', 'e', 'g'],
|
||||
);
|
||||
assert_eq_slice(&db, &input, Some(0), Some(6), Some(2), &['a', 'c', 'e']);
|
||||
assert_eq_slice(&db, &input, Some(0), Some(5), Some(2), &['a', 'c', 'e']);
|
||||
assert_eq_slice(&db, &input, Some(0), Some(4), Some(2), &['a', 'c']);
|
||||
assert_eq_slice(&db, &input, Some(0), Some(3), Some(2), &['a', 'c']);
|
||||
assert_eq_slice(&db, &input, Some(0), Some(2), Some(2), &['a']);
|
||||
assert_eq_slice(&db, &input, Some(0), Some(1), Some(2), &['a']);
|
||||
assert_eq_slice(&db, &input, Some(0), Some(0), Some(2), &[]);
|
||||
assert_eq_slice(&db, &input, Some(1), Some(5), Some(2), &['b', 'd']);
|
||||
|
||||
assert_eq_slice(&input, Some(0), Some(7), Some(3), &['a', 'd', 'g']);
|
||||
assert_eq_slice(&input, Some(0), Some(6), Some(3), &['a', 'd']);
|
||||
assert_eq_slice(&db, &input, Some(0), Some(7), Some(3), &['a', 'd', 'g']);
|
||||
assert_eq_slice(&db, &input, Some(0), Some(6), Some(3), &['a', 'd']);
|
||||
|
||||
assert_eq_slice(&input, Some(0), None, Some(10), &['a']);
|
||||
assert_eq_slice(&db, &input, Some(0), None, Some(10), &['a']);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn py_slice_step_backward() {
|
||||
let db = setup_db();
|
||||
// indices: 0 1 2 3 4 5 6
|
||||
let input = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];
|
||||
|
||||
assert_eq_slice(&input, Some(7), Some(0), Some(-2), &['g', 'e', 'c']);
|
||||
assert_eq_slice(&input, Some(6), Some(0), Some(-2), &['g', 'e', 'c']);
|
||||
assert_eq_slice(&input, Some(5), Some(0), Some(-2), &['f', 'd', 'b']);
|
||||
assert_eq_slice(&input, Some(4), Some(0), Some(-2), &['e', 'c']);
|
||||
assert_eq_slice(&input, Some(3), Some(0), Some(-2), &['d', 'b']);
|
||||
assert_eq_slice(&input, Some(2), Some(0), Some(-2), &['c']);
|
||||
assert_eq_slice(&input, Some(1), Some(0), Some(-2), &['b']);
|
||||
assert_eq_slice(&input, Some(0), Some(0), Some(-2), &[]);
|
||||
assert_eq_slice(&db, &input, Some(7), Some(0), Some(-2), &['g', 'e', 'c']);
|
||||
assert_eq_slice(&db, &input, Some(6), Some(0), Some(-2), &['g', 'e', 'c']);
|
||||
assert_eq_slice(&db, &input, Some(5), Some(0), Some(-2), &['f', 'd', 'b']);
|
||||
assert_eq_slice(&db, &input, Some(4), Some(0), Some(-2), &['e', 'c']);
|
||||
assert_eq_slice(&db, &input, Some(3), Some(0), Some(-2), &['d', 'b']);
|
||||
assert_eq_slice(&db, &input, Some(2), Some(0), Some(-2), &['c']);
|
||||
assert_eq_slice(&db, &input, Some(1), Some(0), Some(-2), &['b']);
|
||||
assert_eq_slice(&db, &input, Some(0), Some(0), Some(-2), &[]);
|
||||
|
||||
assert_eq_slice(&input, Some(7), None, Some(-2), &['g', 'e', 'c', 'a']);
|
||||
assert_eq_slice(&input, None, None, Some(-2), &['g', 'e', 'c', 'a']);
|
||||
assert_eq_slice(&input, None, Some(0), Some(-2), &['g', 'e', 'c']);
|
||||
assert_eq_slice(&db, &input, Some(7), None, Some(-2), &['g', 'e', 'c', 'a']);
|
||||
assert_eq_slice(&db, &input, None, None, Some(-2), &['g', 'e', 'c', 'a']);
|
||||
assert_eq_slice(&db, &input, None, Some(0), Some(-2), &['g', 'e', 'c']);
|
||||
|
||||
assert_eq_slice(&input, Some(5), Some(1), Some(-2), &['f', 'd']);
|
||||
assert_eq_slice(&input, Some(5), Some(2), Some(-2), &['f', 'd']);
|
||||
assert_eq_slice(&input, Some(5), Some(3), Some(-2), &['f']);
|
||||
assert_eq_slice(&input, Some(5), Some(4), Some(-2), &['f']);
|
||||
assert_eq_slice(&input, Some(5), Some(5), Some(-2), &[]);
|
||||
assert_eq_slice(&db, &input, Some(5), Some(1), Some(-2), &['f', 'd']);
|
||||
assert_eq_slice(&db, &input, Some(5), Some(2), Some(-2), &['f', 'd']);
|
||||
assert_eq_slice(&db, &input, Some(5), Some(3), Some(-2), &['f']);
|
||||
assert_eq_slice(&db, &input, Some(5), Some(4), Some(-2), &['f']);
|
||||
assert_eq_slice(&db, &input, Some(5), Some(5), Some(-2), &[]);
|
||||
|
||||
assert_eq_slice(&input, Some(6), None, Some(-3), &['g', 'd', 'a']);
|
||||
assert_eq_slice(&input, Some(6), Some(0), Some(-3), &['g', 'd']);
|
||||
assert_eq_slice(&db, &input, Some(6), None, Some(-3), &['g', 'd', 'a']);
|
||||
assert_eq_slice(&db, &input, Some(6), Some(0), Some(-3), &['g', 'd']);
|
||||
|
||||
assert_eq_slice(&input, Some(7), None, Some(-10), &['g']);
|
||||
assert_eq_slice(&db, &input, Some(7), None, Some(-10), &['g']);
|
||||
|
||||
assert_eq_slice(&input, Some(-6), Some(-9), Some(-1), &['b', 'a']);
|
||||
assert_eq_slice(&input, Some(-6), Some(-8), Some(-1), &['b', 'a']);
|
||||
assert_eq_slice(&input, Some(-6), Some(-7), Some(-1), &['b']);
|
||||
assert_eq_slice(&input, Some(-6), Some(-6), Some(-1), &[]);
|
||||
assert_eq_slice(&db, &input, Some(-6), Some(-9), Some(-1), &['b', 'a']);
|
||||
assert_eq_slice(&db, &input, Some(-6), Some(-8), Some(-1), &['b', 'a']);
|
||||
assert_eq_slice(&db, &input, Some(-6), Some(-7), Some(-1), &['b']);
|
||||
assert_eq_slice(&db, &input, Some(-6), Some(-6), Some(-1), &[]);
|
||||
|
||||
assert_eq_slice(&input, Some(-7), Some(-9), Some(-1), &['a']);
|
||||
assert_eq_slice(&db, &input, Some(-7), Some(-9), Some(-1), &['a']);
|
||||
|
||||
assert_eq_slice(&input, Some(-8), Some(-9), Some(-1), &[]);
|
||||
assert_eq_slice(&input, Some(-9), Some(-9), Some(-1), &[]);
|
||||
assert_eq_slice(&db, &input, Some(-8), Some(-9), Some(-1), &[]);
|
||||
assert_eq_slice(&db, &input, Some(-9), Some(-9), Some(-1), &[]);
|
||||
|
||||
assert_eq_slice(&input, Some(-6), Some(-2), Some(-1), &[]);
|
||||
assert_eq_slice(&input, Some(-9), Some(-6), Some(-1), &[]);
|
||||
assert_eq_slice(&db, &input, Some(-6), Some(-2), Some(-1), &[]);
|
||||
assert_eq_slice(&db, &input, Some(-9), Some(-6), Some(-1), &[]);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue