microsoft / pyright

Static Type Checker for Python
Other
13.21k stars 1.42k forks source link

Recognize Type Expressions in the Context of Generics #8313

Closed max-muoto closed 2 months ago

max-muoto commented 3 months ago

Currently, Pyright can recognize valid type-expressions, take the following examples:

Valid:

from typing import Any

def klass_generator(klass: type[Any]) -> type[Any]:
   class Wrapper(klass):
      ...

   return Wrapper

Invalid

from typing import Any

def klass_generator(klass: str) -> type[Any]:
   # Expected type expression but received "str"
   class Wrapper(klass):
      ...

   return Wrapper

However, this doesn't apply to generics:

from typing import Any

def klass_decorator[T: type[Any]](klass: T) -> T:
    # Expected type expression...
    class Wrapped(klass):
        ...

    return Wrapped

See this Pyright Playground example.

Mostly creating this to capture the fact that this is missing, but understandable if we want to avoid implementing support until there's more real feedback around it.

erictraut commented 3 months ago

Yes, this should work, so I consider this a bug. The wording of the diagnostic message is also misleading because this isn't a "type expression".

max-muoto commented 3 months ago

Yes, this should work, so I consider this a bug. The wording of the diagnostic message is also misleading because this isn't a "type expression".

Thanks for the quick fix!

erictraut commented 3 months ago

BTW, I generally recommend using type[T] rather than specifying an upper bound of type for T. This tends to work more reliably across type checkers and use cases. You can use this as a workaround while you're waiting for the bug fix.

from typing import cast

def klass_decorator[T](klass: type[T]) -> type[T]:
    class Wrapped(klass):
        ...
    return cast(type[T], Wrapped)
max-muoto commented 3 months ago

BTW, I generally recommend using type[T] rather than specifying an upper bound of type for T. This tends to work more reliably across type checkers and use cases. You can use this as a workaround while you're waiting for the bug fix.

from typing import cast

def klass_decorator[T](klass: type[T]) -> type[T]:
    class Wrapped(klass):
        ...
    return cast(type[T], Wrapped)

Good to know, thanks!

erictraut commented 2 months ago

This is addressed in pyright 1.1.371.