ofek · GitHub

Resolves #894


After adding support for subclassing StructMeta in #890, users could define metaclasses like:

class IntegerStructMeta(StructMeta, ABCMeta): ...

and then use them for abstract Struct bases:

class IntegerStructBase(Struct, metaclass=IntegerStructMeta):
    @abstractmethod
    def to_integer(self) -> int: ...

At runtime this exposed two related problems:

  1. Original failure (user issue):
    issubclass(ConcreteIntStruct, IntegerStructBase) and isinstance(obj, IntegerStructBase) crashed with:

    AttributeError: type object 'IntegerStructBase' has no attribute '_abc_impl'
    

    This comes from the __instancecheck__/__subclasscheck__ methods of ABCMeta, which assume the ABC machinery has been initialized (_abc_init/update_abstractmethods have run and _abc_impl is present). Because StructMeta_new_inner built the type via PyType_Type.tp_new without calling any ABC helper, the ABC state was never initialized, so the cache object _abc_impl was missing.

  2. Follow-up failure when trying to just call __new__:
    An attempt to delegate to the "next" metaclass in the MRO (a C equivalent of super(StructMeta, mcls).__new__(...)) resulted in:

    TypeError: type.__new__(IntegerStructMeta) is not safe, use IntegerStructMeta.__new__()
    

    This comes from CPython's new safety check, which rejects calling a base type's __new__ when the static base has a different tp_new. This has been deprecated since 3.12 but is now officially unsupported starting in 3.14.

So mixing StructMeta and ABCMeta gave you either a crash in ABC’s internals or a TypeError from the metaclass construction path.

Taking inspiration from that new safety check, I think it's infeasible to support mixing with arbitrary metaclasses and now we have special logic for ABCMeta.

Read the original on github.com ↗