Skip to content

Commit 06cd249

Browse files
authored
[ty] Track different uses of legacy typevars, including context when rendering typevars (#19604)
This PR introduces a few related changes: - We now keep track of each time a legacy typevar is bound in a different generic context (e.g. class, function), and internally create a new `TypeVarInstance` for each usage. This means the rest of the code can now assume that salsa-equivalent `TypeVarInstance`s refer to the same typevar, even taking into account that legacy typevars can be used more than once. - We also go ahead and track the binding context of PEP 695 typevars. That's _much_ easier to track since we have the binding context right there during type inference. - With that in place, we can now include the name of the binding context when rendering typevars (e.g. `T@f` instead of `T`)
1 parent 48d5bd1 commit 06cd249

28 files changed

+394
-128
lines changed

crates/ty_ide/src/completion.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1247,7 +1247,7 @@ quux.<CURSOR>
12471247
__init_subclass__ :: bound method object.__init_subclass__() -> None
12481248
__module__ :: str
12491249
__ne__ :: bound method object.__ne__(value: object, /) -> bool
1250-
__new__ :: bound method object.__new__() -> Self
1250+
__new__ :: bound method object.__new__() -> Self@object
12511251
__reduce__ :: bound method object.__reduce__() -> str | tuple[Any, ...]
12521252
__reduce_ex__ :: bound method object.__reduce_ex__(protocol: SupportsIndex, /) -> str | tuple[Any, ...]
12531253
__repr__ :: bound method object.__repr__() -> str
@@ -1292,7 +1292,7 @@ quux.b<CURSOR>
12921292
__init_subclass__ :: bound method object.__init_subclass__() -> None
12931293
__module__ :: str
12941294
__ne__ :: bound method object.__ne__(value: object, /) -> bool
1295-
__new__ :: bound method object.__new__() -> Self
1295+
__new__ :: bound method object.__new__() -> Self@object
12961296
__reduce__ :: bound method object.__reduce__() -> str | tuple[Any, ...]
12971297
__reduce_ex__ :: bound method object.__reduce_ex__(protocol: SupportsIndex, /) -> str | tuple[Any, ...]
12981298
__repr__ :: bound method object.__repr__() -> str
@@ -1346,7 +1346,7 @@ C.<CURSOR>
13461346
__mro__ :: tuple[<class 'C'>, <class 'object'>]
13471347
__name__ :: str
13481348
__ne__ :: def __ne__(self, value: object, /) -> bool
1349-
__new__ :: def __new__(cls) -> Self
1349+
__new__ :: def __new__(cls) -> Self@object
13501350
__or__ :: bound method <class 'C'>.__or__(value: Any, /) -> UnionType
13511351
__prepare__ :: bound method <class 'Meta'>.__prepare__(name: str, bases: tuple[type, ...], /, **kwds: Any) -> MutableMapping[str, object]
13521352
__qualname__ :: str
@@ -1522,7 +1522,7 @@ Quux.<CURSOR>
15221522
__mro__ :: tuple[<class 'Quux'>, <class 'object'>]
15231523
__name__ :: str
15241524
__ne__ :: def __ne__(self, value: object, /) -> bool
1525-
__new__ :: def __new__(cls) -> Self
1525+
__new__ :: def __new__(cls) -> Self@object
15261526
__or__ :: bound method <class 'Quux'>.__or__(value: Any, /) -> UnionType
15271527
__prepare__ :: bound method <class 'type'>.__prepare__(name: str, bases: tuple[type, ...], /, **kwds: Any) -> MutableMapping[str, object]
15281528
__qualname__ :: str
@@ -1574,8 +1574,8 @@ Answer.<CURSOR>
15741574
__bool__ :: bound method <class 'Answer'>.__bool__() -> Literal[True]
15751575
__class__ :: <class 'EnumMeta'>
15761576
__contains__ :: bound method <class 'Answer'>.__contains__(value: object) -> bool
1577-
__copy__ :: def __copy__(self) -> Self
1578-
__deepcopy__ :: def __deepcopy__(self, memo: Any) -> Self
1577+
__copy__ :: def __copy__(self) -> Self@Enum
1578+
__deepcopy__ :: def __deepcopy__(self, memo: Any) -> Self@Enum
15791579
__delattr__ :: def __delattr__(self, name: str, /) -> None
15801580
__dict__ :: MappingProxyType[str, Any]
15811581
__dictoffset__ :: int
@@ -1585,28 +1585,28 @@ Answer.<CURSOR>
15851585
__flags__ :: int
15861586
__format__ :: def __format__(self, format_spec: str) -> str
15871587
__getattribute__ :: def __getattribute__(self, name: str, /) -> Any
1588-
__getitem__ :: bound method <class 'Answer'>.__getitem__(name: str) -> _EnumMemberT
1588+
__getitem__ :: bound method <class 'Answer'>.__getitem__(name: str) -> _EnumMemberT@__getitem__
15891589
__getstate__ :: def __getstate__(self) -> object
15901590
__hash__ :: def __hash__(self) -> int
15911591
__init__ :: def __init__(self) -> None
15921592
__init_subclass__ :: def __init_subclass__(cls) -> None
15931593
__instancecheck__ :: bound method <class 'Answer'>.__instancecheck__(instance: Any, /) -> bool
15941594
__itemsize__ :: int
1595-
__iter__ :: bound method <class 'Answer'>.__iter__() -> Iterator[_EnumMemberT]
1595+
__iter__ :: bound method <class 'Answer'>.__iter__() -> Iterator[_EnumMemberT@__iter__]
15961596
__len__ :: bound method <class 'Answer'>.__len__() -> int
15971597
__members__ :: MappingProxyType[str, Unknown]
15981598
__module__ :: str
15991599
__mro__ :: tuple[<class 'Answer'>, <class 'Enum'>, <class 'object'>]
16001600
__name__ :: str
16011601
__ne__ :: def __ne__(self, value: object, /) -> bool
1602-
__new__ :: def __new__(cls, value: object) -> Self
1602+
__new__ :: def __new__(cls, value: object) -> Self@Enum
16031603
__or__ :: bound method <class 'Answer'>.__or__(value: Any, /) -> UnionType
16041604
__order__ :: str
16051605
__prepare__ :: bound method <class 'EnumMeta'>.__prepare__(cls: str, bases: tuple[type, ...], **kwds: Any) -> _EnumDict
16061606
__qualname__ :: str
16071607
__reduce__ :: def __reduce__(self) -> str | tuple[Any, ...]
16081608
__repr__ :: def __repr__(self) -> str
1609-
__reversed__ :: bound method <class 'Answer'>.__reversed__() -> Iterator[_EnumMemberT]
1609+
__reversed__ :: bound method <class 'Answer'>.__reversed__() -> Iterator[_EnumMemberT@__reversed__]
16101610
__ror__ :: bound method <class 'Answer'>.__ror__(value: Any, /) -> UnionType
16111611
__setattr__ :: def __setattr__(self, name: str, value: Any, /) -> None
16121612
__signature__ :: bound method <class 'Answer'>.__signature__() -> str

crates/ty_ide/src/hover.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -371,10 +371,10 @@ mod tests {
371371
);
372372

373373
assert_snapshot!(test.hover(), @r"
374-
T
374+
T@Alias
375375
---------------------------------------------
376376
```python
377-
T
377+
T@Alias
378378
```
379379
---------------------------------------------
380380
info[hover]: Hovered content is

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,15 @@ from typing import Self
1616

1717
class Shape:
1818
def set_scale(self: Self, scale: float) -> Self:
19-
reveal_type(self) # revealed: Self
19+
reveal_type(self) # revealed: Self@Shape
2020
return self
2121

2222
def nested_type(self: Self) -> list[Self]:
2323
return [self]
2424

2525
def nested_func(self: Self) -> Self:
2626
def inner() -> Self:
27-
reveal_type(self) # revealed: Self
27+
reveal_type(self) # revealed: Self@Shape
2828
return self
2929
return inner()
3030

@@ -38,13 +38,13 @@ reveal_type(Shape().nested_func()) # revealed: Shape
3838

3939
class Circle(Shape):
4040
def set_scale(self: Self, scale: float) -> Self:
41-
reveal_type(self) # revealed: Self
41+
reveal_type(self) # revealed: Self@Circle
4242
return self
4343

4444
class Outer:
4545
class Inner:
4646
def foo(self: Self) -> Self:
47-
reveal_type(self) # revealed: Self
47+
reveal_type(self) # revealed: Self@Inner
4848
return self
4949
```
5050

@@ -151,7 +151,7 @@ from typing import Self
151151

152152
class Shape:
153153
def union(self: Self, other: Self | None):
154-
reveal_type(other) # revealed: Self | None
154+
reveal_type(other) # revealed: Self@Shape | None
155155
return self
156156
```
157157

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def i(callback: Callable[Concatenate[int, P], R_co], *args: P.args, **kwargs: P.
2626

2727
class Foo:
2828
def method(self, x: Self):
29-
reveal_type(x) # revealed: Self
29+
reveal_type(x) # revealed: Self@Foo
3030
```
3131

3232
## Type expressions

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

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@ S = TypeVar("S")
1515
class SingleTypevar(Generic[T]): ...
1616
class MultipleTypevars(Generic[T, S]): ...
1717

18-
reveal_type(generic_context(SingleTypevar)) # revealed: tuple[T]
19-
reveal_type(generic_context(MultipleTypevars)) # revealed: tuple[T, S]
18+
# revealed: tuple[T@SingleTypevar]
19+
reveal_type(generic_context(SingleTypevar))
20+
# revealed: tuple[T@MultipleTypevars, S@MultipleTypevars]
21+
reveal_type(generic_context(MultipleTypevars))
2022
```
2123

2224
Inheriting from `Generic` multiple times yields a `duplicate-base` diagnostic, just like any other
@@ -49,9 +51,12 @@ class InheritedGeneric(MultipleTypevars[T, S]): ...
4951
class InheritedGenericPartiallySpecialized(MultipleTypevars[T, int]): ...
5052
class InheritedGenericFullySpecialized(MultipleTypevars[str, int]): ...
5153

52-
reveal_type(generic_context(InheritedGeneric)) # revealed: tuple[T, S]
53-
reveal_type(generic_context(InheritedGenericPartiallySpecialized)) # revealed: tuple[T]
54-
reveal_type(generic_context(InheritedGenericFullySpecialized)) # revealed: None
54+
# revealed: tuple[T@InheritedGeneric, S@InheritedGeneric]
55+
reveal_type(generic_context(InheritedGeneric))
56+
# revealed: tuple[T@InheritedGenericPartiallySpecialized]
57+
reveal_type(generic_context(InheritedGenericPartiallySpecialized))
58+
# revealed: None
59+
reveal_type(generic_context(InheritedGenericFullySpecialized))
5560
```
5661

5762
If you don't specialize a generic base class, we use the default specialization, which maps each
@@ -78,9 +83,12 @@ class ExplicitInheritedGenericPartiallySpecializedExtraTypevar(MultipleTypevars[
7883
# error: [invalid-generic-class] "`Generic` base class must include all type variables used in other base classes"
7984
class ExplicitInheritedGenericPartiallySpecializedMissingTypevar(MultipleTypevars[T, int], Generic[S]): ...
8085

81-
reveal_type(generic_context(ExplicitInheritedGeneric)) # revealed: tuple[T, S]
82-
reveal_type(generic_context(ExplicitInheritedGenericPartiallySpecialized)) # revealed: tuple[T]
83-
reveal_type(generic_context(ExplicitInheritedGenericPartiallySpecializedExtraTypevar)) # revealed: tuple[T, S]
86+
# revealed: tuple[T@ExplicitInheritedGeneric, S@ExplicitInheritedGeneric]
87+
reveal_type(generic_context(ExplicitInheritedGeneric))
88+
# revealed: tuple[T@ExplicitInheritedGenericPartiallySpecialized]
89+
reveal_type(generic_context(ExplicitInheritedGenericPartiallySpecialized))
90+
# revealed: tuple[T@ExplicitInheritedGenericPartiallySpecializedExtraTypevar, S@ExplicitInheritedGenericPartiallySpecializedExtraTypevar]
91+
reveal_type(generic_context(ExplicitInheritedGenericPartiallySpecializedExtraTypevar))
8492
```
8593

8694
## Specializing generic classes explicitly
@@ -446,18 +454,18 @@ class C(Generic[T]):
446454
def generic_method(self, t: T, u: U) -> U:
447455
return u
448456

449-
reveal_type(generic_context(C)) # revealed: tuple[T]
457+
reveal_type(generic_context(C)) # revealed: tuple[T@C]
450458
reveal_type(generic_context(C.method)) # revealed: None
451-
reveal_type(generic_context(C.generic_method)) # revealed: tuple[U]
459+
reveal_type(generic_context(C.generic_method)) # revealed: tuple[U@generic_method]
452460
reveal_type(generic_context(C[int])) # revealed: None
453461
reveal_type(generic_context(C[int].method)) # revealed: None
454-
reveal_type(generic_context(C[int].generic_method)) # revealed: tuple[U]
462+
reveal_type(generic_context(C[int].generic_method)) # revealed: tuple[U@generic_method]
455463

456464
c: C[int] = C[int]()
457465
reveal_type(c.generic_method(1, "string")) # revealed: Literal["string"]
458466
reveal_type(generic_context(c)) # revealed: None
459467
reveal_type(generic_context(c.method)) # revealed: None
460-
reveal_type(generic_context(c.generic_method)) # revealed: tuple[U]
468+
reveal_type(generic_context(c.generic_method)) # revealed: tuple[U@generic_method]
461469
```
462470

463471
## Specializations propagate
@@ -540,7 +548,8 @@ class WithOverloadedMethod(Generic[T]):
540548
def method(self, x: S | T) -> S | T:
541549
return x
542550

543-
reveal_type(WithOverloadedMethod[int].method) # revealed: Overload[(self, x: int) -> int, (self, x: S) -> S | int]
551+
# revealed: Overload[(self, x: int) -> int, (self, x: S@method) -> S@method | int]
552+
reveal_type(WithOverloadedMethod[int].method)
544553
```
545554

546555
## Cyclic class definitions

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ from typing import TypeVar
226226
T = TypeVar("T", bound=int)
227227

228228
def good_param(x: T) -> None:
229-
reveal_type(x) # revealed: T
229+
reveal_type(x) # revealed: T@good_param
230230
```
231231

232232
If the function is annotated as returning the typevar, this means that the upper bound is _not_
@@ -239,7 +239,7 @@ def good_return(x: T) -> T:
239239
return x
240240

241241
def bad_return(x: T) -> T:
242-
# error: [invalid-return-type] "Return type does not match returned value: expected `T`, found `int`"
242+
# error: [invalid-return-type] "Return type does not match returned value: expected `T@bad_return`, found `int`"
243243
return x + 1
244244
```
245245

@@ -257,7 +257,7 @@ def different_types(cond: bool, t: T, s: S) -> T:
257257
if cond:
258258
return t
259259
else:
260-
# error: [invalid-return-type] "Return type does not match returned value: expected `T`, found `S`"
260+
# error: [invalid-return-type] "Return type does not match returned value: expected `T@different_types`, found `S@different_types`"
261261
return s
262262

263263
def same_types(cond: bool, t1: T, t2: T) -> T:
@@ -279,7 +279,7 @@ T = TypeVar("T", int, str)
279279

280280
def same_constrained_types(t1: T, t2: T) -> T:
281281
# TODO: no error
282-
# error: [unsupported-operator] "Operator `+` is unsupported between objects of type `T` and `T`"
282+
# error: [unsupported-operator] "Operator `+` is unsupported between objects of type `T@same_constrained_types` and `T@same_constrained_types`"
283283
return t1 + t2
284284
```
285285

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ from typing import Callable, TypeVar
182182
T = TypeVar("T", bound=Callable[[], int])
183183

184184
def bound(f: T):
185-
reveal_type(f) # revealed: T
185+
reveal_type(f) # revealed: T@bound
186186
reveal_type(f()) # revealed: int
187187
```
188188

@@ -192,7 +192,7 @@ Same with a constrained typevar, as long as all constraints are callable:
192192
T = TypeVar("T", Callable[[], int], Callable[[], str])
193193

194194
def constrained(f: T):
195-
reveal_type(f) # revealed: T
195+
reveal_type(f) # revealed: T@constrained
196196
reveal_type(f()) # revealed: int | str
197197
```
198198

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

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@ from ty_extensions import generic_context
1616
class SingleTypevar[T]: ...
1717
class MultipleTypevars[T, S]: ...
1818

19-
reveal_type(generic_context(SingleTypevar)) # revealed: tuple[T]
20-
reveal_type(generic_context(MultipleTypevars)) # revealed: tuple[T, S]
19+
# revealed: tuple[T@SingleTypevar]
20+
reveal_type(generic_context(SingleTypevar))
21+
# revealed: tuple[T@MultipleTypevars, S@MultipleTypevars]
22+
reveal_type(generic_context(MultipleTypevars))
2123
```
2224

2325
You cannot use the same typevar more than once.
@@ -43,9 +45,12 @@ class InheritedGeneric[U, V](MultipleTypevars[U, V]): ...
4345
class InheritedGenericPartiallySpecialized[U](MultipleTypevars[U, int]): ...
4446
class InheritedGenericFullySpecialized(MultipleTypevars[str, int]): ...
4547

46-
reveal_type(generic_context(InheritedGeneric)) # revealed: tuple[U, V]
47-
reveal_type(generic_context(InheritedGenericPartiallySpecialized)) # revealed: tuple[U]
48-
reveal_type(generic_context(InheritedGenericFullySpecialized)) # revealed: None
48+
# revealed: tuple[U@InheritedGeneric, V@InheritedGeneric]
49+
reveal_type(generic_context(InheritedGeneric))
50+
# revealed: tuple[U@InheritedGenericPartiallySpecialized]
51+
reveal_type(generic_context(InheritedGenericPartiallySpecialized))
52+
# revealed: None
53+
reveal_type(generic_context(InheritedGenericFullySpecialized))
4954
```
5055

5156
If you don't specialize a generic base class, we use the default specialization, which maps each
@@ -406,18 +411,18 @@ class C[T]:
406411
# TODO: error
407412
def cannot_shadow_class_typevar[T](self, t: T): ...
408413

409-
reveal_type(generic_context(C)) # revealed: tuple[T]
414+
reveal_type(generic_context(C)) # revealed: tuple[T@C]
410415
reveal_type(generic_context(C.method)) # revealed: None
411-
reveal_type(generic_context(C.generic_method)) # revealed: tuple[U]
416+
reveal_type(generic_context(C.generic_method)) # revealed: tuple[U@generic_method]
412417
reveal_type(generic_context(C[int])) # revealed: None
413418
reveal_type(generic_context(C[int].method)) # revealed: None
414-
reveal_type(generic_context(C[int].generic_method)) # revealed: tuple[U]
419+
reveal_type(generic_context(C[int].generic_method)) # revealed: tuple[U@generic_method]
415420

416421
c: C[int] = C[int]()
417422
reveal_type(c.generic_method(1, "string")) # revealed: Literal["string"]
418423
reveal_type(generic_context(c)) # revealed: None
419424
reveal_type(generic_context(c.method)) # revealed: None
420-
reveal_type(generic_context(c.generic_method)) # revealed: tuple[U]
425+
reveal_type(generic_context(c.generic_method)) # revealed: tuple[U@generic_method]
421426
```
422427

423428
## Specializations propagate
@@ -466,7 +471,8 @@ class WithOverloadedMethod[T]:
466471
def method[S](self, x: S | T) -> S | T:
467472
return x
468473

469-
reveal_type(WithOverloadedMethod[int].method) # revealed: Overload[(self, x: int) -> int, (self, x: S) -> S | int]
474+
# revealed: Overload[(self, x: int) -> int, (self, x: S@method) -> S@method | int]
475+
reveal_type(WithOverloadedMethod[int].method)
470476
```
471477

472478
## Cyclic class definitions

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ in the function.
202202

203203
```py
204204
def good_param[T: int](x: T) -> None:
205-
reveal_type(x) # revealed: T
205+
reveal_type(x) # revealed: T@good_param
206206
```
207207

208208
If the function is annotated as returning the typevar, this means that the upper bound is _not_
@@ -215,7 +215,7 @@ def good_return[T: int](x: T) -> T:
215215
return x
216216

217217
def bad_return[T: int](x: T) -> T:
218-
# error: [invalid-return-type] "Return type does not match returned value: expected `T`, found `int`"
218+
# error: [invalid-return-type] "Return type does not match returned value: expected `T@bad_return`, found `int`"
219219
return x + 1
220220
```
221221

@@ -228,7 +228,7 @@ def different_types[T, S](cond: bool, t: T, s: S) -> T:
228228
if cond:
229229
return t
230230
else:
231-
# error: [invalid-return-type] "Return type does not match returned value: expected `T`, found `S`"
231+
# error: [invalid-return-type] "Return type does not match returned value: expected `T@different_types`, found `S@different_types`"
232232
return s
233233

234234
def same_types[T](cond: bool, t1: T, t2: T) -> T:
@@ -246,7 +246,7 @@ methods that are compatible with the return type, so the `return` expression is
246246
```py
247247
def same_constrained_types[T: (int, str)](t1: T, t2: T) -> T:
248248
# TODO: no error
249-
# error: [unsupported-operator] "Operator `+` is unsupported between objects of type `T` and `T`"
249+
# error: [unsupported-operator] "Operator `+` is unsupported between objects of type `T@same_constrained_types` and `T@same_constrained_types`"
250250
return t1 + t2
251251
```
252252

0 commit comments

Comments
 (0)