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
Show file tree
Hide file tree
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
Expand Up @@ -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
Expand Down
6 changes: 5 additions & 1 deletion mypy/meet.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 8 additions & 11 deletions mypy/subtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
2 changes: 1 addition & 1 deletion mypy/typeops.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
10 changes: 5 additions & 5 deletions test-data/unit/check-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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]
Expand Down
12 changes: 12 additions & 0 deletions test-data/unit/check-expressions.test
Original file line number Diff line number Diff line change
Expand Up @@ -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
-- ----------------

Expand Down
3 changes: 1 addition & 2 deletions test-data/unit/check-isinstance.test
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 11 additions & 11 deletions test-data/unit/check-unions.test
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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
Expand All @@ -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]
Expand Down Expand Up @@ -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]]
Expand All @@ -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]
Expand Down
1 change: 1 addition & 0 deletions test-data/unit/fixtures/tuple.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -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]):
Expand Down