From 99d4465bbb8026525ba54d82b47b02fb0fff4158 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Wed, 12 Jul 2023 18:31:42 +0300 Subject: [PATCH 1/4] Narrow `is` with final types correctly --- mypy/subtypes.py | 4 ++ mypy/types.py | 9 ++++ test-data/unit/check-narrowing.test | 76 +++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index a6dc071f92b0..f0e7515e2299 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -1782,6 +1782,10 @@ def covers_at_runtime(item: Type, supertype: Type) -> bool: item = get_proper_type(item) supertype = get_proper_type(supertype) + 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 ba629a3553cf..ae3f8dc49826 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -2085,6 +2085,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 __hash__(self) -> int: # self.is_type_obj() will fail if self.fallback.type is a FakeInfo if isinstance(self.fallback.type, FakeInfo): @@ -2856,6 +2859,12 @@ 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 c329ccf840a8..3b0c763824f7 100644 --- a/test-data/unit/check-narrowing.test +++ b/test-data/unit/check-narrowing.test @@ -1269,3 +1269,79 @@ def g() -> None: def foo(): ... 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 testNarrowingIsRegualType] +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] From 0076ae855af496ad7dfbffdd45f4e75d2bc972f3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 12 Jul 2023 16:22:45 +0000 Subject: [PATCH 2/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypy/types.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/mypy/types.py b/mypy/types.py index ae3f8dc49826..6c99677cff5b 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -2860,9 +2860,8 @@ def __eq__(self, other: object) -> bool: 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) + return (isinstance(self.item, Instance) and self.item.type.is_final) or isinstance( + self.item, NoneType ) def serialize(self) -> JsonDict: From a0308bcde13e907e6dd0139f031df9956913bdd1 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Wed, 12 Jul 2023 21:07:02 +0300 Subject: [PATCH 3/4] Address primer output --- mypy/subtypes.py | 6 ++++++ test-data/unit/check-narrowing.test | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index f0e7515e2299..08eebe536749 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -1782,6 +1782,12 @@ 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 diff --git a/test-data/unit/check-narrowing.test b/test-data/unit/check-narrowing.test index 3b0c763824f7..87beb970574b 100644 --- a/test-data/unit/check-narrowing.test +++ b/test-data/unit/check-narrowing.test @@ -1327,6 +1327,28 @@ else: [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 testNarrowingIsRegualType] from typing import Type, Union From 2372c12d674c5e7c8785c4c6a96f1e30f3563fc6 Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Thu, 17 Oct 2024 00:09:10 -0700 Subject: [PATCH 4/4] Update test-data/unit/check-narrowing.test --- test-data/unit/check-narrowing.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-data/unit/check-narrowing.test b/test-data/unit/check-narrowing.test index 837e77aaef23..827a8981edfa 100644 --- a/test-data/unit/check-narrowing.test +++ b/test-data/unit/check-narrowing.test @@ -1452,7 +1452,7 @@ else: [builtins fixtures/isinstancelist.pyi] -[case testNarrowingIsRegualType] +[case testNarrowingIsNonFinalType] from typing import Type, Union class Mark: ...