// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc.  All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd

// Mappings and Sequences of descriptors.
// Used by Descriptor.fields_by_name, EnumDescriptor.values...
//
// They avoid the allocation of a full dictionary or a full list: they simply
// store a pointer to the parent descriptor, use the C++ Descriptor methods (see
// net/proto2/public/descriptor.h) to retrieve other descriptors, and create
// Python objects on the fly.
//
// The containers fully conform to abc.Mapping and abc.Sequence, and behave just
// like read-only dictionaries and lists.
//
// Because the interface of C++ Descriptors is quite regular, this file actually
// defines only three types, the exact behavior of a container is controlled by
// a DescriptorContainerDef structure, which contains functions that uses the
// public Descriptor API.
//
// Note: This DescriptorContainerDef is similar to the "virtual methods table"
// that a C++ compiler generates for a class. We have to make it explicit
// because the Python API is based on C, and does not play well with C++
// inheritance.

// clang-format off
#define PY_SSIZE_T_CLEAN
// This inclusion must appear before all the others.
#include <Python.h>

#include <string>

#include "google/protobuf/pyext/descriptor_containers.h"
// clang-format on

#include "google/protobuf/descriptor.h"
#include "google/protobuf/pyext/descriptor.h"
#include "google/protobuf/pyext/descriptor_pool.h"
#include "google/protobuf/pyext/scoped_pyobject_ptr.h"
#include "absl/strings/string_view.h"

#define PyString_AsStringAndSize(ob, charpp, sizep)              \
  (PyUnicode_Check(ob)                                           \
       ? ((*(charpp) = const_cast<char*>(                        \
               PyUnicode_AsUTF8AndSize(ob, (sizep)))) == nullptr \
              ? -1                                               \
              : 0)                                               \
       : PyBytes_AsStringAndSize(ob, (charpp), (sizep)))

namespace google {
namespace protobuf {
namespace python {

struct PyContainer;

typedef int (*CountMethod)(PyContainer* self);
typedef const void* (*GetByIndexMethod)(PyContainer* self, int index);
typedef const void* (*GetByNameMethod)(PyContainer* self,
                                       absl::string_view name);
typedef const void* (*GetByCamelcaseNameMethod)(PyContainer* self,
                                                absl::string_view name);
typedef const void* (*GetByNumberMethod)(PyContainer* self, int index);
typedef PyObject* (*NewObjectFromItemMethod)(const void* descriptor);
typedef absl::string_view (*GetItemNameMethod)(const void* descriptor);
typedef absl::string_view (*GetItemCamelcaseNameMethod)(const void* descriptor);
typedef int (*GetItemNumberMethod)(const void* descriptor);
typedef int (*GetItemIndexMethod)(const void* descriptor);

struct DescriptorContainerDef {
  const char* mapping_name;
  // Returns the number of items in the container.
  CountMethod count_fn;
  // Retrieve item by index (usually the order of declaration in the proto file)
  // Used by sequences, but also iterators. 0 <= index < Count().
  GetByIndexMethod get_by_index_fn;
  // Retrieve item by name (usually a call to some 'FindByName' method).
  // Used by "by_name" mappings.
  GetByNameMethod get_by_name_fn;
  // Retrieve item by camelcase name (usually a call to some
  // 'FindByCamelcaseName' method). Used by "by_camelcase_name" mappings.
  GetByCamelcaseNameMethod get_by_camelcase_name_fn;
  // Retrieve item by declared number (field tag, or enum value).
  // Used by "by_number" mappings.
  GetByNumberMethod get_by_number_fn;
  // Converts a item C++ descriptor to a Python object. Returns a new reference.
  NewObjectFromItemMethod new_object_from_item_fn;
  // Retrieve the name of an item. Used by iterators on "by_name" mappings.
  GetItemNameMethod get_item_name_fn;
  // Retrieve the camelcase name of an item. Used by iterators on
  // "by_camelcase_name" mappings.
  GetItemCamelcaseNameMethod get_item_camelcase_name_fn;
  // Retrieve the number of an item. Used by iterators on "by_number" mappings.
  GetItemNumberMethod get_item_number_fn;
  // Retrieve the index of an item for the container type.
  // Used by "__contains__".
  // If not set, "x in sequence" will do a linear search.
  GetItemIndexMethod get_item_index_fn;
};

struct PyContainer {
  PyObject_HEAD

  // The proto2 descriptor this container belongs to the global DescriptorPool.
  const void* descriptor;

  // A pointer to a static structure with function pointers that control the
  // behavior of the container. Very similar to the table of virtual functions
  // of a C++ class.
  const DescriptorContainerDef* container_def;

  // The kind of container: list, or dict by name or value.
  enum ContainerKind {
    KIND_SEQUENCE,
    KIND_BYNAME,
    KIND_BYCAMELCASENAME,
    KIND_BYNUMBER,
  } kind;
};

struct PyContainerIterator {
  PyObject_HEAD

  // The container we are iterating over. Own a reference.
  PyContainer* container;

  // The current index in the iterator.
  int index;

