diff --git a/mypy/checker.py b/mypy/checker.py index dbd74d5a8132..b8b85be3fbe8 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -641,7 +641,9 @@ def _visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None: if defn.impl: defn.impl.accept(self) if defn.info: - self.check_method_override(defn) + found_base_method = self.check_method_override(defn) + if defn.is_explicit_override and found_base_method is False: + self.msg.no_overridable_method(defn.name, defn) self.check_inplace_operator_method(defn) if not defn.is_property: self.check_overlapping_overloads(defn) diff --git a/mypy/nodes.py b/mypy/nodes.py index 98af6145edc3..414b5c190aa0 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -512,6 +512,7 @@ class FuncBase(Node): "is_class", # Uses "@classmethod" (explicit or implicit) "is_static", # Uses "@staticmethod" "is_final", # Uses "@final" + "is_explicit_override", # Uses "@override" "_fullname", ) @@ -529,6 +530,7 @@ def __init__(self) -> None: self.is_class = False self.is_static = False self.is_final = False + self.is_explicit_override = False # Name with module prefix self._fullname = "" @@ -747,7 +749,6 @@ class FuncDef(FuncItem, SymbolNode, Statement): "is_mypy_only", # Present only when a function is decorated with @typing.datasclass_transform or similar "dataclass_transform_spec", - "is_explicit_override", ) __match_args__ = ("name", "arguments", "type", "body") @@ -776,8 +777,6 @@ def __init__( # Definitions that appear in if TYPE_CHECKING are marked with this flag. self.is_mypy_only = False self.dataclass_transform_spec: DataclassTransformSpec | None = None - # Decorated with @override - self.is_explicit_override = False @property def name(self) -> str: diff --git a/mypy/semanal.py b/mypy/semanal.py index 08c26170a952..70bd876af46e 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1197,6 +1197,9 @@ def analyze_overload_sigs_and_impl( types.append(callable) if item.var.is_property: self.fail("An overload can not be a property", item) + # If any item was decorated with `@override`, the whole overload + # becomes an explicit override. + defn.is_explicit_override |= item.func.is_explicit_override elif isinstance(item, FuncDef): if i == len(defn.items) - 1 and not self.is_stub_file: impl = item diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index b9baf9e4cf80..4bad19af539c 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -2852,6 +2852,40 @@ class D(A): [builtins fixtures/property.pyi] [typing fixtures/typing-full.pyi] +[case explicitOverrideSettableProperty] +# flags: --python-version 3.12 +from typing import override + +class A: + @property + def f(self) -> str: pass + + @f.setter + def f(self, value: str) -> None: pass + +class B(A): + @property # E: Read-only property cannot override read-write property + @override + def f(self) -> str: pass + +class C(A): + @override + @property + def f(self) -> str: pass + + @f.setter + def f(self, value: str) -> None: pass + +class D(A): + @override # E: Signature of "f" incompatible with supertype "A" + @property + def f(self) -> int: pass + + @f.setter + def f(self, value: int) -> None: pass +[builtins fixtures/property.pyi] +[typing fixtures/typing-full.pyi] + [case invalidExplicitOverride] # flags: --python-version 3.12 from typing import override @@ -2896,3 +2930,72 @@ class B(A): def f2(self, x: int) -> str: pass # E: Method "f2" is marked as an override, but no base method was found with this name [typing fixtures/typing-full.pyi] [builtins fixtures/tuple.pyi] + +[case explicitOverrideOverloads] +# flags: --python-version 3.12 +from typing import overload, override + +class A: + def f(self, x: int) -> str: pass + +class B(A): + @overload # E: Method "f2" is marked as an override, but no base method was found with this name + def f2(self, x: int) -> str: pass + @overload + def f2(self, x: str) -> str: pass + @override + def f2(self, x: int | str) -> str: pass +[typing fixtures/typing-full.pyi] +[builtins fixtures/tuple.pyi] + +[case explicitOverrideNotOnOverloadsImplementation] +# flags: --python-version 3.12 +from typing import overload, override + +class A: + def f(self, x: int) -> str: pass + +class B(A): + @overload # E: Method "f2" is marked as an override, but no base method was found with this name + def f2(self, x: int) -> str: pass + @override + @overload + def f2(self, x: str) -> str: pass + def f2(self, x: int | str) -> str: pass + +class C(A): + @overload + def f(self, y: int) -> str: pass + @override + @overload + def f(self, y: str) -> str: pass + def f(self, y: int | str) -> str: pass +[typing fixtures/typing-full.pyi] +[builtins fixtures/tuple.pyi] + +[case explicitOverrideOnMultipleOverloads] +# flags: --python-version 3.12 +from typing import overload, override + +class A: + def f(self, x: int) -> str: pass + +class B(A): + @override # E: Method "f2" is marked as an override, but no base method was found with this name + @overload + def f2(self, x: int) -> str: pass + @override + @overload + def f2(self, x: str) -> str: pass + def f2(self, x: int | str) -> str: pass + +class C(A): + @overload + def f(self, y: int) -> str: pass + @override + @overload + def f(self, y: str) -> str: pass + @override + def f(self, y: int | str) -> str: pass +[typing fixtures/typing-full.pyi] +[builtins fixtures/tuple.pyi]