Skip to content

Commit b8845ac

Browse files
committed
Remove and refactor old overload selection logic
This commit removes a few remnants of the old overload selection algorithm in checkexpr. Specifically, this commit: 1. Modifies `erased_signature_similarity` so it returns a bool instead of an int. 2. Simplifies how `erased_signature_similarity` handles types like `Type[X]`. Note: although this change relaxes and loosens the precision of `erased_signature_similarity`, it will not impact correctness: we now use this method exclusively as a heuristic to help us determine which overloads the user might have meant to select in our error messages. As a consequence, loosening this function actually led to a more helpful error message in one case. 3. Remove the `match_signature_types` method. It didn't appear as if anybody was calling it.
1 parent 3c9ee34 commit b8845ac

File tree

2 files changed

+54
-132
lines changed

2 files changed

+54
-132
lines changed

mypy/checkexpr.py

Lines changed: 50 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -1568,12 +1568,9 @@ def union_overload_matches(self, types: Sequence[Type]) -> Union[AnyType, Callab
15681568
def erased_signature_similarity(self, arg_types: List[Type], arg_kinds: List[int],
15691569
arg_names: Optional[Sequence[Optional[str]]],
15701570
callee: CallableType,
1571-
context: Context) -> int:
1572-
"""Determine whether arguments could match the signature at runtime.
1573-
1574-
Return similarity level (0 = no match, 1 = can match, 2 = non-promotion match). See
1575-
overload_arg_similarity for a discussion of similarity levels.
1576-
"""
1571+
context: Context) -> bool:
1572+
"""Determine whether arguments could match the signature at runtime, after
1573+
erasing types."""
15771574
formal_to_actual = map_actuals_to_formals(arg_kinds,
15781575
arg_names,
15791576
callee.arg_kinds,
@@ -1583,55 +1580,22 @@ def erased_signature_similarity(self, arg_types: List[Type], arg_kinds: List[int
15831580
if not self.check_argument_count(callee, arg_types, arg_kinds, arg_names,
15841581
formal_to_actual, None, None):
15851582
# Too few or many arguments -> no match.
1586-
return 0
1587-
1588-
similarity = 2
1583+
return False
15891584

15901585
def check_arg(caller_type: Type, original_caller_type: Type, caller_kind: int,
15911586
callee_type: Type, n: int, m: int, callee: CallableType,
15921587
context: Context, messages: MessageBuilder) -> None:
1593-
nonlocal similarity
1594-
similarity = min(similarity,
1595-
overload_arg_similarity(caller_type, callee_type))
1596-
if similarity == 0:
1588+
if not arg_approximate_similarity(caller_type, callee_type):
15971589
# No match -- exit early since none of the remaining work can change
15981590
# the result.
15991591
raise Finished
16001592

16011593
try:
16021594
self.check_argument_types(arg_types, arg_kinds, callee, formal_to_actual,
16031595
context=context, check_arg=check_arg)
1596+
return True
16041597
except Finished:
1605-
pass
1606-
1607-
return similarity
1608-
1609-
def match_signature_types(self, arg_types: List[Type], arg_kinds: List[int],
1610-
arg_names: Optional[Sequence[Optional[str]]], callee: CallableType,
1611-
context: Context) -> bool:
1612-
"""Determine whether arguments types match the signature.
1613-
1614-
Assume that argument counts are compatible.
1615-
1616-
Return True if arguments match.
1617-
"""
1618-
formal_to_actual = map_actuals_to_formals(arg_kinds,
1619-
arg_names,
1620-
callee.arg_kinds,
1621-
callee.arg_names,
1622-
lambda i: arg_types[i])
1623-
ok = True
1624-
1625-
def check_arg(caller_type: Type, original_caller_type: Type, caller_kind: int,
1626-
callee_type: Type, n: int, m: int, callee: CallableType,
1627-
context: Context, messages: MessageBuilder) -> None:
1628-
nonlocal ok
1629-
if not is_subtype(caller_type, callee_type):
1630-
ok = False
1631-
1632-
self.check_argument_types(arg_types, arg_kinds, callee, formal_to_actual,
1633-
context=context, check_arg=check_arg)
1634-
return ok
1598+
return False
16351599

16361600
def apply_generic_arguments(self, callable: CallableType, types: Sequence[Optional[Type]],
16371601
context: Context) -> CallableType:
@@ -3295,101 +3259,68 @@ def visit_uninhabited_type(self, t: UninhabitedType) -> bool:
32953259
return True
32963260

32973261

3298-
def overload_arg_similarity(actual: Type, formal: Type) -> int:
3299-
"""Return if caller argument (actual) is compatible with overloaded signature arg (formal).
3262+
def arg_approximate_similarity(actual: Type, formal: Type) -> bool:
3263+
"""Return if caller argument (actual) is roughly compatible with signature arg (formal).
33003264
3301-
Return a similarity level:
3302-
0: no match
3303-
1: actual is compatible, but only using type promotions (e.g. int vs float)
3304-
2: actual is compatible without type promotions (e.g. int vs object)
3265+
This function is deliberately loose and will report two types are similar
3266+
as long as their "shapes" are plausibly the same.
33053267
3306-
The distinction is important in cases where multiple overload items match. We want
3307-
give priority to higher similarity matches.
3268+
This is useful when we're doing error reporting: for example, if we're trying
3269+
to select an overload alternative and there's no exact match, we can use
3270+
this function to help us identify which alternative the user might have
3271+
*meant* to match.
33083272
"""
3309-
# Replace type variables with their upper bounds. Overloading
3310-
# resolution is based on runtime behavior which erases type
3311-
# parameters, so no need to handle type variables occurring within
3312-
# a type.
3273+
3274+
# Erase typevars: we'll consider them all to have the same "shape".
3275+
33133276
if isinstance(actual, TypeVarType):
33143277
actual = actual.erase_to_union_or_bound()
33153278
if isinstance(formal, TypeVarType):
33163279
formal = formal.erase_to_union_or_bound()
3317-
if (isinstance(actual, UninhabitedType) or isinstance(actual, AnyType) or
3318-
isinstance(formal, AnyType) or
3319-
(isinstance(actual, Instance) and actual.type.fallback_to_any)):
3320-
# These could match anything at runtime.
3321-
return 2
3280+
3281+
# Callable or Type[...]-ish types
3282+
3283+
def is_typetype_like(typ: Type) -> bool:
3284+
return (isinstance(typ, TypeType)
3285+
or (isinstance(typ, FunctionLike) and typ.is_type_obj())
3286+
or (isinstance(typ, Instance) and typ.type.fullname() == "builtins.object"))
3287+
33223288
if isinstance(formal, CallableType):
3323-
if isinstance(actual, (CallableType, Overloaded)):
3324-
# TODO: do more sophisticated callable matching
3325-
return 2
3326-
if isinstance(actual, TypeType):
3327-
return 2 if is_subtype(actual, formal) else 0
3328-
if isinstance(actual, NoneTyp):
3329-
if not experiments.STRICT_OPTIONAL:
3330-
# NoneTyp matches anything if we're not doing strict Optional checking
3331-
return 2
3332-
else:
3333-
# NoneType is a subtype of object
3334-
if isinstance(formal, Instance) and formal.type.fullname() == "builtins.object":
3335-
return 2
3289+
if isinstance(actual, (CallableType, Overloaded, TypeType)):
3290+
return True
3291+
if is_typetype_like(actual) and is_typetype_like(formal):
3292+
return True
3293+
3294+
# Unions
3295+
33363296
if isinstance(actual, UnionType):
3337-
return max(overload_arg_similarity(item, formal)
3338-
for item in actual.relevant_items())
3297+
return any(arg_approximate_similarity(item, formal) for item in actual.relevant_items())
33393298
if isinstance(formal, UnionType):
3340-
return max(overload_arg_similarity(actual, item)
3341-
for item in formal.relevant_items())
3342-
if isinstance(formal, TypeType):
3343-
if isinstance(actual, TypeType):
3344-
# Since Type[T] is covariant, check if actual = Type[A] is
3345-
# a subtype of formal = Type[F].
3346-
return overload_arg_similarity(actual.item, formal.item)
3347-
elif isinstance(actual, FunctionLike) and actual.is_type_obj():
3348-
# Check if the actual is a constructor of some sort.
3349-
# Note that this is this unsound, since we don't check the __init__ signature.
3350-
return overload_arg_similarity(actual.items()[0].ret_type, formal.item)
3351-
else:
3352-
return 0
3299+
return any(arg_approximate_similarity(actual, item) for item in formal.relevant_items())
3300+
3301+
# TypedDicts
3302+
33533303
if isinstance(actual, TypedDictType):
33543304
if isinstance(formal, TypedDictType):
3355-
# Don't support overloading based on the keys or value types of a TypedDict since
3356-
# that would be complicated and probably only marginally useful.
3357-
return 2
3358-
return overload_arg_similarity(actual.fallback, formal)
3305+
return True
3306+
return arg_approximate_similarity(actual.fallback, formal)
3307+
3308+
# Instances
3309+
# For instances, we mostly defer to the existing is_subtype check.
3310+
33593311
if isinstance(formal, Instance):
33603312
if isinstance(actual, CallableType):
33613313
actual = actual.fallback
33623314
if isinstance(actual, Overloaded):
33633315
actual = actual.items()[0].fallback
33643316
if isinstance(actual, TupleType):
33653317
actual = actual.fallback
3366-
if isinstance(actual, Instance):
3367-
# First perform a quick check (as an optimization) and fall back to generic
3368-
# subtyping algorithm if type promotions are possible (e.g., int vs. float).
3369-
if formal.type in actual.type.mro:
3370-
return 2
3371-
elif formal.type.is_protocol and is_subtype(actual, erasetype.erase_type(formal)):
3372-
return 2
3373-
elif actual.type._promote and is_subtype(actual, formal):
3374-
return 1
3375-
else:
3376-
return 0
3377-
elif isinstance(actual, TypeType):
3378-
item = actual.item
3379-
if formal.type.fullname() in {"builtins.object", "builtins.type"}:
3380-
return 2
3381-
elif isinstance(item, Instance) and item.type.metaclass_type:
3382-
# FIX: this does not handle e.g. Union of instances
3383-
return overload_arg_similarity(item.type.metaclass_type, formal)
3384-
else:
3385-
return 0
3386-
else:
3387-
return 0
3388-
if isinstance(actual, UnboundType) or isinstance(formal, UnboundType):
3389-
# Either actual or formal is the result of an error; shut up.
3390-
return 2
3391-
# Fall back to a conservative equality check for the remaining kinds of type.
3392-
return 2 if is_same_type(erasetype.erase_type(actual), erasetype.erase_type(formal)) else 0
3318+
if isinstance(actual, Instance) and formal.type in actual.type.mro:
3319+
# Try performing a quick check as an optimization
3320+
return True
3321+
3322+
# Fall back to a standard subtype check for the remaining kinds of type.
3323+
return is_subtype(erasetype.erase_type(actual), erasetype.erase_type(formal))
33933324

33943325

33953326
def any_causes_overload_ambiguity(items: List[CallableType],

test-data/unit/check-classes.test

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1223,11 +1223,8 @@ class D:
12231223
def __get__(self, inst: Base, own: Type[Base]) -> str: pass
12241224
[builtins fixtures/bool.pyi]
12251225
[out]
1226-
main:5: error: Revealed type is 'Any'
1227-
main:5: error: No overload variant of "__get__" of "D" matches argument types "None", "Type[A]"
1228-
main:5: note: Possible overload variants:
1229-
main:5: note: def __get__(self, inst: None, own: Type[Base]) -> D
1230-
main:5: note: def __get__(self, inst: Base, own: Type[Base]) -> str
1226+
main:5: error: Revealed type is 'd.D'
1227+
main:5: error: Argument 2 to "__get__" of "D" has incompatible type "Type[A]"; expected "Type[Base]"
12311228
main:6: error: Revealed type is 'Any'
12321229
main:6: error: No overload variant of "__get__" of "D" matches argument types "A", "Type[A]"
12331230
main:6: note: Possible overload variants:
@@ -2814,16 +2811,10 @@ def f(a: Type[B]) -> None: pass
28142811
@overload
28152812
def f(a: int) -> None: pass
28162813

2817-
f(A) # E: No overload variant of "f" matches argument type "Type[A]" \
2818-
# N: Possible overload variants: \
2819-
# N: def f(a: Type[B]) -> None \
2820-
# N: def f(a: int) -> None
2814+
f(A) # E: Argument 1 to "f" has incompatible type "Type[A]"; expected "Type[B]"
28212815
f(B)
28222816
f(C)
2823-
f(AType) # E: No overload variant of "f" matches argument type "Type[A]" \
2824-
# N: Possible overload variants: \
2825-
# N: def f(a: Type[B]) -> None \
2826-
# N: def f(a: int) -> None
2817+
f(AType) # E: Argument 1 to "f" has incompatible type "Type[A]"; expected "Type[B]"
28272818
f(BType)
28282819
f(CType)
28292820
[builtins fixtures/classmethod.pyi]

0 commit comments

Comments
 (0)