mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 05:44:56 +00:00
[ty] Make tuple subclass constructors sound (#19469)
This commit is contained in:
parent
fcdffe4ac9
commit
cb5a9ff8dc
7 changed files with 201 additions and 66 deletions
|
@ -93,14 +93,14 @@ class SingleElementTupleSubclass(tuple[int]): ...
|
|||
|
||||
reveal_type(bool(SingleElementTupleSubclass((0,)))) # revealed: Literal[True]
|
||||
reveal_type(SingleElementTupleSubclass.__bool__) # revealed: (self: tuple[int], /) -> Literal[True]
|
||||
reveal_type(SingleElementTupleSubclass().__bool__) # revealed: () -> Literal[True]
|
||||
reveal_type(SingleElementTupleSubclass((1,)).__bool__) # revealed: () -> Literal[True]
|
||||
|
||||
# Unknown length, but we know the length is guaranteed to be >=2
|
||||
class MixedTupleSubclass(tuple[int, *tuple[str, ...], bytes]): ...
|
||||
|
||||
reveal_type(bool(MixedTupleSubclass((1, b"foo")))) # revealed: Literal[True]
|
||||
reveal_type(MixedTupleSubclass.__bool__) # revealed: (self: tuple[int, *tuple[str, ...], bytes], /) -> Literal[True]
|
||||
reveal_type(MixedTupleSubclass().__bool__) # revealed: () -> Literal[True]
|
||||
reveal_type(MixedTupleSubclass((1, b"foo")).__bool__) # revealed: () -> Literal[True]
|
||||
|
||||
# Unknown length with an overridden `__bool__`:
|
||||
class VariadicTupleSubclassWithDunderBoolOverride(tuple[int, ...]):
|
||||
|
|
|
@ -19,14 +19,20 @@ def _(p: P, q: 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.
|
||||
specialization of `tuple` we check that the values passed in match the element types defined in the
|
||||
specialization.
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.11"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing_extensions import Iterable, Never
|
||||
|
||||
reveal_type(tuple()) # revealed: tuple[()]
|
||||
reveal_type(tuple[int]((1,))) # revealed: tuple[int]
|
||||
reveal_type(tuple[int, *tuple[str, ...]]((1,))) # revealed: tuple[int, *tuple[str, ...]]
|
||||
reveal_type(().__class__()) # revealed: tuple[()]
|
||||
reveal_type((1, 2).__class__((1, 2))) # revealed: tuple[Literal[1], Literal[2]]
|
||||
|
||||
|
@ -56,6 +62,63 @@ reveal_type((1,).__class__()) # revealed: tuple[Literal[1]]
|
|||
reveal_type((1, 2).__class__()) # revealed: tuple[Literal[1], Literal[2]]
|
||||
```
|
||||
|
||||
## Instantiating tuple subclasses
|
||||
|
||||
Tuple subclasses inherit the special-cased constructors from their tuple superclasses:
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.11"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing_extensions import Iterable, Never
|
||||
|
||||
class UnspecializedTupleSubclass(tuple): ...
|
||||
class EmptyTupleSubclass(tuple[()]): ...
|
||||
class SingleElementTupleSubclass(tuple[int]): ...
|
||||
class VariadicTupleSubclass(tuple[int, ...]): ...
|
||||
class MixedTupleSubclass(tuple[int, *tuple[str, ...]]): ...
|
||||
|
||||
reveal_type(UnspecializedTupleSubclass()) # revealed: UnspecializedTupleSubclass
|
||||
reveal_type(UnspecializedTupleSubclass(())) # revealed: UnspecializedTupleSubclass
|
||||
reveal_type(UnspecializedTupleSubclass((1, 2, "foo"))) # revealed: UnspecializedTupleSubclass
|
||||
reveal_type(UnspecializedTupleSubclass([1, 2, "foo", b"bar"])) # revealed: UnspecializedTupleSubclass
|
||||
|
||||
reveal_type(EmptyTupleSubclass()) # revealed: EmptyTupleSubclass
|
||||
reveal_type(EmptyTupleSubclass(())) # revealed: EmptyTupleSubclass
|
||||
|
||||
# error: [invalid-argument-type] "Argument is incorrect: Expected `tuple[()]`, found `tuple[Literal[1], Literal[2]]`"
|
||||
reveal_type(EmptyTupleSubclass((1, 2))) # revealed: EmptyTupleSubclass
|
||||
|
||||
reveal_type(SingleElementTupleSubclass((1,))) # revealed: SingleElementTupleSubclass
|
||||
|
||||
# error: [missing-argument] "No argument provided for required parameter `iterable`"
|
||||
reveal_type(SingleElementTupleSubclass()) # revealed: SingleElementTupleSubclass
|
||||
|
||||
reveal_type(VariadicTupleSubclass()) # revealed: VariadicTupleSubclass
|
||||
reveal_type(VariadicTupleSubclass(())) # revealed: VariadicTupleSubclass
|
||||
reveal_type(VariadicTupleSubclass([1, 2, 3])) # revealed: VariadicTupleSubclass
|
||||
reveal_type(VariadicTupleSubclass((1, 2, 3, 4))) # revealed: VariadicTupleSubclass
|
||||
|
||||
reveal_type(MixedTupleSubclass((1,))) # revealed: MixedTupleSubclass
|
||||
reveal_type(MixedTupleSubclass((1, "foo"))) # revealed: MixedTupleSubclass
|
||||
|
||||
# error: [invalid-argument-type] "Argument is incorrect: Expected `tuple[int, *tuple[str, ...]]`, found `tuple[Literal[1], Literal[b"foo"]]`"
|
||||
reveal_type(MixedTupleSubclass((1, b"foo"))) # revealed: MixedTupleSubclass
|
||||
|
||||
# error: [missing-argument] "No argument provided for required parameter `iterable`"
|
||||
reveal_type(MixedTupleSubclass()) # revealed: MixedTupleSubclass
|
||||
|
||||
def _(empty: EmptyTupleSubclass, single_element: SingleElementTupleSubclass, mixed: MixedTupleSubclass, x: tuple[int, int]):
|
||||
# error: [invalid-argument-type] "Argument is incorrect: Expected `tuple[()]`, found `tuple[Literal[1], Literal[2]]`"
|
||||
empty.__class__((1, 2))
|
||||
# error: [invalid-argument-type] "Argument is incorrect: Expected `tuple[int]`, found `tuple[Literal[1], Literal[2]]`"
|
||||
single_element.__class__((1, 2))
|
||||
# error: [missing-argument] "No argument provided for required parameter `iterable`"
|
||||
mixed.__class__()
|
||||
```
|
||||
|
||||
## Subtyping relationships
|
||||
|
||||
The type `tuple[S1, S2]` is a subtype of `tuple[T1, T2]` if and only if `S1` is a subtype of `T1`
|
||||
|
|
|
@ -916,6 +916,7 @@ c: Callable[[Any], str] = A().g
|
|||
|
||||
```py
|
||||
from typing import Any, Callable
|
||||
from ty_extensions import static_assert, is_assignable_to
|
||||
|
||||
c: Callable[[object], type] = type
|
||||
c: Callable[[str], Any] = str
|
||||
|
@ -936,6 +937,15 @@ class C:
|
|||
def __init__(self, x: int) -> None: ...
|
||||
|
||||
c: Callable[[int], C] = C
|
||||
|
||||
def f(a: Callable[..., Any], b: Callable[[Any], Any]): ...
|
||||
|
||||
f(tuple, tuple)
|
||||
|
||||
def g(a: Callable[[Any, Any], Any]): ...
|
||||
|
||||
# error: [invalid-argument-type] "Argument to function `g` is incorrect: Expected `(Any, Any, /) -> Any`, found `<class 'tuple'>`"
|
||||
g(tuple)
|
||||
```
|
||||
|
||||
### Generic class literal types
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue