Skip to content

Ignore promotions when simplifying unions #13781

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 3, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions mypy/checker.py
Original file line number Diff line number Diff line change
@@ -6543,11 +6543,11 @@ def conditional_types(
return proposed_type, default
elif not any(
type_range.is_upper_bound for type_range in proposed_type_ranges
) and is_proper_subtype(current_type, proposed_type):
) and is_proper_subtype(current_type, proposed_type, ignore_promotions=True):
# Expression is always of one of the types in proposed_type_ranges
return default, UninhabitedType()
elif not is_overlapping_types(
current_type, proposed_type, prohibit_none_typevar_overlap=True
current_type, proposed_type, prohibit_none_typevar_overlap=True, ignore_promotions=True
):
# Expression is never of any type in proposed_type_ranges
return UninhabitedType(), default
6 changes: 5 additions & 1 deletion mypy/meet.py
Original file line number Diff line number Diff line change
@@ -121,7 +121,11 @@ def narrow_declared_type(declared: Type, narrowed: Type) -> Type:
return original_declared
if isinstance(declared, UnionType):
return make_simplified_union(
[narrow_declared_type(x, narrowed) for x in declared.relevant_items()]
[
narrow_declared_type(x, narrowed)
for x in declared.relevant_items()
if is_overlapping_types(x, narrowed, ignore_promotions=True)
]
)
if is_enum_overlapping_union(declared, narrowed):
return original_narrowed
19 changes: 8 additions & 11 deletions mypy/subtypes.py
Original file line number Diff line number Diff line change
@@ -1672,48 +1672,45 @@ def try_restrict_literal_union(t: UnionType, s: Type) -> list[Type] | None:
return new_items


def restrict_subtype_away(t: Type, s: Type, *, ignore_promotions: bool = False) -> Type:
def restrict_subtype_away(t: Type, s: Type) -> Type:
"""Return t minus s for runtime type assertions.

If we can't determine a precise result, return a supertype of the
ideal result (just t is a valid result).

This is used for type inference of runtime type checks such as
isinstance(). Currently this just removes elements of a union type.
isinstance(). Currently, this just removes elements of a union type.
"""
p_t = get_proper_type(t)
if isinstance(p_t, UnionType):
new_items = try_restrict_literal_union(p_t, s)
if new_items is None:
new_items = [
restrict_subtype_away(item, s, ignore_promotions=ignore_promotions)
restrict_subtype_away(item, s)
for item in p_t.relevant_items()
if (
isinstance(get_proper_type(item), AnyType)
or not covers_at_runtime(item, s, ignore_promotions)
)
if (isinstance(get_proper_type(item), AnyType) or not covers_at_runtime(item, s))
]
return UnionType.make_union(new_items)
elif covers_at_runtime(t, s, ignore_promotions):
elif covers_at_runtime(t, s):
return UninhabitedType()
else:
return t


def covers_at_runtime(item: Type, supertype: Type, ignore_promotions: bool) -> bool:
def covers_at_runtime(item: Type, supertype: Type) -> bool:
"""Will isinstance(item, supertype) always return True at runtime?"""
item = get_proper_type(item)
supertype = get_proper_type(supertype)

