[ty] Remove Type::Tuple (#19669)

This commit is contained in:
Alex Waygood 2025-08-11 22:03:32 +01:00 committed by GitHub
parent 2abd683376
commit d2fbf2af8f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 1189 additions and 1225 deletions

View file

@ -183,33 +183,27 @@ def takes_at_least_two_positional_only(x: int, y: int, /, *args) -> None: ...
class SingleElementTuple(tuple[int]): ...
def _(args: SingleElementTuple) -> None:
# TODO: we should emit `[too-many-positional-arguments]` here
takes_zero(*args)
takes_zero(*args) # error: [too-many-positional-arguments]
takes_one(*args)
# TODO: we should emit `[missing-argument]` on both of these
takes_two(*args)
takes_two_positional_only(*args)
takes_two(*args) # error: [missing-argument]
takes_two_positional_only(*args) # error: [missing-argument]
# 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_two_different(*args) # error: [missing-argument]
takes_two_different_positional_only(*args) # error: [missing-argument]
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)
takes_at_least_two(*args) # error: [missing-argument]
takes_at_least_two_positional_only(*args) # error: [missing-argument]
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_zero(*args) # error: [too-many-positional-arguments]
takes_one(*args) # error: [too-many-positional-arguments]
takes_two(*args)
takes_two_positional_only(*args)
takes_two_different(*args) # error: [invalid-argument-type]
@ -222,40 +216,23 @@ def _(args: TwoElementIntTuple) -> None:
class IntStrTuple(tuple[int, str]): ...
def _(args: IntStrTuple) -> None:
# TODO: we should emit `[too-many-positional-arguments]` here
takes_zero(*args)
takes_zero(*args) # error: [too-many-positional-arguments]
# TODO: this should be `[too-many-positional-arguments]`, not `[invalid-argument-type]`
takes_one(*args) # error: [invalid-argument-type]
takes_one(*args) # error: [too-many-positional-arguments]
# 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)
```
@ -377,9 +354,7 @@ def takes_at_least_two_positional_only(x: int, y: int, /, *args) -> None: ...
class IntStarInt(tuple[int, *tuple[int, ...]]): ...
def _(args: IntStarInt) -> None:
# TODO: we should emit `[too-many-positional-arguments]` here
takes_zero(*args)
takes_zero(*args) # error: [too-many-positional-arguments]
takes_one(*args)
takes_two(*args)
takes_two_positional_only(*args)
@ -393,50 +368,32 @@ def _(args: IntStarInt) -> None:
class IntStarStr(tuple[int, *tuple[str, ...]]): ...
def _(args: IntStarStr) -> None:
# TODO: we should emit `[too-many-positional-arguments]` here
takes_zero(*args)
takes_zero(*args) # error: [too-many-positional-arguments]
# 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_zero(*args) # error: [too-many-positional-arguments]
takes_one(*args) # error: [too-many-positional-arguments]
takes_two(*args)
takes_two_positional_only(*args)
takes_two_different(*args) # error: [invalid-argument-type]
@ -449,51 +406,31 @@ def _(args: IntIntStarInt) -> None:
class IntIntStarStr(tuple[int, int, *tuple[str, ...]]): ...
def _(args: IntIntStarStr) -> None:
# TODO: we should emit `[too-many-positional-arguments]` here
takes_zero(*args)
takes_zero(*args) # error: [too-many-positional-arguments]
# TODO: this should be `[too-many-positional-arguments]`, not `invalid-argument-type`
takes_one(*args) # error: [invalid-argument-type]
takes_one(*args) # error: [too-many-positional-arguments]
# 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_zero(*args) # error: [too-many-positional-arguments]
takes_one(*args) # error: [too-many-positional-arguments]
takes_two(*args)
takes_two_positional_only(*args)
takes_two_different(*args) # error: [invalid-argument-type]
@ -506,40 +443,25 @@ def _(args: IntStarIntInt) -> None:
class IntStarStrInt(tuple[int, *tuple[str, ...], int]): ...
def _(args: IntStarStrInt) -> None:
# TODO: we should emit `too-many-positional-arguments` here
takes_zero(*args)
takes_zero(*args) # error: [too-many-positional-arguments]
# TODO: this should be `too-many-positional-arguments`, not `invalid-argument-type`
takes_one(*args) # error: [invalid-argument-type]
takes_one(*args) # error: [too-many-positional-arguments]
# 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]
takes_at_least_one(*args)
# 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)
```

View file

