mirror of https://github.com/astral-sh/ruff
315 lines
8.8 KiB
Markdown
315 lines
8.8 KiB
Markdown
# `typing.override`
|
|
|
|
## Basics
|
|
|
|
Decorating a method with `typing.override` decorator is an explicit indication to a type checker
|
|
that the method is intended to override a method on a superclass. If the decorated method does not
|
|
in fact override anything, a type checker should report a diagnostic on that method.
|
|
|
|
<!-- snapshot-diagnostics -->
|
|
|
|
```pyi
|
|
from typing_extensions import override, Callable, TypeVar
|
|
|
|
def lossy_decorator(fn: Callable) -> Callable: ...
|
|
|
|
class A:
|
|
@override
|
|
def __repr__(self): ... # fine: overrides `object.__repr__`
|
|
|
|
class Parent:
|
|
def foo(self): ...
|
|
@property
|
|
def my_property1(self) -> int: ...
|
|
@property
|
|
def my_property2(self) -> int: ...
|
|
baz = None
|
|
@classmethod
|
|
def class_method1(cls) -> int: ...
|
|
@staticmethod
|
|
def static_method1() -> int: ...
|
|
@classmethod
|
|
def class_method2(cls) -> int: ...
|
|
@staticmethod
|
|
def static_method2() -> int: ...
|
|
@lossy_decorator
|
|
def decorated_1(self): ...
|
|
@lossy_decorator
|
|
def decorated_2(self): ...
|
|
@lossy_decorator
|
|
def decorated_3(self): ...
|
|
|
|
class Child(Parent):
|
|
@override
|
|
def foo(self): ... # fine: overrides `Parent.foo`
|
|
@property
|
|
@override
|
|
def my_property1(self) -> int: ... # fine: overrides `Parent.my_property1`
|
|
@override
|
|
@property
|
|
def my_property2(self) -> int: ... # fine: overrides `Parent.my_property2`
|
|
@override
|
|
def baz(self): ... # fine: overrides `Parent.baz`
|
|
@classmethod
|
|
@override
|
|
def class_method1(cls) -> int: ... # fine: overrides `Parent.class_method1`
|
|
@staticmethod
|
|
@override
|
|
def static_method1() -> int: ... # fine: overrides `Parent.static_method1`
|
|
@override
|
|
@classmethod
|
|
def class_method2(cls) -> int: ... # fine: overrides `Parent.class_method2`
|
|
@override
|
|
@staticmethod
|
|
def static_method2() -> int: ... # fine: overrides `Parent.static_method2`
|
|
@override
|
|
def decorated_1(self): ... # fine: overrides `Parent.decorated_1`
|
|
@override
|
|
@lossy_decorator
|
|
def decorated_2(self): ... # fine: overrides `Parent.decorated_2`
|
|
@lossy_decorator
|
|
@override
|
|
def decorated_3(self): ... # fine: overrides `Parent.decorated_3`
|
|
|
|
class OtherChild(Parent): ...
|
|
|
|
class Grandchild(OtherChild):
|
|
@override
|
|
def foo(self): ... # fine: overrides `Parent.foo`
|
|
@override
|
|
@property
|
|
def bar(self) -> int: ... # fine: overrides `Parent.bar`
|
|
@override
|
|
def baz(self): ... # fine: overrides `Parent.baz`
|
|
@classmethod
|
|
@override
|
|
def class_method1(cls) -> int: ... # fine: overrides `Parent.class_method1`
|
|
@staticmethod
|
|
@override
|
|
def static_method1() -> int: ... # fine: overrides `Parent.static_method1`
|
|
@override
|
|
@classmethod
|
|
def class_method2(cls) -> int: ... # fine: overrides `Parent.class_method2`
|
|
@override
|
|
@staticmethod
|
|
def static_method2() -> int: ... # fine: overrides `Parent.static_method2`
|
|
@override
|
|
def decorated_1(self): ... # fine: overrides `Parent.decorated_1`
|
|
@override
|
|
@lossy_decorator
|
|
def decorated_2(self): ... # fine: overrides `Parent.decorated_2`
|
|
@lossy_decorator
|
|
@override
|
|
def decorated_3(self): ... # fine: overrides `Parent.decorated_3`
|
|
|
|
class Invalid:
|
|
@override
|
|
def ___reprrr__(self): ... # error: [invalid-explicit-override]
|
|
@override
|
|
@classmethod
|
|
def foo(self): ... # error: [invalid-explicit-override]
|
|
@classmethod
|
|
@override
|
|
def bar(self): ... # error: [invalid-explicit-override]
|
|
@staticmethod
|
|
@override
|
|
def baz(): ... # error: [invalid-explicit-override]
|
|
@override
|
|
@staticmethod
|
|
def eggs(): ... # error: [invalid-explicit-override]
|
|
@property
|
|
@override
|
|
def bad_property1(self) -> int: ... # TODO: should emit `invalid-explicit-override` here
|
|
@override
|
|
@property
|
|
def bad_property2(self) -> int: ... # TODO: should emit `invalid-explicit-override` here
|
|
@lossy_decorator
|
|
@override
|
|
def lossy(self): ... # TODO: should emit `invalid-explicit-override` here
|
|
@override
|
|
@lossy_decorator
|
|
def lossy2(self): ... # TODO: should emit `invalid-explicit-override` here
|
|
|
|
# TODO: all overrides in this class should cause us to emit *Liskov* violations,
|
|
# but not `@override` violations
|
|
class LiskovViolatingButNotOverrideViolating(Parent):
|
|
@override
|
|
@property
|
|
def foo(self) -> int: ...
|
|
@override
|
|
def my_property1(self) -> int: ...
|
|
@staticmethod
|
|
@override
|
|
def class_method1() -> int: ...
|
|
@classmethod
|
|
@override
|
|
def static_method1(cls) -> int: ...
|
|
|
|
# Diagnostic edge case: `override` is very far away from the method definition in the source code:
|
|
|
|
T = TypeVar("T")
|
|
|
|
def identity(x: T) -> T: ...
|
|
|
|
class Foo:
|
|
@override
|
|
@identity
|
|
@identity
|
|
@identity
|
|
@identity
|
|
@identity
|
|
@identity
|
|
@identity
|
|
@identity
|
|
@identity
|
|
@identity
|
|
@identity
|
|
@identity
|
|
@identity
|
|
@identity
|
|
@identity
|
|
@identity
|
|
@identity
|
|
@identity
|
|
def bar(self): ... # error: [invalid-explicit-override]
|
|
```
|
|
|
|
## Overloads
|
|
|
|
The typing spec states that for an overloaded method, `@override` should only be applied to the
|
|
implementation function. However, we nonetheless respect the decorator in this situation, even
|
|
though we also emit `invalid-overload` on these methods.
|
|
|
|
```py
|
|
from typing_extensions import override, overload
|
|
|
|
class Spam:
|
|
@overload
|
|
def foo(self, x: str) -> str: ...
|
|
@overload
|
|
def foo(self, x: int) -> int: ...
|
|
@override
|
|
def foo(self, x: str | int) -> str | int: # error: [invalid-explicit-override]
|
|
return x
|
|
|
|
@overload
|
|
@override
|
|
def bar(self, x: str) -> str: ...
|
|
@overload
|
|
@override
|
|
def bar(self, x: int) -> int: ...
|
|
@override
|
|
# error: [invalid-overload] "`@override` decorator should be applied only to the overload implementation"
|
|
# error: [invalid-overload] "`@override` decorator should be applied only to the overload implementation"
|
|
# error: [invalid-explicit-override]
|
|
def bar(self, x: str | int) -> str | int:
|
|
return x
|
|
|
|
@overload
|
|
@override
|
|
def baz(self, x: str) -> str: ...
|
|
@overload
|
|
def baz(self, x: int) -> int: ...
|
|
# error: [invalid-overload] "`@override` decorator should be applied only to the overload implementation"
|
|
# error: [invalid-explicit-override]
|
|
def baz(self, x: str | int) -> str | int:
|
|
return x
|
|
```
|
|
|
|
In a stub file, `@override` should always be applied to the first overload. Even if it isn't, we
|
|
always emit `invalid-explicit-override` diagnostics on the first overload.
|
|
|
|
`module.pyi`:
|
|
|
|
```pyi
|
|
from typing_extensions import override, overload
|
|
|
|
class Spam:
|
|
@overload
|
|
def foo(self, x: str) -> str: ... # error: [invalid-explicit-override]
|
|
@overload
|
|
@override
|
|
# error: [invalid-overload] "`@override` decorator should be applied only to the first overload"
|
|
def foo(self, x: int) -> int: ...
|
|
|
|
@overload
|
|
@override
|
|
def bar(self, x: str) -> str: ... # error: [invalid-explicit-override]
|
|
@overload
|
|
@override
|
|
# error: [invalid-overload] "`@override` decorator should be applied only to the first overload"
|
|
def bar(self, x: int) -> int: ...
|
|
|
|
@overload
|
|
@override
|
|
def baz(self, x: str) -> str: ... # error: [invalid-explicit-override]
|
|
@overload
|
|
def baz(self, x: int) -> int: ...
|
|
```
|
|
|
|
## Classes inheriting from `Any`
|
|
|
|
```py
|
|
from typing_extensions import Any, override
|
|
from does_not_exist import SomethingUnknown # error: [unresolved-import]
|
|
|
|
class Parent1(Any): ...
|
|
class Parent2(SomethingUnknown): ...
|
|
|
|
class Child1(Parent1):
|
|
@override
|
|
def bar(self): ... # fine
|
|
|
|
class Child2(Parent2):
|
|
@override
|
|
def bar(self): ... # fine
|
|
```
|
|
|
|
## Override of a synthesized method
|
|
|
|
```pyi
|
|
from typing_extensions import NamedTuple, TypedDict, override, Any, Self
|
|
from dataclasses import dataclass
|
|
|
|
@dataclass(order=True)
|
|
class ParentDataclass:
|
|
x: int
|
|
|
|
class Child(ParentDataclass):
|
|
@override
|
|
def __lt__(self, other: ParentDataclass) -> bool: ... # fine
|
|
|
|
class MyNamedTuple(NamedTuple):
|
|
x: int
|
|
|
|
@override
|
|
# TODO: this raises an exception at runtime (which we should emit a diagnostic for).
|
|
# It shouldn't be an `invalid-explicit-override` diagnostic, however.
|
|
def _asdict(self, /) -> dict[str, Any]: ...
|
|
|
|
class MyNamedTupleParent(NamedTuple):
|
|
x: int
|
|
|
|
class MyNamedTupleChild(MyNamedTupleParent):
|
|
@override
|
|
def _asdict(self, /) -> dict[str, Any]: ... # fine
|
|
|
|
class MyTypedDict(TypedDict):
|
|
x: int
|
|
|
|
@override
|
|
# TODO: it's invalid to define a method on a `TypedDict` class,
|
|
# so we should emit a diagnostic here.
|
|
# It shouldn't be an `invalid-explicit-override` diagnostic, however.
|
|
def copy(self) -> Self: ...
|
|
|
|
class Grandparent(Any): ...
|
|
|
|
class Parent(Grandparent, NamedTuple): # error: [invalid-named-tuple]
|
|
x: int
|
|
|
|
class Child(Parent):
|
|
@override
|
|
def foo(self): ... # fine because `Any` is in the MRO
|
|
```
|