mirror of https://github.com/astral-sh/ruff
8.8 KiB
8.8 KiB
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.
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.
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:
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
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
from typing_extensions import NamedTuple, TypedDict, override, Any, Self
from dataclasses import dataclass
@dataclass(order=True)
class ParentDataclass:
x: int
class Child(ParentDataclass): # error: [subclass-of-dataclass-with-order]
@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