Skip to content

Commit f16e5f9

Browse files
committed
Narrow Any in and_conditional_map
1 parent e5cd1f9 commit f16e5f9

File tree

3 files changed

+33
-9
lines changed

3 files changed

+33
-9
lines changed

mypy/checker.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8625,15 +8625,19 @@ def and_conditional_maps(m1: TypeMap, m2: TypeMap, *, use_meet: bool = False) ->
86258625
the truth of e2.
86268626
"""
86278627
# Both conditions can be true; combine the information. Anything
8628-
# we learn from either conditions' truth is valid. If the same
8629-
# expression's type is refined by both conditions, we somewhat
8630-
# arbitrarily give precedence to m2 unless m1 value is Any.
8631-
# In the future, we could use an intersection type or meet_types().
8628+
# we learn from either conditions' truth is valid.
8629+
# If the same expression's type is refined by both conditions and use_meet=False, we somewhat
8630+
# arbitrarily give precedence to m2 unless m2's value is Any or m1's value is Never.
86328631
result = m2.copy()
8633-
m2_keys = {literal_hash(n2) for n2 in m2}
8632+
m2_exprs = {literal_hash(n2): n2 for n2 in m2}
86348633
for n1 in m1:
8635-
if literal_hash(n1) not in m2_keys or isinstance(
8636-
get_proper_type(m1[n1]), (AnyType, UninhabitedType)
8634+
n1_hash = literal_hash(n1)
8635+
if n1_hash not in m2_exprs or (
8636+
not use_meet
8637+
and (
8638+
isinstance(get_proper_type(m1[n1]), UninhabitedType)
8639+
or isinstance(get_proper_type(m2[m2_exprs[n1_hash]]), AnyType)
8640+
)
86378641
):
86388642
result[n1] = m1[n1]
86398643
if use_meet:

test-data/unit/check-isinstance.test

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1478,6 +1478,26 @@ def f(x: Any):
14781478
x + "foo" # E: Unsupported operand types for + ("int" and "str")
14791479
[builtins fixtures/isinstance.pyi]
14801480

1481+
[case testIsinstanceNarrowingAny]
1482+
# flags: --warn-unreachable
1483+
from typing import Any
1484+
1485+
class A: pass
1486+
class B(A): pass
1487+
1488+
def f1(x: Any) -> None:
1489+
if x and isinstance(x, A):
1490+
reveal_type(x) # N: Revealed type is "__main__.A"
1491+
if isinstance(x, A) and x:
1492+
reveal_type(x) # N: Revealed type is "__main__.A"
1493+
1494+
def f2(x: Any) -> None:
1495+
if isinstance(x, A) and not isinstance(x, B):
1496+
reveal_type(x) # N: Revealed type is "__main__.A"
1497+
if not isinstance(x, B) and isinstance(x, A):
1498+
reveal_type(x) # N: Revealed type is "__main__.A"
1499+
[builtins fixtures/isinstance.pyi]
1500+
14811501
[case testIsinstanceOfGenericClassRetainsParameters]
14821502
# flags: --warn-unreachable
14831503
from typing import List, Union

test-data/unit/check-narrowing.test

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1727,7 +1727,7 @@ reveal_type(c) # N: Revealed type is "__main__.C"
17271727

17281728
c1: C
17291729
if isinstance(c1, tp) and isinstance(c1, D):
1730-
reveal_type(c1) # N: Revealed type is "Any"
1730+
reveal_type(c1) # N: Revealed type is "__main__.D"
17311731
else:
17321732
reveal_type(c1) # N: Revealed type is "__main__.C"
17331733
reveal_type(c1) # N: Revealed type is "__main__.C"
@@ -1741,7 +1741,7 @@ reveal_type(c2) # N: Revealed type is "__main__.C"
17411741

17421742
c3: C
17431743
if isinstance(c3, D) and isinstance(c3, tp):
1744-
reveal_type(c3) # N: Revealed type is "Any"
1744+
reveal_type(c3) # N: Revealed type is "__main__.D"
17451745
else:
17461746
reveal_type(c3) # N: Revealed type is "__main__.C"
17471747
reveal_type(c3) # N: Revealed type is "__main__.C"

0 commit comments

Comments
 (0)