Skip to content

Commit dc409ce

Browse files
committed
fix: do not narrow expression type to TypeVar on equality comparison
When a variable is compared with == against a TypeVar operand, mypy was narrowing its type to the TypeVar, causing false errors on subsequent operations. The regression was introduced in #20602. Skip narrowing in narrow_type_by_identity_equality when the target type is a TypeVarLikeType. Fixes #21199
1 parent f315c8a commit dc409ce

File tree

2 files changed

+37
-0
lines changed

2 files changed

+37
-0
lines changed

mypy/checker.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6787,6 +6787,13 @@ def narrow_type_by_identity_equality(
67876787
):
67886788
continue
67896789

6790+
# Do not narrow based on a TypeVar target: we don't know its concrete type,
6791+
# so narrowing e.g. `x: int` to `T` when comparing `x == y: T` is wrong.
6792+
# The narrowed TypeVar type is typically a supertype of the original, causing
6793+
# false errors on subsequent operations. See: github.com/python/mypy/issues/21199
6794+
if isinstance(get_proper_type(target_type), TypeVarLikeType):
6795+
continue
6796+
67906797
target = TypeRange(target_type, is_upper_bound=False)
67916798

67926799
if_map, else_map = conditional_types_to_typemaps(

test-data/unit/check-narrowing.test

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3993,3 +3993,33 @@ def f2(func: Callable[..., T], arg: str) -> T:
39933993
return func(arg)
39943994
return func(arg)
39953995
[builtins fixtures/primitives.pyi]
3996+
3997+
[case testNarrowingEqualityTypeVarTarget]
3998+
# Regression test for https://github.com/python/mypy/issues/21199
3999+
# When the RHS of == is a TypeVar, the LHS should not be narrowed to the TypeVar type.
4000+
# flags: --python-version 3.10
4001+
from typing import TypeVar
4002+
4003+
T = TypeVar("T")
4004+
4005+
def func_basic(x: int, y: T) -> None:
4006+
# x: int should remain int after comparing with TypeVar y; no error on x += 1
4007+
assert x == y
4008+
reveal_type(x) # N: Revealed type is "builtins.int"
4009+
x += 1
4010+
4011+
def func_union(x: int | str, y: T) -> None:
4012+
# x: int | str should not be narrowed to T when compared with TypeVar
4013+
assert x == y
4014+
reveal_type(x) # N: Revealed type is "builtins.int | builtins.str"
4015+
4016+
def func_not_equal(x: int | str, y: T) -> None:
4017+
# != should also not over-narrow based on TypeVar
4018+
if x != y:
4019+
reveal_type(x) # N: Revealed type is "builtins.int | builtins.str"
4020+
4021+
def func_concrete_target(x: int | str, y: int) -> None:
4022+
# When the target is a concrete type (not TypeVar), narrowing is still allowed
4023+
if x == y:
4024+
reveal_type(x) # N: Revealed type is "builtins.int"
4025+
[builtins fixtures/primitives.pyi]

0 commit comments

Comments
 (0)