Skip to content

Commit 807da26

Browse files
authored
Fix and optimise overload compatibility checking (#14018)
Discovered as part of #14017
1 parent 331b170 commit 807da26

File tree

3 files changed

+96
-45
lines changed

3 files changed

+96
-45
lines changed

mypy/subtypes.py

+23-31
Original file line numberDiff line numberDiff line change
@@ -823,9 +823,8 @@ def visit_overloaded(self, left: Overloaded) -> bool:
823823
# Ensure each overload in the right side (the supertype) is accounted for.
824824
previous_match_left_index = -1
825825
matched_overloads = set()
826-
possible_invalid_overloads = set()
827826

828-
for right_index, right_item in enumerate(right.items):
827+
for right_item in right.items:
829828
found_match = False
830829

831830
for left_index, left_item in enumerate(left.items):
@@ -834,43 +833,36 @@ def visit_overloaded(self, left: Overloaded) -> bool:
834833
# Order matters: we need to make sure that the index of
835834
# this item is at least the index of the previous one.
836835
if subtype_match and previous_match_left_index <= left_index:
837-
if not found_match:
838-
# Update the index of the previous match.
839-
previous_match_left_index = left_index
840-
found_match = True
841-
matched_overloads.add(left_item)
842-
possible_invalid_overloads.discard(left_item)
836+
previous_match_left_index = left_index
837+
found_match = True
838+
matched_overloads.add(left_index)
839+
break
843840
else:
844841
# If this one overlaps with the supertype in any way, but it wasn't
845842
# an exact match, then it's a potential error.
846843
strict_concat = self.options.strict_concatenate if self.options else True
847-
if is_callable_compatible(
848-
left_item,
849-
right_item,
850-
is_compat=self._is_subtype,
851-
ignore_return=True,
852-
ignore_pos_arg_names=self.subtype_context.ignore_pos_arg_names,
853-
strict_concatenate=strict_concat,
854-
) or is_callable_compatible(
855-
right_item,
856-
left_item,
857-
is_compat=self._is_subtype,
858-
ignore_return=True,
859-
ignore_pos_arg_names=self.subtype_context.ignore_pos_arg_names,
860-
strict_concatenate=strict_concat,
844+
if left_index not in matched_overloads and (
845+
is_callable_compatible(
846+
left_item,
847+
right_item,
848+
is_compat=self._is_subtype,
849+
ignore_return=True,
850+
ignore_pos_arg_names=self.subtype_context.ignore_pos_arg_names,
851+
strict_concatenate=strict_concat,
852+
)
853+
or is_callable_compatible(
854+
right_item,
855+
left_item,
856+
is_compat=self._is_subtype,
857+
ignore_return=True,
858+
ignore_pos_arg_names=self.subtype_context.ignore_pos_arg_names,
859+
strict_concatenate=strict_concat,
860+
)
861861
):
862-
# If this is an overload that's already been matched, there's no
863-
# problem.
864-
if left_item not in matched_overloads:
865-
possible_invalid_overloads.add(left_item)
862+
return False
866863

867864
if not found_match:
868865
return False
869-
870-
if possible_invalid_overloads:
871-
# There were potentially invalid overloads that were never matched to the
872-
# supertype.
873-
return False
874866
return True
875867
elif isinstance(right, UnboundType):
876868
return True

test-data/unit/check-classes.test

+45-14
Original file line numberDiff line numberDiff line change
@@ -3872,28 +3872,59 @@ class Super:
38723872
def foo(self, a: C) -> C: pass
38733873

38743874
class Sub(Super):
3875-
@overload # Fail
3875+
@overload
38763876
def foo(self, a: A) -> A: pass
38773877
@overload
38783878
def foo(self, a: B) -> C: pass # Fail
38793879
@overload
38803880
def foo(self, a: C) -> C: pass
3881+
3882+
class Sub2(Super):
3883+
@overload
3884+
def foo(self, a: B) -> C: pass # Fail
3885+
@overload
3886+
def foo(self, a: A) -> A: pass
3887+
@overload
3888+
def foo(self, a: C) -> C: pass
3889+
3890+
class Sub3(Super):
3891+
@overload
3892+
def foo(self, a: A) -> int: pass
3893+
@overload
3894+
def foo(self, a: A) -> A: pass
3895+
@overload
3896+
def foo(self, a: C) -> C: pass
38813897
[builtins fixtures/classmethod.pyi]
38823898
[out]
3883-
tmp/foo.pyi:16: error: Signature of "foo" incompatible with supertype "Super"
3884-
tmp/foo.pyi:16: note: Superclass:
3885-
tmp/foo.pyi:16: note: @overload
3886-
tmp/foo.pyi:16: note: def foo(self, a: A) -> A
3887-
tmp/foo.pyi:16: note: @overload
3888-
tmp/foo.pyi:16: note: def foo(self, a: C) -> C
3889-
tmp/foo.pyi:16: note: Subclass:
3890-
tmp/foo.pyi:16: note: @overload
3891-
tmp/foo.pyi:16: note: def foo(self, a: A) -> A
3892-
tmp/foo.pyi:16: note: @overload
3893-
tmp/foo.pyi:16: note: def foo(self, a: B) -> C
3894-
tmp/foo.pyi:16: note: @overload
3895-
tmp/foo.pyi:16: note: def foo(self, a: C) -> C
38963899
tmp/foo.pyi:19: error: Overloaded function signature 2 will never be matched: signature 1's parameter type(s) are the same or broader
3900+
tmp/foo.pyi:24: error: Signature of "foo" incompatible with supertype "Super"
3901+
tmp/foo.pyi:24: note: Superclass:
3902+
tmp/foo.pyi:24: note: @overload
3903+
tmp/foo.pyi:24: note: def foo(self, a: A) -> A
3904+
tmp/foo.pyi:24: note: @overload
3905+
tmp/foo.pyi:24: note: def foo(self, a: C) -> C
3906+
tmp/foo.pyi:24: note: Subclass:
3907+
tmp/foo.pyi:24: note: @overload
3908+
tmp/foo.pyi:24: note: def foo(self, a: B) -> C
3909+
tmp/foo.pyi:24: note: @overload
3910+
tmp/foo.pyi:24: note: def foo(self, a: A) -> A
3911+
tmp/foo.pyi:24: note: @overload
3912+
tmp/foo.pyi:24: note: def foo(self, a: C) -> C
3913+
tmp/foo.pyi:25: error: Overloaded function signatures 1 and 2 overlap with incompatible return types
3914+
tmp/foo.pyi:32: error: Signature of "foo" incompatible with supertype "Super"
3915+
tmp/foo.pyi:32: note: Superclass:
3916+
tmp/foo.pyi:32: note: @overload
3917+
tmp/foo.pyi:32: note: def foo(self, a: A) -> A
3918+
tmp/foo.pyi:32: note: @overload
3919+
tmp/foo.pyi:32: note: def foo(self, a: C) -> C
3920+
tmp/foo.pyi:32: note: Subclass:
3921+
tmp/foo.pyi:32: note: @overload
3922+
tmp/foo.pyi:32: note: def foo(self, a: A) -> int
3923+
tmp/foo.pyi:32: note: @overload
3924+
tmp/foo.pyi:32: note: def foo(self, a: A) -> A
3925+
tmp/foo.pyi:32: note: @overload
3926+
tmp/foo.pyi:32: note: def foo(self, a: C) -> C
3927+
tmp/foo.pyi:35: error: Overloaded function signature 2 will never be matched: signature 1's parameter type(s) are the same or broader
38973928

38983929
[case testTypeTypeOverlapsWithObjectAndType]
38993930
from foo import *

test-data/unit/check-selftype.test

+28
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,34 @@ reveal_type(cast(A, C()).copy()) # N: Revealed type is "__main__.A"
128128

129129
[builtins fixtures/bool.pyi]
130130

131+
[case testSelfTypeOverrideCompatibility]
132+
from typing import overload, TypeVar, Generic
133+
134+
T = TypeVar("T")
135+
136+
class A(Generic[T]):
137+
@overload
138+
def f(self: A[int]) -> int: ...
139+
@overload
140+
def f(self: A[str]) -> str: ...
141+
def f(self): ...
142+
143+
class B(A[T]):
144+
@overload
145+
def f(self: A[int]) -> int: ...
146+
@overload
147+
def f(self: A[str]) -> str: ...
148+
def f(self): ...
149+
150+
class B2(A[T]):
151+
@overload
152+
def f(self: A[int]) -> int: ...
153+
@overload
154+
def f(self: A[str]) -> str: ...
155+
@overload
156+
def f(self: A[bytes]) -> bytes: ...
157+
def f(self): ...
158+
131159
[case testSelfTypeSuper]
132160
from typing import TypeVar, cast
133161

0 commit comments

Comments
 (0)