crates/ty_python_semantic/resources/mdtest/override.md
typing.overrideDecorating 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 my_property1(self) -> int: ... # fine: overrides `Parent.my_property1`
@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: ... # error: [invalid-explicit-override]
@override
@property
def bad_property2(self) -> int: ... # error: [invalid-explicit-override]
@property
@override
def bad_settable_property(self) -> int: ... # error: [invalid-explicit-override]
@bad_settable_property.setter
def bad_settable_property(self, x: int) -> None: ...
@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: ...
# TODO: This maybe shouldn't be a Liskov violation? Whether called on the type or
# on an instance, it will behave the same from the caller's perspective. The only difference
# is whether the method body gets access to `cls`, which is not a concern of Liskov.
@staticmethod
@override
def class_method1() -> int: ... # error: [invalid-method-override]
@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]
@override decorator[rules]
missing-override-decorator = "error"
This rule requires the @override decorator on any method that overrides a superclass member, with
the exception of __init__, __new__, __init_subclass__, or __post_init__.
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing_extensions import Any, Protocol, overload, override
class Parent:
attr = None
def method(self) -> int:
return 1
@property
def prop(self) -> int:
return 1
@classmethod
def class_method(cls) -> int:
return 1
@staticmethod
def static_method() -> int:
return 1
@overload
def overloaded(self, value: int) -> int: ...
@overload
def overloaded(self, value: str) -> str: ...
def overloaded(self, value: int | str) -> int | str:
return value
class Child(Parent):
def method(self) -> int: # error: [missing-override-decorator]
return 2
@property
def prop(self) -> int: # error: [missing-override-decorator]
return 2
@classmethod
def class_method(cls) -> int: # error: [missing-override-decorator]
return 2
@staticmethod
def static_method() -> int: # error: [missing-override-decorator]
return 2
class AttributeChild(Parent):
def attr(self) -> None: # error: [missing-override-decorator]
pass
class OverloadChild(Parent):
@overload
def overloaded(self, value: int) -> int: ...
@overload
def overloaded(self, value: str) -> str: ...
def overloaded(self, value: int | str) -> int | str: # error: [missing-override-decorator]
return value
# Implementing an interface-only protocol member requires `@override`.
class ProtocolInterface(Protocol):
def method(self) -> int: ...
class ProtocolImplementation(ProtocolInterface):
def method(self) -> int: # error: [missing-override-decorator]
return 1
# Implementing an abstract interface method requires `@override`.
class AbstractInterface(ABC):
@abstractmethod
def method(self) -> int: ...
class AbstractImplementation(AbstractInterface):
def method(self) -> int: # error: [missing-override-decorator]
return 1
class ExplicitChild(Parent):
@override
def method(self) -> int:
return 2
@property
@override
def prop(self) -> int:
return 2
@override
def attr(self) -> None:
pass
@overload
def overloaded(self, value: int) -> int: ...
@overload
def overloaded(self, value: str) -> str: ...
@override
def overloaded(self, value: int | str) -> int | str:
return value
class OverrideOnOverload(Parent):
@overload
@override
def overloaded(self, value: int) -> int: ... # error: [invalid-overload]
@overload
def overloaded(self, value: str) -> str: ...
def overloaded(self, value: int | str) -> int | str: # error: [missing-override-decorator]
return value
class OverrideOnImplementation(Parent):
@overload
def overloaded(self, value: int) -> int: ...
@overload
def overloaded(self, value: str) -> str: ...
@override
def overloaded(self, value: int | str) -> int | str:
return value
class ConstructorParent:
def __init__(self, value: int) -> None:
pass
def __new__(cls, value: int) -> "ConstructorParent":
raise NotImplementedError
def __init_subclass__(cls, value: int = 0) -> None:
pass
def __post_init__(self) -> None:
pass
class ConstructorChild(ConstructorParent):
def __init__(self, value: str) -> None:
pass
def __new__(cls, value: str) -> "ConstructorChild":
raise NotImplementedError
def __init_subclass__(cls, value: str = "") -> None:
pass
def __post_init__(self) -> None:
pass
# Overrides of ty-generated dataclass members require `@override`, except for constructor-like
# methods.
@dataclass(order=True)
class DataClassParent:
field: int
class DataClassChild(DataClassParent): # error: [subclass-of-dataclass-with-order]
def __init__(self, field: str) -> None:
self.field = 1
def __lt__(self, other: DataClassParent) -> bool: # error: [missing-override-decorator]
return True
class DynamicParent(Any): ...
class DynamicChild(DynamicParent):
def method(self) -> int:
return 1
stub.pyi:
from abc import ABC, abstractmethod
from typing_extensions import Protocol, overload, override
class StubParent:
@overload
def method(self, value: int) -> int: ...
@overload
def method(self, value: str) -> str: ...
class StubChild(StubParent):
@overload
def method(self, value: int) -> int: ... # error: [missing-override-decorator]
@overload
def method(self, value: str) -> str: ...
class ExplicitStubChild(StubParent):
@overload
@override
def method(self, value: int) -> int: ...
@overload
def method(self, value: str) -> str: ...
class OverrideOnSecondOverload(StubParent):
@overload
def method(self, value: int) -> int: ... # error: [missing-override-decorator]
@overload
@override
def method(self, value: str) -> str: ... # error: [invalid-overload]
class OverrideOnFirstOverload(StubParent):
@overload
@override
def method(self, value: int) -> int: ...
@overload
def method(self, value: str) -> str: ...
class StubProtocolInterface(Protocol):
def method(self) -> int: ...
class StubProtocolImplementation(StubProtocolInterface):
def method(self) -> int: ... # error: [missing-override-decorator]
class StubAbstractInterface(ABC):
@abstractmethod
def method(self) -> int: ...
class StubAbstractImplementation(StubAbstractInterface):
def method(self) -> int: ... # error: [missing-override-decorator]
from typing_extensions import override
def coinflip() -> bool:
return False
class Parent:
if coinflip():
def method1(self) -> None: ...
def method2(self) -> None: ...
if coinflip():
def method3(self) -> None: ...
def method4(self) -> None: ...
else:
def method3(self) -> None: ...
def method4(self) -> None: ...
def method5(self) -> None: ...
def method6(self) -> None: ...
class Child(Parent):
@override
def method1(self) -> None: ...
@override
def method2(self) -> None: ...
if coinflip():
@override
def method3(self) -> None: ...
if coinflip():
@override
def method4(self) -> None: ...
else:
@override
def method4(self) -> None: ...
if coinflip():
@override
def method5(self) -> None: ...
if coinflip():
@override
def method6(self) -> None: ...
else:
@override
def method6(self) -> None: ...
if coinflip():
@override
def method7(self) -> None: ... # error: [invalid-explicit-override]
if coinflip():
@override
def method8(self) -> None: ... # error: [invalid-explicit-override]
else:
@override
def method8(self) -> None: ...
@overrideThe diagnostic should point to the first definition decorated with @override, which may not
necessarily be the first definition of the symbol overall:
runtime.py:
from typing_extensions import override, overload
def coinflip() -> bool:
return True
class Foo:
if coinflip():
def method(self, x): ...
elif coinflip():
@overload
def method(self, x: str) -> str: ...
@overload
def method(self, x: int) -> int: ...
@override
def method(self, x: str | int) -> str | int: # error: [invalid-explicit-override]
return x
elif coinflip():
@override
def method(self, x): ...
stub.pyi`:
from typing_extensions import override, overload
def coinflip() -> bool:
return True
class Foo:
if coinflip():
def method(self, x): ...
elif coinflip():
@overload
@override
def method(self, x: str) -> str: ... # error: [invalid-explicit-override]
@overload
def method(self, x: int) -> int: ...
if coinflip():
def method2(self, x): ...
elif coinflip():
@overload
@override
def method2(self, x: str) -> str: ... # error: [invalid-explicit-override]
@overload
def method2(self, x: int) -> int: ...
else:
@override
def method2(self, x): ...
[environment]
python-version = "3.10"
import sys
from typing_extensions import override, overload
class Parent:
if sys.version_info >= (3, 10):
def foo(self) -> None: ...
def foooo(self) -> None: ...
else:
def bar(self) -> None: ...
def baz(self) -> None: ...
def spam(self) -> None: ...
class Child(Parent):
@override
def foo(self) -> None: ...
# The declaration on `Parent` is not reachable,
# so this is an error
@override
def bar(self) -> None: ... # error: [invalid-explicit-override]
if sys.version_info >= (3, 10):
@override
def foooo(self) -> None: ...
@override
def baz(self) -> None: ... # error: [invalid-explicit-override]
else:
# This doesn't override any reachable definitions,
# but the subclass definition also isn't a reachable definition
# from the end of the scope with the given configuration,
# so it's not flagged
@override
def foooo(self) -> None: ...
@override
def spam(self) -> None: ...
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
# error: [invalid-overload] "`@override` decorator should be applied only to the overload implementation"
def bar(self, x: str) -> str: ...
@overload
@override
# error: [invalid-overload] "`@override` decorator should be applied only to the overload implementation"
def bar(self, x: int) -> int: ...
@override
# error: [invalid-explicit-override]
def bar(self, x: str | int) -> str | int:
return x
@overload
@override
# error: [invalid-overload] "`@override` decorator should be applied only to the overload implementation"
def baz(self, x: str) -> str: ...
@overload
def baz(self, x: int) -> int: ...
# 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: ...
[environment]
python-version = "3.10"
import sys
from typing_extensions import overload, override
class Foo:
if sys.version_info >= (3, 10):
@overload
@override
def method(self, x: int) -> int: ... # error: [invalid-explicit-override]
else:
@overload
def method(self, x: int) -> int: ...
@overload
def method(self, x: str) -> str: ...
if sys.version_info >= (3, 10):
@overload
def method2(self, x: int) -> int: ...
else:
@overload
@override
def method2(self, x: int) -> int: ...
@overload
def method2(self, x: str) -> str: ...
Anyfrom 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
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
# error: [invalid-named-tuple] "Cannot overwrite NamedTuple attribute `_asdict`"
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
# error: [invalid-typed-dict-statement] "TypedDict class cannot have methods"
@override
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
When checking an override, overloads with explicit receiver annotations only need to be considered
if the receiver can be an instance of the subclass. For example, Child cannot also be an instance
of the unrelated @final class Restricted, so the Restricted-specific overload does not
constrain Child.method.
[environment]
python-version = "3.13"
from __future__ import annotations
from collections.abc import Iterable, Iterator, MutableMapping
from typing import Protocol, TypeVar, final, overload
class Base:
@overload
def method(self: Restricted, extra: str) -> None: ...
@overload
def method(self) -> None: ...
def method(self, extra: str = "") -> None: ...
@final
class Restricted(Base): ...
class Child(Base):
def method(self) -> None: ...
# Regression test for https://github.com/astral-sh/ty/issues/2612: the
# `LiteralString`-specific overload of `str.__iter__` does not constrain a
# method override on a user-defined `str` subclass.
class MyStr(str):
def __iter__(self) -> Iterator[str]:
raise NotImplementedError
# Regression test for https://github.com/astral-sh/ty/issues/2693: the
# receiver-specific overloads of `MutableMapping.update` that use protocols
# should not cause a false-positive Liskov violation.
KT = TypeVar("KT")
VT = TypeVar("VT")
VT_co = TypeVar("VT_co", covariant=True)
class Maplike(Protocol[KT, VT_co]):
def keys(self) -> Iterable[KT]: ...
def __getitem__(self, key: KT, /) -> VT_co: ...
MapOrItems = Maplike[KT, VT] | Iterable[tuple[KT, VT]]
class MyMapping(MutableMapping[KT, VT]):
def __getitem__(self, key: KT) -> VT:
raise NotImplementedError
def __setitem__(self, key: KT, value: VT) -> None: ...
def __delitem__(self, key: KT) -> None: ...
def __iter__(self) -> Iterator[KT]:
raise NotImplementedError
def __len__(self) -> int:
raise NotImplementedError
def update(self, arg: MapOrItems[KT, VT] = (), /, **kw: VT) -> None: ...
# TODO: We should emit an `invalid-method-override` diagnostic on
# `DeferredChild1.method`. The `DeferredChild1`-specific overload applies to
# this subclass, so its override cannot remove the `extra` parameter.
class DeferredBase:
@overload
def method(self) -> None: ...
@overload
def method(self: DeferredChild1, extra: str) -> None: ...
def method(self, extra: str = "") -> None: ...
class DeferredChild1(DeferredBase):
def method(self) -> None: ...
# TODO: A strict Liskov check would emit an `invalid-method-override`
# diagnostic here too. A subclass could inherit from both `DeferredChild1`
# and `DeferredChild2`, making the receiver-specific overload applicable.
class DeferredChild2(DeferredBase):
def method(self) -> None: ...