/*###########################################################################
 #
 # Copyright (c) 2003 Zope Foundation and Contributors.
 # All Rights Reserved.
 #
 # This software is subject to the provisions of the Zope Public License,
 # Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
 # FOR A PARTICULAR PURPOSE.
 #
 ############################################################################*/

#include "Python.h"
#include "structmember.h"

#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-parameter"
#pragma clang diagnostic ignored "-Wmissing-field-initializers"
#endif

#define TYPE(O) ((PyTypeObject*)(O))
#define OBJECT(O) ((PyObject*)(O))
#define CLASSIC(O) ((PyClassObject*)(O))
#ifndef Py_TYPE
#define Py_TYPE(o) ((o)->ob_type)
#endif

#define PyNative_FromString PyUnicode_FromString

#if PY_VERSION_HEX < 0x030d0000
/* PyDict_GetItemRef() was added in Python 3.13 and returns a strong
 * (owned) reference.  This matters for free-threaded Python (PEP 703):
 * PyDict_GetItem() returns a borrowed reference, and another thread
 * could clear the dict between the return and the caller's Py_INCREF,
 * leaving a dangling pointer.  PyDict_GetItemRef() performs the INCREF
 * while still holding the dict's internal lock.
 *
 * Return values:  1 = found (*result set, caller owns a ref)
 *                 0 = not found (*result = NULL)
 *                -1 = error (*result = NULL, exception set)
 *
 * Uses PyDict_GetItemWithError() instead of PyDict_GetItem() to
 * properly propagate exceptions from __hash__/__eq__.
 */
static inline int
_PyDict_GetItemRef(PyObject *p, PyObject *key, PyObject **result)
{
    PyObject *item = PyDict_GetItemWithError(p, key);
    if (item != NULL) {
        Py_INCREF(item);
        *result = item;
        return 1;
    }
    if (PyErr_Occurred()) {
        *result = NULL;
        return -1;
    }
    *result = NULL;
    return 0;
}
#define PyDict_GetItemRef _PyDict_GetItemRef
#endif

#define ASSURE_DICT(N)                                                         \
    if (N == NULL) {                                                           \
        N = PyDict_New();                                                      \
        if (N == NULL)                                                         \
            return NULL;                                                       \
    }

/*
 *  Don't use heap-allocated types for Python < 3.11:  the API needed
 *  to find the dynamic module, 'PyType_GetModuleByDef', was added then.
 */
#if PY_VERSION_HEX < 0x030b0000
#define USE_STATIC_TYPES 1
#define USE_HEAP_TYPES 0
#else
#define USE_STATIC_TYPES 0
#define USE_HEAP_TYPES 1
#endif

#define BASETYPE_FLAGS \
    Py_TPFLAGS_DEFAULT | \
    Py_TPFLAGS_BASETYPE | \
    Py_TPFLAGS_HAVE_GC

#if PY_VERSION_HEX >= 0x030c0000
/* Add MANAGED_WEAKREF flag for Python >= 3.12, and don't define
 * the '.tp_weaklistoffset' slot.
 *
 * See: https://docs.python.org/3/c-api/typeobj.html
 *      #c.PyTypeObject.tp_weaklistoffset
 */
#define USE_EXPLICIT_WEAKREFLIST 0
#define WEAKREFTYPE_FLAGS BASETYPE_FLAGS | Py_TPFLAGS_MANAGED_WEAKREF
#else
/* No MANAGED_WEAKREF flag for Python < 3.12, and therefore define
 * the '.tp_weaklistoffset' slot, and the member whose offset it holds.
 *
 * See: https://docs.python.org/3/c-api/typeobj.html
 *      #c.PyTypeObject.tp_weaklistoffset
 */
#define USE_EXPLICIT_WEAKREFLIST 1
#define WEAKREFTYPE_FLAGS BASETYPE_FLAGS
#endif

/* Static strings, used to invoke PyObject_GetAttr (only in hot paths) */
static PyObject *str__class__ = NULL;
static PyObject *str__conform__ = NULL;
static PyObject *str__dict__ = NULL;
static PyObject *str__module__ = NULL;
static PyObject *str__name__ = NULL;
static PyObject *str__providedBy__ = NULL;
static PyObject *str__provides__ = NULL;
static PyObject *str__self__ = NULL;
static PyObject *str_generation = NULL;
static PyObject *str_registry = NULL;
static PyObject *strro = NULL;

/* Static strings, used to invoke PyObject_CallMethodObjArgs */
static PyObject *str_call_conform = NULL;
static PyObject *str_uncached_lookup = NULL;
static PyObject *str_uncached_lookupAll = NULL;
static PyObject *str_uncached_subscriptions = NULL;
static PyObject *strchanged = NULL;
static PyObject *str__adapt__ = NULL;
static PyObject *str_CALL_CUSTOM_ADAPT = NULL;

/* Static strings, used to invoke PyObject_GetItem
 *
 * We cannot use PyDict_GetItemString, because the '__dict__' we get
 * from our types can be a 'types.mappingproxy', which causes a segfault.
 */
static PyObject* str__implemented__;


