python / mypy

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

Generic Type[T] function argument rejected as base class #14458

Open tyilo opened 1 year ago

tyilo commented 1 year ago

Bug Report

Since https://github.com/python/mypy/issues/5865 was fixed, the following code now type checks:

from typing import Any, Type

def f(typ: Type[Any]) -> Type[Any]:
    class C(typ):
        pass

    return C

However if we make the superclass generic, it doesn't type check anymore which I would expect it to do.

To Reproduce

from typing import Type, TypeVar

T = TypeVar("T")

def f(typ: Type[T]) -> Type[T]:
    class C(typ):
        pass

    return C

Actual Behavior

mypy_return_type.py:7: error: Variable "typ" is not valid as a type  [valid-type]
mypy_return_type.py:7: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases
mypy_return_type.py:7: error: Invalid base class "typ"  [misc]
Found 2 errors in 1 file (checked 1 source file)

Your Environment

nathanjmcdougall commented 1 year ago

I think the following similar example using Protocol should also typecheck, but fails in a similar way:

from typing import Protocol, Type

class P(Protocol):
    pass

def f(typ: Type[P]) -> Type[P]:
    class C(typ):
        pass

    return C

I'm very new to this though, so I'm not sure.

baileywickham commented 1 year ago

From this comment on #5865, this still fails

from typing import TypeVar

C = TypeVar("C", bound="MyClass")

class MyClass:
    class InnerClass:
        pass

    @classmethod
    def dynamic_subclass(cls: type[C]) -> type[C]:
        # Mypy emits two errors on the following line:
        # error: Variable "cls" is not valid as a type  [valid-type]
        # error: Invalid base class "cls"  [misc]
        class MySubClass(cls):
            # Mypy emits one error on the following line:
            # Name "cls.InnerClass" is not defined  [name-defined]
            class MySubInnerClass(cls.InnerClass):
                pass

            pass
        return MySubClass

# to show that everything works as expected at runtime:
SubClass = MyClass.dynamic_subclass()
my_subclass_instance = SubClass()

with errors

../../test.py:15: error: Variable "cls" is not valid as a type  [valid-type]
../../test.py:15: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases
../../test.py:15: error: Invalid base class "cls"  [misc]
../../test.py:18: error: Name "cls.InnerClass" is not defined  [name-defined]
Found 3 errors in 1 file (checked 1 source file)

From what I can tell, these errors are related to this issue.

TLCFEM commented 1 day ago

Also, example.

from typing import cast, TypeVar

T = TypeVar('T')

def cast_as(cls: type[T], a) -> T:
    return cast(cls, a)
OYSL.py:7: error: Variable "cls" is not valid as a type  [valid-type]
OYSL.py:7: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases
Found 1 error in 1 file (checked 1 source file)