diff --git a/mypy/checker.py b/mypy/checker.py index 1b57ef780104..c9aa0a7bb9bd 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -7396,6 +7396,11 @@ def conditional_types( if not type_range.is_upper_bound ] ) + if isinstance(current_type, TypeVarType): + assert not current_type.values # constrained TypeVars should not reach here + proposed_type = current_type.copy_modified( + values=[], upper_bound=proposed_type, narrowed=True + ) remaining_type = restrict_subtype_away(current_type, proposed_precise_type) return proposed_type, remaining_type else: diff --git a/mypy/types.py b/mypy/types.py index f02e56a677ae..2b327e54cae4 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -586,7 +586,7 @@ def has_default(self) -> bool: class TypeVarType(TypeVarLikeType): """Type that refers to a type variable.""" - __slots__ = ("values", "variance") + __slots__ = ("values", "variance", "narrowed") values: list[Type] # Value restriction, empty list if no restriction variance: int @@ -602,11 +602,14 @@ def __init__( variance: int = INVARIANT, line: int = -1, column: int = -1, + *, + narrowed: bool = False, ) -> None: super().__init__(name, fullname, id, upper_bound, default, line, column) assert values is not None, "No restrictions must be represented by empty list" self.values = values self.variance = variance + self.narrowed = narrowed def copy_modified( self, @@ -617,6 +620,7 @@ def copy_modified( id: Bogus[TypeVarId | int] = _dummy, line: int = _dummy_int, column: int = _dummy_int, + narrowed: Bogus[bool] = _dummy, **kwargs: Any, ) -> TypeVarType: return TypeVarType( @@ -629,6 +633,7 @@ def copy_modified( variance=self.variance, line=self.line if line == _dummy_int else line, column=self.column if column == _dummy_int else column, + narrowed=self.narrowed if narrowed is _dummy else narrowed, ) def accept(self, visitor: TypeVisitor[T]) -> T: @@ -3203,7 +3208,7 @@ def visit_type_var(self, t: TypeVarType) -> str: else: # Named type variable type. s = f"{t.name}`{t.id}" - if self.id_mapper and t.upper_bound: + if (self.id_mapper or t.narrowed) and t.upper_bound: s += f"(upper_bound={t.upper_bound.accept(self)})" if t.has_default(): s += f" = {t.default.accept(self)}" diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 983cb8454a05..4fc2fff7a306 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -6685,11 +6685,11 @@ class C(Generic[T]): def meth(self, cls: Type[T]) -> None: if not issubclass(cls, Sub): return - reveal_type(cls) # N: Revealed type is "Type[__main__.Sub]" + reveal_type(cls) # N: Revealed type is "Type[T`1(upper_bound=__main__.Sub)]" def other(self, cls: Type[T]) -> None: if not issubclass(cls, Sub): return - reveal_type(cls) # N: Revealed type is "Type[__main__.Sub]" + reveal_type(cls) # N: Revealed type is "Type[T`1(upper_bound=__main__.Sub)]" [builtins fixtures/isinstancelist.pyi] diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index b7ee38b69d00..a8ba8b123db6 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -1818,21 +1818,45 @@ if issubclass(fm, Baz): [builtins fixtures/isinstance.pyi] [case testIsinstanceAndNarrowTypeVariable] +# flags: --warn-unreachable from typing import TypeVar class A: pass class B(A): pass +class C: pass T = TypeVar('T', bound=A) def f(x: T) -> None: if isinstance(x, B): - reveal_type(x) # N: Revealed type is "__main__.B" + reveal_type(x) # N: Revealed type is "T`-1(upper_bound=__main__.B)" + x1: T = x + a: A = x + b: B = x + c: C = x # E: Incompatible types in assignment (expression has type "T", variable has type "C") + elif isinstance(x, int): + return # E: Statement is unreachable else: reveal_type(x) # N: Revealed type is "T`-1" + x2: T = x reveal_type(x) # N: Revealed type is "T`-1" [builtins fixtures/isinstance.pyi] +[case testIsinstanceAndNarrowTypeVariableIntersection-xfail] +# flags: --warn-unreachable +from typing import TypeVar + +class A: pass +class B: pass + +T = TypeVar('T', bound=A) + +def f(x: T) -> None: + if isinstance(x, B): + reveal_type(x) # N: Revealed type is "T`-1(upper_bound=Union[__main__.A, __main__.B])" + +[builtins fixtures/isinstance.pyi] + [case testIsinstanceAndTypeType] from typing import Type def f(x: Type[int]) -> None: