From ff07d6d7c4b5fec1ce8bff01660e6d36540a029b Mon Sep 17 00:00:00 2001 From: Jerry Chen Date: Mon, 13 Apr 2026 15:46:58 +0800 Subject: [PATCH] fix: do not narrow expression type to TypeVar on equality comparison --- mypy/checker.py | 7 +++++++ test-data/unit/check-narrowing.test | 30 +++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/mypy/checker.py b/mypy/checker.py index 6a0e8f3718d3..5a6c4f9a84e7 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -6787,6 +6787,13 @@ def narrow_type_by_identity_equality( ): continue + # Do not narrow based on a TypeVar target: we don't know its concrete type, + # so narrowing e.g. `x: int` to `T` when comparing `x == y: T` is wrong. + # The narrowed TypeVar type is typically a supertype of the original, causing + # false errors on subsequent operations. See: github.com/python/mypy/issues/21199 + if isinstance(get_proper_type(target_type), TypeVarLikeType): + continue + target = TypeRange(target_type, is_upper_bound=False) if_map, else_map = conditional_types_to_typemaps( diff --git a/test-data/unit/check-narrowing.test b/test-data/unit/check-narrowing.test index 6e3bba6921bf..397b126496e4 100644 --- a/test-data/unit/check-narrowing.test +++ b/test-data/unit/check-narrowing.test @@ -3993,3 +3993,33 @@ def f2(func: Callable[..., T], arg: str) -> T: return func(arg) return func(arg) [builtins fixtures/primitives.pyi] + +[case testNarrowingEqualityTypeVarTarget] +# Regression test for https://github.com/python/mypy/issues/21199 +# When the RHS of == is a TypeVar, the LHS should not be narrowed to the TypeVar type. +# flags: --python-version 3.10 +from typing import TypeVar + +T = TypeVar("T") + +def func_basic(x: int, y: T) -> None: + # x: int should remain int after comparing with TypeVar y; no error on x += 1 + assert x == y + reveal_type(x) # N: Revealed type is "builtins.int" + x += 1 + +def func_union(x: int | str, y: T) -> None: + # x: int | str should not be narrowed to T when compared with TypeVar + assert x == y + reveal_type(x) # N: Revealed type is "builtins.int | builtins.str" + +def func_not_equal(x: int | str, y: T) -> None: + # != should also not over-narrow based on TypeVar + if x != y: + reveal_type(x) # N: Revealed type is "builtins.int | builtins.str" + +def func_concrete_target(x: int | str, y: int) -> None: + # When the target is a concrete type (not TypeVar), narrowing is still allowed + if x == y: + reveal_type(x) # N: Revealed type is "builtins.int" +[builtins fixtures/primitives.pyi]