Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 5c6ca5c

Browse files
authored
Properly use proper subtyping for callables (#16343)
Fixes #16338 This is kind of a major change, but it is technically correct: we should not treat `(*args: Any, **kwargs: Any)` special in `is_proper_subtype()` (only in `is_subtype()`). Unfortunately, this requires an additional flag for `is_callable_compatible()`, since currently we are passing the subtype kind information via a callback, which is not applicable to handling argument kinds.
1 parent 4f05dd5 commit 5c6ca5c

File tree

5 files changed

+51
-9
lines changed

5 files changed

+51
-9
lines changed

mypy/checker.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -800,7 +800,7 @@ def check_overlapping_overloads(self, defn: OverloadedFuncDef) -> None:
800800

801801
# Is the overload alternative's arguments subtypes of the implementation's?
802802
if not is_callable_compatible(
803-
impl, sig1, is_compat=is_subtype, ignore_return=True
803+
impl, sig1, is_compat=is_subtype, is_proper_subtype=False, ignore_return=True
804804
):
805805
self.msg.overloaded_signatures_arg_specific(i + 1, defn.impl)
806806

@@ -7685,6 +7685,7 @@ def is_unsafe_overlapping_overload_signatures(
76857685
signature,
76867686
other,
76877687
is_compat=is_overlapping_types_no_promote_no_uninhabited_no_none,
7688+
is_proper_subtype=False,
76887689
is_compat_return=lambda l, r: not is_subtype_no_promote(l, r),
76897690
ignore_return=False,
76907691
check_args_covariantly=True,
@@ -7694,6 +7695,7 @@ def is_unsafe_overlapping_overload_signatures(
76947695
other,
76957696
signature,
76967697
is_compat=is_overlapping_types_no_promote_no_uninhabited_no_none,
7698+
is_proper_subtype=False,
76977699
is_compat_return=lambda l, r: not is_subtype_no_promote(r, l),
76987700
ignore_return=False,
76997701
check_args_covariantly=False,
@@ -7744,7 +7746,7 @@ def overload_can_never_match(signature: CallableType, other: CallableType) -> bo
77447746
signature, {tvar.id: erase_def_to_union_or_bound(tvar) for tvar in signature.variables}
77457747
)
77467748
return is_callable_compatible(
7747-
exp_signature, other, is_compat=is_more_precise, ignore_return=True
7749+
exp_signature, other, is_compat=is_more_precise, is_proper_subtype=True, ignore_return=True
77487750
)
77497751

77507752

@@ -7754,7 +7756,9 @@ def is_more_general_arg_prefix(t: FunctionLike, s: FunctionLike) -> bool:
77547756
# general than one with fewer items (or just one item)?
77557757
if isinstance(t, CallableType):
77567758
if isinstance(s, CallableType):
7757-
return is_callable_compatible(t, s, is_compat=is_proper_subtype, ignore_return=True)
7759+
return is_callable_compatible(
7760+
t, s, is_compat=is_proper_subtype, is_proper_subtype=True, ignore_return=True
7761+
)
77587762
elif isinstance(t, FunctionLike):
77597763
if isinstance(s, FunctionLike):
77607764
if len(t.items) == len(s.items):
@@ -7769,6 +7773,7 @@ def is_same_arg_prefix(t: CallableType, s: CallableType) -> bool:
77697773
t,
77707774
s,
77717775
is_compat=is_same_type,
7776+
is_proper_subtype=True,
77727777
ignore_return=True,
77737778
check_args_covariantly=True,
77747779
ignore_pos_arg_names=True,

mypy/constraints.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1352,7 +1352,11 @@ def find_matching_overload_item(overloaded: Overloaded, template: CallableType)
13521352
# Return type may be indeterminate in the template, so ignore it when performing a
13531353
# subtype check.
13541354
if mypy.subtypes.is_callable_compatible(
1355-
item, template, is_compat=mypy.subtypes.is_subtype, ignore_return=True
1355+
item,
1356+
template,
1357+
is_compat=mypy.subtypes.is_subtype,
1358+
is_proper_subtype=False,
1359+
ignore_return=True,
13561360
):
13571361
return item
13581362
# Fall back to the first item if we can't find a match. This is totally arbitrary --
@@ -1370,7 +1374,11 @@ def find_matching_overload_items(
13701374
# Return type may be indeterminate in the template, so ignore it when performing a
13711375
# subtype check.
13721376
if mypy.subtypes.is_callable_compatible(
1373-
item, template, is_compat=mypy.subtypes.is_subtype, ignore_return=True
1377+
item,
1378+
template,
1379+
is_compat=mypy.subtypes.is_subtype,
1380+
is_proper_subtype=False,
1381+
ignore_return=True,
13741382
):
13751383
res.append(item)
13761384
if not res:

mypy/meet.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,7 @@ def _type_object_overlap(left: Type, right: Type) -> bool:
462462
left,
463463
right,
464464
is_compat=_is_overlapping_types,
465+
is_proper_subtype=False,
465466
ignore_pos_arg_names=True,
466467
allow_partial_overlap=True,
467468
)

mypy/subtypes.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -658,6 +658,8 @@ def visit_parameters(self, left: Parameters) -> bool:
658658
left,
659659
self.right,
660660
is_compat=self._is_subtype,
661+
# TODO: this should pass the current value, but then couple tests fail.
662+
is_proper_subtype=False,
661663
ignore_pos_arg_names=self.subtype_context.ignore_pos_arg_names,
662664
)
663665
else:
@@ -677,6 +679,7 @@ def visit_callable_type(self, left: CallableType) -> bool:
677679
left,
678680
right,
679681
is_compat=self._is_subtype,
682+
is_proper_subtype=self.proper_subtype,
680683
ignore_pos_arg_names=self.subtype_context.ignore_pos_arg_names,
681684
strict_concatenate=(self.options.extra_checks or self.options.strict_concatenate)
682685
if self.options
@@ -932,6 +935,7 @@ def visit_overloaded(self, left: Overloaded) -> bool:
932935
left_item,
933936
right_item,
934937
is_compat=self._is_subtype,
938+
is_proper_subtype=self.proper_subtype,
935939
ignore_return=True,
936940
ignore_pos_arg_names=self.subtype_context.ignore_pos_arg_names,
937941
strict_concatenate=strict_concat,
@@ -940,6 +944,7 @@ def visit_overloaded(self, left: Overloaded) -> bool:
940944
right_item,
941945
left_item,
942946
is_compat=self._is_subtype,
947+
is_proper_subtype=self.proper_subtype,
943948
ignore_return=True,
944949
ignore_pos_arg_names=self.subtype_context.ignore_pos_arg_names,
945950
strict_concatenate=strict_concat,
@@ -1358,6 +1363,7 @@ def is_callable_compatible(
13581363
right: CallableType,
13591364
*,
13601365
is_compat: Callable[[Type, Type], bool],
1366+
is_proper_subtype: bool,
13611367
is_compat_return: Callable[[Type, Type], bool] | None = None,
13621368
ignore_return: bool = False,
13631369
ignore_pos_arg_names: bool = False,
@@ -1517,6 +1523,7 @@ def g(x: int) -> int: ...
15171523
left,
15181524
right,
15191525
is_compat=is_compat,
1526+
is_proper_subtype=is_proper_subtype,
15201527
ignore_pos_arg_names=ignore_pos_arg_names,
15211528
allow_partial_overlap=allow_partial_overlap,
15221529
strict_concatenate_check=strict_concatenate_check,
@@ -1552,12 +1559,13 @@ def are_parameters_compatible(
15521559
right: Parameters | NormalizedCallableType,
15531560
*,
15541561
is_compat: Callable[[Type, Type], bool],
1562+
is_proper_subtype: bool,
15551563
ignore_pos_arg_names: bool = False,
15561564
allow_partial_overlap: bool = False,
15571565
strict_concatenate_check: bool = False,
15581566
) -> bool:
15591567
"""Helper function for is_callable_compatible, used for Parameter compatibility"""
1560-
if right.is_ellipsis_args:
1568+
if right.is_ellipsis_args and not is_proper_subtype:
15611569
return True
15621570

15631571
left_star = left.var_arg()
@@ -1566,9 +1574,9 @@ def are_parameters_compatible(
15661574
right_star2 = right.kw_arg()
15671575

15681576
# Treat "def _(*a: Any, **kw: Any) -> X" similarly to "Callable[..., X]"
1569-
if are_trivial_parameters(right):
1577+
if are_trivial_parameters(right) and not is_proper_subtype:
15701578
return True
1571-
trivial_suffix = is_trivial_suffix(right)
1579+
trivial_suffix = is_trivial_suffix(right) and not is_proper_subtype
15721580

15731581
# Match up corresponding arguments and check them for compatibility. In
15741582
# every pair (argL, argR) of corresponding arguments from L and R, argL must

test-data/unit/check-overloading.test

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6501,7 +6501,7 @@ eggs = lambda: 'eggs'
65016501
reveal_type(func(eggs)) # N: Revealed type is "def (builtins.str) -> builtins.str"
65026502

65036503
spam: Callable[..., str] = lambda x, y: 'baz'
6504-
reveal_type(func(spam)) # N: Revealed type is "def (*Any, **Any) -> builtins.str"
6504+
reveal_type(func(spam)) # N: Revealed type is "def (*Any, **Any) -> Any"
65056505
[builtins fixtures/paramspec.pyi]
65066506

65076507
[case testGenericOverloadOverlapWithType]
@@ -6673,3 +6673,23 @@ c2 = MyCallable("test")
66736673
reveal_type(c2) # N: Revealed type is "__main__.MyCallable[builtins.str]"
66746674
reveal_type(c2()) # should be int # N: Revealed type is "builtins.int"
66756675
[builtins fixtures/tuple.pyi]
6676+
6677+
[case testOverloadWithStarAnyFallback]
6678+
from typing import overload, Any
6679+
6680+
class A:
6681+
@overload
6682+
def f(self, e: str) -> str: ...
6683+
@overload
6684+
def f(self, *args: Any, **kwargs: Any) -> Any: ...
6685+
def f(self, *args, **kwargs):
6686+
pass
6687+
6688+
class B:
6689+
@overload
6690+
def f(self, e: str, **kwargs: Any) -> str: ...
6691+
@overload
6692+
def f(self, *args: Any, **kwargs: Any) -> Any: ...
6693+
def f(self, *args, **kwargs):
6694+
pass
6695+
[builtins fixtures/tuple.pyi]

0 commit comments

Comments
 (0)