Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = (
Expand Down
28 changes: 27 additions & 1 deletion test-data/unit/check-narrowing.test
Original file line number Diff line number Diff line change
Expand Up @@ -3152,7 +3152,7 @@ else:
reveal_type(y) # N: Revealed type is "builtins.str"
[builtins fixtures/isinstance.pyi]

[case testTypeEqualsCheckUsingIsNonOverlappingChild-xfail]
[case testTypeEqualsCheckUsingIsNonOverlappingChild]
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the test case I was targeting with this PR

# flags: --strict-equality --warn-unreachable
from typing import Union

Expand All @@ -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:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the difference between f1 and f2? Both are generic in a type variable with values.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nvm, I see you just fixed it.

Copy link
Copy Markdown
Collaborator Author

@hauntsaninja hauntsaninja Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh sorry, I fixed that already :-)

More generally, the behaviour here isn't yet perfect.

I opened this PR to fix the testTypeEqualsCheckUsingIsNonOverlappingChild-xfail case, discovered we had an open issue it affected sterliakov/mypy-issues#259 , so adding the test case for it.

The test case I added highlights two preexisting issues. Ideally a) the narrowing works in the unconstrained case, b) we issue an error in both branches in the bad constrained case

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
Expand Down
Loading