Skip to content

Commit 496ca84

Browse files
committed
[ty] support imported PEP 613 type aliases
1 parent 988c38c commit 496ca84

File tree

15 files changed

+188
-69
lines changed

15 files changed

+188
-69
lines changed

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/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

crates/ty_python_semantic/resources/mdtest/sys_version_info.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ properties on instance types:
122122

123123
```py
124124
reveal_type(sys.version_info.micro) # revealed: int
125-
reveal_type(sys.version_info.releaselevel) # revealed: @Todo(Support for `typing.TypeAlias`)
125+
reveal_type(sys.version_info.releaselevel) # revealed: Literal["alpha", "beta", "candidate", "final"]
126126
reveal_type(sys.version_info.serial) # revealed: int
127127
```
128128

crates/ty_python_semantic/src/types.rs

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6656,7 +6656,15 @@ impl<'db> Type<'db> {
66566656

66576657
Ok(typing_self(db, scope_id, typevar_binding_context, class).unwrap_or(*self))
66586658
}
6659-
SpecialFormType::TypeAlias => Ok(Type::Dynamic(DynamicType::TodoTypeAlias)),
6659+
// We ensure that `typing.TypeAlias` used in the expected position (annotating an
6660+
// annotated assignment statement) doesn't reach here. Using it in any other type
6661+
// expression is an error.
6662+
SpecialFormType::TypeAlias => Err(InvalidTypeExpressionError {
6663+
invalid_expressions: smallvec::smallvec_inline![
6664+
InvalidTypeExpression::TypeAlias
6665+
],
6666+
fallback_type: Type::unknown(),
6667+
}),
66606668
SpecialFormType::TypedDict => Err(InvalidTypeExpressionError {
66616669
invalid_expressions: smallvec::smallvec_inline![
66626670
InvalidTypeExpression::TypedDict
@@ -8073,9 +8081,6 @@ pub enum DynamicType<'db> {
80738081
///
80748082
/// This variant should be created with the `todo_type!` macro.
80758083
Todo(TodoType),
8076-
/// A special Todo-variant for type aliases declared using `typing.TypeAlias`.
8077-
/// A temporary variant to detect and special-case the handling of these aliases in autocomplete suggestions.
8078-
TodoTypeAlias,
80798084
/// A special Todo-variant for `Unpack[Ts]`, so that we can treat it specially in `Generic[Unpack[Ts]]`
80808085
TodoUnpack,
80818086
/// A type that is determined to be divergent during type inference for a recursive function.
@@ -8107,13 +8112,6 @@ impl std::fmt::Display for DynamicType<'_> {
81078112
f.write_str("@Todo")
81088113
}
81098114
}
8110-
DynamicType::TodoTypeAlias => {
8111-
if cfg!(debug_assertions) {
8112-
f.write_str("@Todo(Support for `typing.TypeAlias`)")
8113-
} else {
8114-
f.write_str("@Todo")
8115-
}
8116-
}
81178115
DynamicType::Divergent(_) => f.write_str("Divergent"),
81188116
}
81198117
}
@@ -8272,6 +8270,9 @@ enum InvalidTypeExpression<'db> {
82728270
ConstraintSet,
82738271
/// Same for `typing.TypedDict`
82748272
TypedDict,
8273+
/// Same for `typing.TypeAlias`, anywhere except for as the sole annotation on an annotated
8274+
/// assignment
8275+
TypeAlias,
82758276
/// Type qualifiers are always invalid in *type expressions*,
82768277
/// but these ones are okay with 0 arguments in *annotation expressions*
82778278
TypeQualifier(SpecialFormType),
@@ -8327,6 +8328,11 @@ impl<'db> InvalidTypeExpression<'db> {
83278328
"The special form `typing.TypedDict` is not allowed in type expressions. \
83288329
Did you mean to use a concrete TypedDict or `collections.abc.Mapping[str, object]` instead?")
83298330
}
8331+
InvalidTypeExpression::TypeAlias => {
8332+
f.write_str(
8333+
"`typing.TypeAlias` is only allowed as the sole annotation on an annotated assignment",
8334+
)
8335+
}
83308336
InvalidTypeExpression::TypeQualifier(qualifier) => write!(
83318337
f,
83328338
"Type qualifier `{qualifier}` is not allowed in type expressions \

0 commit comments

Comments
 (0)