@ -411,10 +411,9 @@ 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]
reveal_type(test_seq(t1)) # revealed: Sequence[int | float | complex | list[int]]
reveal_type(test_seq(t2)) # revealed: Sequence[int | str]
# TODO: this should be `Sequence[Never]`
reveal_type(test_seq(t3)) # revealed: Sequence[Unknown]
```

View file

@ -367,10 +367,9 @@ 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]
reveal_type(test_seq(t1)) # revealed: Sequence[int | float | complex | list[int]]
reveal_type(test_seq(t2)) # revealed: Sequence[int | str]
# TODO: this should be `Sequence[Never]`
reveal_type(test_seq(t3)) # revealed: Sequence[Unknown]
```

View file

@ -23,6 +23,7 @@ We can then place custom stub files in `/typeshed/stdlib`, for example:
```pyi
class object: ...
class tuple: ...
class BuiltinClass: ...
builtin_symbol: BuiltinClass
@ -97,6 +98,12 @@ simple untyped definition is enough to make `reveal_type` work in tests:
typeshed = "/typeshed"
```
`/typeshed/stdlib/builtins.pyi`:
```pyi
class tuple: ...
```
`/typeshed/stdlib/typing_extensions.pyi`:
```pyi
@ -104,5 +111,5 @@ def reveal_type(obj, /): ...
```
```py
reveal_type(()) # revealed: tuple[()]
reveal_type(()) # revealed: tuple
```

View file

