mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 13:24:57 +00:00
[ty] Add failing tests for tuple subclasses (#19803)
This commit is contained in:
parent
7b6abfb030
commit
c401a6d86e
7 changed files with 636 additions and 1 deletions
|
@ -164,6 +164,101 @@ def _(args: tuple[int, str]) -> None:
|
||||||
takes_at_least_two_positional_only(*args) # error: [invalid-argument-type]
|
takes_at_least_two_positional_only(*args) # error: [invalid-argument-type]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Subclass of fixed-length tuple argument
|
||||||
|
|
||||||
|
```py
|
||||||
|
def takes_zero() -> None: ...
|
||||||
|
def takes_one(x: int) -> None: ...
|
||||||
|
def takes_two(x: int, y: int) -> None: ...
|
||||||
|
def takes_two_positional_only(x: int, y: int, /) -> None: ...
|
||||||
|
def takes_two_different(x: int, y: str) -> None: ...
|
||||||
|
def takes_two_different_positional_only(x: int, y: str, /) -> None: ...
|
||||||
|
def takes_at_least_zero(*args) -> None: ...
|
||||||
|
def takes_at_least_one(x: int, *args) -> None: ...
|
||||||
|
def takes_at_least_two(x: int, y: int, *args) -> None: ...
|
||||||
|
def takes_at_least_two_positional_only(x: int, y: int, /, *args) -> None: ...
|
||||||
|
|
||||||
|
# Test all of the above with a number of different splatted argument types
|
||||||
|
|
||||||
|
class SingleElementTuple(tuple[int]): ...
|
||||||
|
|
||||||
|
def _(args: SingleElementTuple) -> None:
|
||||||
|
# TODO: we should emit `[too-many-positional-arguments]` here
|
||||||
|
takes_zero(*args)
|
||||||
|
|
||||||
|
takes_one(*args)
|
||||||
|
|
||||||
|
# TODO: we should emit `[missing-argument]` on both of these
|
||||||
|
takes_two(*args)
|
||||||
|
takes_two_positional_only(*args)
|
||||||
|
|
||||||
|
# TODO: these should both be `[missing-argument]`, not `[invalid-argument-type]`
|
||||||
|
takes_two_different(*args) # error: [invalid-argument-type]
|
||||||
|
takes_two_different_positional_only(*args) # error: [invalid-argument-type]
|
||||||
|
|
||||||
|
takes_at_least_zero(*args)
|
||||||
|
takes_at_least_one(*args)
|
||||||
|
|
||||||
|
# TODO: we should emit `[missing-argument]` on both of these
|
||||||
|
takes_at_least_two(*args)
|
||||||
|
takes_at_least_two_positional_only(*args)
|
||||||
|
|
||||||
|
class TwoElementIntTuple(tuple[int, int]): ...
|
||||||
|
|
||||||
|
def _(args: TwoElementIntTuple) -> None:
|
||||||
|
# TODO: we should emit `[too-many-positional-arguments]` on both of these
|
||||||
|
takes_zero(*args)
|
||||||
|
takes_one(*args)
|
||||||
|
|
||||||
|
takes_two(*args)
|
||||||
|
takes_two_positional_only(*args)
|
||||||
|
takes_two_different(*args) # error: [invalid-argument-type]
|
||||||
|
takes_two_different_positional_only(*args) # error: [invalid-argument-type]
|
||||||
|
takes_at_least_zero(*args)
|
||||||
|
takes_at_least_one(*args)
|
||||||
|
takes_at_least_two(*args)
|
||||||
|
takes_at_least_two_positional_only(*args)
|
||||||
|
|
||||||
|
class IntStrTuple(tuple[int, str]): ...
|
||||||
|
|
||||||
|
def _(args: IntStrTuple) -> None:
|
||||||
|
# TODO: we should emit `[too-many-positional-arguments]` here
|
||||||
|
takes_zero(*args)
|
||||||
|
|
||||||
|
# TODO: this should be `[too-many-positional-arguments]`, not `[invalid-argument-type]`
|
||||||
|
takes_one(*args) # error: [invalid-argument-type]
|
||||||
|
|
||||||
|
# TODO: we should have one diagnostic for each of these, not two
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
takes_two(*args)
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
takes_two_positional_only(*args)
|
||||||
|
|
||||||
|
# TODO: these are all false positives
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
takes_two_different(*args)
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
takes_two_different_positional_only(*args)
|
||||||
|
|
||||||
|
takes_at_least_zero(*args)
|
||||||
|
|
||||||
|
# TODO: false positive
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
takes_at_least_one(*args)
|
||||||
|
|
||||||
|
# TODO: we should only emit one diagnostic for each of these, not two
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
takes_at_least_two(*args)
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
takes_at_least_two_positional_only(*args)
|
||||||
|
```
|
||||||
|
|
||||||
### Mixed tuple argument
|
### Mixed tuple argument
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
|
@ -258,6 +353,197 @@ def _(args: tuple[int, *tuple[str, ...], int]) -> None:
|
||||||
takes_at_least_two_positional_only(*args) # error: [invalid-argument-type]
|
takes_at_least_two_positional_only(*args) # error: [invalid-argument-type]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Subclass of mixed tuple argument
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python-version = "3.11"
|
||||||
|
```
|
||||||
|
|
||||||
|
```py
|
||||||
|
def takes_zero() -> None: ...
|
||||||
|
def takes_one(x: int) -> None: ...
|
||||||
|
def takes_two(x: int, y: int) -> None: ...
|
||||||
|
def takes_two_positional_only(x: int, y: int, /) -> None: ...
|
||||||
|
def takes_two_different(x: int, y: str) -> None: ...
|
||||||
|
def takes_two_different_positional_only(x: int, y: str, /) -> None: ...
|
||||||
|
def takes_at_least_zero(*args) -> None: ...
|
||||||
|
def takes_at_least_one(x: int, *args) -> None: ...
|
||||||
|
def takes_at_least_two(x: int, y: int, *args) -> None: ...
|
||||||
|
def takes_at_least_two_positional_only(x: int, y: int, /, *args) -> None: ...
|
||||||
|
|
||||||
|
# Test all of the above with a number of different splatted argument types
|
||||||
|
|
||||||
|
class IntStarInt(tuple[int, *tuple[int, ...]]): ...
|
||||||
|
|
||||||
|
def _(args: IntStarInt) -> None:
|
||||||
|
# TODO: we should emit `[too-many-positional-arguments]` here
|
||||||
|
takes_zero(*args)
|
||||||
|
|
||||||
|
takes_one(*args)
|
||||||
|
takes_two(*args)
|
||||||
|
takes_two_positional_only(*args)
|
||||||
|
takes_two_different(*args) # error: [invalid-argument-type]
|
||||||
|
takes_two_different_positional_only(*args) # error: [invalid-argument-type]
|
||||||
|
takes_at_least_zero(*args)
|
||||||
|
takes_at_least_one(*args)
|
||||||
|
takes_at_least_two(*args)
|
||||||
|
takes_at_least_two_positional_only(*args)
|
||||||
|
|
||||||
|
class IntStarStr(tuple[int, *tuple[str, ...]]): ...
|
||||||
|
|
||||||
|
def _(args: IntStarStr) -> None:
|
||||||
|
# TODO: we should emit `[too-many-positional-arguments]` here
|
||||||
|
takes_zero(*args)
|
||||||
|
|
||||||
|
# TODO: false positive
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
takes_one(*args)
|
||||||
|
|
||||||
|
# TODO: we should only emit one diagnostic for each of these, not two
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
takes_two(*args)
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
takes_two_positional_only(*args)
|
||||||
|
|
||||||
|
# TODO: false positives
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
takes_two_different(*args)
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
takes_two_different_positional_only(*args)
|
||||||
|
|
||||||
|
takes_at_least_zero(*args)
|
||||||
|
|
||||||
|
# TODO: false positive
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
takes_at_least_one(*args)
|
||||||
|
|
||||||
|
# TODO: we should only have one diagnostic for each of these, not two
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
takes_at_least_two(*args)
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
takes_at_least_two_positional_only(*args)
|
||||||
|
|
||||||
|
class IntIntStarInt(tuple[int, int, *tuple[int, ...]]): ...
|
||||||
|
|
||||||
|
def _(args: IntIntStarInt) -> None:
|
||||||
|
# TODO: we should emit `[too-many-positional-arguments]` on both of these
|
||||||
|
takes_zero(*args)
|
||||||
|
takes_one(*args)
|
||||||
|
|
||||||
|
takes_two(*args)
|
||||||
|
takes_two_positional_only(*args)
|
||||||
|
takes_two_different(*args) # error: [invalid-argument-type]
|
||||||
|
takes_two_different_positional_only(*args) # error: [invalid-argument-type]
|
||||||
|
takes_at_least_zero(*args)
|
||||||
|
takes_at_least_one(*args)
|
||||||
|
takes_at_least_two(*args)
|
||||||
|
takes_at_least_two_positional_only(*args)
|
||||||
|
|
||||||
|
class IntIntStarStr(tuple[int, int, *tuple[str, ...]]): ...
|
||||||
|
|
||||||
|
def _(args: IntIntStarStr) -> None:
|
||||||
|
# TODO: we should emit `[too-many-positional-arguments]` here
|
||||||
|
takes_zero(*args)
|
||||||
|
|
||||||
|
# TODO: this should be `[too-many-positional-arguments]`, not `invalid-argument-type`
|
||||||
|
takes_one(*args) # error: [invalid-argument-type]
|
||||||
|
|
||||||
|
# TODO: these are all false positives
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
takes_two(*args)
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
takes_two_positional_only(*args)
|
||||||
|
|
||||||
|
# TODO: each of these should only have one diagnostic, not two
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
takes_two_different(*args)
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
takes_two_different_positional_only(*args)
|
||||||
|
|
||||||
|
takes_at_least_zero(*args)
|
||||||
|
|
||||||
|
# TODO: false positive
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
takes_at_least_one(*args)
|
||||||
|
|
||||||
|
# TODO: these are both false positives
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
takes_at_least_two(*args)
|
||||||
|
|
||||||
|
# TODO: these are both false positives
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
takes_at_least_two_positional_only(*args)
|
||||||
|
|
||||||
|
class IntStarIntInt(tuple[int, *tuple[int, ...], int]): ...
|
||||||
|
|
||||||
|
def _(args: IntStarIntInt) -> None:
|
||||||
|
# TODO: we should emit `[too-many-positional-arguments]` on both of these
|
||||||
|
takes_zero(*args)
|
||||||
|
takes_one(*args)
|
||||||
|
|
||||||
|
takes_two(*args)
|
||||||
|
takes_two_positional_only(*args)
|
||||||
|
takes_two_different(*args) # error: [invalid-argument-type]
|
||||||
|
takes_two_different_positional_only(*args) # error: [invalid-argument-type]
|
||||||
|
takes_at_least_zero(*args)
|
||||||
|
takes_at_least_one(*args)
|
||||||
|
takes_at_least_two(*args)
|
||||||
|
takes_at_least_two_positional_only(*args)
|
||||||
|
|
||||||
|
class IntStarStrInt(tuple[int, *tuple[str, ...], int]): ...
|
||||||
|
|
||||||
|
def _(args: IntStarStrInt) -> None:
|
||||||
|
# TODO: we should emit `too-many-positional-arguments` here
|
||||||
|
takes_zero(*args)
|
||||||
|
|
||||||
|
# TODO: this should be `too-many-positional-arguments`, not `invalid-argument-type`
|
||||||
|
takes_one(*args) # error: [invalid-argument-type]
|
||||||
|
|
||||||
|
# TODO: we should only emit one diagnostic for each of these
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
takes_two(*args)
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
takes_two_positional_only(*args)
|
||||||
|
|
||||||
|
# TODO: we should not emit diagnostics for these
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
takes_two_different(*args)
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
takes_two_different_positional_only(*args)
|
||||||
|
|
||||||
|
takes_at_least_zero(*args)
|
||||||
|
|
||||||
|
# TODO: false positive
|
||||||
|
takes_at_least_one(*args) # error: [invalid-argument-type]
|
||||||
|
|
||||||
|
# TODO: should only have one diagnostic here
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
takes_at_least_two(*args)
|
||||||
|
|
||||||
|
# TODO: should only have one diagnostic here
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
takes_at_least_two_positional_only(*args)
|
||||||
|
```
|
||||||
|
|
||||||
### String argument
|
### String argument
|
||||||
|
|
||||||
```py
|
```py
|
||||||
|
|
|
@ -392,6 +392,33 @@ class C(tuple[T, U]): ...
|
||||||
reveal_type(C((1, 2))) # revealed: C[int, int]
|
reveal_type(C((1, 2))) # revealed: C[int, int]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Upcasting a `tuple` to its `Sequence` supertype
|
||||||
|
|
||||||
|
This test is taken from the
|
||||||
|
[typing spec conformance suite](https://github.com/python/typing/blob/c141cdfb9d7085c1aafa76726c8ce08362837e8b/conformance/tests/tuples_type_compat.py#L133-L153)
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python-version = "3.11"
|
||||||
|
```
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import TypeVar, Sequence, Never
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
def test_seq(x: Sequence[T]) -> Sequence[T]:
|
||||||
|
return x
|
||||||
|
|
||||||
|
def func8(t1: tuple[complex, list[int]], t2: tuple[int, *tuple[str, ...]], t3: tuple[()]):
|
||||||
|
# TODO: should be `Sequence[int | float | complex | list[int]]`
|
||||||
|
reveal_type(test_seq(t1)) # revealed: Sequence[Unknown]
|
||||||
|
# TODO: should be `Sequence[int | str]`
|
||||||
|
reveal_type(test_seq(t2)) # revealed: Sequence[Unknown]
|
||||||
|
# TODO: this should be `Sequence[Never]`
|
||||||
|
reveal_type(test_seq(t3)) # revealed: Sequence[Unknown]
|
||||||
|
```
|
||||||
|
|
||||||
### `__init__` is itself generic
|
### `__init__` is itself generic
|
||||||
|
|
||||||
```py
|
```py
|
||||||
|
|
|
@ -355,6 +355,26 @@ class C[T, U](tuple[T, U]): ...
|
||||||
reveal_type(C((1, 2))) # revealed: C[int, int]
|
reveal_type(C((1, 2))) # revealed: C[int, int]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Upcasting a `tuple` to its `Sequence` supertype
|
||||||
|
|
||||||
|
This test is taken from the
|
||||||
|
[typing spec conformance suite](https://github.com/python/typing/blob/c141cdfb9d7085c1aafa76726c8ce08362837e8b/conformance/tests/tuples_type_compat.py#L133-L153)
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import Sequence, Never
|
||||||
|
|
||||||
|
def test_seq[T](x: Sequence[T]) -> Sequence[T]:
|
||||||
|
return x
|
||||||
|
|
||||||
|
def func8(t1: tuple[complex, list[int]], t2: tuple[int, *tuple[str, ...]], t3: tuple[()]):
|
||||||
|
# TODO: should be `Sequence[int | float | complex | list[int]]`
|
||||||
|
reveal_type(test_seq(t1)) # revealed: Sequence[Unknown]
|
||||||
|
# TODO: should be `Sequence[int | str]`
|
||||||
|
reveal_type(test_seq(t2)) # revealed: Sequence[Unknown]
|
||||||
|
# TODO: this should be `Sequence[Never]`
|
||||||
|
reveal_type(test_seq(t3)) # revealed: Sequence[Unknown]
|
||||||
|
```
|
||||||
|
|
||||||
### `__init__` is itself generic
|
### `__init__` is itself generic
|
||||||
|
|
||||||
```py
|
```py
|
||||||
|
|
|
@ -217,9 +217,89 @@ def _(m: int, n: int):
|
||||||
|
|
||||||
tuple_slice = t[m:n]
|
tuple_slice = t[m:n]
|
||||||
reveal_type(tuple_slice) # revealed: tuple[Literal[1, "a", b"b"] | None, ...]
|
reveal_type(tuple_slice) # revealed: tuple[Literal[1, "a", b"b"] | None, ...]
|
||||||
|
|
||||||
|
class I0: ...
|
||||||
|
class I1: ...
|
||||||
|
class I2: ...
|
||||||
|
class I3: ...
|
||||||
|
class HeterogeneousTupleSubclass(tuple[I0, I1, I2, I3]): ...
|
||||||
|
|
||||||
|
def __(t: HeterogeneousTupleSubclass, m: int, n: int):
|
||||||
|
# TODO: should be `tuple[()]`
|
||||||
|
reveal_type(t[0:0]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||||
|
# TODO: should be `tuple[I0]`
|
||||||
|
reveal_type(t[0:1]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||||
|
# TODO: should be `tuple[I0, I1]`
|
||||||
|
reveal_type(t[0:2]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||||
|
# TODO: should be tuple[I0, I1, I2, I3]`
|
||||||
|
reveal_type(t[0:4]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||||
|
# TODO: should be tuple[I0, I1, I2, I3]`
|
||||||
|
reveal_type(t[0:5]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||||
|
# TODO: should be `tuple[I0, I1]`
|
||||||
|
reveal_type(t[1:3]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||||
|
|
||||||
|
# TODO: should be `tuple[I2, I3]`
|
||||||
|
reveal_type(t[-2:4]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||||
|
# TODO: should be `tuple[I1, I2]`
|
||||||
|
reveal_type(t[-3:-1]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||||
|
# TODO: should be `tuple[I0, I1, I2, I3]`
|
||||||
|
reveal_type(t[-10:10]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||||
|
|
||||||
|
# TODO: should be `tuple[I0, I1, I2, I3]`
|
||||||
|
reveal_type(t[0:]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||||
|
# TODO: should be `tuple[I2, I3]`
|
||||||
|
reveal_type(t[2:]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||||
|
# TODO: should be `tuple[()]`
|
||||||
|
reveal_type(t[4:]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||||
|
# TODO: should be `tuple[()]`
|
||||||
|
reveal_type(t[:0]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||||
|
# TODO: should be `tuple[I0, I1]`
|
||||||
|
reveal_type(t[:2]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||||
|
# TODO: should be `tuple[I0, I1, I2, I3]`
|
||||||
|
reveal_type(t[:10]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||||
|
# TODO: should be `tuple[I0, I1, I2, I3]`
|
||||||
|
reveal_type(t[:]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||||
|
|
||||||
|
# TODO: should be `tuple[I3, I2, I1, I0]`
|
||||||
|
reveal_type(t[::-1]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||||
|
# TODO: should be `tuple[I0, I2]`
|
||||||
|
reveal_type(t[::2]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||||
|
# TODO: should be `tuple[I2, I1, I0]`
|
||||||
|
reveal_type(t[-2:-5:-1]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||||
|
# TODO: should be `tuple[I3, I1]`
|
||||||
|
reveal_type(t[::-2]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||||
|
# TODO: should be `tuple[I3, I0]`
|
||||||
|
reveal_type(t[-1::-3]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||||
|
|
||||||
|
# TODO: should be `tuple[I0, I1]`
|
||||||
|
reveal_type(t[None:2:None]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||||
|
# TODO: should be `tuple[I1, I2, I3]`
|
||||||
|
reveal_type(t[1:None:1]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||||
|
# TODO: should be `tuple[I0, I1, I2, I3]`
|
||||||
|
reveal_type(t[None:None:None]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||||
|
|
||||||
|
start = 1
|
||||||
|
stop = None
|
||||||
|
step = 2
|
||||||
|
# TODO: should be `tuple[I1, I3]`
|
||||||
|
reveal_type(t[start:stop:step]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||||
|
|
||||||
|
# TODO: should be `tuple[I0]`
|
||||||
|
reveal_type(t[False:True]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||||
|
# TODO: should be `tuple[I1, I2]`
|
||||||
|
reveal_type(t[True:3]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||||
|
|
||||||
|
# TODO: we should emit `zero-stepsize-in-slice` on all of these:
|
||||||
|
t[0:4:0]
|
||||||
|
t[:4:0]
|
||||||
|
t[0::0]
|
||||||
|
t[::0]
|
||||||
|
|
||||||
|
tuple_slice = t[m:n]
|
||||||
|
reveal_type(tuple_slice) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Slices of homogeneous and mixed tuples
|
## Indexes into homogeneous and mixed tuples
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[environment]
|
[environment]
|
||||||
|
|
|
@ -477,4 +477,52 @@ class NotAlwaysTruthyTuple(tuple[int]):
|
||||||
t: tuple[int] = NotAlwaysTruthyTuple((1,))
|
t: tuple[int] = NotAlwaysTruthyTuple((1,))
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Unspecialized
|
||||||
|
|
||||||
|
An unspecialized tuple is equivalent to `tuple[Any, ...]` and `tuple[Unknown, ...]`.
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing_extensions import Any, assert_type
|
||||||
|
from ty_extensions import Unknown, is_equivalent_to, static_assert
|
||||||
|
|
||||||
|
static_assert(is_equivalent_to(tuple[Any, ...], tuple[Unknown, ...]))
|
||||||
|
|
||||||
|
def f(x: tuple, y: tuple[Unknown, ...]):
|
||||||
|
reveal_type(x) # revealed: tuple[Unknown, ...]
|
||||||
|
assert_type(x, tuple[Any, ...])
|
||||||
|
assert_type(x, tuple[Unknown, ...])
|
||||||
|
reveal_type(y) # revealed: tuple[Unknown, ...]
|
||||||
|
assert_type(y, tuple[Any, ...])
|
||||||
|
assert_type(y, tuple[Unknown, ...])
|
||||||
|
```
|
||||||
|
|
||||||
|
## Converting a `tuple` to another `Sequence` type
|
||||||
|
|
||||||
|
For covariant types, such as `frozenset`, the ideal behaviour would be to not promote `Literal`
|
||||||
|
types to their instance supertypes: doing so causes more false positives than it fixes:
|
||||||
|
|
||||||
|
```py
|
||||||
|
# TODO: should be `frozenset[Literal[1, 2, 3]]`
|
||||||
|
reveal_type(frozenset((1, 2, 3))) # revealed: frozenset[Unknown]
|
||||||
|
# TODO: should be `frozenset[tuple[Literal[1], Literal[2], Literal[3]]]`
|
||||||
|
reveal_type(frozenset(((1, 2, 3),))) # revealed: frozenset[Unknown]
|
||||||
|
```
|
||||||
|
|
||||||
|
Literals are always promoted for invariant containers such as `list`, however, even though this can
|
||||||
|
in some cases cause false positives:
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
|
# TODO: should be `list[int]`
|
||||||
|
reveal_type(list((1, 2, 3))) # revealed: list[Unknown]
|
||||||
|
# TODO: should be `list[tuple[int, int, int]]`
|
||||||
|
reveal_type(list(((1, 2, 3),))) # revealed: list[Unknown]
|
||||||
|
|
||||||
|
x: list[Literal[1, 2, 3]] = list((1, 2, 3))
|
||||||
|
|
||||||
|
# TODO: should be `list[Literal[1, 2, 3]]`
|
||||||
|
reveal_type(x) # revealed: list[Unknown]
|
||||||
|
```
|
||||||
|
|
||||||
[not a singleton type]: https://discuss.python.org/t/should-we-specify-in-the-language-reference-that-the-empty-tuple-is-a-singleton/67957
|
[not a singleton type]: https://discuss.python.org/t/should-we-specify-in-the-language-reference-that-the-empty-tuple-is-a-singleton/67957
|
||||||
|
|
|
@ -16,6 +16,21 @@ static_assert(is_single_valued(Literal[b"abc"]))
|
||||||
static_assert(is_single_valued(tuple[()]))
|
static_assert(is_single_valued(tuple[()]))
|
||||||
static_assert(is_single_valued(tuple[Literal[True], Literal[1]]))
|
static_assert(is_single_valued(tuple[Literal[True], Literal[1]]))
|
||||||
|
|
||||||
|
class EmptyTupleSubclass(tuple[()]): ...
|
||||||
|
class HeterogeneousTupleSubclass(tuple[Literal[True], Literal[1]]): ...
|
||||||
|
|
||||||
|
# N.B. this follows from the fact that `EmptyTupleSubclass` is a subtype of `tuple[()]`,
|
||||||
|
# and any property recognised for `tuple[()]` should therefore also be recognised for
|
||||||
|
# `EmptyTupleSubclass` since an `EmptyTupleSubclass` instance can be used anywhere where
|
||||||
|
# `tuple[()]` is accepted. This is only sound, however, if we ban `__eq__` and `__ne__`
|
||||||
|
# from being overridden on a tuple subclass. This is something we plan to do as part of
|
||||||
|
# our implementation of the Liskov Substitution Principle
|
||||||
|
# (https://github.com/astral-sh/ty/issues/166)
|
||||||
|
#
|
||||||
|
# TODO: these should pass
|
||||||
|
static_assert(is_single_valued(EmptyTupleSubclass)) # error: [static-assert-error]
|
||||||
|
static_assert(is_single_valued(HeterogeneousTupleSubclass)) # error: [static-assert-error]
|
||||||
|
|
||||||
static_assert(not is_single_valued(str))
|
static_assert(not is_single_valued(str))
|
||||||
static_assert(not is_single_valued(Never))
|
static_assert(not is_single_valued(Never))
|
||||||
static_assert(not is_single_valued(Any))
|
static_assert(not is_single_valued(Any))
|
||||||
|
@ -24,6 +39,10 @@ static_assert(not is_single_valued(Literal[1, 2]))
|
||||||
|
|
||||||
static_assert(not is_single_valued(tuple[None, int]))
|
static_assert(not is_single_valued(tuple[None, int]))
|
||||||
|
|
||||||
|
class MultiValuedHeterogeneousTupleSubclass(tuple[None, int]): ...
|
||||||
|
|
||||||
|
static_assert(not is_single_valued(MultiValuedHeterogeneousTupleSubclass))
|
||||||
|
|
||||||
static_assert(not is_single_valued(Callable[..., None]))
|
static_assert(not is_single_valued(Callable[..., None]))
|
||||||
static_assert(not is_single_valued(Callable[[int, str], None]))
|
static_assert(not is_single_valued(Callable[[int, str], None]))
|
||||||
|
|
||||||
|
|
|
@ -407,6 +407,161 @@ def _(value: tuple[int, int, *tuple[str, ...], int]):
|
||||||
reveal_type(c) # revealed: int
|
reveal_type(c) # revealed: int
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Tuple subclasses
|
||||||
|
|
||||||
|
A tuple subclass inherits its heterogeneous unpacking behaviour from its tuple superclass.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python-version = "3.11"
|
||||||
|
```
|
||||||
|
|
||||||
|
```py
|
||||||
|
class I0: ...
|
||||||
|
class I1: ...
|
||||||
|
class I2: ...
|
||||||
|
class HeterogeneousTupleSubclass(tuple[I0, I1, I2]): ...
|
||||||
|
|
||||||
|
def f(x: HeterogeneousTupleSubclass):
|
||||||
|
a, b, c = x
|
||||||
|
|
||||||
|
# TODO: should be `I0`
|
||||||
|
reveal_type(a) # revealed: I0 | I1 | I2
|
||||||
|
# TODO: should be `I1`
|
||||||
|
reveal_type(b) # revealed: I0 | I1 | I2
|
||||||
|
# TODO: should be `I2`
|
||||||
|
reveal_type(c) # revealed: I0 | I1 | I2
|
||||||
|
|
||||||
|
# TODO: should emit a diagnostic ([invalid-assignment] "Too many values to unpack: Expected 2")
|
||||||
|
d, e = x
|
||||||
|
|
||||||
|
reveal_type(d) # revealed: I0 | I1 | I2
|
||||||
|
reveal_type(e) # revealed: I0 | I1 | I2
|
||||||
|
|
||||||
|
# TODO: should emit a diagnostic ([invalid-assignment] "Not enough values to unpack: Expected 4")
|
||||||
|
f, g, h, i = x
|
||||||
|
|
||||||
|
reveal_type(f) # revealed: I0 | I1 | I2
|
||||||
|
reveal_type(g) # revealed: I0 | I1 | I2
|
||||||
|
reveal_type(h) # revealed: I0 | I1 | I2
|
||||||
|
reveal_type(i) # revealed: I0 | I1 | I2
|
||||||
|
|
||||||
|
[j, *k] = x
|
||||||
|
|
||||||
|
# TODO: should be `I0`
|
||||||
|
reveal_type(j) # revealed: I0 | I1 | I2
|
||||||
|
# TODO: should be `list[I1 | I2]`
|
||||||
|
reveal_type(k) # revealed: list[I0 | I1 | I2]
|
||||||
|
|
||||||
|
[l, m, *n] = x
|
||||||
|
|
||||||
|
# TODO: should be `I0`
|
||||||
|
reveal_type(l) # revealed: I0 | I1 | I2
|
||||||
|
# TODO: should be `I1`
|
||||||
|
reveal_type(m) # revealed: I0 | I1 | I2
|
||||||
|
# TODO: should be `list[I2]`
|
||||||
|
reveal_type(n) # revealed: list[I0 | I1 | I2]
|
||||||
|
|
||||||
|
[o, p, q, *r] = x
|
||||||
|
|
||||||
|
# TODO: should be `I0`
|
||||||
|
reveal_type(o) # revealed: I0 | I1 | I2
|
||||||
|
# TODO: should be `I1`
|
||||||
|
reveal_type(p) # revealed: I0 | I1 | I2
|
||||||
|
# TODO: should be `I2`
|
||||||
|
reveal_type(q) # revealed: I0 | I1 | I2
|
||||||
|
# TODO: should be `list[Never]`
|
||||||
|
reveal_type(r) # revealed: list[I0 | I1 | I2]
|
||||||
|
|
||||||
|
# TODO: should emit a diagnostic ([invalid-assignment] "Not enough values to unpack: Expected at least 4")
|
||||||
|
[s, t, u, v, *w] = x
|
||||||
|
reveal_type(s) # revealed: I0 | I1 | I2
|
||||||
|
reveal_type(t) # revealed: I0 | I1 | I2
|
||||||
|
reveal_type(u) # revealed: I0 | I1 | I2
|
||||||
|
reveal_type(v) # revealed: I0 | I1 | I2
|
||||||
|
reveal_type(w) # revealed: list[I0 | I1 | I2]
|
||||||
|
|
||||||
|
class MixedTupleSubclass(tuple[I0, *tuple[I1, ...], I2]): ...
|
||||||
|
|
||||||
|
def f(x: MixedTupleSubclass):
|
||||||
|
# TODO: should emit a diagnostic: ([invalid-assignment] "Too many values to unpack: Expected 1"`)
|
||||||
|
(a,) = x
|
||||||
|
reveal_type(a) # revealed: I0 | I1 | I2
|
||||||
|
|
||||||
|
c, d = x
|
||||||
|
# TODO: should be `I0`
|
||||||
|
reveal_type(c) # revealed: I0 | I1 | I2
|
||||||
|
# TODO: should be `I2`
|
||||||
|
reveal_type(d) # revealed: I0 | I1 | I2
|
||||||
|
|
||||||
|
e, f, g = x
|
||||||
|
|
||||||
|
# TODO: should be `I0`
|
||||||
|
reveal_type(e) # revealed: I0 | I1 | I2
|
||||||
|
# TODO: should be `I1`
|
||||||
|
reveal_type(f) # revealed: I0 | I1 | I2
|
||||||
|
# TODO: should be `I2`
|
||||||
|
reveal_type(g) # revealed: I0 | I1 | I2
|
||||||
|
|
||||||
|
h, i, j, k = x
|
||||||
|
|
||||||
|
# TODO: should be `I0`
|
||||||
|
reveal_type(h) # revealed: I0 | I1 | I2
|
||||||
|
# TODO: should be `I1`
|
||||||
|
reveal_type(i) # revealed: I0 | I1 | I2
|
||||||
|
# TODO: should be `I1`
|
||||||
|
reveal_type(j) # revealed: I0 | I1 | I2
|
||||||
|
# TODO: should be `I2`
|
||||||
|
reveal_type(k) # revealed: I0 | I1 | I2
|
||||||
|
|
||||||
|
[l, *m] = x
|
||||||
|
|
||||||
|
# TODO: should be `I0`
|
||||||
|
reveal_type(l) # revealed: I0 | I1 | I2
|
||||||
|
# TODO: should be `list[I1 | I2]`
|
||||||
|
reveal_type(m) # revealed: list[I0 | I1 | I2]
|
||||||
|
|
||||||
|
[n, o, *p] = x
|
||||||
|
|
||||||
|
# TODO: should be `I0`
|
||||||
|
reveal_type(n) # revealed: I0 | I1 | I2
|
||||||
|
# TODO: should be `I1 | I2`
|
||||||
|
reveal_type(o) # revealed: I0 | I1 | I2
|
||||||
|
# TODO: should be `list[I1 | I2]`
|
||||||
|
reveal_type(p) # revealed: list[I0 | I1 | I2]
|
||||||
|
|
||||||
|
[o, p, q, *r] = x
|
||||||
|
|
||||||
|
# TODO: should be `I0`
|
||||||
|
reveal_type(o) # revealed: I0 | I1 | I2
|
||||||
|
# TODO: should be `I1 | I2`
|
||||||
|
reveal_type(p) # revealed: I0 | I1 | I2
|
||||||
|
# TODO: should be `I1 | I2
|
||||||
|
reveal_type(q) # revealed: I0 | I1 | I2
|
||||||
|
# TODO: should be `list[I1 | I2]
|
||||||
|
reveal_type(r) # revealed: list[I0 | I1 | I2]
|
||||||
|
|
||||||
|
s, *t, u = x
|
||||||
|
|
||||||
|
# TODO: should be `I0`
|
||||||
|
reveal_type(s) # revealed: I0 | I1 | I2
|
||||||
|
# TODO: should be `list[I1]`
|
||||||
|
reveal_type(t) # revealed: list[I0 | I1 | I2]
|
||||||
|
# TODO: should be `I2`
|
||||||
|
reveal_type(u) # revealed: I0 | I1 | I2
|
||||||
|
|
||||||
|
aa, bb, *cc, dd = x
|
||||||
|
|
||||||
|
# TODO: should be `I0`
|
||||||
|
reveal_type(aa) # revealed: I0 | I1 | I2
|
||||||
|
# TODO: should be `I1`
|
||||||
|
reveal_type(bb) # revealed: I0 | I1 | I2
|
||||||
|
# TODO: should be `list[I1]`
|
||||||
|
reveal_type(cc) # revealed: list[I0 | I1 | I2]
|
||||||
|
# TODO: should be I2
|
||||||
|
reveal_type(dd) # revealed: I0 | I1 | I2
|
||||||
|
```
|
||||||
|
|
||||||
## String
|
## String
|
||||||
|
|
||||||
### Simple unpacking
|
### Simple unpacking
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue