Skip to content

Allow overloaded overrides if they don't extend domain #5505

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

Merged
merged 2 commits into from
Aug 23, 2018

Conversation

ilevkivskyi
Copy link
Member

Fixes #4985

Mypy previously disallowed introducing overloads for operator methods in overrides because this can interact unsafely with reverse methods. However it looks like this is safe for the situations where the domain of the override is not extended.

In fact the same unsafety exists for non-overloaded op-methods (see example in #4985 (comment)), but it looks like we are fine with this, I have found few existing tests explicitly testing for this.

Copy link
Collaborator

@Michael0x2a Michael0x2a left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, LGTM -- I did have one or two questions regarding edge cases though.

@@ -1341,6 +1341,20 @@ def check_method_override_for_base_with_name(
self.msg.signature_incompatible_with_supertype(
defn.name(), name, base.name(), context)

def get_op_other_domain(self, tp: FunctionLike) -> Optional[Type]:
if isinstance(tp, CallableType):
if tp.arg_kinds and tp.arg_kinds[0] == ARG_POS:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we care what happens if the user does def __add__(self, *args: Foo) -> Foo or something?

It's technically not illegal, and I think we even have one or two tests existing tests somewhere that check for this sort of thing in operator methods.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we don't care for now, I would say this kind of situations is already rare, and detecting a rare unsafety due to extended domain is essentially rare ** 2.

mypy/checker.py Outdated
@@ -1357,15 +1371,18 @@ def check_override(self, override: FunctionLike, original: FunctionLike,
"""
# Use boolean variable to clarify code.
fail = False
dunder_note = False
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we rename this variable to op_method_wider_note or something else that makes it clear this is related to operator methods, not dunder methods in general? (E.g. we don't display this note for dunder methods like __len__ or __init__.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point!

from typing import overload

class A:
def f(self, x : 'A') -> 'A': ...
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should also add a unit test to see what happens if the parent class is also defining an overloaded operator? Up to you.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh wait, it looks like I used f instead of __add__, I will switch to __add__. Btw the test case you mention already exists in other file, so I will just keep this one.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually this test gets updated in this PR, it was passing but now fails, see testOverloadedOperatorMethodOverrideWithNewItem.

isinstance(override, Overloaded) and
self.is_forward_op_method(name)):
# Operator method overrides cannot introduce overloading, as
elif isinstance(override, Overloaded) and self.is_forward_op_method(name):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While we're at it, do you think we also need to perform this same check for reverse operator methods?

(I'm actually not sure myself. I spent a little bit of time trying to come up with an example but couldn't think of anything, but that's not a particularly convincing argument.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not 100% sure, but I think we don't need this for __rX__. It seems to me that we can check unsafe overlap only for one, and not extending the domain only for other. This however probably only applies for simple cases where the runtime logic is simple: first check __X__ if it doesn't work, go to __rX__. But then again, the rare ** 2 argument probably applies here.

@ilevkivskyi
Copy link
Member Author

@Michael0x2a Thanks for review! Are you OK with my responses to comments?

@Michael0x2a
Copy link
Collaborator

Yup, looks good to me -- I'll go ahead and merge this in.

@Michael0x2a Michael0x2a merged commit fec59ed into python:master Aug 23, 2018
@ilevkivskyi ilevkivskyi deleted the overload-override branch April 22, 2020 06:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants