From 770c5d0e8da45067d5d7e630bc788e5e2fd3a877 Mon Sep 17 00:00:00 2001 From: Shantanu Jain Date: Sat, 10 Jan 2026 22:48:27 -0800 Subject: [PATCH 1/5] Narrow for type expr comparisons to type exprs --- mypy/checker.py | 63 +++++++++++++++++++++++-------------------------- 1 file changed, 29 insertions(+), 34 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index d56414dcb693..522fb1c818e0 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -6726,37 +6726,37 @@ def narrow_type_by_identity_equality( else_map = {} # this is the big difference compared to the above partial_type_maps.append((if_map, else_map)) - exprs_in_type_calls = [] for i in expr_indices: - expr = operands[i] - if isinstance(expr, CallExpr) and is_type_call(expr): - exprs_in_type_calls.append(expr.args[0]) - - if exprs_in_type_calls: - for expr_in_type_call in exprs_in_type_calls: - for i in expr_indices: - expr = operands[i] - if isinstance(expr, CallExpr) and is_type_call(expr): - continue - - current_type_range = self.get_isinstance_type(expr) - if_map, else_map = conditional_types_to_typemaps( - expr_in_type_call, - *self.conditional_types_with_intersection( - self.lookup_type(expr_in_type_call), - current_type_range, - expr_in_type_call, - ), - ) + type_expr = operands[i] + if ( + isinstance(type_expr, CallExpr) + and refers_to_fullname(type_expr.callee, "builtins.type") + and len(type_expr.args) == 1 + ): + expr_in_type_expr = type_expr.args[0] + else: + continue + for j in expr_indices: + if i == j: + continue + expr = operands[j] + + current_type_range = self.get_isinstance_type(expr) + 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 + ), + ) - is_final = ( - expr.node.is_final - if isinstance(expr, RefExpr) and isinstance(expr.node, TypeInfo) - else False - ) - if not is_final: - else_map = {} - partial_type_maps.append((if_map, else_map)) + is_final = ( + expr.node.is_final + if isinstance(expr, RefExpr) and isinstance(expr.node, TypeInfo) + else False + ) + if not is_final: + else_map = {} + partial_type_maps.append((if_map, else_map)) # We will not have duplicate entries in our type maps if we only have two operands, # so we can skip running meets on the intersections @@ -8562,11 +8562,6 @@ def has_custom_eq_checks(t: Type) -> bool: ) -def is_type_call(expr: CallExpr) -> bool: - """Is expr a call to type with one argument?""" - return refers_to_fullname(expr.callee, "builtins.type") and len(expr.args) == 1 - - def convert_to_typetype(type_map: TypeMap) -> TypeMap: converted_type_map: dict[Expression, Type] = {} if type_map is None: From 29e77f115ab0f435901adc66d90ff61e0cf8c253 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Thu, 22 Jan 2026 20:32:41 -0800 Subject: [PATCH 2/5] test --- test-data/unit/check-narrowing.test | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test-data/unit/check-narrowing.test b/test-data/unit/check-narrowing.test index 9a784e72726b..59c63f0f09cb 100644 --- a/test-data/unit/check-narrowing.test +++ b/test-data/unit/check-narrowing.test @@ -3021,6 +3021,15 @@ if type(x) is not int: else: reveal_type(x) # N: Revealed type is "builtins.int" +[case testTypeNarrowingAgainstType] +# flags: --strict-equality --warn-unreachable +class A: + def foo(self, x: object) -> None: + reveal_type(self) # N: Revealed type is "__main__.A" + if type(self) is type(x): + reveal_type(self) # N: Revealed type is "__main__.A" + reveal_type(x) # N: Revealed type is "__main__.A" + [case testNarrowInElseCaseIfFinal] # flags: --strict-equality --warn-unreachable from typing import final, Union From bbedae39e368d784070fcaf7edb4e0dd3f7bc519 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Thu, 22 Jan 2026 20:38:28 -0800 Subject: [PATCH 3/5] . --- test-data/unit/check-narrowing.test | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test-data/unit/check-narrowing.test b/test-data/unit/check-narrowing.test index 59c63f0f09cb..c4e0c800a9a2 100644 --- a/test-data/unit/check-narrowing.test +++ b/test-data/unit/check-narrowing.test @@ -3026,9 +3026,15 @@ else: class A: def foo(self, x: object) -> None: reveal_type(self) # N: Revealed type is "__main__.A" + reveal_type(x) # N: Revealed type is "builtins.object" if type(self) is type(x): reveal_type(self) # N: Revealed type is "__main__.A" reveal_type(x) # N: Revealed type is "__main__.A" + if type(self) == type(x): + reveal_type(self) # N: Revealed type is "__main__.A" + reveal_type(x) # N: Revealed type is "__main__.A" + +[builtins fixtures/primitives.pyi] [case testNarrowInElseCaseIfFinal] # flags: --strict-equality --warn-unreachable From b6f630b659eb2bb5a17f08a2819302311478440e Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Thu, 22 Jan 2026 20:39:06 -0800 Subject: [PATCH 4/5] . --- test-data/unit/check-narrowing.test | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test-data/unit/check-narrowing.test b/test-data/unit/check-narrowing.test index c4e0c800a9a2..2f8ac04deb0c 100644 --- a/test-data/unit/check-narrowing.test +++ b/test-data/unit/check-narrowing.test @@ -3030,9 +3030,15 @@ class A: if type(self) is type(x): reveal_type(self) # N: Revealed type is "__main__.A" reveal_type(x) # N: Revealed type is "__main__.A" + else: + reveal_type(self) # N: Revealed type is "__main__.A" + reveal_type(x) # N: Revealed type is "builtins.object" if type(self) == type(x): reveal_type(self) # N: Revealed type is "__main__.A" reveal_type(x) # N: Revealed type is "__main__.A" + else: + reveal_type(self) # N: Revealed type is "__main__.A" + reveal_type(x) # N: Revealed type is "builtins.object" [builtins fixtures/primitives.pyi] From 715529864ec2dbda0084d0b16baff0222e6205e6 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Thu, 22 Jan 2026 20:42:35 -0800 Subject: [PATCH 5/5] . --- test-data/unit/check-narrowing.test | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test-data/unit/check-narrowing.test b/test-data/unit/check-narrowing.test index 2f8ac04deb0c..fb14efea6663 100644 --- a/test-data/unit/check-narrowing.test +++ b/test-data/unit/check-narrowing.test @@ -3040,6 +3040,11 @@ class A: reveal_type(self) # N: Revealed type is "__main__.A" reveal_type(x) # N: Revealed type is "builtins.object" +class B: + y: int + + def __eq__(self, other: object) -> bool: + return type(other) is type(self) and other.y == self.y [builtins fixtures/primitives.pyi] [case testNarrowInElseCaseIfFinal]