Skip to content

Commit dd70710

Browse files
authored
Add partial overload checks (#5476)
This pull request adds more robust support for detecting partially overlapping types. Specifically, it detects overlaps with... 1. TypedDicts 2. Tuples 3. Unions 4. TypeVars 5. Generic types containing variations of the above. This new overlapping type check is used for detecting unsafe overlaps with overloads and operator methods as well as for performing reachability/unreachability analysis in `isinstance` and `if x is None` checks and the like. This PR also removes some (now unused) code that used to be used for detecting overlaps with operators. This pull request builds on top of #5474 and #5475 and supersedes #5475.
1 parent 4e6d753 commit dd70710

8 files changed

+1050
-264
lines changed

mypy/checker.py

+107-118
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from contextlib import contextmanager
66

77
from typing import (
8-
Dict, Set, List, cast, Tuple, TypeVar, Union, Optional, NamedTuple, Iterator, Any
8+
Dict, Set, List, cast, Tuple, TypeVar, Union, Optional, NamedTuple, Iterator, Iterable, Any
99
)
1010

1111
from mypy.errors import Errors, report_internal_error
@@ -30,7 +30,7 @@
3030
Type, AnyType, CallableType, FunctionLike, Overloaded, TupleType, TypedDictType,
3131
Instance, NoneTyp, strip_type, TypeType, TypeOfAny,
3232
UnionType, TypeVarId, TypeVarType, PartialType, DeletedType, UninhabitedType, TypeVarDef,
33-
true_only, false_only, function_type, is_named_instance, union_items,
33+
true_only, false_only, function_type, is_named_instance, union_items, TypeQuery
3434
)
3535
from mypy.sametypes import is_same_type, is_same_types
3636
from mypy.messages import MessageBuilder, make_inferred_type_note
@@ -55,7 +55,7 @@
5555
from mypy.join import join_types
5656
from mypy.treetransform import TransformVisitor
5757
from mypy.binder import ConditionalTypeBinder, get_declaration
58-
from mypy.meet import is_overlapping_types, is_partially_overlapping_types
58+
from mypy.meet import is_overlapping_erased_types, is_overlapping_types
5959
from mypy.options import Options
6060
from mypy.plugin import Plugin, CheckerPluginInterface
6161
from mypy.sharedparse import BINARY_MAGIC_METHODS
@@ -495,12 +495,12 @@ def check_overlapping_overloads(self, defn: OverloadedFuncDef) -> None:
495495

496496
# Is the overload alternative's arguments subtypes of the implementation's?
497497
if not is_callable_compatible(impl, sig1,
498-
is_compat=is_subtype,
498+
is_compat=is_subtype_no_promote,
499499
ignore_return=True):
500500
self.msg.overloaded_signatures_arg_specific(i + 1, defn.impl)
501501

502502
# Is the overload alternative's return type a subtype of the implementation's?
503-
if not is_subtype(sig1.ret_type, impl.ret_type):
503+
if not is_subtype_no_promote(sig1.ret_type, impl.ret_type):
504504
self.msg.overloaded_signatures_ret_specific(i + 1, defn.impl)
505505

506506
# Here's the scoop about generators and coroutines.
@@ -3156,7 +3156,7 @@ def find_isinstance_check(self, node: Expression
31563156
else:
31573157
optional_type, comp_type = second_type, first_type
31583158
optional_expr = node.operands[1]
3159-
if is_overlapping_types(optional_type, comp_type):
3159+
if is_overlapping_erased_types(optional_type, comp_type):
31603160
return {optional_expr: remove_optional(optional_type)}, {}
31613161
elif node.operators in [['in'], ['not in']]:
31623162
expr = node.operands[0]
@@ -3167,7 +3167,7 @@ def find_isinstance_check(self, node: Expression
31673167
right_type.type.fullname() != 'builtins.object'))
31683168
if (right_type and right_ok and is_optional(left_type) and
31693169
literal(expr) == LITERAL_TYPE and not is_literal_none(expr) and
3170-
is_overlapping_types(left_type, right_type)):
3170+
is_overlapping_erased_types(left_type, right_type)):
31713171
if node.operators == ['in']:
31723172
return {expr: remove_optional(left_type)}, {}
31733173
if node.operators == ['not in']:
@@ -3515,7 +3515,8 @@ def conditional_type_map(expr: Expression,
35153515
and is_proper_subtype(current_type, proposed_type)):
35163516
# Expression is always of one of the types in proposed_type_ranges
35173517
return {}, None
3518-
elif not is_overlapping_types(current_type, proposed_type):
3518+
elif not is_overlapping_types(current_type, proposed_type,
3519+
prohibit_none_typevar_overlap=True):
35193520
# Expression is never of any type in proposed_type_ranges
35203521
return None, {}
35213522
else:
@@ -3731,9 +3732,9 @@ def are_argument_counts_overlapping(t: CallableType, s: CallableType) -> bool:
37313732

