diff --git a/mypy/checker.py b/mypy/checker.py index 5201037242ac..2686c22dded4 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -7691,9 +7691,13 @@ def get_isinstance_type(self, expr: Expression) -> list[TypeRange] | None: types: list[TypeRange] = [] for typ in all_types: if isinstance(typ, FunctionLike) and typ.is_type_obj(): - # Type variables may be present -- erase them, which is the best - # we can do (outside disallowing them here). - erased_type = erase_typevars(typ.items[0].ret_type) + # If a type is generic, `isinstance` can only narrow its variables to Any. + any_parameterized = fill_typevars_with_any(typ.type_object()) + # Tuples may have unattended type variables among their items + if isinstance(any_parameterized, TupleType): + erased_type = erase_typevars(any_parameterized) + else: + erased_type = any_parameterized types.append(TypeRange(erased_type, is_upper_bound=False)) elif isinstance(typ, TypeType): # Type[A] means "any type that is a subtype of A" rather than "precisely type A" diff --git a/test-data/unit/check-narrowing.test b/test-data/unit/check-narrowing.test index 36b2ced075d2..a5c8f53b9726 100644 --- a/test-data/unit/check-narrowing.test +++ b/test-data/unit/check-narrowing.test @@ -2463,3 +2463,60 @@ def test(x: T) -> T: reveal_type(x.x) # N: Revealed type is "builtins.str" return x [builtins fixtures/isinstance.pyi] + +[case testIsinstanceNarrowingWithSelfTypes] +from typing import Generic, TypeVar, overload + +T = TypeVar("T") + +class A(Generic[T]): + def __init__(self: A[int]) -> None: + pass + +def check_a(obj: "A[T] | str") -> None: + reveal_type(obj) # N: Revealed type is "Union[__main__.A[T`-1], builtins.str]" + if isinstance(obj, A): + reveal_type(obj) # N: Revealed type is "__main__.A[T`-1]" + else: + reveal_type(obj) # N: Revealed type is "builtins.str" + + +class B(Generic[T]): + @overload + def __init__(self, x: T) -> None: ... + @overload + def __init__(self: B[int]) -> None: ... + def __init__(self, x: "T | None" = None) -> None: + pass + +def check_b(obj: "B[T] | str") -> None: + reveal_type(obj) # N: Revealed type is "Union[__main__.B[T`-1], builtins.str]" + if isinstance(obj, B): + reveal_type(obj) # N: Revealed type is "__main__.B[T`-1]" + else: + reveal_type(obj) # N: Revealed type is "builtins.str" + + +class C(Generic[T]): + @overload + def __init__(self: C[int]) -> None: ... + @overload + def __init__(self, x: T) -> None: ... + def __init__(self, x: "T | None" = None) -> None: + pass + +def check_c(obj: "C[T] | str") -> None: + reveal_type(obj) # N: Revealed type is "Union[__main__.C[T`-1], builtins.str]" + if isinstance(obj, C): + reveal_type(obj) # N: Revealed type is "__main__.C[T`-1]" + else: + reveal_type(obj) # N: Revealed type is "builtins.str" + + +class D(tuple[T], Generic[T]): ... + +def check_d(arg: D[T]) -> None: + if not isinstance(arg, D): + return + reveal_type(arg) # N: Revealed type is "tuple[T`-1, fallback=__main__.D[Any]]" +[builtins fixtures/tuple.pyi] diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test index 4ac69321a250..6bcc6e20328b 100644 --- a/test-data/unit/check-typeddict.test +++ b/test-data/unit/check-typeddict.test @@ -810,7 +810,7 @@ from typing import TypedDict D = TypedDict('D', {'x': int}) d: object if isinstance(d, D): # E: Cannot use isinstance() with TypedDict type - reveal_type(d) # N: Revealed type is "TypedDict('__main__.D', {'x': builtins.int})" + reveal_type(d) # N: Revealed type is "__main__.D" issubclass(object, D) # E: Cannot use issubclass() with TypedDict type [builtins fixtures/isinstancelist.pyi] [typing fixtures/typing-typeddict.pyi]