[ty] Add support for properties that return Self (#21335)

## Summary

Detect usages of implicit `self` in property getters, which allows us to
treat their signature as being generic.

closes https://github.com/astral-sh/ty/issues/1502

## Typing conformance

Two new type assertions that are succeeding.

## Ecosystem results

Mostly look good. There are a few new false positives related to a bug
with constrained typevars that is unrelated to the work here. I reported
this as https://github.com/astral-sh/ty/issues/1503.

## Test Plan

Added regression tests.
This commit is contained in:
David Peter 2025-11-10 11:13:36 +01:00 committed by GitHub
parent a6f2dee33b
commit ab46c8de0f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 88 additions and 22 deletions

View file

@ -139,7 +139,7 @@ The first parameter of instance methods always has type `Self`, if it is not exp
The name `self` is not special in any way.
```py
def some_decorator(f: Callable) -> Callable:
def some_decorator[**P, R](f: Callable[P, R]) -> Callable[P, R]:
return f
class B:
@ -188,10 +188,10 @@ class B:
reveal_type(B().name_does_not_matter()) # revealed: B
reveal_type(B().positional_only(1)) # revealed: B
reveal_type(B().keyword_only(x=1)) # revealed: B
# TODO: This should deally be `B`
reveal_type(B().decorated_method()) # revealed: Unknown
# TODO: this should be B
reveal_type(B().a_property) # revealed: Unknown
reveal_type(B().a_property) # revealed: B
async def _():
reveal_type(await B().async_method()) # revealed: B

View file

@ -49,6 +49,40 @@ c.my_property = 2
c.my_property = "a"
```
## Properties returning `Self`
A property that returns `Self` refers to an instance of the class:
```py
from typing_extensions import Self
class Path:
@property
def parent(self) -> Self:
raise NotImplementedError
reveal_type(Path().parent) # revealed: Path
```
This also works when a setter is defined:
```py
class Node:
@property
def parent(self) -> Self:
raise NotImplementedError
@parent.setter
def parent(self, value: Self) -> None:
pass
root = Node()
child = Node()
child.parent = root
reveal_type(child.parent) # revealed: Node
```
## `property.getter`
`property.getter` can be used to overwrite the getter method of a property. This does not overwrite