  // The kind of container: list, or dict by name or value.
  enum IterKind {
    KIND_ITERKEY,
    KIND_ITERVALUE,
    KIND_ITERITEM,
    KIND_ITERVALUE_REVERSED,  // For sequences
  } kind;
};

namespace descriptor {

// Returns the C++ item descriptor for a given Python key.
// When the descriptor is found, return true and set *item.
// When the descriptor is not found, return true, but set *item to null.
// On error, returns false with an exception set.
static bool _GetItemByKey(PyContainer* self, PyObject* key, const void** item) {
  switch (self->kind) {
    case PyContainer::KIND_BYNAME: {
      char* name;
      Py_ssize_t name_size;
      if (PyString_AsStringAndSize(key, &name, &name_size) < 0) {
        if (PyErr_ExceptionMatches(PyExc_TypeError)) {
          // Not a string, cannot be in the container.
          PyErr_Clear();
          *item = nullptr;
          return true;
        }
        return false;
      }
      *item = self->container_def->get_by_name_fn(
          self, absl::string_view(name, name_size));
      return true;
    }
    case PyContainer::KIND_BYCAMELCASENAME: {
      char* camelcase_name;
      Py_ssize_t name_size;
      if (PyString_AsStringAndSize(key, &camelcase_name, &name_size) < 0) {
        if (PyErr_ExceptionMatches(PyExc_TypeError)) {
          // Not a string, cannot be in the container.
          PyErr_Clear();
          *item = nullptr;
          return true;
        }
        return false;
      }
      *item = self->container_def->get_by_camelcase_name_fn(
          self, absl::string_view(camelcase_name, name_size));
      return true;
    }
    case PyContainer::KIND_BYNUMBER: {
      Py_ssize_t number = PyNumber_AsSsize_t(key, nullptr);
      if (number == -1 && PyErr_Occurred()) {
        if (PyErr_ExceptionMatches(PyExc_TypeError)) {
          // Not a number, cannot be in the container.
          PyErr_Clear();
          *item = nullptr;
          return true;
        }
        return false;
      }
      *item = self->container_def->get_by_number_fn(self, number);
      return true;
    }
    default:
      PyErr_SetNone(PyExc_NotImplementedError);
      return false;
  }
}

// Returns the key of the object at the given index.
// Used when iterating over mappings.
static PyObject* _NewKey_ByIndex(PyContainer* self, Py_ssize_t index) {
  const void* item = self->container_def->get_by_index_fn(self, index);
  switch (self->kind) {
    case PyContainer::KIND_BYNAME: {
      absl::string_view name = self->container_def->get_item_name_fn(item);
      return PyUnicode_FromStringAndSize(name.data(), name.size());
    }
    case PyContainer::KIND_BYCAMELCASENAME: {
      absl::string_view name =
          self->container_def->get_item_camelcase_name_fn(item);
      return PyUnicode_FromStringAndSize(name.data(), name.size());
    }
    case PyContainer::KIND_BYNUMBER: {
      int value = self->container_def->get_item_number_fn(item);
      return PyLong_FromLong(value);
    }
    default:
      PyErr_SetNone(PyExc_NotImplementedError);
      return nullptr;
  }
}

// Returns the object at the given index.
// Also used when iterating over mappings.
static PyObject* _NewObj_ByIndex(PyContainer* self, Py_ssize_t index) {
  return self->container_def->new_object_from_item_fn(
      self->container_def->get_by_index_fn(self, index));
}

static Py_ssize_t Length(PyContainer* self) {
  return self->container_def->count_fn(self);
}

// The DescriptorMapping type.

static PyObject* Subscript(PyContainer* self, PyObject* key) {
  const void* item = nullptr;
  if (!_GetItemByKey(self, key, &item)) {
    return nullptr;
  }
  if (!item) {
    PyErr_SetObject(PyExc_KeyError, key);
    return nullptr;
  }
  return self->container_def->new_object_from_item_fn(item);
}

static int AssSubscript(PyContainer* self, PyObject* key, PyObject* value) {
  if (_CalledFromGeneratedFile(0)) {
    return 0;
  }
  PyErr_Format(PyExc_TypeError,
               "'%.200s' object does not support item assignment",
               Py_TYPE(self)->tp_name);
  return -1;
}

static PyMappingMethods MappingMappingMethods = {
  (lenfunc)Length,               // mp_length
  (binaryfunc)Subscript,         // mp_subscript
  (objobjargproc)AssSubscript,   // mp_ass_subscript
};

static int Contains(PyContainer* self, PyObject* key) {
  const void* item = nullptr;
  if (!_GetItemByKey(self, key, &item)) {
    return -1;
  }
  if (item) {
    return 1;
  } else {
    return 0;
  }
}

static PyObject* ContainerRepr(PyContainer* self) {
  const char* kind = "";
  switch (self->kind) {
    case PyContainer::KIND_SEQUENCE:
      kind = "sequence";
      break;
    case PyContainer::KIND_BYNAME:
      kind = "mapping by name";
      break;
    case PyContainer::KIND_BYCAMELCASENAME:
      kind = "mapping by camelCase name";
      break;
    case PyContainer::KIND_BYNUMBER:
      kind = "mapping by number";
      break;
  }
  return PyUnicode_FromFormat("<%s %s>", self->container_def->mapping_name,
                              kind);
}

extern PyTypeObject DescriptorMapping_Type;
extern PyTypeObject DescriptorSequence_Type;

// A sequence container can only be equal to another sequence container, or (for
// backward compatibility) to a list containing the same items.
// Returns 1 if equal, 0 if unequal, -1 on error.
static int DescriptorSequence_Equal(PyContainer* self, PyObject* other) {
  // Check the identity of C++ pointers.
  if (PyObject_TypeCheck(other, &DescriptorSequence_Type)) {
    PyContainer* other_container = reinterpret_cast<PyContainer*>(other);
    if (self->descriptor == other_container->descriptor &&
        self->container_def == other_container->container_def &&
        self->kind == other_container->kind) {
      return 1;
    } else {
      return 0;
    }
  }

  // If other is a list
  if (PyList_Check(other)) {
    // return list(self) == other
    int size = Length(self);
    if (size != PyList_Size(other)) {
      return false;
    }
    for (int index = 0; index < size; index++) {
      ScopedPyObjectPtr value1(_NewObj_ByIndex(self, index));
      if (value1 == nullptr) {
        return -1;
      }
      PyObject* value2 = PyList_GetItem(other, index);
      if (value2 == nullptr) {
        return -1;
      }
      int cmp = PyObject_RichCompareBool(value1.get(), value2, Py_EQ);
      if (cmp != 1)  // error or not equal
          return cmp;
    }
    // All items were found and equal
    return 1;
  }

  // Any other object is different.
  return 0;
}

// A mapping container can only be equal to another mapping container, or (for
// backward compatibility) to a dict containing the same items.
// Returns 1 if equal, 0 if unequal, -1 on error.
static int DescriptorMapping_Equal(PyContainer* self, PyObject* other) {
  // Check the identity of C++ pointers.
  if (PyObject_TypeCheck(other, &DescriptorMapping_Type)) {
    PyContainer* other_container = reinterpret_cast<PyContainer*>(other);
    if (self->descriptor == other_container->descriptor &&
        self->container_def == other_container->container_def &&
        self->kind == other_container->kind) {
      return 1;
    } else {
      return 0;
    }
  }

  // If other is a dict
  if (PyDict_Check(other)) {
    // equivalent to dict(self.items()) == other
    int size = Length(self);
    if (size != PyDict_Size(other)) {
      return false;
    }
    for (int index = 0; index < size; index++) {
      ScopedPyObjectPtr key(_NewKey_ByIndex(self, index));
      if (key == nullptr) {
        return -1;
      }
      ScopedPyObjectPtr value1(_NewObj_ByIndex(self, index));
      if (value1 == nullptr) {
        return -1;
      }
      PyObject* value2 = PyDict_GetItem(other, key.get());
      if (value2 == nullptr) {
        // Not found in the other dictionary
        return 0;
      }
      int cmp = PyObject_RichCompareBool(value1.get(), value2, Py_EQ);
      if (cmp != 1)  // error or not equal
          return cmp;
    }
    // All items were found and equal
    return 1;
  }

  // Any other object is different.
  return 0;
}

static PyObject* RichCompare(PyContainer* self, PyObject* other, int opid) {
  if (opid != Py_EQ && opid != Py_NE) {
    Py_INCREF(Py_NotImplemented);
    return Py_NotImplemented;
  }

  int result;

  if (self->kind == PyContainer::KIND_SEQUENCE) {
    result = DescriptorSequence_Equal(self, other);
  } else {
    result = DescriptorMapping_Equal(self, other);
  }
  if (result < 0) {
    return nullptr;
  }
  if (result ^ (opid == Py_NE)) {
    Py_RETURN_TRUE;
  } else {
    Py_RETURN_FALSE;
  }
}

static PySequenceMethods MappingSequenceMethods = {
    nullptr,               // sq_length
    nullptr,               // sq_concat
    nullptr,               // sq_repeat
    nullptr,               // sq_item
    nullptr,               // sq_slice
    nullptr,               // sq_ass_item
    nullptr,               // sq_ass_slice
    (objobjproc)Contains,  // sq_contains
};

static PyObject* Get(PyContainer* self, PyObject* args) {
  PyObject* key;
  PyObject* default_value = Py_None;
  if (!PyArg_UnpackTuple(args, "get", 1, 2, &key, &default_value)) {
    return nullptr;
  }

  const void* item;
  if (!_GetItemByKey(self, key, &item)) {
    return nullptr;
  }
  if (item == nullptr) {
    Py_INCREF(default_value);
    return default_value;
  }
  return self->container_def->new_object_from_item_fn(item);
}

static PyObject* Keys(PyContainer* self, PyObject* args) {
  Py_ssize_t count = Length(self);
  ScopedPyObjectPtr list(PyList_New(count));
  if (list == nullptr) {
    return nullptr;
  }
  for (Py_ssize_t index = 0; index < count; ++index) {
    PyObject* key = _NewKey_ByIndex(self, index);
    if (key == nullptr) {
      return nullptr;
    }
    PyList_SET_ITEM(list.get(), index, key);
  }
  return list.release();
}

static PyObject* Values(PyContainer* self, PyObject* args) {
  Py_ssize_t count = Length(self);
  ScopedPyObjectPtr list(PyList_New(count));
  if (list == nullptr) {
    return nullptr;
  }
  for (Py_ssize_t index = 0; index < count; ++index) {
    PyObject* value = _NewObj_ByIndex(self, index);
    if (value == nullptr) {
      return nullptr;
    }
    PyList_SET_ITEM(list.get(), index, value);
  }
  return list.release();
}

static PyObject* Items(PyContainer* self, PyObject* args) {
  Py_ssize_t count = Length(self);
  ScopedPyObjectPtr list(PyList_New(count));
  if (list == nullptr) {
    return nullptr;
  }
  for (Py_ssize_t index = 0; index < count; ++index) {
    ScopedPyObjectPtr obj(PyTuple_New(2));
    if (obj == nullptr) {
      return nullptr;
    }
    PyObject* key = _NewKey_ByIndex(self, index);
    if (key == nullptr) {
      return nullptr;
    }
    PyTuple_SET_ITEM(obj.get(), 0, key);
    PyObject* value = _NewObj_ByIndex(self, index);
    if (value == nullptr) {
      return nullptr;
    }
    PyTuple_SET_ITEM(obj.get(), 1, value);
    PyList_SET_ITEM(list.get(), index, obj.release());
  }
  return list.release();
}

static PyObject* NewContainerIterator(PyContainer* mapping,
                                      PyContainerIterator::IterKind kind);

static PyObject* Iter(PyContainer* self) {
  return NewContainerIterator(self, PyContainerIterator::KIND_ITERKEY);
}
static PyObject* IterKeys(PyContainer* self, PyObject* args) {
  return NewContainerIterator(self, PyContainerIterator::KIND_ITERKEY);
}
static PyObject* IterValues(PyContainer* self, PyObject* args) {
  return NewContainerIterator(self, PyContainerIterator::KIND_ITERVALUE);
}
static PyObject* IterItems(PyContainer* self, PyObject* args) {
  return NewContainerIterator(self, PyContainerIterator::KIND_ITERITEM);
}

static PyMethodDef MappingMethods[] = {
    {"get", (PyCFunction)Get, METH_VARARGS},
    {"keys", (PyCFunction)Keys, METH_NOARGS},
    {"values", (PyCFunction)Values, METH_NOARGS},
    {"items", (PyCFunction)Items, METH_NOARGS},
    {"iterkeys", (PyCFunction)IterKeys, METH_NOARGS},
    {"itervalues", (PyCFunction)IterValues, METH_NOARGS},
    {"iteritems", (PyCFunction)IterItems, METH_NOARGS},
    {nullptr},
};

PyTypeObject DescriptorMapping_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0) "DescriptorMapping",  // tp_name
    sizeof(PyContainer),                                         // tp_basicsize
    0,                                                           // tp_itemsize
    nullptr,                                                     // tp_dealloc
#if PY_VERSION_HEX < 0x03080000
    nullptr,  // tp_print
#else
    0,  // tp_vectorcall_offset
#endif
    nullptr,                   // tp_getattr
    nullptr,                   // tp_setattr
    nullptr,                   // tp_compare
    (reprfunc)ContainerRepr,   // tp_repr
    nullptr,                   // tp_as_number
    &MappingSequenceMethods,   // tp_as_sequence
    &MappingMappingMethods,    // tp_as_mapping
    nullptr,                   // tp_hash
    nullptr,                   // tp_call
    nullptr,                   // tp_str
    nullptr,                   // tp_getattro
    nullptr,                   // tp_setattro
    nullptr,                   // tp_as_buffer
    Py_TPFLAGS_DEFAULT,        // tp_flags
    nullptr,                   // tp_doc
    nullptr,                   // tp_traverse
    nullptr,                   // tp_clear
    (richcmpfunc)RichCompare,  // tp_richcompare
    0,                         // tp_weaklistoffset
    (getiterfunc)Iter,         // tp_iter
    nullptr,                   // tp_iternext
    MappingMethods,            // tp_methods
    nullptr,                   // tp_members
    nullptr,                   // tp_getset
    nullptr,                   // tp_base
    nullptr,                   // tp_dict
    nullptr,                   // tp_descr_get
    nullptr,                   // tp_descr_set
    0,                         // tp_dictoffset
    nullptr,                   // tp_init
    nullptr,                   // tp_alloc
    nullptr,                   // tp_new
    nullptr,                   // tp_free
};

// The DescriptorSequence type.

static PyObject* GetItem(PyContainer* self, Py_ssize_t index) {
  if (index < 0) {
    index += Length(self);
  }
  if (index < 0 || index >= Length(self)) {
    PyErr_SetString(PyExc_IndexError, "index out of range");
    return nullptr;
  }
  return _NewObj_ByIndex(self, index);
}

static PyObject *
SeqSubscript(PyContainer* self, PyObject* item) {
  if (PyIndex_Check(item)) {
      Py_ssize_t index;
      index = PyNumber_AsSsize_t(item, PyExc_IndexError);
      if (index == -1 && PyErr_Occurred()) return nullptr;
      return GetItem(self, index);
  }
  // Materialize the list and delegate the operation to it.
  ScopedPyObjectPtr list(PyObject_CallFunctionObjArgs(
      reinterpret_cast<PyObject*>(&PyList_Type), self, nullptr));
  if (list == nullptr) {
    return nullptr;
  }
  return Py_TYPE(list.get())->tp_as_mapping->mp_subscript(list.get(), item);
}

// Returns the position of the item in the sequence, of -1 if not found.
// This function never fails.
int Find(PyContainer* self, PyObject* item) {
  // The item can only be in one position: item.index.
  // Check that self[item.index] == item, it's faster than a linear search.
  //
  // This assumes that sequences are only defined by syntax of the .proto file:
  // a specific item belongs to only one sequence, depending on its position in
  // the .proto file definition.
  const void* descriptor_ptr = PyDescriptor_AsVoidPtr(item);
  if (descriptor_ptr == nullptr) {
    PyErr_Clear();
    // Not a descriptor, it cannot be in the list.
    return -1;
  }
  if (self->container_def->get_item_index_fn) {
    int index = self->container_def->get_item_index_fn(descriptor_ptr);
    if (index < 0 || index >= Length(self)) {
      // This index is not from this collection.
      return -1;
    }
    if (self->container_def->get_by_index_fn(self, index) != descriptor_ptr) {
      // The descriptor at this index is not the same.
      return -1;
    }
    // self[item.index] == item, so return the index.
    return index;
  } else {
    // Fall back to linear search.
    int length = Length(self);
    for (int index=0; index < length; index++) {
      if (self->container_def->get_by_index_fn(self, index) == descriptor_ptr) {
        return index;
      }
    }
    // Not found
    return -1;
  }
}

// Implements list.index(): the position of the item is in the sequence.
static PyObject* Index(PyContainer* self, PyObject* item) {
  int position = Find(self, item);
  if (position < 0) {
    // Not found
    PyErr_SetNone(PyExc_ValueError);
    return nullptr;
  } else {
    return PyLong_FromLong(position);
  }
}
// Implements "list.__contains__()": is the object in the sequence.
static int SeqContains(PyContainer* self, PyObject* item) {
  int position = Find(self, item);
  if (position < 0) {
    return 0;
  } else {
    return 1;
  }
}

// Implements list.count(): number of occurrences of the item in the sequence.
// An item can only appear once in a sequence. If it exists, return 1.
static PyObject* Count(PyContainer* self, PyObject* item) {
  int position = Find(self, item);
  if (position < 0) {
    return PyLong_FromLong(0);
  } else {
    return PyLong_FromLong(1);
  }
}

static PyObject* Append(PyContainer* self, PyObject* args) {
  if (_CalledFromGeneratedFile(0)) {
    Py_RETURN_NONE;
  }
  PyErr_Format(PyExc_TypeError,
               "'%.200s' object is not a mutable sequence",
               Py_TYPE(self)->tp_name);
  return nullptr;
}

static PyObject* Reversed(PyContainer* self, PyObject* args) {
  return NewContainerIterator(self,
                              PyContainerIterator::KIND_ITERVALUE_REVERSED);
}

static PyMethodDef SeqMethods[] = {
    {"index", (PyCFunction)Index, METH_O},
    {"count", (PyCFunction)Count, METH_O},
    {"append", (PyCFunction)Append, METH_O},
    {"__reversed__", (PyCFunction)Reversed, METH_NOARGS},
    {nullptr},
};

static PySequenceMethods SeqSequenceMethods = {
    (lenfunc)Length,          // sq_length
    nullptr,                  // sq_concat
    nullptr,                  // sq_repeat
    (ssizeargfunc)GetItem,    // sq_item
    nullptr,                  // sq_slice
    nullptr,                  // sq_ass_item
    nullptr,                  // sq_ass_slice
    (objobjproc)SeqContains,  // sq_contains
};

static PyMappingMethods SeqMappingMethods = {
    (lenfunc)Length,           // mp_length
    (binaryfunc)SeqSubscript,  // mp_subscript
    nullptr,                   // mp_ass_subscript
};

PyTypeObject DescriptorSequence_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0) "DescriptorSequence",  // tp_name
    sizeof(PyContainer),  // tp_basicsize
    0,                    // tp_itemsize
    nullptr,              // tp_dealloc
#if PY_VERSION_HEX < 0x03080000
    nullptr,  // tp_print
#else
    0,  // tp_vectorcall_offset
#endif
    nullptr,                   // tp_getattr
    nullptr,                   // tp_setattr
    nullptr,                   // tp_compare
    (reprfunc)ContainerRepr,   // tp_repr
    nullptr,                   // tp_as_number
    &SeqSequenceMethods,       // tp_as_sequence
    &SeqMappingMethods,        // tp_as_mapping
    nullptr,                   // tp_hash
    nullptr,                   // tp_call
    nullptr,                   // tp_str
    nullptr,                   // tp_getattro
    nullptr,                   // tp_setattro
    nullptr,                   // tp_as_buffer
    Py_TPFLAGS_DEFAULT,        // tp_flags
    nullptr,                   // tp_doc
    nullptr,                   // tp_traverse
    nullptr,                   // tp_clear
    (richcmpfunc)RichCompare,  // tp_richcompare
    0,                         // tp_weaklistoffset
    nullptr,                   // tp_iter
    nullptr,                   // tp_iternext
    SeqMethods,                // tp_methods
    nullptr,                   // tp_members
    nullptr,                   // tp_getset
    nullptr,                   // tp_base
    nullptr,                   // tp_dict
    nullptr,                   // tp_descr_get
    nullptr,                   // tp_descr_set
    0,                         // tp_dictoffset
    nullptr,                   // tp_init
    nullptr,                   // tp_alloc
    nullptr,                   // tp_new
    nullptr,                   // tp_free
};

