-
-
Notifications
You must be signed in to change notification settings - Fork 3k
Fixes mypy crash on protocol with contravariant var #11135
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
Conversation
inst = mypy.subtypes.find_member(member, instance, subtype) | ||
temp = mypy.subtypes.find_member(member, template, subtype) | ||
assert inst is not None and temp is not None | ||
if inst is None or temp is None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not familiar with this code. Do you know why it is safe to ignore it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a pretty old piece from initial protocols commit c55e48b
I was not sure if this is safe to ignore or not. All code / tests I've tried showed that nothing has changed.
But, from personal experience finding bugs in constraints solver is pretty hard.
We can ask @ilevkivskyi if he knows some examples / cases where this can backfire.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a basic consistency check. One should never get here for something that can never be a protocol implementation. So I don't think this fixes the actual issue. Couple other comments:
- Variance in the place where it is used in the original repro should be totally irrelevant. Variance is only relevant for class definition type variables. It should be irrelevant in generic function/method type variables (including self-types). Variance is a property of a class, not a property of a type variable. If the repro depends on variance we have a much bigger problem.
- I think the actual culprit is overload on self-type. This is a relatively new feature (much newer than protocols), and it requires some special handling in
bind_self()
(and/or somewhere around). I betfind_member()
doesn't handle this logic correctly. So you should either fix that, or better refactorfind_member()
somehow to use the same logic.
This also happens with: from typing import overload, Protocol, TypeVar
I = TypeVar('I')
V = TypeVar('V')
class C(Protocol[I]): # E: Invariant type variable "I" used in protocol where covariant one is expected
def __abs__(self: 'C[V]') -> 'C[V]':
...
@overload
def f(self, q: int) -> int:
...
@overload
def f(self, q: float) -> 'C[float]':
... Related traceback:
|
This also fails: from typing import overload, Protocol, TypeVar
I = TypeVar('I')
V = TypeVar('V')
class C(Protocol[I]):
def __abs__(self: 'C[V]') -> 'C[V]':
...
class D:
@overload
def f(self, q: int) -> int:
...
@overload
def f(self, q: float) -> 'C[float]':
...
x: C = D() Traceback is similar:
|
Yeah, it looks like it fails for generic self-types in general: from typing import Protocol, TypeVar
I = TypeVar('I', covariant=True)
V = TypeVar('V')
class C(Protocol[I]):
def g(self: 'C[V]') -> 'C[V]':
...
class D:
pass
x: C = D() Btw IIRC they were implemented approx. at the same time as overload on self-types. So the problems happen with all "tricky" self-types. |
I think I understand the problem: |
@ilevkivskyi you are ahead of me 🙂 Copy paste from my commit comment:
|
Here's what was wrong: 1. When checking if some type is a subtype of a protocol, we iterate over all its members 2. When we are inside `is_protocol_implementation` we set `.assuming` or `.assuming_proper` attributes and continue to check the other members 3. While checking methods with annotated `self` type with a protocol itself, we recurse into `is_protocol_implementation` once again 4. It always returns `True` in this case, because `assuming` is set in the context above 5. Because `is_protocol_implementation` is set to `True`, we dive into `infer_constraints_from_protocol_members` with a type, which is not really a protocol implementation 6. Sanity check breaks! Solution: return empty constraints instead of raising `AssertionError`
Thanks, @ilevkivskyi! Your help helped a lot 🙂 |
Closes #11020