pytype icon indicating copy to clipboard operation
pytype copied to clipboard

Type narrowing does not narrow the type of `__class__`

Open eltoder opened this issue 2 years ago • 1 comments

Consider the following example:

from typing import TypeVar

T = TypeVar("T")

def test(obj: T | None) -> type[T]:
    if obj is None:
        raise TypeError
    return obj.__class__

test(123)

This produces an unexpected error:

$ pytype typevar_narrow2.py
File "/home/eltoder/dev/scratch/typevar_narrow2.py", line 8, in test: bad option 'Type[None]' in return type [bad-return-type]
           Expected: Type[int]
  Actually returned: Type[Optional[int]]

Even though pytype knows that the type of obj after the if is just int and not Optional[int] (confirmed by adding reveal_type), it still thinks that the type of obj.__class__ is Type[Optional[int]]. However, pytype works as expected if I replace obj.__class__ with type(obj). A similar case can be constructed by using a bounded TypeVar:

from typing import TypeVar

T = TypeVar("T", bound=int)

def test(obj: T | None) -> type[T]:
    if obj is None:
        raise TypeError
    return obj.__class__

This one does not need a call to test.

Both examples work as expected in mypy.

eltoder avatar Jul 26 '23 21:07 eltoder

Actually, this does not need TypeVars:

def test(obj: int | None) -> type[int]:
    if obj is None:
        return int
    return obj.__class__

produces

$ pytype class_narrow.py
...
File "/home/eltoder/dev/scratch/class_narrow.py", line 4, in test: bad option 'Type[None]' in return type [bad-return-type]
           Expected: Type[int]
  Actually returned: Type[Optional[int]]

But using type(obj) works.

eltoder avatar Jul 26 '23 21:07 eltoder