static PyObject* NewMappingByName(
    DescriptorContainerDef* container_def, const void* descriptor) {
  PyContainer* self = PyObject_New(PyContainer, &DescriptorMapping_Type);
  if (self == nullptr) {
    return nullptr;
  }
  self->descriptor = descriptor;
  self->container_def = container_def;
  self->kind = PyContainer::KIND_BYNAME;
  return reinterpret_cast<PyObject*>(self);
}

static PyObject* NewMappingByCamelcaseName(
    DescriptorContainerDef* container_def, const void* descriptor) {
  PyContainer* self = PyObject_New(PyContainer, &DescriptorMapping_Type);
  if (self == nullptr) {
    return nullptr;
  }
  self->descriptor = descriptor;
  self->container_def = container_def;
  self->kind = PyContainer::KIND_BYCAMELCASENAME;
  return reinterpret_cast<PyObject*>(self);
}

static PyObject* NewMappingByNumber(
    DescriptorContainerDef* container_def, const void* descriptor) {
  if (container_def->get_by_number_fn == nullptr ||
      container_def->get_item_number_fn == nullptr) {
    PyErr_SetNone(PyExc_NotImplementedError);
    return nullptr;
  }
  PyContainer* self = PyObject_New(PyContainer, &DescriptorMapping_Type);
  if (self == nullptr) {
    return nullptr;
  }
  self->descriptor = descriptor;
  self->container_def = container_def;
  self->kind = PyContainer::KIND_BYNUMBER;
  return reinterpret_cast<PyObject*>(self);
}

