Skip to content

Commit b892e45

Browse files
dcreagercarljm
andauthored
[ty] Track when type variables are inferable or not (#19786)
`Type::TypeVar` now distinguishes whether the typevar in question is inferable or not. A typevar is _not inferable_ inside the body of the generic class or function that binds it: ```py def f[T](t: T) -> T: return t ``` The infered type of `t` in the function body is `TypeVar(T, NotInferable)`. This represents how e.g. assignability checks need to be valid for all possible specializations of the typevar. Most of the existing assignability/etc logic only applies to non-inferable typevars. Outside of the function body, the typevar is _inferable_: ```py f(4) ``` Here, the parameter type of `f` is `TypeVar(T, Inferable)`. This represents how e.g. assignability doesn't need to hold for _all_ specializations; instead, we need to find the constraints under which this specific assignability check holds. This is in support of starting to perform specialization inference _as part of_ performing the assignability check at the call site. In the [[POPL2015][]] paper, this concept is called _monomorphic_ / _polymorphic_, but I thought _non-inferable_ / _inferable_ would be clearer for us. Depends on #19784 [POPL2015]: https://doi.org/10.1145/2676726.2676991 --------- Co-authored-by: Carl Meyer <[email protected]>
1 parent 9ac39ce commit b892e45

File tree

21 files changed

+383
-158
lines changed

21 files changed

+383
-158
lines changed

crates/ty_ide/src/completion.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1247,7 +1247,7 @@ quux.<CURSOR>
12471247
__init_subclass__ :: bound method Quux.__init_subclass__() -> None
12481248
__module__ :: str
12491249
__ne__ :: bound method Quux.__ne__(value: object, /) -> bool
1250-
__new__ :: bound method Quux.__new__() -> Self@__new__
1250+
__new__ :: bound method Quux.__new__() -> Quux
12511251
__reduce__ :: bound method Quux.__reduce__() -> str | tuple[Any, ...]
12521252
__reduce_ex__ :: bound method Quux.__reduce_ex__(protocol: SupportsIndex, /) -> str | tuple[Any, ...]
12531253
__repr__ :: bound method Quux.__repr__() -> str
@@ -1292,7 +1292,7 @@ quux.b<CURSOR>
12921292
__init_subclass__ :: bound method Quux.__init_subclass__() -> None
12931293
__module__ :: str
12941294
__ne__ :: bound method Quux.__ne__(value: object, /) -> bool
1295-
__new__ :: bound method Quux.__new__() -> Self@__new__
1295+
__new__ :: bound method Quux.__new__() -> Quux
12961296
__reduce__ :: bound method Quux.__reduce__() -> str | tuple[Any, ...]
12971297
__reduce_ex__ :: bound method Quux.__reduce_ex__(protocol: SupportsIndex, /) -> str | tuple[Any, ...]
12981298
__repr__ :: bound method Quux.__repr__() -> str

crates/ty_ide/src/semantic_tokens.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,9 @@ impl<'db> SemanticTokenVisitor<'db> {
337337

338338
match ty {
339339
Type::ClassLiteral(_) => (SemanticTokenType::Class, modifiers),
340-
Type::TypeVar(_) => (SemanticTokenType::TypeParameter, modifiers),
340+
Type::NonInferableTypeVar(_) | Type::TypeVar(_) => {
341+
(SemanticTokenType::TypeParameter, modifiers)
342+
}
341343
Type::FunctionLiteral(_) => {
342344
// Check if this is a method based on current scope
343345
if self.in_class_scope {

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

Lines changed: 34 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
# Self
22

3+
```toml
4+
[environment]
5+
python-version = "3.11"
6+
```
7+
38
`Self` is treated as if it were a `TypeVar` bound to the class it's being used on.
49

510
`typing.Self` is only available in Python 3.11 and later.
611

712
## Methods
813

9-
```toml
10-
[environment]
11-
python-version = "3.11"
12-
```
13-
1414
```py
1515
from typing import Self
1616

@@ -74,11 +74,6 @@ reveal_type(C().method()) # revealed: C
7474

7575
## Class Methods
7676

77-
```toml
78-
[environment]
79-
python-version = "3.11"
80-
```
81-
8277
```py
8378
from typing import Self, TypeVar
8479

@@ -101,11 +96,6 @@ reveal_type(Shape.bar()) # revealed: Unknown
10196

10297
## Attributes
10398

104-
```toml
105-
[environment]
106-
python-version = "3.11"
107-
```
108-
10999
TODO: The use of `Self` to annotate the `next_node` attribute should be
110100
[modeled as a property][self attribute], using `Self` in its parameter and return type.
111101

@@ -127,11 +117,6 @@ reveal_type(LinkedList().next()) # revealed: LinkedList
127117

128118
## Generic Classes
129119

130-
```toml
131-
[environment]
132-
python-version = "3.11"
133-
```
134-
135120
```py
136121
from typing import Self, Generic, TypeVar
137122

@@ -153,11 +138,6 @@ TODO: <https://typing.python.org/en/latest/spec/generics.html#use-in-protocols>
153138

154139
## Annotations
155140

156-
```toml
157-
[environment]
158-
python-version = "3.11"
159-
```
160-
161141
```py
162142
from typing import Self
163143

@@ -171,11 +151,6 @@ class Shape:
171151

172152
`Self` cannot be used in the signature of a function or variable.
173153

174-
```toml
175-
[environment]
176-
python-version = "3.11"
177-
```
178-
179154
```py
180155
from typing import Self, Generic, TypeVar
181156

@@ -218,4 +193,33 @@ class MyMetaclass(type):
218193
return super().__new__(cls)
219194
```
220195

196+
## Binding a method fixes `Self`
197+
198+
When a method is bound, any instances of `Self` in its signature are "fixed", since we now know the
199+
specific type of the bound parameter.
200+
201+
```py
202+
from typing import Self
203+
204+
class C:
205+
def instance_method(self, other: Self) -> Self:
206+
return self
207+
208+
@classmethod
209+
def class_method(cls) -> Self:
210+
return cls()
211+
212+
# revealed: bound method C.instance_method(other: C) -> C
213+
reveal_type(C().instance_method)
214+
# revealed: bound method <class 'C'>.class_method() -> C
215+
reveal_type(C.class_method)
216+
217+
class D(C): ...
218+
219+
# revealed: bound method D.instance_method(other: D) -> D
220+
reveal_type(D().instance_method)
221+
# revealed: bound method <class 'D'>.class_method() -> D
222+
reveal_type(D.class_method)
223+
```
224+
221225
[self attribute]: https://typing.python.org/en/latest/spec/generics.html#use-in-attribute-annotations

crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,24 @@ class A(Generic[T]):
491491
reveal_type(A(x=1)) # revealed: A[int]
492492
```
493493

494+
### Class typevar has another typevar as a default
495+
496+
```py
497+
from typing import Generic, TypeVar
498+
499+
T = TypeVar("T")
500+
U = TypeVar("U", default=T)
501+
502+
class C(Generic[T, U]): ...
503+
504+
reveal_type(C()) # revealed: C[Unknown, Unknown]
505+
506+
class D(Generic[T, U]):
507+
def __init__(self) -> None: ...
508+
509+
reveal_type(D()) # revealed: D[Unknown, Unknown]
510+
```
511+
494512
## Generic subclass
495513

496514
When a generic subclass fills its superclass's type parameter with one of its own, the actual types

crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,19 @@ class A[T]:
438438
reveal_type(A(x=1)) # revealed: A[int]
439439
```
440440

441+
### Class typevar has another typevar as a default
442+
443+
```py
444+
class C[T, U = T]: ...
445+
446+
reveal_type(C()) # revealed: C[Unknown, Unknown]
447+
448+
class D[T, U = T]:
449+
def __init__(self) -> None: ...
450+
451+
reveal_type(D()) # revealed: D[Unknown, Unknown]
452+
```
453+
441454
## Generic subclass
442455

443456
When a generic subclass fills its superclass's type parameter with one of its own, the actual types

crates/ty_python_semantic/resources/mdtest/named_tuple.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ class Person(NamedTuple):
243243

244244
reveal_type(Person._field_defaults) # revealed: dict[str, Any]
245245
reveal_type(Person._fields) # revealed: tuple[str, ...]
246-
reveal_type(Person._make) # revealed: bound method <class 'Person'>._make(iterable: Iterable[Any]) -> Self@_make
246+
reveal_type(Person._make) # revealed: bound method <class 'Person'>._make(iterable: Iterable[Any]) -> Person
247247
reveal_type(Person._asdict) # revealed: def _asdict(self) -> dict[str, Any]
248248
reveal_type(Person._replace) # revealed: def _replace(self, **kwargs: Any) -> Self@_replace
249249

@@ -304,8 +304,8 @@ satisfy:
304304
```py
305305
def expects_named_tuple(x: typing.NamedTuple):
306306
reveal_type(x) # revealed: tuple[object, ...] & NamedTupleLike
307-
reveal_type(x._make) # revealed: bound method type[NamedTupleLike]._make(iterable: Iterable[Any]) -> Self@_make
308-
reveal_type(x._replace) # revealed: bound method NamedTupleLike._replace(**kwargs) -> Self@_replace
307+
reveal_type(x._make) # revealed: bound method type[NamedTupleLike]._make(iterable: Iterable[Any]) -> NamedTupleLike
308+
reveal_type(x._replace) # revealed: bound method NamedTupleLike._replace(**kwargs) -> NamedTupleLike
309309
# revealed: Overload[(value: tuple[object, ...], /) -> tuple[object, ...], (value: tuple[_T@__add__, ...], /) -> tuple[object, ...]]
310310
reveal_type(x.__add__)
311311
reveal_type(x.__iter__) # revealed: bound method tuple[object, ...].__iter__() -> Iterator[object]
@@ -328,7 +328,7 @@ class Point(NamedTuple):
328328
x: int
329329
y: int
330330

331-
reveal_type(Point._make) # revealed: bound method <class 'Point'>._make(iterable: Iterable[Any]) -> Self@_make
331+
reveal_type(Point._make) # revealed: bound method <class 'Point'>._make(iterable: Iterable[Any]) -> Point
332332
reveal_type(Point._asdict) # revealed: def _asdict(self) -> dict[str, Any]
333333
reveal_type(Point._replace) # revealed: def _replace(self, **kwargs: Any) -> Self@_replace
334334

crates/ty_python_semantic/src/semantic_model.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ impl<'db> Completion<'db> {
232232
| Type::BytesLiteral(_) => CompletionKind::Value,
233233
Type::EnumLiteral(_) => CompletionKind::Enum,
234234
Type::ProtocolInstance(_) => CompletionKind::Interface,
235-
Type::TypeVar(_) => CompletionKind::TypeParameter,
235+
Type::NonInferableTypeVar(_) | Type::TypeVar(_) => CompletionKind::TypeParameter,
236236
Type::Union(union) => union.elements(db).iter().find_map(|&ty| imp(db, ty))?,
237237
Type::Intersection(intersection) => {
238238
intersection.iter_positive(db).find_map(|ty| imp(db, ty))?

0 commit comments

Comments
 (0)