Skip to content

Commit 2d604fa

Browse files
committed
Make overload impl checks correctly handle TypeVars and untyped impls
This pull request fixes #4619 as well as an unrelated bug where if the overload implementation was untyped, we only check to see if there are unsafe overlaps between the *first* overload alternative and the rest.
1 parent a55635d commit 2d604fa

File tree

4 files changed

+150
-65
lines changed

4 files changed

+150
-65
lines changed

mypy/checker.py

Lines changed: 41 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
restrict_subtype_away, is_subtype_ignoring_tvars, is_callable_compatible,
4343
unify_generic_callable, find_member
4444
)
45+
from mypy.constraints import SUPERTYPE_OF
4546
from mypy.maptype import map_instance_to_supertype
4647
from mypy.typevars import fill_typevars, has_no_typevars
4748
from mypy.semanal import set_callable_name, refers_to_fullname, calculate_mro
@@ -414,6 +415,23 @@ def _visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None:
414415
def check_overlapping_overloads(self, defn: OverloadedFuncDef) -> None:
415416
# At this point we should have set the impl already, and all remaining
416417
# items are decorators
418+
419+
# Compute some info about the implementation (if it exists) for use below
420+
impl_type = None # type: Optional[CallableType]
421+
if defn.impl:
422+
if isinstance(defn.impl, FuncDef):
423+
inner_type = defn.impl.type
424+
elif isinstance(defn.impl, Decorator):
425+
inner_type = defn.impl.var.type
426+
else:
427+
assert False, "Impl isn't the right type"
428+
429+
# This can happen if we've got an overload with a different
430+
# decorator or if the implementation is untyped -- we gave up on the types.
431+
if inner_type is not None and not isinstance(inner_type, AnyType):
432+
assert isinstance(inner_type, CallableType)
433+
impl_type = inner_type
434+
417435
is_descriptor_get = defn.info is not None and defn.name() == "__get__"
418436
for i, item in enumerate(defn.items):
419437
# TODO overloads involving decorators
@@ -451,43 +469,36 @@ def check_overlapping_overloads(self, defn: OverloadedFuncDef) -> None:
451469
self.msg.overloaded_signatures_overlap(
452470
i + 1, i + j + 2, item.func)
453471

454-
if defn.impl:
455-
if isinstance(defn.impl, FuncDef):
456-
impl_type = defn.impl.type
457-
elif isinstance(defn.impl, Decorator):
458-
impl_type = defn.impl.var.type
459-
else:
460-
assert False, "Impl isn't the right type"
472+
if impl_type is not None:
473+
assert defn.impl is not None
461474

462-
# This can happen if we've got an overload with a different
463-
# decorator too -- we gave up on the types.
464-
if impl_type is None or isinstance(impl_type, AnyType):
465-
return
466-
assert isinstance(impl_type, CallableType)
475+
# We perform a unification step that's very similar to what
476+
# 'is_callable_compatible' would have done if we had set
477+
# 'unify_generics' to True -- the only difference is that
478+
# we check and see if the impl_type's return value is a
479+
# *supertype* of the overload alternative, not a *subtype*.
480+
#
481+
# This is to match the direction the implementation's return
482+
# needs to be compatible in.
483+
if impl_type.variables:
484+
impl = unify_generic_callable(impl_type, sig1,
485+
ignore_return=False,
486+
return_constraint_direction=SUPERTYPE_OF)
487+
if impl is None:
488+
self.msg.overloaded_signatures_typevar_specific(i + 1, defn.impl)
489+
continue
490+
else:
491+
impl = impl_type
467492

468493
# Is the overload alternative's arguments subtypes of the implementation's?
469-
if not is_callable_compatible(impl_type, sig1,
494+
if not is_callable_compatible(impl, sig1,
470495
is_compat=is_subtype,
471-
ignore_return=True):
496+
ignore_return=True,
497+
unify_generics=False):
472498
self.msg.overloaded_signatures_arg_specific(i + 1, defn.impl)
473499

474-
# Repeat the same unification process 'is_callable_compatible'
475-
# internally performs so we can examine the return type separately.
476-
if impl_type.variables:
477-
# Note: we set 'ignore_return=True' because 'unify_generic_callable'
478-
# normally checks the arguments and return types with differing variance.
479-
#
480-
# This is normally what we want, but for checking the validity of overload
481-
# implementations, we actually want to use the same variance for both.
482-
#
483-
# TODO: Patch 'is_callable_compatible' and 'unify_generic_callable'?
484-
# somehow so we can customize the variance in all different sorts
485-
# of ways? This would let us infer more constraints, letting us
486-
# infer more precise types.
487-
impl_type = unify_generic_callable(impl_type, sig1, ignore_return=True)
488-
489500
# Is the overload alternative's return type a subtype of the implementation's?
490-
if impl_type is not None and not is_subtype(sig1.ret_type, impl_type.ret_type):
501+
if not is_subtype(sig1.ret_type, impl.ret_type):
491502
self.msg.overloaded_signatures_ret_specific(i + 1, defn.impl)
492503