static int
define_static_strings()
{
    if (str__class__ != NULL) {
        return 0;
    }

#define DEFINE_STATIC_STRING(S)                  \
    if (!(str##S = PyUnicode_FromString(#S)))    \
    return -1

    DEFINE_STATIC_STRING(__class__);
    DEFINE_STATIC_STRING(__conform__);
    DEFINE_STATIC_STRING(__dict__);
    DEFINE_STATIC_STRING(__module__);
    DEFINE_STATIC_STRING(__name__);
    DEFINE_STATIC_STRING(__providedBy__);
    DEFINE_STATIC_STRING(__provides__);
    DEFINE_STATIC_STRING(__self__);
    DEFINE_STATIC_STRING(_generation);
    DEFINE_STATIC_STRING(_registry);
    DEFINE_STATIC_STRING(ro);
    DEFINE_STATIC_STRING(__implemented__);
    DEFINE_STATIC_STRING(_call_conform);
    DEFINE_STATIC_STRING(_uncached_lookup);
    DEFINE_STATIC_STRING(_uncached_lookupAll);
    DEFINE_STATIC_STRING(_uncached_subscriptions);
    DEFINE_STATIC_STRING(changed);
    DEFINE_STATIC_STRING(__adapt__);
    DEFINE_STATIC_STRING(_CALL_CUSTOM_ADAPT);
#undef DEFINE_STATIC_STRING

    return 0;
}

/* Public module-scope functions, forward-declared here for type methods. */
static PyObject *implementedBy(PyObject* module, PyObject *cls);
static PyObject *getObjectSpecification(PyObject *module, PyObject *ob);
static PyObject *providedBy(PyObject *module, PyObject *ob);

/*
 * Utility functions, forward-declared here for type methods.
 */
static PyObject* _get_module(PyTypeObject *typeobj);
static PyObject* _get_adapter_hooks(PyTypeObject *typeobj);
static PyTypeObject* _get_specification_base_class(PyTypeObject *typeobj);
static PyTypeObject* _get_interface_base_class(PyTypeObject *typeobj);

#if USE_STATIC_TYPES
/*
 *  Global used by static IB__adapt
 */
static PyObject*       adapter_hooks = NULL;

/*
 *  Globals imported from 'zope.interface.declarations'
 */
static int imported_declarations = 0;
static PyObject* BuiltinImplementationSpecifications;
static PyObject* empty;
static PyObject* fallback;
static PyTypeObject *Implements;

/* Import zope.interface.declarations and store results in global statics.
 *
 * Static alternative to '_zic_state_load_declarations' below.
 */
static int
import_declarations(void)
{
    PyObject *declarations, *i;

    declarations = PyImport_ImportModule("zope.interface.declarations");
    if (declarations == NULL) { return -1; }

    BuiltinImplementationSpecifications = PyObject_GetAttrString(
                        declarations, "BuiltinImplementationSpecifications");
    if (BuiltinImplementationSpecifications == NULL) { return -1; }

    empty = PyObject_GetAttrString(declarations, "_empty");
    if (empty == NULL) { return -1; }

    fallback = PyObject_GetAttrString(declarations, "implementedByFallback");
    if (fallback == NULL) { return -1;}

    i = PyObject_GetAttrString(declarations, "Implements");
    if (i == NULL) { return -1; }

    if (! PyType_Check(i)) {
        PyErr_SetString(
            PyExc_TypeError,
            "zope.interface.declarations.Implements is not a type");
        return -1;
    }

    Implements = (PyTypeObject *)i;

    Py_DECREF(declarations);

    imported_declarations = 1;
    return 0;
}

#endif

/*
 *  SpecificationBase class
 */
typedef struct
{
    PyObject_HEAD
    /*
      In the past, these fields were stored in the __dict__
      and were technically allowed to contain any Python object, though
      other type checks would fail or fall back to generic code paths if
      they didn't have the expected type. We preserve that behaviour and don't
      make any assumptions about contents.
    */
    PyObject* _implied;
#if USE_EXPLICIT_WEAKREFLIST
    PyObject* weakreflist;
#endif
    /*
      The remainder aren't used in C code but must be stored here
      to prevent instance layout conflicts.
    */
    PyObject* _dependents;
    PyObject* _bases;
    PyObject* _v_attrs;
    PyObject* __iro__;
    PyObject* __sro__;
} SB;

/*
  We know what the fields are *supposed* to define, but
  they could have anything, so we need to traverse them.
*/
static int
SB_traverse(SB* self, visitproc visit, void* arg)
{
/* Visit our 'tp_type' only on Python >= 3.9, per
 * https://docs.python.org/3/howto/isolating-extensions.html
 * #tp-traverse-in-python-3-8-and-lower
 */
#if USE_HEAP_TYPES && PY_VERSION_HEX > 0x03090000
    Py_VISIT(Py_TYPE(self));
#endif
    Py_VISIT(self->_implied);
    Py_VISIT(self->_dependents);
    Py_VISIT(self->_bases);
    Py_VISIT(self->_v_attrs);
    Py_VISIT(self->__iro__);
    Py_VISIT(self->__sro__);
    return 0;
}

static int
SB_clear(SB* self)
{
    Py_CLEAR(self->_implied);
    Py_CLEAR(self->_dependents);
    Py_CLEAR(self->_bases);
    Py_CLEAR(self->_v_attrs);
    Py_CLEAR(self->__iro__);
    Py_CLEAR(self->__sro__);
    return 0;
}

static void
SB_dealloc(SB* self)
{
    PyObject_GC_UnTrack((PyObject*)self);
    PyObject_ClearWeakRefs(OBJECT(self));
    PyTypeObject* tp = Py_TYPE(self);
    SB_clear(self);
    tp->tp_free(OBJECT(self));
#if USE_HEAP_TYPES
    Py_DECREF(tp);
#endif
}

static char SB_extends__doc__[] =
  "Test whether a specification is or extends another";

static PyObject*
SB_extends(SB* self, PyObject* other)
{
    PyObject* implied;
    int contains;

    implied = self->_implied;
    if (implied == NULL) {
        return NULL;
    }

    contains = PyDict_Contains(implied, other);
    if (contains < 0)
        return NULL;
    if (contains)
        Py_RETURN_TRUE;
    Py_RETURN_FALSE;
}

static PyObject*
SB__call__(SB* self, PyObject* args, PyObject* kw)
{
    PyObject* spec;

    if (!PyArg_ParseTuple(args, "O", &spec))
        return NULL;
    return SB_extends(self, spec);
}

static char SB_providedBy__doc__[] =
  "Test whether an interface is implemented by the specification";

static PyObject*
SB_providedBy(PyObject* self, PyObject* ob)
{
    PyObject *decl;
    PyObject *item;
    PyObject *module;
    PyTypeObject *specification_base_class;

    module = _get_module(Py_TYPE(self));
    specification_base_class = _get_specification_base_class(Py_TYPE(self));

    decl = providedBy(module, ob);
    if (decl == NULL)
        return NULL;

    if (PyObject_TypeCheck(decl, specification_base_class))
        item = SB_extends((SB*)decl, self);
    else
        /* decl is probably a security proxy.  We have to go the long way
           around.
        */
        item = PyObject_CallFunctionObjArgs(decl, self, NULL);

    Py_DECREF(decl);
    return item;
}

static char SB_implementedBy__doc__[] =
  "Test whether the specification is implemented by a class or factory.\n"
  "Raise TypeError if argument is neither a class nor a callable.";

static PyObject*
SB_implementedBy(PyObject* self, PyObject* cls)
{
    PyObject *decl;
    PyObject *item;
    PyObject *module;
    PyTypeObject *specification_base_class;

    module = _get_module(Py_TYPE(self));
    specification_base_class = _get_specification_base_class(Py_TYPE(self));

    decl = implementedBy(module, cls);
    if (decl == NULL)
        return NULL;

    if (PyObject_TypeCheck(decl, specification_base_class))
        item = SB_extends((SB*)decl, self);
    else
        item = PyObject_CallFunctionObjArgs(decl, self, NULL);

    Py_DECREF(decl);
    return item;
}

static struct PyMethodDef SB_methods[] = {
    { "providedBy",
      (PyCFunction)SB_providedBy,
      METH_O,
      SB_providedBy__doc__ },
    { "implementedBy",
      (PyCFunction)SB_implementedBy,
      METH_O,
      SB_implementedBy__doc__ },
    { "isOrExtends",
      (PyCFunction)SB_extends,
      METH_O,
      SB_extends__doc__ },

    { NULL, NULL } /* sentinel */
};

static PyMemberDef SB_members[] = {
    { "_implied", T_OBJECT_EX, offsetof(SB, _implied), 0, "" },
    { "_dependents", T_OBJECT_EX, offsetof(SB, _dependents), 0, "" },
    { "_bases", T_OBJECT_EX, offsetof(SB, _bases), 0, "" },
    { "_v_attrs", T_OBJECT_EX, offsetof(SB, _v_attrs), 0, "" },
    { "__iro__", T_OBJECT_EX, offsetof(SB, __iro__), 0, "" },
    { "__sro__", T_OBJECT_EX, offsetof(SB, __sro__), 0, "" },
#if USE_EXPLICIT_WEAKREFLIST
    { "__weaklistoffset__", T_PYSSIZET, offsetof(SB, weakreflist), READONLY, "" },
#endif
    { NULL },
};

static char SB__name__[] = "_zope_interface_coptimizations.SpecificationBase";
static char SB__doc__[] = "Base type for Specification objects";

#if USE_STATIC_TYPES

/*
 * Static type: SpecificationBase
 */

static PyTypeObject SB_type_def = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name           = SB__name__,
    .tp_doc            = SB__doc__,
    .tp_basicsize      = sizeof(SB),
    .tp_flags          = WEAKREFTYPE_FLAGS,
    .tp_call           = (ternaryfunc)SB__call__,
    .tp_traverse       = (traverseproc)SB_traverse,
    .tp_clear          = (inquiry)SB_clear,
    .tp_dealloc        = (destructor)SB_dealloc,
#if USE_EXPLICIT_WEAKREFLIST
    .tp_weaklistoffset = offsetof(SB, weakreflist),
#endif
    .tp_methods        = SB_methods,
    .tp_members        = SB_members,
};

#else

/*
 * Heap-based type: SpecificationBase
 */
static PyType_Slot SB_type_slots[] = {
    {Py_tp_doc,         SB__doc__},
    {Py_tp_call,        SB__call__},
    {Py_tp_traverse,    SB_traverse},
    {Py_tp_clear,       SB_clear},
    {Py_tp_dealloc,     SB_dealloc},
    {Py_tp_methods,     SB_methods},
    {Py_tp_members,     SB_members},
    {0,                 NULL}
};

static PyType_Spec SB_type_spec = {
    .name               = SB__name__,
    .basicsize          = sizeof(SB),
    .flags              = WEAKREFTYPE_FLAGS,
    .slots              = SB_type_slots
};

#endif

/*
 *  ObjectSpecificationDescriptor class
 */
#if USE_HEAP_TYPES
static int
OSD_traverse(PyObject* self, visitproc visit, void* arg)
{
    Py_VISIT(Py_TYPE(self));
    return 0;
}

static void
OSD_dealloc(PyObject* self)
{
    PyObject_GC_UnTrack(self);
    PyTypeObject *tp = Py_TYPE(self);
    tp->tp_free(OBJECT(self));
    Py_DECREF(tp);
}
#endif

static PyObject*
OSD_descr_get(PyObject* self, PyObject* inst, PyObject* cls)
{
    PyObject* provides;
    PyObject *module;

    module = _get_module(Py_TYPE(self));

    if (inst == NULL) {
        return getObjectSpecification(module, cls);
    }

    provides = PyObject_GetAttr(inst, str__provides__);
    /* Return __provides__ if we got it, or return NULL and propagate
     * non-AttributeError. */
    if (provides != NULL || !PyErr_ExceptionMatches(PyExc_AttributeError)) {
        return provides;
    }

    PyErr_Clear();

    return implementedBy(module, cls);
}

static char OSD__name__[] = (
    "_zope_interface_coptimizations.ObjectSpecificationDescriptor");
static char OSD__doc__[] = "Object Specification Descriptor";

#if USE_STATIC_TYPES

/*
 * Static type: ObjectSpecificationDescriptor
 */

static PyTypeObject OSD_type_def = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name            = OSD__name__,
    .tp_doc             = OSD__doc__,
    /* No GC for the static version */
    .tp_flags           = Py_TPFLAGS_DEFAULT |
                          Py_TPFLAGS_BASETYPE,
    .tp_descr_get       = (descrgetfunc)OSD_descr_get,
  /*.tp_traverse,       = OSD_traverse},     not reqd for static */
  /*.tp_dealloc,        = OSD_dealloc},      not reqd for static */
};

#else

/*
 * Heap type: ObjectSpecificationDescriptor
 */
static PyType_Slot OSD_type_slots[] = {
    {Py_tp_doc,         OSD__doc__},
    {Py_tp_descr_get,   OSD_descr_get},
    {Py_tp_traverse,    OSD_traverse},
    {Py_tp_dealloc,     OSD_dealloc},
    {0,                 NULL}
};

static PyType_Spec OSD_type_spec = {
    .name               = OSD__name__,
    .basicsize          = 0,
    .flags              = BASETYPE_FLAGS,
    .slots              = OSD_type_slots
};

#endif

/*
 *  ClassProvidesBase class
 */
typedef struct
{
    SB spec;
    /* These members are handled generically, as for SB members. */
    PyObject* _cls;
    PyObject* _implements;
} CPB;

static int
CPB_traverse(CPB* self, visitproc visit, void* arg)
{
    Py_VISIT(self->_cls);
    Py_VISIT(self->_implements);
    return SB_traverse((SB*)self, visit, arg);
}

static int
CPB_clear(CPB* self)
{
    Py_CLEAR(self->_cls);
    Py_CLEAR(self->_implements);
    return SB_clear((SB*)self);
}

static void
CPB_dealloc(CPB* self)
{
    PyObject_GC_UnTrack((PyObject*)self);
    CPB_clear(self);
    SB_dealloc((SB*)self); /* handles decrefing tp */
}

static PyObject*
CPB_descr_get(CPB* self, PyObject* inst, PyObject* cls)
{
    PyObject* implements;

    if (self->_cls == NULL)
        return NULL;

    if (cls == self->_cls) {
        if (inst == NULL) {
            Py_INCREF(self);
            return OBJECT(self);
        }

        implements = self->_implements;
        Py_XINCREF(implements);
        return implements;
    }

    PyErr_SetString(PyExc_AttributeError, "__provides__");
    return NULL;
}

static PyMemberDef CPB_members[] = {
    { "_cls", T_OBJECT_EX, offsetof(CPB, _cls), 0, "Defining class." },
    { "_implements",
      T_OBJECT_EX,
      offsetof(CPB, _implements),
      0,
      "Result of implementedBy." },
    { NULL }
};

static char CPB__name__[] = "_zope_interface_coptimizations.ClassProvidesBase";
static char CPB__doc__[] = "C Base class for ClassProvides";

#if USE_STATIC_TYPES

/*
 * Static type: ClassProvidesBase
 */

static PyTypeObject CPB_type_def = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name            = CPB__name__,
    .tp_doc             = CPB__doc__,
    .tp_base            = &SB_type_def,
    .tp_basicsize       = sizeof(CPB),
    .tp_flags           = BASETYPE_FLAGS,
    .tp_descr_get       = (descrgetfunc)CPB_descr_get,
    .tp_traverse        = (traverseproc)CPB_traverse,
    .tp_clear           = (inquiry)CPB_clear,
    .tp_dealloc         = (destructor)CPB_dealloc,
    .tp_members         = CPB_members,
};

#else

/*
 * Heap type: ClassProvidesBase
 */
static PyType_Slot CPB_type_slots[] = {
    {Py_tp_doc,         CPB__doc__},
    {Py_tp_descr_get,   CPB_descr_get},
    {Py_tp_traverse,    CPB_traverse},
    {Py_tp_clear,       CPB_clear},
    {Py_tp_dealloc,     CPB_dealloc},
    {Py_tp_members,     CPB_members},
    /* tp_base cannot be set as a slot -- pass to PyType_FromModuleAndSpec */
    {0,                 NULL}
};

static PyType_Spec CPB_type_spec = {
    .name               = CPB__name__,
    .basicsize          = sizeof(CPB),
    .flags              = BASETYPE_FLAGS,
    .slots              = CPB_type_slots
};

#endif

/*
 *  InterfaceBase class
 */

typedef struct
{
    SB spec;
    PyObject* __name__;
    PyObject* __module__;
    Py_hash_t _v_cached_hash;
} IB;

static int
IB_traverse(IB* self, visitproc visit, void* arg)
{
    Py_VISIT(self->__name__);
    Py_VISIT(self->__module__);
    return SB_traverse((SB*)self, visit, arg);
}

static int
IB_clear(IB* self)
{
    Py_CLEAR(self->__name__);
    Py_CLEAR(self->__module__);
    return SB_clear((SB*)self);
}

static void
IB_dealloc(IB* self)
{
    PyObject_GC_UnTrack((PyObject*)self);
    IB_clear(self);
    SB_dealloc((SB*)self); /* handles decrefing tp */
}

static int
IB__init__(IB* self, PyObject* args, PyObject* kwargs)
{
    static char* kwlist[] = { "__name__", "__module__", NULL };
    PyObject* module = NULL;
    PyObject* name = NULL;

    if (!PyArg_ParseTupleAndKeywords(
          args, kwargs, "|OO:InterfaceBase.__init__", kwlist, &name, &module)) {
        return -1;
    }
    IB_clear(self);
    self->__module__ = module ? module : Py_None;
    Py_INCREF(self->__module__);
    self->__name__ = name ? name : Py_None;
    Py_INCREF(self->__name__);
    return 0;
}

/*
    def __adapt__(self, obj):
        """Adapt an object to the receiver
        """
        if self.providedBy(obj):
            return obj

        for hook in adapter_hooks:
            adapter = hook(self, obj)
            if adapter is not None:
                return adapter


*/
const char IB__adapt____doc__[] = "Adapt an object to the receiver";

static PyObject*
IB__adapt__(PyObject* self, PyObject* obj)
{
    PyObject *decl;
    PyObject *args;
    PyObject *adapter;
    PyObject *module;
    PyObject *adapter_hooks;
    PyTypeObject *specification_base_class;
    int implements;
    int i;
    int l;

    module = _get_module(Py_TYPE(self));

    decl = providedBy(module, obj);
    if (decl == NULL)
        return NULL;

    specification_base_class = _get_specification_base_class(Py_TYPE(self));

    if (PyObject_TypeCheck(decl, specification_base_class)) {
        PyObject* implied;

        implied = ((SB*)decl)->_implied;
        if (implied == NULL) {
            Py_DECREF(decl);
            return NULL;
        }

        implements = PyDict_Contains(implied, self);
        Py_DECREF(decl);
        if (implements < 0)
            return NULL;
    } else {
        /* decl is probably a security proxy.  We have to go the long way
           around.
        */
        PyObject* r;
        r = PyObject_CallFunctionObjArgs(decl, self, NULL);
        Py_DECREF(decl);
        if (r == NULL)
            return NULL;
        implements = PyObject_IsTrue(r);
        Py_DECREF(r);
    }

    if (implements) {
        Py_INCREF(obj);
        return obj;
    }

    args = PyTuple_New(2);
    if (args == NULL) { return NULL; }

    Py_INCREF(self);
    PyTuple_SET_ITEM(args, 0, self);

    Py_INCREF(obj);
    PyTuple_SET_ITEM(args, 1, obj);

    adapter_hooks = _get_adapter_hooks(Py_TYPE(self));
    l = PyList_GET_SIZE(adapter_hooks);
    for (i = 0; i < l; i++) {
        adapter = PyObject_CallObject(PyList_GET_ITEM(adapter_hooks, i), args);
        if (adapter == NULL || adapter != Py_None) {
            Py_DECREF(args);
            return adapter;
        }
        Py_DECREF(adapter);
    }

    Py_DECREF(args);

    Py_INCREF(Py_None);
    return Py_None;
}

/*
    def __call__(self, obj, alternate=_marker):
        try:
            conform = obj.__conform__
        except AttributeError: # pylint:disable=bare-except
            conform = None

        if conform is not None:
            adapter = self._call_conform(conform)
            if adapter is not None:
                return adapter

        adapter = self.__adapt__(obj)

        if adapter is not None:
            return adapter
        if alternate is not _marker:
            return alternate
        raise TypeError("Could not adapt", obj, self)

*/
static PyObject*
IB__call__(PyObject* self, PyObject* args, PyObject* kwargs)
{
    PyObject *conform, *obj, *alternate, *adapter;
    int _has_custom;
    static char* kwlist[] = { "obj", "alternate", NULL };
    conform = obj = alternate = adapter = NULL;

    if (!PyArg_ParseTupleAndKeywords(
          args, kwargs, "O|O", kwlist, &obj, &alternate))
        return NULL;

    conform = PyObject_GetAttr(obj, str__conform__);
    if (conform == NULL) {
        if (!PyErr_ExceptionMatches(PyExc_AttributeError)) {
            /* Propagate non-AttributeErrors */
            return NULL;
        }
        PyErr_Clear();

        Py_INCREF(Py_None);
        conform = Py_None;
    }

    if (conform != Py_None) {
        adapter =
          PyObject_CallMethodObjArgs(self, str_call_conform, conform, NULL);
        Py_DECREF(conform);
        if (adapter == NULL || adapter != Py_None)
            return adapter;
        Py_DECREF(adapter);
    } else {
        Py_DECREF(conform);
    }

    /* We differ from the Python code here. For speed, instead of always calling
       self.__adapt__(), we check to see if the type has defined it. Checking in
       the dict for __adapt__ isn't sufficient because there's no cheap way to
       tell if it's the __adapt__ that InterfaceBase itself defines (our type
       will *never* be InterfaceBase, we're always subclassed by
       InterfaceClass). Instead, we cooperate with InterfaceClass in Python to
       set a flag in a new subclass when this is necessary. */
    /* Check if the type has a _CALL_CUSTOM_ADAPT flag set by InterfaceClass.
     * Use PyDict_Contains (error-safe) instead of PyDict_GetItem (which
     * silently swallows exceptions).  On 3.13+ use PyType_GetDict() for a
     * strong reference to the type dict, needed for free-threaded Python. */
    {
#if PY_VERSION_HEX >= 0x030d0000
        PyObject* _tp_dict = PyType_GetDict(Py_TYPE(self));
        _has_custom = PyDict_Contains(_tp_dict, str_CALL_CUSTOM_ADAPT);
        Py_DECREF(_tp_dict);
#else
        _has_custom = PyDict_Contains(
            Py_TYPE(self)->tp_dict, str_CALL_CUSTOM_ADAPT);
#endif
        if (_has_custom < 0)
            return NULL;
    }
    if (_has_custom) {
        /* Doesn't matter what the value is. Simply being present is enough. */
        adapter = PyObject_CallMethodObjArgs(self, str__adapt__, obj, NULL);
    } else {
        adapter = IB__adapt__(self, obj);
    }

    if (adapter == NULL || adapter != Py_None) {
        return adapter;
    }
    Py_DECREF(adapter);

    if (alternate != NULL) {
        Py_INCREF(alternate);
        return alternate;
    }

    adapter = Py_BuildValue("sOO", "Could not adapt", obj, self);
    if (adapter != NULL) {
        PyErr_SetObject(PyExc_TypeError, adapter);
        Py_DECREF(adapter);
    }
    return NULL;
}

static Py_hash_t
IB__hash__(IB* self)
{
    PyObject* tuple;
    if (!self->__module__) {
        PyErr_SetString(PyExc_AttributeError, "__module__");
        return -1;
    }
    if (!self->__name__) {
        PyErr_SetString(PyExc_AttributeError, "__name__");
        return -1;
    }

    if (self->_v_cached_hash) {
        return self->_v_cached_hash;
    }

    tuple = PyTuple_Pack(2, self->__name__, self->__module__);
    if (!tuple) {
        return -1;
    }
    self->_v_cached_hash = PyObject_Hash(tuple);
    Py_CLEAR(tuple);
    return self->_v_cached_hash;
}

static PyObject*
IB_richcompare(IB* self, PyObject* other, int op)
{
    PyObject* othername;
    PyObject* othermod;
    PyObject* oresult;
    PyTypeObject* interface_base_class;
    IB* otherib;
    int result;

    otherib = NULL;
    oresult = othername = othermod = NULL;

    if (OBJECT(self) == other) {
        switch (op) {
            case Py_EQ:
            case Py_LE:
            case Py_GE:
                Py_RETURN_TRUE;
                break;
            case Py_NE:
                Py_RETURN_FALSE;
        }
    }

    if (other == Py_None) {
        switch (op) {
            case Py_LT:
            case Py_LE:
            case Py_NE:
                Py_RETURN_TRUE;
            default:
                Py_RETURN_FALSE;
        }
    }

    interface_base_class = _get_interface_base_class(Py_TYPE(self));
    if (interface_base_class == NULL) {
        oresult = Py_NotImplemented;
        goto cleanup;
    }

    if (PyObject_TypeCheck(other, interface_base_class)) {
        // This branch borrows references. No need to clean
        // up if otherib is not null.
        otherib = (IB*)other;
        othername = otherib->__name__;
        othermod = otherib->__module__;
    } else {
        othername = PyObject_GetAttr(other, str__name__);
        if (othername) {
            othermod = PyObject_GetAttr(other, str__module__);
        }
        if (!othername || !othermod) {
            if (PyErr_Occurred() &&
                PyErr_ExceptionMatches(PyExc_AttributeError)) {
                PyErr_Clear();
                oresult = Py_NotImplemented;
            }
            goto cleanup;
        }
    }
#if 0
// This is the simple, straightforward version of what Python does.
    PyObject* pt1 = PyTuple_Pack(2, self->__name__, self->__module__);
    PyObject* pt2 = PyTuple_Pack(2, othername, othermod);
    oresult = PyObject_RichCompare(pt1, pt2, op);
#endif

    // tuple comparison is decided by the first non-equal element.
    result = PyObject_RichCompareBool(self->__name__, othername, Py_EQ);
    if (result == 0) {
        result = PyObject_RichCompareBool(self->__name__, othername, op);
    } else if (result == 1) {
        result = PyObject_RichCompareBool(self->__module__, othermod, op);
    }
    // If either comparison failed, we have an error set.
    // Leave oresult NULL so we raise it.
    if (result == -1) {
        goto cleanup;
    }

    oresult = result ? Py_True : Py_False;

cleanup:
    Py_XINCREF(oresult);

    if (!otherib) {
        Py_XDECREF(othername);
        Py_XDECREF(othermod);
    }
    return oresult;
}

static PyMemberDef IB_members[] = {
    { "__name__", T_OBJECT_EX, offsetof(IB, __name__), 0, "" },
    // The redundancy between __module__ and __ibmodule__ is because
    // __module__ is often shadowed by subclasses.
    { "__module__", T_OBJECT_EX, offsetof(IB, __module__), READONLY, "" },
    { "__ibmodule__", T_OBJECT_EX, offsetof(IB, __module__), 0, "" },
    { NULL }
};

static struct PyMethodDef IB_methods[] = {
    { "__adapt__", (PyCFunction)IB__adapt__, METH_O, IB__adapt____doc__},
    { NULL, NULL } /* sentinel */
};

static char IB__name__[] ="_zope_interface_coptimizations.InterfaceBase";
static char IB__doc__[] = (
    "Interface base type providing __call__ and __adapt__"
);

#if USE_STATIC_TYPES

/*
 * Static type: InterfaceBase
 */

static PyTypeObject IB_type_def = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name            = IB__name__,
    .tp_doc             = IB__doc__,
    .tp_base            = &SB_type_def,
    .tp_basicsize       = sizeof(IB),
    .tp_flags           = BASETYPE_FLAGS,
    .tp_init            = (initproc)IB__init__,
    .tp_hash            = (hashfunc)IB__hash__,
    .tp_richcompare     = (richcmpfunc)IB_richcompare,
    .tp_call            = (ternaryfunc)IB__call__,
    .tp_traverse        = (traverseproc)IB_traverse,
    .tp_clear           = (inquiry)IB_clear,
    .tp_dealloc         = (destructor)IB_dealloc,
    .tp_methods         = IB_methods,
    .tp_members         = IB_members,
};

#else

/*
 * Heap type: InterfaceBase
 */
static PyType_Slot IB_type_slots[] = {
    {Py_tp_doc,         IB__doc__},
    {Py_tp_init,        IB__init__},
    {Py_tp_hash,        IB__hash__},
    {Py_tp_richcompare, IB_richcompare},
    {Py_tp_call,        IB__call__},
    {Py_tp_traverse,    IB_traverse},
    {Py_tp_clear,       IB_clear},
    {Py_tp_dealloc,     IB_dealloc},
    {Py_tp_methods,     IB_methods},
    {Py_tp_members,     IB_members},
    /* tp_base cannot be set as a slot -- pass to PyType_FromModuleAndSpec */
    {0,                   NULL}
};

static PyType_Spec IB_type_spec = {
    .name               = IB__name__,
    .basicsize          = sizeof(IB),
    .flags              = BASETYPE_FLAGS,
    .slots              = IB_type_slots
};

#endif

/*
 *  LookupBase class
 */
typedef struct
{
    PyObject_HEAD
    PyObject* _cache;
    PyObject* _mcache;
    PyObject* _scache;
} LB;

static int
LB_traverse(LB* self, visitproc visit, void* arg)
{
/* Visit our 'tp_type' only on Python >= 3.9, per
 * https://docs.python.org/3/howto/isolating-extensions.html
 * #tp-traverse-in-python-3-8-and-lower
 */
#if USE_HEAP_TYPES && PY_VERSION_HEX > 0x03090000
    Py_VISIT(Py_TYPE(self));
#endif
    Py_VISIT(self->_cache);
    Py_VISIT(self->_mcache);
    Py_VISIT(self->_scache);
    return 0;
}

static int
LB_clear(LB* self)
{
    Py_CLEAR(self->_cache);
    Py_CLEAR(self->_mcache);
    Py_CLEAR(self->_scache);
    return 0;
}

static void
LB_dealloc(LB* self)
{
    PyObject_GC_UnTrack((PyObject*)self);
    PyTypeObject* tp = Py_TYPE(self);
    LB_clear(self);
    tp->tp_free((PyObject*)self);
#if USE_HEAP_TYPES
    Py_DECREF(tp);
#endif
}

/*
    def changed(self, ignored=None):
        self._cache.clear()
        self._mcache.clear()
        self._scache.clear()
*/
static PyObject*
LB_changed(LB* self, PyObject* ignored)
{
    LB_clear(self);
    Py_INCREF(Py_None);
    return Py_None;
}

/*
    def _getcache(self, provided, name):
        cache = self._cache.get(provided)
        if cache is None:
            cache = {}
            self._cache[provided] = cache
        if name:
            c = cache.get(name)
            if c is None:
                c = {}
                cache[name] = c
            cache = c
        return cache
*/
/* Return a strong reference to a sub-dict of 'cache' for 'key'.
 * Creates a new empty sub-dict if one doesn't exist yet.
 *
 * Uses PyDict_GetItemRef() for a strong reference, needed for
 * free-threaded Python where another thread could clear the parent
 * cache between PyDict_GetItem()'s return and our use of the result. */
static PyObject*
_subcache(PyObject* cache, PyObject* key)
{
    PyObject* subcache;
    int found;

    found = PyDict_GetItemRef(cache, key, &subcache);
    if (found < 0)
        return NULL;
    if (found == 0) {
        int status;

        subcache = PyDict_New();
        if (subcache == NULL)
            return NULL;
        status = PyDict_SetItem(cache, key, subcache);
        if (status < 0) {
            Py_DECREF(subcache);
            return NULL;
        }
        /* subcache has 2 refs now: one from PyDict_New(), one held
         * by the parent dict.  We return the PyDict_New() ref. */
    }

    return subcache;  /* Caller owns a reference */
}

/* Return a strong reference to the cache dict for (provided, name).
 * Caller must Py_DECREF() the returned dict when done. */
static PyObject*
_getcache(LB* self, PyObject* provided, PyObject* name)
{
    PyObject* cache;

    ASSURE_DICT(self->_cache);

    cache = _subcache(self->_cache, provided);  /* strong ref */
    if (cache == NULL)
        return NULL;

    /* Use PyUnicode_GET_LENGTH for a direct struct field access instead
     * of PyObject_IsTrue which dispatches through the generic truth
     * protocol (type slot lookup -> sq_length or nb_bool). */
    if (name != NULL && PyUnicode_GET_LENGTH(name) > 0) {
        PyObject* subcache = _subcache(cache, name);  /* strong ref */
        Py_DECREF(cache);  /* release provided-level cache ref */
        cache = subcache;
    }

    return cache;  /* Caller owns a reference */
}

/*
    def lookup(self, required, provided, name=u'', default=None):
        cache = self._getcache(provided, name)
        if len(required) == 1:
            result = cache.get(required[0], _not_in_mapping)
        else:
            result = cache.get(tuple(required), _not_in_mapping)

        if result is _not_in_mapping:
            result = self._uncached_lookup(required, provided, name)
            if len(required) == 1:
                cache[required[0]] = result
            else:
                cache[tuple(required)] = result

        if result is None:
            return default

        return result
*/

static PyObject*
_lookup(LB* self,
        PyObject* required,
        PyObject* provided,
        PyObject* name,
        PyObject* default_)
{
    PyObject *result, *key, *cache;
    result = key = cache = NULL;
    if (name && !PyUnicode_Check(name)) {
        PyErr_SetString(PyExc_ValueError, "name is not a string");
        return NULL;
    }

    /* If `required` is a lazy sequence, it could have arbitrary side-effects,
       such as clearing our caches. So we must not retrieve the cache until
       after resolving it. */
    /* Fast path: skip PySequence_Tuple allocation when required is
     * already a tuple (the common case from Python callers).
     * Py_INCREF so we own a reference in both branches — the else
     * branch gets a new reference from PySequence_Tuple, so this
     * branch must match, allowing a single Py_DECREF below. */
    if (PyTuple_CheckExact(required)) {
        Py_INCREF(required);
    } else {
        required = PySequence_Tuple(required);
        if (required == NULL)
            return NULL;
    }

    cache = _getcache(self, provided, name);  /* strong ref */
    if (cache == NULL) {
        Py_DECREF(required);
        return NULL;
    }

    if (PyTuple_GET_SIZE(required) == 1)
        key = PyTuple_GET_ITEM(required, 0);
    else
        key = required;

    /* Use PyDict_GetItemRef() for a strong reference to the cached result.
     * Needed for free-threaded Python where another thread could clear
     * the cache between PyDict_GetItem()'s return and our Py_INCREF(). */
    {
        int found = PyDict_GetItemRef(cache, key, &result);
        if (found < 0) {
            Py_DECREF(cache);
            Py_DECREF(required);
            return NULL;
        }
        if (found == 0) {
            int status;

            result = PyObject_CallMethodObjArgs(
              OBJECT(self), str_uncached_lookup, required, provided, name, NULL);
            if (result == NULL) {
                Py_DECREF(cache);
                Py_DECREF(required);
                return NULL;
            }
            status = PyDict_SetItem(cache, key, result);
            Py_DECREF(cache);
            Py_DECREF(required);
            if (status < 0) {
                Py_DECREF(result);
                return NULL;
            }
        } else {
            /* found == 1: result already has a strong ref */
            Py_DECREF(cache);
            Py_DECREF(required);
        }
    }

    if (result == Py_None && default_ != NULL) {
        Py_DECREF(Py_None);
        Py_INCREF(default_);
        return default_;
    }

    return result;
}

static PyObject*
LB_lookup(LB* self, PyObject* args, PyObject* kwds)
{
    static char* kwlist[] = { "required", "provided", "name", "default", NULL };
    PyObject *required, *provided, *name = NULL, *default_ = NULL;

    if (!PyArg_ParseTupleAndKeywords(args,
                                     kwds,
                                     "OO|OO:LookupBase.lookup",
                                     kwlist,
                                     &required,
                                     &provided,
                                     &name,
                                     &default_))
        return NULL;

    return _lookup(self, required, provided, name, default_);
}