static PyObject* NewSequence(
    DescriptorContainerDef* container_def, const void* descriptor) {
  PyContainer* self = PyObject_New(PyContainer, &DescriptorSequence_Type);
  if (self == nullptr) {
    return nullptr;
  }
  self->descriptor = descriptor;
  self->container_def = container_def;
  self->kind = PyContainer::KIND_SEQUENCE;
  return reinterpret_cast<PyObject*>(self);
}

// Implement iterators over PyContainers.

static void Iterator_Dealloc(PyContainerIterator* self) {
  Py_CLEAR(self->container);
  Py_TYPE(self)->tp_free(reinterpret_cast<PyObject*>(self));
}

static PyObject* Iterator_Next(PyContainerIterator* self) {
  int count = self->container->container_def->count_fn(self->container);
  if (self->index >= count) {
    // Return null with no exception to indicate the end.
    return nullptr;
  }
  int index = self->index;
  self->index += 1;
  switch (self->kind) {
    case PyContainerIterator::KIND_ITERKEY:
      return _NewKey_ByIndex(self->container, index);
    case PyContainerIterator::KIND_ITERVALUE:
      return _NewObj_ByIndex(self->container, index);
    case PyContainerIterator::KIND_ITERVALUE_REVERSED:
      return _NewObj_ByIndex(self->container, count - index - 1);
    case PyContainerIterator::KIND_ITERITEM: {
      PyObject* obj = PyTuple_New(2);
      if (obj == nullptr) {
        return nullptr;
      }
      PyObject* key = _NewKey_ByIndex(self->container, index);
      if (key == nullptr) {
        Py_DECREF(obj);
        return nullptr;
      }
      PyTuple_SET_ITEM(obj, 0, key);
      PyObject* value = _NewObj_ByIndex(self->container, index);
      if (value == nullptr) {
        Py_DECREF(obj);
        return nullptr;
      }
      PyTuple_SET_ITEM(obj, 1, value);
      return obj;
    }
    default:
      PyErr_SetNone(PyExc_NotImplementedError);
      return nullptr;
  }
}

