Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can't overload __add__ for subclass #1987

Closed
gvanrossum opened this issue Aug 5, 2016 · 7 comments
Closed

Can't overload __add__ for subclass #1987

gvanrossum opened this issue Aug 5, 2016 · 7 comments
Labels
bug mypy got something wrong priority-2-low

Comments

@gvanrossum
Copy link
Member

gvanrossum commented Aug 5, 2016

Consider the following working code:

from typing import overload

class S:
    @overload
    def __add__(self, arg: 'A') -> 'S': ...
    @overload
    def __add__(self, arg: 'S') -> 'S': ...

class A(S):
    @overload
    def __add__(self, arg: 'A') -> 'A': ...
    @overload
    def __add__(self, arg: 'S') -> 'S': ...

Think of 'S' as 'str' and 'A' as 'ascii', a subclass used only for literals containing only ASCII characters. The idea is that adding ASCII to ASCII produces ASCII, but all other combinations produce just strings.

The subclass 'A' redefines the method __add__ to express this idea. This version of the code works. However, the base class contains redundant overloads: 'A' is a subclass of 'S' so the base class is equivalent to the following, which doesn't use overloads:

class S:
    def __add__(self, arg: 'S') -> 'S': ...

And here's the rub: with this definition of 'S', the definition of __add__ in 'A' (still using overloads) is rejected:

__tmp__.py:10: error: Signature of "__add__" incompatible with supertype "S"

(The error points to the first @overload, but it seems to apply to the entire set of overloads.)

Now here's the real rub: if I change __add__ consistently to method, the error goes away!

[UPDATE I can't repro this for __radd__ -- it seems unique to __add__.]

@gvanrossum
Copy link
Member Author

@JukkaL Any idea what could be causing this?

@JukkaL
Copy link
Collaborator

JukkaL commented Aug 11, 2016

It's this code in checker.py (in check_override):

        elif (not isinstance(original, Overloaded) and
              isinstance(override, Overloaded) and
              name in nodes.reverse_op_methods.keys()):
            # Operator method overrides cannot introduce overloading, as
            # this could be unsafe with reverse operator methods.
            fail = True         <==== here

This seems to be overly conservative. I'd suggest just removing the check and seeing if this breaks something. I'm sure there is some edge case where things can go wrong, but it's probably not important enough to worry about it.

@gnprice gnprice added bug mypy got something wrong and removed question labels Aug 11, 2016
@gnprice gnprice added this to the Undetermined priority milestone Aug 11, 2016
@gnprice
Copy link
Collaborator

gnprice commented Aug 11, 2016

Given that it applies only in the special case of a method like __add__, I think this is clearly a bug of some nature.

@elazarg
Copy link
Contributor

elazarg commented Sep 19, 2016

@JukkaL there is a test case for this behavior, which I don't understand. I left some questions there (let me know if that's not acceptable). Or perhaps that's what you mean when you say it's too conservative.

@henryJack
Copy link

henryJack commented Oct 4, 2017

Any updates on this? Overloaded methods like __mul__ and __add__ still mess with mypy. e.g.

class A:
    def __mul__(self, other: A) -> 'B':
        ...
    def __add__(self, other: A) -> 'C':
        ...

type(A() * A()) is B and type(A() + A()) is C but mypy still throws error.

If you want types, the work around would be to manually define the add and multiply methods to the class.

class A:
    def multiply(self, other: A) -> 'B':
        ...
    def add(self, other: A) -> 'C':
        ...

...then doing A().add(A()) and A().multiple(A()) will make mypy happy

@gvanrossum
Copy link
Member Author

Can you show a complete example? I tried this and it gives me the expected output, no errors:

class B: pass
class C: pass
class A:
    def __mul__(self, other: A) -> 'B':
        ...
    def __add__(self, other: A) -> 'C':
        ...
reveal_type(A()*A())  # B
reveal_type(A()+A())  # C

@ilevkivskyi
Copy link
Member

Superseded by #4985

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong priority-2-low
Projects
None yet
Development

No branches or pull requests

6 participants