python / mypy

Optional static typing for Python
https://www.mypy-lang.org/
Other
18.5k stars 2.83k forks source link

`TypeVar` not narrowed when using `isinstance` or `issubclass` on `final` classes #12163

Open DetachHead opened 2 years ago

DetachHead commented 2 years ago
from typing import TypeVar, final

@final
class A:
    def __init__(self,value: str) -> None: ...

@final
class B: 
    def __init__(self,value: int) -> None: ...

T = TypeVar("T", A, B)

def foo(cls: type[T]) -> T:
    if issubclass(cls, A):
        return cls("") # error: Argument 1 to <subclass of "B" and "A"> has incompatible type "str"; expected "int
        # return cls(1) # error: Argument 1 to "A" has incompatible type "int"; expected "str
        # return A("") # error: Incompatible return value type (got "A", expected "B")
        # return B("") # error: Incompatible return value type (got "B", expected "A")
    else:
        return cls(1)

https://mypy-play.net/?mypy=master&python=3.10&flags=show-error-codes%2Callow-redefinition%2Cstrict%2Ccheck-untyped-defs%2Cdisallow-any-decorated%2Cdisallow-any-expr%2Cdisallow-any-explicit%2Cdisallow-any-generics%2Cdisallow-any-unimported%2Cdisallow-incomplete-defs%2Cdisallow-subclassing-any%2Cdisallow-untyped-calls%2Cdisallow-untyped-decorators%2Cdisallow-untyped-defs%2Cno-implicit-optional%2Cno-implicit-reexport%2Cstrict-equality%2Cwarn-incomplete-stub%2Cwarn-redundant-casts%2Cwarn-return-any%2Cwarn-unreachable%2Cwarn-unused-configs%2Cwarn-unused-ignores&gist=43b6f21a9c890c7da5e41e2591b321b0

(the error messages are wrong due to #11536)

KotlinIsland commented 2 years ago

Here is the minified version:

from typing import final

@final
class A: ...

class B: ...

a: A
assert isinstance(a, B)
reveal_type(a)  # note: Revealed type is "__main__.<subclass of "A" and "B">"
print("hi") # no unreachable error
pwuertz commented 1 year ago

This issue is probably the same reason for this error here?

import typing

class A: ...

@typing.final
class AA(A): ...

T = typing.TypeVar("T", bound=A)

def test(x: T) -> T:
    if isinstance(x, AA):
        return AA()  # error: Incompatible return value type (got "AA", expected "T")
    raise NotImplementedError()

The expected return type within the isinstance(x, AA) branch is guaranteed to be AA and the typing should be valid, right?

Using T = typing.TypeVar("T", AA, AB, ...) doesn't help either, although T should be constrained to exactly those types even without interpreting the final decorator.