Skip to content

Commit 58006c5

Browse files
committed
Fix overload resolution with ducktype compatibility
Ignore ducktype compatibility (e.g. int being compatible with float) when determining which overload variant to pick, since ducktype declarations are not honored by isinstance(). Fixes #410.
1 parent a84d3f7 commit 58006c5

File tree

3 files changed

+33
-6
lines changed

3 files changed

+33
-6
lines changed

mypy/checkexpr.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
from mypy.infer import infer_type_arguments, infer_function_type_arguments
2727
from mypy import join
2828
from mypy.expandtype import expand_type, expand_caller_var_args
29-
from mypy.subtypes import is_subtype
29+
from mypy.subtypes import is_subtype, is_more_precise
3030
from mypy import applytype
3131
from mypy import erasetype
3232
from mypy.checkmember import analyse_member_access, type_object_type
@@ -602,15 +602,19 @@ def matches_signature_erased(self, arg_types: List[Type], is_var_arg: bool,
602602
# Fixed function arguments.
603603
func_fixed = callee.max_fixed_args()
604604
for i in range(min(len(arg_types), func_fixed)):
605-
if not is_subtype(self.erase(arg_types[i]),
606-
self.erase(
607-
callee.arg_types[i])):
605+
# Use is_more_precise rather than is_subtype because it ignores ducktype
606+
# declarations. This is important since ducktype declarations are ignored
607+
# when performing runtime type checking.
608+
if not is_more_precise(self.erase(arg_types[i]),
609+
self.erase(
610+
callee.arg_types[i])):
608611
return False
609612
# Function varargs.
610613
if callee.is_var_arg:
611614
for i in range(func_fixed, len(arg_types)):
612-
if not is_subtype(self.erase(arg_types[i]),
613-
self.erase(callee.arg_types[func_fixed])):
615+
# See above for why we use is_more_precise.
616+
if not is_more_precise(self.erase(arg_types[i]),
617+
self.erase(callee.arg_types[func_fixed])):
614618
return False
615619
return True
616620

mypy/subtypes.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,5 +287,8 @@ def is_more_precise(t: Type, s: Type) -> bool:
287287
if isinstance(s, AnyType):
288288
return True
289289
if isinstance(s, Instance):
290+
if isinstance(t, Callable):
291+
# Fall back to subclass check and ignore other properties of the callable.
292+
return is_proper_subtype(t.fallback, s)
290293
return is_proper_subtype(t, s)
291294
return sametypes.is_same_type(t, s)

mypy/test/data/check-overloading.test

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,3 +476,23 @@ main: In class "B":
476476
main, line 12: Signature of "f" incompatible with supertype "A"
477477
main: In class "C":
478478
main, line 17: Signature of "f" incompatible with supertype "A"
479+
480+
[case testOverloadingAndDucktypeCompatibility]
481+
from typing import overload, ducktype, builtinclass
482+
483+
@builtinclass
484+
class A: pass
485+
486+
@builtinclass
487+
@ducktype(A)
488+
class B: pass
489+
490+
@overload
491+
def f(n: B) -> B:
492+
return n
493+
@overload
494+
def f(n: A) -> A:
495+
return n
496+
497+
f(B()) + 'x' # E: Unsupported left operand type for + ("B")
498+
f(A()) + 'x' # E: Unsupported left operand type for + ("A")

0 commit comments

Comments
 (0)