[ty] typing.Self is bound by the method, not the class (#19784)

This fixes our logic for binding a legacy typevar with its binding
context. (To recap, a legacy typevar starts out "unbound" when it is
first created, and each time it's used in a generic class or function,
we "bind" it with the corresponding `Definition`.)

We treat `typing.Self` the same as a legacy typevar, and so we apply
this binding logic to it too. Before, we were using the enclosing class
as its binding context. But that's not correct — it's the method where
`typing.Self` is used that binds the typevar. (Each invocation of the
method will find a new specialization of `Self` based on the specific
instance type containing the invoked method.)

This required plumbing through some additional state to the
`in_type_expression` method.

This also revealed that we weren't handling `Self`-typed instance
attributes correctly (but were coincidentally not getting the expected
false positive diagnostics).
This commit is contained in:
Douglas Creager 2025-08-06 17:26:17 -04:00 committed by GitHub
parent 21ac16db85
commit 585ce12ace
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 216 additions and 69 deletions

View file

@ -365,3 +365,33 @@ def g(x: T) -> T | None:
reveal_type(f(g("a"))) # revealed: tuple[Literal["a"] | None, int]
reveal_type(g(f("a"))) # revealed: tuple[Literal["a"], int] | None
```
## Opaque decorators don't affect typevar binding
Inside the body of a generic function, we should be able to see that the typevars bound by that
function are in fact bound by that function. This requires being able to see the enclosing
function's _undecorated_ type and signature, especially in the case where a gradually typed
decorator "hides" the function type from outside callers.
```py
from typing import cast, Any, Callable, TypeVar
F = TypeVar("F", bound=Callable[..., Any])
T = TypeVar("T")
def opaque_decorator(f: Any) -> Any:
return f
def transparent_decorator(f: F) -> F:
return f
@opaque_decorator
def decorated(t: T) -> None:
# error: [redundant-cast]
reveal_type(cast(T, t)) # revealed: T@decorated
@transparent_decorator
def decorated(t: T) -> None:
# error: [redundant-cast]
reveal_type(cast(T, t)) # revealed: T@decorated
```