Skip to content

Commit

Permalink
Include method base class in error message
Browse files Browse the repository at this point in the history
  • Loading branch information
cdce8p committed Jul 5, 2023
1 parent 537e794 commit 6e40627
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 20 deletions.
39 changes: 24 additions & 15 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -643,15 +643,17 @@ def _visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None:
if defn.impl:
defn.impl.accept(self)
if defn.info:
found_base_method = self.check_method_override(defn)
if defn.is_explicit_override and found_base_method is False:
method_base_class = self.check_method_override(defn)
if defn.is_explicit_override and not method_base_class:
self.msg.no_overridable_method(defn.name, defn)
elif (
found_base_method
method_base_class
and self.options.strict_override_decorator
and not defn.is_explicit_override
):
self.msg.override_decorator_missing(defn.name, defn.impl or defn)
self.msg.override_decorator_missing(
defn.name, method_base_class.fullname, defn.impl or defn
)
self.check_inplace_operator_method(defn)
if not defn.is_property:
self.check_overlapping_overloads(defn)
Expand Down Expand Up @@ -978,13 +980,15 @@ def _visit_func_def(self, defn: FuncDef) -> None:
# overload, the legality of the override has already
# been typechecked, and decorated methods will be
# checked when the decorator is.
found_base_method = self.check_method_override(defn)
method_base_class = self.check_method_override(defn)
if (
found_base_method
method_base_class
and self.options.strict_override_decorator
and defn.name not in ("__init__", "__new__")
):
self.msg.override_decorator_missing(defn.name, defn)
self.msg.override_decorator_missing(
defn.name, method_base_class.fullname, defn
)
self.check_inplace_operator_method(defn)
if defn.original_def:
# Override previous definition.
Expand Down Expand Up @@ -1826,23 +1830,26 @@ def expand_typevars(
else:
return [(defn, typ)]

def check_method_override(self, defn: FuncDef | OverloadedFuncDef | Decorator) -> bool | None:
def check_method_override(
self, defn: FuncDef | OverloadedFuncDef | Decorator
) -> TypeInfo | None:
"""Check if function definition is compatible with base classes.
This may defer the method if a signature is not available in at least one base class.
Return ``None`` if that happens.
Return the first base class if an attribute with the method name was found within it.
Return ``True`` if an attribute with the method name was found in the base class.
"""
# Check against definitions in base classes.
found_base_method = False
for base in defn.info.mro[1:]:
result = self.check_method_or_accessor_override_for_base(defn, base)
if result is None:
# Node was deferred, we will have another attempt later.
return None
found_base_method |= result
return found_base_method
if result:
return base
return None

def check_method_or_accessor_override_for_base(
self, defn: FuncDef | OverloadedFuncDef | Decorator, base: TypeInfo
Expand Down Expand Up @@ -4752,15 +4759,17 @@ def visit_decorator(self, e: Decorator) -> None:
self.check_incompatible_property_override(e)
# For overloaded functions we already checked override for overload as a whole.
if e.func.info and not e.func.is_dynamic() and not e.is_overload:
found_base_method = self.check_method_override(e)
if e.func.is_explicit_override and found_base_method is False:
method_base_class = self.check_method_override(e)
if e.func.is_explicit_override and not method_base_class:
self.msg.no_overridable_method(e.func.name, e.func)
elif (
found_base_method
method_base_class
and self.options.strict_override_decorator
and not e.func.is_explicit_override
):
self.msg.override_decorator_missing(e.func.name, e.func)
self.msg.override_decorator_missing(
e.func.name, method_base_class.fullname, e.func
)

if e.func.info and e.func.name in ("__init__", "__new__"):
if e.type and not isinstance(get_proper_type(e.type), (FunctionLike, AnyType)):
Expand Down
4 changes: 2 additions & 2 deletions mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -1525,10 +1525,10 @@ def no_overridable_method(self, name: str, context: Context) -> None:
context,
)

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

Expand Down
26 changes: 23 additions & 3 deletions test-data/unit/check-functions.test
Original file line number Diff line number Diff line change
Expand Up @@ -3013,7 +3013,10 @@ class B(A):
def f(self, y: int) -> str: pass

class C(A):
def f(self, y: int) -> str: pass # E: Method "f" is not marked as override but is overriding a method in a base class
def f(self, y: int) -> str: pass # E: Method "f" is not marked as override but is overriding a method in class "__main__.A"

class D(B):
def f(self, y: int) -> str: pass # E: Method "f" is not marked as override but is overriding a method in class "__main__.B"
[typing fixtures/typing-override.pyi]

[case requireExplicitOverrideSpecialMethod]
Expand Down Expand Up @@ -3042,7 +3045,7 @@ class B(A):

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

Expand Down Expand Up @@ -3070,5 +3073,22 @@ class C(A):
def f(self, y: int) -> str: ...
@overload
def f(self, y: str) -> str: ...
def f(self, y): pass # E: Method "f" is not marked as override but is overriding a method in a base class
def f(self, y): pass # E: Method "f" is not marked as override but is overriding a method in class "__main__.A"
[typing fixtures/typing-override.pyi]

[case requireExplicitOverrideMultipleInheritance]
# flags: --strict-override-decorator --python-version 3.12
from typing import override

class A:
def f(self, x: int) -> str: pass
class B:
def f(self, y: int) -> str: pass

class C(A, B):
@override
def f(self, z: int) -> str: pass

class D(A, B):
def f(self, z: int) -> str: pass # E: Method "f" is not marked as override but is overriding a method in class "__main__.A"
[typing fixtures/typing-override.pyi]

0 comments on commit 6e40627

Please sign in to comment.