493504
# Here's the scoop about generators and coroutines.

mypy/messages.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -961,13 +961,17 @@ def overloaded_signature_will_never_match(self, index1: int, index2: int,
961961
index2=index2),
962962
context)
963963

964-
def overloaded_signatures_arg_specific(self, index1: int, context: Context) -> None:
964+
def overloaded_signatures_typevar_specific(self, index: int, context: Context) -> None:
965+
self.fail('Overloaded function implementation cannot satisfy signature {} '.format(index) +
966+
'due to inconsistencies in how they use type variables', context)
967+
968+
def overloaded_signatures_arg_specific(self, index: int, context: Context) -> None:
965969
self.fail('Overloaded function implementation does not accept all possible arguments '
966-
'of signature {}'.format(index1), context)
970+
'of signature {}'.format(index), context)
967971

968-
def overloaded_signatures_ret_specific(self, index1: int, context: Context) -> None:
972+
def overloaded_signatures_ret_specific(self, index: int, context: Context) -> None:
969973
self.fail('Overloaded function implementation cannot produce return type '
970-
'of signature {}'.format(index1), context)
974+
'of signature {}'.format(index), context)
971975

972976
def operator_method_signatures_overlap(
973977
self, reverse_class: TypeInfo, reverse_method: str, forward_class: Type,

mypy/subtypes.py

Lines changed: 42 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,8 @@ def visit_type_var(self, left: TypeVarType) -> bool:
198198
right = self.right
199199
if isinstance(right, TypeVarType) and left.id == right.id:
200200
return True
201+
if left.values and is_subtype(UnionType.make_simplified_union(left.values), right):
202+
return True
201203
return is_subtype(left.upper_bound, self.right)
202204

203205
def visit_callable_type(self, left: CallableType) -> bool:
@@ -578,7 +580,8 @@ def is_callable_compatible(left: CallableType, right: CallableType,
578580
ignore_return: bool = False,
579581
ignore_pos_arg_names: bool = False,
580582
check_args_covariantly: bool = False,
581-
allow_partial_overlap: bool = False) -> bool:
583+
allow_partial_overlap: bool = False,
584+
unify_generics: bool = True) -> bool:
582585
"""Is the left compatible with the right, using the provided compatibility check?
583586
584587
is_compat:
@@ -666,6 +669,11 @@ def g(x: int) -> int: ...
666669
667670
If the 'some_check' function is also symmetric, the two calls would be equivalent
668671
whether or not we check the args covariantly.
672+
673+
unify_generics:
674+
If this parameter is set to False, we do not attempt to unify away TypeVars before
675+
checking the callable. This option should be set to False only if you want to externally
676+
handle generics in some custom way.
669677
"""
670678
if is_compat_return is None:
671679
is_compat_return = is_compat
@@ -678,34 +686,35 @@ def g(x: int) -> int: ...
678686
if right.is_type_obj() and not left.is_type_obj():
679687
return False
680688

681-
# A callable L is a subtype of a generic callable R if L is a
682-
# subtype of every type obtained from R by substituting types for
683-
# the variables of R. We can check this by simply leaving the
684-
# generic variables of R as type variables, effectively varying
685-
# over all possible values.
686-
687-
# It's okay even if these variables share ids with generic
688-
# type variables of L, because generating and solving
689-
# constraints for the variables of L to make L a subtype of R
690-
# (below) treats type variables on the two sides as independent.
691-
if left.variables:
692-
# Apply generic type variables away in left via type inference.
693-
unified = unify_generic_callable(left, right, ignore_return=ignore_return)
694-
if unified is None:
695-
return False
696-
else:
697-
left = unified
689+
if unify_generics:
690+
# A callable L is a subtype of a generic callable R if L is a
691+
# subtype of every type obtained from R by substituting types for
692+
# the variables of R. We can check this by simply leaving the
693+
# generic variables of R as type variables, effectively varying
694+
# over all possible values.
695+
696+
# It's okay even if these variables share ids with generic
697+
# type variables of L, because generating and solving
698+
# constraints for the variables of L to make L a subtype of R
699+
# (below) treats type variables on the two sides as independent.
700+
if left.variables:
701+
# Apply generic type variables away in left via type inference.
702+
unified = unify_generic_callable(left, right, ignore_return=ignore_return)
703+
if unified is None:
704+
return False
705+
else:
706+
left = unified
698707

699-
# If we allow partial overlaps, we don't need to leave R generic:
700-
# if we can find even just a single typevar assignment which
701-
# would make these callables compatible, we should return True.
708+
# If we allow partial overlaps, we don't need to leave R generic:
709+
# if we can find even just a single typevar assignment which
710+
# would make these callables compatible, we should return True.
702711

703-
# So, we repeat the above checks in the opposite direction. This also
704-
# lets us preserve the 'symmetry' property of allow_partial_overlap.
705-
if allow_partial_overlap and right.variables:
706-
unified = unify_generic_callable(right, left, ignore_return=ignore_return)
707-
if unified is not None:
708-
right = unified
712+
# So, we repeat the above checks in the opposite direction. This also
713+
# lets us preserve the 'symmetry' property of allow_partial_overlap.
714+
if allow_partial_overlap and right.variables:
715+
unified = unify_generic_callable(right, left, ignore_return=ignore_return)
716+
if unified is not None:
717+
right = unified
709718

