From 05f90efaa2aa711834470a18fcb2eb843728b4e7 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Wed, 28 Jan 2026 00:19:19 -0800 Subject: [PATCH 1/5] Model exact narrowing with type(x) checks --- mypy/checker.py | 19 +++++++++++++------ test-data/unit/check-narrowing.test | 2 +- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index d371f40ccaae..b2a444721604 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -6812,13 +6812,20 @@ def narrow_type_by_identity_equality( # We patch this here because it is desirable to widen to any for cases like # isinstance(x, (y: Any)) continue + + arg_type = self.lookup_type(expr_in_type_expr) + if_type, else_type = self.conditional_types_with_intersection( + arg_type, [current_type_range], expr_in_type_expr + ) + if if_type is not None and ( + not current_type_range.is_upper_bound + and not is_equivalent(if_type, current_type_range.item) + ): + # type(x) and x.__class__ checks must exact match + if_type = UninhabitedType() + if_map, else_map = conditional_types_to_typemaps( - expr_in_type_expr, - *self.conditional_types_with_intersection( - self.lookup_type(expr_in_type_expr), - [current_type_range], - expr_in_type_expr, - ), + expr_in_type_expr, if_type, else_type ) is_final = ( diff --git a/test-data/unit/check-narrowing.test b/test-data/unit/check-narrowing.test index b9bb0c1efa6a..c9aa352e1d61 100644 --- a/test-data/unit/check-narrowing.test +++ b/test-data/unit/check-narrowing.test @@ -3152,7 +3152,7 @@ else: reveal_type(y) # N: Revealed type is "builtins.str" [builtins fixtures/isinstance.pyi] -[case testTypeEqualsCheckUsingIsNonOverlappingChild-xfail] +[case testTypeEqualsCheckUsingIsNonOverlappingChild] # flags: --strict-equality --warn-unreachable from typing import Union From 3ee33bd45e00293d5a749abae2a4e86d7cd99ab1 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Fri, 30 Jan 2026 12:27:50 -0800 Subject: [PATCH 2/5] . --- test-data/unit/check-narrowing.test | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/test-data/unit/check-narrowing.test b/test-data/unit/check-narrowing.test index c9aa352e1d61..1498dbb0d9b3 100644 --- a/test-data/unit/check-narrowing.test +++ b/test-data/unit/check-narrowing.test @@ -3168,6 +3168,32 @@ def main(x: Union[B, C]): reveal_type(x) # N: Revealed type is "__main__.B | __main__.C" [builtins fixtures/isinstance.pyi] +[case testTypeEqualsCheckTypeVar] +# flags: --strict-equality --warn-unreachable +from typing import TypeVar + +class A: ... +class B: ... + +T = TypeVar("T", A, B) + +def f1(self, obj: T) -> T: + if type(obj) == A: + return A() + elif type(obj) == B: + return B() + raise + +T_value = TypeVar("T_value", A, B) + +def f2(self, obj: T_value) -> T_value: + if type(obj) == A: + return A() + elif type(obj) == B: + return B() + raise +[builtins fixtures/primitives.pyi] + [case testTypeEqualsCheckUsingDifferentSpecializedTypes] # flags: --warn-unreachable from collections import defaultdict From 2db9e30d7284d83c5f30be37b9980d70dbb0af2e Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Fri, 30 Jan 2026 12:29:21 -0800 Subject: [PATCH 3/5] . --- test-data/unit/check-narrowing.test | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test-data/unit/check-narrowing.test b/test-data/unit/check-narrowing.test index 1498dbb0d9b3..686ee418baf6 100644 --- a/test-data/unit/check-narrowing.test +++ b/test-data/unit/check-narrowing.test @@ -3177,6 +3177,13 @@ class B: ... T = TypeVar("T", A, B) +def bad(self, obj: T) -> T: + if isinstance(obj, A): + return A() # E: Incompatible return value type (got "A", expected "B") + elif isinstance(obj, B): + return B() + raise + def f1(self, obj: T) -> T: if type(obj) == A: return A() From b601c582e10f09aabb86806d0a4d2698712a68c2 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Fri, 30 Jan 2026 12:31:40 -0800 Subject: [PATCH 4/5] . --- test-data/unit/check-narrowing.test | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/test-data/unit/check-narrowing.test b/test-data/unit/check-narrowing.test index 686ee418baf6..dcd4725b2076 100644 --- a/test-data/unit/check-narrowing.test +++ b/test-data/unit/check-narrowing.test @@ -3175,24 +3175,31 @@ from typing import TypeVar class A: ... class B: ... -T = TypeVar("T", A, B) +T = TypeVar("T") -def bad(self, obj: T) -> T: +def bad1(self, obj: T) -> T: if isinstance(obj, A): - return A() # E: Incompatible return value type (got "A", expected "B") + return A() # E: Incompatible return value type (got "A", expected "T") elif isinstance(obj, B): - return B() + return B() # E: Incompatible return value type (got "B", expected "T") raise def f1(self, obj: T) -> T: if type(obj) == A: - return A() + return A() # E: Incompatible return value type (got "A", expected "T") elif type(obj) == B: - return B() + return B() # E: Incompatible return value type (got "B", expected "T") raise T_value = TypeVar("T_value", A, B) +def bad2(self, obj: T_value) -> T_value: + if isinstance(obj, A): + return A() # E: Incompatible return value type (got "A", expected "B") + elif isinstance(obj, B): + return B() + raise + def f2(self, obj: T_value) -> T_value: if type(obj) == A: return A() From efca8acd339c569d5ce9e1b021fa485647214990 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Fri, 30 Jan 2026 12:40:22 -0800 Subject: [PATCH 5/5] . --- test-data/unit/check-narrowing.test | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test-data/unit/check-narrowing.test b/test-data/unit/check-narrowing.test index dcd4725b2076..675ff86e64ae 100644 --- a/test-data/unit/check-narrowing.test +++ b/test-data/unit/check-narrowing.test @@ -3177,14 +3177,14 @@ class B: ... T = TypeVar("T") -def bad1(self, obj: T) -> T: +def forgets_about_subclasses1(self, obj: T) -> T: if isinstance(obj, A): return A() # E: Incompatible return value type (got "A", expected "T") elif isinstance(obj, B): return B() # E: Incompatible return value type (got "B", expected "T") raise -def f1(self, obj: T) -> T: +def correct1(self, obj: T) -> T: if type(obj) == A: return A() # E: Incompatible return value type (got "A", expected "T") elif type(obj) == B: @@ -3193,14 +3193,14 @@ def f1(self, obj: T) -> T: T_value = TypeVar("T_value", A, B) -def bad2(self, obj: T_value) -> T_value: +def forgets_about_subclasses2(self, obj: T_value) -> T_value: if isinstance(obj, A): return A() # E: Incompatible return value type (got "A", expected "B") elif isinstance(obj, B): return B() raise -def f2(self, obj: T_value) -> T_value: +def correct2(self, obj: T_value) -> T_value: if type(obj) == A: return A() elif type(obj) == B: