Skip to content

Commit 75d4c79

Browse files
committed
Overhaul overload semantics, remove erasure, add union math
This pull request: 1. Modifies how mypy handles overloads to match the proposal I made in the typing repo. 2. Starts removing type erasure from overload checks. 3. Adds support for basic union math. 4. Makes overloads respect keyword-only args This pull request does NOT implement the following: 1. Special-casing descriptors 2. Improving how operator methods are handled: I've tweaked one or two methods to take advantage of some of the changes I made in this PR, but stayed away from making any drastic changes for now. 3. Detecting partially overlapping argument types -- e.g. code like: @overload def f(x: Union[A, B]) -> str: ... @overload def f(x: Union[B, C]) -> int: ... @overload def g(x: Tuple[int, ...]) -> str: ... @overload def g(x: Tuple[int, int]) -> int: ... 4. Detecting overlapping "argument counts". For example, we should flag the following as an error since the first alternative could potentially overlap with the second. @overload def f(*args: int) -> int: ... @overload def f(x: int, y: int, z: int) -> str: ... 5. The "is-more-precise" relation. It's working in most normal cases but still contains a few bugs, mostly relating to type vars. For example, this currently isn't being flagged as an error: class Wrapper(Generic[T]): @overload def f(self, x: int) -> int: ... # No error? @overload def f(self, x: T) -> str: ... (This PR does the right thing if 'T' isn't bound to a containing class though:) class Wrapper: @overload def f(self, x: int, y: int) -> int: ... # Error @overload def f(self, x: T, y: T) -> str: ... Currently, what I'm doing is using the existing `is_more_precise` method, which calls `is_proper_subtype`. I think i'll either need to rewrite `is_more_precise` to do what I want with typevars or find a way of forcing the two methods to unify their typevars before running the `is_proper_subtype` check. The plan is to address these 5 TODOs in future pull requests. Items 1 and 2 are basically orthogonal to the overloads overhaul; items 3, 4, and 5 basically boil down to finding ways to teach mypy to detect if one thing is *potentially* compatible with another. For example, mypy contains code to tell if one type is *definitely* a subtype of another; fixing items 3 and 5 involve writing code to check if a type is *potentially* a subtype of another.
1 parent d6566be commit 75d4c79

11 files changed

+846
-182
lines changed

mypy/checker.py

Lines changed: 122 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
from mypy import messages
4040
from mypy.subtypes import (
4141
is_subtype, is_equivalent, is_proper_subtype, is_more_precise,
42-
restrict_subtype_away, is_subtype_ignoring_tvars, is_callable_subtype,
42+
restrict_subtype_away, is_subtype_ignoring_tvars, is_callable_compatible,
4343
unify_generic_callable, find_member
4444
)
4545
from mypy.maptype import map_instance_to_supertype
@@ -407,22 +407,32 @@ def _visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None:
407407
if defn.info:
408408
self.check_method_override(defn)
409409
self.check_inplace_operator_method(defn)
410-
self.check_overlapping_overloads(defn)
410+
if not defn.is_property:
411+
self.check_overlapping_overloads(defn)
411412
return None
412413

413414
def check_overlapping_overloads(self, defn: OverloadedFuncDef) -> None:
414415
# At this point we should have set the impl already, and all remaining
415416
# items are decorators
416417
for i, item in enumerate(defn.items):
418+
# TODO overloads involving decorators
417419
assert isinstance(item, Decorator)
418420
sig1 = self.function_type(item.func)
421+
419422
for j, item2 in enumerate(defn.items[i + 1:]):
420-
# TODO overloads involving decorators
421423
assert isinstance(item2, Decorator)
422424
sig2 = self.function_type(item2.func)
423-
if is_unsafe_overlapping_signatures(sig1, sig2):
424-
self.msg.overloaded_signatures_overlap(i + 1, i + j + 2,
425-
item.func)
425+
426+
assert isinstance(sig1, CallableType)
427+
assert isinstance(sig2, CallableType)
428+
429+
if not are_argument_counts_overlapping(sig1, sig2):
430+
continue
431+
432+
if if_overload_can_never_match(sig1, sig2):
433+
self.msg.overloaded_signature_will_never_match(i + 1, i + j + 2, item2.func)
434+
elif is_unsafe_overlapping_overload_signatures(sig1, sig2):
435+
self.msg.overloaded_signatures_overlap(i + 1, i + j + 2, item.func)
426436
if defn.impl:
427437
if isinstance(defn.impl, FuncDef):
428438
impl_type = defn.impl.type
@@ -437,7 +447,8 @@ def check_overlapping_overloads(self, defn: OverloadedFuncDef) -> None:
437447

438448
assert isinstance(impl_type, CallableType)
439449
assert isinstance(sig1, CallableType)
440-
if not is_callable_subtype(impl_type, sig1, ignore_return=True):
450+
if not is_callable_compatible(impl_type, sig1,
451+
is_compat=is_subtype, ignore_return=True):
441452
self.msg.overloaded_signatures_arg_specific(i + 1, defn.impl)
442453
impl_type_subst = impl_type
443454
if impl_type.variables:
@@ -1038,8 +1049,8 @@ def check_overlapping_op_methods(self,
10381049
fallback=self.named_type('builtins.function'),
10391050
name=reverse_type.name)
10401051

