8000 Improve error message for overload overrides w/ swapped variants (#5369) · python/mypy@bfb47e1 · GitHub
[go: up one dir, main page]

Skip to content

Commit bfb47e1

Browse files
Michael0x2ailevkivskyi
authored andcommitted
Improve error message for overload overrides w/ swapped variants (#5369)
This commit is intended to resolve #1270. Currently, running mypy on the following program: from typing import overload class Parent: @overload def foo(self, x: A) -> A: ... @overload def foo(self, x: B) -> B: ... class Child(Parent): @overload def foo(self, x: B) -> B: ... @overload def foo(self, x: A) -> A: ... ...will make mypy report the following error -- it considers the fact that we've swapped the order of the two variants to be unsafe: test.pyi:10: error: Signature of "foo" incompatible with supertype "Parent" This error message can be confusing for some users who may not be aware that the order in which your overloads are defined can sometimes matter/is something mypy relies on. This commit modifies the error message to the following to try and make this more clear: test.pyi:10: error: Signature of "foo" incompatible with supertype "Parent" test.pyi:10: note: Overload variants must be defined in the same order as they are in "Parent"
1 parent 132dfa2 commit bfb47e1

File tree

4 files changed

+116
-0
lines changed

4 files changed

+116
-0
lines changed

mypy/checker.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1349,6 +1349,25 @@ def erase_override(t: Type) -> Type:
13491349
self.msg.return_type_incompatible_with_supertype(
13501350
name, name_in_super, supertype, node)
13511351
emitted_msg = True
1352+
elif isinstance(override, Overloaded) and isinstance(original, Overloaded):
1353+
# Give a more detailed message in the case where the user is trying to
1354+
# override an overload, and the subclass's overload is plausible, except
1355+
# that the order of the variants are wrong.
1356+
#
1357+
# For example, if the parent defines the overload f(int) -> int and f(str) -> str
1358+
# (in that order), and if the child swaps the two and does f(str) -> str and
1359+
# f(int) -> int
1360+
order = []
1361+
for child_variant in override.items():
1362+
for i, parent_variant in enumerate(original.items()):
1363+
if is_subtype(child_variant, parent_variant):
1364+
order.append(i)
1365+
break
1366+
1367+
if len(order) == len(original.items()) and order != sorted(order):
1368+
self.msg.overload_signature_incompatible_with_supertype(
1369+
name, name_in_super, supertype, override, node)
1370+
emitted_msg = True
13521371

13531372
if not emitted_msg:
13541373
# Fall back to generic incompatibility message.

mypy/messages.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -794,6 +794,16 @@ def incompatible_operator_assignment(self, op: str,
794794
self.fail('Result type of {} incompatible in assignment'.format(op),
795795
context)
796796

797+
def overload_signature_incompatible_with_supertype(
798+
self, name: str, name_in_super: str, supertype: str,
799+
overload: Overloaded, context: Context) -> None:
800+
target = self.override_target(name, name_in_super, supertype)
801+
self.fail('Signature of "{}" incompatible with {}'.format(
802+
name, target), context)
803+
804+
note_template = 'Overload variants must be defined in the same order as they are in "{}"'
805+
self.note(note_template.format(supertype), context)
806+
797807
def signature_incompatible_with_supertype(
798808
self, name: str, name_in_super: str, supertype: str,
799809
context: Context) -> None:

test-data/unit/check-classes.test

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1666,6 +1666,7 @@ class C(A):
16661666
def __add__(self, x: 'A') -> 'A': pass
16671667
[out]
16681668
tmp/foo.pyi:8: error: Signature of "__add__" incompatible with supertype "A"
1669+
tmp/foo.pyi:8: note: Overload variants must be defined in the same order as they are in "A"
16691670
tmp/foo.pyi:11: error: Overloaded function signature 2 will never be matched: signature 1's parameter type(s) are the same or broader
16701671

16711672
[case testReverseOperatorMethodArgumentType]

test-data/unit/check-overloading.test

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -849,6 +849,92 @@ A() + '' # E: No overload variant of "__add__" of "A" matches argument type "str
849849
# N: def __add__(self, A) -> int \
850850
# N: def __add__(self, int) -> int
851851

852+
[case testOverrideOverloadSwapped]
853+
from foo import *
854+
[file foo.pyi]
855+
from typing import overload
856+
857+
class Parent:
858+
@overload
859+
def f(self, x: int) -> int: ...
860+
@overload
861+
def f(self, x: str) -> str: ...
862+
class Child(Parent):
863+
@overload # E: Signature of "f" incompatible with supertype "Parent" \
864+
# N: Overload variants must be defined in the same order as they are in "Parent"
865+
def f(self, x: str) -> str: ...
866+
@overload
867+
def f(self, x: int) -> int: ...
868+
869+
[case testOverrideOverloadSwappedWithExtraVariants]
870+
from foo import *
871+
[file foo.pyi]
872+
from typing import overload
873+
874+
class bool: pass
875+
876+
class Parent:
877+
@overload
878+
def f(self, x: int) -> int: ...
879+
@overload
880+
def f(self, x: str) -> str: ...
881+
class Child1(Parent):
882+
@overload # E: Signature of "f" incompatible with supertype "Parent" \
883+
# N: Overload variants must be defined in the same order as they are in "Parent"
884+
def f(self, x: bool) -> bool: ...
885+
@overload
886+
def f(self, x: str) -> str: ...
887+
@overload
888+
def f(self, x: int) -> int: ...
889+
class Child2(Parent):
890+
@overload # E: Signature of "f" incompatible with supertype "Parent" \
891+
# N: Overload variants must be defined in the same order as they are in "Parent"
892+
def f(self, x: str) -> str: ...
893+
@overload
894+
def f(self, x: bool) -> bool: ...
895+
@overload
896+
def f(self, x: int) -> int: ...
897+
class Child3(Parent):
898+
@overload # E: Signature of "f" incompatible with supertype "Parent" \
899+
# N: Overload variants must be defined in the same order as they are in "Parent"
900+
def f(self, x: str) -> str: ...
901+
@overload
902+
def f(self, x: int) -> int: ...
903+
@overload
904+
def f(self, x: bool) -> bool: ...
905+
906+
[case testOverrideOverloadSwappedWithAdjustedVariants]
907+
from foo import *
908+
[file foo.pyi]
909+
from typing import overload
910+
911+
class A: pass
912+
class B(A): pass
913+
class C(B): pass
914+
915+
class Parent:
916+
@overload
917+
def f(self, x: int) -> int: ...
918+
@overload
919+
def f(self, x: B) -> B: ...
920+
class Child1(Parent):
921+
@overload # E: Signature of "f" incompatible with supertype "Parent" \
922+
# N: Overload variants must be defined in the same order as they are in "Parent"
923+
def f(self, x: A) -> B: ...
924+
@overload
925+
def f(self, x: int) -> int: ...
926+
class Child2(Parent):
927+
@overload # E: Signature of "f" incompatible with supertype "Parent" \
928+
# N: Overload variants must be defined in the same order as they are in "Parent"
929+
def f(self, x: B) -> C: ...
930+
@overload
931+
def f(self, x: int) -> int: ...
932+
class Child3(Parent):
933+
@overload # E: Signature of "f" incompatible with supertype "Parent"
934+
def f(self, x: B) -> A: ...
935+
@overload
936+
def f(self, x: int) -> int: ...
937+
852938
[case testOverrideOverloadedMethodWithMoreGeneralArgumentTypes]
853939
from foo import *
854940
[file foo.pyi]

0 commit comments

Comments
 (0)
0