diff --git a/mypy/subtypes.py b/mypy/subtypes.py index a63db93fd9cb..b64876ada87c 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -1959,6 +1959,16 @@ def covers_at_runtime(item: Type, supertype: Type) -> bool: item = get_proper_type(item) supertype = get_proper_type(supertype) + # Left `Any` or `Type[Any]` type will never be covered at runtime: + if isinstance(item, AnyType) or ( + isinstance(item, TypeType) and isinstance(item.item, AnyType) + ): + return False + + if isinstance(item, (CallableType, TypeType)) and item.is_singleton_type(): + if is_proper_subtype(item, supertype): + return True + # Since runtime type checks will ignore type arguments, erase the types. supertype = erase_type(supertype) if is_proper_subtype( diff --git a/mypy/types.py b/mypy/types.py index 897e19d6ee19..bb3645e1b132 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -2167,6 +2167,9 @@ def with_unpacked_kwargs(self) -> NormalizedCallableType: ) ) + def is_singleton_type(self) -> bool: + return self.is_type_obj() and self.type_object().is_final + def with_normalized_var_args(self) -> Self: var_arg = self.var_arg() if not var_arg or not isinstance(var_arg.typ, UnpackType): @@ -3111,6 +3114,11 @@ def __eq__(self, other: object) -> bool: return NotImplemented return self.item == other.item + def is_singleton_type(self) -> bool: + return (isinstance(self.item, Instance) and self.item.type.is_final) or isinstance( + self.item, NoneType + ) + def serialize(self) -> JsonDict: return {".class": "TypeType", "item": self.item.serialize()} diff --git a/test-data/unit/check-narrowing.test b/test-data/unit/check-narrowing.test index d740708991d0..827a8981edfa 100644 --- a/test-data/unit/check-narrowing.test +++ b/test-data/unit/check-narrowing.test @@ -1374,7 +1374,102 @@ def g() -> None: foo() [builtins fixtures/dict.pyi] +[case testNarrowingIsFinalType] +from typing import Type, Union +from typing_extensions import final + +@final +class Mark: ... + +@final +class Other: ... + +x: Union[Type[Mark], Type[Other], Type[None], int] + +if x is Mark: + reveal_type(x) # N: Revealed type is "Type[__main__.Mark]" +else: + reveal_type(x) # N: Revealed type is "Union[Type[__main__.Other], Type[None], builtins.int]" + +if x is not Mark: + reveal_type(x) # N: Revealed type is "Union[Type[__main__.Other], Type[None], builtins.int]" +else: + reveal_type(x) # N: Revealed type is "Type[__main__.Mark]" + +y: Type[Mark] = Mark +if x is y: + reveal_type(x) # N: Revealed type is "Type[__main__.Mark]" +else: + reveal_type(x) # N: Revealed type is "Union[Type[__main__.Other], Type[None], builtins.int]" + +if x is not y: + reveal_type(x) # N: Revealed type is "Union[Type[__main__.Other], Type[None], builtins.int]" +else: + reveal_type(x) # N: Revealed type is "Type[__main__.Mark]" + +if x is type(None): + reveal_type(x) # N: Revealed type is "Type[None]" +else: + reveal_type(x) # N: Revealed type is "Union[Type[__main__.Mark], Type[__main__.Other], builtins.int]" + +if x is not type(None): + reveal_type(x) # N: Revealed type is "Union[Type[__main__.Mark], Type[__main__.Other], builtins.int]" +else: + reveal_type(x) # N: Revealed type is "Type[None]" + +z: Type[None] +if x is z: + reveal_type(x) # N: Revealed type is "Type[None]" +else: + reveal_type(x) # N: Revealed type is "Union[Type[__main__.Mark], Type[__main__.Other], builtins.int]" + +if x is not z: + reveal_type(x) # N: Revealed type is "Union[Type[__main__.Mark], Type[__main__.Other], builtins.int]" +else: + reveal_type(x) # N: Revealed type is "Type[None]" +[builtins fixtures/isinstancelist.pyi] + + +[case testNarrowingAnyAgainstFinalType] +from typing import Type, Any +from typing_extensions import final + +@final +class Mark: ... + +x: Any +if x is Mark: + reveal_type(x) # N: Revealed type is "def () -> __main__.Mark" +else: + reveal_type(x) # N: Revealed type is "Any" + +y: Type[Any] +if y is Mark: + reveal_type(y) # N: Revealed type is "Type[__main__.Mark]" +else: + reveal_type(y) # N: Revealed type is "Type[Any]" +[builtins fixtures/isinstancelist.pyi] + + +[case testNarrowingIsNonFinalType] +from typing import Type, Union + +class Mark: ... + +x: Union[Type[Mark], Type[None], int] + +if x is Mark: + reveal_type(x) # N: Revealed type is "Union[Type[__main__.Mark], Type[None], builtins.int]" +else: + reveal_type(x) # N: Revealed type is "Union[Type[__main__.Mark], Type[None], builtins.int]" + +y: Type[Mark] = Mark +if x is y: + reveal_type(x) # N: Revealed type is "Union[Type[__main__.Mark], Type[None], builtins.int]" +else: + reveal_type(x) # N: Revealed type is "Union[Type[__main__.Mark], Type[None], builtins.int]" +[builtins fixtures/isinstancelist.pyi] [case testNarrowingOptionalEqualsNone] from typing import Optional