1041-
if is_unsafe_overlapping_signatures(forward_tweaked,
1042-
reverse_tweaked):
1052+
if is_unsafe_overlapping_operator_signatures(
1053+
forward_tweaked, reverse_tweaked):
10431054
self.msg.operator_method_signatures_overlap(
10441055
reverse_class, reverse_name,
10451056
forward_base, forward_name, context)
@@ -1812,10 +1823,18 @@ def check_multi_assignment_from_union(self, lvalues: List[Expression], rvalue: E
18121823
# Bind a union of types collected in 'assignments' to every expression.
18131824
if isinstance(expr, StarExpr):
18141825
expr = expr.expr
1815-
types, declared_types = zip(*items)
1826+
1827+
# TODO: See todo in binder.py, ConditionalTypeBinder.assign_type
1828+
# It's unclear why the 'declared_type' param is sometimes 'None'
1829+
clean_items = [] # type: List[Tuple[Type, Type]]
1830+
for type, declared_type in items:
1831+
assert declared_type is not None
1832+
clean_items.append((type, declared_type))
1833+
1834+
types, declared_types = zip(*clean_items)
18161835
self.binder.assign_type(expr,
1817-
UnionType.make_simplified_union(types),
1818-
UnionType.make_simplified_union(declared_types),
1836+
UnionType.make_simplified_union(list(types)),
1837+
UnionType.make_simplified_union(list(declared_types)),
18191838
False)
18201839
for union, lv in zip(union_types, self.flatten_lvalues(lvalues)):
18211840
# Properly store the inferred types.
@@ -3527,18 +3546,96 @@ def type(self, type: Type) -> Type:
35273546
return expand_type(type, self.map)
35283547

35293548

3530-
def is_unsafe_overlapping_signatures(signature: Type, other: Type) -> bool:
3531-
"""Check if two signatures may be unsafely overlapping.
3549+
def are_argument_counts_overlapping(t: CallableType, s: CallableType) -> bool:
3550+
"""Can a single call match both t and s, based just on positional argument counts?
3551+
"""
3552+
min_args = max(t.min_args, s.min_args)
3553+
max_args = min(t.max_possible_positional_args(), s.max_possible_positional_args())
3554+
return min_args <= max_args
35323555

3533-
Two signatures s and t are overlapping if both can be valid for the same
3556+
3557+
def is_unsafe_overlapping_overload_signatures(signature: CallableType,
3558+
other: CallableType) -> bool:
3559+
"""Check if two overloaded function signatures may be unsafely overlapping.
3560+
3561+
We consider two functions 's' and 't' to be unsafely overlapping both
3562+
of the following are true:
3563+
3564+
1. s's parameters are all more precise or partially overlapping with t's
3565+
1. s's return type is NOT a subtype of t's.
3566+
3567+
both can be valid for the same
35343568
statically typed values and the return types are incompatible.
35353569
3570+
Assumes that 'signature' appears earlier in the list of overload
3571+
alternatives then 'other' and that their argument counts are overlapping.
3572+
"""
3573+
# TODO: Handle partially overlapping parameter types and argument counts
3574+
#
3575+
# For example, the signatures "f(x: Union[A, B]) -> int" and "f(x: Union[B, C]) -> str"
3576+
# is unsafe: the parameter types are partially overlapping.
3577+
#
3578+
# To fix this, we need to either modify meet.is_overlapping_types or add a new
3579+
# function and use "is_more_precise(...) or is_partially_overlapping(...)" for the is_compat
3580+
# checks.
3581+
#
3582+
# Similarly, the signatures "f(x: A, y: A) -> str" and "f(*x: A) -> int" are also unsafe:
3583+
# the parameter *counts* or arity are partially overlapping.
3584+
#
3585+
# To fix this, we need to modify is_callable_compatible so it can optionally detect
3586+
# functions that are *potentially* compatible rather then *definitely* compatible.
3587+
3588+
# The reason we repeat this check twice is so we can do a slightly better job of
3589+
# checking for potentially overlapping param counts. Both calls will actually check
3590+
# the param and return types in the same "direction" -- the only thing that differs
3591+
# is how is_callable_compatible checks non-positional arguments.
3592+
return (is_callable_compatible(signature, other,
3593+
is_compat=is_more_precise,
3594+
is_compat_return=lambda l, r: not is_subtype(l, r),
3595+
check_args_covariantly=True) or
3596+
is_callable_compatible(other, signature,
3597+
is_compat=is_more_precise,
3598+
is_compat_return=lambda l, r: not is_subtype(r, l)))
3599+
3600+
3601+
def if_overload_can_never_match(signature: CallableType, other: CallableType) -> bool:
3602+
"""Check if the 'other' method can never be matched due to 'signature'.
3603+
3604+
This can happen if signature's parameters are all strictly broader then
3605+
other's parameters.
3606+
3607+
Assumes that both signatures have overlapping argument counts.
3608+
"""
3609+
return is_callable_compatible(signature, other,
3610+
is_compat=is_more_precise,
3611+
ignore_return=True)
3612+
3613+
3614+
def is_unsafe_overlapping_operator_signatures(signature: Type, other: Type) -> bool:
3615+
"""Check if two operator method signatures may be unsafely overlapping.
3616+
3617+
Two signatures s and t are overlapping if both can be valid for the same
3618+
statically typed values and the return types are incompatible.
3619+
35363620
Assume calls are first checked against 'signature', then against 'other'.
35373621
Thus if 'signature' is more general than 'other', there is no unsafe
35383622
overlapping.
35393623
3540-
TODO If argument types vary covariantly, the return type may vary
3541-
covariantly as well.
3624+
TODO: Clean up this function and make it not perform type erasure.
3625+
3626+
Context: This function was previously used to make sure both overloaded
3627+
functions and operator methods were not unsafely overlapping.
3628+
3629+
We changed the semantics for we should handle overloaded definitions,
3630+
but not operator functions. (We can't reuse the same semantics for both:
3631+
the overload semantics are too restrictive here).
3632+
3633+
We should rewrite this method so that:
3634+
3635+
1. It uses many of the improvements made to overloads: in particular,
3636+
eliminating type erasure.
3637+
3638+
2. It contains just the logic necessary for operator methods.
35423639
"""
35433640
if isinstance(signature, CallableType):
35443641
if isinstance(other, CallableType):
@@ -3581,12 +3678,11 @@ def is_more_general_arg_prefix(t: FunctionLike, s: FunctionLike) -> bool:
35813678
"""Does t have wider arguments than s?"""
35823679
# TODO should an overload with additional items be allowed to be more
35833680
# general than one with fewer items (or just one item)?
3584-
# TODO check argument kinds and otherwise make more general
35853681
if isinstance(t, CallableType):
35863682
if isinstance(s, CallableType):
3587-
t, s = unify_generic_callables(t, s)
3588-
return all(is_proper_subtype(args, argt)
3589-
for argt, args in zip(t.arg_types, s.arg_types))
3683+
return is_callable_compatible(t, s,
3684+
is_compat=is_proper_subtype,
3685+
ignore_return=True)
35903686
elif isinstance(t, FunctionLike):
35913687
if isinstance(s, FunctionLike):
35923688
if len(t.items()) == len(s.items()):
@@ -3595,29 +3691,6 @@ def is_more_general_arg_prefix(t: FunctionLike, s: FunctionLike) -> bool:
35953691
return False
35963692

35973693

3598-
def unify_generic_callables(t: CallableType,
3599-
s: CallableType) -> Tuple[CallableType,
3600-
CallableType]:
3601-
"""Make type variables in generic callables the same if possible.
3602-
3603-
Return updated callables. If we can't unify the type variables,
3604-
return the unmodified arguments.
3605-
"""
3606-
# TODO: Use this elsewhere when comparing generic callables.
3607-
if t.is_generic() and s.is_generic():
3608-
t_substitutions = {}
3609-
s_substitutions = {}
3610-
for tv1, tv2 in zip(t.variables, s.variables):
3611-
# Are these something we can unify?
3612-
if tv1.id != tv2.id and is_equivalent_type_var_def(tv1, tv2):
3613-
newdef = TypeVarDef.new_unification_variable(tv2)
3614-
t_substitutions[tv1.id] = TypeVarType(newdef)
3615-
s_substitutions[tv2.id] = TypeVarType(newdef)
3616-
return (cast(CallableType, expand_type(t, t_substitutions)),
3617-
cast(CallableType, expand_type(s, s_substitutions)))
3618-
return t, s
3619-
3620-
36213694
def is_equivalent_type_var_def(tv1: TypeVarDef, tv2: TypeVarDef) -> bool:
36223695
"""Are type variable definitions equivalent?
36233696
@@ -3633,17 +3706,17 @@ def is_equivalent_type_var_def(tv1: TypeVarDef, tv2: TypeVarDef) -> bool:
36333706

36343707

36353708
def is_same_arg_prefix(t: CallableType, s: CallableType) -> bool:
3636-
# TODO check argument kinds
3637-
return all(is_same_type(argt, args)
3638-
for argt, args in zip(t.arg_types, s.arg_types))
3709+
return is_callable_compatible(t, s,
3710+
is_compat=is_same_type,
3711+
ignore_return=True,
3712+
check_args_covariantly=True,
3713+
ignore_pos_arg_names=True)
36393714

36403715

36413716
def is_more_precise_signature(t: CallableType, s: CallableType) -> bool:
36423717
"""Is t more precise than s?
3643-
36443718
A signature t is more precise than s if all argument types and the return
36453719
type of t are more precise than the corresponding types in s.
3646-
36473720
Assume that the argument kinds and names are compatible, and that the
36483721
argument counts are overlapping.
36493722
"""

0 commit comments

Comments
 (0)