Skip to content

Commit d748e77

Browse files
authored
Fix reachability inference with 'isinstance(Any, Any)' (#7048)
While experimenting with some reachability inference logic, I discovered the following program does not seem to behave as expected: ```python from typing import Any from foo import A # type: ignore x: Any if isinstance(x, A): reveal_type(x) else: reveal_type(x) ``` Both branches really ought to be reachable: since both `x` and `A` are of type `Any`, we really can't say whether or not any particular branch will run. However, mypy currently instead assumes that only the first branch is reachable and does not type-check the second branch. So in this example, only the first `reveal_type(...)` outputs a note. This pull request fixes this bug.
1 parent 32c20bf commit d748e77

File tree

2 files changed

+36
-5
lines changed

2 files changed

+36
-5
lines changed

mypy/checker.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3895,12 +3895,15 @@ def conditional_type_map(expr: Expression,
38953895
second element is a map from the expression to the type it would hold
38963896
if it was not the proposed type, if any. None means bot, {} means top"""
38973897
if proposed_type_ranges:
3898-
if len(proposed_type_ranges) == 1:
3899-
proposed_type = proposed_type_ranges[0].item # Union with a single type breaks tests
3900-
else:
3901-
proposed_type = UnionType([type_range.item for type_range in proposed_type_ranges])
3898+
proposed_items = [type_range.item for type_range in proposed_type_ranges]
3899+
proposed_type = UnionType.make_simplified_union(proposed_items)
39023900
if current_type:
3903-
if (not any(type_range.is_upper_bound for type_range in proposed_type_ranges)
3901+
if isinstance(proposed_type, AnyType):
3902+
# We don't really know much about the proposed type, so we shouldn't
3903+
# attempt to narrow anything. Instead, we broaden the expr to Any to
3904+
# avoid false positives
3905+
return {expr: proposed_type}, {}
3906+
elif (not any(type_range.is_upper_bound for type_range in proposed_type_ranges)
39043907
and is_proper_subtype(current_type, proposed_type)):
39053908
# Expression is always of one of the types in proposed_type_ranges
39063909
return {}, None

test-data/unit/check-isinstance.test

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2101,6 +2101,34 @@ def f(x: Union[A, str]) -> None:
21012101
x.method_only_in_a()
21022102
[builtins fixtures/isinstance.pyi]
21032103

2104+
[case testIsinstanceIgnoredImportDualAny]
2105+
from typing import Any
2106+
from foo import Bad, OtherBad # type: ignore
2107+
x: Any
2108+
if isinstance(x, Bad):
2109+
reveal_type(x) # N: Revealed type is 'Any'
2110+
else:
2111+
reveal_type(x) # N: Revealed type is 'Any'
2112+
2113+
if isinstance(x, (Bad, OtherBad)):
2114+
reveal_type(x) # N: Revealed type is 'Any'
2115+
else:
2116+
reveal_type(x) # N: Revealed type is 'Any'
2117+
2118+
y: object
2119+
if isinstance(y, Bad):
2120+
reveal_type(y) # N: Revealed type is 'Any'
2121+
else:
2122+
reveal_type(y) # N: Revealed type is 'builtins.object'
2123+
2124+
class Ok: pass
2125+
z: Any
2126+
if isinstance(z, Ok):
2127+
reveal_type(z) # N: Revealed type is '__main__.Ok'
2128+
else:
2129+
reveal_type(z) # N: Revealed type is 'Any'
2130+
[builtins fixtures/isinstance.pyi]
2131+
21042132
[case testIsInstanceInitialNoneCheckSkipsImpossibleCasesNoStrictOptional]
21052133
# flags: --strict-optional
21062134
from typing import Optional, Union

0 commit comments

Comments
 (0)