Skip to content

Commit 6e40627

Browse files
committed
Include method base class in error message
1 parent 537e794 commit 6e40627

File tree

3 files changed

+49
-20
lines changed

3 files changed

+49
-20
lines changed

mypy/checker.py

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -643,15 +643,17 @@ def _visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None:
643643
if defn.impl:
644644
defn.impl.accept(self)
645645
if defn.info:
646-
found_base_method = self.check_method_override(defn)
647-
if defn.is_explicit_override and found_base_method is False:
646+
method_base_class = self.check_method_override(defn)
647+
if defn.is_explicit_override and not method_base_class:
648648
self.msg.no_overridable_method(defn.name, defn)
649649
elif (
650-
found_base_method
650+
method_base_class
651651
and self.options.strict_override_decorator
652652
and not defn.is_explicit_override
653653
):
654-
self.msg.override_decorator_missing(defn.name, defn.impl or defn)
654+
self.msg.override_decorator_missing(
655+
defn.name, method_base_class.fullname, defn.impl or defn
656+
)
655657
self.check_inplace_operator_method(defn)
656658
if not defn.is_property:
657659
self.check_overlapping_overloads(defn)
@@ -978,13 +980,15 @@ def _visit_func_def(self, defn: FuncDef) -> None:
978980
# overload, the legality of the override has already
979981
# been typechecked, and decorated methods will be
980982
# checked when the decorator is.
981-
found_base_method = self.check_method_override(defn)
983+
method_base_class = self.check_method_override(defn)
982984
if (
983-
found_base_method
985+
method_base_class
984986
and self.options.strict_override_decorator
985987
and defn.name not in ("__init__", "__new__")
986988
):
987-
self.msg.override_decorator_missing(defn.name, defn)
989+
self.msg.override_decorator_missing(
990+
defn.name, method_base_class.fullname, defn
991+
)
988992
self.check_inplace_operator_method(defn)
989993
if defn.original_def:
990994
# Override previous definition.
@@ -1826,23 +1830,26 @@ def expand_typevars(
18261830
else:
18271831
return [(defn, typ)]
18281832

1829-
def check_method_override(self, defn: FuncDef | OverloadedFuncDef | Decorator) -> bool | None:
1833+
def check_method_override(
1834+
self, defn: FuncDef | OverloadedFuncDef | Decorator
1835+
) -> TypeInfo | None:
18301836
"""Check if function definition is compatible with base classes.
18311837
18321838
This may defer the method if a signature is not available in at least one base class.
18331839
Return ``None`` if that happens.
18341840
1841+
Return the first base class if an attribute with the method name was found within it.
18351842
Return ``True`` if an attribute with the method name was found in the base class.
18361843
"""
18371844
# Check against definitions in base classes.
1838-
found_base_method = False
18391845
for base in defn.info.mro[1:]:
18401846
result = self.check_method_or_accessor_override_for_base(defn, base)
18411847
if result is None:
18421848
# Node was deferred, we will have another attempt later.
18431849
return None
1844-
found_base_method |= result
1845-
return found_base_method
1850+
if result:
1851+
return base
1852+
return None
18461853

18471854
def check_method_or_accessor_override_for_base(
18481855
self, defn: FuncDef | OverloadedFuncDef | Decorator, base: TypeInfo
@@ -4752,15 +4759,17 @@ def visit_decorator(self, e: Decorator) -> None:
47524759
self.check_incompatible_property_override(e)
47534760
# For overloaded functions we already checked override for overload as a whole.
47544761
if e.func.info and not e.func.is_dynamic() and not e.is_overload:
4755-
found_base_method = self.check_method_override(e)
4756-
if e.func.is_explicit_override and found_base_method is False:
4762+
method_base_class = self.check_method_override(e)
4763+
if e.func.is_explicit_override and not method_base_class:
47574764
self.msg.no_overridable_method(e.func.name, e.func)
47584765
elif (
4759-
found_base_method
4766+
method_base_class
47604767
and self.options.strict_override_decorator
47614768
and not e.func.is_explicit_override
47624769
):
4763-
self.msg.override_decorator_missing(e.func.name, e.func)
4770+
self.msg.override_decorator_missing(
4771+
e.func.name, method_base_class.fullname, e.func
4772+
)
47644773

47654774
if e.func.info and e.func.name in ("__init__", "__new__"):
47664775
if e.type and not isinstance(get_proper_type(e.type), (FunctionLike, AnyType)):

mypy/messages.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1525,10 +1525,10 @@ def no_overridable_method(self, name: str, context: Context) -> None:
15251525
context,
15261526
)
15271527

1528-
def override_decorator_missing(self, name: str, context: Context) -> None:
1528+
def override_decorator_missing(self, name: str, base_name: str, context: Context) -> None:
15291529
self.fail(
15301530
f'Method "{name}" is not marked as override '
1531-
"but is overriding a method in a base class",
1531+
f'but is overriding a method in class "{base_name}"',
15321532
context,
15331533
)
15341534

test-data/unit/check-functions.test

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3013,7 +3013,10 @@ class B(A):
30133013
def f(self, y: int) -> str: pass
30143014

30153015
class C(A):
3016-
def f(self, y: int) -> str: pass # E: Method "f" is not marked as override but is overriding a method in a base class
3016+
def f(self, y: int) -> str: pass # E: Method "f" is not marked as override but is overriding a method in class "__main__.A"
3017+
3018+
class D(B):
3019+
def f(self, y: int) -> str: pass # E: Method "f" is not marked as override but is overriding a method in class "__main__.B"
30173020
[typing fixtures/typing-override.pyi]
30183021

30193022
[case requireExplicitOverrideSpecialMethod]
@@ -3042,7 +3045,7 @@ class B(A):
30423045

30433046
class C(A):
30443047
@property
3045-
def prop(self) -> int: pass # E: Method "prop" is not marked as override but is overriding a method in a base class
3048+
def prop(self) -> int: pass # E: Method "prop" is not marked as override but is overriding a method in class "__main__.A"
30463049
[typing fixtures/typing-override.pyi]
30473050
[builtins fixtures/property.pyi]
30483051

@@ -3070,5 +3073,22 @@ class C(A):
30703073
def f(self, y: int) -> str: ...
30713074
@overload
30723075
def f(self, y: str) -> str: ...
3073-
def f(self, y): pass # E: Method "f" is not marked as override but is overriding a method in a base class
3076+
def f(self, y): pass # E: Method "f" is not marked as override but is overriding a method in class "__main__.A"
3077+
[typing fixtures/typing-override.pyi]
3078+
3079+
[case requireExplicitOverrideMultipleInheritance]
3080+
# flags: --strict-override-decorator --python-version 3.12
3081+
from typing import override
3082+
3083+
class A:
3084+
def f(self, x: int) -> str: pass
3085+
class B:
3086+
def f(self, y: int) -> str: pass
3087+
3088+
class C(A, B):
3089+
@override
3090+
def f(self, z: int) -> str: pass
3091+
3092+
class D(A, B):
3093+
def f(self, z: int) -> str: pass # E: Method "f" is not marked as override but is overriding a method in class "__main__.A"
30743094
[typing fixtures/typing-override.pyi]

0 commit comments

Comments
 (0)