Skip to content

Commit ce6355e

Browse files
authored
Fix type extraction from isinstance checks (#19223)
Fixes #19221. Instead of trying to use the first (maybe) overload item and erase it, just use the underlying type with Any-filled typevars
1 parent f902275 commit ce6355e

File tree

3 files changed

+65
-4
lines changed

3 files changed

+65
-4
lines changed

mypy/checker.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7697,9 +7697,13 @@ def get_isinstance_type(self, expr: Expression) -> list[TypeRange] | None:
76977697
types: list[TypeRange] = []
76987698
for typ in all_types:
76997699
if isinstance(typ, FunctionLike) and typ.is_type_obj():
7700-
# Type variables may be present -- erase them, which is the best
7701-
# we can do (outside disallowing them here).
7702-
erased_type = erase_typevars(typ.items[0].ret_type)
7700+
# If a type is generic, `isinstance` can only narrow its variables to Any.
7701+
any_parameterized = fill_typevars_with_any(typ.type_object())
7702+
# Tuples may have unattended type variables among their items
7703+
if isinstance(any_parameterized, TupleType):
7704+
erased_type = erase_typevars(any_parameterized)
7705+
else:
7706+
erased_type = any_parameterized
77037707
types.append(TypeRange(erased_type, is_upper_bound=False))
77047708
elif isinstance(typ, TypeType):
77057709
# Type[A] means "any type that is a subtype of A" rather than "precisely type A"

test-data/unit/check-narrowing.test

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2463,3 +2463,60 @@ def test(x: T) -> T:
24632463
reveal_type(x.x) # N: Revealed type is "builtins.str"
24642464
return x
24652465
[builtins fixtures/isinstance.pyi]
2466+
2467+
[case testIsinstanceNarrowingWithSelfTypes]
2468+
from typing import Generic, TypeVar, overload
2469+
2470+
T = TypeVar("T")
2471+
2472+
class A(Generic[T]):
2473+
def __init__(self: A[int]) -> None:
2474+
pass
2475+
2476+
def check_a(obj: "A[T] | str") -> None:
2477+
reveal_type(obj) # N: Revealed type is "Union[__main__.A[T`-1], builtins.str]"
2478+
if isinstance(obj, A):
2479+
reveal_type(obj) # N: Revealed type is "__main__.A[T`-1]"
2480+
else:
2481+
reveal_type(obj) # N: Revealed type is "builtins.str"
2482+
2483+
2484+
class B(Generic[T]):
2485+
@overload
2486+
def __init__(self, x: T) -> None: ...
2487+
@overload
2488+
def __init__(self: B[int]) -> None: ...
2489+
def __init__(self, x: "T | None" = None) -> None:
2490+
pass
2491+
2492+
def check_b(obj: "B[T] | str") -> None:
2493+
reveal_type(obj) # N: Revealed type is "Union[__main__.B[T`-1], builtins.str]"
2494+
if isinstance(obj, B):
2495+
reveal_type(obj) # N: Revealed type is "__main__.B[T`-1]"
2496+
else:
2497+
reveal_type(obj) # N: Revealed type is "builtins.str"
2498+
2499+
2500+
class C(Generic[T]):
2501+
@overload
2502+
def __init__(self: C[int]) -> None: ...
2503+
@overload
2504+
def __init__(self, x: T) -> None: ...
2505+
def __init__(self, x: "T | None" = None) -> None:
2506+
pass
2507+
2508+
def check_c(obj: "C[T] | str") -> None:
2509+
reveal_type(obj) # N: Revealed type is "Union[__main__.C[T`-1], builtins.str]"
2510+
if isinstance(obj, C):
2511+
reveal_type(obj) # N: Revealed type is "__main__.C[T`-1]"
2512+
else:
2513+
reveal_type(obj) # N: Revealed type is "builtins.str"
2514+
2515+
2516+
class D(tuple[T], Generic[T]): ...
2517+
2518+
def check_d(arg: D[T]) -> None:
2519+
if not isinstance(arg, D):
2520+
return
2521+
reveal_type(arg) # N: Revealed type is "tuple[T`-1, fallback=__main__.D[Any]]"
2522+
[builtins fixtures/tuple.pyi]

test-data/unit/check-typeddict.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -810,7 +810,7 @@ from typing import TypedDict
810810
D = TypedDict('D', {'x': int})
811811
d: object
812812
if isinstance(d, D): # E: Cannot use isinstance() with TypedDict type
813-
reveal_type(d) # N: Revealed type is "TypedDict('__main__.D', {'x': builtins.int})"
813+
reveal_type(d) # N: Revealed type is "__main__.D"
814814
issubclass(object, D) # E: Cannot use issubclass() with TypedDict type
815815
[builtins fixtures/isinstancelist.pyi]
816816
[typing fixtures/typing-typeddict.pyi]

0 commit comments

Comments
 (0)