Description
As a part of my adventures in attempting to add semi-usable stubs for the fractions and statistics modules, I ran into an edge case involving the __add__
and __radd__
methods (or any pair of operator methods). Here is a simplified version of decimal.Decimal
:
from typing import Union
class FakeDecimal1:
def __add__(self, other: Union[int, FakeDecimal1]) -> FakeDecimal1:
return FakeDecimal1()
def __radd__(self, other: Union[int, FakeDecimal1]) -> FakeDecimal1:
return FakeDecimal1()
When typechecking this, I got the following (and somewhat cryptic) error message:
experiments\test.py: note: In member "__radd__" of class "FakeDecimal":
experiments\test.py:6: error: Forward operator "__add__" is not callable
It seems mypy wanted me to do this instead:
from typing import Union
class FakeDecimal2:
def __add__(self, other: Union[int, FakeDecimal2]) -> FakeDecimal2:
return FakeDecimal2()
def __radd__(self, other: int) -> FakeDecimal2:
return FakeDecimal2()
However, the following code which uses overrides instead of Union does not result in any errors, which feels inconsistent:
class FakeDecimal3:
@overload
def __add__(self, other: int) -> FakeDecimal3:
return FakeDecimal2()
@overload
def __add__(self, other: FakeDecimal3) -> FakeDecimal3:
return FakeDecimal3()
@overload
def __radd__(self, other: int) -> FakeDecimal3:
return FakeDecimal2()
@overload
def __radd__(self, other: FakeDecimal3) -> FakeDecimal3:
return FakeDecimal2()
After some digging, it appeared to me that the reason for this behavior is because the check_overlapping_op_methods
within checker.py
handles the case where forward_type
is either a CallableType
, Overloaded
, or AnyType
, but not the case where it's a UnionType
.
Is this intentional, or an oversight? I think this is a bug, mainly since if mypy accepts version 3, it ought to accept verison 1? Or perhaps the fact that __radd__
should never need to accept a value of the same type as its instance means that the version 2 is more correct and versions 1 and 3 ought to be rejected?
(For what it's worth, adding a new case to make UnionType
do the same thing as the Overloaded
case makes version 1 pass.)
This is perhaps related: #1442