-
-
Notifications
You must be signed in to change notification settings - Fork 3k
Description
Bug Report
While fixing #15759 (#15760), another unsoundness case occurred to me. By overriding __eq__
in surprising ways, it's not hard to produce unsound results from mypy. Playground link
# Optional narrowing testcase
from typing import Optional, Any, reveal_type
class A:
def __eq__(self, other: Any) -> bool:
return other is None
val: Optional[A] = None
if val == A():
reveal_type(val) # ❌ N: Revealed type is "__main__.A"
# ✅ Runtime type is 'NoneType'
It would be possible to check for the existence of __eq__
method. But if any subclasses override __eq__
, it's still possible to override __eq__
in a subclass and break it.
Literal+Union narrowing seems to already perform such an __eq__
check, but we can still defeat it by subclassing. Playground link
# Literal narrowing testcase
from typing import Any, reveal_type, Literal
class A:
pass
class B(A):
def __eq__(self, other: Any) -> bool:
return other == 'blab'
val: A | Literal['blab'] = B()
if val == 'blab':
reveal_type(val) # ❌ N: Revealed type is "Literal['blab']"
# ✅ Runtime type is 'B'
Possible Solutions
I couldn't find documentation or prior discussion about this. But I'm guessing these don't come as a surprise to mypy developers?
Possibly the right approach for these kinds of errors could be to simply document the assumptions that mypy relies for its type narrowing? That could also serve as a guide for future mypy developers to explain, what kinds of assumptions we should or shouldn't rely on.
Your Environment
- Mypy version used: 1.4.1