You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Overriding __eq__ can break Optional/Literal narrowing assumptions #15764
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 testcasefromtypingimportOptional, Any, reveal_typeclassA:
def__eq__(self, other: Any) ->bool:
returnotherisNoneval: Optional[A] =Noneifval==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 testcasefromtypingimportAny, reveal_type, LiteralclassA:
passclassB(A):
def__eq__(self, other: Any) ->bool:
returnother=='blab'val: A|Literal['blab'] =B()
ifval=='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.
I'm in the process of rewriting mypy's type narrowing logic.
I originally fixed the first form of unsoundness you mention, but in #20756 we are potentially reintroducing unsoundness specifically for None.
The potential existence of a subclass with custom __eq__ is a hole that we can't really close (although my experience with this rewrites suggests it might be less completely terrible than I would have thought going in to it, as long as you model things you know the exact type for well, like literals).
Activity
hauntsaninja commentedon Feb 6, 2026
I'm in the process of rewriting mypy's type narrowing logic.
I originally fixed the first form of unsoundness you mention, but in #20756 we are potentially reintroducing unsoundness specifically for None.
The potential existence of a subclass with custom
__eq__is a hole that we can't really close (although my experience with this rewrites suggests it might be less completely terrible than I would have thought going in to it, as long as you model things you know the exact type for well, like literals).Unsoundly narrow away from None with custom eq (python#20756)