Skip to content

Commit 40e2a6a

Browse files
committed
[ty] support imported PEP 613 type aliases
1 parent a6abd65 commit 40e2a6a

File tree

19 files changed

+204
-225
lines changed

19 files changed

+204
-225
lines changed

crates/ruff_benchmark/benches/ty_walltime.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ static FREQTRADE: Benchmark = Benchmark::new(
143143
max_dep_date: "2025-06-17",
144144
python_version: PythonVersion::PY312,
145145
},
146-
525,
146+
600,
147147
);
148148

149149
static PANDAS: Benchmark = Benchmark::new(
@@ -163,7 +163,7 @@ static PANDAS: Benchmark = Benchmark::new(
163163
max_dep_date: "2025-06-17",
164164
python_version: PythonVersion::PY312,
165165
},
166-
3000,
166+
4000,
167167
);
168168

169169
static PYDANTIC: Benchmark = Benchmark::new(

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

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,8 @@ P = ParamSpec("P")
1212
Ts = TypeVarTuple("Ts")
1313
R_co = TypeVar("R_co", covariant=True)
1414

15-
Alias: TypeAlias = int
16-
1715
def f(*args: Unpack[Ts]) -> tuple[Unpack[Ts]]:
1816
reveal_type(args) # revealed: tuple[@Todo(`Unpack[]` special form), ...]
19-
reveal_type(Alias) # revealed: @Todo(Support for `typing.TypeAlias`)
2017
return args
2118

2219
def g() -> TypeGuard[int]: ...

crates/ty_python_semantic/resources/mdtest/attributes.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2208,9 +2208,9 @@ reveal_type(False.real) # revealed: Literal[0]
22082208
All attribute access on literal `bytes` types is currently delegated to `builtins.bytes`:
22092209

22102210
```py
2211-
# revealed: bound method Literal[b"foo"].join(iterable_of_bytes: Iterable[@Todo(Support for `typing.TypeAlias`)], /) -> bytes
2211+
# revealed: bound method Literal[b"foo"].join(iterable_of_bytes: Iterable[Buffer], /) -> bytes
22122212
reveal_type(b"foo".join)
2213-
# revealed: bound method Literal[b"foo"].endswith(suffix: @Todo(Support for `typing.TypeAlias`) | tuple[@Todo(Support for `typing.TypeAlias`), ...], start: SupportsIndex | None = None, end: SupportsIndex | None = None, /) -> bool
2213+
# revealed: bound method Literal[b"foo"].endswith(suffix: Buffer | tuple[Buffer, ...], start: SupportsIndex | None = None, end: SupportsIndex | None = None, /) -> bool
22142214
reveal_type(b"foo".endswith)
22152215
```
22162216

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -313,8 +313,7 @@ reveal_type(A() + "foo") # revealed: A
313313
reveal_type("foo" + A()) # revealed: A
314314

315315
reveal_type(A() + b"foo") # revealed: A
316-
# TODO should be `A` since `bytes.__add__` doesn't support `A` instances
317-
reveal_type(b"foo" + A()) # revealed: bytes
316+
reveal_type(b"foo" + A()) # revealed: A
318317

319318
reveal_type(A() + ()) # revealed: A
320319
reveal_type(() + A()) # revealed: A

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,8 @@ reveal_type(2**largest_u32) # revealed: int
5454

5555
def variable(x: int):
5656
reveal_type(x**2) # revealed: int
57-
# TODO: should be `Any` (overload 5 on `__pow__`), requires correct overload matching
58-
reveal_type(2**x) # revealed: int
59-
# TODO: should be `Any` (overload 5 on `__pow__`), requires correct overload matching
60-
reveal_type(x**x) # revealed: int
57+
reveal_type(2**x) # revealed: Any
58+
reveal_type(x**x) # revealed: Any
6159
```
6260

6361
If the second argument is \<0, a `float` is returned at runtime. If the first argument is \<0 but

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -598,9 +598,9 @@ from typing_extensions import Self
598598

599599
reveal_type(object.__new__) # revealed: def __new__(cls) -> Self@__new__
600600
reveal_type(object().__new__) # revealed: def __new__(cls) -> Self@__new__
601-
# revealed: Overload[(cls, x: @Todo(Support for `typing.TypeAlias`) = Literal[0], /) -> Self@__new__, (cls, x: str | bytes | bytearray, /, base: SupportsIndex) -> Self@__new__]
601+
# revealed: Overload[(cls, x: str | Buffer | SupportsInt | SupportsIndex | SupportsTrunc = Literal[0], /) -> Self@__new__, (cls, x: str | bytes | bytearray, /, base: SupportsIndex) -> Self@__new__]
602602
reveal_type(int.__new__)
603-
# revealed: Overload[(cls, x: @Todo(Support for `typing.TypeAlias`) = Literal[0], /) -> Self@__new__, (cls, x: str | bytes | bytearray, /, base: SupportsIndex) -> Self@__new__]
603+
# revealed: Overload[(cls, x: str | Buffer | SupportsInt | SupportsIndex | SupportsTrunc = Literal[0], /) -> Self@__new__, (cls, x: str | bytes | bytearray, /, base: SupportsIndex) -> Self@__new__]
604604
reveal_type((42).__new__)
605605

606606
class X:

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@ import pickle
1010

1111
reveal_type(open("")) # revealed: TextIOWrapper[_WrappedBuffer]
1212
reveal_type(open("", "r")) # revealed: TextIOWrapper[_WrappedBuffer]
13-
reveal_type(open("", "rb")) # revealed: @Todo(`builtins.open` return type)
13+
reveal_type(open("", "rb")) # revealed: BufferedReader[_BufferedReaderStream]
1414

1515
with open("foo.pickle", "rb") as f:
1616
x = pickle.load(f) # fine
1717

1818
def _(mode: str):
19-
reveal_type(open("", mode)) # revealed: @Todo(`builtins.open` return type)
19+
reveal_type(open("", mode)) # revealed: IO[Any]
2020
```
2121

2222
## `os.fdopen`
@@ -29,7 +29,7 @@ import os
2929

3030
reveal_type(os.fdopen(0)) # revealed: TextIOWrapper[_WrappedBuffer]
3131
reveal_type(os.fdopen(0, "r")) # revealed: TextIOWrapper[_WrappedBuffer]
32-
reveal_type(os.fdopen(0, "rb")) # revealed: @Todo(`os.fdopen` return type)
32+
reveal_type(os.fdopen(0, "rb")) # revealed: BufferedReader[_BufferedReaderStream]
3333

3434
with os.fdopen(0, "rb") as f:
3535
x = pickle.load(f) # fine
@@ -43,9 +43,9 @@ And similarly for `Path.open()`:
4343
from pathlib import Path
4444
import pickle
4545

46-
reveal_type(Path("").open()) # revealed: @Todo(`Path.open` return type)
47-
reveal_type(Path("").open("r")) # revealed: @Todo(`Path.open` return type)
48-
reveal_type(Path("").open("rb")) # revealed: @Todo(`Path.open` return type)
46+
reveal_type(Path("").open()) # revealed: TextIOWrapper[_WrappedBuffer]
47+
reveal_type(Path("").open("r")) # revealed: TextIOWrapper[_WrappedBuffer]
48+
reveal_type(Path("").open("rb")) # revealed: BufferedReader[_BufferedReaderStream]
4949

5050
with Path("foo.pickle").open("rb") as f:
5151
x = pickle.load(f) # fine
@@ -61,7 +61,7 @@ import pickle
6161

6262
reveal_type(NamedTemporaryFile()) # revealed: _TemporaryFileWrapper[bytes]
6363
reveal_type(NamedTemporaryFile("r")) # revealed: _TemporaryFileWrapper[str]
64-
reveal_type(NamedTemporaryFile("rb")) # revealed: @Todo(`tempfile.NamedTemporaryFile` return type)
64+
reveal_type(NamedTemporaryFile("rb")) # revealed: _TemporaryFileWrapper[bytes]
6565

6666
with NamedTemporaryFile("rb") as f:
6767
x = pickle.load(f) # fine

crates/ty_python_semantic/resources/mdtest/expression/lambda.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ x = lambda y: y
127127
reveal_type(x.__code__) # revealed: CodeType
128128
reveal_type(x.__name__) # revealed: str
129129
reveal_type(x.__defaults__) # revealed: tuple[Any, ...] | None
130-
reveal_type(x.__annotations__) # revealed: dict[str, @Todo(Support for `typing.TypeAlias`)]
130+
reveal_type(x.__annotations__) # revealed: dict[str, Any]
131131
reveal_type(x.__dict__) # revealed: dict[str, Any]
132132
reveal_type(x.__doc__) # revealed: str | None
133133
reveal_type(x.__kwdefaults__) # revealed: dict[str, Any] | None

crates/ty_python_semantic/resources/mdtest/pep613_type_aliases.md

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,71 @@
11
# PEP 613 type aliases
22

3-
We do not support PEP 613 type aliases yet. For now, just make sure that we don't panic:
3+
PEP 613 type aliases are simple assignment statements, annotated with `typing.TypeAlias` to mark
4+
them as a type alias. At runtime, they behave the same as implicit type aliases. Our support for
5+
them is currently the same as for implicit type aliases, but we don't reproduce the full
6+
implicit-type-alias test suite here, just some particularly interesting cases.
7+
8+
## Basic
9+
10+
### as `TypeAlias`
11+
12+
```py
13+
from typing import TypeAlias
14+
15+
IntOrStr: TypeAlias = int | str
16+
17+
def _(x: IntOrStr):
18+
reveal_type(x) # revealed: int | str
19+
```
20+
21+
### as `typing.TypeAlias`
22+
23+
```py
24+
import typing
25+
26+
IntOrStr: typing.TypeAlias = int | str
27+
28+
def _(x: IntOrStr):
29+
reveal_type(x) # revealed: int | str
30+
```
31+
32+
## Can be used as value
33+
34+
Because PEP 613 type aliases are just annotated assignments, they can be used as values, like a
35+
legacy type expression (and unlike a PEP 695 type alias). We might prefer this wasn't allowed, but
36+
people do use it.
37+
38+
```py
39+
from typing import TypeAlias
40+
41+
MyExc: TypeAlias = Exception
42+
43+
try:
44+
raise MyExc("error")
45+
except MyExc as e:
46+
reveal_type(e) # revealed: Exception
47+
```
48+
49+
## Imported
50+
51+
`alias.py`:
52+
53+
```py
54+
from typing import TypeAlias
55+
56+
MyAlias: TypeAlias = int | str
57+
```
58+
59+
`main.py`:
60+
61+
```py
62+
from alias import MyAlias
63+
64+
def _(x: MyAlias):
65+
reveal_type(x) # revealed: int | str
66+
```
67+
68+
## Cyclic
469

570
```py
671
from typing import TypeAlias
@@ -15,3 +80,28 @@ RecursiveHomogeneousTuple: TypeAlias = tuple[int | "RecursiveHomogeneousTuple",
1580
def _(rec: RecursiveHomogeneousTuple):
1681
reveal_type(rec) # revealed: tuple[Divergent, ...]
1782
```
83+
84+
## Invalid position
85+
86+
`typing.TypeAlias` must be used as the sole annotation in an annotated assignment. Use in any other
87+
context is an error.
88+
89+
```py
90+
from typing import TypeAlias
91+
92+
# error: [invalid-type-form]
93+
def _(x: TypeAlias):
94+
reveal_type(x) # revealed: Unknown
95+
96+
# error: [invalid-type-form]
97+
y: list[TypeAlias] = []
98+
```
99+
100+
## RHS is required
101+
102+
```py
103+
from typing import TypeAlias
104+
105+
# error: [invalid-type-form]
106+
Empty: TypeAlias
107+
```

crates/ty_python_semantic/resources/mdtest/pep695_type_aliases.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def f() -> None:
2828
```py
2929
type IntOrStr = int | str
3030

31-
reveal_type(IntOrStr.__value__) # revealed: @Todo(Support for `typing.TypeAlias`)
31+
reveal_type(IntOrStr.__value__) # revealed: Any
3232
```
3333

3434
## Invalid assignment

0 commit comments

Comments
 (0)