# Since runtime type checks will ignore type arguments, erase the types.
supertype = erase_type(supertype)
if is_proper_subtype(
erase_type(item), supertype, ignore_promotions=ignore_promotions, erase_instances=True
erase_type(item), supertype, ignore_promotions=True, erase_instances=True
):
return True
if isinstance(supertype, Instance) and supertype.type.is_protocol:
# TODO: Implement more robust support for runtime isinstance() checks, see issue #3827.
if is_proper_subtype(item, supertype, ignore_promotions=ignore_promotions):
if is_proper_subtype(item, supertype, ignore_promotions=True):
return True
if isinstance(item, TypedDictType) and isinstance(supertype, Instance):
# Special case useful for selecting TypedDicts from unions using isinstance(x, dict).
2 changes: 1 addition & 1 deletion mypy/typeops.py
Original file line number Diff line number Diff line change
@@ -531,7 +531,7 @@ def _remove_redundant_union_items(items: list[Type], keep_erased: bool) -> list[
continue
# actual redundancy checks (XXX?)
if is_redundant_literal_instance(proper_item, proper_tj) and is_proper_subtype(
tj, item, keep_erased_types=keep_erased
tj, item, keep_erased_types=keep_erased, ignore_promotions=True
):
# We found a redundant item in the union.
removed.add(j)
10 changes: 5 additions & 5 deletions test-data/unit/check-classes.test
Original file line number Diff line number Diff line change
@@ -2394,9 +2394,9 @@ a: Union[int, float]
b: int
c: float

reveal_type(a + a) # N: Revealed type is "builtins.float"
reveal_type(a + b) # N: Revealed type is "builtins.float"
reveal_type(b + a) # N: Revealed type is "builtins.float"
reveal_type(a + a) # N: Revealed type is "Union[builtins.int, builtins.float]"
reveal_type(a + b) # N: Revealed type is "Union[builtins.int, builtins.float]"
reveal_type(b + a) # N: Revealed type is "Union[builtins.int, builtins.float]"
reveal_type(a + c) # N: Revealed type is "builtins.float"
reveal_type(c + a) # N: Revealed type is "builtins.float"
[builtins fixtures/ops.pyi]
@@ -2535,8 +2535,8 @@ def sum(x: Iterable[T]) -> Union[T, int]: ...
def len(x: Iterable[T]) -> int: ...

x = [1.1, 2.2, 3.3]
reveal_type(sum(x)) # N: Revealed type is "builtins.float"
reveal_type(sum(x) / len(x)) # N: Revealed type is "builtins.float"
reveal_type(sum(x)) # N: Revealed type is "Union[builtins.float, builtins.int]"
reveal_type(sum(x) / len(x)) # N: Revealed type is "Union[builtins.float, builtins.int]"
[builtins fixtures/floatdict.pyi]

[case testOperatorWithEmptyListAndSum]
12 changes: 12 additions & 0 deletions test-data/unit/check-expressions.test
Original file line number Diff line number Diff line change
@@ -974,6 +974,18 @@ def f():
main:6: error: Expression is of type "int", not "Literal[42]"
[builtins fixtures/tuple.pyi]

[case testAssertTypeNoPromoteUnion]
from typing import Union, assert_type

Scalar = Union[int, bool, bytes, bytearray]


def reduce_it(s: Scalar) -> Scalar:
return s

assert_type(reduce_it(True), Scalar)
[builtins fixtures/tuple.pyi]

-- None return type
-- ----------------

3 changes: 1 addition & 2 deletions test-data/unit/check-isinstance.test
Original file line number Diff line number Diff line change
@@ -1321,8 +1321,7 @@ def f(x: Union[A, B]) -> None:
f(x)
[builtins fixtures/isinstance.pyi]

[case testIsinstanceWithOverlappingPromotionTypes-skip]
# Currently disabled: see https://github.com/python/mypy/issues/6060 for context
[case testIsinstanceWithOverlappingPromotionTypes]
from typing import Union

class FloatLike: pass
22 changes: 11 additions & 11 deletions test-data/unit/check-unions.test
Original file line number Diff line number Diff line change
@@ -355,12 +355,12 @@ def foo(a: Union[A, B, C]):
from typing import TypeVar, Union
T = TypeVar('T')
S = TypeVar('S')
def u(x: T, y: S) -> Union[S, T]: pass
def u(x: T, y: S) -> Union[T, S]: pass

reveal_type(u(1, 2.3)) # N: Revealed type is "builtins.float"
reveal_type(u(2.3, 1)) # N: Revealed type is "builtins.float"
reveal_type(u(False, 2.2)) # N: Revealed type is "builtins.float"
reveal_type(u(2.2, False)) # N: Revealed type is "builtins.float"
reveal_type(u(1, 2.3)) # N: Revealed type is "Union[builtins.int, builtins.float]"
reveal_type(u(2.3, 1)) # N: Revealed type is "Union[builtins.float, builtins.int]"
reveal_type(u(False, 2.2)) # N: Revealed type is "Union[builtins.bool, builtins.float]"
reveal_type(u(2.2, False)) # N: Revealed type is "Union[builtins.float, builtins.bool]"
[builtins fixtures/primitives.pyi]

[case testSimplifyingUnionWithTypeTypes1]
@@ -491,15 +491,15 @@ class E:
[case testUnionSimplificationWithBoolIntAndFloat]
from typing import List, Union
l = reveal_type([]) # type: List[Union[bool, int, float]] \
# N: Revealed type is "builtins.list[builtins.float]"
# N: Revealed type is "builtins.list[Union[builtins.int, builtins.float]]"
reveal_type(l) \
# N: Revealed type is "builtins.list[Union[builtins.bool, builtins.int, builtins.float]]"
[builtins fixtures/list.pyi]

[case testUnionSimplificationWithBoolIntAndFloat2]
from typing import List, Union
l = reveal_type([]) # type: List[Union[bool, int, float, str]] \
# N: Revealed type is "builtins.list[Union[builtins.float, builtins.str]]"
# N: Revealed type is "builtins.list[Union[builtins.int, builtins.float, builtins.str]]"
reveal_type(l) \
# N: Revealed type is "builtins.list[Union[builtins.bool, builtins.int, builtins.float, builtins.str]]"
[builtins fixtures/list.pyi]
@@ -545,7 +545,7 @@ from typing import Union, Tuple, Any

a: Union[Tuple[int], Tuple[float]]
(a1,) = a
reveal_type(a1) # N: Revealed type is "builtins.float"
reveal_type(a1) # N: Revealed type is "Union[builtins.int, builtins.float]"

b: Union[Tuple[int], Tuple[str]]
(b1,) = b
@@ -558,7 +558,7 @@ from typing import Union, Tuple
c: Union[Tuple[int, int], Tuple[int, float]]
(c1, c2) = c
reveal_type(c1) # N: Revealed type is "builtins.int"
reveal_type(c2) # N: Revealed type is "builtins.float"
reveal_type(c2) # N: Revealed type is "Union[builtins.int, builtins.float]"
[builtins fixtures/tuple.pyi]

[case testUnionMultiassignGeneric]
@@ -625,7 +625,7 @@ b: Union[Tuple[float, int], Tuple[int, int]]
b1: object
b2: int
(b1, b2) = b
reveal_type(b1) # N: Revealed type is "builtins.float"
reveal_type(b1) # N: Revealed type is "Union[builtins.float, builtins.int]"
reveal_type(b2) # N: Revealed type is "builtins.int"

c: Union[Tuple[int, int], Tuple[int, int]]
@@ -639,7 +639,7 @@ d: Union[Tuple[int, int], Tuple[int, float]]
d1: object
(d1, d2) = d
reveal_type(d1) # N: Revealed type is "builtins.int"
reveal_type(d2) # N: Revealed type is "builtins.float"
reveal_type(d2) # N: Revealed type is "Union[builtins.int, builtins.float]"
[builtins fixtures/tuple.pyi]

[case testUnionMultiassignIndexed]
1 change: 1 addition & 0 deletions test-data/unit/fixtures/tuple.pyi
Original file line number Diff line number Diff line change
@@ -35,6 +35,7 @@ class slice: pass
class bool(int): pass
class str: pass # For convenience
class bytes: pass
class bytearray: pass
class unicode: pass

class list(Sequence[T], Generic[T]):