/*
    def lookup1(self, required, provided, name=u'', default=None):
        cache = self._getcache(provided, name)
        result = cache.get(required, _not_in_mapping)
        if result is _not_in_mapping:
            return self.lookup((required, ), provided, name, default)

        if result is None:
            return default

        return result
*/
static PyObject*
_lookup1(LB* self,
         PyObject* required,
         PyObject* provided,
         PyObject* name,
         PyObject* default_)
{
    PyObject *result, *cache;

    if (name && !PyUnicode_Check(name)) {
        PyErr_SetString(PyExc_ValueError, "name is not a string");
        return NULL;
    }

    cache = _getcache(self, provided, name);  /* strong ref */
    if (cache == NULL)
        return NULL;

    /* Use PyDict_GetItemRef() for a strong reference.  See _lookup(). */
    {
        int found = PyDict_GetItemRef(cache, required, &result);
        Py_DECREF(cache);
        if (found < 0)
            return NULL;
        if (found == 0) {
            PyObject* tup;

            tup = PyTuple_New(1);
            if (tup == NULL)
                return NULL;
            Py_INCREF(required);
            PyTuple_SET_ITEM(tup, 0, required);
            result = _lookup(self, tup, provided, name, default_);
            Py_DECREF(tup);
        } else {
            /* found == 1: result already has a strong ref */
            if (result == Py_None && default_ != NULL) {
                Py_DECREF(result);
                result = default_;
                Py_INCREF(result);
            }
        }
    }

    return result;
}
static PyObject*
LB_lookup1(LB* self, PyObject* args, PyObject* kwds)
{
    static char* kwlist[] = { "required", "provided", "name", "default", NULL };
    PyObject *required, *provided, *name = NULL, *default_ = NULL;

    if (!PyArg_ParseTupleAndKeywords(args,
                                     kwds,
                                     "OO|OO:LookupBase.lookup1",
                                     kwlist,
                                     &required,
                                     &provided,
                                     &name,
                                     &default_))
        return NULL;

    return _lookup1(self, required, provided, name, default_);
}

/*
    def adapter_hook(self, provided, object, name=u'', default=None):
        required = providedBy(object)
        cache = self._getcache(provided, name)
        factory = cache.get(required, _not_in_mapping)
        if factory is _not_in_mapping:
            factory = self.lookup((required, ), provided, name)

        if factory is not None:
            if isinstance(object, super):
                object = object.__self__
            result = factory(object)
            if result is not None:
                return result

        return default
*/
static PyObject*
_adapter_hook(LB* self,
              PyObject* provided,
              PyObject* object,
              PyObject* name,
              PyObject* default_)
{
    PyObject *required;
    PyObject *factory;
    PyObject *result;
    PyObject *module;

    module = _get_module(Py_TYPE(self));

    if (name && !PyUnicode_Check(name)) {
        PyErr_SetString(PyExc_ValueError, "name is not a string");
        return NULL;
    }

    required = providedBy(module, object);
    if (required == NULL)
        return NULL;

    factory = _lookup1(self, required, provided, name, Py_None);
    Py_DECREF(required);
    if (factory == NULL)
        return NULL;

    if (factory != Py_None) {
        if (PyObject_TypeCheck(object, &PySuper_Type)) {
            PyObject* self = PyObject_GetAttr(object, str__self__);
            if (self == NULL) {
                Py_DECREF(factory);
                return NULL;
            }
            // Borrow the reference to self
            Py_DECREF(self);
            object = self;
        }
        result = PyObject_CallFunctionObjArgs(factory, object, NULL);
        Py_DECREF(factory);
        if (result == NULL || result != Py_None)
            return result;
    } else
        result = factory; /* None */

    if (default_ == NULL || default_ == result) /* No default specified, */
        return result; /* Return None.  result is owned None */

    Py_DECREF(result);
    Py_INCREF(default_);

    return default_;
}

static PyObject*
LB_adapter_hook(LB* self, PyObject* args, PyObject* kwds)
{
    static char* kwlist[] = { "provided", "object", "name", "default", NULL };
    PyObject *object, *provided, *name = NULL, *default_ = NULL;

    if (!PyArg_ParseTupleAndKeywords(args,
                                     kwds,
                                     "OO|OO:LookupBase.adapter_hook",
                                     kwlist,
                                     &provided,
                                     &object,
                                     &name,
                                     &default_))
        return NULL;

    return _adapter_hook(self, provided, object, name, default_);
}

static PyObject*
LB_queryAdapter(LB* self, PyObject* args, PyObject* kwds)
{
    static char* kwlist[] = { "object", "provided", "name", "default", NULL };
    PyObject *object, *provided, *name = NULL, *default_ = NULL;

    if (!PyArg_ParseTupleAndKeywords(args,
                                     kwds,
                                     "OO|OO:LookupBase.queryAdapter",
                                     kwlist,
                                     &object,
                                     &provided,
                                     &name,
                                     &default_))
        return NULL;

    return _adapter_hook(self, provided, object, name, default_);
}

/*
    def lookupAll(self, required, provided):
        cache = self._mcache.get(provided)
        if cache is None:
            cache = {}
            self._mcache[provided] = cache

        required = tuple(required)
        result = cache.get(required, _not_in_mapping)
        if result is _not_in_mapping:
            result = self._uncached_lookupAll(required, provided)
            cache[required] = result

        return result
*/
static PyObject*
_lookupAll(LB* self, PyObject* required, PyObject* provided)
{
    PyObject *cache, *result;

    /* resolve before getting cache. See note in _lookup. */
    if (PyTuple_CheckExact(required)) {
        Py_INCREF(required);
    } else {
        required = PySequence_Tuple(required);
        if (required == NULL)
            return NULL;
    }

    ASSURE_DICT(self->_mcache);

    cache = _subcache(self->_mcache, provided);  /* strong ref */
    if (cache == NULL) {
        Py_DECREF(required);
        return NULL;
    }

    /* Use PyDict_GetItemRef() for a strong reference.  See _lookup(). */
    {
        int found = PyDict_GetItemRef(cache, required, &result);
        if (found < 0) {
            Py_DECREF(cache);
            Py_DECREF(required);
            return NULL;
        }
        if (found == 0) {
            int status;

            result = PyObject_CallMethodObjArgs(
              OBJECT(self), str_uncached_lookupAll, required, provided, NULL);
            if (result == NULL) {
                Py_DECREF(cache);
                Py_DECREF(required);
                return NULL;
            }
            status = PyDict_SetItem(cache, required, result);
            Py_DECREF(cache);
            Py_DECREF(required);
            if (status < 0) {
                Py_DECREF(result);
                return NULL;
            }
        } else {
            /* found == 1: result already has a strong ref */
            Py_DECREF(cache);
            Py_DECREF(required);
        }
    }

    return result;
}

static PyObject*
LB_lookupAll(LB* self, PyObject* args, PyObject* kwds)
{
    static char* kwlist[] = { "required", "provided", NULL };
    PyObject *required, *provided;

    if (!PyArg_ParseTupleAndKeywords(
          args, kwds, "OO:LookupBase.lookupAll", kwlist, &required, &provided))
        return NULL;

    return _lookupAll(self, required, provided);
}

/*
    def subscriptions(self, required, provided):
        cache = self._scache.get(provided)
        if cache is None:
            cache = {}
            self._scache[provided] = cache

        required = tuple(required)
        result = cache.get(required, _not_in_mapping)
        if result is _not_in_mapping:
            result = self._uncached_subscriptions(required, provided)
            cache[required] = result

        return result
*/
static PyObject*
_subscriptions(LB* self, PyObject* required, PyObject* provided)
{
    PyObject *cache, *result;

    /* resolve before getting cache. See note in _lookup. */
    if (PyTuple_CheckExact(required)) {
        Py_INCREF(required);
    } else {
        required = PySequence_Tuple(required);
        if (required == NULL)
            return NULL;
    }

    ASSURE_DICT(self->_scache);

    cache = _subcache(self->_scache, provided);  /* strong ref */
    if (cache == NULL) {
        Py_DECREF(required);
        return NULL;
    }

    /* Use PyDict_GetItemRef() for a strong reference.  See _lookup(). */
    {
        int found = PyDict_GetItemRef(cache, required, &result);
        if (found < 0) {
            Py_DECREF(cache);
            Py_DECREF(required);
            return NULL;
        }
        if (found == 0) {
            int status;

            result = PyObject_CallMethodObjArgs(
              OBJECT(self), str_uncached_subscriptions, required, provided, NULL);
            if (result == NULL) {
                Py_DECREF(cache);
                Py_DECREF(required);
                return NULL;
            }
            status = PyDict_SetItem(cache, required, result);
            Py_DECREF(cache);
            Py_DECREF(required);
            if (status < 0) {
                Py_DECREF(result);
                return NULL;
            }
        } else {
            /* found == 1: result already has a strong ref */
            Py_DECREF(cache);
            Py_DECREF(required);
        }
    }

    return result;
}

static PyObject*
LB_subscriptions(LB* self, PyObject* args, PyObject* kwds)
{
    static char* kwlist[] = { "required", "provided", NULL };
    PyObject *required, *provided;

    if (!PyArg_ParseTupleAndKeywords(
          args, kwds, "OO", kwlist, &required, &provided))
        return NULL;

    return _subscriptions(self, required, provided);
}

static struct PyMethodDef LB_methods[] = {
    { "changed", (PyCFunction)LB_changed, METH_O, "" },
    { "lookup", (PyCFunction)LB_lookup, METH_KEYWORDS | METH_VARARGS, "" },
    { "lookup1",
      (PyCFunction)LB_lookup1,
      METH_KEYWORDS | METH_VARARGS,
      "" },
    { "queryAdapter",
      (PyCFunction)LB_queryAdapter,
      METH_KEYWORDS | METH_VARARGS,
      "" },
    { "adapter_hook",
      (PyCFunction)LB_adapter_hook,
      METH_KEYWORDS | METH_VARARGS,
      "" },
    { "lookupAll",
      (PyCFunction)LB_lookupAll,
      METH_KEYWORDS | METH_VARARGS,
      "" },
    { "subscriptions",
      (PyCFunction)LB_subscriptions,
      METH_KEYWORDS | METH_VARARGS,
      "" },
    { NULL, NULL } /* sentinel */
};

static char LB__name__[] = "_zope_interface_coptimizations.LookupBase";
static char LB__doc__[] = "Base class for adapter registries";


#if USE_STATIC_TYPES

/*
 * Static type: LookupBase
 */

