Shoobx / mypy-zope

Plugin for mypy to support zope.interface
MIT License
39 stars 13 forks source link

make it possible to define a generic interface #95

Open glyph opened 1 year ago

glyph commented 1 year ago

This probably requires some implementation support from the zope.interface side, but I find myself wanting to be able to write this:

from dataclasses import dataclass
from typing import Generic, TypeVar

from zope.interface import Interface, implementer

T = TypeVar("T")

class IA(Generic[T], Interface):
    def m() -> T:
        ...

@implementer(IA)
@dataclass
class A(Generic[T]):
    _v: T
    def m(self) -> T:
        return self._v

a: IA[int] = A(3)

This actually appears to do what I want at type-check time! But then it (somewhat obviously) crashes at runtime.

This horrible hack almost works though, which suggests that this could work properly with some very small changes:

# from __future__ import annotations
from dataclasses import dataclass
from typing import Generic, TypeVar, TYPE_CHECKING

from zope.interface import Interface, implementer

T = TypeVar("T")

if TYPE_CHECKING:
    from typing import Generic as GenericInterface
else:
    from zope.interface.interface import InterfaceClass
    class SpecialInterfaceClass(InterfaceClass):
        def __getitem__(self, key):
            return self
    EmptyInterface = SpecialInterfaceClass("<Empty>", __module__=__name__)
    class GenericInterfaceClass:
        def __getitem__(self, typevars):
            return EmptyInterface
    GenericInterface = GenericInterfaceClass()

class IA(Interface, GenericInterface[T]):
    def m() -> T:
        ...

@implementer(IA)
@dataclass
class A(Generic[T]):
    _v: T
    def m(self) -> T:
        return self._v

@dataclass
class B:
    ...

a: IA[int] = A(3)               # works, hooray
a = B()                         # error, hooray
a = A("oops")                   # no error, boo