diff --git a/mypy/checker.py b/mypy/checker.py index 8dac00bba23a..2fd5b34a1507 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -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 diff --git a/mypy/meet.py b/mypy/meet.py index 1da80741d70b..3e772419ef3e 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -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 diff --git a/mypy/subtypes.py b/mypy/subtypes.py index bc35b1a4d683..e79297b600f8 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -1672,35 +1672,32 @@ 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) @@ -1708,12 +1705,12 @@ def covers_at_runtime(item: Type, supertype: Type, ignore_promotions: bool) -> b # 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). diff --git a/mypy/typeops.py b/mypy/typeops.py index 3fc756ca4170..7eb1a67b46ea 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -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) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index ff38297ae488..ae7d02f9edfc 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -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] diff --git a/test-data/unit/check-expressions.test b/test-data/unit/check-expressions.test index 1a1272002562..f7aa43d43f3e 100644 --- a/test-data/unit/check-expressions.test +++ b/test-data/unit/check-expressions.test @@ -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 -- ---------------- diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index c06802e69a69..1514d2ce8473 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -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 diff --git a/test-data/unit/check-unions.test b/test-data/unit/check-unions.test index 733e2be1eac6..a561c29e54f7 100644 --- a/test-data/unit/check-unions.test +++ b/test-data/unit/check-unions.test @@ -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,7 +491,7 @@ 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] @@ -499,7 +499,7 @@ reveal_type(l) \ [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] diff --git a/test-data/unit/fixtures/tuple.pyi b/test-data/unit/fixtures/tuple.pyi index a919b5a37b28..5c69a4ad1eb5 100644 --- a/test-data/unit/fixtures/tuple.pyi +++ b/test-data/unit/fixtures/tuple.pyi @@ -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]):