static PyTypeObject ContainerIterator_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type,
                          0) "DescriptorContainerIterator",  // tp_name
    sizeof(PyContainerIterator),                             // tp_basicsize
    0,                                                       // tp_itemsize
    (destructor)Iterator_Dealloc,                            // tp_dealloc
#if PY_VERSION_HEX < 0x03080000
    nullptr,  // tp_print
#else
    0,  // tp_vectorcall_offset
#endif
    nullptr,                      // tp_getattr
    nullptr,                      // tp_setattr
    nullptr,                      // tp_compare
    nullptr,                      // tp_repr
    nullptr,                      // tp_as_number
    nullptr,                      // tp_as_sequence
    nullptr,                      // tp_as_mapping
    nullptr,                      // tp_hash
    nullptr,                      // tp_call
    nullptr,                      // tp_str
    nullptr,                      // tp_getattro
    nullptr,                      // tp_setattro
    nullptr,                      // tp_as_buffer
    Py_TPFLAGS_DEFAULT,           // tp_flags
    nullptr,                      // tp_doc
    nullptr,                      // tp_traverse
    nullptr,                      // tp_clear
    nullptr,                      // tp_richcompare
    0,                            // tp_weaklistoffset
    PyObject_SelfIter,            // tp_iter
    (iternextfunc)Iterator_Next,  // tp_iternext
    nullptr,                      // tp_methods
    nullptr,                      // tp_members
    nullptr,                      // tp_getset
    nullptr,                      // tp_base
    nullptr,                      // tp_dict
    nullptr,                      // tp_descr_get
    nullptr,                      // tp_descr_set
    0,                            // tp_dictoffset
    nullptr,                      // tp_init
    nullptr,                      // tp_alloc
    nullptr,                      // tp_new
    nullptr,                      // tp_free
};

static PyObject* NewContainerIterator(PyContainer* container,
                                      PyContainerIterator::IterKind kind) {
  PyContainerIterator* self = PyObject_New(PyContainerIterator,
                                           &ContainerIterator_Type);
  if (self == nullptr) {
    return nullptr;
  }
  Py_INCREF(container);
  self->container = container;
  self->kind = kind;
  self->index = 0;

  return reinterpret_cast<PyObject*>(self);
}

}  // namespace descriptor

// Now define the real collections!