static PyTypeObject LB_type_def = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name            = LB__name__,
    .tp_doc             = LB__doc__,
    .tp_basicsize       = sizeof(LB),
    .tp_flags           = BASETYPE_FLAGS,
    .tp_traverse        = (traverseproc)LB_traverse,
    .tp_clear           = (inquiry)LB_clear,
    .tp_dealloc         = (destructor)&LB_dealloc,
    .tp_methods         = LB_methods,
};

#else

/*
 * Heap type: LookupBase
 */
static PyType_Slot LB_type_slots[] = {
    {Py_tp_doc,         LB__doc__},
    {Py_tp_traverse,    LB_traverse},
    {Py_tp_clear,       LB_clear},
    {Py_tp_dealloc,     LB_dealloc},
    {Py_tp_methods,     LB_methods},
    {0,                 NULL}
};

static PyType_Spec LB_type_spec = {
    .name               = LB__name__,
    .basicsize          = sizeof(LB),
    .flags              = BASETYPE_FLAGS,
    .slots              = LB_type_slots
};

#endif

typedef struct
{
    LB          lookup;
    PyObject*   _verify_ro;
    PyObject*   _verify_generations;
} VB;

static int
VB_traverse(VB* self, visitproc visit, void* arg)
{
    Py_VISIT(self->_verify_ro);
    Py_VISIT(self->_verify_generations);
    return LB_traverse((LB*)self, visit, arg);
}

static int
VB_clear(VB* self)
{
    Py_CLEAR(self->_verify_generations);
    Py_CLEAR(self->_verify_ro);
    return LB_clear((LB*)self);
}

static void
VB_dealloc(VB* self)
{
    PyObject_GC_UnTrack((PyObject*)self);
    PyTypeObject *tp = Py_TYPE(self);
    VB_clear(self);
    tp->tp_free((PyObject*)self);
#if USE_HEAP_TYPES
    Py_DECREF(tp);
#endif
}

/*
    def changed(self, originally_changed):
        super(VerifyingBasePy, self).changed(originally_changed)
        self._verify_ro = self._registry.ro[1:]
        self._verify_generations = [r._generation for r in self._verify_ro]
*/
static PyObject*
_generations_tuple(PyObject* ro)
{
    int i, l;
    PyObject* generations;

    l = PyTuple_GET_SIZE(ro);
    generations = PyTuple_New(l);
    for (i = 0; i < l; i++) {
        PyObject* generation;

        generation = PyObject_GetAttr(PyTuple_GET_ITEM(ro, i), str_generation);
        if (generation == NULL) {
            Py_DECREF(generations);
            return NULL;
        }
        PyTuple_SET_ITEM(generations, i, generation);
    }

    return generations;
}
static PyObject*
verify_changed(VB* self, PyObject* ignored)
{
    PyObject *t, *ro;

    VB_clear(self);

    t = PyObject_GetAttr(OBJECT(self), str_registry);
    if (t == NULL)
        return NULL;

    ro = PyObject_GetAttr(t, strro);
    Py_DECREF(t);
    if (ro == NULL)
        return NULL;

    t = PyObject_CallFunctionObjArgs(OBJECT(&PyTuple_Type), ro, NULL);
    Py_DECREF(ro);
    if (t == NULL)
        return NULL;

    ro = PyTuple_GetSlice(t, 1, PyTuple_GET_SIZE(t));
    Py_DECREF(t);
    if (ro == NULL)
        return NULL;

    self->_verify_generations = _generations_tuple(ro);
    if (self->_verify_generations == NULL) {
        Py_DECREF(ro);
        return NULL;
    }

    self->_verify_ro = ro;

    Py_INCREF(Py_None);
    return Py_None;
}

/*
    def _verify(self):
        if ([r._generation for r in self._verify_ro]
            != self._verify_generations):
            self.changed(None)
*/
static int
_verify(VB* self)
{
    PyObject* changed_result;

    if (self->_verify_ro != NULL && self->_verify_generations != NULL) {
        int i, l;
        l = PyTuple_GET_SIZE(self->_verify_ro);

        /* Compare each registry's current _generation counter against the
         * snapshot stored in _verify_generations, without allocating a
         * temporary tuple.  The old code built a full tuple via
         * _generations_tuple() on every call and then compared it with
         * RichCompareBool.  This version compares in-place and exits
         * early on the first mismatch. */
        for (i = 0; i < l; i++) {
            PyObject *reg = PyTuple_GET_ITEM(self->_verify_ro, i);
            PyObject *current_gen = PyObject_GetAttr(reg, str_generation);
            if (current_gen == NULL)
                return -1;

            PyObject *stored_gen = PyTuple_GET_ITEM(
                self->_verify_generations, i);
            int eq = PyObject_RichCompareBool(current_gen, stored_gen, Py_EQ);
            Py_DECREF(current_gen);

            if (eq < 0) return -1;   /* error */
            if (eq == 0) goto changed; /* mismatch — early exit */
        }
        return 0;  /* all match, cache is still valid */
    }

changed:
    changed_result =
      PyObject_CallMethodObjArgs(OBJECT(self), strchanged, Py_None, NULL);
    if (changed_result == NULL)
        return -1;

    Py_DECREF(changed_result);
    return 0;
}

static PyObject*
VB_lookup(VB* self, PyObject* args, PyObject* kwds)
{
    static char* kwlist[] = { "required", "provided", "name", "default", NULL };
    PyObject *required, *provided, *name = NULL, *default_ = NULL;

    if (!PyArg_ParseTupleAndKeywords(
          args, kwds, "OO|OO", kwlist, &required, &provided, &name, &default_))
        return NULL;

    if (_verify(self) < 0)
        return NULL;

    return _lookup((LB*)self, required, provided, name, default_);
}

static PyObject*
VB_lookup1(VB* self, PyObject* args, PyObject* kwds)
{
    static char* kwlist[] = { "required", "provided", "name", "default", NULL };
    PyObject *required, *provided, *name = NULL, *default_ = NULL;

    if (!PyArg_ParseTupleAndKeywords(
          args, kwds, "OO|OO", kwlist, &required, &provided, &name, &default_))
        return NULL;

    if (_verify(self) < 0)
        return NULL;

    return _lookup1((LB*)self, required, provided, name, default_);
}

static PyObject*
VB_adapter_hook(VB* self, PyObject* args, PyObject* kwds)
{
    static char* kwlist[] = { "provided", "object", "name", "default", NULL };
    PyObject *object, *provided, *name = NULL, *default_ = NULL;

    if (!PyArg_ParseTupleAndKeywords(
          args, kwds, "OO|OO", kwlist, &provided, &object, &name, &default_))
        return NULL;

    if (_verify(self) < 0)
        return NULL;

    return _adapter_hook((LB*)self, provided, object, name, default_);
}

static PyObject*
VB_queryAdapter(VB* self, PyObject* args, PyObject* kwds)
{
    static char* kwlist[] = { "object", "provided", "name", "default", NULL };
    PyObject *object, *provided, *name = NULL, *default_ = NULL;

    if (!PyArg_ParseTupleAndKeywords(
          args, kwds, "OO|OO", kwlist, &object, &provided, &name, &default_))
        return NULL;

    if (_verify(self) < 0)
        return NULL;

    return _adapter_hook((LB*)self, provided, object, name, default_);
}

static PyObject*
VB_lookupAll(VB* self, PyObject* args, PyObject* kwds)
{
    static char* kwlist[] = { "required", "provided", NULL };
    PyObject *required, *provided;

    if (!PyArg_ParseTupleAndKeywords(
          args, kwds, "OO", kwlist, &required, &provided))
        return NULL;

    if (_verify(self) < 0)
        return NULL;

    return _lookupAll((LB*)self, required, provided);
}

static PyObject*
VB_subscriptions(VB* self, PyObject* args, PyObject* kwds)
{
    static char* kwlist[] = { "required", "provided", NULL };
    PyObject *required, *provided;

    if (!PyArg_ParseTupleAndKeywords(
          args, kwds, "OO", kwlist, &required, &provided))
        return NULL;

    if (_verify(self) < 0)
        return NULL;

    return _subscriptions((LB*)self, required, provided);
}

static struct PyMethodDef VB_methods[] = {
    { "changed", (PyCFunction)verify_changed, METH_O, "" },
    { "lookup",
      (PyCFunction)VB_lookup,
      METH_KEYWORDS | METH_VARARGS,
      "" },
    { "lookup1",
      (PyCFunction)VB_lookup1,
      METH_KEYWORDS | METH_VARARGS,
      "" },
    { "queryAdapter",
      (PyCFunction)VB_queryAdapter,
      METH_KEYWORDS | METH_VARARGS,
      "" },
    { "adapter_hook",
      (PyCFunction)VB_adapter_hook,
      METH_KEYWORDS | METH_VARARGS,
      "" },
    { "lookupAll",
      (PyCFunction)VB_lookupAll,
      METH_KEYWORDS | METH_VARARGS,
      "" },
    { "subscriptions",
      (PyCFunction)VB_subscriptions,
      METH_KEYWORDS | METH_VARARGS,
      "" },
    { NULL, NULL } /* sentinel */
};

static char VB__name__[] = "_zope_interface_coptimizations.VerifyingBase";
static char VB__doc__[] = "Base class for verifying adapter registries.";

#if USE_STATIC_TYPES

/*
 * Static type: VerifyingBase
 */

static PyTypeObject VB_type_def = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name            = VB__name__,
    .tp_doc             = VB__doc__,
    .tp_base            = &LB_type_def,
    .tp_basicsize       = sizeof(VB),
    .tp_flags           = BASETYPE_FLAGS,
    .tp_traverse        = (traverseproc)VB_traverse,
    .tp_clear           = (inquiry)VB_clear,
    .tp_dealloc         = (destructor)&VB_dealloc,
    .tp_methods         = VB_methods,
};


#else

/*
 * Heap type: VerifyingBase
 */
static PyType_Slot VB_type_slots[] = {
    {Py_tp_doc,         VB__doc__},
    {Py_tp_traverse,    VB_traverse},
    {Py_tp_clear,       VB_clear},
    {Py_tp_dealloc,     VB_dealloc},
    {Py_tp_methods,     VB_methods},
    /* tp_base cannot be set as a slot -- pass to PyType_FromModuleAndSpec */
    {0,                 NULL}
};

static PyType_Spec VB_type_spec = {
    .name               = VB__name__,
    .basicsize          = sizeof(VB),
    .flags              = BASETYPE_FLAGS,
    .slots              = VB_type_slots
};

