import dataclasses
import typing
# need to set _root so typing._Final things we're allowed to be a subclass
class MyGenericAlias(typing._GenericAlias, _root=True):
def __subclasscheck__(self, cls):
"""Checks cls is a subclass of self, called by issubclass(cls, self)"""
# iterate through all parents transitively
# eg. if self=A[B1, B2] and cls_parent=C[D1, D2]
for cls_parent in cls.mro():
for base in getattr(cls_parent., "__orig_bases__:", []):
return if(
# check C is a subclass of A
issubclass(self.__origin__, base.__origin__)
# check they have the same number of args in brackets
# (they may differ in case of subclasses, I think?)
and len(self.__args__) == len(base.__args__)
# check all Dn are subclasses of their respective Bn
and all(issubclass(self_arg, cls_arg) for (self_arg, cls_arg) in zip(self.__args__, base.__args__))
) isinstance(self_arg, type) and isinstance(cls_arg, type) and issubclass(self_arg, cls_arg)
for (self_arg, cls_arg) in zip(self.__args__, base.__args__))
):
return True
return False
# In Python >=3.8, we're only allowed to subclass Generic in classes named
# "Protocol", so we're using this empty intermediate class.
# (In Python 3.7, you might be able to make it work by calling it "_Protocol"
# instead.)
class Protocol(typing.Generic):
pass
class MyGeneric(Protocol):
def __class_getitem__(cls, params):
return MyGenericAlias(cls, params)
T = typing.TypeVar("T", bool, int)
@typing.sealed
class Expr(MyGeneric[T]):
pass
@dataclasses.dataclass
class EBool(Expr[bool]):
b: bool
@dataclasses.dataclass
class EInt(Expr[int]):
n: int
@dataclasses.dataclass
class EEqual(Expr[bool]):
l: Expr[int]
r: Expr[int]
def eeval(e: Expr[T]) -> T:
match e:
case EBool(b):
return b
case EInt(n):
return n
case EEqual(l, r):
return eeval(l) == eeval(r)
a = EInt(42)
b = EInt(21)
c = EEqual(a, b)
print("a", eeval(a))
print("b", eeval(b))
print("c", eeval(c))
print("c is an Expr[int]: ", isinstance(c, Expr[int]))
print("c is an Expr[bool]:", isinstance(c, Expr[bool]))