namespace message_descriptor {

typedef const Descriptor* ParentDescriptor;

static ParentDescriptor GetDescriptor(PyContainer* self) {
  return reinterpret_cast<ParentDescriptor>(self->descriptor);
}

namespace fields {

typedef const FieldDescriptor* ItemDescriptor;

static int Count(PyContainer* self) {
  return GetDescriptor(self)->field_count();
}

static const void* GetByName(PyContainer* self, absl::string_view name) {
  return GetDescriptor(self)->FindFieldByName(name);
}

static const void* GetByCamelcaseName(PyContainer* self,
                                      absl::string_view name) {
  return GetDescriptor(self)->FindFieldByCamelcaseName(name);
}

static const void* GetByNumber(PyContainer* self, int number) {
  return GetDescriptor(self)->FindFieldByNumber(number);
}

static const void* GetByIndex(PyContainer* self, int index) {
  return GetDescriptor(self)->field(index);
}

static PyObject* NewObjectFromItem(const void* item) {
  return PyFieldDescriptor_FromDescriptor(static_cast<ItemDescriptor>(item));
}

static absl::string_view GetItemName(const void* item) {
  return static_cast<ItemDescriptor>(item)->name();
}

static absl::string_view GetItemCamelcaseName(const void* item) {
  return static_cast<ItemDescriptor>(item)->camelcase_name();
}

static int GetItemNumber(const void* item) {
  return static_cast<ItemDescriptor>(item)->number();
}

static int GetItemIndex(const void* item) {
  return static_cast<ItemDescriptor>(item)->index();
}

static DescriptorContainerDef ContainerDef = {
    "MessageFields",      Count,         GetByIndex,        GetByName,
    GetByCamelcaseName,   GetByNumber,   NewObjectFromItem, GetItemName,
    GetItemCamelcaseName, GetItemNumber, GetItemIndex,
};

}  // namespace fields

PyObject* NewMessageFieldsByName(ParentDescriptor descriptor) {
  return descriptor::NewMappingByName(&fields::ContainerDef, descriptor);
}

PyObject* NewMessageFieldsByCamelcaseName(ParentDescriptor descriptor) {
  return descriptor::NewMappingByCamelcaseName(&fields::ContainerDef,
                                               descriptor);
}

PyObject* NewMessageFieldsByNumber(ParentDescriptor descriptor) {
  return descriptor::NewMappingByNumber(&fields::ContainerDef, descriptor);
}

PyObject* NewMessageFieldsSeq(ParentDescriptor descriptor) {
  return descriptor::NewSequence(&fields::ContainerDef, descriptor);
}

namespace nested_types {

typedef const Descriptor* ItemDescriptor;

static int Count(PyContainer* self) {
  return GetDescriptor(self)->nested_type_count();
}

static const void* GetByName(PyContainer* self, absl::string_view name) {
  return GetDescriptor(self)->FindNestedTypeByName(name);
}

static const void* GetByIndex(PyContainer* self, int index) {
  return GetDescriptor(self)->nested_type(index);
}

static PyObject* NewObjectFromItem(const void* item) {
  return PyMessageDescriptor_FromDescriptor(static_cast<ItemDescriptor>(item));
}

static absl::string_view GetItemName(const void* item) {
  return static_cast<ItemDescriptor>(item)->name();
}

static int GetItemIndex(const void* item) {
  return static_cast<ItemDescriptor>(item)->index();
}

static DescriptorContainerDef ContainerDef = {
    "MessageNestedTypes",
    Count,
    GetByIndex,
    GetByName,
    nullptr,
    nullptr,
    NewObjectFromItem,
    GetItemName,
    nullptr,
    nullptr,
    GetItemIndex,
};

}  // namespace nested_types

PyObject* NewMessageNestedTypesSeq(ParentDescriptor descriptor) {
  return descriptor::NewSequence(&nested_types::ContainerDef, descriptor);
}

PyObject* NewMessageNestedTypesByName(ParentDescriptor descriptor) {
  return descriptor::NewMappingByName(&nested_types::ContainerDef, descriptor);
}

namespace enums {

typedef const EnumDescriptor* ItemDescriptor;

static int Count(PyContainer* self) {
  return GetDescriptor(self)->enum_type_count();
}

static const void* GetByName(PyContainer* self, absl::string_view name) {
  return GetDescriptor(self)->FindEnumTypeByName(name);
}

static const void* GetByIndex(PyContainer* self, int index) {
  return GetDescriptor(self)->enum_type(index);
}

static PyObject* NewObjectFromItem(const void* item) {
  return PyEnumDescriptor_FromDescriptor(static_cast<ItemDescriptor>(item));
}

static absl::string_view GetItemName(const void* item) {
  return static_cast<ItemDescriptor>(item)->name();
}

static int GetItemIndex(const void* item) {
  return static_cast<ItemDescriptor>(item)->index();
}

static DescriptorContainerDef ContainerDef = {
    "MessageNestedEnums",
    Count,
    GetByIndex,
    GetByName,
    nullptr,
    nullptr,
    NewObjectFromItem,
    GetItemName,
    nullptr,
    nullptr,
    GetItemIndex,
};

}  // namespace enums

PyObject* NewMessageEnumsByName(ParentDescriptor descriptor) {
  return descriptor::NewMappingByName(&enums::ContainerDef, descriptor);
}

PyObject* NewMessageEnumsSeq(ParentDescriptor descriptor) {
  return descriptor::NewSequence(&enums::ContainerDef, descriptor);
}

namespace enumvalues {

// This is the "enum_values_by_name" mapping, which collects values from all
// enum types in a message.
//
// Note that the behavior of the C++ descriptor is different: it will search and
// return the first value that matches the name, whereas the Python
// implementation retrieves the last one.

typedef const EnumValueDescriptor* ItemDescriptor;

static int Count(PyContainer* self) {
  int count = 0;
  for (int i = 0; i < GetDescriptor(self)->enum_type_count(); ++i) {
    count += GetDescriptor(self)->enum_type(i)->value_count();
  }
  return count;
}

static const void* GetByName(PyContainer* self, absl::string_view name) {
  return GetDescriptor(self)->FindEnumValueByName(name);
}

static const void* GetByIndex(PyContainer* self, int index) {
  // This is not optimal, but the number of enums *types* in a given message
  // is small.  This function is only used when iterating over the mapping.
  const EnumDescriptor* enum_type = nullptr;
  int enum_type_count = GetDescriptor(self)->enum_type_count();
  for (int i = 0; i < enum_type_count; ++i) {
    enum_type = GetDescriptor(self)->enum_type(i);
    int enum_value_count = enum_type->value_count();
    if (index < enum_value_count) {
      // Found it!
      break;
    }
    index -= enum_value_count;
  }
  // The next statement cannot overflow, because this function is only called by
  // internal iterators which ensure that 0 <= index < Count().
  return enum_type->value(index);
}

static PyObject* NewObjectFromItem(const void* item) {
  return PyEnumValueDescriptor_FromDescriptor(
      static_cast<ItemDescriptor>(item));
}

static absl::string_view GetItemName(const void* item) {
  return static_cast<ItemDescriptor>(item)->name();
}

static DescriptorContainerDef ContainerDef = {
    "MessageEnumValues", Count,       GetByIndex, GetByName, nullptr, nullptr,
    NewObjectFromItem,   GetItemName, nullptr,    nullptr,   nullptr,
};

}  // namespace enumvalues

PyObject* NewMessageEnumValuesByName(ParentDescriptor descriptor) {
  return descriptor::NewMappingByName(&enumvalues::ContainerDef, descriptor);
}

namespace extensions {

typedef const FieldDescriptor* ItemDescriptor;

static int Count(PyContainer* self) {
  return GetDescriptor(self)->extension_count();
}

static const void* GetByName(PyContainer* self, absl::string_view name) {
  return GetDescriptor(self)->FindExtensionByName(name);
}

static const void* GetByIndex(PyContainer* self, int index) {
  return GetDescriptor(self)->extension(index);
}

static PyObject* NewObjectFromItem(const void* item) {
  return PyFieldDescriptor_FromDescriptor(static_cast<ItemDescriptor>(item));
}

static absl::string_view GetItemName(const void* item) {
  return static_cast<ItemDescriptor>(item)->name();
}

static int GetItemIndex(const void* item) {
  return static_cast<ItemDescriptor>(item)->index();
}

static DescriptorContainerDef ContainerDef = {
    "MessageExtensions",
    Count,
    GetByIndex,
    GetByName,
    nullptr,
    nullptr,
    NewObjectFromItem,
    GetItemName,
    nullptr,
    nullptr,
    GetItemIndex,
};

}  // namespace extensions

PyObject* NewMessageExtensionsByName(ParentDescriptor descriptor) {
  return descriptor::NewMappingByName(&extensions::ContainerDef, descriptor);
}

PyObject* NewMessageExtensionsSeq(ParentDescriptor descriptor) {
  return descriptor::NewSequence(&extensions::ContainerDef, descriptor);
}

namespace oneofs {

typedef const OneofDescriptor* ItemDescriptor;

static int Count(PyContainer* self) {
  return GetDescriptor(self)->oneof_decl_count();
}

static const void* GetByName(PyContainer* self, absl::string_view name) {
  return GetDescriptor(self)->FindOneofByName(name);
}

static const void* GetByIndex(PyContainer* self, int index) {
  return GetDescriptor(self)->oneof_decl(index);
}

static PyObject* NewObjectFromItem(const void* item) {
  return PyOneofDescriptor_FromDescriptor(static_cast<ItemDescriptor>(item));
}

static absl::string_view GetItemName(const void* item) {
  return static_cast<ItemDescriptor>(item)->name();
}

static int GetItemIndex(const void* item) {
  return static_cast<ItemDescriptor>(item)->index();
}

static DescriptorContainerDef ContainerDef = {
    "MessageOneofs", Count,   GetByIndex,        GetByName,
    nullptr,         nullptr, NewObjectFromItem, GetItemName,
    nullptr,         nullptr, GetItemIndex,
};

}  // namespace oneofs

PyObject* NewMessageOneofsByName(ParentDescriptor descriptor) {
  return descriptor::NewMappingByName(&oneofs::ContainerDef, descriptor);
}

PyObject* NewMessageOneofsSeq(ParentDescriptor descriptor) {
  return descriptor::NewSequence(&oneofs::ContainerDef, descriptor);
}

}  // namespace message_descriptor

