Skip to content

Commit 08c566c

Browse files
authored
Ignore promotions when simplifying unions (#13781)
Fixes #13760 Fixes #6060 Fixes #12824 This is a right thing to do, but let's what `mypy_primer` will be. This also required re-applying #6181 (which is also a right thing to do) otherwise some tests fail.
1 parent c5db2b6 commit 08c566c

9 files changed

+46
-33
lines changed

mypy/checker.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -6550,11 +6550,11 @@ def conditional_types(
65506550
return proposed_type, default
65516551
elif not any(
65526552
type_range.is_upper_bound for type_range in proposed_type_ranges
6553-
) and is_proper_subtype(current_type, proposed_type):
6553+
) and is_proper_subtype(current_type, proposed_type, ignore_promotions=True):
65546554
# Expression is always of one of the types in proposed_type_ranges
65556555
return default, UninhabitedType()
65566556
elif not is_overlapping_types(
6557-
current_type, proposed_type, prohibit_none_typevar_overlap=True
6557+
current_type, proposed_type, prohibit_none_typevar_overlap=True, ignore_promotions=True
65586558
):
65596559
# Expression is never of any type in proposed_type_ranges
65606560
return UninhabitedType(), default

mypy/meet.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,11 @@ def narrow_declared_type(declared: Type, narrowed: Type) -> Type:
121121
return original_declared
122122
if isinstance(declared, UnionType):
123123
return make_simplified_union(
124-
[narrow_declared_type(x, narrowed) for x in declared.relevant_items()]
124+
[
125+
narrow_declared_type(x, narrowed)
126+
for x in declared.relevant_items()
127+
if is_overlapping_types(x, narrowed, ignore_promotions=True)
128+
]
125129
)
126130
if is_enum_overlapping_union(declared, narrowed):
127131
return original_narrowed

mypy/subtypes.py

+8-11
Original file line numberDiff line numberDiff line change
@@ -1677,48 +1677,45 @@ def try_restrict_literal_union(t: UnionType, s: Type) -> list[Type] | None:
16771677
return new_items
16781678

16791679

1680-
def restrict_subtype_away(t: Type, s: Type, *, ignore_promotions: bool = False) -> Type:
1680+
def restrict_subtype_away(t: Type, s: Type) -> Type:
16811681
"""Return t minus s for runtime type assertions.
16821682
16831683
If we can't determine a precise result, return a supertype of the
16841684
ideal result (just t is a valid result).
16851685
16861686
This is used for type inference of runtime type checks such as
1687-
isinstance(). Currently this just removes elements of a union type.
1687+
isinstance(). Currently, this just removes elements of a union type.
16881688
"""
16891689
p_t = get_proper_type(t)
16901690
if isinstance(p_t, UnionType):
16911691
new_items = try_restrict_literal_union(p_t, s)
16921692
if new_items is None:
16931693
new_items = [
1694-
restrict_subtype_away(item, s, ignore_promotions=ignore_promotions)
1694+
restrict_subtype_away(item, s)
16951695
for item in p_t.relevant_items()
1696-
if (
1697-
isinstance(get_proper_type(item), AnyType)
1698-
or not covers_at_runtime(item, s, ignore_promotions)
1699-
)
1696+
if (isinstance(get_proper_type(item), AnyType) or not covers_at_runtime(item, s))
17001697
]
17011698
return UnionType.make_union(new_items)
1702-
elif covers_at_runtime(t, s, ignore_promotions):
1699+
elif covers_at_runtime(t, s):
17031700
return UninhabitedType()
17041701
else:
17051702
return t
17061703

17071704

1708-
def covers_at_runtime(item: Type, supertype: Type, ignore_promotions: bool) -> bool:
1705+
def covers_at_runtime(item: Type, supertype: Type) -> bool:
17091706
"""Will isinstance(item, supertype) always return True at runtime?"""
17101707
item = get_proper_type(item)
17111708
supertype = get_proper_type(supertype)
17121709

17131710
# Since runtime type checks will ignore type arguments, erase the types.
17141711
supertype = erase_type(supertype)
17151712
if is_proper_subtype(
1716-
erase_type(item), supertype, ignore_promotions=ignore_promotions, erase_instances=True
1713+
erase_type(item), supertype, ignore_promotions=True, erase_instances=True
17171714
):
17181715
return True
17191716
if isinstance(supertype, Instance) and supertype.type.is_protocol:
17201717
# TODO: Implement more robust support for runtime isinstance() checks, see issue #3827.
1721-
if is_proper_subtype(item, supertype, ignore_promotions=ignore_promotions):
1718+
if is_proper_subtype(item, supertype, ignore_promotions=True):
17221719
return True
17231720
if isinstance(item, TypedDictType) and isinstance(supertype, Instance):
17241721
# Special case useful for selecting TypedDicts from unions using isinstance(x, dict).

mypy/typeops.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -531,7 +531,7 @@ def _remove_redundant_union_items(items: list[Type], keep_erased: bool) -> list[
531531
continue
532532
# actual redundancy checks (XXX?)
533533
if is_redundant_literal_instance(proper_item, proper_tj) and is_proper_subtype(
534-
tj, item, keep_erased_types=keep_erased
534+
tj, item, keep_erased_types=keep_erased, ignore_promotions=True
535535
):
536536
# We found a redundant item in the union.
537537
removed.add(j)

test-data/unit/check-classes.test

+5-5
Original file line numberDiff line numberDiff line change
@@ -2394,9 +2394,9 @@ a: Union[int, float]
23942394
b: int
23952395
c: float
23962396

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

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

25422542
[case testOperatorWithEmptyListAndSum]

test-data/unit/check-expressions.test

+12
Original file line numberDiff line numberDiff line change
@@ -974,6 +974,18 @@ def f():
974974
main:6: error: Expression is of type "int", not "Literal[42]"
975975
[builtins fixtures/tuple.pyi]
976976

977+
[case testAssertTypeNoPromoteUnion]
978+
from typing import Union, assert_type
979+
980+
Scalar = Union[int, bool, bytes, bytearray]
981+
982+
983+
def reduce_it(s: Scalar) -> Scalar:
984+
return s
985+
986+
assert_type(reduce_it(True), Scalar)
987+
[builtins fixtures/tuple.pyi]
988+
977989
-- None return type
978990
-- ----------------
979991

test-data/unit/check-isinstance.test

+1-2
Original file line numberDiff line numberDiff line change
@@ -1321,8 +1321,7 @@ def f(x: Union[A, B]) -> None:
13211321
f(x)
13221322
[builtins fixtures/isinstance.pyi]
13231323

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

13281327
class FloatLike: pass

test-data/unit/check-unions.test

+11-11
Original file line numberDiff line numberDiff line change
@@ -355,12 +355,12 @@ def foo(a: Union[A, B, C]):
355355
from typing import TypeVar, Union
356356
T = TypeVar('T')
357357
S = TypeVar('S')
358-
def u(x: T, y: S) -> Union[S, T]: pass
358+
def u(x: T, y: S) -> Union[T, S]: pass
359359

360-
reveal_type(u(1, 2.3)) # N: Revealed type is "builtins.float"
361-
reveal_type(u(2.3, 1)) # N: Revealed type is "builtins.float"
362-
reveal_type(u(False, 2.2)) # N: Revealed type is "builtins.float"
363-
reveal_type(u(2.2, False)) # N: Revealed type is "builtins.float"
360+
reveal_type(u(1, 2.3)) # N: Revealed type is "Union[builtins.int, builtins.float]"
361+
reveal_type(u(2.3, 1)) # N: Revealed type is "Union[builtins.float, builtins.int]"
362+
reveal_type(u(False, 2.2)) # N: Revealed type is "Union[builtins.bool, builtins.float]"
363+
reveal_type(u(2.2, False)) # N: Revealed type is "Union[builtins.float, builtins.bool]"
364364
[builtins fixtures/primitives.pyi]
365365

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

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

546546
a: Union[Tuple[int], Tuple[float]]
547547
(a1,) = a
548-
reveal_type(a1) # N: Revealed type is "builtins.float"
548+
reveal_type(a1) # N: Revealed type is "Union[builtins.int, builtins.float]"
549549

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

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

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

645645
[case testUnionMultiassignIndexed]

test-data/unit/fixtures/tuple.pyi

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ class slice: pass
3535
class bool(int): pass
3636
class str: pass # For convenience
3737
class bytes: pass
38+
class bytearray: pass
3839
class unicode: pass
3940

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

0 commit comments

Comments
 (0)