mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-09 10:00:25 +00:00

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
`Type::TypeVar` now distinguishes whether the typevar in question is inferable or not. A typevar is _not inferable_ inside the body of the generic class or function that binds it: ```py def f[T](t: T) -> T: return t ``` The infered type of `t` in the function body is `TypeVar(T, NotInferable)`. This represents how e.g. assignability checks need to be valid for all possible specializations of the typevar. Most of the existing assignability/etc logic only applies to non-inferable typevars. Outside of the function body, the typevar is _inferable_: ```py f(4) ``` Here, the parameter type of `f` is `TypeVar(T, Inferable)`. This represents how e.g. assignability doesn't need to hold for _all_ specializations; instead, we need to find the constraints under which this specific assignability check holds. This is in support of starting to perform specialization inference _as part of_ performing the assignability check at the call site. In the [[POPL2015][]] paper, this concept is called _monomorphic_ / _polymorphic_, but I thought _non-inferable_ / _inferable_ would be clearer for us. Depends on #19784 [POPL2015]: https://doi.org/10.1145/2676726.2676991 --------- Co-authored-by: Carl Meyer <carl@astral.sh>
5.2 KiB
5.2 KiB
Self
[environment]
python-version = "3.11"
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
from typing import Self
class Shape:
def set_scale(self: Self, scale: float) -> Self:
reveal_type(self) # revealed: Self@set_scale
return self
def nested_type(self: Self) -> list[Self]:
return [self]
def nested_func(self: Self) -> Self:
def inner() -> Self:
reveal_type(self) # revealed: Self@nested_func
return self
return inner()
def nested_func_without_enclosing_binding(self):
def inner(x: Self):
# TODO: revealed: Self@nested_func_without_enclosing_binding
# (The outer method binds an implicit `Self`)
reveal_type(x) # revealed: Self@inner
inner(self)
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: list[Shape]
reveal_type(Shape().nested_func()) # revealed: Shape
class Circle(Shape):
def set_scale(self: Self, scale: float) -> Self:
reveal_type(self) # revealed: Self@set_scale
return self
class Outer:
class Inner:
def foo(self: Self) -> Self:
reveal_type(self) # revealed: Self@foo
return self
typing_extensions
[environment]
python-version = "3.10"
from typing_extensions import Self
class C:
def method(self: Self) -> Self:
return self
reveal_type(C().method()) # revealed: C
Class Methods
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
TODO: The use of Self
to annotate the next_node
attribute should be
modeled as a property, using Self
in its parameter and return type.
from typing import Self
class LinkedList:
value: int
next_node: Self
def next(self: Self) -> Self:
reveal_type(self.value) # revealed: int
# TODO: no error
# error: [invalid-return-type]
return self.next_node
reveal_type(LinkedList().next()) # revealed: LinkedList
Generic Classes
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
from typing import Self
class Shape:
def union(self: Self, other: Self | None):
reveal_type(other) # revealed: Self@union | None
return self
Invalid Usage
Self
cannot be used in the signature of a function or variable.
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)
Binding a method fixes Self
When a method is bound, any instances of Self
in its signature are "fixed", since we now know the
specific type of the bound parameter.
from typing import Self
class C:
def instance_method(self, other: Self) -> Self:
return self
@classmethod
def class_method(cls) -> Self:
return cls()
# revealed: bound method C.instance_method(other: C) -> C
reveal_type(C().instance_method)
# revealed: bound method <class 'C'>.class_method() -> C
reveal_type(C.class_method)
class D(C): ...
# revealed: bound method D.instance_method(other: D) -> D
reveal_type(D().instance_method)
# revealed: bound method <class 'D'>.class_method() -> D
reveal_type(D.class_method)