Support typing.Self in methods (#17689)
Some checks are pending
CI / cargo fmt (push) Waiting to run
CI / Determine changes (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 (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run

## Summary

Fixes: astral-sh/ty#159 

This PR adds support for using `Self` in methods.
When the type of an annotation is `TypingSelf` it is converted to a type
var based on:
https://typing.python.org/en/latest/spec/generics.html#self

I just skipped Protocols because it had more problems and the tests was
not useful.
Also I need to create a follow up PR that implicitly assumes `self`
argument has type `Self`.

In order to infer the type in the `in_type_expression` method I needed
to have scope id and semantic index available. I used the idea from
[this PR](https://github.com/astral-sh/ruff/pull/17589/files) to pass
additional context to this method.
Also I think in all places that `in_type_expression` is called we need
to have this context because `Self` can be there so I didn't split the
method into one version with context and one without.

## Test Plan

Added new tests from spec.

---------

Co-authored-by: Micha Reiser <micha@reiser.io>
Co-authored-by: Carl Meyer <carl@astral.sh>
This commit is contained in:
Shaygan Hooshyari 2025-05-08 00:58:00 +02:00 committed by GitHub
parent 51cef5a72b
commit d566636ca5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 267 additions and 33 deletions

View file

@ -0,0 +1,190 @@
# Self
`Self` is treated as if it were a `TypeVar` bound to the class it's being used on.
`typing.Self` is only available in Python 3.11 and later.
## Methods
```toml
[environment]
python-version = "3.11"
```
```py
from typing import Self
class Shape:
def set_scale(self: Self, scale: float) -> Self:
reveal_type(self) # revealed: Self
return self
def nested_type(self) -> list[Self]:
return [self]
def nested_func(self: Self) -> Self:
def inner() -> Self:
reveal_type(self) # revealed: Self
return self
return inner()
def implicit_self(self) -> Self:
# TODO: first argument in a method should be considered as "typing.Self"
reveal_type(self) # revealed: Unknown
return self
reveal_type(Shape().nested_type()) # revealed: @Todo(specialized non-generic class)
reveal_type(Shape().nested_func()) # revealed: Shape
class Circle(Shape):
def set_scale(self: Self, scale: float) -> Self:
reveal_type(self) # revealed: Self
return self
class Outer:
class Inner:
def foo(self: Self) -> Self:
reveal_type(self) # revealed: Self
return self
```
## Class Methods
```toml
[environment]
python-version = "3.11"
```
```py
from typing import Self, TypeVar
class Shape:
def foo(self: Self) -> Self:
return self
@classmethod
def bar(cls: type[Self]) -> Self:
# TODO: type[Shape]
reveal_type(cls) # revealed: @Todo(unsupported type[X] special form)
return cls()
class Circle(Shape): ...
reveal_type(Shape().foo()) # revealed: Shape
# TODO: Shape
reveal_type(Shape.bar()) # revealed: Unknown
```
## Attributes
```toml
[environment]
python-version = "3.11"
```
```py
from typing import Self
class LinkedList:
value: int
next_node: Self
def next(self: Self) -> Self:
reveal_type(self.value) # revealed: int
return self.next_node
reveal_type(LinkedList().next()) # revealed: LinkedList
```
## Generic Classes
```toml
[environment]
python-version = "3.11"
```
```py
from typing import Self, Generic, TypeVar
T = TypeVar("T")
class Container(Generic[T]):
value: T
def set_value(self: Self, value: T) -> Self:
return self
int_container: Container[int] = Container[int]()
reveal_type(int_container) # revealed: Container[int]
reveal_type(int_container.set_value(1)) # revealed: Container[int]
```
## Protocols
TODO: <https://typing.python.org/en/latest/spec/generics.html#use-in-protocols>
## Annotations
```toml
[environment]
python-version = "3.11"
```
```py
from typing import Self
class Shape:
def union(self: Self, other: Self | None):
reveal_type(other) # revealed: Self | None
return self
```
## Invalid Usage
`Self` cannot be used in the signature of a function or variable.
```toml
[environment]
python-version = "3.11"
```
```py
from typing import Self, Generic, TypeVar
T = TypeVar("T")
# error: [invalid-type-form]
def x(s: Self): ...
# error: [invalid-type-form]
b: Self
# TODO: "Self" cannot be used in a function with a `self` or `cls` parameter that has a type annotation other than "Self"
class Foo:
# TODO: rejected Self because self has a different type
def has_existing_self_annotation(self: T) -> Self:
return self # error: [invalid-return-type]
def return_concrete_type(self) -> Self:
# TODO: tell user to use "Foo" instead of "Self"
# error: [invalid-return-type]
return Foo()
@staticmethod
# TODO: reject because of staticmethod
def make() -> Self:
# error: [invalid-return-type]
return Foo()
class Bar(Generic[T]):
foo: T
def bar(self) -> T:
return self.foo
# error: [invalid-type-form]
class Baz(Bar[Self]): ...
class MyMetaclass(type):
# TODO: rejected
def __new__(cls) -> Self:
return super().__new__(cls)
```

View file

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