Skip to content

Commit 40b7c95

Browse files
committed
add support for generic PEP695 type aliases
1 parent 5d52902 commit 40b7c95

File tree

7 files changed

+413
-40
lines changed

7 files changed

+413
-40
lines changed

crates/ty_ide/src/hover.rs

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -312,14 +312,14 @@ mod tests {
312312
This is such a great class!!
313313
314314
Don't you know?
315-
315+
316316
Everyone loves my class!!
317317
318318
'''
319319
def __init__(self, val):
320320
"""initializes MyClass (perfectly)"""
321321
self.val = val
322-
322+
323323
def my_method(self, a, b):
324324
'''This is such a great func!!
325325
@@ -379,14 +379,14 @@ mod tests {
379379
This is such a great class!!
380380
381381
Don't you know?
382-
382+
383383
Everyone loves my class!!
384384
385385
'''
386386
def __init__(self, val):
387387
"""initializes MyClass (perfectly)"""
388388
self.val = val
389-
389+
390390
def my_method(self, a, b):
391391
'''This is such a great func!!
392392
@@ -444,14 +444,14 @@ mod tests {
444444
This is such a great class!!
445445
446446
Don't you know?
447-
447+
448448
Everyone loves my class!!
449449
450450
'''
451451
def __init__(self, val):
452452
"""initializes MyClass (perfectly)"""
453453
self.val = val
454-
454+
455455
def my_method(self, a, b):
456456
'''This is such a great func!!
457457
@@ -505,7 +505,7 @@ mod tests {
505505
This is such a great class!!
506506
507507
Don't you know?
508-
508+
509509
Everyone loves my class!!
510510
511511
'''
@@ -562,13 +562,13 @@ mod tests {
562562
This is such a great class!!
563563
564564
Don't you know?
565-
565+
566566
Everyone loves my class!!
567567
568568
'''
569569
def __init__(self, val):
570570
self.val = val
571-
571+
572572
def my_method(self, a, b):
573573
'''This is such a great func!!
574574
@@ -628,14 +628,14 @@ mod tests {
628628
This is such a great class!!
629629
630630
Don't you know?
631-
631+
632632
Everyone loves my class!!
633633
634634
'''
635635
def __init__(self, val):
636636
"""initializes MyClass (perfectly)"""
637637
self.val = val
638-
638+
639639
def my_method(self, a, b):
640640
'''This is such a great func!!
641641
@@ -1591,10 +1591,10 @@ def ab(a: int, *, c: int):
15911591

15921592
// TODO: This should render T@Alias once we create GenericContexts for type alias scopes.
15931593
assert_snapshot!(test.hover(), @r"
1594-
typing.TypeVar
1594+
T@Alias
15951595
---------------------------------------------
15961596
```python
1597-
typing.TypeVar
1597+
T@Alias
15981598
```
15991599
---------------------------------------------
16001600
info[hover]: Hovered content is
@@ -1875,9 +1875,9 @@ def ab(a: int, *, c: int):
18751875
def foo(a: str | None, b):
18761876
'''
18771877
My cool func
1878-
1878+
18791879
Args:
1880-
a: hopefully a string, right?!
1880+
a: hopefully a string, right?!
18811881
'''
18821882
if a is not None:
18831883
print(a<CURSOR>)
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
# Generic type aliases: PEP 695 syntax
2+
3+
```toml
4+
[environment]
5+
python-version = "3.13"
6+
```
7+
8+
## Defining a generic alias
9+
10+
At its simplest, to define a type alias using PEP 695 syntax, you add a list of `TypeVar`s,
11+
`ParamSpec`s or `TypeVarTuple`s after the alias name.
12+
13+
```py
14+
from ty_extensions import generic_context
15+
16+
type SingleTypevar[T] = ...
17+
type MultipleTypevars[T, S] = ...
18+
type SingleParamSpec[**P] = ...
19+
type TypeVarAndParamSpec[T, **P] = ...
20+
type SingleTypeVarTuple[*Ts] = ...
21+
type TypeVarAndTypeVarTuple[T, *Ts] = ...
22+
23+
# revealed: tuple[T@SingleTypevar]
24+
reveal_type(generic_context(SingleTypevar))
25+
# revealed: tuple[T@MultipleTypevars, S@MultipleTypevars]
26+
reveal_type(generic_context(MultipleTypevars))
27+
28+
# TODO: support `ParamSpec`/`TypeVarTuple` properly
29+
# (these should include the `ParamSpec`s and `TypeVarTuple`s in their generic contexts)
30+
reveal_type(generic_context(SingleParamSpec)) # revealed: tuple[()]
31+
reveal_type(generic_context(TypeVarAndParamSpec)) # revealed: tuple[T@TypeVarAndParamSpec]
32+
reveal_type(generic_context(SingleTypeVarTuple)) # revealed: tuple[()]
33+
reveal_type(generic_context(TypeVarAndTypeVarTuple)) # revealed: tuple[T@TypeVarAndTypeVarTuple]
34+
```
35+
36+
You cannot use the same typevar more than once.
37+
38+
```py
39+
# error: [invalid-syntax] "duplicate type parameter"
40+
type RepeatedTypevar[T, T] = ...
41+
```
42+
43+
You can only use typevars (TODO: or param specs or typevar tuples) in the alias's generic context.
44+
45+
```py
46+
# TODO: error
47+
type GenericOfType[int] = ...
48+
```
49+
50+
## Specializing type aliases explicitly
51+
52+
The type parameter can be specified explicitly:
53+
54+
```py
55+
from typing import Literal
56+
57+
type C[T] = T
58+
59+
def reveal(a: C[int], b: C[Literal[5]]):
60+
reveal_type(a) # revealed: int
61+
reveal_type(b) # revealed: Literal[5]
62+
```
63+
64+
The specialization must match the generic types:
65+
66+
```py
67+
# error: [too-many-positional-arguments] "Too many positional arguments: expected 1, got 2"
68+
reveal_type(C[int, int]) # revealed: Unknown
69+
```
70+
71+
And non-generic types cannot be specialized:
72+
73+
```py
74+
type B = ...
75+
76+
# error: [non-subscriptable] "Cannot subscript non-generic type alias"
77+
reveal_type(B[int]) # revealed: Unknown
78+
79+
# error: [non-subscriptable] "Cannot subscript non-generic type alias"
80+
def x(b: B[int]): ...
81+
```
82+
83+
If the type variable has an upper bound, the specialized type must satisfy that bound:
84+
85+
```py
86+
type Bounded[T: int] = ...
87+
type BoundedByUnion[T: int | str] = ...
88+
89+
class IntSubclass(int): ...
90+
91+
reveal_type(Bounded[int]) # revealed: Bounded[int]
92+
reveal_type(Bounded[IntSubclass]) # revealed: Bounded[IntSubclass]
93+
94+
# TODO: update this diagnostic to talk about type parameters and specializations
95+
# error: [invalid-argument-type] "Argument is incorrect: Expected `int`, found `str`"
96+
reveal_type(Bounded[str]) # revealed: Unknown
97+
98+
# TODO: update this diagnostic to talk about type parameters and specializations
99+
# error: [invalid-argument-type] "Argument is incorrect: Expected `int`, found `int | str`"
100+
reveal_type(Bounded[int | str]) # revealed: Unknown
101+
102+
reveal_type(BoundedByUnion[int]) # revealed: BoundedByUnion[int]
103+
reveal_type(BoundedByUnion[IntSubclass]) # revealed: BoundedByUnion[IntSubclass]
104+
reveal_type(BoundedByUnion[str]) # revealed: BoundedByUnion[str]
105+
reveal_type(BoundedByUnion[int | str]) # revealed: BoundedByUnion[int | str]
106+
```
107+
108+
If the type variable is constrained, the specialized type must satisfy those constraints:
109+
110+
```py
111+
type Constrained[T: (int, str)] = ...
112+
113+
reveal_type(Constrained[int]) # revealed: Constrained[int]
114+
115+
# TODO: error: [invalid-argument-type]
116+
# TODO: revealed: Constrained[Unknown]
117+
reveal_type(Constrained[IntSubclass]) # revealed: Constrained[IntSubclass]
118+
119+
reveal_type(Constrained[str]) # revealed: Constrained[str]
120+
121+
# TODO: error: [invalid-argument-type]
122+
# TODO: revealed: Unknown
123+
reveal_type(Constrained[int | str]) # revealed: Constrained[int | str]
124+
125+
# TODO: update this diagnostic to talk about type parameters and specializations
126+
# error: [invalid-argument-type] "Argument is incorrect: Expected `int | str`, found `object`"
127+
reveal_type(Constrained[object]) # revealed: Unknown
128+
```
129+
130+
If the type variable has a default, it can be omitted:
131+
132+
```py
133+
type WithDefault[T, U = int] = ...
134+
135+
reveal_type(WithDefault[str, str]) # revealed: WithDefault[str, str]
136+
reveal_type(WithDefault[str]) # revealed: WithDefault[str, int]
137+
```
138+
139+
## Aliases are not callable
140+
141+
```py
142+
type A = int
143+
type B[T] = T
144+
145+
# error: [call-non-callable] "Object of type `TypeAliasType` is not callable"
146+
reveal_type(A()) # revealed: Unknown
147+
148+
# error: [call-non-callable] "Object of type `GenericAlias` is not callable"
149+
reveal_type(B[int]()) # revealed: Unknown
150+
```

crates/ty_python_semantic/resources/mdtest/pep695_type_aliases.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ T = TypeVar("T")
188188
IntAnd = TypeAliasType("IntAndT", tuple[int, T], type_params=(T,))
189189

190190
def f(x: IntAnd[str]) -> None:
191-
reveal_type(x) # revealed: @Todo(Generic PEP-695 type alias)
191+
reveal_type(x) # revealed: @Todo(Generic manual PEP-695 type alias)
192192
```
193193

194194
### Error cases

0 commit comments

Comments
 (0)