710719
# Check return types.
711720
if not ignore_return and not is_compat_return(left.ret_type, right.ret_type):
@@ -901,7 +910,9 @@ def new_is_compat(left: Type, right: Type) -> bool:
901910

902911

903912
def unify_generic_callable(type: CallableType, target: CallableType,
904-
ignore_return: bool) -> Optional[CallableType]:
913+
ignore_return: bool,
914+
return_constraint_direction: int = mypy.constraints.SUBTYPE_OF,
915+
) -> Optional[CallableType]:
905916
"""Try to unify a generic callable type with another callable type.
906917
907918
Return unified CallableType if successful; otherwise, return None.
@@ -914,7 +925,7 @@ def unify_generic_callable(type: CallableType, target: CallableType,
914925
constraints.extend(c)
915926
if not ignore_return:
916927
c = mypy.constraints.infer_constraints(
917-
type.ret_type, target.ret_type, mypy.constraints.SUBTYPE_OF)
928+
type.ret_type, target.ret_type, return_constraint_direction)
918929
constraints.extend(c)
919930
type_var_ids = [tvar.id for tvar in type.variables]
920931
inferred_vars = mypy.solve.solve_constraints(type_var_ids, constraints)
@@ -1036,7 +1047,8 @@ def check_argument(leftarg: Type, rightarg: Type, variance: int) -> bool:
10361047
def visit_type_var(self, left: TypeVarType) -> bool:
10371048
if isinstance(self.right, TypeVarType) and left.id == self.right.id:
10381049
return True
1039-
# TODO: Value restrictions
1050+
if left.values and is_subtype(UnionType.make_simplified_union(left.values), self.right):
1051+
return True
10401052
return is_proper_subtype(left.upper_bound, self.right)
10411053

10421054
def visit_callable_type(self, left: CallableType) -> bool:

test-data/unit/check-overloading.test

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,17 @@ class A: pass
204204
class B: pass
205205
[builtins fixtures/isinstance.pyi]
206206

207+
[case testTypeCheckOverloadWithUntypedImplAndMultipleVariants]
208+
from typing import overload
209+
210+
@overload
211+
def f(x: int) -> str: ...
212+
@overload
213+
def f(x: str) -> int: ... # E: Overloaded function signatures 2 and 3 overlap with incompatible return types
214+
@overload
215+
def f(x: object) -> str: ...
216+
def f(x): ...
217+
207218
[case testTypeCheckOverloadWithImplTooSpecificArg]
208219
from typing import overload, Any
209220

@@ -284,14 +295,61 @@ def f(x: 'A') -> 'A': ...
284295
@overload
285296
def f(x: 'B') -> 'B': ...
286297

287-
def f(x: Union[T, B]) -> T: # E: Overloaded function implementation cannot produce return type of signature 2
298+
def f(x: Union[T, B]) -> T: # E: Overloaded function implementation cannot satisfy signature 2 due to inconsistencies in how they use type variables
288299
...
289300

290301
reveal_type(f(A())) # E: Revealed type is '__main__.A'
291302
reveal_type(f(B())) # E: Revealed type is '__main__.B'
292303

293304
[builtins fixtures/isinstance.pyi]
294305

306+
[case testTypeCheckOverloadImplementationTypeVarWithValueRestriction]
307+
from typing import overload, TypeVar, Union
308+
309+
class A: pass
310+
class B: pass
311+
class C: pass
312+
313+
T = TypeVar('T', A, B)
314+
315+
@overload
316+
def foo(x: T) -> T: ...
317+
@overload
318+
def foo(x: C) -> int: ...
319+
def foo(x: Union[A, B, C]) -> Union[A, B, int]:
320+
if isinstance(x, C):
321+
return 3
322+
else:
323+
return x
324+
325+
@overload
326+
def bar(x: T) -> T: ...
327+
@overload
328+
def bar(x: C) -> int: ...
329+
def bar(x: Union[T, C]) -> Union[T, int]:
330+
if isinstance(x, C):
331+
return 3
332+
else:
333+
return x
334+
335+
[builtins fixtures/isinstancelist.pyi]
336+
337+
[case testTypeCheckOverloadImplementationTypeVarDifferingUsage]
338+
from typing import overload, Union, List, TypeVar
339+
340+
T = TypeVar('T')
341+
342+
@overload
343+
def foo(t: List[T]) -> T: ...
344+
@overload
345+
def foo(t: T) -> T: ...
346+
def foo(t: Union[List[T], T]) -> T:
347+
if isinstance(t, list):
348+
return t[0]
349+
else:
350+
return t
351+
[builtins fixtures/isinstancelist.pyi]
352+
295353
[case testTypeCheckOverloadedFunctionBody]
296354
from foo import *
297355
[file foo.pyi]

0 commit comments

Comments
 (0)