@ -193,6 +193,7 @@ typeshed = "/typeshed"
```pyi
class object: ...
class tuple: ...
class int: ...
class bytes: ...

View file

@ -39,9 +39,13 @@ class HeterogeneousSubclass0(tuple[()]): ...
reveal_type(HeterogeneousSubclass0.__getitem__)
def f0(h0: HeterogeneousSubclass0, i: int):
reveal_type(h0[0]) # revealed: Never
reveal_type(h0[1]) # revealed: Never
reveal_type(h0[-1]) # revealed: Never
# error: [index-out-of-bounds]
reveal_type(h0[0]) # revealed: Unknown
# error: [index-out-of-bounds]
reveal_type(h0[1]) # revealed: Unknown
# error: [index-out-of-bounds]
reveal_type(h0[-1]) # revealed: Unknown
reveal_type(h0[i]) # revealed: Never
class HeterogeneousSubclass1(tuple[I0]): ...
@ -51,7 +55,8 @@ reveal_type(HeterogeneousSubclass1.__getitem__)
def f0(h1: HeterogeneousSubclass1, i: int):
reveal_type(h1[0]) # revealed: I0
reveal_type(h1[1]) # revealed: I0
# error: [index-out-of-bounds]
reveal_type(h1[1]) # revealed: Unknown
reveal_type(h1[-1]) # revealed: I0
reveal_type(h1[i]) # revealed: I0
@ -84,25 +89,19 @@ def g(m: MixedSubclass, i: int):
reveal_type(m[2]) # revealed: I1 | I2 | I3
reveal_type(m[3]) # revealed: I1 | I2 | I3
reveal_type(m[4]) # revealed: I1 | I2 | I3 | I5
reveal_type(m[5]) # revealed: I1 | I2 | I3 | I5
reveal_type(m[10]) # revealed: I1 | I2 | I3 | I5
reveal_type(m[-1]) # revealed: I5
reveal_type(m[-2]) # revealed: I2
reveal_type(m[-3]) # revealed: I3
reveal_type(m[-4]) # revealed: I2
reveal_type(m[-5]) # revealed: I1 | I0
reveal_type(m[-5]) # revealed: I0 | I1
reveal_type(m[-6]) # revealed: I0 | I1
reveal_type(m[-10]) # revealed: I0 | I1
reveal_type(m[i]) # revealed: I0 | I1 | I2 | I3 | I5
# Ideally we would not include `I0` in the unions for these,
# but it's not possible to do this using only synthesized overloads.
reveal_type(m[5]) # revealed: I0 | I1 | I2 | I3 | I5
reveal_type(m[10]) # revealed: I0 | I1 | I2 | I3 | I5
# Similarly, ideally these would just be `I0` | I1`,
# but achieving that with only synthesized overloads wouldn't be possible
reveal_type(m[-6]) # revealed: I0 | I1 | I2 | I3 | I5
reveal_type(m[-10]) # revealed: I0 | I1 | I2 | I3 | I5
class MixedSubclass2(tuple[I0, I1, *tuple[I2, ...], I3]): ...
# revealed: Overload[(self, index: Literal[0], /) -> I0, (self, index: Literal[-2], /) -> I2 | I1, (self, index: Literal[1], /) -> I1, (self, index: Literal[-3], /) -> I2 | I1 | I0, (self, index: Literal[-1], /) -> I3, (self, index: Literal[2], /) -> I2 | I3, (self, index: SupportsIndex, /) -> I0 | I1 | I2 | I3, (self, index: slice[Any, Any, Any], /) -> tuple[I0 | I1 | I2 | I3, ...]]
@ -112,18 +111,12 @@ def g(m: MixedSubclass2, i: int):
reveal_type(m[0]) # revealed: I0
reveal_type(m[1]) # revealed: I1
reveal_type(m[2]) # revealed: I2 | I3
# Ideally this would just be `I2 | I3`,
# but that's not possible to achieve with synthesized overloads
reveal_type(m[3]) # revealed: I0 | I1 | I2 | I3
reveal_type(m[3]) # revealed: I2 | I3
reveal_type(m[-1]) # revealed: I3
reveal_type(m[-2]) # revealed: I2 | I1
reveal_type(m[-3]) # revealed: I2 | I1 | I0
# Ideally this would just be `I2 | I1 | I0`,
# but that's not possible to achieve with synthesized overloads
reveal_type(m[-4]) # revealed: I0 | I1 | I2 | I3
reveal_type(m[-2]) # revealed: I1 | I2
reveal_type(m[-3]) # revealed: I0 | I1 | I2
reveal_type(m[-4]) # revealed: I0 | I1 | I2
```
The stdlib API `os.stat` is a commonly used API that returns an instance of a tuple subclass
@ -225,75 +218,47 @@ 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, ...]
reveal_type(t[0:0]) # revealed: tuple[()]
reveal_type(t[0:1]) # revealed: tuple[I0]
reveal_type(t[0:2]) # revealed: tuple[I0, I1]
reveal_type(t[0:4]) # revealed: tuple[I0, I1, I2, I3]
reveal_type(t[0:5]) # revealed: tuple[I0, I1, I2, I3]
reveal_type(t[1:3]) # revealed: tuple[I1, I2]
# 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, ...]
reveal_type(t[-2:4]) # revealed: tuple[I2, I3]
reveal_type(t[-3:-1]) # revealed: tuple[I1, I2]
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, ...]
reveal_type(t[0:]) # revealed: tuple[I0, I1, I2, I3]
reveal_type(t[2:]) # revealed: tuple[I2, I3]
reveal_type(t[4:]) # revealed: tuple[()]
reveal_type(t[:0]) # revealed: tuple[()]
reveal_type(t[:2]) # revealed: tuple[I0, I1]
reveal_type(t[:10]) # revealed: 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, ...]
reveal_type(t[::-1]) # revealed: tuple[I3, I2, I1, I0]
reveal_type(t[::2]) # revealed: tuple[I0, I2]
reveal_type(t[-2:-5:-1]) # revealed: tuple[I2, I1, I0]
reveal_type(t[::-2]) # revealed: tuple[I3, I1]
reveal_type(t[-1::-3]) # revealed: tuple[I3, I0]
# 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, ...]
reveal_type(t[None:2:None]) # revealed: tuple[I0, I1]
reveal_type(t[1:None:1]) # revealed: tuple[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, ...]
reveal_type(t[start:stop:step]) # revealed: tuple[I1, 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, ...]
reveal_type(t[False:True]) # revealed: tuple[I0]
reveal_type(t[True:3]) # revealed: tuple[I1, I2]
# TODO: we should emit `zero-stepsize-in-slice` on all of these:
t[0:4:0]
t[:4:0]
t[0::0]
t[::0]
t[0:4:0] # error: [zero-stepsize-in-slice]
t[:4:0] # error: [zero-stepsize-in-slice]
t[0::0] # error: [zero-stepsize-in-slice]
t[::0] # error: [zero-stepsize-in-slice]
tuple_slice = t[m:n]
reveal_type(tuple_slice) # revealed: tuple[I0 | I1 | I2 | I3, ...]

View file

@ -502,10 +502,10 @@ For covariant types, such as `frozenset`, the ideal behaviour would be to not pr
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]
# TODO: better here would be `frozenset[Literal[1, 2, 3]]`
reveal_type(frozenset((1, 2, 3))) # revealed: frozenset[int]
# TODO: better here would be `frozenset[tuple[Literal[1], Literal[2], Literal[3]]]`
reveal_type(frozenset(((1, 2, 3),))) # revealed: frozenset[tuple[int, int, int]]
```
Literals are always promoted for invariant containers such as `list`, however, even though this can
@ -514,15 +514,15 @@ 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]
reveal_type(list((1, 2, 3))) # revealed: list[int]
reveal_type(list(((1, 2, 3),))) # revealed: list[tuple[int, int, int]]
# TODO: we could bidirectionally infer that the user does not want literals to be promoted here,
# and avoid this diagnostic
#
# error: [invalid-assignment] "`list[int]` is not assignable to `list[Literal[1, 2, 3]]`"
x: list[Literal[1, 2, 3]] = list((1, 2, 3))
# TODO: should be `list[Literal[1, 2, 3]]`
reveal_type(x) # revealed: list[Unknown]
reveal_type(x) # revealed: list[Literal[1, 2, 3]]
```
[not a singleton type]: https://discuss.python.org/t/should-we-specify-in-the-language-reference-that-the-empty-tuple-is-a-singleton/67957

View file

@ -26,10 +26,8 @@ class HeterogeneousTupleSubclass(tuple[Literal[True], Literal[1]]): ...
# 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(is_single_valued(EmptyTupleSubclass))
static_assert(is_single_valued(HeterogeneousTupleSubclass))
static_assert(not is_single_valued(str))
static_assert(not is_single_valued(Never))

View file

@ -424,142 +424,97 @@ class HeterogeneousTupleSubclass(tuple[I0, I1, I2]): ...
def f(x: HeterogeneousTupleSubclass):
a, b, c = x
reveal_type(a) # revealed: I0
reveal_type(b) # revealed: I1
reveal_type(c) # revealed: I2
# 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
d, e = x # error: [invalid-assignment] "Too many values to unpack: Expected 2"
# TODO: should emit a diagnostic ([invalid-assignment] "Too many values to unpack: Expected 2")
d, e = x
reveal_type(d) # revealed: Unknown
reveal_type(e) # revealed: Unknown
reveal_type(d) # revealed: I0 | I1 | I2
reveal_type(e) # revealed: I0 | I1 | I2
f, g, h, i = x # error: [invalid-assignment] "Not enough values to unpack: Expected 4"
# 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
reveal_type(f) # revealed: Unknown
reveal_type(g) # revealed: Unknown
reveal_type(h) # revealed: Unknown
reveal_type(i) # revealed: Unknown
[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]
reveal_type(j) # revealed: I0
reveal_type(k) # revealed: list[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]
reveal_type(l) # revealed: I0
reveal_type(m) # revealed: I1
reveal_type(n) # revealed: list[I2]
[o, p, q, *r] = x
reveal_type(o) # revealed: I0
reveal_type(p) # revealed: I1
reveal_type(q) # revealed: I2
reveal_type(r) # revealed: list[Never]
# 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")
# error: [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]
reveal_type(s) # revealed: Unknown
reveal_type(t) # revealed: Unknown
reveal_type(u) # revealed: Unknown
reveal_type(v) # revealed: Unknown
reveal_type(w) # revealed: list[Unknown]
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
(a,) = x # error: [invalid-assignment] "Too many values to unpack: Expected 1"
reveal_type(a) # revealed: Unknown
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
reveal_type(c) # revealed: I0
reveal_type(d) # revealed: 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
reveal_type(e) # revealed: I0
reveal_type(f) # revealed: I1
reveal_type(g) # revealed: 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
reveal_type(h) # revealed: I0
reveal_type(i) # revealed: I1
reveal_type(j) # revealed: I1
reveal_type(k) # revealed: 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]
reveal_type(l) # revealed: I0
reveal_type(m) # revealed: list[I1 | I2]
[n, o, *p] = x
reveal_type(n) # revealed: I0
# 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]
# TODO: `I1 | I2` might be better here? (https://github.com/astral-sh/ty/issues/947)
reveal_type(o) # revealed: I1
reveal_type(p) # revealed: list[I1 | I2]
[o, p, q, *r] = x
reveal_type(o) # revealed: I0
# 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]
# TODO: `I1 | I2` might be better for both of these? (https://github.com/astral-sh/ty/issues/947)
reveal_type(p) # revealed: I1
reveal_type(q) # revealed: I1
reveal_type(r) # revealed: list[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
reveal_type(s) # revealed: I0
reveal_type(t) # revealed: list[I1]
reveal_type(u) # revealed: 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
reveal_type(aa) # revealed: I0
reveal_type(bb) # revealed: I1
reveal_type(cc) # revealed: list[I1]
reveal_type(dd) # revealed: I2
```
## String