-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Signatures of overloaded operators are incompatible with super type #4985
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
Comments
It looks like this is intentional. The point is if (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 TBH, I don't see an immediate reason why this is unsafe ( |
I think I might understand what's going on here: I believe mypy is correct in saying that overloads don't necessarily interact in a typesafe way with operator dunder methods. Here's an example: it's similar to the original code snippet, except that I changed the signature of B slightly.
That said, I think the specific example OP originally posted is typesafe: I think The problem now is that mypy doesn't understand how to perform subtype checks against overloaded functions: if you look at the So, here's what I think a fix might look like:
Step 1 is obviously the hard part, though I sort of have an idea of how this might be done: we take the overload and remove alternatives where the param and return types are all strictly narrower so that all of the alternatives are distinct from one another, then take the callable/alternatives in the left and make sure they're a subset of the right. Disclaimer: I haven't really thought about this too much, so I could be totally wrong about all of the above. |
Hm, I think you mean |
@Michael0x2a As I understand you are going to take care of this one during your re-working of overloads. |
Also this turns out to be a duplicate of #1987 but this one has more up-to-date discussion, so I closed the other one. |
@Michael0x2a Could you clarify what what do you mean by
As I mentioned above there is already a decent check for overload subtypes. Also in your example |
Another comment: if I will not use overload at all, it seems to me there is still the problem you shown. For example: class B(A):
def __add__(self, x: Union[A, Other]) -> A: ... has exactly the same problem. So it looks like this problem has nothing to do with overloads. |
@ilevkivskyi -- yes, I think I misdiagnosed the underlying bug in my first post above. I was mistakenly under the assumption that both subtype visitors were missing logic for whether an overloaded function is a subtype of another callable, when in reality only the proper subtype visitor was missing that logic. My newest hypothesis is that the existing subtype logic for overloads is buggy or too over-zealous in some way, though I'm not 100% sure of that -- I tried reading through the existing implementation in SubtypeVisitor a little while back, and TBH found it a bit difficult to follow. My new plan is at some point to audit the subtyping logic for overloads and simplify it/re-think it if possible. (In particular, I'm hoping that the more tighter checks on overload definitions will let us remove/simplify some of the subtyping logic.) My latest operator overlapping PR only changes how mypy behaves when a reverse operator is present. The examples up above only contain |
Huh, really? That's confusing. I guess just about the only thing I'm certain about at this point then is that my recent refactor is unrelated to the problems raised in this PR. |
|
Just to clarify, I didn't think much about this, but it seems to me that the fact that |
I don't want to say your overlap PR did something bad (or insufficiently good) :-) I really like it. What I want to say is that fixing the original problem in this issue while avoiding the problem you shown above (and its union variant) might look like this:
Does this make sense to you? |
OK, after some more thinking my solution is problematic in incremental mode (and generally looks awkward). Another option (a bit sad) is to have special stricter subtyping checks for dunders (something like the domain shouldn't be wider in the subtype). |
On the other hand, "refining" the type of |
Oh, I didn't mean to come off as defensive -- it's just that the very original post doesn't contain an
idk if I like this approach -- it seems expensive, and I also don't know how well it'll play when mixing together different libraries. (E.g. what if the library defines a bunch of stuff that includes
I think this is probably the most pragmatic solution, yeah.
Yeah, agreed. The only real use-case I've seen for doing weird stuff with operators is when you're trying to create a custom DSL (e.g. something like what pyparsing does) -- but I don't think it's worth investing too much time into supporting these types of use cases (at least, until somebody complains, at least). |
(I still think it's worth cleaning up the subtyping for overloads code at some point though -- setting everything aside, it's a bit difficult to read IMO.) |
Yes, I agree it is hard to read. Plus we need to copy it to |
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.
Sorry for necroposting, but it seemed relevant to the issue:
There is no error without overloaded variants, So I can't undestand where problem is. |
Hm, for me, with mypy 0.770 I still have the problem
for this code: from typing import TypeVar, overload, Union
T = TypeVar("T", bound="A")
class A:
def __mul__(self: T, other: int) -> T:
raise NotImplementedError()
U = TypeVar("U", bound="B")
class B(A):
@overload
def __mul__(self: U, other: U) -> U:
...
@overload
def __mul__(self: U, other: int) -> U:
...
def __mul__(self: U, other: Union[int, U]) -> U:
raise NotImplementedError() where this code passes validation if
I am not sure I understand that comment, does it mean that the |
Yes, exactly, this is because with |
Could this error be relaxed if the class were marked |
These methods have a generic `type: ignore` with no clarification why. The issue is we need the ignore here because otherwise `mypy` will give this error: > Overloaded operator methods can't have wider argument types in > overrides The problem seems to be when the other type implements an **incompatible** __rmul__ method, which is not the case here, so we should be safe. Please see this example: https://github.com/python/mypy/blob/c26f1297d4f19d2d1124a30efc97caebb8c28616/test-data/unit/check-overloading.test#L4738C1-L4769C55 And a discussion in a mypy issue here: python/mypy#4985 (comment) For more information. This commit uses a more specific `type: ignore[override]` and add a comment clarifying this. Signed-off-by: Leandro Lucarella <[email protected]>
These methods have a generic `type: ignore` with no clarification why. The issue is we need the ignore here because otherwise `mypy` will give this error: > Overloaded operator methods can't have wider argument types in > overrides The problem seems to be when the other type implements an **incompatible** __rmul__ method, which is not the case here, so we should be safe. Please see this example: https://github.com/python/mypy/blob/c26f1297d4f19d2d1124a30efc97caebb8c28616/test-data/unit/check-overloading.test#L4738C1-L4769C55 And a discussion in a mypy issue here: python/mypy#4985 (comment) For more information. This commit uses a more specific `type: ignore[override]` and add a comment clarifying this. Signed-off-by: Leandro Lucarella <[email protected]>
These methods have a generic `type: ignore` with no clarification why. The issue is we need the ignore here because otherwise `mypy` will give this error: > Overloaded operator methods can't have wider argument types in > overrides The problem seems to be when the other type implements an **incompatible** __rmul__ method, which is not the case here, so we should be safe. Please see this example: https://github.com/python/mypy/blob/c26f1297d4f19d2d1124a30efc97caebb8c28616/test-data/unit/check-overloading.test#L4738C1-L4769C55 And a discussion in a mypy issue here: python/mypy#4985 (comment) For more information. This commit uses a more specific `type: ignore[override]` and add a comment clarifying this. Signed-off-by: Leandro Lucarella <[email protected]>
I've noticed this problem, very similar to #3262 , except with overloaded operators.
Originally, #3262 made it so that code like this:
Wouldn't typecheck.
#3263 appears to have fixed this. Unfortunately, while this works for normal method signatures, it doesn't appear to work for overloaded operators. So the very similar code:
Still fails to typecheck, with the error message:
I can't think of any reason why this should be a problem with operators, although maybe someone else can explain.
I'm on Python 3.6.3 and MyPy 0.600+dev (master branch from git)
The text was updated successfully, but these errors were encountered: