Skip to content

Commit 0c26253

Browse files
authored
Ignore overload impl when checking __OP__ and __rOP__ compatibility (#18502)
Fixes #18498
1 parent f97a56e commit 0c26253

File tree

2 files changed

+75
-2
lines changed

2 files changed

+75
-2
lines changed

mypy/checker.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@
107107
OperatorAssignmentStmt,
108108
OpExpr,
109109
OverloadedFuncDef,
110+
OverloadPart,
110111
PassStmt,
111112
PromoteExpr,
112113
RaiseStmt,
@@ -407,6 +408,11 @@ def __init__(
407408
# argument through various `checker` and `checkmember` functions.
408409
self._is_final_def = False
409410

411+
# Track when we enter an overload implementation. Some checks should not be applied
412+
# to the implementation signature when specific overloads are available.
413+
# Use `enter_overload_impl` to modify.
414+
self.overload_impl_stack: list[OverloadPart] = []
415+
410416
# This flag is set when we run type-check or attribute access check for the purpose
411417
# of giving a note on possibly missing "await". It is used to avoid infinite recursion.
412418
self.checking_missing_await = False
@@ -709,7 +715,8 @@ def _visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None:
709715
if num_abstract not in (0, len(defn.items)):
710716
self.fail(message_registry.INCONSISTENT_ABSTRACT_OVERLOAD, defn)
711717
if defn.impl:
712-
defn.impl.accept(self)
718+
with self.enter_overload_impl(defn.impl):
719+
defn.impl.accept(self)
713720
if not defn.is_property:
714721
self.check_overlapping_overloads(defn)
715722
if defn.type is None:
@@ -752,6 +759,14 @@ def _visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None:
752759
self.check_explicit_override_decorator(defn, found_method_base_classes, defn.impl)
753760
self.check_inplace_operator_method(defn)
754761

762+
@contextmanager
763+
def enter_overload_impl(self, impl: OverloadPart) -> Iterator[None]:
764+
self.overload_impl_stack.append(impl)
765+
try:
766+
yield
767+
finally:
768+
assert self.overload_impl_stack.pop() == impl
769+
755770
def extract_callable_type(self, inner_type: Type | None, ctx: Context) -> CallableType | None:
756771
"""Get type as seen by an overload item caller."""
757772
inner_type = get_proper_type(inner_type)
@@ -1278,7 +1293,11 @@ def check_func_def(
12781293
)
12791294

12801295
if name: # Special method names
1281-
if defn.info and self.is_reverse_op_method(name):
1296+
if (
1297+
defn.info
1298+
and self.is_reverse_op_method(name)
1299+
and defn not in self.overload_impl_stack
1300+
):
12821301
self.check_reverse_op_method(item, typ, name, defn)
12831302
elif name in ("__getattr__", "__getattribute__"):
12841303
self.check_getattr_method(typ, defn, name)

test-data/unit/check-classes.test

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2487,6 +2487,60 @@ reveal_type(Num3() + Num1()) # N: Revealed type is "__main__.Num3"
24872487
reveal_type(Num2() + Num3()) # N: Revealed type is "__main__.Num2"
24882488
reveal_type(Num3() + Num2()) # N: Revealed type is "__main__.Num3"
24892489

2490+
[case testReverseOperatorWithOverloads3]
2491+
from typing import Union, overload
2492+
2493+
class A:
2494+
def __mul__(self, value: A, /) -> A: ...
2495+
def __rmul__(self, value: A, /) -> A: ...
2496+
2497+
class B:
2498+
@overload
2499+
def __mul__(self, other: B, /) -> B: ...
2500+
@overload
2501+
def __mul__(self, other: A, /) -> str: ...
2502+
def __mul__(self, other: Union[B, A], /) -> Union[B, str]: pass
2503+
2504+
@overload
2505+
def __rmul__(self, other: B, /) -> B: ...
2506+
@overload
2507+
def __rmul__(self, other: A, /) -> str: ...
2508+
def __rmul__(self, other: Union[B, A], /) -> Union[B, str]: pass
2509+
2510+
[case testReverseOperatorWithOverloadsNested]
2511+
from typing import Union, overload
2512+
2513+
class A:
2514+
def __mul__(self, value: A, /) -> A: ...
2515+
def __rmul__(self, value: A, /) -> A: ...
2516+
2517+
class B:
2518+
@overload
2519+
def __mul__(self, other: B, /) -> B: ...
2520+
@overload
2521+
def __mul__(self, other: A, /) -> str: ...
2522+
def __mul__(self, other: Union[B, A], /) -> Union[B, str]: pass
2523+
2524+
@overload
2525+
def __rmul__(self, other: B, /) -> B: ...
2526+
@overload
2527+
def __rmul__(self, other: A, /) -> str: ...
2528+
def __rmul__(self, other: Union[B, A], /) -> Union[B, str]:
2529+
class A1:
2530+
def __add__(self, other: C1) -> int: ...
2531+
2532+
class B1:
2533+
def __add__(self, other: C1) -> int: ...
2534+
2535+
class C1:
2536+
@overload
2537+
def __radd__(self, other: A1) -> str: ... # E: Signatures of "__radd__" of "C1" and "__add__" of "A1" are unsafely overlapping
2538+
@overload
2539+
def __radd__(self, other: B1) -> str: ... # E: Signatures of "__radd__" of "C1" and "__add__" of "B1" are unsafely overlapping
2540+
def __radd__(self, other): pass
2541+
2542+
return ""
2543+
24902544
[case testDivReverseOperator]
24912545
# No error: __div__ has no special meaning in Python 3
24922546
class A1:

0 commit comments

Comments
 (0)