Skip to content

Improve error message for overload overrides w/ swapped variants #5369

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -1349,6 +1349,25 @@ def erase_override(t: Type) -> Type:
self.msg.return_type_incompatible_with_supertype(
name, name_in_super, supertype, node)
emitted_msg = True
elif isinstance(override, Overloaded) and isinstance(original, Overloaded):
# Give a more detailed message in the case where the user is trying to
# override an overload, and the subclass's overload is plausible, except
# that the order of the variants are wrong.
#
# For example, if the parent defines the overload f(int) -> int and f(str) -> str
# (in that order), and if the child swaps the two and does f(str) -> str and
# f(int) -> int
order = []
for child_variant in override.items():
for i, parent_variant in enumerate(original.items()):
if is_subtype(child_variant, parent_variant):
order.append(i)
break

if len(order) == len(original.items()) and order != sorted(order):
self.msg.overload_signature_incompatible_with_supertype(
name, name_in_super, supertype, override, node)
emitted_msg = True

if not emitted_msg:
# Fall back to generic incompatibility message.
Expand Down
10 changes: 10 additions & 0 deletions mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -794,6 +794,16 @@ def incompatible_operator_assignment(self, op: str,
self.fail('Result type of {} incompatible in assignment'.format(op),
context)

def overload_signature_incompatible_with_supertype(
self, name: str, name_in_super: str, supertype: str,
overload: Overloaded, context: Context) -> None:
target = self.override_target(name, name_in_super, supertype)
self.fail('Signature of "{}" incompatible with {}'.format(
name, target), context)

note_template = 'Overload variants must be defined in the same order as they are in "{}"'
self.note(note_template.format(supertype), context)

def signature_incompatible_with_supertype(
self, name: str, name_in_super: str, supertype: str,
context: Context) -> None:
Expand Down
1 change: 1 addition & 0 deletions test-data/unit/check-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -1666,6 +1666,7 @@ class C(A):
def __add__(self, x: 'A') -> 'A': pass
[out]
tmp/foo.pyi:8: error: Signature of "__add__" incompatible with supertype "A"
tmp/foo.pyi:8: note: Overload variants must be defined in the same order as they are in "A"
tmp/foo.pyi:11: error: Overloaded function signature 2 will never be matched: signature 1's parameter type(s) are the same or broader

[case testReverseOperatorMethodArgumentType]
Expand Down
86 changes: 86 additions & 0 deletions test-data/unit/check-overloading.test
Original file line number Diff line number Diff line change
Expand Up @@ -849,6 +849,92 @@ A() + '' # E: No overload variant of "__add__" of "A" matches argument type "str
# N: def __add__(self, A) -> int \
# N: def __add__(self, int) -> int

[case testOverrideOverloadSwapped]
from foo import *
[file foo.pyi]
from typing import overload

class Parent:
@overload
def f(self, x: int) -> int: ...
@overload
def f(self, x: str) -> str: ...
class Child(Parent):
@overload # E: Signature of "f" incompatible with supertype "Parent" \
# N: Overload variants must be defined in the same order as they are in "Parent"
def f(self, x: str) -> str: ...
@overload
def f(self, x: int) -> int: ...

[case testOverrideOverloadSwappedWithExtraVariants]
from foo import *
[file foo.pyi]
from typing import overload

class bool: pass

class Parent:
@overload
def f(self, x: int) -> int: ...
@overload
def f(self, x: str) -> str: ...
class Child1(Parent):
@overload # E: Signature of "f" incompatible with supertype "Parent" \
# N: Overload variants must be defined in the same order as they are in "Parent"
def f(self, x: bool) -> bool: ...
@overload
def f(self, x: str) -> str: ...
@overload
def f(self, x: int) -> int: ...
class Child2(Parent):
@overload # E: Signature of "f" incompatible with supertype "Parent" \
# N: Overload variants must be defined in the same order as they are in "Parent"
def f(self, x: str) -> str: ...
@overload
def f(self, x: bool) -> bool: ...
@overload
def f(self, x: int) -> int: ...
class Child3(Parent):
@overload # E: Signature of "f" incompatible with supertype "Parent" \
# N: Overload variants must be defined in the same order as they are in "Parent"
def f(self, x: str) -> str: ...
@overload
def f(self, x: int) -> int: ...
@overload
def f(self, x: bool) -> bool: ...

[case testOverrideOverloadSwappedWithAdjustedVariants]
from foo import *
[file foo.pyi]
from typing import overload

class A: pass
class B(A): pass
class C(B): pass

class Parent:
@overload
def f(self, x: int) -> int: ...
@overload
def f(self, x: B) -> B: ...
class Child1(Parent):
@overload # E: Signature of "f" incompatible with supertype "Parent" \
# N: Overload variants must be defined in the same order as they are in "Parent"
def f(self, x: A) -> B: ...
@overload
def f(self, x: int) -> int: ...
class Child2(Parent):
@overload # E: Signature of "f" incompatible with supertype "Parent" \
# N: Overload variants must be defined in the same order as they are in "Parent"
def f(self, x: B) -> C: ...
@overload
def f(self, x: int) -> int: ...
class Child3(Parent):
@overload # E: Signature of "f" incompatible with supertype "Parent"
def f(self, x: B) -> A: ...
@overload
def f(self, x: int) -> int: ...

[case testOverrideOverloadedMethodWithMoreGeneralArgumentTypes]
from foo import *
[file foo.pyi]
Expand Down