namespace enum_descriptor {

typedef const EnumDescriptor* ParentDescriptor;

static ParentDescriptor GetDescriptor(PyContainer* self) {
  return reinterpret_cast<ParentDescriptor>(self->descriptor);
}

namespace enumvalues {

typedef const EnumValueDescriptor* ItemDescriptor;

static int Count(PyContainer* self) {
  return GetDescriptor(self)->value_count();
}

static const void* GetByIndex(PyContainer* self, int index) {
  return GetDescriptor(self)->value(index);
}

static const void* GetByName(PyContainer* self, absl::string_view name) {
  return GetDescriptor(self)->FindValueByName(name);
}

static const void* GetByNumber(PyContainer* self, int number) {
  return GetDescriptor(self)->FindValueByNumber(number);
}

static PyObject* NewObjectFromItem(const void* item) {
  return PyEnumValueDescriptor_FromDescriptor(
      static_cast<ItemDescriptor>(item));
}

static absl::string_view GetItemName(const void* item) {
  return static_cast<ItemDescriptor>(item)->name();
}

static int GetItemNumber(const void* item) {
  return static_cast<ItemDescriptor>(item)->number();
}

static int GetItemIndex(const void* item) {
  return static_cast<ItemDescriptor>(item)->index();
}

static DescriptorContainerDef ContainerDef = {
    "EnumValues", Count,         GetByIndex,        GetByName,
    nullptr,      GetByNumber,   NewObjectFromItem, GetItemName,
    nullptr,      GetItemNumber, GetItemIndex,
};

}  // namespace enumvalues

PyObject* NewEnumValuesByName(ParentDescriptor descriptor) {
  return descriptor::NewMappingByName(&enumvalues::ContainerDef, descriptor);
}

PyObject* NewEnumValuesByNumber(ParentDescriptor descriptor) {
  return descriptor::NewMappingByNumber(&enumvalues::ContainerDef, descriptor);
}

PyObject* NewEnumValuesSeq(ParentDescriptor descriptor) {
  return descriptor::NewSequence(&enumvalues::ContainerDef, descriptor);
}

}  // namespace enum_descriptor

namespace oneof_descriptor {

typedef const OneofDescriptor* ParentDescriptor;

static ParentDescriptor GetDescriptor(PyContainer* self) {
  return reinterpret_cast<ParentDescriptor>(self->descriptor);
}

namespace fields {

typedef const FieldDescriptor* ItemDescriptor;

static int Count(PyContainer* self) {
  return GetDescriptor(self)->field_count();
}

static const void* GetByIndex(PyContainer* self, int index) {
  return GetDescriptor(self)->field(index);
}

static PyObject* NewObjectFromItem(const void* item) {
  return PyFieldDescriptor_FromDescriptor(static_cast<ItemDescriptor>(item));
}

static int GetItemIndex(const void* item) {
  return static_cast<ItemDescriptor>(item)->index_in_oneof();
}

static DescriptorContainerDef ContainerDef = {
    "OneofFields",     Count,   GetByIndex, nullptr, nullptr,      nullptr,
    NewObjectFromItem, nullptr, nullptr,    nullptr, GetItemIndex,
};

}  // namespace fields

PyObject* NewOneofFieldsSeq(ParentDescriptor descriptor) {
  return descriptor::NewSequence(&fields::ContainerDef, descriptor);
}

}  // namespace oneof_descriptor

namespace service_descriptor {

typedef const ServiceDescriptor* ParentDescriptor;

static ParentDescriptor GetDescriptor(PyContainer* self) {
  return reinterpret_cast<ParentDescriptor>(self->descriptor);
}

namespace methods {

typedef const MethodDescriptor* ItemDescriptor;

static int Count(PyContainer* self) {
  return GetDescriptor(self)->method_count();
}

static const void* GetByName(PyContainer* self, absl::string_view name) {
  return GetDescriptor(self)->FindMethodByName(name);
}

static const void* GetByIndex(PyContainer* self, int index) {
  return GetDescriptor(self)->method(index);
}

static PyObject* NewObjectFromItem(const void* item) {
  return PyMethodDescriptor_FromDescriptor(static_cast<ItemDescriptor>(item));
}

static absl::string_view GetItemName(const void* item) {
  return static_cast<ItemDescriptor>(item)->name();
}

static int GetItemIndex(const void* item) {
  return static_cast<ItemDescriptor>(item)->index();
}

static DescriptorContainerDef ContainerDef = {
    "ServiceMethods", Count,   GetByIndex,        GetByName,
    nullptr,          nullptr, NewObjectFromItem, GetItemName,
    nullptr,          nullptr, GetItemIndex,
};

}  // namespace methods

PyObject* NewServiceMethodsSeq(ParentDescriptor descriptor) {
  return descriptor::NewSequence(&methods::ContainerDef, descriptor);
}

PyObject* NewServiceMethodsByName(ParentDescriptor descriptor) {
  return descriptor::NewMappingByName(&methods::ContainerDef, descriptor);
}

}  // namespace service_descriptor