#endif


/*
 * Module state struct:  holds all data formerly kept as static globals.
 */
typedef struct
{
    /* our globals (exposed to Python) */
    PyTypeObject*   specification_base_class;
    PyTypeObject*   object_specification_descriptor_class;
    PyTypeObject*   class_provides_base_class;
    PyTypeObject*   interface_base_class;
    PyTypeObject*   lookup_base_class;
    PyTypeObject*   verifying_base_class;
    PyObject*       adapter_hooks;
    /* members imported from 'zope.interface.declarations'
     */
    PyObject*       empty;
    PyObject*       fallback;
    PyObject*       builtin_impl_specs;
    PyTypeObject*   implements_class;
    /* flag:  have we imported the next set of members yet from
     * 'zope.interface.declarations?
     */
    int             decl_imported;
} _zic_module_state;

/*
 *  Macro to speed lookup of state members
 */
#define _zic_state(o) ((_zic_module_state*)PyModule_GetState(o))

static _zic_module_state*
_zic_state_init(PyObject* module)
{
    _zic_module_state* rec = _zic_state(module);

    rec->specification_base_class = NULL;
    rec->object_specification_descriptor_class = NULL;
    rec->class_provides_base_class = NULL;
    rec->interface_base_class = NULL;
    rec->lookup_base_class = NULL;
    rec->verifying_base_class = NULL;
    rec->adapter_hooks = NULL;

    rec->builtin_impl_specs = NULL;
    rec->empty = NULL;
    rec->fallback = NULL;
    rec->implements_class = NULL;
    rec->decl_imported = 0;

    return rec;
}

static int
_zic_state_traverse(PyObject* module, visitproc visit, void* arg)
{
    _zic_module_state* rec = _zic_state(module);

    Py_VISIT(rec->specification_base_class);
    Py_VISIT(rec->object_specification_descriptor_class);
    Py_VISIT(rec->class_provides_base_class);
    Py_VISIT(rec->interface_base_class);
    Py_VISIT(rec->lookup_base_class);
    Py_VISIT(rec->verifying_base_class);
    Py_VISIT(rec->adapter_hooks);

    Py_VISIT(rec->builtin_impl_specs);
    Py_VISIT(rec->empty);
    Py_VISIT(rec->fallback);
    Py_VISIT(rec->implements_class);

    return 0;
}

static int
_zic_state_clear(PyObject* module)
{
    _zic_module_state* rec = _zic_state(module);

    Py_CLEAR(rec->specification_base_class);
    Py_CLEAR(rec->object_specification_descriptor_class);
    Py_CLEAR(rec->class_provides_base_class);
    Py_CLEAR(rec->interface_base_class);
    Py_CLEAR(rec->lookup_base_class);
    Py_CLEAR(rec->verifying_base_class);
    Py_CLEAR(rec->adapter_hooks);

    Py_CLEAR(rec->builtin_impl_specs);
    Py_CLEAR(rec->empty);
    Py_CLEAR(rec->fallback);
    Py_CLEAR(rec->implements_class);

    return 0;
}

#if USE_HEAP_TYPES
/* Import zope.interface.declarations and store results in module state.
 *
 * Dynamic alternative to 'import_declarations' above.
 */
static _zic_module_state*
_zic_state_load_declarations(PyObject* module)
{
    PyObject* declarations;
    PyObject* builtin_impl_specs;
    PyObject* empty;
    PyObject* fallback;
    PyObject* implements;

    _zic_module_state* rec = _zic_state(module);

    if (!rec->decl_imported) {
        declarations = PyImport_ImportModule("zope.interface.declarations");
        if (declarations == NULL) {
            return NULL;
        }

        builtin_impl_specs = PyObject_GetAttrString(
          declarations, "BuiltinImplementationSpecifications");
        if (builtin_impl_specs == NULL) {
            return NULL;
        }

        empty = PyObject_GetAttrString(declarations, "_empty");
        if (empty == NULL) {
            return NULL;
        }

        fallback =
          PyObject_GetAttrString(declarations, "implementedByFallback");
        if (fallback == NULL) {
            return NULL;
        }

        implements = PyObject_GetAttrString(declarations, "Implements");
        if (implements == NULL) {
            return NULL;
        }

        if (!PyType_Check(implements)) {
            PyErr_SetString(
              PyExc_TypeError,
              "zope.interface.declarations.Implements is not a type");
            return NULL;
        }

        Py_DECREF(declarations);

        rec->builtin_impl_specs = builtin_impl_specs;
        rec->empty = empty;
        rec->fallback = fallback;
        rec->implements_class = (PyTypeObject*)implements;
        rec->decl_imported = 1;
    }
    return rec;
}

#endif

/*
 *  Provide access to the current module given the type.
 */

static struct PyModuleDef _zic_module_def;

static PyObject*
_get_module(PyTypeObject *typeobj)
{
#if USE_STATIC_TYPES
    return (PyObject*)&_zic_module_def;
#else
    if (PyType_Check(typeobj)) {
        /* Only added in Python 3.11 */
        return PyType_GetModuleByDef(typeobj, &_zic_module_def);
    }

    PyErr_SetString(PyExc_TypeError, "_get_module: called w/ non-type");
    return NULL;
#endif
}

/*
 * Fetch the adapter hooks for the current type's module.
 */
static PyObject*
_get_adapter_hooks(PyTypeObject *typeobj)
{
#if USE_STATIC_TYPES
    return adapter_hooks;
#else
    PyObject* module;
    _zic_module_state* rec;

    module = _get_module(typeobj);
    if (module == NULL) { return NULL; }

    rec = _zic_state(module);
    return rec->adapter_hooks;
#endif
}

/*
 * Fetch the 'SpecificationBase' class for the current type's module.
 */
static PyTypeObject*
_get_specification_base_class(PyTypeObject *typeobj)
{
#if USE_STATIC_TYPES
    return &SB_type_def;
#else
    PyObject* module;
    _zic_module_state* rec;

    module = _get_module(typeobj);
    if (module == NULL) { return NULL; }

    rec = _zic_state(module);
    return rec->specification_base_class;
#endif
}

/*
 * Fetch the 'InterfaceBase' class for the current type's module.
 */
static PyTypeObject*
_get_interface_base_class(PyTypeObject *typeobj)
{
#if USE_STATIC_TYPES
    return &IB_type_def;
#else
    PyObject* module;
    _zic_module_state* rec;

    module = _get_module(typeobj);
    if (module == NULL) { return NULL; }

    rec = _zic_state(module);
    return rec->interface_base_class;
#endif
}

static PyObject*
implementedByFallback(PyObject* module, PyObject* cls)
{
#if USE_STATIC_TYPES
    if (imported_declarations == 0 && import_declarations() < 0) {
        return NULL;
    }
    /* now use static 'fallback' */
#else
    PyObject* fallback;

    _zic_module_state* rec = _zic_state_load_declarations(module);
    if (rec == NULL) { return NULL; }

    fallback = rec->fallback;
#endif

    return PyObject_CallFunctionObjArgs(fallback, cls, NULL);
}

static char implementedBy___doc__[] =
  ("Interfaces implemented by a class or factory.\n"
   "Raises TypeError if argument is neither a class nor a callable.");

static PyObject*
implementedBy(PyObject* module, PyObject* cls)
{
    /* Fast retrieval of implements spec, if possible, to optimize
       common case.  Use fallback code if we get stuck.
    */
    PyObject *dict = NULL;
    PyObject *spec;
    PyTypeObject *implements_class;
    PyObject *builtin_impl_specs;

#if USE_STATIC_TYPES
    if (imported_declarations == 0 && import_declarations() < 0) {
        return NULL;
    }

    implements_class = Implements;
    builtin_impl_specs = BuiltinImplementationSpecifications;
#else
    _zic_module_state* rec = _zic_state_load_declarations(module);
    if (rec == NULL) { return NULL; }

    implements_class = rec->implements_class;
    builtin_impl_specs = rec->builtin_impl_specs;
#endif

    if (PyObject_TypeCheck(cls, &PySuper_Type)) {
        // Let merging be handled by Python.
        return implementedByFallback(module, cls);
    }

    if (PyType_Check(cls)) {
        dict = TYPE(cls)->tp_dict;
        Py_XINCREF(dict);
    }

    if (dict == NULL)
        dict = PyObject_GetAttr(cls, str__dict__);

    if (dict == NULL) {
        /* Probably a security proxied class, use more expensive fallback code.
         * Only swallow AttributeError — propagate everything else. */
        if (!PyErr_ExceptionMatches(PyExc_AttributeError))
            return NULL;
        PyErr_Clear();
        return implementedByFallback(module, cls);
    }

    spec = PyObject_GetItem(dict, str__implemented__);
    Py_DECREF(dict);
    if (spec) {

        if (PyObject_TypeCheck(spec, implements_class))
            return spec;

        /* Old-style declaration, use more expensive fallback code */
        Py_DECREF(spec);
        return implementedByFallback(module, cls);
    }

    /* PyObject_GetItem raises KeyError when key is not found.
     * Only swallow KeyError — propagate everything else. */
    if (!PyErr_ExceptionMatches(PyExc_KeyError))
        return NULL;
    PyErr_Clear();

    /* Maybe we have a builtin */
    {
        int found = PyDict_GetItemRef(builtin_impl_specs, cls, &spec);
        if (found < 0)
            return NULL;
        if (found == 1)
            return spec;  /* strong ref from PyDict_GetItemRef */
    }

    /* We're stuck, use fallback */
    return implementedByFallback(module, cls);
}

static char getObjectSpecification___doc__[] =
  ("Get an object's interfaces (internal api)");

static PyObject*
getObjectSpecification(PyObject* module, PyObject* ob)
{
    PyObject *cls;
    PyObject *result;
    PyTypeObject *specification_base_class;
    PyObject *empty_;

#if USE_STATIC_TYPES
    specification_base_class = &SB_type_def;

    if (imported_declarations == 0 && import_declarations() < 0) {
        return NULL;
    }
    empty_ = empty;  /* global from import */

#else
    _zic_module_state* rec = _zic_state_load_declarations(module);
    if (rec == NULL) { return NULL; }

    specification_base_class = rec->specification_base_class;
    empty_ = rec->empty;
#endif

    result = PyObject_GetAttr(ob, str__provides__);
    if (!result) {
        if (!PyErr_ExceptionMatches(PyExc_AttributeError)) {
            /* Propagate non AttributeError exceptions. */
            return NULL;
        }
        PyErr_Clear();
    } else {
        int is_instance = -1;
        is_instance =
          PyObject_IsInstance(result, OBJECT(specification_base_class));
        if (is_instance < 0) {
            /* Propagate all errors */
            return NULL;
        }
        if (is_instance) {
            return result;
        }
    }

    /* We do a getattr here so as not to be defeated by proxies */
    cls = PyObject_GetAttr(ob, str__class__);
    if (cls == NULL) {
        if (!PyErr_ExceptionMatches(PyExc_AttributeError)) {
            /* Propagate non-AttributeErrors */
            return NULL;
        }
        PyErr_Clear();

        Py_INCREF(empty_);
        return empty_;
    }
    result = implementedBy(module, cls);
    Py_DECREF(cls);

    return result;
}

