Description
Combining two isinstance
checks with and
where the first one deduces the type to Any
ignores the second one.
To Reproduce
from typing import Any, reveal_type
class A: pass
class B(A): pass
value: Any
if isinstance(value, A) and not isinstance(value, B):
reveal_type(value)
if not isinstance(value, B) and isinstance(value, A):
reveal_type(value)
https://mypy-play.net/?mypy=latest&python=3.12&gist=643066dcf62c1c2b88bb4714032fa8b5
Expected Behavior
Mypy should deduce the type as A
in both cases:
main.py:8: note: Revealed type is "__main__.A"
main.py:10: note: Revealed type is "__main__.A"
Actual Behavior
main.py:8: note: Revealed type is "__main__.A"
main.py:10: note: Revealed type is "Any"
Background
This worked before mypy 1.7 and is a regression caused by #16237. The direct reason is that Any
is now preferred in some circumstances in and_conditional_maps()
(the isinstance
in the if
):
Lines 7609 to 7618 in 8019010
I'm not sure why this is the case or why it is needed to prefer Any
here. Intuitively, it should be the other way around. The additional Any
doesn't add any new information but it shouldn't destroy the information already known from other sources.
When I remove the or isinstance(get_proper_type(m1[n1]), AnyType)
in the above code, only one testcase fails (in testNarrowingWithAnyOps
) because it explicitly tests for this Any
result, like this:
class C: ...
class D(C): ...
tp: Any
...
c1: C
if isinstance(c1, tp) and isinstance(c1, D):
reveal_type(c1) # N: Revealed type is "Any"
With the changed code this deduces the type as D
instead.