Update Markdown tests

This commit is contained in:
David Peter 2025-09-24 16:40:49 +02:00
parent eea69f2357
commit 390513dc18

View file

@ -48,11 +48,11 @@ class Outer:
return self return self
``` ```
## Detection of implicit Self ## Type of (unannotated) `self` parameters
In instance methods, the first parameter (regardless of its name) is assumed to have type In instance methods, the first parameter (regardless of its name) is assumed to have the type
`typing.Self` unless it has an explicit annotation. This does not apply to `@classmethod` and `typing.Self`, unless it has an explicit annotation. This does not apply to `@classmethod` and
`@staticmethod`. `@staticmethod`s.
```toml ```toml
[environment] [environment]
@ -64,74 +64,108 @@ from typing import Self
class A: class A:
def implicit_self(self) -> Self: def implicit_self(self) -> Self:
# TODO: first argument in a method should be considered as "typing.Self" # TODO: This should be Self@implicit_self
reveal_type(self) # revealed: Unknown reveal_type(self) # revealed: Unknown
return self return self
def foo(self) -> int: def a_method(self) -> int:
def first_arg_is_not_self(a: int) -> int: def first_arg_is_not_self(a: int) -> int:
reveal_type(a) # revealed: int
return a return a
return first_arg_is_not_self(1) return first_arg_is_not_self(1)
@classmethod @classmethod
def bar(cls): ... def a_classmethod(cls) -> Self:
# TODO: This should be type[Self@bar]
reveal_type(cls) # revealed: Unknown
return cls()
@staticmethod @staticmethod
def static(x): ... def a_staticmethod(x: int): ...
a = A() a = A()
# TODO: Should reveal Self@implicit_self. Requires implicit self in method body(https://github.com/astral-sh/ruff/pull/18473)
reveal_type(a.implicit_self()) # revealed: A reveal_type(a.implicit_self()) # revealed: A
reveal_type(a.implicit_self) # revealed: bound method A.implicit_self() -> A reveal_type(a.implicit_self) # revealed: bound method A.implicit_self() -> A
``` ```
Calling an instance method explicitly verifies the first argument:
```py
A.implicit_self(a)
# error: [invalid-argument-type] "Argument to function `implicit_self` is incorrect: Argument type `Literal[1]` does not satisfy upper bound `A` of type variable `Self`"
A.implicit_self(1)
```
If the method is a class or static method then first argument is not self: If the method is a class or static method then first argument is not self:
```py ```py
A.bar() A.a_classmethod()
a.static(1)
A.a_classmethod(a) # error: [too-many-positional-arguments]
A.a_staticmethod(1)
a.a_staticmethod(1)
A.a_staticmethod(a) # error: [invalid-argument-type]
``` ```
"self" name is not special; any first parameter name is treated as Self. The first parameter of instance methods always has type `Self`, if it is not explicitly annotated.
The name `self` is not special in any way.
```py
class B:
def name_does_not_matter(this) -> Self:
# TODO: Should reveal Self@name_does_not_matter
reveal_type(this) # revealed: Unknown
return this
def positional_only(self, /, x: int) -> Self:
# TODO: Should reveal Self@positional_only
reveal_type(self) # revealed: Unknown
return self
def keyword_only(self, *, x: int) -> Self:
# TODO: Should reveal Self@keyword_only
reveal_type(self) # revealed: Unknown
return self
@property
def a_property(self) -> Self:
# TODO: Should reveal Self@a_property
reveal_type(self) # revealed: Unknown
return self
reveal_type(B().name_does_not_matter()) # revealed: B
# TODO: this should be B
reveal_type(B().positional_only(1)) # revealed: Unknown
reveal_type(B().keyword_only(x=1)) # revealed: B
# TODO: this should be B
reveal_type(B().a_property) # revealed: Unknown
```
This also works for generic classes:
```py ```py
from typing import Self, Generic, TypeVar from typing import Self, Generic, TypeVar
T = TypeVar("T") T = TypeVar("T")
class B:
def implicit_this(this) -> Self:
# TODO: Should reveal Self@implicit_this
reveal_type(this) # revealed: Unknown
return this
def ponly(self, /, x: int) -> None:
# TODO: Should reveal Self@ponly
reveal_type(self) # revealed: Unknown
def kwonly(self, *, x: int) -> None:
# TODO: Should reveal Self@kwonly
reveal_type(self) # revealed: Unknown
@property
def name(self) -> str:
# TODO: Should reveal Self@name
reveal_type(self) # revealed: Unknown
return "b"
B.ponly(B(), 1)
B.name
B.kwonly(B(), x=1)
class G(Generic[T]): class G(Generic[T]):
def id(self) -> Self: def id(self) -> Self:
# TODO: Should reveal Self@id # TODO: Should reveal Self@id
reveal_type(self) # revealed: Unknown reveal_type(self) # revealed: Unknown
return self return self
g = G[int]() reveal_type(G[int]().id()) # revealed: G[int]
reveal_type(G[str]().id()) # revealed: G[str]
# TODO: Should reveal Self@id Requires implicit self in method body(https://github.com/astral-sh/ruff/pull/18473)
reveal_type(G[int].id(g)) # revealed: G[int]
``` ```
Free functions and nested functions do not use implicit `Self`: Free functions and nested functions do not use implicit `Self`:
@ -140,10 +174,16 @@ Free functions and nested functions do not use implicit `Self`:
def not_a_method(self): def not_a_method(self):
reveal_type(self) # revealed: Unknown reveal_type(self) # revealed: Unknown
# error: [invalid-type-form]
def does_not_return_self(self) -> Self:
return self
class C: class C:
def outer(self) -> None: def outer(self) -> None:
def inner(self): def inner(self):
reveal_type(self) # revealed: Unknown reveal_type(self) # revealed: Unknown
reveal_type(not_a_method) # revealed: def not_a_method(self) -> Unknown
``` ```
## typing_extensions ## typing_extensions
@ -301,51 +341,40 @@ class MyMetaclass(type):
return super().__new__(cls) return super().__new__(cls)
``` ```
## Explicit Annotation Overrides Implicit `Self` ## Explicit annotations override implicit `Self`
If the first parameter is explicitly annotated, that annotation takes precedence over the implicit If the first parameter is explicitly annotated, that annotation takes precedence over the implicit
`Self` treatment. `Self` type.
```toml ```toml
[environment] [environment]
python-version = "3.11" python-version = "3.12"
``` ```
```py ```py
from __future__ import annotations
class Explicit: class Explicit:
# TODO: We could emit a warning if the annotated type of `self` is disjoint from `Explicit` # TODO: We could emit a warning if the annotated type of `self` is disjoint from `Explicit`
def bad(self: int) -> None: def bad(self: int) -> None:
reveal_type(self) # revealed: int reveal_type(self) # revealed: int
def forward(self: "Explicit") -> None: def forward(self: Explicit) -> None:
reveal_type(self) # revealed: Explicit reveal_type(self) # revealed: Explicit
e = Explicit()
# error: [invalid-argument-type] "Argument to bound method `bad` is incorrect: Expected `int`, found `Explicit`" # error: [invalid-argument-type] "Argument to bound method `bad` is incorrect: Expected `int`, found `Explicit`"
e.bad() Explicit().bad()
```
## Type of Implicit Self Explicit().forward()
The assigned type to self argument depends on the method signature. When the method is defined in a class ExplicitGeneric[T]:
non-generic class and has no other mention of `typing.Self` (for example in return type) then type def special(self: ExplicitGeneric[int]) -> None:
of `self` is instance of the class. reveal_type(self) # revealed: ExplicitGeneric[int]
```py ExplicitGeneric[int]().special()
from typing import Self
class C: # TODO: this should be an `invalid-argument-type` error
def f(self) -> Self: ExplicitGeneric[str]().special()
return self
def z(self) -> None: ...
C.z(1) # error: [invalid-argument-type] "Argument to function `z` is incorrect: Expected `C`, found `Literal[1]`"
```
```py
# error: [invalid-argument-type] "Argument to function `f` is incorrect: Argument type `Literal[1]` does not satisfy upper bound `C` of type variable `Self`"
C.f(1)
``` ```
## Binding a method fixes `Self` ## Binding a method fixes `Self`