namespace file_descriptor {

typedef const FileDescriptor* ParentDescriptor;

static ParentDescriptor GetDescriptor(PyContainer* self) {
  return reinterpret_cast<ParentDescriptor>(self->descriptor);
}

namespace messages {

typedef const Descriptor* ItemDescriptor;

static int Count(PyContainer* self) {
  return GetDescriptor(self)->message_type_count();
}

static const void* GetByName(PyContainer* self, absl::string_view name) {
  return GetDescriptor(self)->FindMessageTypeByName(name);
}

static const void* GetByIndex(PyContainer* self, int index) {
  return GetDescriptor(self)->message_type(index);
}

static PyObject* NewObjectFromItem(const void* item) {
  return PyMessageDescriptor_FromDescriptor(static_cast<ItemDescriptor>(item));
}

static absl::string_view GetItemName(const void* item) {
  return static_cast<ItemDescriptor>(item)->name();
}

static int GetItemIndex(const void* item) {
  return static_cast<ItemDescriptor>(item)->index();
}

static DescriptorContainerDef ContainerDef = {
    "FileMessages", Count,   GetByIndex,        GetByName,
    nullptr,        nullptr, NewObjectFromItem, GetItemName,
    nullptr,        nullptr, GetItemIndex,
};

}  // namespace messages

PyObject* NewFileMessageTypesByName(ParentDescriptor descriptor) {
  return descriptor::NewMappingByName(&messages::ContainerDef, descriptor);
}

namespace enums {

typedef const EnumDescriptor* ItemDescriptor;

static int Count(PyContainer* self) {
  return GetDescriptor(self)->enum_type_count();
}

static const void* GetByName(PyContainer* self, absl::string_view name) {
  return GetDescriptor(self)->FindEnumTypeByName(name);
}

static const void* GetByIndex(PyContainer* self, int index) {
  return GetDescriptor(self)->enum_type(index);
}

static PyObject* NewObjectFromItem(const void* item) {
  return PyEnumDescriptor_FromDescriptor(static_cast<ItemDescriptor>(item));
}

static absl::string_view GetItemName(const void* item) {
  return static_cast<ItemDescriptor>(item)->name();
}

static int GetItemIndex(const void* item) {
  return static_cast<ItemDescriptor>(item)->index();
}

static DescriptorContainerDef ContainerDef = {
    "FileEnums", Count,   GetByIndex,        GetByName,
    nullptr,     nullptr, NewObjectFromItem, GetItemName,
    nullptr,     nullptr, GetItemIndex,
};

}  // namespace enums

PyObject* NewFileEnumTypesByName(ParentDescriptor descriptor) {
  return descriptor::NewMappingByName(&enums::ContainerDef, descriptor);
}

namespace extensions {

typedef const FieldDescriptor* ItemDescriptor;

static int Count(PyContainer* self) {
  return GetDescriptor(self)->extension_count();
}

static const void* GetByName(PyContainer* self, absl::string_view name) {
  return GetDescriptor(self)->FindExtensionByName(name);
}

static const void* GetByIndex(PyContainer* self, int index) {
  return GetDescriptor(self)->extension(index);
}

static PyObject* NewObjectFromItem(const void* item) {
  return PyFieldDescriptor_FromDescriptor(static_cast<ItemDescriptor>(item));
}

static absl::string_view GetItemName(const void* item) {
  return static_cast<ItemDescriptor>(item)->name();
}

static int GetItemIndex(const void* item) {
  return static_cast<ItemDescriptor>(item)->index();
}

static DescriptorContainerDef ContainerDef = {
    "FileExtensions", Count,   GetByIndex,        GetByName,
    nullptr,          nullptr, NewObjectFromItem, GetItemName,
    nullptr,          nullptr, GetItemIndex,
};

}  // namespace extensions

PyObject* NewFileExtensionsByName(ParentDescriptor descriptor) {
  return descriptor::NewMappingByName(&extensions::ContainerDef, descriptor);
}

namespace services {

typedef const ServiceDescriptor* ItemDescriptor;

static int Count(PyContainer* self) {
  return GetDescriptor(self)->service_count();
}

static const void* GetByName(PyContainer* self, absl::string_view name) {
  return GetDescriptor(self)->FindServiceByName(name);
}

static const void* GetByIndex(PyContainer* self, int index) {
  return GetDescriptor(self)->service(index);
}

static PyObject* NewObjectFromItem(const void* item) {
  return PyServiceDescriptor_FromDescriptor(static_cast<ItemDescriptor>(item));
}

static absl::string_view GetItemName(const void* item) {
  return static_cast<ItemDescriptor>(item)->name();
}

static int GetItemIndex(const void* item) {
  return static_cast<ItemDescriptor>(item)->index();
}

static DescriptorContainerDef ContainerDef = {
    "FileServices", Count,   GetByIndex,        GetByName,
    nullptr,        nullptr, NewObjectFromItem, GetItemName,
    nullptr,        nullptr, GetItemIndex,
};

}  // namespace services

PyObject* NewFileServicesByName(const FileDescriptor* descriptor) {
  return descriptor::NewMappingByName(&services::ContainerDef, descriptor);
}

namespace dependencies {

typedef const FileDescriptor* ItemDescriptor;

static int Count(PyContainer* self) {
  return GetDescriptor(self)->dependency_count();
}

static const void* GetByIndex(PyContainer* self, int index) {
  return GetDescriptor(self)->dependency(index);
}

static PyObject* NewObjectFromItem(const void* item) {
  return PyFileDescriptor_FromDescriptor(static_cast<ItemDescriptor>(item));
}

static DescriptorContainerDef ContainerDef = {
    "FileDependencies", Count,   GetByIndex, nullptr, nullptr, nullptr,
    NewObjectFromItem,  nullptr, nullptr,    nullptr, nullptr,
};

}  // namespace dependencies

PyObject* NewFileDependencies(const FileDescriptor* descriptor) {
  return descriptor::NewSequence(&dependencies::ContainerDef, descriptor);
}

namespace public_dependencies {

typedef const FileDescriptor* ItemDescriptor;

static int Count(PyContainer* self) {
  return GetDescriptor(self)->public_dependency_count();
}

static const void* GetByIndex(PyContainer* self, int index) {
  return GetDescriptor(self)->public_dependency(index);
}

static PyObject* NewObjectFromItem(const void* item) {
  return PyFileDescriptor_FromDescriptor(static_cast<ItemDescriptor>(item));
}

static DescriptorContainerDef ContainerDef = {
    "FilePublicDependencies", Count,   GetByIndex, nullptr, nullptr, nullptr,
    NewObjectFromItem,        nullptr, nullptr,    nullptr, nullptr,
};

}  // namespace public_dependencies

PyObject* NewFilePublicDependencies(const FileDescriptor* descriptor) {
  return descriptor::NewSequence(&public_dependencies::ContainerDef,
                                 descriptor);
}

}  // namespace file_descriptor


// Register all implementations

bool InitDescriptorMappingTypes() {
  if (PyType_Ready(&descriptor::DescriptorMapping_Type) < 0)
    return false;
  if (PyType_Ready(&descriptor::DescriptorSequence_Type) < 0)
    return false;
  if (PyType_Ready(&descriptor::ContainerIterator_Type) < 0)
    return false;
  return true;
}

}  // namespace python
}  // namespace protobuf
}  // namespace google