static char providedBy___doc__[] = ("Get an object's interfaces");

static PyObject*
providedBy(PyObject* module, PyObject* ob)
{
    PyObject *result = NULL;
    PyObject *cls;
    PyObject *cp;
    PyTypeObject *specification_base_class;
    int is_instance = -1;

    is_instance = PyObject_IsInstance(ob, (PyObject*)&PySuper_Type);
    if (is_instance < 0) {
        if (!PyErr_ExceptionMatches(PyExc_AttributeError)) {
            /* Propagate non-AttributeErrors */
            return NULL;
        }
        PyErr_Clear();
    }
    if (is_instance) {
        return implementedBy(module, ob);
    }

    result = PyObject_GetAttr(ob, str__providedBy__);

    if (result == NULL) {
        if (!PyErr_ExceptionMatches(PyExc_AttributeError)) {
            return NULL;
        }

        PyErr_Clear();
        return getObjectSpecification(module, ob);
    }

    /* We want to make sure we have a spec. We can't do a type check
       because we may have a proxy, so we'll just try to get the
       only attribute.
    */
#if USE_STATIC_TYPES
    specification_base_class = &SB_type_def;
#else
    _zic_module_state* rec = _zic_state(module);
    specification_base_class = rec->specification_base_class;
#endif
    if (PyObject_TypeCheck(result, specification_base_class) ||
        PyObject_HasAttrString(result, "extends"))
        return result;

    /*
      The object's class doesn't understand descriptors.
      Sigh. We need to get an object descriptor, but we have to be
      careful.  We want to use the instance's __provides__,l if
      there is one, but only if it didn't come from the class.
    */
    Py_DECREF(result);

    cls = PyObject_GetAttr(ob, str__class__);
    if (cls == NULL)
        return NULL;

    result = PyObject_GetAttr(ob, str__provides__);
    if (result == NULL) {
        /* No __provides__, so just fall back to implementedBy.
         * Only swallow AttributeError — propagate everything else. */
        if (!PyErr_ExceptionMatches(PyExc_AttributeError)) {
            Py_DECREF(cls);
            return NULL;
        }
        PyErr_Clear();
        result = implementedBy(module, cls);
        Py_DECREF(cls);
        return result;
    }

    cp = PyObject_GetAttr(cls, str__provides__);
    if (cp == NULL) {
        /* The the class has no provides, assume we're done.
         * Only swallow AttributeError — propagate everything else. */
        if (!PyErr_ExceptionMatches(PyExc_AttributeError)) {
            Py_DECREF(cls);
            Py_DECREF(result);
            return NULL;
        }
        PyErr_Clear();
        Py_DECREF(cls);
        return result;
    }

    if (cp == result) {
        /*
          Oops, we got the provides from the class. This means
          the object doesn't have it's own. We should use implementedBy
        */
        Py_DECREF(result);
        result = implementedBy(module, cls);
    }

    Py_DECREF(cls);
    Py_DECREF(cp);

    return result;
}

static struct PyMethodDef _zic_module_methods[] = {
    { "implementedBy",
      (PyCFunction)implementedBy,
      METH_O,
      implementedBy___doc__ },
    { "getObjectSpecification",
      (PyCFunction)getObjectSpecification,
      METH_O,
      getObjectSpecification___doc__ },
    { "providedBy", (PyCFunction)providedBy, METH_O, providedBy___doc__ },

    { NULL, (PyCFunction)NULL, 0, NULL } /* sentinel */
};


/* Handler for the 'execute' phase of multi-phase initialization
 *
 * See: https://docs.python.org/3/c-api/module.html#multi-phase-initialization
 * and: https://peps.python.org/pep-0489/#module-execution-phase
 */
static int
_zic_module_exec(PyObject* module)
{
    _zic_module_state* rec = _zic_state_init(module);

    rec->adapter_hooks = PyList_New(0);
    if (rec->adapter_hooks == NULL)
        return -1;
    Py_INCREF(rec->adapter_hooks);

#if USE_STATIC_TYPES

    /* Initialize static global */
    adapter_hooks = rec->adapter_hooks;

    /* Initialize types: */
    SB_type_def.tp_new = PyBaseObject_Type.tp_new;
    if (PyType_Ready(&SB_type_def) < 0) { return -1; }
    Py_INCREF(&SB_type_def);
    rec->specification_base_class = &SB_type_def;

    OSD_type_def.tp_new = PyBaseObject_Type.tp_new;
    if (PyType_Ready(&OSD_type_def) < 0) { return -1; }
    Py_INCREF(&OSD_type_def);
    rec->object_specification_descriptor_class = &OSD_type_def;

    CPB_type_def.tp_new = PyBaseObject_Type.tp_new;
    if (PyType_Ready(&CPB_type_def) < 0) { return -1; }
    Py_INCREF(&CPB_type_def);
    rec->class_provides_base_class = &CPB_type_def;

    IB_type_def.tp_new = PyBaseObject_Type.tp_new;
    if (PyType_Ready(&IB_type_def) < 0) { return -1; }
    Py_INCREF(&IB_type_def);
    rec->interface_base_class = &IB_type_def;

    LB_type_def.tp_new = PyBaseObject_Type.tp_new;
    if (PyType_Ready(&LB_type_def) < 0) { return -1; }
    Py_INCREF(&LB_type_def);
    rec->lookup_base_class = &LB_type_def;

    VB_type_def.tp_new = PyBaseObject_Type.tp_new;
    if (PyType_Ready(&VB_type_def) < 0) { return -1; }
    Py_INCREF(&VB_type_def);
    rec->verifying_base_class = &VB_type_def;

#else

    PyObject *sb_class;
    PyObject *osd_class;
    PyObject *cpb_class;
    PyObject *ib_class;
    PyObject *lb_class;
    PyObject *vb_class;

    /* Initialize types:
     */
    sb_class = PyType_FromModuleAndSpec(module, &SB_type_spec, NULL);
    if (sb_class == NULL) { return -1; }
    Py_INCREF(sb_class);
    rec->specification_base_class = TYPE(sb_class);

    osd_class = PyType_FromModuleAndSpec(module, &OSD_type_spec, NULL);
    if (osd_class == NULL) { return -1; }
    Py_INCREF(osd_class);
    rec->object_specification_descriptor_class = TYPE(osd_class);

    cpb_class = PyType_FromModuleAndSpec(module, &CPB_type_spec, sb_class);
    if (cpb_class == NULL) { return -1; }
    Py_INCREF(cpb_class);
    rec->class_provides_base_class = TYPE(cpb_class);

    ib_class = PyType_FromModuleAndSpec(module, &IB_type_spec, sb_class);
    if (ib_class == NULL) { return -1; }
    Py_INCREF(ib_class);
    rec->interface_base_class = TYPE(ib_class);

    lb_class = PyType_FromModuleAndSpec(module, &LB_type_spec, NULL);
    if (lb_class == NULL) { return -1; }
    Py_INCREF(lb_class);
    rec->lookup_base_class = TYPE(lb_class);

    vb_class = PyType_FromModuleAndSpec(module, &VB_type_spec, lb_class);
    if (vb_class == NULL) { return -1; }
    Py_INCREF(vb_class);
    rec->verifying_base_class = TYPE(vb_class);

#endif

    /* Add types to our dict FBO python;  also the adapter hooks */
    if (PyModule_AddObject(module,
        "SpecificationBase", OBJECT(rec->specification_base_class)) < 0)
        return -1;

    if (PyModule_AddObject(module,
        "ObjectSpecificationDescriptor",
        OBJECT(rec->object_specification_descriptor_class)) <
        0)
        return -1;

    if (PyModule_AddObject(module,
        "ClassProvidesBase", OBJECT(rec->class_provides_base_class)) < 0)
        return -1;

    if (PyModule_AddObject(module,
        "InterfaceBase", OBJECT(rec->interface_base_class)) < 0)
        return -1;

    if (PyModule_AddObject(module,
        "LookupBase", OBJECT(rec->lookup_base_class)) < 0)
        return -1;

    if (PyModule_AddObject(module,
        "VerifyingBase", OBJECT(rec->verifying_base_class)) < 0)
        return -1;

    if (PyModule_AddObject(module, "adapter_hooks", rec->adapter_hooks) < 0)
        return -1;

    return 0;
}


/* Slot definitions for multi-phase initialization
 *
 * See: https://docs.python.org/3/c-api/module.html#multi-phase-initialization
 * and: https://peps.python.org/pep-0489
 */
static PyModuleDef_Slot _zic_module_slots[] = {
    {Py_mod_exec,       _zic_module_exec},
#if PY_VERSION_HEX >= 0x030d0000
    /* Declare that this module supports running without the GIL
     * (free-threaded Python, PEP 703).  Without this slot, Python
     * re-enables the GIL for the entire process when this module
     * is imported on a free-threaded build. */
    {Py_mod_gil,        Py_MOD_GIL_NOT_USED},
#endif
    {0,                 NULL}
};

static char _zic_module__doc__[] = "C optimizations for zope.interface\n\n";

static struct PyModuleDef _zic_module_def = {
    PyModuleDef_HEAD_INIT,
    .m_name = "_zope_interface_coptimizations",
    .m_doc = _zic_module__doc__,
    .m_size = sizeof(_zic_module_state),
    .m_methods = _zic_module_methods,
    .m_slots=_zic_module_slots,
    .m_traverse = _zic_state_traverse,
    .m_clear = _zic_state_clear,
};

static PyObject*
init(void)
{
    if (define_static_strings() < 0) { return NULL; }

    return PyModuleDef_Init(&_zic_module_def);
}

PyMODINIT_FUNC
PyInit__zope_interface_coptimizations(void)
{
    return init();
}

#ifdef __clang__
#pragma clang diagnostic pop
#endif
