Skip to content

Commit fecb496

Browse files
refactored conditional_types and restrict_subtype_away
1 parent 246e461 commit fecb496

File tree

4 files changed

+53
-43
lines changed

4 files changed

+53
-43
lines changed

mypy/checker.py

Lines changed: 39 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -8001,7 +8001,10 @@ def conditional_types(
80018001
None means no new information can be inferred.
80028002
If default is set it is returned instead.
80038003
"""
8004-
if proposed_type_ranges and len(proposed_type_ranges) == 1:
8004+
if not proposed_type_ranges:
8005+
return UninhabitedType(), default
8006+
8007+
if len(proposed_type_ranges) == 1:
80058008
# expand e.g. bool -> Literal[True] | Literal[False]
80068009
target = proposed_type_ranges[0].item
80078010
target = get_proper_type(target)
@@ -8012,7 +8015,11 @@ def conditional_types(
80128015
current_type = try_expanding_sum_type_to_union(current_type, enum_name)
80138016

80148017
current_type = get_proper_type(current_type)
8015-
if isinstance(current_type, UnionType) and (default == current_type):
8018+
if (
8019+
isinstance(current_type, UnionType)
8020+
and not any(tr.is_upper_bound for tr in proposed_type_ranges)
8021+
and (default in (current_type, None))
8022+
):
80168023
# factorize over union types
80178024
# if we try to narrow A|B to C, we instead narrow A to C and B to C, and
80188025
# return the union of the results
@@ -8027,47 +8034,39 @@ def conditional_types(
80278034
]
80288035
# separate list of tuples into two lists
80298036
yes_types, no_types = zip(*result)
8030-
yes_type = make_simplified_union([t for t in yes_types if t is not None])
8031-
no_type = restrict_subtype_away(
8032-
current_type, yes_type, consider_runtime_isinstance=consider_runtime_isinstance
8033-
)
8034-
8035-
return yes_type, no_type
8036-
8037-
if proposed_type_ranges:
8037+
proposed_type = make_simplified_union([t for t in yes_types if t is not None])
8038+
else:
80388039
proposed_items = [type_range.item for type_range in proposed_type_ranges]
80398040
proposed_type = make_simplified_union(proposed_items)
8040-
if isinstance(proposed_type, AnyType):
8041-
# We don't really know much about the proposed type, so we shouldn't
8042-
# attempt to narrow anything. Instead, we broaden the expr to Any to
8043-
# avoid false positives
8044-
return proposed_type, default
8045-
elif not any(
8046-
type_range.is_upper_bound for type_range in proposed_type_ranges
8047-
) and is_proper_subtype(current_type, proposed_type, ignore_promotions=True):
8048-
# Expression is always of one of the types in proposed_type_ranges
8049-
return default, UninhabitedType()
8050-
elif not is_overlapping_types(current_type, proposed_type, ignore_promotions=True):
8051-
# Expression is never of any type in proposed_type_ranges
8052-
return UninhabitedType(), default
8053-
else:
8054-
# we can only restrict when the type is precise, not bounded
8055-
proposed_precise_type = UnionType.make_union(
8056-
[
8057-
type_range.item
8058-
for type_range in proposed_type_ranges
8059-
if not type_range.is_upper_bound
8060-
]
8061-
)
8062-
remaining_type = restrict_subtype_away(
8063-
current_type,
8064-
proposed_precise_type,
8065-
consider_runtime_isinstance=consider_runtime_isinstance,
8066-
)
8067-
return proposed_type, remaining_type
8041+
8042+
if isinstance(proposed_type, AnyType):
8043+
# We don't really know much about the proposed type, so we shouldn't
8044+
# attempt to narrow anything. Instead, we broaden the expr to Any to
8045+
# avoid false positives
8046+
return proposed_type, default
8047+
elif not any(
8048+
type_range.is_upper_bound for type_range in proposed_type_ranges
8049+
) and is_proper_subtype(current_type, proposed_type, ignore_promotions=True):
8050+
# Expression is always of one of the types in proposed_type_ranges
8051+
return default, UninhabitedType()
8052+
elif not is_overlapping_types(current_type, proposed_type, ignore_promotions=True):
8053+
# Expression is never of any type in proposed_type_ranges
8054+
return UninhabitedType(), default
80688055
else:
8069-
# An isinstance check, but we don't understand the type
8070-
return current_type, default
8056+
# we can only restrict when the type is precise, not bounded
8057+
proposed_precise_type = UnionType.make_union(
8058+
[
8059+
type_range.item
8060+
for type_range in proposed_type_ranges
8061+
if not type_range.is_upper_bound
8062+
]
8063+
)
8064+
remaining_type = restrict_subtype_away(
8065+
current_type,
8066+
proposed_precise_type,
8067+
consider_runtime_isinstance=consider_runtime_isinstance,
8068+
)
8069+
return proposed_type, remaining_type
80718070

80728071

80738072
def conditional_types_to_typemaps(

mypy/subtypes.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2094,6 +2094,8 @@ def restrict_subtype_away(t: Type, s: Type, *, consider_runtime_isinstance: bool
20942094
isinstance(). Currently, this just removes elements of a union type.
20952095
"""
20962096
p_t = get_proper_type(t)
2097+
s_t = get_proper_type(s)
2098+
20972099
if isinstance(p_t, UnionType):
20982100
new_items = try_restrict_literal_union(p_t, s)
20992101
if new_items is None:
@@ -2107,7 +2109,15 @@ def restrict_subtype_away(t: Type, s: Type, *, consider_runtime_isinstance: bool
21072109
[item for item in new_items if not isinstance(get_proper_type(item), UninhabitedType)]
21082110
)
21092111
elif isinstance(p_t, TypeVarType):
2110-
return p_t.copy_modified(upper_bound=restrict_subtype_away(p_t.upper_bound, s))
2112+
if isinstance(s_t, TypeVarType):
2113+
upper_bound = restrict_subtype_away(p_t.upper_bound, s_t.upper_bound)
2114+
upper_bound = get_proper_type(upper_bound)
2115+
return (
2116+
upper_bound
2117+
if isinstance(upper_bound, UninhabitedType)
2118+
else p_t.copy_modified(upper_bound=upper_bound)
2119+
)
2120+
return p_t.copy_modified(upper_bound=restrict_subtype_away(p_t.upper_bound, s_t))
21112121

21122122
if consider_runtime_isinstance:
21132123
if covers_at_runtime(t, s):

test-data/unit/check-isinstance.test

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1483,11 +1483,12 @@ def f(x: Union[int, A], a: Type[A]) -> None:
14831483
[builtins fixtures/isinstancelist.pyi]
14841484

14851485
[case testIsInstanceWithEmtpy2ndArg]
1486+
# flags: --warn-unreachable
14861487
from typing import Union
14871488

14881489
def f(x: Union[int, str]) -> None:
14891490
if isinstance(x, ()):
1490-
reveal_type(x) # N: Revealed type is "Union[builtins.int, builtins.str]"
1491+
reveal_type(x) # E: Statement is unreachable
14911492
else:
14921493
reveal_type(x) # N: Revealed type is "Union[builtins.int, builtins.str]"
14931494
[builtins fixtures/isinstancelist.pyi]

test-data/unit/check-narrowing.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2639,7 +2639,7 @@ def baz(item: Base) -> None:
26392639

26402640
reveal_type(item) # N: Revealed type is "Union[__main__.<subclass of "__main__.Base" and "__main__.FooMixin">, __main__.<subclass of "__main__.Base" and "__main__.BarMixin">]"
26412641
if isinstance(item, FooMixin):
2642-
reveal_type(item) # N: Revealed type is "__main__.FooMixin"
2642+
reveal_type(item) # N: Revealed type is "__main__.<subclass of "__main__.Base" and "__main__.FooMixin">"
26432643
item.foo()
26442644
else:
26452645
reveal_type(item) # N: Revealed type is "__main__.<subclass of "__main__.Base" and "__main__.BarMixin">"

0 commit comments

Comments
 (0)