Skip to content

Commit 55df927

Browse files
authored
[ty] Understand homogeneous tuple annotations (#17998)
1 parent f301931 commit 55df927

File tree

17 files changed

+196
-104
lines changed

17 files changed

+196
-104
lines changed

crates/ty_python_semantic/resources/mdtest/annotations/starred.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,10 @@ from typing_extensions import TypeVarTuple
1313
Ts = TypeVarTuple("Ts")
1414

1515
def append_int(*args: *Ts) -> tuple[*Ts, int]:
16-
# TODO: tuple[*Ts]
17-
reveal_type(args) # revealed: tuple[Unknown, ...]
16+
reveal_type(args) # revealed: @Todo(PEP 646)
1817

1918
return (*args, 1)
2019

2120
# TODO should be tuple[Literal[True], Literal["a"], int]
22-
reveal_type(append_int(True, "a")) # revealed: @Todo(full tuple[...] support)
21+
reveal_type(append_int(True, "a")) # revealed: @Todo(PEP 646)
2322
```

crates/ty_python_semantic/resources/mdtest/annotations/unsupported_special_forms.md

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,13 @@ R_co = TypeVar("R_co", covariant=True)
1515
Alias: TypeAlias = int
1616

1717
def f(*args: Unpack[Ts]) -> tuple[Unpack[Ts]]:
18-
# TODO: should understand the annotation
19-
reveal_type(args) # revealed: tuple[Unknown, ...]
20-
18+
reveal_type(args) # revealed: tuple[@Todo(`Unpack[]` special form), ...]
2119
reveal_type(Alias) # revealed: @Todo(Support for `typing.TypeAlias`)
2220

2321
def g() -> TypeGuard[int]: ...
2422
def h() -> TypeIs[int]: ...
2523
def i(callback: Callable[Concatenate[int, P], R_co], *args: P.args, **kwargs: P.kwargs) -> R_co:
26-
# TODO: should understand the annotation
27-
reveal_type(args) # revealed: tuple[Unknown, ...]
24+
reveal_type(args) # revealed: tuple[@Todo(Support for `typing.ParamSpec`), ...]
2825
reveal_type(kwargs) # revealed: dict[str, @Todo(Support for `typing.ParamSpec`)]
2926
return callback(42, *args, **kwargs)
3027

crates/ty_python_semantic/resources/mdtest/assignment/annotations.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,12 @@ reveal_type(a) # revealed: tuple[()]
5656
reveal_type(b) # revealed: tuple[int]
5757
reveal_type(c) # revealed: tuple[str, int]
5858
reveal_type(d) # revealed: tuple[tuple[str, str], tuple[int, int]]
59+
reveal_type(e) # revealed: tuple[str, ...]
5960

60-
# TODO: homogeneous tuples, PEP-646 tuples, generics
61-
reveal_type(e) # revealed: @Todo(full tuple[...] support)
62-
reveal_type(f) # revealed: @Todo(full tuple[...] support)
63-
reveal_type(g) # revealed: @Todo(full tuple[...] support)
64-
reveal_type(h) # revealed: tuple[list[int], list[int]]
61+
reveal_type(f) # revealed: @Todo(PEP 646)
62+
reveal_type(g) # revealed: @Todo(PEP 646)
6563

64+
reveal_type(h) # revealed: tuple[list[int], list[int]]
6665
reveal_type(i) # revealed: tuple[str | int, str | int]
6766
reveal_type(j) # revealed: tuple[str | int]
6867
```

crates/ty_python_semantic/resources/mdtest/attributes.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1676,7 +1676,7 @@ functions are instances of that class:
16761676
```py
16771677
def f(): ...
16781678

1679-
reveal_type(f.__defaults__) # revealed: @Todo(full tuple[...] support) | None
1679+
reveal_type(f.__defaults__) # revealed: tuple[Any, ...] | None
16801680
reveal_type(f.__kwdefaults__) # revealed: dict[str, Any] | None
16811681
```
16821682

@@ -1730,7 +1730,7 @@ All attribute access on literal `bytes` types is currently delegated to `builtin
17301730
```py
17311731
# revealed: bound method Literal[b"foo"].join(iterable_of_bytes: Iterable[@Todo(Support for `typing.TypeAlias`)], /) -> bytes
17321732
reveal_type(b"foo".join)
1733-
# revealed: bound method Literal[b"foo"].endswith(suffix: @Todo(Support for `typing.TypeAlias`), start: SupportsIndex | None = ellipsis, end: SupportsIndex | None = ellipsis, /) -> bool
1733+
# revealed: bound method Literal[b"foo"].endswith(suffix: @Todo(Support for `typing.TypeAlias`) | tuple[@Todo(Support for `typing.TypeAlias`), ...], start: SupportsIndex | None = ellipsis, end: SupportsIndex | None = ellipsis, /) -> bool
17341734
reveal_type(b"foo".endswith)
17351735
```
17361736

crates/ty_python_semantic/resources/mdtest/binary/instances.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -317,8 +317,7 @@ reveal_type(A() + b"foo") # revealed: A
317317
reveal_type(b"foo" + A()) # revealed: bytes
318318

319319
reveal_type(A() + ()) # revealed: A
320-
# TODO this should be `A`, since `tuple.__add__` doesn't support `A` instances
321-
reveal_type(() + A()) # revealed: @Todo(full tuple[...] support)
320+
reveal_type(() + A()) # revealed: A
322321

323322
literal_string_instance = "foo" * 1_000_000_000
324323
# the test is not testing what it's meant to be testing if this isn't a `LiteralString`:

crates/ty_python_semantic/resources/mdtest/binary/tuples.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ def _(x: tuple[int, str], y: tuple[None, tuple[int]]):
1717

1818
```py
1919
def _(x: tuple[int, ...], y: tuple[str, ...]):
20-
reveal_type(x + y) # revealed: @Todo(full tuple[...] support)
21-
reveal_type(x + (1, 2)) # revealed: @Todo(full tuple[...] support)
20+
# TODO: should be `tuple[int | str, ...]`
21+
reveal_type(x + y) # revealed: tuple[int | Unknown, ...]
22+
reveal_type(x + (1, 2)) # revealed: tuple[int, ...]
2223
```

crates/ty_python_semantic/resources/mdtest/call/builtins.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ type(b"Foo", (), {})
5454
# error: [no-matching-overload] "No overload of class `type` matches arguments"
5555
type("Foo", Base, {})
5656

57-
# TODO: this should be an error
57+
# error: [no-matching-overload] "No overload of class `type` matches arguments"
5858
type("Foo", (1, 2), {})
5959

6060
# TODO: this should be an error

crates/ty_python_semantic/resources/mdtest/exception/basic.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ def foo(
4545
x: type[AttributeError],
4646
y: tuple[type[OSError], type[RuntimeError]],
4747
z: tuple[type[BaseException], ...],
48+
zz: tuple[type[TypeError | RuntimeError], ...],
49+
zzz: type[BaseException] | tuple[type[BaseException], ...],
4850
):
4951
try:
5052
help()
@@ -53,8 +55,11 @@ def foo(
5355
except y as f:
5456
reveal_type(f) # revealed: OSError | RuntimeError
5557
except z as g:
56-
# TODO: should be `BaseException`
57-
reveal_type(g) # revealed: @Todo(full tuple[...] support)
58+
reveal_type(g) # revealed: BaseException
59+
except zz as h:
60+
reveal_type(h) # revealed: TypeError | RuntimeError
61+
except zzz as i:
62+
reveal_type(i) # revealed: BaseException
5863
```
5964

6065
## Invalid exception handlers
@@ -86,9 +91,9 @@ def foo(
8691
# error: [invalid-exception-caught]
8792
except y as f:
8893
reveal_type(f) # revealed: OSError | RuntimeError | Unknown
94+
# error: [invalid-exception-caught]
8995
except z as g:
90-
# TODO: should emit a diagnostic here:
91-
reveal_type(g) # revealed: @Todo(full tuple[...] support)
96+
reveal_type(g) # revealed: Unknown
9297
```
9398

9499
## Object raised is not an exception

crates/ty_python_semantic/resources/mdtest/function/parameters.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@ def f(a, b: int, c=1, d: int = 2, /, e=3, f: Literal[4] = 4, *args: object, g=5,
2525
reveal_type(f) # revealed: Literal[4]
2626
reveal_type(g) # revealed: Unknown | Literal[5]
2727
reveal_type(h) # revealed: Literal[6]
28-
# TODO: should be `tuple[object, ...]`
29-
reveal_type(args) # revealed: tuple[Unknown, ...]
28+
reveal_type(args) # revealed: tuple[object, ...]
3029
reveal_type(kwargs) # revealed: dict[str, str]
3130
```
3231

crates/ty_python_semantic/resources/mdtest/pep695_type_aliases.md

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,7 @@ y: MyIntOrStr = None
6868

6969
```py
7070
type ListOrSet[T] = list[T] | set[T]
71-
72-
# TODO: Should be `tuple[typing.TypeVar | typing.ParamSpec | typing.TypeVarTuple, ...]`,
73-
# as specified in the `typeshed` stubs.
74-
reveal_type(ListOrSet.__type_params__) # revealed: @Todo(full tuple[...] support)
71+
reveal_type(ListOrSet.__type_params__) # revealed: tuple[TypeVar | ParamSpec | TypeVarTuple, ...]
7572
```
7673

7774
## `TypeAliasType` properties

crates/ty_python_semantic/resources/mdtest/subscript/tuple.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ def _(m: int, n: int):
7070

7171
tuple_slice = t[m:n]
7272
# TODO: Should be `tuple[Literal[1, 'a', b"b"] | None, ...]`
73-
reveal_type(tuple_slice) # revealed: @Todo(full tuple[...] support)
73+
reveal_type(tuple_slice) # revealed: tuple[Unknown, ...]
7474
```
7575

7676
## Inheritance
@@ -101,7 +101,7 @@ class A: ...
101101
def _(c: Tuple, d: Tuple[int, A], e: Tuple[Any, ...]):
102102
reveal_type(c) # revealed: tuple[Unknown, ...]
103103
reveal_type(d) # revealed: tuple[int, A]
104-
reveal_type(e) # revealed: @Todo(full tuple[...] support)
104+
reveal_type(e) # revealed: tuple[Any, ...]
105105
```
106106

107107
### Inheritance

crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ static_assert(is_assignable_to(Meta, type[Any]))
198198
static_assert(is_assignable_to(Meta, type[Unknown]))
199199
```
200200

201-
## Tuple types
201+
## Heterogeneous tuple types
202202

203203
```py
204204
from ty_extensions import static_assert, is_assignable_to, AlwaysTruthy, AlwaysFalsy
@@ -232,6 +232,35 @@ static_assert(not is_assignable_to(tuple[int, int], tuple[Literal[1], int]))
232232
static_assert(not is_assignable_to(tuple[Any, Literal[2]], tuple[int, str]))
233233
```
234234

235+
## Assignability of heterogeneous tuple types to homogeneous tuple types
236+
237+
While a homogeneous tuple type is not assignable to any heterogeneous tuple types, a heterogeneous
238+
tuple type can be assignable to a homogeneous tuple type, and homogeneous tuple types can be
239+
assignable to `Sequence`:
240+
241+
```py
242+
from typing import Literal, Any, Sequence
243+
from ty_extensions import static_assert, is_assignable_to, Not, AlwaysFalsy
244+
245+
static_assert(is_assignable_to(tuple[Literal[1], Literal[2]], tuple[Literal[1, 2], ...]))
246+
static_assert(is_assignable_to(tuple[Literal[1], Literal[2]], tuple[int, ...]))
247+
static_assert(is_assignable_to(tuple[Literal[1], Literal[2]], tuple[int | str, ...]))
248+
static_assert(is_assignable_to(tuple[Literal[1], Literal[2]], tuple[Any, ...]))
249+
static_assert(is_assignable_to(tuple[Literal[1], Literal[2]], tuple[Not[AlwaysFalsy], ...]))
250+
static_assert(is_assignable_to(tuple[Literal[1], Literal[2]], Sequence[int]))
251+
static_assert(is_assignable_to(tuple[int, ...], Sequence[int]))
252+
static_assert(is_assignable_to(tuple[int, ...], Sequence[Any]))
253+
static_assert(is_assignable_to(tuple[Any, ...], Sequence[int]))
254+
255+
static_assert(is_assignable_to(tuple[()], tuple[Literal[1, 2], ...]))
256+
static_assert(is_assignable_to(tuple[()], tuple[int, ...]))
257+
static_assert(is_assignable_to(tuple[()], tuple[int | str, ...]))
258+
static_assert(is_assignable_to(tuple[()], tuple[Not[AlwaysFalsy], ...]))
259+
static_assert(is_assignable_to(tuple[()], Sequence[int]))
260+
261+
static_assert(not is_assignable_to(tuple[int, int], tuple[str, ...]))
262+
```
263+
235264
## Union types
236265

237266
```py

crates/ty_python_semantic/resources/mdtest/type_properties/is_disjoint_from.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ static_assert(is_disjoint_from(tuple[Literal[1], Literal[2]], tuple[Literal[1]])
9191
static_assert(is_disjoint_from(tuple[Literal[1], Literal[2]], tuple[Literal[1], Literal[3]]))
9292

9393
static_assert(not is_disjoint_from(tuple[Literal[1], Literal[2]], tuple[Literal[1], int]))
94+
static_assert(not is_disjoint_from(tuple[Literal[1], Literal[2]], tuple[int, ...]))
95+
96+
# TODO: should pass
97+
static_assert(is_disjoint_from(tuple[int, int], tuple[None, ...])) # error: [static-assert-error]
9498
```
9599

96100
## Unions

crates/ty_python_semantic/resources/mdtest/type_properties/is_fully_static.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@ static_assert(not is_fully_static(Any | str))
4848
static_assert(not is_fully_static(str | Unknown))
4949
static_assert(not is_fully_static(Intersection[Any, Not[LiteralString]]))
5050

51-
static_assert(not is_fully_static(tuple[Any, ...]))
51+
# TODO: should pass
52+
static_assert(not is_fully_static(tuple[Any, ...])) # error: [static-assert-error]
53+
5254
static_assert(not is_fully_static(tuple[int, Any]))
5355
static_assert(not is_fully_static(type[Any]))
5456
```

crates/ty_python_semantic/resources/mdtest/type_properties/is_subtype_of.md

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ static_assert(is_subtype_of(Literal[b"foo"], bytes))
118118
static_assert(is_subtype_of(Literal[b"foo"], object))
119119
```
120120

121-
## Tuple types
121+
## Heterogeneous tuple types
122122

123123
```py
124124
from ty_extensions import is_subtype_of, static_assert
@@ -150,9 +150,36 @@ static_assert(not is_subtype_of(tuple[B1, B2], tuple[Unrelated, Unrelated]))
150150
static_assert(not is_subtype_of(tuple[B1, B2], tuple[()]))
151151
static_assert(not is_subtype_of(tuple[B1, B2], tuple[A1]))
152152
static_assert(not is_subtype_of(tuple[B1, B2], tuple[A1, A2, Unrelated]))
153+
static_assert(is_subtype_of(tuple[int], tuple[object, ...]))
154+
```
155+
156+
## Subtyping of heterogeneous tuple types and homogeneous tuple types
157+
158+
While a homogeneous tuple type is not a subtype of any heterogeneous tuple types, a heterogeneous
159+
tuple type can be a subtype of a homogeneous tuple type, and homogeneous tuple types can be subtypes
160+
of `Sequence`:
161+
162+
```py
163+
from typing import Literal, Any, Sequence
164+
from ty_extensions import static_assert, is_subtype_of, Not, AlwaysFalsy
165+
166+
static_assert(is_subtype_of(tuple[Literal[1], Literal[2]], tuple[Literal[1, 2], ...]))
167+
static_assert(is_subtype_of(tuple[Literal[1], Literal[2]], tuple[int, ...]))
168+
static_assert(is_subtype_of(tuple[Literal[1], Literal[2]], tuple[int | str, ...]))
169+
static_assert(is_subtype_of(tuple[Literal[1], Literal[2]], tuple[Not[AlwaysFalsy], ...]))
170+
static_assert(is_subtype_of(tuple[Literal[1], Literal[2]], Sequence[int]))
171+
static_assert(is_subtype_of(tuple[int, ...], Sequence[int]))
172+
173+
static_assert(is_subtype_of(tuple[()], tuple[Literal[1, 2], ...]))
174+
static_assert(is_subtype_of(tuple[()], tuple[int, ...]))
175+
static_assert(is_subtype_of(tuple[()], tuple[int | str, ...]))
176+
static_assert(is_subtype_of(tuple[()], tuple[Not[AlwaysFalsy], ...]))
177+
static_assert(is_subtype_of(tuple[()], Sequence[int]))
153178

154-
# TODO: should pass
155-
static_assert(is_subtype_of(tuple[int], tuple[object, ...])) # error: [static-assert-error]
179+
static_assert(not is_subtype_of(tuple[Literal[1], Literal[2]], tuple[Any, ...]))
180+
static_assert(not is_subtype_of(tuple[int, int], tuple[str, ...]))
181+
static_assert(not is_subtype_of(tuple[int, ...], Sequence[Any]))
182+
static_assert(not is_subtype_of(tuple[Any, ...], Sequence[int]))
156183
```
157184

158185
## Union types

0 commit comments

Comments
 (0)