37323733
def is_unsafe_overlapping_overload_signatures(signature: CallableType,
37333734
other: CallableType) -> bool:
3734-
"""Check if two overloaded function signatures may be unsafely overlapping.
3735+
"""Check if two overloaded signatures are unsafely overlapping or partially overlapping.
37353736
3736-
We consider two functions 's' and 't' to be unsafely overlapping both if
3737+
We consider two functions 's' and 't' to be unsafely overlapping if both
37373738
of the following are true:
37383739
37393740
1. s's parameters are all more precise or partially overlapping with t's
@@ -3742,26 +3743,98 @@ def is_unsafe_overlapping_overload_signatures(signature: CallableType,
37423743
Assumes that 'signature' appears earlier in the list of overload
37433744
alternatives then 'other' and that their argument counts are overlapping.
37443745
"""
3745-
# TODO: Handle partially overlapping parameter types
3746+
# Try detaching callables from the containing class so that all TypeVars
3747+
# are treated as being free.
37463748
#
3747-
# For example, the signatures "f(x: Union[A, B]) -> int" and "f(x: Union[B, C]) -> str"
3748-
# is unsafe: the parameter types are partially overlapping.
3749+
# This lets us identify cases where the two signatures use completely
3750+
# incompatible types -- e.g. see the testOverloadingInferUnionReturnWithMixedTypevars
3751+
# test case.
3752+
signature = detach_callable(signature)
3753+
other = detach_callable(other)
3754+
3755+
# Note: We repeat this check twice in both directions due to a slight
3756+
# asymmetry in 'is_callable_compatible'. When checking for partial overlaps,
3757+
# we attempt to unify 'signature' and 'other' both against each other.
37493758
#
3750-
# To fix this, we need to either modify meet.is_overlapping_types or add a new
3751-
# function and use "is_more_precise(...) or is_partially_overlapping(...)" for the is_compat
3752-
# checks.
3759+
# If 'signature' cannot be unified with 'other', we end early. However,
3760+
# if 'other' cannot be modified with 'signature', the function continues
3761+
# using the older version of 'other'.
37533762
#
3754-
# (We already have a rudimentary implementation of 'is_partially_overlapping', but it only
3755-
# attempts to handle the obvious cases -- see its docstring for more info.)
3756-
3757-
def is_more_precise_or_partially_overlapping(t: Type, s: Type) -> bool:
3758-
return is_more_precise(t, s) or is_partially_overlapping_types(t, s)
3759-
3760-
return is_callable_compatible(signature, other,
3761-
is_compat=is_more_precise_or_partially_overlapping,
3762-
is_compat_return=lambda l, r: not is_subtype(l, r),
3763+
# This discrepancy is unfortunately difficult to get rid of, so we repeat the
3764+
# checks twice in both directions for now.
3765+
return (is_callable_compatible(signature, other,
3766+
is_compat=is_overlapping_types_no_promote,
3767+
is_compat_return=lambda l, r: not is_subtype_no_promote(l, r),
3768+
ignore_return=False,
37633769
check_args_covariantly=True,
3764-
allow_partial_overlap=True)
3770+
allow_partial_overlap=True) or
3771+
is_callable_compatible(other, signature,
3772+
is_compat=is_overlapping_types_no_promote,
3773+
is_compat_return=lambda l, r: not is_subtype_no_promote(r, l),
3774+
ignore_return=False,
3775+
check_args_covariantly=False,
3776+
allow_partial_overlap=True))
3777+
3778+
3779+
def detach_callable(typ: CallableType) -> CallableType:
3780+
"""Ensures that the callable's type variables are 'detached' and independent of the context.
3781+
3782+
A callable normally keeps track of the type variables it uses within its 'variables' field.
3783+
However, if the callable is from a method and that method is using a class type variable,
3784+
the callable will not keep track of that type variable since it belongs to the class.
3785+
3786+
This function will traverse the callable and find all used type vars and add them to the
3787+
variables field if it isn't already present.
3788+
3789+
The caller can then unify on all type variables whether or not the callable is originally
3790+
from a class or not."""
3791+
type_list = typ.arg_types + [typ.ret_type]
3792+
3793+
appear_map = {} # type: Dict[str, List[int]]
3794+
for i, inner_type in enumerate(type_list):
3795+
typevars_available = inner_type.accept(TypeVarExtractor())
3796+
for var in typevars_available:
3797+
if var.fullname not in appear_map:
3798+
appear_map[var.fullname] = []
3799+
appear_map[var.fullname].append(i)
3800+
3801+
used_type_var_names = set()
3802+
for var_name, appearances in appear_map.items():
3803+
used_type_var_names.add(var_name)
3804+
3805+
all_type_vars = typ.accept(TypeVarExtractor())
3806+
new_variables = []
3807+
for var in set(all_type_vars):
3808+
if var.fullname not in used_type_var_names:
3809+
continue
3810+
new_variables.append(TypeVarDef(
3811+
name=var.name,
3812+
fullname=var.fullname,
3813+
id=var.id,
3814+
values=var.values,
3815+
upper_bound=var.upper_bound,
3816+
variance=var.variance,
3817+
))
3818+
out = typ.copy_modified(
3819+
variables=new_variables,
3820+
arg_types=type_list[:-1],
3821+
ret_type=type_list[-1],
3822+
)
3823+
return out
3824+
3825+
3826+
class TypeVarExtractor(TypeQuery[List[TypeVarType]]):
3827+
def __init__(self) -> None:
3828+
super().__init__(self._merge)
3829+
3830+
def _merge(self, iter: Iterable[List[TypeVarType]]) -> List[TypeVarType]:
3831+
out = []
3832+
for item in iter:
3833+
out.extend(item)
3834+
return out
3835+
3836+
def visit_type_var(self, t: TypeVarType) -> List[TypeVarType]:
3837+
return [t]
37653838

37663839

37673840
def overload_can_never_match(signature: CallableType, other: CallableType) -> bool:
@@ -3787,69 +3860,6 @@ def overload_can_never_match(signature: CallableType, other: CallableType) -> bo
37873860
ignore_return=True)
37883861

37893862

3790-
def is_unsafe_overlapping_operator_signatures(signature: Type, other: Type) -> bool:
3791-
"""Check if two operator method signatures may be unsafely overlapping.
3792-
3793-
Two signatures s and t are overlapping if both can be valid for the same
3794-
statically typed values and the return types are incompatible.
3795-
3796-
Assume calls are first checked against 'signature', then against 'other'.
3797-
Thus if 'signature' is more general than 'other', there is no unsafe
3798-
overlapping.
3799-
3800-
TODO: Clean up this function and make it not perform type erasure.
3801-
3802-
Context: This function was previously used to make sure both overloaded
3803-
functions and operator methods were not unsafely overlapping.
3804-
3805-
We changed the semantics for we should handle overloaded definitions,
3806-
but not operator functions. (We can't reuse the same semantics for both:
3807-
the overload semantics are too restrictive here).
3808-
3809-
We should rewrite this method so that:
3810-
3811-
1. It uses many of the improvements made to overloads: in particular,
3812-
eliminating type erasure.
3813-
3814-
2. It contains just the logic necessary for operator methods.
3815-
"""
3816-
if isinstance(signature, CallableType):
3817-
if isinstance(other, CallableType):
3818-
# TODO varargs
3819-
# TODO keyword args
3820-
# TODO erasure
3821-
# TODO allow to vary covariantly
3822-
# Check if the argument counts are overlapping.
3823-
min_args = max(signature.min_args, other.min_args)
3824-
max_args = min(len(signature.arg_types), len(other.arg_types))
3825-
if min_args > max_args:
3826-
# Argument counts are not overlapping.
3827-
return False
3828-
# Signatures are overlapping iff if they are overlapping for the
3829-
# smallest common argument count.
3830-
for i in range(min_args):
3831-
t1 = signature.arg_types[i]
3832-
t2 = other.arg_types[i]
3833-
if not is_overlapping_types(t1, t2):
3834-
return False
3835-
# All arguments types for the smallest common argument count are
3836-
# overlapping => the signature is overlapping. The overlapping is
3837-
# safe if the return types are identical.
3838-
if is_same_type(signature.ret_type, other.ret_type):
3839-
return False
3840-
# If the first signature has more general argument types, the
3841-
# latter will never be called
3842-
if is_more_general_arg_prefix(signature, other):
3843-
return False
3844-
# Special case: all args are subtypes, and returns are subtypes
3845-
if (all(is_proper_subtype(s, o)
3846-
for (s, o) in zip(signature.arg_types, other.arg_types)) and
3847-
is_subtype(signature.ret_type, other.ret_type)):
3848-
return False
3849-
return not is_more_precise_signature(signature, other)
3850-
return True
3851-
3852-
38533863
def is_more_general_arg_prefix(t: FunctionLike, s: FunctionLike) -> bool:
38543864
"""Does t have wider arguments than s?"""
38553865
# TODO should an overload with additional items be allowed to be more
@@ -3867,20 +3877,6 @@ def is_more_general_arg_prefix(t: FunctionLike, s: FunctionLike) -> bool:
38673877
return False
38683878

38693879

3870-
def is_equivalent_type_var_def(tv1: TypeVarDef, tv2: TypeVarDef) -> bool:
3871-
"""Are type variable definitions equivalent?
3872-
3873-
Ignore ids, locations in source file and names.
3874-
"""
3875-
return (
3876-
tv1.variance == tv2.variance
3877-
and is_same_types(tv1.values, tv2.values)
3878-
and ((tv1.upper_bound is None and tv2.upper_bound is None)
3879-
or (tv1.upper_bound is not None
3880-
and tv2.upper_bound is not None
3881-
and is_same_type(tv1.upper_bound, tv2.upper_bound))))
3882-
3883-
38843880
def is_same_arg_prefix(t: CallableType, s: CallableType) -> bool:
38853881
return is_callable_compatible(t, s,
38863882
is_compat=is_same_type,
@@ -3889,21 +3885,6 @@ def is_same_arg_prefix(t: CallableType, s: CallableType) -> bool:
38893885
ignore_pos_arg_names=True)
38903886

38913887

3892-
def is_more_precise_signature(t: CallableType, s: CallableType) -> bool:
3893-
"""Is t more precise than s?
3894-
A signature t is more precise than s if all argument types and the return
3895-
type of t are more precise than the corresponding types in s.
3896-
Assume that the argument kinds and names are compatible, and that the
3897-
argument counts are overlapping.
3898-
"""
3899-
# TODO generic function types
3900-
# Only consider the common prefix of argument types.
3901-
for argt, args in zip(t.arg_types, s.arg_types):
3902-
if not is_more_precise(argt, args):
3903-
return False
3904-
return is_more_precise(t.ret_type, s.ret_type)
3905-
3906-
39073888
def infer_operator_assignment_method(typ: Type, operator: str) -> Tuple[bool, str]:
39083889
"""Determine if operator assignment on given value type is in-place, and the method name.
39093890
@@ -4045,3 +4026,11 @@ def is_static(func: Union[FuncBase, Decorator]) -> bool:
40454026
elif isinstance(func, FuncBase):
40464027
return func.is_static
40474028
assert False, "Unexpected func type: {}".format(type(func))
4029+
4030+
4031+
def is_subtype_no_promote(left: Type, right: Type) -> bool:
4032+
return is_subtype(left, right, ignore_promotions=True)
4033+
4034+
4035+
def is_overlapping_types_no_promote(left: Type, right: Type) -> bool:
4036+
return is_overlapping_types(left, right, ignore_promotions=True)

0 commit comments

Comments
 (0)