[ty] Track different uses of legacy typevars, including context when rendering typevars (#19604)
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks-instrumented (push) Blocked by required conditions
CI / benchmarks-walltime (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run

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`)
This commit is contained in:
Douglas Creager 2025-08-01 12:20:32 -04:00 committed by GitHub
parent 48d5bd13fa
commit 06cd249a9b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 394 additions and 128 deletions

View file

@ -1247,7 +1247,7 @@ quux.<CURSOR>
__init_subclass__ :: bound method object.__init_subclass__() -> None __init_subclass__ :: bound method object.__init_subclass__() -> None
__module__ :: str __module__ :: str
__ne__ :: bound method object.__ne__(value: object, /) -> bool __ne__ :: bound method object.__ne__(value: object, /) -> bool
__new__ :: bound method object.__new__() -> Self __new__ :: bound method object.__new__() -> Self@object
__reduce__ :: bound method object.__reduce__() -> str | tuple[Any, ...] __reduce__ :: bound method object.__reduce__() -> str | tuple[Any, ...]
__reduce_ex__ :: bound method object.__reduce_ex__(protocol: SupportsIndex, /) -> str | tuple[Any, ...] __reduce_ex__ :: bound method object.__reduce_ex__(protocol: SupportsIndex, /) -> str | tuple[Any, ...]
__repr__ :: bound method object.__repr__() -> str __repr__ :: bound method object.__repr__() -> str
@ -1292,7 +1292,7 @@ quux.b<CURSOR>
__init_subclass__ :: bound method object.__init_subclass__() -> None __init_subclass__ :: bound method object.__init_subclass__() -> None
__module__ :: str __module__ :: str
__ne__ :: bound method object.__ne__(value: object, /) -> bool __ne__ :: bound method object.__ne__(value: object, /) -> bool
__new__ :: bound method object.__new__() -> Self __new__ :: bound method object.__new__() -> Self@object
__reduce__ :: bound method object.__reduce__() -> str | tuple[Any, ...] __reduce__ :: bound method object.__reduce__() -> str | tuple[Any, ...]
__reduce_ex__ :: bound method object.__reduce_ex__(protocol: SupportsIndex, /) -> str | tuple[Any, ...] __reduce_ex__ :: bound method object.__reduce_ex__(protocol: SupportsIndex, /) -> str | tuple[Any, ...]
__repr__ :: bound method object.__repr__() -> str __repr__ :: bound method object.__repr__() -> str
@ -1346,7 +1346,7 @@ C.<CURSOR>
__mro__ :: tuple[<class 'C'>, <class 'object'>] __mro__ :: tuple[<class 'C'>, <class 'object'>]
__name__ :: str __name__ :: str
__ne__ :: def __ne__(self, value: object, /) -> bool __ne__ :: def __ne__(self, value: object, /) -> bool
__new__ :: def __new__(cls) -> Self __new__ :: def __new__(cls) -> Self@object
__or__ :: bound method <class 'C'>.__or__(value: Any, /) -> UnionType __or__ :: bound method <class 'C'>.__or__(value: Any, /) -> UnionType
__prepare__ :: bound method <class 'Meta'>.__prepare__(name: str, bases: tuple[type, ...], /, **kwds: Any) -> MutableMapping[str, object] __prepare__ :: bound method <class 'Meta'>.__prepare__(name: str, bases: tuple[type, ...], /, **kwds: Any) -> MutableMapping[str, object]
__qualname__ :: str __qualname__ :: str
@ -1522,7 +1522,7 @@ Quux.<CURSOR>
__mro__ :: tuple[<class 'Quux'>, <class 'object'>] __mro__ :: tuple[<class 'Quux'>, <class 'object'>]
__name__ :: str __name__ :: str
__ne__ :: def __ne__(self, value: object, /) -> bool __ne__ :: def __ne__(self, value: object, /) -> bool
__new__ :: def __new__(cls) -> Self __new__ :: def __new__(cls) -> Self@object
__or__ :: bound method <class 'Quux'>.__or__(value: Any, /) -> UnionType __or__ :: bound method <class 'Quux'>.__or__(value: Any, /) -> UnionType
__prepare__ :: bound method <class 'type'>.__prepare__(name: str, bases: tuple[type, ...], /, **kwds: Any) -> MutableMapping[str, object] __prepare__ :: bound method <class 'type'>.__prepare__(name: str, bases: tuple[type, ...], /, **kwds: Any) -> MutableMapping[str, object]
__qualname__ :: str __qualname__ :: str
@ -1574,8 +1574,8 @@ Answer.<CURSOR>
__bool__ :: bound method <class 'Answer'>.__bool__() -> Literal[True] __bool__ :: bound method <class 'Answer'>.__bool__() -> Literal[True]
__class__ :: <class 'EnumMeta'> __class__ :: <class 'EnumMeta'>
__contains__ :: bound method <class 'Answer'>.__contains__(value: object) -> bool __contains__ :: bound method <class 'Answer'>.__contains__(value: object) -> bool
__copy__ :: def __copy__(self) -> Self __copy__ :: def __copy__(self) -> Self@Enum
__deepcopy__ :: def __deepcopy__(self, memo: Any) -> Self __deepcopy__ :: def __deepcopy__(self, memo: Any) -> Self@Enum
__delattr__ :: def __delattr__(self, name: str, /) -> None __delattr__ :: def __delattr__(self, name: str, /) -> None
__dict__ :: MappingProxyType[str, Any] __dict__ :: MappingProxyType[str, Any]
__dictoffset__ :: int __dictoffset__ :: int
@ -1585,28 +1585,28 @@ Answer.<CURSOR>
__flags__ :: int __flags__ :: int
__format__ :: def __format__(self, format_spec: str) -> str __format__ :: def __format__(self, format_spec: str) -> str
__getattribute__ :: def __getattribute__(self, name: str, /) -> Any __getattribute__ :: def __getattribute__(self, name: str, /) -> Any
__getitem__ :: bound method <class 'Answer'>.__getitem__(name: str) -> _EnumMemberT __getitem__ :: bound method <class 'Answer'>.__getitem__(name: str) -> _EnumMemberT@__getitem__
__getstate__ :: def __getstate__(self) -> object __getstate__ :: def __getstate__(self) -> object
__hash__ :: def __hash__(self) -> int __hash__ :: def __hash__(self) -> int
__init__ :: def __init__(self) -> None __init__ :: def __init__(self) -> None
__init_subclass__ :: def __init_subclass__(cls) -> None __init_subclass__ :: def __init_subclass__(cls) -> None
__instancecheck__ :: bound method <class 'Answer'>.__instancecheck__(instance: Any, /) -> bool __instancecheck__ :: bound method <class 'Answer'>.__instancecheck__(instance: Any, /) -> bool
__itemsize__ :: int __itemsize__ :: int
__iter__ :: bound method <class 'Answer'>.__iter__() -> Iterator[_EnumMemberT] __iter__ :: bound method <class 'Answer'>.__iter__() -> Iterator[_EnumMemberT@__iter__]
__len__ :: bound method <class 'Answer'>.__len__() -> int __len__ :: bound method <class 'Answer'>.__len__() -> int
__members__ :: MappingProxyType[str, Unknown] __members__ :: MappingProxyType[str, Unknown]
__module__ :: str __module__ :: str
__mro__ :: tuple[<class 'Answer'>, <class 'Enum'>, <class 'object'>] __mro__ :: tuple[<class 'Answer'>, <class 'Enum'>, <class 'object'>]
__name__ :: str __name__ :: str
__ne__ :: def __ne__(self, value: object, /) -> bool __ne__ :: def __ne__(self, value: object, /) -> bool
__new__ :: def __new__(cls, value: object) -> Self __new__ :: def __new__(cls, value: object) -> Self@Enum
__or__ :: bound method <class 'Answer'>.__or__(value: Any, /) -> UnionType __or__ :: bound method <class 'Answer'>.__or__(value: Any, /) -> UnionType
__order__ :: str __order__ :: str
__prepare__ :: bound method <class 'EnumMeta'>.__prepare__(cls: str, bases: tuple[type, ...], **kwds: Any) -> _EnumDict __prepare__ :: bound method <class 'EnumMeta'>.__prepare__(cls: str, bases: tuple[type, ...], **kwds: Any) -> _EnumDict
__qualname__ :: str __qualname__ :: str
__reduce__ :: def __reduce__(self) -> str | tuple[Any, ...] __reduce__ :: def __reduce__(self) -> str | tuple[Any, ...]
__repr__ :: def __repr__(self) -> str __repr__ :: def __repr__(self) -> str
__reversed__ :: bound method <class 'Answer'>.__reversed__() -> Iterator[_EnumMemberT] __reversed__ :: bound method <class 'Answer'>.__reversed__() -> Iterator[_EnumMemberT@__reversed__]
__ror__ :: bound method <class 'Answer'>.__ror__(value: Any, /) -> UnionType __ror__ :: bound method <class 'Answer'>.__ror__(value: Any, /) -> UnionType
__setattr__ :: def __setattr__(self, name: str, value: Any, /) -> None __setattr__ :: def __setattr__(self, name: str, value: Any, /) -> None
__signature__ :: bound method <class 'Answer'>.__signature__() -> str __signature__ :: bound method <class 'Answer'>.__signature__() -> str

View file

@ -371,10 +371,10 @@ mod tests {
); );
assert_snapshot!(test.hover(), @r" assert_snapshot!(test.hover(), @r"
T T@Alias
--------------------------------------------- ---------------------------------------------
```python ```python
T T@Alias
``` ```
--------------------------------------------- ---------------------------------------------
info[hover]: Hovered content is info[hover]: Hovered content is

View file

@ -16,7 +16,7 @@ from typing import Self
class Shape: class Shape:
def set_scale(self: Self, scale: float) -> Self: def set_scale(self: Self, scale: float) -> Self:
reveal_type(self) # revealed: Self reveal_type(self) # revealed: Self@Shape
return self return self
def nested_type(self: Self) -> list[Self]: def nested_type(self: Self) -> list[Self]:
@ -24,7 +24,7 @@ class Shape:
def nested_func(self: Self) -> Self: def nested_func(self: Self) -> Self:
def inner() -> Self: def inner() -> Self:
reveal_type(self) # revealed: Self reveal_type(self) # revealed: Self@Shape
return self return self
return inner() return inner()
@ -38,13 +38,13 @@ reveal_type(Shape().nested_func()) # revealed: Shape
class Circle(Shape): class Circle(Shape):
def set_scale(self: Self, scale: float) -> Self: def set_scale(self: Self, scale: float) -> Self:
reveal_type(self) # revealed: Self reveal_type(self) # revealed: Self@Circle
return self return self
class Outer: class Outer:
class Inner: class Inner:
def foo(self: Self) -> Self: def foo(self: Self) -> Self:
reveal_type(self) # revealed: Self reveal_type(self) # revealed: Self@Inner
return self return self
``` ```
@ -151,7 +151,7 @@ from typing import Self
class Shape: class Shape:
def union(self: Self, other: Self | None): def union(self: Self, other: Self | None):
reveal_type(other) # revealed: Self | None reveal_type(other) # revealed: Self@Shape | None
return self return self
``` ```

View file

@ -26,7 +26,7 @@ def i(callback: Callable[Concatenate[int, P], R_co], *args: P.args, **kwargs: P.
class Foo: class Foo:
def method(self, x: Self): def method(self, x: Self):
reveal_type(x) # revealed: Self reveal_type(x) # revealed: Self@Foo
``` ```
## Type expressions ## Type expressions

View file

@ -15,8 +15,10 @@ S = TypeVar("S")
class SingleTypevar(Generic[T]): ... class SingleTypevar(Generic[T]): ...
class MultipleTypevars(Generic[T, S]): ... class MultipleTypevars(Generic[T, S]): ...
reveal_type(generic_context(SingleTypevar)) # revealed: tuple[T] # revealed: tuple[T@SingleTypevar]
reveal_type(generic_context(MultipleTypevars)) # revealed: tuple[T, S] reveal_type(generic_context(SingleTypevar))
# revealed: tuple[T@MultipleTypevars, S@MultipleTypevars]
reveal_type(generic_context(MultipleTypevars))
``` ```
Inheriting from `Generic` multiple times yields a `duplicate-base` diagnostic, just like any other Inheriting from `Generic` multiple times yields a `duplicate-base` diagnostic, just like any other
@ -49,9 +51,12 @@ class InheritedGeneric(MultipleTypevars[T, S]): ...
class InheritedGenericPartiallySpecialized(MultipleTypevars[T, int]): ... class InheritedGenericPartiallySpecialized(MultipleTypevars[T, int]): ...
class InheritedGenericFullySpecialized(MultipleTypevars[str, int]): ... class InheritedGenericFullySpecialized(MultipleTypevars[str, int]): ...
reveal_type(generic_context(InheritedGeneric)) # revealed: tuple[T, S] # revealed: tuple[T@InheritedGeneric, S@InheritedGeneric]
reveal_type(generic_context(InheritedGenericPartiallySpecialized)) # revealed: tuple[T] reveal_type(generic_context(InheritedGeneric))
reveal_type(generic_context(InheritedGenericFullySpecialized)) # revealed: None # revealed: tuple[T@InheritedGenericPartiallySpecialized]
reveal_type(generic_context(InheritedGenericPartiallySpecialized))
# revealed: None
reveal_type(generic_context(InheritedGenericFullySpecialized))
``` ```
If you don't specialize a generic base class, we use the default specialization, which maps each If you don't specialize a generic base class, we use the default specialization, which maps each
@ -78,9 +83,12 @@ class ExplicitInheritedGenericPartiallySpecializedExtraTypevar(MultipleTypevars[
# error: [invalid-generic-class] "`Generic` base class must include all type variables used in other base classes" # error: [invalid-generic-class] "`Generic` base class must include all type variables used in other base classes"
class ExplicitInheritedGenericPartiallySpecializedMissingTypevar(MultipleTypevars[T, int], Generic[S]): ... class ExplicitInheritedGenericPartiallySpecializedMissingTypevar(MultipleTypevars[T, int], Generic[S]): ...
reveal_type(generic_context(ExplicitInheritedGeneric)) # revealed: tuple[T, S] # revealed: tuple[T@ExplicitInheritedGeneric, S@ExplicitInheritedGeneric]
reveal_type(generic_context(ExplicitInheritedGenericPartiallySpecialized)) # revealed: tuple[T] reveal_type(generic_context(ExplicitInheritedGeneric))
reveal_type(generic_context(ExplicitInheritedGenericPartiallySpecializedExtraTypevar)) # revealed: tuple[T, S] # revealed: tuple[T@ExplicitInheritedGenericPartiallySpecialized]
reveal_type(generic_context(ExplicitInheritedGenericPartiallySpecialized))
# revealed: tuple[T@ExplicitInheritedGenericPartiallySpecializedExtraTypevar, S@ExplicitInheritedGenericPartiallySpecializedExtraTypevar]
reveal_type(generic_context(ExplicitInheritedGenericPartiallySpecializedExtraTypevar))
``` ```
## Specializing generic classes explicitly ## Specializing generic classes explicitly
@ -446,18 +454,18 @@ class C(Generic[T]):
def generic_method(self, t: T, u: U) -> U: def generic_method(self, t: T, u: U) -> U:
return u return u
reveal_type(generic_context(C)) # revealed: tuple[T] reveal_type(generic_context(C)) # revealed: tuple[T@C]
reveal_type(generic_context(C.method)) # revealed: None reveal_type(generic_context(C.method)) # revealed: None
reveal_type(generic_context(C.generic_method)) # revealed: tuple[U] reveal_type(generic_context(C.generic_method)) # revealed: tuple[U@generic_method]
reveal_type(generic_context(C[int])) # revealed: None reveal_type(generic_context(C[int])) # revealed: None
reveal_type(generic_context(C[int].method)) # revealed: None reveal_type(generic_context(C[int].method)) # revealed: None
reveal_type(generic_context(C[int].generic_method)) # revealed: tuple[U] reveal_type(generic_context(C[int].generic_method)) # revealed: tuple[U@generic_method]
c: C[int] = C[int]() c: C[int] = C[int]()
reveal_type(c.generic_method(1, "string")) # revealed: Literal["string"] reveal_type(c.generic_method(1, "string")) # revealed: Literal["string"]
reveal_type(generic_context(c)) # revealed: None reveal_type(generic_context(c)) # revealed: None
reveal_type(generic_context(c.method)) # revealed: None reveal_type(generic_context(c.method)) # revealed: None
reveal_type(generic_context(c.generic_method)) # revealed: tuple[U] reveal_type(generic_context(c.generic_method)) # revealed: tuple[U@generic_method]
``` ```
## Specializations propagate ## Specializations propagate
@ -540,7 +548,8 @@ class WithOverloadedMethod(Generic[T]):
def method(self, x: S | T) -> S | T: def method(self, x: S | T) -> S | T:
return x return x
reveal_type(WithOverloadedMethod[int].method) # revealed: Overload[(self, x: int) -> int, (self, x: S) -> S | int] # revealed: Overload[(self, x: int) -> int, (self, x: S@method) -> S@method | int]
reveal_type(WithOverloadedMethod[int].method)
``` ```
## Cyclic class definitions ## Cyclic class definitions

View file

@ -226,7 +226,7 @@ from typing import TypeVar
T = TypeVar("T", bound=int) T = TypeVar("T", bound=int)
def good_param(x: T) -> None: def good_param(x: T) -> None:
reveal_type(x) # revealed: T reveal_type(x) # revealed: T@good_param
``` ```
If the function is annotated as returning the typevar, this means that the upper bound is _not_ 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:
return x return x
def bad_return(x: T) -> T: def bad_return(x: T) -> T:
# error: [invalid-return-type] "Return type does not match returned value: expected `T`, found `int`" # error: [invalid-return-type] "Return type does not match returned value: expected `T@bad_return`, found `int`"
return x + 1 return x + 1
``` ```
@ -257,7 +257,7 @@ def different_types(cond: bool, t: T, s: S) -> T:
if cond: if cond:
return t return t
else: else:
# error: [invalid-return-type] "Return type does not match returned value: expected `T`, found `S`" # error: [invalid-return-type] "Return type does not match returned value: expected `T@different_types`, found `S@different_types`"
return s return s
def same_types(cond: bool, t1: T, t2: T) -> T: def same_types(cond: bool, t1: T, t2: T) -> T:
@ -279,7 +279,7 @@ T = TypeVar("T", int, str)
def same_constrained_types(t1: T, t2: T) -> T: def same_constrained_types(t1: T, t2: T) -> T:
# TODO: no error # TODO: no error
# error: [unsupported-operator] "Operator `+` is unsupported between objects of type `T` and `T`" # error: [unsupported-operator] "Operator `+` is unsupported between objects of type `T@same_constrained_types` and `T@same_constrained_types`"
return t1 + t2 return t1 + t2
``` ```

View file

@ -182,7 +182,7 @@ from typing import Callable, TypeVar
T = TypeVar("T", bound=Callable[[], int]) T = TypeVar("T", bound=Callable[[], int])
def bound(f: T): def bound(f: T):
reveal_type(f) # revealed: T reveal_type(f) # revealed: T@bound
reveal_type(f()) # revealed: int reveal_type(f()) # revealed: int
``` ```
@ -192,7 +192,7 @@ Same with a constrained typevar, as long as all constraints are callable:
T = TypeVar("T", Callable[[], int], Callable[[], str]) T = TypeVar("T", Callable[[], int], Callable[[], str])
def constrained(f: T): def constrained(f: T):
reveal_type(f) # revealed: T reveal_type(f) # revealed: T@constrained
reveal_type(f()) # revealed: int | str reveal_type(f()) # revealed: int | str
``` ```

View file

@ -16,8 +16,10 @@ from ty_extensions import generic_context
class SingleTypevar[T]: ... class SingleTypevar[T]: ...
class MultipleTypevars[T, S]: ... class MultipleTypevars[T, S]: ...
reveal_type(generic_context(SingleTypevar)) # revealed: tuple[T] # revealed: tuple[T@SingleTypevar]
reveal_type(generic_context(MultipleTypevars)) # revealed: tuple[T, S] reveal_type(generic_context(SingleTypevar))
# revealed: tuple[T@MultipleTypevars, S@MultipleTypevars]
reveal_type(generic_context(MultipleTypevars))
``` ```
You cannot use the same typevar more than once. You cannot use the same typevar more than once.
@ -43,9 +45,12 @@ class InheritedGeneric[U, V](MultipleTypevars[U, V]): ...
class InheritedGenericPartiallySpecialized[U](MultipleTypevars[U, int]): ... class InheritedGenericPartiallySpecialized[U](MultipleTypevars[U, int]): ...
class InheritedGenericFullySpecialized(MultipleTypevars[str, int]): ... class InheritedGenericFullySpecialized(MultipleTypevars[str, int]): ...
reveal_type(generic_context(InheritedGeneric)) # revealed: tuple[U, V] # revealed: tuple[U@InheritedGeneric, V@InheritedGeneric]
reveal_type(generic_context(InheritedGenericPartiallySpecialized)) # revealed: tuple[U] reveal_type(generic_context(InheritedGeneric))
reveal_type(generic_context(InheritedGenericFullySpecialized)) # revealed: None # revealed: tuple[U@InheritedGenericPartiallySpecialized]
reveal_type(generic_context(InheritedGenericPartiallySpecialized))
# revealed: None
reveal_type(generic_context(InheritedGenericFullySpecialized))
``` ```
If you don't specialize a generic base class, we use the default specialization, which maps each If you don't specialize a generic base class, we use the default specialization, which maps each
@ -406,18 +411,18 @@ class C[T]:
# TODO: error # TODO: error
def cannot_shadow_class_typevar[T](self, t: T): ... def cannot_shadow_class_typevar[T](self, t: T): ...
reveal_type(generic_context(C)) # revealed: tuple[T] reveal_type(generic_context(C)) # revealed: tuple[T@C]
reveal_type(generic_context(C.method)) # revealed: None reveal_type(generic_context(C.method)) # revealed: None
reveal_type(generic_context(C.generic_method)) # revealed: tuple[U] reveal_type(generic_context(C.generic_method)) # revealed: tuple[U@generic_method]
reveal_type(generic_context(C[int])) # revealed: None reveal_type(generic_context(C[int])) # revealed: None
reveal_type(generic_context(C[int].method)) # revealed: None reveal_type(generic_context(C[int].method)) # revealed: None
reveal_type(generic_context(C[int].generic_method)) # revealed: tuple[U] reveal_type(generic_context(C[int].generic_method)) # revealed: tuple[U@generic_method]
c: C[int] = C[int]() c: C[int] = C[int]()
reveal_type(c.generic_method(1, "string")) # revealed: Literal["string"] reveal_type(c.generic_method(1, "string")) # revealed: Literal["string"]
reveal_type(generic_context(c)) # revealed: None reveal_type(generic_context(c)) # revealed: None
reveal_type(generic_context(c.method)) # revealed: None reveal_type(generic_context(c.method)) # revealed: None
reveal_type(generic_context(c.generic_method)) # revealed: tuple[U] reveal_type(generic_context(c.generic_method)) # revealed: tuple[U@generic_method]
``` ```
## Specializations propagate ## Specializations propagate
@ -466,7 +471,8 @@ class WithOverloadedMethod[T]:
def method[S](self, x: S | T) -> S | T: def method[S](self, x: S | T) -> S | T:
return x return x
reveal_type(WithOverloadedMethod[int].method) # revealed: Overload[(self, x: int) -> int, (self, x: S) -> S | int] # revealed: Overload[(self, x: int) -> int, (self, x: S@method) -> S@method | int]
reveal_type(WithOverloadedMethod[int].method)
``` ```
## Cyclic class definitions ## Cyclic class definitions

View file

@ -202,7 +202,7 @@ in the function.
```py ```py
def good_param[T: int](x: T) -> None: def good_param[T: int](x: T) -> None:
reveal_type(x) # revealed: T reveal_type(x) # revealed: T@good_param
``` ```
If the function is annotated as returning the typevar, this means that the upper bound is _not_ 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:
return x return x
def bad_return[T: int](x: T) -> T: def bad_return[T: int](x: T) -> T:
# error: [invalid-return-type] "Return type does not match returned value: expected `T`, found `int`" # error: [invalid-return-type] "Return type does not match returned value: expected `T@bad_return`, found `int`"
return x + 1 return x + 1
``` ```
@ -228,7 +228,7 @@ def different_types[T, S](cond: bool, t: T, s: S) -> T:
if cond: if cond:
return t return t
else: else:
# error: [invalid-return-type] "Return type does not match returned value: expected `T`, found `S`" # error: [invalid-return-type] "Return type does not match returned value: expected `T@different_types`, found `S@different_types`"
return s return s
def same_types[T](cond: bool, t1: T, t2: T) -> T: 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
```py ```py
def same_constrained_types[T: (int, str)](t1: T, t2: T) -> T: def same_constrained_types[T: (int, str)](t1: T, t2: T) -> T:
# TODO: no error # TODO: no error
# error: [unsupported-operator] "Operator `+` is unsupported between objects of type `T` and `T`" # error: [unsupported-operator] "Operator `+` is unsupported between objects of type `T@same_constrained_types` and `T@same_constrained_types`"
return t1 + t2 return t1 + t2
``` ```

View file

@ -104,13 +104,11 @@ different uses of the same typevar.
```py ```py
def f[T](x: T, y: T) -> None: def f[T](x: T, y: T) -> None:
# TODO: revealed: T@f reveal_type(x) # revealed: T@f
reveal_type(x) # revealed: T
class C[T]: class C[T]:
def m(self, x: T) -> None: def m(self, x: T) -> None:
# TODO: revealed: T@c reveal_type(x) # revealed: T@C
reveal_type(x) # revealed: T
``` ```
## Subtyping and assignability ## Subtyping and assignability
@ -452,19 +450,19 @@ class Unrelated: ...
def unbounded_unconstrained[T](t: T) -> None: def unbounded_unconstrained[T](t: T) -> None:
def _(x: T | Super) -> None: def _(x: T | Super) -> None:
reveal_type(x) # revealed: T | Super reveal_type(x) # revealed: T@unbounded_unconstrained | Super
def _(x: T | Base) -> None: def _(x: T | Base) -> None:
reveal_type(x) # revealed: T | Base reveal_type(x) # revealed: T@unbounded_unconstrained | Base
def _(x: T | Sub) -> None: def _(x: T | Sub) -> None:
reveal_type(x) # revealed: T | Sub reveal_type(x) # revealed: T@unbounded_unconstrained | Sub
def _(x: T | Unrelated) -> None: def _(x: T | Unrelated) -> None:
reveal_type(x) # revealed: T | Unrelated reveal_type(x) # revealed: T@unbounded_unconstrained | Unrelated
def _(x: T | Any) -> None: def _(x: T | Any) -> None:
reveal_type(x) # revealed: T | Any reveal_type(x) # revealed: T@unbounded_unconstrained | Any
``` ```
The union of a bounded typevar with its bound is that bound. (The typevar is guaranteed to be The union of a bounded typevar with its bound is that bound. (The typevar is guaranteed to be
@ -480,13 +478,13 @@ def bounded[T: Base](t: T) -> None:
reveal_type(x) # revealed: Base reveal_type(x) # revealed: Base
def _(x: T | Sub) -> None: def _(x: T | Sub) -> None:
reveal_type(x) # revealed: T | Sub reveal_type(x) # revealed: T@bounded | Sub
def _(x: T | Unrelated) -> None: def _(x: T | Unrelated) -> None:
reveal_type(x) # revealed: T | Unrelated reveal_type(x) # revealed: T@bounded | Unrelated
def _(x: T | Any) -> None: def _(x: T | Any) -> None:
reveal_type(x) # revealed: T | Any reveal_type(x) # revealed: T@bounded | Any
``` ```
The union of a constrained typevar with a type depends on how that type relates to the constraints. The union of a constrained typevar with a type depends on how that type relates to the constraints.
@ -503,13 +501,13 @@ def constrained[T: (Base, Sub)](t: T) -> None:
reveal_type(x) # revealed: Base reveal_type(x) # revealed: Base
def _(x: T | Sub) -> None: def _(x: T | Sub) -> None:
reveal_type(x) # revealed: T reveal_type(x) # revealed: T@constrained
def _(x: T | Unrelated) -> None: def _(x: T | Unrelated) -> None:
reveal_type(x) # revealed: T | Unrelated reveal_type(x) # revealed: T@constrained | Unrelated
def _(x: T | Any) -> None: def _(x: T | Any) -> None:
reveal_type(x) # revealed: T | Any reveal_type(x) # revealed: T@constrained | Any
``` ```
## Intersections involving typevars ## Intersections involving typevars
@ -528,19 +526,19 @@ class Unrelated: ...
def unbounded_unconstrained[T](t: T) -> None: def unbounded_unconstrained[T](t: T) -> None:
def _(x: Intersection[T, Super]) -> None: def _(x: Intersection[T, Super]) -> None:
reveal_type(x) # revealed: T & Super reveal_type(x) # revealed: T@unbounded_unconstrained & Super
def _(x: Intersection[T, Base]) -> None: def _(x: Intersection[T, Base]) -> None:
reveal_type(x) # revealed: T & Base reveal_type(x) # revealed: T@unbounded_unconstrained & Base
def _(x: Intersection[T, Sub]) -> None: def _(x: Intersection[T, Sub]) -> None:
reveal_type(x) # revealed: T & Sub reveal_type(x) # revealed: T@unbounded_unconstrained & Sub
def _(x: Intersection[T, Unrelated]) -> None: def _(x: Intersection[T, Unrelated]) -> None:
reveal_type(x) # revealed: T & Unrelated reveal_type(x) # revealed: T@unbounded_unconstrained & Unrelated
def _(x: Intersection[T, Any]) -> None: def _(x: Intersection[T, Any]) -> None:
reveal_type(x) # revealed: T & Any reveal_type(x) # revealed: T@unbounded_unconstrained & Any
``` ```
The intersection of a bounded typevar with its bound or a supertype of its bound is the typevar The intersection of a bounded typevar with its bound or a supertype of its bound is the typevar
@ -552,19 +550,19 @@ from its bound is `Never`.
```py ```py
def bounded[T: Base](t: T) -> None: def bounded[T: Base](t: T) -> None:
def _(x: Intersection[T, Super]) -> None: def _(x: Intersection[T, Super]) -> None:
reveal_type(x) # revealed: T reveal_type(x) # revealed: T@bounded
def _(x: Intersection[T, Base]) -> None: def _(x: Intersection[T, Base]) -> None:
reveal_type(x) # revealed: T reveal_type(x) # revealed: T@bounded
def _(x: Intersection[T, Sub]) -> None: def _(x: Intersection[T, Sub]) -> None:
reveal_type(x) # revealed: T & Sub reveal_type(x) # revealed: T@bounded & Sub
def _(x: Intersection[T, None]) -> None: def _(x: Intersection[T, None]) -> None:
reveal_type(x) # revealed: Never reveal_type(x) # revealed: Never
def _(x: Intersection[T, Any]) -> None: def _(x: Intersection[T, Any]) -> None:
reveal_type(x) # revealed: T & Any reveal_type(x) # revealed: T@bounded & Any
``` ```
Constrained typevars can be modeled using a hypothetical `OneOf` connector, where the typevar must Constrained typevars can be modeled using a hypothetical `OneOf` connector, where the typevar must
@ -586,7 +584,7 @@ can simplify the intersection as a whole to that constraint.
def constrained[T: (Base, Sub, Unrelated)](t: T) -> None: def constrained[T: (Base, Sub, Unrelated)](t: T) -> None:
def _(x: Intersection[T, Base]) -> None: def _(x: Intersection[T, Base]) -> None:
# With OneOf this would be OneOf[Base, Sub] # With OneOf this would be OneOf[Base, Sub]
reveal_type(x) # revealed: T & Base reveal_type(x) # revealed: T@constrained & Base
def _(x: Intersection[T, Unrelated]) -> None: def _(x: Intersection[T, Unrelated]) -> None:
reveal_type(x) # revealed: Unrelated reveal_type(x) # revealed: Unrelated
@ -598,7 +596,7 @@ def constrained[T: (Base, Sub, Unrelated)](t: T) -> None:
reveal_type(x) # revealed: Never reveal_type(x) # revealed: Never
def _(x: Intersection[T, Any]) -> None: def _(x: Intersection[T, Any]) -> None:
reveal_type(x) # revealed: T & Any reveal_type(x) # revealed: T@constrained & Any
``` ```
We can simplify the intersection similarly when removing a type from a constrained typevar, since We can simplify the intersection similarly when removing a type from a constrained typevar, since
@ -613,19 +611,19 @@ def remove_constraint[T: (int, str, bool)](t: T) -> None:
def _(x: Intersection[T, Not[str]]) -> None: def _(x: Intersection[T, Not[str]]) -> None:
# With OneOf this would be OneOf[int, bool] # With OneOf this would be OneOf[int, bool]
reveal_type(x) # revealed: T & ~str reveal_type(x) # revealed: T@remove_constraint & ~str
def _(x: Intersection[T, Not[bool]]) -> None: def _(x: Intersection[T, Not[bool]]) -> None:
reveal_type(x) # revealed: T & ~bool reveal_type(x) # revealed: T@remove_constraint & ~bool
def _(x: Intersection[T, Not[int], Not[str]]) -> None: def _(x: Intersection[T, Not[int], Not[str]]) -> None:
reveal_type(x) # revealed: Never reveal_type(x) # revealed: Never
def _(x: Intersection[T, Not[None]]) -> None: def _(x: Intersection[T, Not[None]]) -> None:
reveal_type(x) # revealed: T reveal_type(x) # revealed: T@remove_constraint
def _(x: Intersection[T, Not[Any]]) -> None: def _(x: Intersection[T, Not[Any]]) -> None:
reveal_type(x) # revealed: T & Any reveal_type(x) # revealed: T@remove_constraint & Any
``` ```
The intersection of a typevar with any other type is assignable to (and if fully static, a subtype The intersection of a typevar with any other type is assignable to (and if fully static, a subtype
@ -710,7 +708,7 @@ A typevar bound to a Callable type is callable:
from typing import Callable from typing import Callable
def bound[T: Callable[[], int]](f: T): def bound[T: Callable[[], int]](f: T):
reveal_type(f) # revealed: T reveal_type(f) # revealed: T@bound
reveal_type(f()) # revealed: int reveal_type(f()) # revealed: int
``` ```
@ -718,7 +716,7 @@ Same with a constrained typevar, as long as all constraints are callable:
```py ```py
def constrained[T: (Callable[[], int], Callable[[], str])](f: T): def constrained[T: (Callable[[], int], Callable[[], str])](f: T):
reveal_type(f) # revealed: T reveal_type(f) # revealed: T@constrained
reveal_type(f()) # revealed: int | str reveal_type(f()) # revealed: int | str
``` ```

View file

@ -152,9 +152,9 @@ already solved and specialized when the class was specialized:
from ty_extensions import generic_context from ty_extensions import generic_context
legacy.m("string", None) # error: [invalid-argument-type] legacy.m("string", None) # error: [invalid-argument-type]
reveal_type(legacy.m) # revealed: bound method Legacy[int].m(x: int, y: S) -> S reveal_type(legacy.m) # revealed: bound method Legacy[int].m(x: int, y: S@m) -> S@m
reveal_type(generic_context(Legacy)) # revealed: tuple[T] reveal_type(generic_context(Legacy)) # revealed: tuple[T@Legacy]
reveal_type(generic_context(legacy.m)) # revealed: tuple[S] reveal_type(generic_context(legacy.m)) # revealed: tuple[S@m]
``` ```
With PEP 695 syntax, it is clearer that the method uses a separate typevar: With PEP 695 syntax, it is clearer that the method uses a separate typevar:

View file

@ -536,19 +536,19 @@ T = TypeVar("T")
class peekable(Generic[T], Iterator[T]): ... class peekable(Generic[T], Iterator[T]): ...
# revealed: tuple[<class 'peekable[Unknown]'>, <class 'Iterator[T]'>, <class 'Iterable[T]'>, typing.Protocol, typing.Generic, <class 'object'>] # revealed: tuple[<class 'peekable[Unknown]'>, <class 'Iterator[T@peekable]'>, <class 'Iterable[T@peekable]'>, typing.Protocol, typing.Generic, <class 'object'>]
reveal_type(peekable.__mro__) reveal_type(peekable.__mro__)
class peekable2(Iterator[T], Generic[T]): ... class peekable2(Iterator[T], Generic[T]): ...
# revealed: tuple[<class 'peekable2[Unknown]'>, <class 'Iterator[T]'>, <class 'Iterable[T]'>, typing.Protocol, typing.Generic, <class 'object'>] # revealed: tuple[<class 'peekable2[Unknown]'>, <class 'Iterator[T@peekable2]'>, <class 'Iterable[T@peekable2]'>, typing.Protocol, typing.Generic, <class 'object'>]
reveal_type(peekable2.__mro__) reveal_type(peekable2.__mro__)
class Base: ... class Base: ...
class Intermediate(Base, Generic[T]): ... class Intermediate(Base, Generic[T]): ...
class Sub(Intermediate[T], Base): ... class Sub(Intermediate[T], Base): ...
# revealed: tuple[<class 'Sub[Unknown]'>, <class 'Intermediate[T]'>, <class 'Base'>, typing.Generic, <class 'object'>] # revealed: tuple[<class 'Sub[Unknown]'>, <class 'Intermediate[T@Sub]'>, <class 'Base'>, typing.Generic, <class 'object'>]
reveal_type(Sub.__mro__) reveal_type(Sub.__mro__)
``` ```

View file

@ -150,9 +150,9 @@ class Person(NamedTuple):
reveal_type(Person._field_defaults) # revealed: dict[str, Any] reveal_type(Person._field_defaults) # revealed: dict[str, Any]
reveal_type(Person._fields) # revealed: tuple[str, ...] reveal_type(Person._fields) # revealed: tuple[str, ...]
reveal_type(Person._make) # revealed: bound method <class 'Person'>._make(iterable: Iterable[Any]) -> Self reveal_type(Person._make) # revealed: bound method <class 'Person'>._make(iterable: Iterable[Any]) -> Self@NamedTupleFallback
reveal_type(Person._asdict) # revealed: def _asdict(self) -> dict[str, Any] reveal_type(Person._asdict) # revealed: def _asdict(self) -> dict[str, Any]
reveal_type(Person._replace) # revealed: def _replace(self, **kwargs: Any) -> Self reveal_type(Person._replace) # revealed: def _replace(self, **kwargs: Any) -> Self@NamedTupleFallback
# TODO: should be `Person` once we support `Self` # TODO: should be `Person` once we support `Self`
reveal_type(Person._make(("Alice", 42))) # revealed: Unknown reveal_type(Person._make(("Alice", 42))) # revealed: Unknown

View file

@ -136,19 +136,19 @@ def f(a: int | None):
reveal_type(c.y) # revealed: int | None reveal_type(c.y) # revealed: int | None
def g[T](c: C[T]): def g[T](c: C[T]):
reveal_type(c.x) # revealed: T reveal_type(c.x) # revealed: T@g
reveal_type(c.y) # revealed: T reveal_type(c.y) # revealed: T@g
reveal_type(c) # revealed: C[T] reveal_type(c) # revealed: C[T@g]
if isinstance(c.x, int): if isinstance(c.x, int):
reveal_type(c.x) # revealed: T & int reveal_type(c.x) # revealed: T@g & int
reveal_type(c.y) # revealed: T reveal_type(c.y) # revealed: T@g
reveal_type(c) # revealed: C[T] reveal_type(c) # revealed: C[T@g]
if isinstance(c.x, int) and isinstance(c.y, int): if isinstance(c.x, int) and isinstance(c.y, int):
reveal_type(c.x) # revealed: T & int reveal_type(c.x) # revealed: T@g & int
reveal_type(c.y) # revealed: T & int reveal_type(c.y) # revealed: T@g & int
# TODO: Probably better if inferred as `C[T & int]` (mypy and pyright don't support this) # TODO: Probably better if inferred as `C[T & int]` (mypy and pyright don't support this)
reveal_type(c) # revealed: C[T] reveal_type(c) # revealed: C[T@g]
``` ```
### With intermediate scopes ### With intermediate scopes

View file

@ -299,7 +299,7 @@ def func[T](x: T) -> T: ...
def func[T](x: T | None = None) -> T | None: def func[T](x: T | None = None) -> T | None:
return x return x
reveal_type(func) # revealed: Overload[() -> None, (x: T) -> T] reveal_type(func) # revealed: Overload[() -> None, (x: T@func) -> T@func]
reveal_type(func()) # revealed: None reveal_type(func()) # revealed: None
reveal_type(func(1)) # revealed: Literal[1] reveal_type(func(1)) # revealed: Literal[1]
reveal_type(func("")) # revealed: Literal[""] reveal_type(func("")) # revealed: Literal[""]

View file

@ -393,7 +393,7 @@ from typing import SupportsIndex, SupportsAbs
reveal_protocol_interface(Foo) reveal_protocol_interface(Foo)
# error: [revealed-type] "Revealed protocol interface: `{"__index__": MethodMember(`(self) -> int`)}`" # error: [revealed-type] "Revealed protocol interface: `{"__index__": MethodMember(`(self) -> int`)}`"
reveal_protocol_interface(SupportsIndex) reveal_protocol_interface(SupportsIndex)
# error: [revealed-type] "Revealed protocol interface: `{"__abs__": MethodMember(`(self) -> _T_co`)}`" # error: [revealed-type] "Revealed protocol interface: `{"__abs__": MethodMember(`(self) -> _T_co@SupportsAbs`)}`"
reveal_protocol_interface(SupportsAbs) reveal_protocol_interface(SupportsAbs)
# error: [invalid-argument-type] "Invalid argument to `reveal_protocol_interface`: Only protocol classes can be passed to `reveal_protocol_interface`" # error: [invalid-argument-type] "Invalid argument to `reveal_protocol_interface`: Only protocol classes can be passed to `reveal_protocol_interface`"

View file

@ -11,7 +11,7 @@ def _(flag: bool) -> None:
abs = 1 abs = 1
chr: int = 1 chr: int = 1
reveal_type(abs) # revealed: Literal[1] | (def abs(x: SupportsAbs[_T], /) -> _T) reveal_type(abs) # revealed: Literal[1] | (def abs(x: SupportsAbs[_T@abs], /) -> _T@abs)
reveal_type(chr) # revealed: Literal[1] | (def chr(i: SupportsIndex, /) -> str) reveal_type(chr) # revealed: Literal[1] | (def chr(i: SupportsIndex, /) -> str)
``` ```

View file

@ -24,7 +24,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/mro.md
# Diagnostics # Diagnostics
``` ```
error[inconsistent-mro]: Cannot create a consistent method resolution order (MRO) for class `Baz` with bases list `[typing.Protocol[T], <class 'Foo'>, <class 'Bar[T]'>]` error[inconsistent-mro]: Cannot create a consistent method resolution order (MRO) for class `Baz` with bases list `[typing.Protocol[T], <class 'Foo'>, <class 'Bar[T@Baz]'>]`
--> src/mdtest_snippet.py:7:1 --> src/mdtest_snippet.py:7:1
| |
5 | class Foo(Protocol): ... 5 | class Foo(Protocol): ...

View file

@ -85,7 +85,7 @@ info: rule `invalid-return-type` is enabled by default
``` ```
``` ```
error[invalid-return-type]: Function always implicitly returns `None`, which is not assignable to return type `T` error[invalid-return-type]: Function always implicitly returns `None`, which is not assignable to return type `T@m`
--> src/mdtest_snippet.py:18:16 --> src/mdtest_snippet.py:18:16
| |
17 | # error: [invalid-return-type] 17 | # error: [invalid-return-type]

View file

@ -86,7 +86,7 @@ error[call-non-callable]: Object of type `Literal[5]` is not callable
| ^^^^ | ^^^^
| |
info: Union variant `Literal[5]` is incompatible with this call site info: Union variant `Literal[5]` is incompatible with this call site
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable` info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T@f4) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable`
info: rule `call-non-callable` is enabled by default info: rule `call-non-callable` is enabled by default
``` ```
@ -101,7 +101,7 @@ error[call-non-callable]: Object of type `PossiblyNotCallable` is not callable (
| ^^^^ | ^^^^
| |
info: Union variant `PossiblyNotCallable` is incompatible with this call site info: Union variant `PossiblyNotCallable` is incompatible with this call site
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable` info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T@f4) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable`
info: rule `call-non-callable` is enabled by default info: rule `call-non-callable` is enabled by default
``` ```
@ -116,7 +116,7 @@ error[missing-argument]: No argument provided for required parameter `b` of func
| ^^^^ | ^^^^
| |
info: Union variant `def f3(a: int, b: int) -> int` is incompatible with this call site info: Union variant `def f3(a: int, b: int) -> int` is incompatible with this call site
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable` info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T@f4) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable`
info: rule `missing-argument` is enabled by default info: rule `missing-argument` is enabled by default
``` ```
@ -152,7 +152,7 @@ info: Overload implementation defined here
28 | return x + y if x and y else None 28 | return x + y if x and y else None
| |
info: Union variant `Overload[() -> None, (x: str, y: str) -> str]` is incompatible with this call site info: Union variant `Overload[() -> None, (x: str, y: str) -> str]` is incompatible with this call site
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable` info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T@f4) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable`
info: rule `no-matching-overload` is enabled by default info: rule `no-matching-overload` is enabled by default
``` ```
@ -176,7 +176,7 @@ info: Function defined here
8 | return 0 8 | return 0
| |
info: Union variant `def f2(name: str) -> int` is incompatible with this call site info: Union variant `def f2(name: str) -> int` is incompatible with this call site
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable` info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T@f4) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable`
info: rule `invalid-argument-type` is enabled by default info: rule `invalid-argument-type` is enabled by default
``` ```
@ -199,8 +199,8 @@ info: Type variable defined here
| ^^^^^^ | ^^^^^^
14 | return 0 14 | return 0
| |
info: Union variant `def f4(x: T) -> int` is incompatible with this call site info: Union variant `def f4(x: T@f4) -> int` is incompatible with this call site
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable` info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T@f4) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable`
info: rule `invalid-argument-type` is enabled by default info: rule `invalid-argument-type` is enabled by default
``` ```
@ -227,7 +227,7 @@ info: Matching overload defined here
info: Non-matching overloads for function `f5`: info: Non-matching overloads for function `f5`:
info: () -> None info: () -> None
info: Union variant `Overload[() -> None, (x: str) -> str]` is incompatible with this call site info: Union variant `Overload[() -> None, (x: str) -> str]` is incompatible with this call site
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable` info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T@f4) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable`
info: rule `invalid-argument-type` is enabled by default info: rule `invalid-argument-type` is enabled by default
``` ```
@ -242,7 +242,7 @@ error[too-many-positional-arguments]: Too many positional arguments to function
| ^ | ^
| |
info: Union variant `def f1() -> int` is incompatible with this call site info: Union variant `def f1() -> int` is incompatible with this call site
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable` info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T@f4) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable`
info: rule `too-many-positional-arguments` is enabled by default info: rule `too-many-positional-arguments` is enabled by default
``` ```

View file

@ -60,6 +60,35 @@ impl<'db> Definition<'db> {
FileRange::new(self.file(db), self.kind(db).target_range(module)) FileRange::new(self.file(db), self.kind(db).target_range(module))
} }
/// Returns the name of the item being defined, if applicable.
pub fn name(self, db: &'db dyn Db) -> Option<String> {
let file = self.file(db);
let module = parsed_module(db, file).load(db);
let kind = self.kind(db);
match kind {
DefinitionKind::Function(def) => {
let node = def.node(&module);
Some(node.name.as_str().to_string())
}
DefinitionKind::Class(def) => {
let node = def.node(&module);
Some(node.name.as_str().to_string())
}
DefinitionKind::TypeAlias(def) => {
let node = def.node(&module);
Some(
node.name
.as_name_expr()
.expect("type alias name should be a NameExpr")
.id
.as_str()
.to_string(),
)
}
_ => None,
}
}
/// Extract a docstring from this definition, if applicable. /// Extract a docstring from this definition, if applicable.
/// This method returns a docstring for function and class definitions. /// This method returns a docstring for function and class definitions.
/// The docstring is extracted from the first statement in the body if it's a string literal. /// The docstring is extracted from the first statement in the body if it's a string literal.

View file

@ -701,6 +701,7 @@ impl<'db> Type<'db> {
Name::new_static("T_all"), Name::new_static("T_all"),
None, None,
None, None,
None,
variance, variance,
None, None,
TypeVarKind::Pep695, TypeVarKind::Pep695,
@ -5128,6 +5129,7 @@ impl<'db> Type<'db> {
db, db,
Name::new(format!("{}'instance", typevar.name(db))), Name::new(format!("{}'instance", typevar.name(db))),
None, None,
None,
Some(bound_or_constraints), Some(bound_or_constraints),
typevar.variance(db), typevar.variance(db),
None, None,
@ -5295,10 +5297,12 @@ impl<'db> Type<'db> {
let instance = Type::ClassLiteral(class).to_instance(db).expect( let instance = Type::ClassLiteral(class).to_instance(db).expect(
"nearest_enclosing_class must return type that can be instantiated", "nearest_enclosing_class must return type that can be instantiated",
); );
let class_definition = class.definition(db);
Ok(Type::TypeVar(TypeVarInstance::new( Ok(Type::TypeVar(TypeVarInstance::new(
db, db,
ast::name::Name::new_static("Self"), ast::name::Name::new_static("Self"),
Some(class.definition(db)), Some(class_definition),
Some(class_definition),
Some(TypeVarBoundOrConstraints::UpperBound(instance)), Some(TypeVarBoundOrConstraints::UpperBound(instance)),
TypeVarVariance::Invariant, TypeVarVariance::Invariant,
None, None,
@ -5573,6 +5577,9 @@ impl<'db> Type<'db> {
partial.get(db, typevar).unwrap_or(self) partial.get(db, typevar).unwrap_or(self)
} }
TypeMapping::PromoteLiterals => self, TypeMapping::PromoteLiterals => self,
TypeMapping::BindLegacyTypevars(binding_context) => {
Type::TypeVar(typevar.with_binding_context(db, *binding_context))
}
} }
Type::FunctionLiteral(function) => { Type::FunctionLiteral(function) => {
@ -5660,7 +5667,8 @@ impl<'db> Type<'db> {
| Type::BytesLiteral(_) | Type::BytesLiteral(_)
| Type::EnumLiteral(_) => match type_mapping { | Type::EnumLiteral(_) => match type_mapping {
TypeMapping::Specialization(_) | TypeMapping::Specialization(_) |
TypeMapping::PartialSpecialization(_) => self, TypeMapping::PartialSpecialization(_) |
TypeMapping::BindLegacyTypevars(_) => self,
TypeMapping::PromoteLiterals => self.literal_fallback_instance(db) TypeMapping::PromoteLiterals => self.literal_fallback_instance(db)
.expect("literal type should have fallback instance type"), .expect("literal type should have fallback instance type"),
} }
@ -6009,6 +6017,9 @@ pub enum TypeMapping<'a, 'db> {
/// Promotes any literal types to their corresponding instance types (e.g. `Literal["string"]` /// Promotes any literal types to their corresponding instance types (e.g. `Literal["string"]`
/// to `str`) /// to `str`)
PromoteLiterals, PromoteLiterals,
/// Binds a legacy typevar with the generic context (class, function, type alias) that it is
/// being used in.
BindLegacyTypevars(Definition<'db>),
} }
fn walk_type_mapping<'db, V: visitor::TypeVisitor<'db> + ?Sized>( fn walk_type_mapping<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
@ -6023,7 +6034,7 @@ fn walk_type_mapping<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
TypeMapping::PartialSpecialization(specialization) => { TypeMapping::PartialSpecialization(specialization) => {
walk_partial_specialization(db, specialization, visitor); walk_partial_specialization(db, specialization, visitor);
} }
TypeMapping::PromoteLiterals => {} TypeMapping::PromoteLiterals | TypeMapping::BindLegacyTypevars(_) => {}
} }
} }
@ -6037,6 +6048,9 @@ impl<'db> TypeMapping<'_, 'db> {
TypeMapping::PartialSpecialization(partial.to_owned()) TypeMapping::PartialSpecialization(partial.to_owned())
} }
TypeMapping::PromoteLiterals => TypeMapping::PromoteLiterals, TypeMapping::PromoteLiterals => TypeMapping::PromoteLiterals,
TypeMapping::BindLegacyTypevars(binding_context) => {
TypeMapping::BindLegacyTypevars(*binding_context)
}
} }
} }
@ -6049,6 +6063,9 @@ impl<'db> TypeMapping<'_, 'db> {
TypeMapping::PartialSpecialization(partial.normalized_impl(db, visitor)) TypeMapping::PartialSpecialization(partial.normalized_impl(db, visitor))
} }
TypeMapping::PromoteLiterals => TypeMapping::PromoteLiterals, TypeMapping::PromoteLiterals => TypeMapping::PromoteLiterals,
TypeMapping::BindLegacyTypevars(binding_context) => {
TypeMapping::BindLegacyTypevars(*binding_context)
}
} }
} }
} }
@ -6582,6 +6599,35 @@ pub struct TypeVarInstance<'db> {
/// The type var's definition (None if synthesized) /// The type var's definition (None if synthesized)
pub definition: Option<Definition<'db>>, pub definition: Option<Definition<'db>>,
/// The definition of the generic class, function, or type alias that binds this typevar. This
/// is `None` for a legacy typevar outside of a context that can bind it.
///
/// For a legacy typevar, the binding context might be missing:
///
/// ```py
/// T = TypeVar("T") # [1]
/// def generic_function(t: T) -> T: ... # [2]
/// ```
///
/// Here, we will create two `TypeVarInstance`s for the typevar `T`. Both will have `[1]` as
/// their [`definition`][Self::definition]. The first represents the variable when it is first
/// created, and not yet used, so it's `binding_context` will be `None`. The second represents
/// when the typevar is used in `generic_function`, and its `binding_context` will be `[2]`
/// (that is, the definition of `generic_function`).
///
/// For a PEP 695 typevar, there will always be a binding context, since you can only define
/// one as part of creating the generic context that uses it:
///
/// ```py
/// def generic_function[T](t: T) -> T: ...
/// ```
///
/// Here, we will create a single `TypeVarInstance`. Its [`definition`][Self::definition] will
/// be the `T` in `[T]` (i.e., the definition of the typevar in the syntactic construct that
/// creates the generic context that uses it). Its `binding_context` will be the definition of
/// `generic_function`.
binding_context: Option<Definition<'db>>,
/// The upper bound or constraint on the type of this TypeVar /// The upper bound or constraint on the type of this TypeVar
bound_or_constraints: Option<TypeVarBoundOrConstraints<'db>>, bound_or_constraints: Option<TypeVarBoundOrConstraints<'db>>,
@ -6611,6 +6657,25 @@ fn walk_type_var_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
} }
impl<'db> TypeVarInstance<'db> { impl<'db> TypeVarInstance<'db> {
pub(crate) fn with_binding_context(
self,
db: &'db dyn Db,
binding_context: Definition<'db>,
) -> Self {
Self::new(
db,
self.name(db),
self.definition(db),
Some(binding_context),
self.bound_or_constraints(db),
self.variance(db),
self.default_ty(db).map(|ty| {
ty.apply_type_mapping(db, &TypeMapping::BindLegacyTypevars(binding_context))
}),
self.kind(db),
)
}
pub(crate) fn is_legacy(self, db: &'db dyn Db) -> bool { pub(crate) fn is_legacy(self, db: &'db dyn Db) -> bool {
matches!(self.kind(db), TypeVarKind::Legacy) matches!(self.kind(db), TypeVarKind::Legacy)
} }
@ -6640,6 +6705,7 @@ impl<'db> TypeVarInstance<'db> {
db, db,
self.name(db), self.name(db),
self.definition(db), self.definition(db),
self.binding_context(db),
self.bound_or_constraints(db) self.bound_or_constraints(db)
.map(|b| b.normalized_impl(db, visitor)), .map(|b| b.normalized_impl(db, visitor)),
self.variance(db), self.variance(db),
@ -6653,6 +6719,7 @@ impl<'db> TypeVarInstance<'db> {
db, db,
self.name(db), self.name(db),
self.definition(db), self.definition(db),
self.binding_context(db),
self.bound_or_constraints(db) self.bound_or_constraints(db)
.map(|b| b.materialize(db, variance)), .map(|b| b.materialize(db, variance)),
self.variance(db), self.variance(db),

View file

@ -1273,7 +1273,21 @@ impl<'db> ClassLiteral<'db> {
class_stmt class_stmt
.bases() .bases()
.iter() .iter()
.map(|base_node| definition_expression_type(db, class_definition, base_node)) .map(
|base_node| match definition_expression_type(db, class_definition, base_node) {
Type::KnownInstance(KnownInstanceType::SubscriptedGeneric(generic_context)) => {
Type::KnownInstance(KnownInstanceType::SubscriptedGeneric(
generic_context.with_binding_context(db, class_definition),
))
}
Type::KnownInstance(KnownInstanceType::SubscriptedProtocol(
generic_context,
)) => Type::KnownInstance(KnownInstanceType::SubscriptedProtocol(
generic_context.with_binding_context(db, class_definition),
)),
ty => ty,
},
)
.collect() .collect()
} }
@ -4098,11 +4112,15 @@ impl KnownClass {
}; };
let containing_assignment = index.expect_single_definition(target); let containing_assignment = index.expect_single_definition(target);
// A freshly created legacy TypeVar does not have a binding context until it is
// used in a base class list, function parameter list, or type alias.
let binding_context = None;
overload.set_return_type(Type::KnownInstance(KnownInstanceType::TypeVar( overload.set_return_type(Type::KnownInstance(KnownInstanceType::TypeVar(
TypeVarInstance::new( TypeVarInstance::new(
db, db,
&target.id, &target.id,
Some(containing_assignment), Some(containing_assignment),
binding_context,
bound_or_constraint, bound_or_constraint,
variance, variance,
*default, *default,

View file

@ -205,7 +205,16 @@ impl Display for DisplayRepresentation<'_> {
) )
} }
Type::Tuple(specialization) => specialization.tuple(self.db).display(self.db).fmt(f), Type::Tuple(specialization) => specialization.tuple(self.db).display(self.db).fmt(f),
Type::TypeVar(typevar) => f.write_str(typevar.name(self.db)), Type::TypeVar(typevar) => {
f.write_str(typevar.name(self.db))?;
if let Some(binding_context) = typevar
.binding_context(self.db)
.and_then(|def| def.name(self.db))
{
write!(f, "@{binding_context}")?;
}
Ok(())
}
Type::AlwaysTruthy => f.write_str("AlwaysTruthy"), Type::AlwaysTruthy => f.write_str("AlwaysTruthy"),
Type::AlwaysFalsy => f.write_str("AlwaysFalsy"), Type::AlwaysFalsy => f.write_str("AlwaysFalsy"),
Type::BoundSuper(bound_super) => { Type::BoundSuper(bound_super) => {

View file

@ -14,13 +14,13 @@ use crate::types::signatures::{Parameter, Parameters, Signature};
use crate::types::tuple::{TupleSpec, TupleType}; use crate::types::tuple::{TupleSpec, TupleType};
use crate::types::{ use crate::types::{
KnownInstanceType, Type, TypeMapping, TypeRelation, TypeTransformer, TypeVarBoundOrConstraints, KnownInstanceType, Type, TypeMapping, TypeRelation, TypeTransformer, TypeVarBoundOrConstraints,
TypeVarInstance, TypeVarVariance, UnionType, binding_type, declaration_type, TypeVarInstance, TypeVarKind, TypeVarVariance, UnionType, binding_type, declaration_type,
}; };
use crate::{Db, FxOrderSet}; use crate::{Db, FxOrderSet};
/// Returns an iterator of any generic context introduced by the given scope or any enclosing /// Returns an iterator of any generic context introduced by the given scope or any enclosing
/// scope. /// scope.
fn enclosing_generic_contexts<'db>( pub(crate) fn enclosing_generic_contexts<'db>(
db: &'db dyn Db, db: &'db dyn Db,
module: &ParsedModuleRef, module: &ParsedModuleRef,
index: &SemanticIndex<'db>, index: &SemanticIndex<'db>,
@ -175,6 +175,19 @@ impl<'db> GenericContext<'db> {
Some(Self::new(db, variables)) Some(Self::new(db, variables))
} }
pub(crate) fn with_binding_context(
self,
db: &'db dyn Db,
binding_context: Definition<'db>,
) -> Self {
let variables: FxOrderSet<_> = self
.variables(db)
.iter()
.map(|typevar| typevar.with_binding_context(db, binding_context))
.collect();
Self::new(db, variables)
}
pub(crate) fn len(self, db: &'db dyn Db) -> usize { pub(crate) fn len(self, db: &'db dyn Db) -> usize {
self.variables(db).len() self.variables(db).len()
} }
@ -241,6 +254,22 @@ impl<'db> GenericContext<'db> {
self.variables(db).is_subset(other.variables(db)) self.variables(db).is_subset(other.variables(db))
} }
pub(crate) fn binds_legacy_typevar(
self,
db: &'db dyn Db,
typevar: TypeVarInstance<'db>,
) -> Option<TypeVarInstance<'db>> {
assert!(typevar.kind(db) == TypeVarKind::Legacy);
let typevar_def = typevar.definition(db);
self.variables(db)
.iter()
.find(|self_typevar| {
self_typevar.kind(db) == TypeVarKind::Legacy
&& self_typevar.definition(db) == typevar_def
})
.copied()
}
/// Creates a specialization of this generic context. Panics if the length of `types` does not /// Creates a specialization of this generic context. Panics if the length of `types` does not
/// match the number of typevars in the generic context. You must provide a specific type for /// match the number of typevars in the generic context. You must provide a specific type for
/// each typevar; no defaults are used. (Use [`specialize_partial`](Self::specialize_partial) /// each typevar; no defaults are used. (Use [`specialize_partial`](Self::specialize_partial)

View file

@ -110,7 +110,7 @@ use crate::types::enums::is_enum_class;
use crate::types::function::{ use crate::types::function::{
FunctionDecorators, FunctionLiteral, FunctionType, KnownFunction, OverloadLiteral, FunctionDecorators, FunctionLiteral, FunctionType, KnownFunction, OverloadLiteral,
}; };
use crate::types::generics::GenericContext; use crate::types::generics::{GenericContext, enclosing_generic_contexts};
use crate::types::mro::MroErrorKind; use crate::types::mro::MroErrorKind;
use crate::types::signatures::{CallableSignature, Signature}; use crate::types::signatures::{CallableSignature, Signature};
use crate::types::tuple::{TupleSpec, TupleType}; use crate::types::tuple::{TupleSpec, TupleType};
@ -803,6 +803,9 @@ pub(super) struct TypeInferenceBuilder<'db, 'ast> {
/// [`check_overloaded_functions`]: TypeInferenceBuilder::check_overloaded_functions /// [`check_overloaded_functions`]: TypeInferenceBuilder::check_overloaded_functions
called_functions: FxHashSet<FunctionType<'db>>, called_functions: FxHashSet<FunctionType<'db>>,
/// Whether we are in a context that binds unbound legacy typevars.
legacy_typevar_binding_context: Option<Definition<'db>>,
/// The deferred state of inferring types of certain expressions within the region. /// The deferred state of inferring types of certain expressions within the region.
/// ///
/// This is different from [`InferenceRegion::Deferred`] which works on the entire definition /// This is different from [`InferenceRegion::Deferred`] which works on the entire definition
@ -847,6 +850,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
expressions: FxHashMap::default(), expressions: FxHashMap::default(),
bindings: VecMap::default(), bindings: VecMap::default(),
declarations: VecMap::default(), declarations: VecMap::default(),
legacy_typevar_binding_context: None,
deferred: VecSet::default(), deferred: VecSet::default(),
cycle_fallback: false, cycle_fallback: false,
} }
@ -1713,9 +1717,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
match definition.kind(self.db()) { match definition.kind(self.db()) {
DefinitionKind::Function(function) => { DefinitionKind::Function(function) => {
self.infer_function_deferred(function.node(self.module())); self.infer_function_deferred(definition, function.node(self.module()));
}
DefinitionKind::Class(class) => {
self.infer_class_deferred(definition, class.node(self.module()));
} }
DefinitionKind::Class(class) => self.infer_class_deferred(class.node(self.module())),
_ => {} _ => {}
} }
} }
@ -2207,6 +2213,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
self.infer_type_parameters(type_params); self.infer_type_parameters(type_params);
if let Some(arguments) = class.arguments.as_deref() { if let Some(arguments) = class.arguments.as_deref() {
// Note: We do not install a new `legacy_typevar_binding_context`; since this class has
// PEP 695 typevars, it should not also bind any legacy typevars via inheriting from
// `typing.Generic` or `typing.Protocol`.
let mut call_arguments = let mut call_arguments =
CallArguments::from_arguments(self.db(), arguments, |argument, splatted_value| { CallArguments::from_arguments(self.db(), arguments, |argument, splatted_value| {
let ty = self.infer_expression(splatted_value); let ty = self.infer_expression(splatted_value);
@ -2228,6 +2237,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
.as_deref() .as_deref()
.expect("function type params scope without type params"); .expect("function type params scope without type params");
// Note: We do not install a new `legacy_typevar_binding_context`; since this function has
// PEP 695 typevars, it should not also bind any legacy typevars by referencing them in its
// parameter or return type annotations.
self.infer_return_type_annotation( self.infer_return_type_annotation(
function.returns.as_deref(), function.returns.as_deref(),
DeferredExpressionState::None, DeferredExpressionState::None,
@ -2590,11 +2602,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
if self.defer_annotations() { if self.defer_annotations() {
self.deferred.insert(definition); self.deferred.insert(definition);
} else { } else {
let previous_legacy_typevar_binding_context =
self.legacy_typevar_binding_context.replace(definition);
self.infer_return_type_annotation( self.infer_return_type_annotation(
returns.as_deref(), returns.as_deref(),
DeferredExpressionState::None, DeferredExpressionState::None,
); );
self.infer_parameters(parameters); self.infer_parameters(parameters);
self.legacy_typevar_binding_context = previous_legacy_typevar_binding_context;
} }
} }
@ -3006,25 +3021,38 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
if self.in_stub() || class_node.bases().iter().any(contains_string_literal) { if self.in_stub() || class_node.bases().iter().any(contains_string_literal) {
self.deferred.insert(definition); self.deferred.insert(definition);
} else { } else {
let previous_legacy_typevar_binding_context =
self.legacy_typevar_binding_context.replace(definition);
for base in class_node.bases() { for base in class_node.bases() {
self.infer_expression(base); self.infer_expression(base);
} }
self.legacy_typevar_binding_context = previous_legacy_typevar_binding_context;
} }
} }
} }
fn infer_function_deferred(&mut self, function: &ast::StmtFunctionDef) { fn infer_function_deferred(
&mut self,
definition: Definition<'db>,
function: &ast::StmtFunctionDef,
) {
let previous_legacy_typevar_binding_context =
self.legacy_typevar_binding_context.replace(definition);
self.infer_return_type_annotation( self.infer_return_type_annotation(
function.returns.as_deref(), function.returns.as_deref(),
DeferredExpressionState::Deferred, DeferredExpressionState::Deferred,
); );
self.infer_parameters(function.parameters.as_ref()); self.infer_parameters(function.parameters.as_ref());
self.legacy_typevar_binding_context = previous_legacy_typevar_binding_context;
} }
fn infer_class_deferred(&mut self, class: &ast::StmtClassDef) { fn infer_class_deferred(&mut self, definition: Definition<'db>, class: &ast::StmtClassDef) {
let previous_legacy_typevar_binding_context =
self.legacy_typevar_binding_context.replace(definition);
for base in class.bases() { for base in class.bases() {
self.infer_expression(base); self.infer_expression(base);
} }
self.legacy_typevar_binding_context = previous_legacy_typevar_binding_context;
} }
fn infer_type_alias_definition( fn infer_type_alias_definition(
@ -3330,6 +3358,28 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
bound, bound,
default, default,
} = node; } = node;
// Find the binding context for the PEP 695 typevars defined in this scope. The typevar
// scope should have a child containing the class, function, or type alias definition. Find
// that scope and use its definition as the binding context.
let typevar_scope = definition.file_scope(self.db());
let child_scopes = self.index.child_scopes(typevar_scope);
let binding_context = child_scopes
.filter_map(|(_, binding_scope)| match binding_scope.node() {
NodeWithScopeKind::Class(class) => {
Some(DefinitionNodeKey::from(class.node(self.context.module())))
}
NodeWithScopeKind::Function(function) => Some(DefinitionNodeKey::from(
function.node(self.context.module()),
)),
NodeWithScopeKind::TypeAlias(alias) => {
Some(DefinitionNodeKey::from(alias.node(self.context.module())))
}
_ => None,
})
.map(|key| self.index.expect_single_definition(key))
.next();
let bound_or_constraint = match bound.as_deref() { let bound_or_constraint = match bound.as_deref() {
Some(expr @ ast::Expr::Tuple(ast::ExprTuple { elts, .. })) => { Some(expr @ ast::Expr::Tuple(ast::ExprTuple { elts, .. })) => {
if elts.len() < 2 { if elts.len() < 2 {
@ -3374,6 +3424,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
self.db(), self.db(),
&name.id, &name.id,
Some(definition), Some(definition),
binding_context,
bound_or_constraint, bound_or_constraint,
TypeVarVariance::Invariant, // TODO: infer this TypeVarVariance::Invariant, // TODO: infer this
default_ty, default_ty,
@ -6351,6 +6402,52 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
self.infer_place_load(PlaceExprRef::from(&expr), ast::ExprRef::Name(name_node)); self.infer_place_load(PlaceExprRef::from(&expr), ast::ExprRef::Name(name_node));
resolved resolved
.map_type(|ty| {
// If the expression resolves to a legacy typevar, we will have the TypeVarInstance
// that was created when the typevar was created, which will not have an associated
// binding context. If this expression appears inside of a generic context that
// binds that typevar, we need to update the TypeVarInstance to include that
// binding context. To do that, we walk the enclosing scopes, looking for the
// nearest generic context that binds the typevar.
//
// If the legacy typevar is still unbound after that search, and we are in a
// context that binds unbound legacy typevars (i.e., the signature of a generic
// function), bind it with that context.
let find_legacy_typevar_binding = |typevar: TypeVarInstance<'db>| {
enclosing_generic_contexts(
self.db(),
self.context.module(),
self.index,
self.scope().file_scope_id(self.db()),
)
.find_map(|enclosing_context| {
enclosing_context.binds_legacy_typevar(self.db(), typevar)
})
.or_else(|| {
self.legacy_typevar_binding_context
.map(|legacy_typevar_binding_context| {
typevar
.with_binding_context(self.db(), legacy_typevar_binding_context)
})
})
};
match ty {
Type::TypeVar(typevar) if typevar.is_legacy(self.db()) => {
find_legacy_typevar_binding(typevar)
.map(Type::TypeVar)
.unwrap_or(ty)
}
Type::KnownInstance(KnownInstanceType::TypeVar(typevar))
if typevar.is_legacy(self.db()) =>
{
find_legacy_typevar_binding(typevar)
.map(|typevar| Type::KnownInstance(KnownInstanceType::TypeVar(typevar)))
.unwrap_or(ty)
}
_ => ty,
}
})
// Not found in the module's explicitly declared global symbols? // Not found in the module's explicitly declared global symbols?
// Check the "implicit globals" such as `__doc__`, `__file__`, `__name__`, etc. // Check the "implicit globals" such as `__doc__`, `__file__`, `__name__`, etc.
// These are looked up as attributes on `types.ModuleType`. // These are looked up as attributes on `types.ModuleType`.
@ -8933,6 +9030,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
cycle_fallback, cycle_fallback,
// builder only state // builder only state
legacy_typevar_binding_context: _,
deferred_state: _, deferred_state: _,
called_functions: _, called_functions: _,
index: _, index: _,
@ -8992,6 +9090,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
cycle_fallback, cycle_fallback,
// builder only state // builder only state
legacy_typevar_binding_context: _,
deferred_state: _, deferred_state: _,
called_functions: _, called_functions: _,
index: _, index: _,
@ -9054,6 +9153,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
declarations: _, declarations: _,
// Builder only state // Builder only state
legacy_typevar_binding_context: _,
deferred_state: _, deferred_state: _,
called_functions: _, called_functions: _,
index: _, index: _,

View file

@ -1790,7 +1790,7 @@ mod tests {
a_annotated_ty.unwrap().display(&db).to_string(), a_annotated_ty.unwrap().display(&db).to_string(),
"Unknown | A | B" "Unknown | A | B"
); );
assert_eq!(b_annotated_ty.unwrap().display(&db).to_string(), "T"); assert_eq!(b_annotated_ty.unwrap().display(&db).to_string(), "T@f");
} }
#[test] #[test]
@ -1835,7 +1835,7 @@ mod tests {
assert_eq!(b_name, "b"); assert_eq!(b_name, "b");
// Parameter resolution deferred: // Parameter resolution deferred:
assert_eq!(a_annotated_ty.unwrap().display(&db).to_string(), "A | B"); assert_eq!(a_annotated_ty.unwrap().display(&db).to_string(), "A | B");
assert_eq!(b_annotated_ty.unwrap().display(&db).to_string(), "T"); assert_eq!(b_annotated_ty.unwrap().display(&db).to_string(), "T@f");
} }
#[test] #[test]

View file

@ -93,6 +93,7 @@ impl<'db> SubclassOfType<'db> {
db, db,
Name::new_static("T_all"), Name::new_static("T_all"),
None, None,
None,
Some(TypeVarBoundOrConstraints::UpperBound( Some(TypeVarBoundOrConstraints::UpperBound(
KnownClass::Type.to_instance(db), KnownClass::Type.to_instance(db),
)), )),