/* -*- c -*- */
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <structmember.h>

#define NPY_NO_DEPRECATED_API NPY_API_VERSION
#ifndef _MULTIARRAYMODULE
#define _MULTIARRAYMODULE
#endif

#include "numpy/arrayobject.h"
#include "numpy/npy_math.h"
#include "numpy/halffloat.h"
#include "numpy/arrayscalars.h"

#include "npy_pycompat.h"

#include "arraytypes.h"
#include "npy_config.h"
#include "mapping.h"
#include "ctors.h"
#include "dtypemeta.h"
#include "usertypes.h"
#include "number.h"
#include "numpyos.h"
#include "can_cast_table.h"
#include "common.h"
#include "conversion_utils.h"
#include "flagsobject.h"
#include "scalartypes.h"
#include "_datetime.h"
#include "datetime_strings.h"
#include "alloc.h"
#include "npy_import.h"
#include "dragon4.h"
#include "npy_longdouble.h"
#include "npy_buffer.h"
#include "npy_static_data.h"
#include "multiarraymodule.h"
#include "array_api_standard.h"

#include <stdlib.h>

#include "binop_override.h"

/*
 * used for allocating a single scalar, so use the default numpy
 * memory allocators instead of the (maybe) user overrides
 */
NPY_NO_EXPORT void *
npy_alloc_cache_zero(size_t nmemb, size_t size);

NPY_NO_EXPORT void
npy_free_cache(void * p, npy_uintp sz);

NPY_NO_EXPORT PyBoolScalarObject _PyArrayScalar_BoolValues[] = {
    {PyObject_HEAD_INIT(&PyBoolArrType_Type) 0},
    {PyObject_HEAD_INIT(&PyBoolArrType_Type) 1},
};

/* TimeInteger is deleted, but still here to fill the API slot */
NPY_NO_EXPORT PyTypeObject PyTimeIntegerArrType_Type;

/*
 * Inheritance is established later when tp_bases is set (or tp_base for
 * single inheritance)
 */

/**begin repeat
 * #name = number, integer, signedinteger, unsignedinteger, inexact,
 *         floating, complexfloating, flexible, character#
 * #NAME = Number, Integer, SignedInteger, UnsignedInteger, Inexact,
 *         Floating, ComplexFloating, Flexible, Character#
 */
NPY_NO_EXPORT PyTypeObject Py@NAME@ArrType_Type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "numpy.@name@",
    .tp_basicsize = sizeof(PyObject),
};
/**end repeat**/

static PyObject *
gentype_alloc(PyTypeObject *type, Py_ssize_t nitems)
{
    PyObject *obj;
    const size_t size = _PyObject_VAR_SIZE(type, nitems + 1);

    obj = (PyObject *)PyObject_Malloc(size);
    if (obj == NULL) {
        PyErr_NoMemory();
        return NULL;
    }
    /*
     * If we don't need to zero memory, we could use
     * PyObject_{New, NewVar} for this whole function.
     */
    memset(obj, 0, size);
    if (type->tp_itemsize == 0) {
        PyObject_Init(obj, type);
    }
    else {
        (void) PyObject_InitVar((PyVarObject *)obj, type, nitems);
    }
    return obj;
}

static void
gentype_dealloc(PyObject *v)
{
    Py_TYPE(v)->tp_free(v);
}

static void
gentype_free(PyObject *v)
{
    /*
     * have an explicit tp_free to enforce inheritance from it.
     * PyObject_Free is also the tp_free of PyBaseObject so python does not
     * COPYSLOT it, instead it takes the next parent PyInt which has a
     * different allocator
     */
    PyObject_Free(v);
}


static PyObject *
gentype_generic_method(PyObject *self, PyObject *args, PyObject *kwds,
        char *str)
{
    PyObject *arr, *meth, *ret;

    arr = PyArray_FromScalar(self, NULL);
    if (arr == NULL) {
        return NULL;
    }
    meth = PyObject_GetAttrString(arr, str);
    if (meth == NULL) {
        Py_DECREF(arr);
        return NULL;
    }
    if (kwds == NULL) {
        ret = PyObject_CallObject(meth, args);
    }
    else {
        ret = PyObject_Call(meth, args, kwds);
    }
    Py_DECREF(meth);
    Py_DECREF(arr);
    if (ret && PyArray_Check(ret)) {
        return PyArray_Return((PyArrayObject *)ret);
    }
    else {
        return ret;
    }
}


/*
 * Helper function to deal with binary operator deferral.  Must be passed a
 * valid self (a generic scalar) and an other item.
 * May fill self_item and/or other_arr (but not both) with non-NULL values.
 *
 * Why this dance?  When the other object is a exactly Python scalar something
 * awkward happens historically in NumPy.
 * NumPy doesn't define a result, but the ufunc would cast to `astype(object)`
 * which is the same as `scalar.item()`.  And that operation converts e.g.
 * float32 or float64 to Python floats.
 * It then retries. And because it is a builtin type now the operation may
 * succeed.
 *
 * This retrying pass only makes sense if the other object is a Python
 * scalar (otherwise we fill in `other_arr` which can be used to call the
 * ufunc).
 * Additionally, if `self.item()` has the same type as `self` we would end up
 * in an infinite recursion.
 *
 * So the result of this function means the following:
 *   - < 0 error return.
 *   - self_op is filled in: Retry the Python operator.
 *   - other_op is filled in: Use the array operator (goes into ufuncs)
 *     (This may be the original generic if it is one.)
 *   - neither is filled in: Return NotImplemented.
 *
 * It is not possible for both to be filled.  If `other` is also a generics,
 * it is returned.
 */
static inline int
find_binary_operation_path(
    PyObject *self, PyObject *other, PyObject **self_op, PyObject **other_op)
{
    *other_op = NULL;
    *self_op = NULL;

    if (PyArray_IsScalar(other, Generic) ||
            PyLong_CheckExact(other) ||
            PyFloat_CheckExact(other) ||
            PyComplex_CheckExact(other) ||
            PyBool_Check(other) ||
            PyArray_Check(other)) {
        /*
         * The other operand is ready for the operation already.  Must pass on
         * on float/long/complex mainly for weak promotion (NEP 50).
         */
        *other_op = Py_NewRef(other);
        return 0;
    }
    /*
     * If other has __array_ufunc__ always use ufunc.  If array-ufunc was None
     * we already deferred.  And any custom object with array-ufunc cannot call
     * our ufuncs without preventing recursion.
     * It may be nice to avoid double lookup in `BINOP_GIVE_UP_IF_NEEDED`.
     */
    PyObject *attr;
    if (PyArray_LookupSpecial(other, npy_interned_str.array_ufunc, &attr) < 0) {
        PyErr_Clear(); /* TODO[gh-14801]: propagate crashes during attribute access? */
    }
    else if (attr != NULL) {
        Py_DECREF(attr);
        *other_op = Py_NewRef(other);
        return 0;
    }

    /*
     * Now check `other`.  We want to know whether it is an object scalar
     * and the easiest way is by converting to an array here.
     */
    int was_scalar;
    PyArrayObject *arr = (PyArrayObject *)PyArray_FromAny_int(
                other, NULL, NULL, 0, 0, 0, NULL, &was_scalar);
    if (arr == NULL) {
        return -1;
    }

    if (!was_scalar || PyArray_DESCR(arr)->type_num != NPY_OBJECT) {
        /* 
         * The array is OK for usage and we can simply forward it.  There
         * is a theoretical subtlety here:  If the other object implements
         * `__array_wrap__`, we may ignore that.  However, this only matters
         * if the other object has the identical `__array_priority__` and
         * additionally already deferred back to us.
         * (`obj + scalar` and `scalar + obj` are not symmetric.)
         *
         * NOTE: Future NumPy may need to distinguish scalars here, one option
         *       could be marking the array.
         */
         *other_op = (PyObject *)arr;
         return 0;
    }
    Py_DECREF(arr);

    /*
     * If we are here, we need to operate on Python scalars.  In general
     * that would just fails since NumPy doesn't know the other object!
     *
     * However, NumPy (historically) made this often work magically because
     * ufuncs for object dtype end up casting to object with `.item()`. This in
     * turn often returns a Python type (e.g. float for float32, float64)!
     * Retrying then succeeds. So if (and only if) `self.item()` returns a new
     * type, we can safely attempt the operation (again) with that.
     */
    PyObject *self_item = PyObject_CallMethodNoArgs(self, npy_interned_str.item);
    if (self_item == NULL) {
        return -1;
    }
    if (Py_TYPE(self_item) != Py_TYPE(self)) {
        /* self_item can be used to retry the operation */
        *self_op = self_item;
        return 0;
    }
    /* The operation can't work and we will return NotImplemented */
    Py_DECREF(self_item);
    return 0;
}


/*
 * These are defined below as they require special handling, we still define
 * a _gen version here.  `power` is special as it has three arguments.
 */
static PyObject *
gentype_add(PyObject *m1, PyObject *m2);

static PyObject *
gentype_multiply(PyObject *m1, PyObject *m2);


/**begin repeat
 *
 * #name = add, multiply, subtract, remainder, divmod,
 *         lshift, rshift, and, xor, or, floor_divide, true_divide#
 * #ufunc = add, multiply, subtract, remainder, divmod,
 *         left_shift, right_shift, bitwise_and, bitwise_xor, bitwise_or,
 *         floor_divide, true_divide#
 * #func = Add, Multiply, Subtract, Remainder, Divmod,
 *         Lshift, Rshift, And, Xor, Or, FloorDivide, TrueDivide#
 * #suff = _gen, _gen,,,,,,,,,,#
 */
/* NOTE: We suffix the name for functions requiring special handling first. */
static PyObject *
gentype_@name@@suff@(PyObject *m1, PyObject *m2)
{
    BINOP_GIVE_UP_IF_NEEDED(m1, m2, nb_@name@, gentype_@name@);

    PyObject *self = NULL;
    PyObject *other = NULL;
    PyObject *self_op, *other_op;

    if (!PyArray_IsScalar(m2, Generic)) {
        self = m1;
        other = m2;
    }
    else {
        self = m2;
        other = m1;
    }
    if (find_binary_operation_path(self, other, &self_op, &other_op) < 0) {
        return NULL;
    }
    if (self_op != NULL) {
        PyObject *res;
        if (self == m1) {
            res = PyNumber_@func@(self_op, m2);
        }
        else {
            res = PyNumber_@func@(m1, self_op);
        }
        Py_DECREF(self_op);
        return res;
    }
    else if (other_op != NULL) {
        /* Call the corresponding ufunc (with the array) */
        PyObject *res;
        if (self == m1) {
            res = PyArray_GenericBinaryFunction(m1, other_op, n_ops.@ufunc@);
        }
        else {
            res = PyArray_GenericBinaryFunction(other_op, m2, n_ops.@ufunc@);
        }
        Py_DECREF(other_op);
        return res;
    }
    else {
        assert(other_op == NULL);
        Py_RETURN_NOTIMPLEMENTED;
    }
}

/**end repeat**/

/*
 * The following operators use the above, but require specialization.
 */

static PyObject *
gentype_add(PyObject *m1, PyObject *m2)
{
    /* special case str.__radd__, which should not call array_add */
    if (PyBytes_Check(m1) || PyUnicode_Check(m1)) {
        Py_INCREF(Py_NotImplemented);
        return Py_NotImplemented;
    }

    return gentype_add_gen(m1, m2);
}

/* Get a nested slot, or NULL if absent (for multiply implementation) */
#define GET_NESTED_SLOT(type, group, slot) \
    ((type)->group == NULL ? NULL : (type)->group->slot)

static PyObject *
gentype_multiply(PyObject *m1, PyObject *m2)
{
    /*
     * If the other object supports sequence repeat and not number multiply
     * we fall back on the python builtin to invoke the sequence repeat, rather
     * than promoting both arguments to ndarray.
     * This covers a list repeat by numpy scalars.
     * A python defined class will always only have the nb_multiply slot and
     * some classes may have neither defined. For the latter we want need
     * to give the normal case a chance to convert the object to ndarray.
     * Probably no class has both defined, but if they do, prefer number.
     */
    if (!PyArray_IsScalar(m1, Generic) &&
            GET_NESTED_SLOT(Py_TYPE(m1), tp_as_sequence, sq_repeat) != NULL &&
            GET_NESTED_SLOT(Py_TYPE(m1), tp_as_number, nb_multiply) == NULL) {
        Py_INCREF(Py_NotImplemented);
        return Py_NotImplemented;
    }
    if (!PyArray_IsScalar(m2, Generic) &&
            GET_NESTED_SLOT(Py_TYPE(m2), tp_as_sequence, sq_repeat) != NULL &&
            GET_NESTED_SLOT(Py_TYPE(m2), tp_as_number, nb_multiply) == NULL) {
        Py_INCREF(Py_NotImplemented);
        return Py_NotImplemented;
    }

    return gentype_multiply_gen(m1, m2);
}


/*
 * NOTE: The three argument nature of power  requires code duplication here.
 */
static PyObject *
gentype_power(PyObject *m1, PyObject *m2, PyObject *modulo)
{
    if (modulo != Py_None) {
        /* modular exponentiation is not implemented (gh-8804) */
        Py_INCREF(Py_NotImplemented);
        return Py_NotImplemented;
    }

    BINOP_GIVE_UP_IF_NEEDED(m1, m2, nb_power, gentype_power);

    PyObject *self = NULL;
    PyObject *other = NULL;
    PyObject *self_op, *other_op;

    if (!PyArray_IsScalar(m2, Generic)) {
        self = m1;
        other = m2;
    }
    else {
        self = m2;
        other = m1;
    }
    if (find_binary_operation_path(self, other, &self_op, &other_op) < 0) {
        return NULL;
    }
    if (self_op != NULL) {
        PyObject *res;
        if (self == m1) {
            res = PyNumber_Power(self_op, m2, Py_None);
        }
        else {
            res = PyNumber_Power(m1, self_op, Py_None);
        }
        Py_DECREF(self_op);
        return res;
    }
    else if (other_op != NULL) {
        /* Call the corresponding ufunc (with the array)
         * NOTE: As of NumPy 2.0 there are inconsistencies in array_power
         *       calling it would fail a (niche) test because an array is
         *       returned in one of the fast-paths.
         *       (once NumPy propagates 0-D arrays, this is irrelevant)
         */
        PyObject *res;
        if (self == m1) {
            res = PyArray_GenericBinaryFunction(m1, other_op, n_ops.power);
        }
        else {
            res = PyArray_GenericBinaryFunction(other_op, m2, n_ops.power);
        }
        Py_DECREF(other_op);
        return res;
    }
    else {
        assert(other_op == NULL);
        Py_RETURN_NOTIMPLEMENTED;
    }
}


/**begin repeat
 * #TYPE    = BYTE, UBYTE, SHORT, USHORT, INT, UINT,
 *            LONG, ULONG, LONGLONG, ULONGLONG#
 * #type    = npy_byte, npy_ubyte, npy_short, npy_ushort, npy_int, npy_uint,
 *            npy_long, npy_ulong, npy_longlong, npy_ulonglong#
 * #c       = hh, uhh, h, uh,, u, l, ul, ll, ull#
 * #Name    = Byte, UByte, Short, UShort, Int, UInt,
 *            Long, ULong, LongLong, ULongLong#
 * #convert = Long*8, LongLong*2#
 */
static PyObject *
@type@_bit_count(PyObject *self, PyObject *NPY_UNUSED(args))
{
    @type@ scalar = PyArrayScalar_VAL(self, @Name@);
    uint8_t count = npy_popcount@c@(scalar);
    PyObject *result = PyLong_From@convert@(count);

    return result;
}
/**end repeat**/

/**begin repeat
 *
 * #name = positive, negative, absolute, invert, int, float#
 */
static PyObject *
gentype_@name@(PyObject *m1)
{
    PyObject *arr, *ret;

    arr = PyArray_FromScalar(m1, NULL);
    if (arr == NULL) {
        return NULL;
    }
    ret = Py_TYPE(arr)->tp_as_number->nb_@name@(arr);
    Py_DECREF(arr);
    return ret;
}
/**end repeat**/

static int
gentype_nonzero_number(PyObject *m1)
{
    PyObject *arr;
    int ret;

    arr = PyArray_FromScalar(m1, NULL);
    if (arr == NULL) {
        return -1;
    }
    ret = Py_TYPE(arr)->tp_as_number->nb_bool(arr);
    Py_DECREF(arr);
    return ret;
}

static PyObject *
genint_type_str(PyObject *self)
{
    PyObject  *item, *item_str;
    PyArray_Descr *dtype = PyArray_DescrFromTypeObject((PyObject *)Py_TYPE(self));
    void *val = scalar_value(self, dtype);
    switch (dtype->type_num) {
        case NPY_BYTE:
            item = PyLong_FromLong(*(npy_byte *)val);
            break;
        case NPY_UBYTE:
            item = PyLong_FromUnsignedLong(*(npy_ubyte *)val);
            break;
        case NPY_SHORT:
            item = PyLong_FromLong(*(npy_short *)val);
            break;
        case NPY_USHORT:
            item = PyLong_FromUnsignedLong(*(npy_ushort *)val);
            break;
        case NPY_INT:
            item = PyLong_FromLong(*(npy_int *)val);
            break;
        case NPY_UINT:
            item = PyLong_FromUnsignedLong(*(npy_uint *)val);
            break;
        case NPY_LONG:
            item = PyLong_FromLong(*(npy_long *)val);
            break;
        case NPY_ULONG:
            item = PyLong_FromUnsignedLong(*(npy_ulong *)val);
            break;
        case NPY_LONGLONG:
            item = PyLong_FromLongLong(*(npy_longlong *)val);
            break;
        case NPY_ULONGLONG:
            item = PyLong_FromUnsignedLongLong(*(npy_ulonglong *)val);
            break;
        default:
            item = gentype_generic_method(self, NULL, NULL, "item");
            break;
    }
    Py_DECREF(dtype);
    if (item == NULL) {
        return NULL;
    }
    item_str = PyObject_Str(item);
    Py_DECREF(item);
    return item_str;
}

static PyObject *
genint_type_repr(PyObject *self)
{
    PyObject *value_string = genint_type_str(self);
    if (value_string == NULL) {
        return NULL;
    }
    int legacy_print_mode = get_legacy_print_mode();
    if (legacy_print_mode == -1) {
        return NULL;
    }
    if (legacy_print_mode <= 125) {
        return value_string;
    }

    int num = _typenum_fromtypeobj((PyObject *)Py_TYPE(self), 0);

    PyObject *repr;
    if (num == NPY_NOTYPE) {
        /* Not a builtin scalar (presumably), just use the name */
        repr = PyUnicode_FromFormat("%s(%S)", Py_TYPE(self)->tp_name, value_string);
        Py_DECREF(value_string);
        return repr;
    }

    PyArray_Descr *descr = PyArray_DescrFromType(num);  /* cannot fail */
    int bitsize = descr->elsize * 8;
    Py_DECREF(descr);
    if (PyTypeNum_ISUNSIGNED(num)) {
        repr = PyUnicode_FromFormat("np.uint%d(%S)", bitsize, value_string);
    }
    else {
        repr = PyUnicode_FromFormat("np.int%d(%S)", bitsize, value_string);
    }
    Py_DECREF(value_string);
    return repr;
}

static PyObject *
genbool_type_str(PyObject *self)
{
    return PyUnicode_FromString(
            ((PyBoolScalarObject *)self)->obval ? "True" : "False");
}

static PyObject *
genbool_type_repr(PyObject *self)
{
    int legacy_print_mode = get_legacy_print_mode();
    if (legacy_print_mode == -1) {
        return NULL;
    }
    if (legacy_print_mode <= 125) {
        return genbool_type_str(self);
    }
    return PyUnicode_FromString(
            ((PyBoolScalarObject *)self)->obval ? "np.True_" : "np.False_");
}

/*
 * The __format__ method for PEP 3101.
 */
static PyObject *
gentype_format(PyObject *self, PyObject *args)
{
    PyObject *format_spec;
    PyObject *obj, *ret;

    if (!PyArg_ParseTuple(args, "U:__format__", &format_spec)) {
        return NULL;
    }

    /*
     * Convert to an appropriate Python type and call its format.
     * TODO: For some types, like long double, this isn't right,
     *       because it throws away precision.
     */
    if (Py_TYPE(self) == &PyBoolArrType_Type) {
        obj = PyBool_FromLong(PyArrayScalar_VAL(self, Bool));
    }
    else if (PyArray_IsScalar(self, Integer)
             && !PyArray_IsScalar(self, Timedelta)) {
        obj = Py_TYPE(self)->tp_as_number->nb_int(self);
    }
    else if (PyArray_IsScalar(self, Floating)) {
        obj = Py_TYPE(self)->tp_as_number->nb_float(self);
    }
    else if (PyArray_IsScalar(self, ComplexFloating)) {
        double val[2];
        PyArray_Descr *dtype = PyArray_DescrFromScalar(self);

        if (dtype == NULL) {
            return NULL;
        }
        if (PyArray_CastScalarDirect(self, dtype, &val[0], NPY_CDOUBLE) < 0) {
            Py_DECREF(dtype);
            return NULL;
        }
        obj = PyComplex_FromDoubles(val[0], val[1]);
        Py_DECREF(dtype);
    }
    else {
        obj = PyObject_Str(self);
    }

    if (obj == NULL) {
        return NULL;
    }

    ret = PyObject_Format(obj, format_spec);
    Py_DECREF(obj);
    return ret;
}

#ifdef FORCE_NO_LONG_DOUBLE_FORMATTING
#undef NPY_LONGDOUBLE_FMT
#define NPY_LONGDOUBLE_FMT NPY_DOUBLE_FMT
#endif

/**begin repeat
 * #name = half, float, double, longdouble#
 * #Name = Half, Float, Double, LongDouble#
 * #NAME = HALF, FLOAT, DOUBLE, LONGDOUBLE#
 * #type = npy_half, npy_float, npy_double, npy_longdouble#
 * #suff = h, f, d, l#
 */

NPY_NO_EXPORT PyObject *
format_@name@(@type@ val, npy_bool scientific,
              int precision, int sign, TrimMode trim,
              int pad_left, int pad_right, int exp_digits)
{
    if (scientific) {
        return Dragon4_Scientific_@Name@(&val,
                        DigitMode_Unique, precision, -1,
                        sign, trim, pad_left, exp_digits);
    }
    else {
        return Dragon4_Positional_@Name@(&val,
                        DigitMode_Unique, CutoffMode_TotalLength, precision,
                        -1, sign, trim, pad_left, pad_right);
    }
}


/**end repeat**/

/*
 * Over-ride repr and str of array-scalar byte strings to remove NULL bytes and
 * then call the corresponding functions of PyBytes_Type to generate the string
 */

/**begin repeat
 * #form = repr, str#
 */
#define IS_@form@

static PyObject *
stringtype_@form@(PyObject *self)
{
    const npy_char *dptr, *ip;
    Py_ssize_t len;
    PyObject *new;
    PyObject *ret;

    ip = PyBytes_AS_STRING(self);
    len = PyBytes_GET_SIZE(self);
    for(dptr = ip + len - 1; len > 0 && *dptr == 0; len--, dptr--);
    new = PyBytes_FromStringAndSize(ip, len);
    if (new == NULL) {
        return NULL;
    }
    ret = PyBytes_Type.tp_@form@(new);
    Py_DECREF(new);
#ifdef IS_repr
    if (ret == NULL) {
        return NULL;
    }
    int legacy_print_mode = get_legacy_print_mode();
    if (legacy_print_mode == -1) {
        return NULL;
    }
    if (legacy_print_mode > 125) {
        Py_SETREF(ret, PyUnicode_FromFormat("np.bytes_(%S)", ret));
    }
#endif   /* IS_repr */
    return ret;
}
#undef IS_@form@
/**end repeat**/

/*
 * Over-ride repr and str of array-scalar strings to remove NULL code points and
 * then call the corresponding functions of PyUnicode_Type to generate the string
 */

/**begin repeat
 * #form = repr, str#
 */
#define IS_@form@

static PyObject *
unicodetype_@form@(PyObject *self)
{
    Py_UCS4 *dptr, *ip;
    Py_ssize_t len;
    PyObject *new;
    PyObject *ret;

    /* PyUnicode_READY is called by PyUnicode_GetLength */
    len = PyUnicode_GetLength(self);
    ip = PyUnicode_AsUCS4Copy(self);
    if (ip == NULL) {
        return NULL;
    }
    for(dptr = ip + len - 1; len > 0 && *dptr == 0; len--, dptr--);
    new = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, ip, len);
    if (new == NULL) {
        PyMem_Free(ip);
        return NULL;
    }
    ret = PyUnicode_Type.tp_@form@(new);
    Py_DECREF(new);
    PyMem_Free(ip);

#ifdef IS_repr
    if (ret == NULL) {
        return NULL;
    }
    int legacy_print_mode = get_legacy_print_mode();
    if (legacy_print_mode == -1) {
        return NULL;
    }
    if (legacy_print_mode > 125) {
        Py_SETREF(ret, PyUnicode_FromFormat("np.str_(%S)", ret));
    }
#endif  /* IS_repr */

    return ret;
}

#undef IS_@form@
/**end repeat**/

/*
 * Convert array of bytes to a string representation much like bytes.__repr__,
 * but convert all bytes (including ASCII) to the `\x00` notation with
 * uppercase hex codes (FF not ff).
 *
 * Largely copied from _Py_strhex_impl in CPython implementation
 */
static inline PyObject *
_void_to_hex(const char* argbuf, const Py_ssize_t arglen,
             const char *schars, const char *bprefix, const char *echars)
{
    PyObject *retval;
    int extrachars, slen;
    char *retbuf;
    Py_ssize_t i, j;
    char const *hexdigits = "0123456789ABCDEF";

    extrachars = strlen(schars) + strlen(echars);
    slen = extrachars + arglen*(2 + strlen(bprefix));

    if (arglen > (PY_SSIZE_T_MAX / 2) - extrachars) {
        return PyErr_NoMemory();
    }

    retbuf = (char *)PyMem_Malloc(slen);
    if (!retbuf) {
        return PyErr_NoMemory();
    }

    memcpy(retbuf, schars, strlen(schars));
    j = strlen(schars);

    for (i = 0; i < arglen; i++) {
        unsigned char c;
        memcpy(&retbuf[j], bprefix, strlen(bprefix));
        j += strlen(bprefix);
        c = (argbuf[i] >> 4) & 0xf;
        retbuf[j++] = hexdigits[c];
        c = argbuf[i] & 0xf;
        retbuf[j++] = hexdigits[c];
    }
    memcpy(&retbuf[j], echars, strlen(echars));

    retval = PyUnicode_FromStringAndSize(retbuf, slen);
    PyMem_Free(retbuf);

    return retval;
}

static PyObject *
_void_scalar_to_string(PyObject *obj, int repr) {
    if (npy_cache_import_runtime(
                "numpy._core.arrayprint", "_void_scalar_to_string",
                &npy_runtime_imports._void_scalar_to_string) == -1) {
        return NULL;
    }
    PyObject *is_repr = repr ? Py_True : Py_False;
    return PyObject_CallFunctionObjArgs(
            npy_runtime_imports._void_scalar_to_string, obj, is_repr, NULL);
}

static PyObject *
voidtype_repr(PyObject *self)
{
    PyVoidScalarObject *s = (PyVoidScalarObject*) self;
    if (PyDataType_HASFIELDS(s->descr)) {
        /* Python helper checks for the legacy mode printing */
        return _void_scalar_to_string(self, 1);
    }
    int legacy_print_mode = get_legacy_print_mode();
    if (legacy_print_mode == -1) {
        return NULL;
    }
    if (legacy_print_mode > 125) {
        return _void_to_hex(s->obval, s->descr->elsize, "np.void(b'", "\\x", "')");
    }
    else {
        return _void_to_hex(s->obval, s->descr->elsize, "void(b'", "\\x", "')");
    }
}

static PyObject *
voidtype_str(PyObject *self)
{
    PyVoidScalarObject *s = (PyVoidScalarObject*) self;
    if (PyDataType_HASFIELDS(s->descr)) {
        return _void_scalar_to_string(self, 0);
    }
    return _void_to_hex(s->obval, s->descr->elsize, "b'", "\\x", "'");
}

static PyObject *
datetimetype_repr(PyObject *self)
{
    PyDatetimeScalarObject *scal;
    npy_datetimestruct dts;
    PyObject *ret;
    char iso[NPY_DATETIME_MAX_ISO8601_STRLEN];
    NPY_DATETIMEUNIT unit;

    if (!PyArray_IsScalar(self, Datetime)) {
        PyErr_SetString(PyExc_RuntimeError,
                "Called NumPy datetime repr on a non-datetime type");
        return NULL;
    }

    scal = (PyDatetimeScalarObject *)self;

    if (NpyDatetime_ConvertDatetime64ToDatetimeStruct(&scal->obmeta,
                scal->obval, &dts) < 0) {
        return NULL;
    }

    unit = scal->obmeta.base;
    if (NpyDatetime_MakeISO8601Datetime(&dts, iso, sizeof(iso), 0, 0,
                            unit, -1, NPY_SAFE_CASTING) < 0) {
        return NULL;
    }

    /*
     * For straight units or generic units, the unit will be deduced
     * from the string, so it's not necessary to specify it.
     */
    if ((scal->obmeta.num == 1 && scal->obmeta.base != NPY_FR_h) ||
            scal->obmeta.base == NPY_FR_GENERIC) {
        int legacy_print_mode = get_legacy_print_mode();
        if (legacy_print_mode == -1) {
            return NULL;
        }
        if (legacy_print_mode > 125) {
            ret = PyUnicode_FromFormat("np.datetime64('%s')", iso);
        }
        else {
            ret = PyUnicode_FromFormat("numpy.datetime64('%s')", iso);
        }
    }
    else {
        PyObject *meta = metastr_to_unicode(&scal->obmeta, 1);
        if (meta == NULL) {
            return NULL;
        }
        int legacy_print_mode = get_legacy_print_mode();
        if (legacy_print_mode == -1) {
            return NULL;
        }
        if (legacy_print_mode > 125) {
            ret = PyUnicode_FromFormat("np.datetime64('%s','%S')", iso, meta);
        }
        else {
            ret = PyUnicode_FromFormat("numpy.datetime64('%s','%S')", iso, meta);
        }
        Py_DECREF(meta);
    }

    return ret;
}

static PyObject *
timedeltatype_repr(PyObject *self)
{
    PyTimedeltaScalarObject *scal;
    PyObject *val, *ret;

    if (!PyArray_IsScalar(self, Timedelta)) {
        PyErr_SetString(PyExc_RuntimeError,
                "Called NumPy timedelta repr on a non-datetime type");
        return NULL;
    }

    scal = (PyTimedeltaScalarObject *)self;

    /* The value */
    if (scal->obval == NPY_DATETIME_NAT) {
        val = PyUnicode_FromString("'NaT'");
    }
    else {
         /* Can't use "%lld" if HAVE_LONG_LONG is not defined */
#if defined(HAVE_LONG_LONG)
        val = PyUnicode_FromFormat("%lld", (long long)scal->obval);
#else
        val = PyUnicode_FromFormat("%ld", (long)scal->obval);
#endif
    }
    if (val == NULL) {
        return NULL;
    }

    /* The metadata unit */
    if (scal->obmeta.base == NPY_FR_GENERIC) {
        int legacy_print_mode = get_legacy_print_mode();
        if (legacy_print_mode == -1) {
            return NULL;
        }
        if (legacy_print_mode > 125) {
            ret = PyUnicode_FromFormat("np.timedelta64(%S)", val);
        }
        else {
            ret = PyUnicode_FromFormat("numpy.timedelta64(%S)", val);
        }
    }
    else {
        PyObject *meta = metastr_to_unicode(&scal->obmeta, 1);
        if (meta == NULL) {
            Py_DECREF(val);
            return NULL;
        }
        int legacy_print_mode = get_legacy_print_mode();
        if (legacy_print_mode == -1) {
            return NULL;
        }
        if (legacy_print_mode > 125) {
            ret = PyUnicode_FromFormat("np.timedelta64(%S,'%S')", val, meta);
        }
        else {
            ret = PyUnicode_FromFormat("numpy.timedelta64(%S,'%S')", val, meta);
        }
        Py_DECREF(meta);
    }
    Py_DECREF(val);

    return ret;
}

static PyObject *
datetimetype_str(PyObject *self)
{
    PyDatetimeScalarObject *scal;
    npy_datetimestruct dts;
    char iso[NPY_DATETIME_MAX_ISO8601_STRLEN];
    NPY_DATETIMEUNIT unit;

    if (!PyArray_IsScalar(self, Datetime)) {
        PyErr_SetString(PyExc_RuntimeError,
                "Called NumPy datetime str on a non-datetime type");
        return NULL;
    }

    scal = (PyDatetimeScalarObject *)self;

    if (NpyDatetime_ConvertDatetime64ToDatetimeStruct(&scal->obmeta, scal->obval,
                                                            &dts) < 0) {
        return NULL;
    }

    unit = scal->obmeta.base;
    if (NpyDatetime_MakeISO8601Datetime(&dts, iso, sizeof(iso), 0, 0,
                            unit, -1, NPY_SAFE_CASTING) < 0) {
        return NULL;
    }

    return PyUnicode_FromString(iso);
}

static char *_datetime_verbose_strings[NPY_DATETIME_NUMUNITS] = {
    "years",
    "months",
    "weeks",
    "<invalid>",
    "days",
    "hours",
    "minutes",
    "seconds",
    "milliseconds",
    "microseconds",
    "nanoseconds",
    "picoseconds",
    "femtoseconds",
    "attoseconds",
    "generic time units"
};

static PyObject *
timedeltatype_str(PyObject *self)
{
    PyTimedeltaScalarObject *scal;
    PyObject *ret;
    char *basestr = "invalid";

    if (!PyArray_IsScalar(self, Timedelta)) {
        PyErr_SetString(PyExc_RuntimeError,
                "Called NumPy timedelta str on a non-datetime type");
        return NULL;
    }

    scal = (PyTimedeltaScalarObject *)self;

    if (scal->obmeta.base >= 0 && scal->obmeta.base < NPY_DATETIME_NUMUNITS) {
        basestr = _datetime_verbose_strings[scal->obmeta.base];
    }
    else {
        PyErr_SetString(PyExc_RuntimeError,
                "NumPy datetime metadata is corrupted");
        return NULL;
    }

    if (scal->obval == NPY_DATETIME_NAT) {
        ret = PyUnicode_FromString("NaT");
    }
    else {
        /*
         * Can't use "%lld" if HAVE_LONG_LONG is not defined
         */
#if defined(HAVE_LONG_LONG)
        ret = PyUnicode_FromFormat("%lld %s",
            (long long)(scal->obval * scal->obmeta.num), basestr);
#else
        ret = PyUnicode_FromFormat("%ld %s",
            (long)(scal->obval * scal->obmeta.num), basestr);
#endif
    }

    return ret;
}

/*
 * float type str and repr
 *
 * These functions will return NULL if PyString creation fails.
 */


/*
 *               *** BEGIN LEGACY PRINTING MODE CODE ***
 *
 * This code is legacy code needed to reproduce the printing behavior of
 * scalars in numpy 1.13. One day we hope to remove it.
 */


#define HALFPREC_REPR 5
#define HALFPREC_STR 5
#define FLOATPREC_REPR 8
#define FLOATPREC_STR 6
#define DOUBLEPREC_REPR 17
#define DOUBLEPREC_STR 12
#if NPY_SIZEOF_LONGDOUBLE == NPY_SIZEOF_DOUBLE
#define LONGDOUBLEPREC_REPR DOUBLEPREC_REPR
#define LONGDOUBLEPREC_STR DOUBLEPREC_STR
#else /* More than probably needed on Intel FP */
#define LONGDOUBLEPREC_REPR 20
#define LONGDOUBLEPREC_STR 12
#endif

/**begin repeat
 * #kind = str, repr#
 * #KIND = STR, REPR#
 */

/**begin repeat1
 * #name = cfloat, cdouble, clongdouble#
 * #NAME = FLOAT, DOUBLE, LONGDOUBLE#
 * #TYPE = CFLOAT, CDOUBLE, CLONGDOUBLE#
 * #type = npy_cfloat, npy_cdouble, npy_clongdouble#
 * #t = f, , l#
 * #suff = f, d, l#
 */

#define _FMT1 "%%.%i" NPY_@NAME@_FMT
#define _FMT2 "%%+.%i" NPY_@NAME@_FMT

static PyObject*
legacy_@name@_format@kind@(@type@ val)
{
    /* XXX: Find a correct size here for format string */
    char format[64], buf[100], *res;

    /*
     * Ideally, we should handle this nan/inf stuff in NumpyOS_ascii_format*
     */
    if (npy_creal@t@(val) == 0.0 && npy_signbit(npy_creal@t@(val)) == 0) {
        PyOS_snprintf(format, sizeof(format), _FMT1, @NAME@PREC_@KIND@);
        res = NumPyOS_ascii_format@suff@(buf, sizeof(buf) - 1, format, npy_cimag@t@(val), 0);
        if (res == NULL) {
            PyErr_SetString(PyExc_RuntimeError, "Error while formatting");
            return NULL;
        }
        if (!npy_isfinite(npy_cimag@t@(val))) {
            strncat(buf, "*", sizeof(buf) - strlen(buf) - 1);
        }
        strncat(buf, "j", sizeof(buf) - strlen(buf) - 1);
    }
    else {
        char re[64], im[64];

        if (npy_isfinite(npy_creal@t@(val))) {
            PyOS_snprintf(format, sizeof(format), _FMT1, @NAME@PREC_@KIND@);
            res = NumPyOS_ascii_format@suff@(re, sizeof(re), format,
                                             npy_creal@t@(val), 0);
            if (res == NULL) {
                PyErr_SetString(PyExc_RuntimeError, "Error while formatting");
                return NULL;
            }
        }
        else {
            if (npy_isnan(npy_creal@t@(val))) {
                strcpy(re, "nan");
            }
            else if (npy_creal@t@(val) > 0){
                strcpy(re, "inf");
            }
            else {
                strcpy(re, "-inf");
            }
        }


        if (npy_isfinite(npy_cimag@t@(val))) {
            PyOS_snprintf(format, sizeof(format), _FMT2, @NAME@PREC_@KIND@);
            res = NumPyOS_ascii_format@suff@(im, sizeof(im), format,
                                             npy_cimag@t@(val), 0);
            if (res == NULL) {
                PyErr_SetString(PyExc_RuntimeError, "Error while formatting");
                return NULL;
            }
        }
        else {
            if (npy_isnan(npy_cimag@t@(val))) {
                strcpy(im, "+nan");
            }
            else if (npy_cimag@t@(val) > 0){
                strcpy(im, "+inf");
            }
            else {
                strcpy(im, "-inf");
            }
            if (!npy_isfinite(npy_cimag@t@(val))) {
                strncat(im, "*", sizeof(im) - strlen(im) - 1);
            }
        }
        PyOS_snprintf(buf, sizeof(buf), "(%s%sj)", re, im);
    }

    return PyUnicode_FromString(buf);
}

#undef _FMT1
#undef _FMT2

/**end repeat1**/

/**begin repeat1
 * #name = float, double, longdouble#
 * #Name = Float, Double, LongDouble#
 * #NAME = FLOAT, DOUBLE, LONGDOUBLE#
 * #suff = f, d, l#
 */

#define _FMT1 "%%.%i" NPY_@NAME@_FMT

static PyObject *
legacy_@name@_format@kind@(npy_@name@ val){
    /* XXX: Find a correct size here for format string */
    char format[64], buf[100], *res;
    size_t i, cnt;

    PyOS_snprintf(format, sizeof(format), _FMT1, @NAME@PREC_@KIND@);
    res = NumPyOS_ascii_format@suff@(buf, sizeof(buf), format, val, 0);
    if (res == NULL) {
        PyErr_SetString(PyExc_RuntimeError, "Error while formatting");
        return NULL;
    }

    /* If nothing but digits after sign, append ".0" */
    cnt = strlen(buf);
    for (i = (buf[0] == '-') ? 1 : 0; i < cnt; ++i) {
        if (!isdigit(Py_CHARMASK(buf[i]))) {
            break;
        }
    }
    if (i == cnt && sizeof(buf) >= cnt + 3) {
        strcpy(&buf[cnt],".0");
    }

    return PyUnicode_FromString(buf);
}

#undef _FMT1

/**end repeat1**/

/**end repeat**/


/*
 *               *** END LEGACY PRINTING MODE CODE ***
 */


/**begin repeat
 * #kind = str, repr#
 */

#define IS_@kind@

/**begin repeat1
 * #name = float, double, longdouble#
 * #max_positional = 1.e6L, 1.e16L, 1.e16L#
 * #Name = Float, Double, LongDouble#
 * #NAME = FLOAT, DOUBLE, LONGDOUBLE#
 * #n = f, , l#
 * #repr_format = np.float32(%S), np.float64(%S), np.longdouble('%S')#
 * #crepr_imag_format = np.complex64(%Sj), np.complex128(%Sj),
 *                 np.clongdouble('%Sj')#
 * #crepr_format = np.complex64(%S%Sj), np.complex128(%S%Sj),
 *                 np.clongdouble('%S%Sj')#
 */

/* helper function choose scientific of fractional output, based on a cutoff */
static PyObject *
@name@type_@kind@_either(npy_@name@ val, TrimMode trim_pos, TrimMode trim_sci,
                         npy_bool sign)
{

    int legacy_print_mode = get_legacy_print_mode();
    if (legacy_print_mode == -1) {
        return NULL;
    }
    if (legacy_print_mode <= 113) {
        return legacy_@name@_format@kind@(val);
    }
    long double max_positional;
    if (legacy_print_mode <= 202) {
        max_positional = 1.e16L;
    }
    else {
        max_positional = @max_positional@;
    }

    int use_positional;
    if (npy_isnan(val) || val == 0) {
         use_positional = 1;
    }
    else {
        npy_@name@ absval = val < 0 ? -val : val;
        use_positional = absval < max_positional && absval >= 1.e-4L;
    }

    if (use_positional) {
        return format_@name@(val, 0, -1, sign, trim_pos, -1, -1, -1);
    }
    return format_@name@(val, 1, -1, sign, trim_sci, -1, -1, -1);
}

static PyObject *
@name@type_@kind@(PyObject *self)
{
    PyObject *string;
    string = @name@type_@kind@_either(
            PyArrayScalar_VAL(self, @Name@),
            TrimMode_LeaveOneZero, TrimMode_DptZeros, 0);

#ifdef IS_repr
    if (string == NULL) {
        return NULL;
    }
    int legacy_print_mode = get_legacy_print_mode();
    if (legacy_print_mode == -1) {
        return NULL;
    }
    if (legacy_print_mode > 125) {
        Py_SETREF(string, PyUnicode_FromFormat("@repr_format@", string));
    }
#endif  /* IS_repr */

    return string;
}

static PyObject *
c@name@type_@kind@(PyObject *self)
{
    PyObject *rstr, *istr;
    npy_c@name@ val = PyArrayScalar_VAL(self, C@Name@);
    TrimMode trim = TrimMode_DptZeros;

    int legacy_print_mode = get_legacy_print_mode();
    if (legacy_print_mode == -1) {
        return NULL;
    }
    if (legacy_print_mode <= 113) {
        return legacy_c@name@_format@kind@(val);
    }

    if (npy_creal@n@(val) == 0.0 && npy_signbit(npy_creal@n@(val)) == 0) {
        istr = @name@type_@kind@_either(npy_cimag@n@(val), trim, trim, 0);
        if (istr == NULL) {
            return NULL;
        }
        PyObject *ret;
#ifdef IS_str
        ret = PyUnicode_FromFormat("%Sj", istr);
#else  /* IS_repr */
        int legacy_print_mode = get_legacy_print_mode();
        if (legacy_print_mode == -1) {
            return NULL;
        }
        if (legacy_print_mode <= 125) {
            ret = PyUnicode_FromFormat("%Sj", istr);
        }
        else {
            ret = PyUnicode_FromFormat("@crepr_imag_format@", istr);
        }
#endif  /* IS_repr */
        Py_DECREF(istr);
        return ret;
    }

    if (npy_isfinite(npy_creal@n@(val))) {
        rstr = @name@type_@kind@_either(npy_creal@n@(val), trim, trim, 0);
    }
    else if (npy_isnan(npy_creal@n@(val))) {
        rstr = PyUnicode_FromString("nan");
    }
    else if (npy_creal@n@(val) > 0){
        rstr = PyUnicode_FromString("inf");
    }
    else {
        rstr = PyUnicode_FromString("-inf");
    }
    if (rstr == NULL) {
        return NULL;
    }

    if (npy_isfinite(npy_cimag@n@(val))) {
        istr = @name@type_@kind@_either(npy_cimag@n@(val), trim, trim, 1);
    }
    else if (npy_isnan(npy_cimag@n@(val))) {
        istr = PyUnicode_FromString("+nan");
    }
    else if (npy_cimag@n@(val) > 0){
        istr = PyUnicode_FromString("+inf");
    }
    else {
        istr = PyUnicode_FromString("-inf");
    }
    if (istr == NULL) {
        Py_DECREF(rstr);
        return NULL;
    }

    PyObject *string;
#ifdef IS_str
    string = PyUnicode_FromFormat("(%S%Sj)", rstr, istr);
#else  /* IS_repr */
    legacy_print_mode = get_legacy_print_mode();
    if (legacy_print_mode == -1) {
        return NULL;
    }
    if (legacy_print_mode > 125) {
        string = PyUnicode_FromFormat("@crepr_format@", rstr, istr);
    }
    else {
        string = PyUnicode_FromFormat("(%S%Sj)", rstr, istr);
    }
#endif  /* IS_repr */

    Py_DECREF(rstr);
    Py_DECREF(istr);
    return string;
}

#undef PREC

/**end repeat1**/


static PyObject *
halftype_@kind@(PyObject *self)
{
    npy_half val = PyArrayScalar_VAL(self, Half);
    float floatval = npy_half_to_float(val);
    float absval;

    int legacy_print_mode = get_legacy_print_mode();
    if (legacy_print_mode == -1) {
        return NULL;
    }
    if (legacy_print_mode <= 113) {
        return legacy_float_format@kind@(floatval);
    }
    long double max_positional;
    if (legacy_print_mode <= 202) {
        max_positional = 1.e16L;
    }
    else {
        max_positional = 1.e3L;
    }

    absval = floatval < 0 ? -floatval : floatval;

    PyObject *string;
    if (absval == 0 || (absval < max_positional && absval >= 1.e-4) ) {
        string = format_half(val, 0, -1, 0, TrimMode_LeaveOneZero, -1, -1, -1);
    }
    else {
        string = format_half(val, 1, -1, 0, TrimMode_DptZeros, -1, -1, -1);
    }
#ifdef IS_str
    return string;
#else
    legacy_print_mode = get_legacy_print_mode();
    if (legacy_print_mode == -1) {
        return NULL;
    }
    if (string == NULL || legacy_print_mode <= 125) {
        return string;
    }
    PyObject *res = PyUnicode_FromFormat("np.float16(%S)", string);
    Py_DECREF(string);
    return res;
#endif
}

#undef IS_@kind@
/**end repeat**/

static PyObject *
longdoubletype_float(PyObject *self)
{
    npy_longdouble val = PyArrayScalar_VAL(self, LongDouble);
    return PyFloat_FromDouble((double) val);
}

static PyObject *
longdoubletype_long(PyObject *self)
{
    npy_longdouble val = PyArrayScalar_VAL(self, LongDouble);
    return npy_longdouble_to_PyLong(val);
}

static PyObject *
clongdoubletype_float(PyObject *self)
{
    npy_longdouble val = npy_creall(PyArrayScalar_VAL(self, CLongDouble));
    return PyFloat_FromDouble((double) val);
}

static PyObject *
clongdoubletype_long(PyObject *self)
{
    npy_longdouble val = npy_creall(PyArrayScalar_VAL(self, CLongDouble));
    return npy_longdouble_to_PyLong(val);
}

static PyNumberMethods gentype_as_number = {
    .nb_add = (binaryfunc)gentype_add,
    .nb_subtract = (binaryfunc)gentype_subtract,
    .nb_multiply = (binaryfunc)gentype_multiply,
    .nb_remainder = (binaryfunc)gentype_remainder,
    .nb_divmod = (binaryfunc)gentype_divmod,
    .nb_power = (ternaryfunc)gentype_power,
    .nb_negative = (unaryfunc)gentype_negative,
    .nb_positive = (unaryfunc)gentype_positive,
    .nb_absolute = (unaryfunc)gentype_absolute,
    .nb_bool = (inquiry)gentype_nonzero_number,
    .nb_invert = (unaryfunc)gentype_invert,
    .nb_lshift = (binaryfunc)gentype_lshift,
    .nb_rshift = (binaryfunc)gentype_rshift,
    .nb_and = (binaryfunc)gentype_and,
    .nb_xor = (binaryfunc)gentype_xor,
    .nb_or = (binaryfunc)gentype_or,
    .nb_int = (unaryfunc)gentype_int,
    .nb_float = (unaryfunc)gentype_float,
    .nb_floor_divide = (binaryfunc)gentype_floor_divide,
    .nb_true_divide = (binaryfunc)gentype_true_divide,
};


static PyObject *
gentype_richcompare(PyObject *self, PyObject *other, int cmp_op)
{
    /*
     * If the other object is None, False is always right. This avoids
     * the array None comparison, at least until deprecation it is fixed.
     * After that, this may be removed and numpy false would be returned.
     *
     * NOTE: np.equal(NaT, None) evaluates to TRUE! This is an
     *       an inconsistency, which may has to be considered
     *       when the deprecation is finished.
     */
    if (other == Py_None) {
        if (cmp_op == Py_EQ) {
            Py_RETURN_FALSE;
        }
        if (cmp_op == Py_NE) {
            Py_RETURN_TRUE;
        }
    }

   RICHCMP_GIVE_UP_IF_NEEDED(self, other);

    PyObject *self_op;
    PyObject *other_op;
    if (find_binary_operation_path(self, other, &self_op, &other_op) < 0) {
        return NULL;
    }

    /* We can always just call RichCompare again */
    if (other_op != NULL) {
        /* If we use richcompare again, need to ensure that one op is array */
        self_op = PyArray_FromScalar(self, NULL);
        if (self_op == NULL) {
            Py_DECREF(other_op);
            return NULL;
        }
        PyObject *res = PyObject_RichCompare(self_op, other_op, cmp_op);
        Py_DECREF(self_op);
        Py_DECREF(other_op);
        return res;
    }
    else if (self_op != NULL) {
        /* Try again, since other is an object scalar and this one mutated */
        PyObject *res = PyObject_RichCompare(self_op, other, cmp_op);
        Py_DECREF(self_op);
        return res;
    }
    else {
        /* Comparison with arbitrary objects cannot be defined. */
        Py_RETURN_NOTIMPLEMENTED;
    }
}

static PyObject *
gentype_ndim_get(PyObject *NPY_UNUSED(self), void *NPY_UNUSED(ignored))
{
    return PyLong_FromLong(0);
}

static PyObject *
gentype_flags_get(PyObject *NPY_UNUSED(self), void *NPY_UNUSED(ignored))
{
    return PyArray_NewFlagsObject(NULL);
}

static PyObject *
voidtype_flags_get(PyVoidScalarObject *self, void *NPY_UNUSED(ignored))
{
    PyObject *flagobj;
    flagobj = PyArrayFlags_Type.tp_alloc(&PyArrayFlags_Type, 0);
    if (flagobj == NULL) {
        return NULL;
    }
    ((PyArrayFlagsObject *)flagobj)->arr = NULL;
    ((PyArrayFlagsObject *)flagobj)->flags = self->flags;
    return flagobj;
}

static PyObject *
voidtype_dtypedescr_get(PyVoidScalarObject *self, void *NPY_UNUSED(ignored))
{
    Py_INCREF(self->descr);
    return (PyObject *)self->descr;
}


static PyObject *
inttype_numerator_get(PyObject *self, void *NPY_UNUSED(ignored))
{
    Py_INCREF(self);
    return self;
}


static PyObject *
inttype_denominator_get(PyObject *self, void *NPY_UNUSED(ignored))
{
    return PyLong_FromLong(1);
}


static PyObject *
gentype_data_get(PyObject *self, void *NPY_UNUSED(ignored))
{
    return PyMemoryView_FromObject(self);
}


static PyObject *
gentype_itemsize_get(PyObject *self, void *NPY_UNUSED(ignored))
{
    PyArray_Descr *typecode;
    PyObject *ret;
    int elsize;

    typecode = PyArray_DescrFromScalar(self);
    elsize = typecode->elsize;
    ret = PyLong_FromLong((long) elsize);
    Py_DECREF(typecode);
    return ret;
}

static PyObject *
gentype_size_get(PyObject *NPY_UNUSED(self), void *NPY_UNUSED(ignored))
{
    return PyLong_FromLong(1);
}

static PyObject *
gentype_sizeof(PyObject *self, PyObject *NPY_UNUSED(args))
{
    Py_ssize_t nbytes;
    PyObject * isz = gentype_itemsize_get(self, NULL);
    if (isz == NULL) {
        return NULL;
    }
    nbytes = PyLong_AsLong(isz) + Py_TYPE(self)->tp_basicsize +
        Py_SIZE(self) * Py_TYPE(self)->tp_itemsize;
    Py_DECREF(isz);
    return PyLong_FromSsize_t(nbytes);
}

NPY_NO_EXPORT void
gentype_struct_free(PyObject *ptr)
{
    PyArrayInterface *arrif = (PyArrayInterface*)PyCapsule_GetPointer(ptr, NULL);
    if (arrif == NULL) {
        PyErr_WriteUnraisable(ptr);
        return;
    }
    PyObject *context = (PyObject *)PyCapsule_GetContext(ptr);
    if (context == NULL && PyErr_Occurred()) {
        PyErr_WriteUnraisable(ptr);
    }
    Py_XDECREF(context);
    Py_XDECREF(arrif->descr);
    PyArray_free(arrif->shape);
    PyArray_free(arrif);
}

static PyObject *
gentype_struct_get(PyObject *self, void *NPY_UNUSED(ignored))
{
    PyArrayObject *arr;
    PyArrayInterface *inter;
    PyObject *ret;

    arr = (PyArrayObject *)PyArray_FromScalar(self, NULL);
    inter = (PyArrayInterface *)PyArray_malloc(sizeof(PyArrayInterface));
    inter->two = 2;
    inter->nd = 0;
    inter->flags = PyArray_FLAGS(arr);
    inter->flags &= ~(NPY_ARRAY_WRITEBACKIFCOPY | NPY_ARRAY_OWNDATA);
    inter->flags |= NPY_ARRAY_NOTSWAPPED;
    inter->typekind = PyArray_DESCR(arr)->kind;
    inter->itemsize = PyArray_ITEMSIZE(arr);
    inter->strides = NULL;
    inter->shape = NULL;
    inter->data = PyArray_DATA(arr);
    inter->descr = NULL;

    ret = NpyCapsule_FromVoidPtrAndDesc(inter, arr, gentype_struct_free);
    return ret;
}

static PyObject *
gentype_priority_get(PyObject *NPY_UNUSED(self), void *NPY_UNUSED(ignored))
{
    return PyFloat_FromDouble(NPY_SCALAR_PRIORITY);
}

static PyObject *
gentype_shape_get(PyObject *NPY_UNUSED(self), void *NPY_UNUSED(ignored))
{
    return PyTuple_New(0);
}


static PyObject *
gentype_interface_get(PyObject *self, void *NPY_UNUSED(ignored))
{
    PyArrayObject *arr;
    PyObject *inter;

    arr = (PyArrayObject *)PyArray_FromScalar(self, NULL);
    if (arr == NULL) {
        return NULL;
    }
    inter = PyObject_GetAttrString((PyObject *)arr, "__array_interface__");
    if (inter != NULL) {
        PyDict_SetItemString(inter, "__ref", (PyObject *)arr);
    }
    Py_DECREF(arr);
    return inter;
}



static PyObject *
gentype_typedescr_get(PyObject *self, void *NPY_UNUSED(ignored))
{
    return (PyObject *)PyArray_DescrFromScalar(self);
}


static PyObject *
gentype_base_get(PyObject *NPY_UNUSED(self), void *NPY_UNUSED(ignored))
{
    Py_RETURN_NONE;
}

static PyObject *
voidtype_base_get(PyVoidScalarObject *self, void *NPY_UNUSED(ignored))
{
    if (self->base == NULL) {
        Py_RETURN_NONE;
    }
    else {
        Py_INCREF(self->base);
        return self->base;
    }
}


static PyArray_Descr *
_realdescr_fromcomplexscalar(PyObject *self, int *typenum)
{
    if (PyArray_IsScalar(self, CDouble)) {
        *typenum = NPY_CDOUBLE;
        return PyArray_DescrFromType(NPY_DOUBLE);
    }
    if (PyArray_IsScalar(self, CFloat)) {
        *typenum = NPY_CFLOAT;
        return PyArray_DescrFromType(NPY_FLOAT);
    }
    if (PyArray_IsScalar(self, CLongDouble)) {
        *typenum = NPY_CLONGDOUBLE;
        return PyArray_DescrFromType(NPY_LONGDOUBLE);
    }
    return NULL;
}

static PyObject *
gentype_real_get(PyObject *self, void *NPY_UNUSED(ignored))
{
    PyArray_Descr *typecode;
    PyObject *ret;
    int typenum;

    if (PyArray_IsScalar(self, ComplexFloating)) {
        void *ptr;
        typecode = _realdescr_fromcomplexscalar(self, &typenum);
        ptr = scalar_value(self, NULL);
        ret = PyArray_Scalar(ptr, typecode, NULL);
        Py_DECREF(typecode);
        return ret;
    }
    else if (PyArray_IsScalar(self, Object)) {
        PyObject *obj = PyArrayScalar_VAL(self, Object);
        ret = PyObject_GetAttrString(obj, "real");
        if (ret != NULL) {
            return ret;
        }
        PyErr_Clear();
    }
    Py_INCREF(self);
    return (PyObject *)self;
}

static PyObject *
gentype_imag_get(PyObject *self, void *NPY_UNUSED(ignored))
{
    PyArray_Descr *typecode=NULL;
    PyObject *ret;
    int typenum;

    if (PyArray_IsScalar(self, ComplexFloating)) {
        char *ptr;
        typecode = _realdescr_fromcomplexscalar(self, &typenum);
        ptr = (char *)scalar_value(self, NULL);
        ret = PyArray_Scalar(ptr + typecode->elsize, typecode, NULL);
    }
    else if (PyArray_IsScalar(self, Object)) {
        PyObject *obj = PyArrayScalar_VAL(self, Object);
        PyArray_Descr *newtype;
        ret = PyObject_GetAttrString(obj, "imag");
        if (ret == NULL) {
            PyErr_Clear();
            obj = PyLong_FromLong(0);
            newtype = PyArray_DescrFromType(NPY_OBJECT);
            ret = PyArray_Scalar((char *)&obj, newtype, NULL);
            Py_DECREF(newtype);
            Py_DECREF(obj);
        }
    }
    else {
        char *temp;
        int elsize;
        typecode = PyArray_DescrFromScalar(self);
        elsize = typecode->elsize;
        temp = npy_alloc_cache_zero(1, elsize);
        ret = PyArray_Scalar(temp, typecode, NULL);
        npy_free_cache(temp, elsize);
    }

    Py_XDECREF(typecode);
    return ret;
}

static PyObject *
gentype_flat_get(PyObject *self, void *NPY_UNUSED(ignored))
{
    PyObject *ret, *arr;

    arr = PyArray_FromScalar(self, NULL);
    if (arr == NULL) {
        return NULL;
    }
    ret = PyArray_IterNew(arr);
    Py_DECREF(arr);
    return ret;
}


static PyObject *
gentype_transpose_get(PyObject *self, void *NPY_UNUSED(ignored))
{
    Py_INCREF(self);
    return self;
}

static PyObject *
gentype_newbyteorder(PyObject *NPY_UNUSED(self), void *NPY_UNUSED(ignored))
{
    PyErr_SetString(PyExc_AttributeError, 
                    "`newbyteorder` was removed from scalar types in NumPy 2.0. "
                    "Use `sc.view(sc.dtype.newbyteorder(order))` instead.");
    return NULL;
}

static PyObject *
gentype_itemset(PyObject *NPY_UNUSED(self), void *NPY_UNUSED(ignored))
{
    PyErr_SetString(PyExc_AttributeError, 
                    "`itemset` was removed from scalar types in NumPy 2.0 "
                    "because scalars are immutable.");
    return NULL;
}

static PyObject *
gentype_ptp(PyObject *NPY_UNUSED(self), void *NPY_UNUSED(ignored))
{
    PyErr_SetString(PyExc_AttributeError, 
                    "`ptp` was removed from scalar types in NumPy 2.0. "
                    "For a scalar, the range of values always equals 0.");
    return NULL;
}


static PyGetSetDef gentype_getsets[] = {
    {"ndim",
        (getter)gentype_ndim_get,
        (setter) 0, NULL, NULL},
    {"flags",
        (getter)gentype_flags_get,
        (setter)0, NULL, NULL},
    {"shape",
        (getter)gentype_shape_get,
        (setter)0, NULL, NULL},
    {"strides",
        (getter)gentype_shape_get,
        (setter) 0, NULL, NULL},
    {"data",
        (getter)gentype_data_get,
        (setter) 0, NULL, NULL},
    {"itemsize",
        (getter)gentype_itemsize_get,
        (setter)0, NULL, NULL},
    {"size",
        (getter)gentype_size_get,
        (setter)0, NULL, NULL},
    {"nbytes",
        (getter)gentype_itemsize_get,
        (setter)0, NULL, NULL},
    {"base",
        (getter)gentype_base_get,
        (setter)0, NULL, NULL},
    {"dtype",
        (getter)gentype_typedescr_get,
        NULL, NULL, NULL},
    {"real",
        (getter)gentype_real_get,
        (setter)0, NULL, NULL},
    {"imag",
        (getter)gentype_imag_get,
        (setter)0, NULL, NULL},
    {"flat",
        (getter)gentype_flat_get,
        (setter)0, NULL, NULL},
    {"T",
        (getter)gentype_transpose_get,
        (setter)0, NULL, NULL},
    {"newbyteorder",
        (getter)gentype_newbyteorder,
        (setter)0, NULL, NULL},
    {"itemset",
        (getter)gentype_itemset,
        (setter)0, NULL, NULL},
    {"ptp",
        (getter)gentype_ptp,
        (setter)0, NULL, NULL},
    {"device",
        (getter)array_device,
        (setter)0, NULL, NULL},
    {"__array_interface__",
        (getter)gentype_interface_get,
        NULL,
        "Array protocol: Python side",
        NULL},
    {"__array_struct__",
        (getter)gentype_struct_get,
        NULL,
        "Array protocol: struct",
        NULL},
    {"__array_priority__",
        (getter)gentype_priority_get,
        NULL,
        "Array priority.",
        NULL},
    {NULL, NULL, NULL, NULL, NULL}  /* Sentinel */
};


/* 0-dim array from scalar object */

static char doc_getarray[] = "sc.__array__(dtype) return 0-dim array from "
                             "scalar with specified dtype";

static PyObject *
gentype_getarray(PyObject *scalar, PyObject *args)
{
    PyArray_Descr *outcode=NULL;
    PyObject *ret;

    if (!PyArg_ParseTuple(args, "|O&:__array__", &PyArray_DescrConverter,
                &outcode)) {
        Py_XDECREF(outcode);
        return NULL;
    }
    ret = PyArray_FromScalar(scalar, outcode);
    return ret;
}

static char doc_sc_wraparray[] = "__array_wrap__ implementation for scalar types";

/*
 * __array_wrap__ for scalars, returning a scalar if possible.
 * (note that NumPy itself may well never call this itself).
 */
static PyObject *
gentype_wraparray(PyObject *NPY_UNUSED(scalar), PyObject *args)
{
    PyArrayObject *arr;
    PyObject *UNUSED = NULL;  /* for the context argument */
    /* return_scalar should be passed, but we're scalar, so return scalar by default */
    int return_scalar = 1;

    if (!PyArg_ParseTuple(args, "O!|OO&:__array_wrap__",
                &PyArray_Type, &arr, &UNUSED,
                &PyArray_OptionalBoolConverter, &return_scalar)) {
        return NULL;
    }

    Py_INCREF(arr);
    if (!return_scalar) {
        return (PyObject *)arr;
    }
    else {
        return PyArray_Return(arr);
    }
}

/*
 * These gentype_* functions do not take keyword arguments.
 * The proper flag is METH_VARARGS.
 */
/**begin repeat
 *
 * #name = tolist, item, __deepcopy__, __copy__,
 *         swapaxes, conj, conjugate, nonzero,
 *         fill, transpose#
 */
static PyObject *
gentype_@name@(PyObject *self, PyObject *args)
{
    return gentype_generic_method(self, args, NULL, "@name@");
}
/**end repeat**/

static PyObject *
gentype_byteswap(PyObject *self, PyObject *args, PyObject *kwds)
{
    npy_bool inplace = NPY_FALSE;
    static char *kwlist[] = {"inplace", NULL};

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O&:byteswap", kwlist,
                                     PyArray_BoolConverter, &inplace)) {
        return NULL;
    }
    if (inplace) {
        PyErr_SetString(PyExc_ValueError,
                "cannot byteswap a scalar in-place");
        return NULL;
    }
    else {
        /* get the data, copyswap it and pass it to a new Array scalar */
        char *data;
        PyArray_Descr *descr;
        PyObject *new;
        char *newmem;

        descr = PyArray_DescrFromScalar(self);
        data = (void *)scalar_value(self, descr);

        newmem = PyObject_Malloc(descr->elsize);
        if (newmem == NULL) {
            Py_DECREF(descr);
            return PyErr_NoMemory();
        }
        else {
            PyDataType_GetArrFuncs(descr)->copyswap(newmem, data, 1, NULL);
        }
        new = PyArray_Scalar(newmem, descr, NULL);
        PyObject_Free(newmem);
        Py_DECREF(descr);
        return new;
    }
}


/*
 * These gentype_* functions take keyword arguments.
 * The proper flag is METH_VARARGS | METH_KEYWORDS.
 */
/**begin repeat
 *
 * #name = take, getfield, put, repeat, tofile, mean, trace, diagonal, clip,
 *         std, var, sum, cumsum, prod, cumprod, compress, sort, argsort,
 *         round, argmax, argmin, max, min, any, all, astype, resize,
 *         reshape, choose, tostring, tobytes, copy, searchsorted, view,
 *         flatten, ravel, squeeze#
 */
static PyObject *
gentype_@name@(PyObject *self, PyObject *args, PyObject *kwds)
{
    return gentype_generic_method(self, args, kwds, "@name@");
}
/**end repeat**/


/**begin repeat
 * #name = integer, floating#
 */
static PyObject *
@name@type_dunder_round(PyObject *self, PyObject *args, PyObject *kwds)
{
    static char *kwlist[] = {"ndigits", NULL};
    PyObject *ndigits = Py_None;
    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O:__round__", kwlist, &ndigits)) {
        return NULL;
    }

    PyObject *tup;
    if (ndigits == Py_None) {
        tup = PyTuple_Pack(0);
    }
    else {
        tup = PyTuple_Pack(1, ndigits);
    }

    if (tup == NULL) {
        return NULL;
    }

    PyObject *obj = gentype_round(self, tup, NULL);
    Py_DECREF(tup);
    if (obj == NULL) {
        return NULL;
    }

    if (ndigits == Py_None) {
        PyObject *ret = PyNumber_Long(obj);
        Py_DECREF(obj);
        return ret;
    }

    return obj;
}
/**end repeat**/

static PyObject *
voidtype_getfield(PyVoidScalarObject *self, PyObject *args, PyObject *kwds)
{
    /* Use ndarray's getfield to obtain the field safely */
    return gentype_generic_method((PyObject *)self, args, kwds, "getfield");
}

static PyObject *
gentype_setfield(PyObject *NPY_UNUSED(self), PyObject *NPY_UNUSED(args),
                 PyObject *NPY_UNUSED(kwds))
{
    PyErr_SetString(PyExc_TypeError,
            "Can't set fields in a non-void array scalar.");
    return NULL;
}

static PyObject *
voidtype_setfield(PyVoidScalarObject *self, PyObject *args, PyObject *kwds)
{
    /*
     * We would like to use ndarray's setfield because it performs safety
     * checks on the field datatypes and because it broadcasts properly.
     * However, as a special case, void-scalar assignment broadcasts
     * differently from ndarrays when assigning to an object field: Assignment
     * to an ndarray object field broadcasts, but assignment to a void-scalar
     * object-field should not, in order to allow nested ndarrays.
     * These lines should then behave identically:
     *
     *     b = np.zeros(1, dtype=[('x', 'O')])
     *     b[0]['x'] = arange(3)  # uses voidtype_setfield
     *     b['x'][0] = arange(3)  # uses ndarray setitem
     *
     * Ndarray's setfield would try to broadcast the lhs. Instead we use
     * ndarray getfield to get the field safely, then setitem with an empty
     * tuple to set the value without broadcast. Note we also want subarrays to
     * be set properly, ie
     *
     *     a = np.zeros(1, dtype=[('x', 'i', 5)])
     *     a[0]['x'] = 1
     *
     * sets all values to 1. "getfield + setitem with empty tuple" takes
     * care of both object arrays and subarrays.
     */
    PyObject *getfield_args, *value, *arr, *meth, *arr_field, *emptytuple;

    value = PyTuple_GetItem(args, 0);
    if (value == NULL) {
        return NULL;
    }
    getfield_args = PyTuple_GetSlice(args, 1, 3);
    if (getfield_args == NULL) {
        return NULL;
    }

    /* 1. Convert to 0-d array and use getfield */
    arr = PyArray_FromScalar((PyObject*)self, NULL);
    if (arr == NULL) {
        Py_DECREF(getfield_args);
        return NULL;
    }
    meth = PyObject_GetAttrString(arr, "getfield");
    if (meth == NULL) {
        Py_DECREF(getfield_args);
        Py_DECREF(arr);
        return NULL;
    }
    if (kwds == NULL) {
        arr_field = PyObject_CallObject(meth, getfield_args);
    }
    else {
        arr_field = PyObject_Call(meth, getfield_args, kwds);
    }
    Py_DECREF(getfield_args);
    Py_DECREF(meth);
    Py_DECREF(arr);

    if(arr_field == NULL){
        return NULL;
    }

    /* 2. Assign the value using setitem with empty tuple. */
    emptytuple = PyTuple_New(0);
    if (PyObject_SetItem(arr_field, emptytuple, value) < 0) {
        Py_DECREF(arr_field);
        Py_DECREF(emptytuple);
        return NULL;
    }
    Py_DECREF(emptytuple);
    Py_DECREF(arr_field);

    Py_RETURN_NONE;
}


static PyObject *
gentype_reduce(PyObject *self, PyObject *NPY_UNUSED(args))
{
    PyObject *ret = NULL, *obj = NULL, *mod = NULL;
    Py_buffer view;
    const char *buffer;
    Py_ssize_t buflen;

    /* Return a tuple of (callable object, arguments) */
    ret = PyTuple_New(2);
    if (ret == NULL) {
        return NULL;
    }

    if (PyObject_GetBuffer(self, &view, PyBUF_SIMPLE) >= 0) {
        buffer = view.buf;
        buflen = view.len;
        /*
         * Both of the deprecated functions PyObject_AsWriteBuffer and
         * PyObject_AsReadBuffer that this code replaces release the buffer. It is
         * up to the object that supplies the buffer to guarantee that the buffer
         * sticks around after the release.
         */
        PyBuffer_Release(&view);
    }
    else {
        Py_DECREF(ret);
        return NULL;
    }

    mod = PyImport_ImportModule("numpy._core._multiarray_umath");
    if (mod == NULL) {
        Py_DECREF(ret);
        return NULL;
    }
    obj = PyObject_GetAttrString(mod, "scalar");
    Py_DECREF(mod);
    if (obj == NULL) {
        Py_DECREF(ret);
        return NULL;
    }
    PyTuple_SET_ITEM(ret, 0, obj);
    obj = PyObject_GetAttrString((PyObject *)self, "dtype");
    if (PyArray_IsScalar(self, Object)) {
        PyObject *val = PyArrayScalar_VAL(self, Object);
        PyObject *tup = Py_BuildValue("NO", obj, val);
        if (tup == NULL) {
            Py_DECREF(ret);
            return NULL;
        }
        PyTuple_SET_ITEM(ret, 1, tup);
    }
    else if (obj && PyDataType_FLAGCHK((PyArray_Descr *)obj, NPY_LIST_PICKLE)) {
        /* a structured dtype with an object in a field */
        PyArrayObject *arr = (PyArrayObject *)PyArray_FromScalar(self, NULL);
        if (arr == NULL) {
            Py_DECREF(ret);
            return NULL;
        }
        /* Use the whole array which handles structured void correctly */
        PyObject *tup = Py_BuildValue("NN", obj, arr);
        if (tup == NULL) {
            Py_DECREF(ret);
            return NULL;
        }
        PyTuple_SET_ITEM(ret, 1, tup);
    }
    else {
        mod = PyBytes_FromStringAndSize(buffer, buflen);
        if (mod == NULL) {
            Py_DECREF(ret);
            return NULL;
        }
        PyTuple_SET_ITEM(ret, 1,
                Py_BuildValue("NN", obj, mod));
    }
    return ret;
}

/* ignores everything */
static PyObject *
gentype_setstate(PyObject *NPY_UNUSED(self), PyObject *NPY_UNUSED(args))
{
    Py_RETURN_NONE;
}

static PyObject *
gentype_dump(PyObject *self, PyObject *args)
{
    PyObject *file = NULL;
    int ret;

    if (!PyArg_ParseTuple(args, "O:dump", &file)) {
        return NULL;
    }
    ret = PyArray_Dump(self, file, 2);
    if (ret < 0) {
        return NULL;
    }
    Py_RETURN_NONE;
}

static PyObject *
gentype_dumps(PyObject *self, PyObject *args)
{
    if (!PyArg_ParseTuple(args, "")) {
        return NULL;
    }
    return PyArray_Dumps(self, 2);
}


/* setting flags cannot be done for scalars */
static PyObject *
gentype_setflags(PyObject *NPY_UNUSED(self), PyObject *NPY_UNUSED(args),
        PyObject *NPY_UNUSED(kwds))
{
    Py_RETURN_NONE;
}

static PyObject *
numbertype_class_getitem_abc(PyObject *cls, PyObject *args)
{
    const Py_ssize_t args_len = PyTuple_Check(args) ? PyTuple_Size(args) : 1;
    int args_len_expected;

    /* complexfloating should take 2 parameters, all others take 1 */
    if (PyType_IsSubtype((PyTypeObject *)cls,
                         &PyComplexFloatingArrType_Type)) {
        args_len_expected = 2;
    }
    else {
        args_len_expected = 1;
    }

    if ((args_len > args_len_expected) || (args_len == 0)) {
        return PyErr_Format(PyExc_TypeError,
                            "Too %s arguments for %s",
                            args_len > args_len_expected ? "many" : "few",
                            ((PyTypeObject *)cls)->tp_name);
    }
    return Py_GenericAlias(cls, args);
}

/*
 * Use for concrete np.number subclasses, making them act as if they
 * were subtyped from e.g. np.signedinteger[object], thus lacking any
 * free subscription parameters. Requires python >= 3.9.
 */
static PyObject *
numbertype_class_getitem(PyObject *cls, PyObject *args)
{
    PyErr_Format(PyExc_TypeError,
                 "There are no type variables left in %s",
                 ((PyTypeObject *)cls)->tp_name);
    return NULL;
}

/*
 * casting complex numbers (that don't inherit from Python complex)
 * to Python complex
 */

/**begin repeat
 * #name = cfloat, clongdouble#
 * #Name = CFloat, CLongDouble#
 * #NAME = CFLOAT, CLONGDOUBLE#
 * #n = f, l#
 */
static PyObject *
@name@_complex(PyObject *self, PyObject *NPY_UNUSED(args),
               PyObject *NPY_UNUSED(kwds))
{
    return PyComplex_FromDoubles(npy_creal@n@(PyArrayScalar_VAL(self, @Name@)),
                                 npy_cimag@n@(PyArrayScalar_VAL(self, @Name@)));
}
/**end repeat**/

/**begin repeat
 *  #name = half, float, double, longdouble#
 *  #Name = Half, Float, Double, LongDouble#
 *  #is_half = 1,0,0,0#
 *  #c    = f, f, , l#
 *  #convert = PyLong_FromDouble, PyLong_FromDouble, PyLong_FromDouble,
 *             npy_longdouble_to_PyLong#
 *  #
 */
/* Heavily copied from the builtin float.as_integer_ratio */
static PyObject *
@name@_as_integer_ratio(PyObject *self, PyObject *NPY_UNUSED(args))
{
#if @is_half@
    npy_double val = npy_half_to_double(PyArrayScalar_VAL(self, @Name@));
    npy_double frac;
#else
    npy_@name@ val = PyArrayScalar_VAL(self, @Name@);
    npy_@name@ frac;
#endif
    int exponent;

    PyObject *py_exponent = NULL;
    PyObject *numerator = NULL;
    PyObject *denominator = NULL;
    PyObject *result_pair = NULL;
    PyNumberMethods *long_methods = PyLong_Type.tp_as_number;

    if (npy_isnan(val)) {
        PyErr_SetString(PyExc_ValueError,
                        "cannot convert NaN to integer ratio");
        return NULL;
    }
    if (!npy_isfinite(val)) {
        PyErr_SetString(PyExc_OverflowError,
                        "cannot convert Infinity to integer ratio");
        return NULL;
    }

    frac = npy_frexp@c@(val, &exponent); /* val == frac * 2**exponent exactly */

    /* This relies on the floating point type being base 2 to converge */
    while (frac != npy_floor@c@(frac)) {
        frac *= 2.0;
        exponent--;
    }

    /* self == frac * 2**exponent exactly and frac is integral. */
    numerator = @convert@(frac);
    if (numerator == NULL)
        goto error;
    denominator = PyLong_FromLong(1);
    if (denominator == NULL)
        goto error;
    py_exponent = PyLong_FromLong(exponent < 0 ? -exponent : exponent);
    if (py_exponent == NULL)
        goto error;

    /* fold in 2**exponent */
    if (exponent > 0) {
        PyObject *temp = long_methods->nb_lshift(numerator, py_exponent);
        if (temp == NULL)
            goto error;
        Py_DECREF(numerator);
        numerator = temp;
    }
    else {
        PyObject *temp = long_methods->nb_lshift(denominator, py_exponent);
        if (temp == NULL)
            goto error;
        Py_DECREF(denominator);
        denominator = temp;
    }

    result_pair = PyTuple_Pack(2, numerator, denominator);

error:
    Py_XDECREF(py_exponent);
    Py_XDECREF(denominator);
    Py_XDECREF(numerator);
    return result_pair;
}
/**end repeat**/

/**begin repeat
 *  #name = half, float, double, longdouble#
 *  #Name = Half, Float, Double, LongDouble#
 *  #is_half = 1,0,0,0#
 *  #c    = f, f, , l#
 */
static PyObject *
@name@_is_integer(PyObject *self, PyObject *NPY_UNUSED(args))
{
#if @is_half@
    npy_double val = npy_half_to_double(PyArrayScalar_VAL(self, @Name@));
#else
    npy_@name@ val = PyArrayScalar_VAL(self, @Name@);
#endif
    PyObject *ret;

    if (npy_isnan(val)) {
        Py_RETURN_FALSE;
    }
    if (!npy_isfinite(val)) {
        Py_RETURN_FALSE;
    }

    ret = (npy_floor@c@(val) == val) ? Py_True : Py_False;
    Py_INCREF(ret);
    return ret;
}
/**end repeat**/

static PyObject *
integer_is_integer(PyObject *self, PyObject *NPY_UNUSED(args)) {
    Py_RETURN_TRUE;
}

/*
 * need to fill in doc-strings for these methods on import -- copy from
 * array docstrings
 */
static PyMethodDef gentype_methods[] = {
    {"tolist",
        (PyCFunction)gentype_tolist,
        METH_VARARGS, NULL},
    {"item",
        (PyCFunction)gentype_item,
        METH_VARARGS, NULL},
    {"tobytes",
        (PyCFunction)gentype_tobytes,
        METH_VARARGS | METH_KEYWORDS, NULL},
    {"tofile",
        (PyCFunction)gentype_tofile,
        METH_VARARGS | METH_KEYWORDS, NULL},
    {"tostring",
        (PyCFunction)gentype_tostring,
        METH_VARARGS | METH_KEYWORDS, NULL},
    {"byteswap",
        (PyCFunction)gentype_byteswap,
        METH_VARARGS | METH_KEYWORDS, NULL},
    {"astype",
        (PyCFunction)gentype_astype,
        METH_VARARGS | METH_KEYWORDS, NULL},
    {"getfield",
        (PyCFunction)gentype_getfield,
        METH_VARARGS | METH_KEYWORDS, NULL},
    {"setfield",
        (PyCFunction)gentype_setfield,
        METH_VARARGS | METH_KEYWORDS, NULL},
    {"copy",
        (PyCFunction)gentype_copy,
        METH_VARARGS | METH_KEYWORDS, NULL},
    {"resize",
        (PyCFunction)gentype_resize,
        METH_VARARGS | METH_KEYWORDS, NULL},
    {"__array__",
        (PyCFunction)gentype_getarray,
        METH_VARARGS, doc_getarray},
    {"__array_wrap__",
        (PyCFunction)gentype_wraparray,
        METH_VARARGS, doc_sc_wraparray},

    /* for the sys module */
    {"__sizeof__",
        (PyCFunction)gentype_sizeof,
        METH_NOARGS, NULL},

    /* for the copy module */
    {"__copy__",
        (PyCFunction)gentype___copy__,
        METH_VARARGS, NULL},
    {"__deepcopy__",
        (PyCFunction)gentype___deepcopy__,
        METH_VARARGS, NULL},

    {"__reduce__",
        (PyCFunction) gentype_reduce,
        METH_VARARGS, NULL},
    /* For consistency does nothing */
    {"__setstate__",
        (PyCFunction) gentype_setstate,
        METH_VARARGS, NULL},

    {"dumps",
        (PyCFunction) gentype_dumps,
        METH_VARARGS, NULL},
    {"dump",
        (PyCFunction) gentype_dump,
        METH_VARARGS, NULL},

    /* Methods for array */
    {"fill",
        (PyCFunction)gentype_fill,
        METH_VARARGS, NULL},
    {"transpose",
        (PyCFunction)gentype_transpose,
        METH_VARARGS, NULL},
    {"take",
        (PyCFunction)gentype_take,
        METH_VARARGS | METH_KEYWORDS, NULL},
    {"put",
        (PyCFunction)gentype_put,
        METH_VARARGS | METH_KEYWORDS, NULL},
    {"repeat",
        (PyCFunction)gentype_repeat,
        METH_VARARGS | METH_KEYWORDS, NULL},
    {"choose",
        (PyCFunction)gentype_choose,
        METH_VARARGS | METH_KEYWORDS, NULL},
    {"sort",
        (PyCFunction)gentype_sort,
        METH_VARARGS | METH_KEYWORDS, NULL},
    {"argsort",
        (PyCFunction)gentype_argsort,
        METH_VARARGS | METH_KEYWORDS, NULL},
    {"searchsorted",
        (PyCFunction)gentype_searchsorted,
        METH_VARARGS | METH_KEYWORDS, NULL},
    {"argmax",
        (PyCFunction)gentype_argmax,
        METH_VARARGS | METH_KEYWORDS, NULL},
    {"argmin",
        (PyCFunction)gentype_argmin,
        METH_VARARGS | METH_KEYWORDS, NULL},
    {"reshape",
        (PyCFunction)gentype_reshape,
        METH_VARARGS | METH_KEYWORDS, NULL},
    {"squeeze",
        (PyCFunction)gentype_squeeze,
        METH_VARARGS | METH_KEYWORDS, NULL},
    {"view",
        (PyCFunction)gentype_view,
        METH_VARARGS | METH_KEYWORDS, NULL},
    {"swapaxes",
        (PyCFunction)gentype_swapaxes,
        METH_VARARGS, NULL},
    {"max",
        (PyCFunction)gentype_max,
        METH_VARARGS | METH_KEYWORDS, NULL},
    {"min",
        (PyCFunction)gentype_min,
        METH_VARARGS | METH_KEYWORDS, NULL},
    {"mean",
        (PyCFunction)gentype_mean,
        METH_VARARGS | METH_KEYWORDS, NULL},
    {"trace",
        (PyCFunction)gentype_trace,
        METH_VARARGS | METH_KEYWORDS, NULL},
    {"diagonal",
        (PyCFunction)gentype_diagonal,
        METH_VARARGS | METH_KEYWORDS, NULL},
    {"clip",
        (PyCFunction)gentype_clip,
        METH_VARARGS | METH_KEYWORDS, NULL},
    {"conj",
        (PyCFunction)gentype_conj,
        METH_VARARGS, NULL},
    {"conjugate",
        (PyCFunction)gentype_conjugate,
        METH_VARARGS, NULL},
    {"nonzero",
        (PyCFunction)gentype_nonzero,
        METH_VARARGS, NULL},
    {"std",
        (PyCFunction)gentype_std,
        METH_VARARGS | METH_KEYWORDS, NULL},
    {"var",
        (PyCFunction)gentype_var,
        METH_VARARGS | METH_KEYWORDS, NULL},
    {"sum",
        (PyCFunction)gentype_sum,
        METH_VARARGS | METH_KEYWORDS, NULL},
    {"cumsum",
        (PyCFunction)gentype_cumsum,
        METH_VARARGS | METH_KEYWORDS, NULL},
    {"prod",
        (PyCFunction)gentype_prod,
        METH_VARARGS | METH_KEYWORDS, NULL},
    {"cumprod",
        (PyCFunction)gentype_cumprod,
        METH_VARARGS | METH_KEYWORDS, NULL},
    {"all",
        (PyCFunction)gentype_all,
        METH_VARARGS | METH_KEYWORDS, NULL},
    {"any",
        (PyCFunction)gentype_any,
        METH_VARARGS | METH_KEYWORDS, NULL},
    {"compress",
        (PyCFunction)gentype_compress,
        METH_VARARGS | METH_KEYWORDS, NULL},
    {"flatten",
        (PyCFunction)gentype_flatten,
        METH_VARARGS | METH_KEYWORDS, NULL},
    {"ravel",
        (PyCFunction)gentype_ravel,
        METH_VARARGS | METH_KEYWORDS, NULL},
    {"round",
        (PyCFunction)gentype_round,
        METH_VARARGS | METH_KEYWORDS, NULL},
    /* For the format function */
    {"__format__",
        gentype_format,
        METH_VARARGS,
        "NumPy array scalar formatter"},
    {"setflags",
        (PyCFunction)gentype_setflags,
        METH_VARARGS | METH_KEYWORDS, NULL},

    /* For Array API compatibility */
    {"__array_namespace__",
        (PyCFunction)array_array_namespace,
        METH_VARARGS | METH_KEYWORDS, NULL},
    {"to_device",
        (PyCFunction)array_to_device,
        METH_VARARGS | METH_KEYWORDS, NULL},

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


static PyGetSetDef voidtype_getsets[] = {
    {"flags",
        (getter)voidtype_flags_get,
        (setter)0,
        "integer value of flags",
        NULL},
    {"dtype",
        (getter)voidtype_dtypedescr_get,
        (setter)0,
        "dtype object",
        NULL},
    {"base",
        (getter)voidtype_base_get,
        (setter)0,
        "base object",
        NULL},
    {NULL, NULL, NULL, NULL, NULL}
};

static PyMethodDef voidtype_methods[] = {
    {"getfield",
        (PyCFunction)voidtype_getfield,
        METH_VARARGS | METH_KEYWORDS, NULL},
    {"setfield",
        (PyCFunction)voidtype_setfield,
        METH_VARARGS | METH_KEYWORDS, NULL},
    {NULL, NULL, 0, NULL}
};

static PyGetSetDef inttype_getsets[] = {
    {"numerator",
        (getter)inttype_numerator_get,
        (setter)0,
        "numerator of value (the value itself)",
        NULL},
    {"denominator",
        (getter)inttype_denominator_get,
        (setter)0,
        "denominator of value (1)",
        NULL},
    {NULL, NULL, NULL, NULL, NULL}
};

static PyMethodDef numbertype_methods[] = {
    /* for typing */
    {"__class_getitem__",
        (PyCFunction)numbertype_class_getitem_abc,
        METH_CLASS | METH_O, NULL},
    {NULL, NULL, 0, NULL}  /* sentinel */
};

/**begin repeat
 * #name = cfloat,clongdouble#
 */
static PyMethodDef @name@type_methods[] = {
    {"__complex__",
        (PyCFunction)@name@_complex,
        METH_VARARGS | METH_KEYWORDS, NULL},
    /* for typing */
    {"__class_getitem__",
        (PyCFunction)numbertype_class_getitem,
        METH_CLASS | METH_O, NULL},
    {NULL, NULL, 0, NULL}
};
/**end repeat**/

static PyMethodDef floatingtype_methods[] = {
    /* Hook for the round() builtin */
    {"__round__",
        (PyCFunction)floatingtype_dunder_round,
        METH_VARARGS | METH_KEYWORDS, NULL},
    {NULL, NULL, 0, NULL} /* sentinel */
};

static PyMethodDef integertype_methods[] = {
    /* Hook for the round() builtin */
    {"__round__",
        (PyCFunction)integertype_dunder_round,
        METH_VARARGS | METH_KEYWORDS, NULL},
    {"is_integer",
        (PyCFunction)integer_is_integer,
        METH_NOARGS, NULL},
    {NULL, NULL, 0, NULL} /* sentinel */
};

/**begin repeat
 * #name = half,float,double,longdouble#
 */
static PyMethodDef @name@type_methods[] = {
    {"as_integer_ratio",
        (PyCFunction)@name@_as_integer_ratio,
        METH_NOARGS, NULL},
    {"is_integer",
        (PyCFunction)@name@_is_integer,
        METH_NOARGS, NULL},
    /* for typing */
    {"__class_getitem__",
        (PyCFunction)numbertype_class_getitem,
        METH_CLASS | METH_O, NULL},
    {NULL, NULL, 0, NULL}
};
/**end repeat**/

/**begin repeat
 * #name = timedelta, cdouble#
 */
static PyMethodDef @name@type_methods[] = {
    /* for typing */
    {"__class_getitem__",
        (PyCFunction)numbertype_class_getitem,
        METH_CLASS | METH_O, NULL},
    {NULL, NULL, 0, NULL}
};
/**end repeat**/

/**begin repeat
 * #name = byte, ubyte, short, ushort, int, uint,
 *         long, ulong, longlong, ulonglong#
 */
static PyMethodDef @name@type_methods[] = {
    /* for typing */
    {"__class_getitem__",
        (PyCFunction)numbertype_class_getitem,
        METH_CLASS | METH_O, NULL},
    {"bit_count",
        (PyCFunction)npy_@name@_bit_count,
        METH_NOARGS, NULL},
    {NULL, NULL, 0, NULL} /* sentinel */
};
/**end repeat**/


/************* As_mapping functions for void array scalar ************/

static Py_ssize_t
voidtype_length(PyVoidScalarObject *self)
{
    if (!PyDataType_HASFIELDS(self->descr)) {
        return 0;
    }
    else {
        /* return the number of fields */
        return (Py_ssize_t) PyTuple_GET_SIZE(self->descr->names);
    }
}

static PyObject *
voidtype_subscript(PyVoidScalarObject *self, PyObject *ind);

static PyObject *
voidtype_item(PyVoidScalarObject *self, Py_ssize_t n)
{
    npy_intp m;
    PyObject *flist=NULL;

    if (!(PyDataType_HASFIELDS(self->descr))) {
        PyErr_SetString(PyExc_IndexError,
                "can't index void scalar without fields");
        return NULL;
    }
    flist = self->descr->names;
    m = PyTuple_GET_SIZE(flist);
    if (n < 0) {
        n += m;
    }
    if (n < 0 || n >= m) {
        PyErr_Format(PyExc_IndexError, "invalid index (%d)", (int) n);
        return NULL;
    }

    return voidtype_subscript(self, PyTuple_GetItem(flist, n));
}

/* get field by name or number */
static PyObject *
voidtype_subscript(PyVoidScalarObject *self, PyObject *ind)
{
    npy_intp n;
    PyObject *ret, *res;

    /* structured voids will accept an integer index */
    if (PyDataType_HASFIELDS(self->descr)) {
        n = PyArray_PyIntAsIntp(ind);
        if (!error_converting(n)) {
            return voidtype_item(self, (Py_ssize_t)n);
        }
        PyErr_Clear();
    }

    res = PyArray_FromScalar((PyObject*)self, NULL);

    /* ellipsis should return 0d array */
    if(ind == Py_Ellipsis){
        return res;
    }

    /*
     * other cases (field names, empty tuple) will return either
     * scalar or non-0d array. Compute this using ndarray subscript.
     */
    ret = array_subscript((PyArrayObject *)res, ind);
    Py_DECREF(res);
    return PyArray_Return((PyArrayObject*)ret);
}

static int
voidtype_ass_subscript(PyVoidScalarObject *self, PyObject *ind, PyObject *val);

static int
voidtype_ass_item(PyVoidScalarObject *self, Py_ssize_t n, PyObject *val)
{
    npy_intp m;
    PyObject *flist=NULL;

    if (!(PyDataType_HASFIELDS(self->descr))) {
        PyErr_SetString(PyExc_IndexError,
                "can't index void scalar without fields");
        return -1;
    }

    flist = self->descr->names;
    m = PyTuple_GET_SIZE(flist);
    if (n < 0) {
        n += m;
    }
    if (n < 0 || n >= m) {
        PyErr_Format(PyExc_IndexError, "invalid index (%d)", (int) n);
        return -1;
    }

    return voidtype_ass_subscript(self, PyTuple_GetItem(flist, n), val);
}

static int
voidtype_ass_subscript(PyVoidScalarObject *self, PyObject *ind, PyObject *val)
{
    npy_intp n;
    char *msg = "invalid index";
    PyObject *args;

    if (!PyDataType_HASFIELDS(self->descr)) {
        PyErr_SetString(PyExc_IndexError,
                "can't index void scalar without fields");
        return -1;
    }

    if (!val) {
        PyErr_SetString(PyExc_ValueError,
                "cannot delete scalar field");
        return -1;
    }

    if (PyUnicode_Check(ind)) {
        /*
         * Much like in voidtype_setfield, we cannot simply use ndarray's
         * __setitem__ since assignment to void scalars should not broadcast
         * the lhs. Instead we get a view through __getitem__ and then assign
         * the value using setitem with an empty tuple (which treats both
         * object arrays and subarrays properly).
         *
         * Also we do not want to use voidtype_setfield here, since we do
         * not need to do the (slow) view safety checks, since we already
         * know the dtype/offset are safe.
         */

        PyObject *arr, *arr_field, *meth, *emptytuple;

        /* 1. Convert to 0-d array and use getitem */
        arr = PyArray_FromScalar((PyObject*)self, NULL);
        if (arr == NULL) {
            return -1;
        }
        meth = PyObject_GetAttrString(arr, "__getitem__");
        if (meth == NULL) {
            Py_DECREF(arr);
            return -1;
        }
        args = Py_BuildValue("(O)", ind);
        arr_field = PyObject_CallObject(meth, args);
        Py_DECREF(meth);
        Py_DECREF(arr);
        Py_DECREF(args);

        if(arr_field == NULL){
            return -1;
        }

        /* 2. Assign the value using setitem with empty tuple. */
        emptytuple = PyTuple_New(0);
        if (PyObject_SetItem(arr_field, emptytuple, val) < 0) {
            Py_DECREF(arr_field);
            Py_DECREF(emptytuple);
            return -1;
        }
        Py_DECREF(emptytuple);
        Py_DECREF(arr_field);
        return 0;
    }

    /* try to convert it to a number */
    n = PyArray_PyIntAsIntp(ind);
    if (error_converting(n)) {
        goto fail;
    }
    return voidtype_ass_item(self, (Py_ssize_t)n, val);

fail:
    PyErr_SetString(PyExc_IndexError, msg);
    return -1;
}

static PyMappingMethods voidtype_as_mapping = {
    .mp_length = (lenfunc)voidtype_length,
    .mp_subscript = (binaryfunc)voidtype_subscript,
    .mp_ass_subscript = (objobjargproc)voidtype_ass_subscript,
};


static PySequenceMethods voidtype_as_sequence = {
    .sq_length = (lenfunc)voidtype_length,
    .sq_item = (ssizeargfunc)voidtype_item,
    .sq_ass_item = (ssizeobjargproc)voidtype_ass_item,
};


/*
 * This function implements simple buffer export for user defined subclasses
 * of `np.generic`. All other scalar types override the buffer export.
 */
static int
gentype_arrtype_getbuffer(PyObject *self, Py_buffer *view, int flags)
{
    if ((flags & PyBUF_FORMAT) == PyBUF_FORMAT) {
        PyErr_Format(PyExc_TypeError,
                "NumPy scalar %R can only exported as a buffer without format.",
                self);
        return -1;
    }
    if ((flags & PyBUF_WRITEABLE) == PyBUF_WRITEABLE) {
        PyErr_SetString(PyExc_BufferError, "scalar buffer is readonly");
        return -1;
    }
    PyArray_Descr *descr = PyArray_DescrFromScalar(self);
    if (descr == NULL) {
        return -1;
    }
    if (!PyDataType_ISUSERDEF(descr)) {
        /* This path would also reject the (hopefully) impossible "object" */
        PyErr_Format(PyExc_TypeError,
                "user-defined scalar %R registered for built-in dtype %S? "
                "This should be impossible.",
                self, descr);
        Py_DECREF(descr);
        return -1;
    }
    view->ndim = 0;
    view->len = descr->elsize;
    view->itemsize = descr->elsize;
    view->shape = NULL;
    view->strides = NULL;
    view->suboffsets = NULL;
    view->readonly = 1;  /* assume general (user) scalars are readonly. */
    Py_INCREF(self);
    view->obj = self;
    view->buf = scalar_value(self, descr);
    Py_DECREF(descr);
    view->format = NULL;
    return 0;
}


static PyBufferProcs gentype_arrtype_as_buffer = {
    .bf_getbuffer = (getbufferproc)gentype_arrtype_getbuffer,
};


/**begin repeat
 * #name = bool, byte, short, int, long, longlong, ubyte, ushort, uint, ulong,
 *         ulonglong, half, float, double, longdouble, cfloat, cdouble,
 *         clongdouble#
 * #Name = Bool, Byte, Short, Int, Long, LongLong, UByte, UShort, UInt, ULong,
 *         ULongLong, Half, Float, Double, LongDouble, CFloat, CDouble,
 *         CLongDouble#
 * #NAME = BOOL, BYTE, SHORT, INT, LONG, LONGLONG, UBYTE, USHORT, UINT, ULONG,
 *         ULONGLONG, HALF, FLOAT, DOUBLE, LONGDOUBLE, CFLOAT, CDOUBLE,
 *         CLONGDOUBLE#
 * #fmt = ?, b, h, i, l, q, B, H, I, L, Q, e, f, d, g, Zf, Zd, Zg#
 */

static int
@name@_getbuffer(PyObject *self, Py_buffer *view, int flags)
{
    if ((flags & PyBUF_WRITEABLE) == PyBUF_WRITEABLE) {
        PyErr_SetString(PyExc_BufferError, "scalar buffer is readonly");
        return -1;
    }
    Py@Name@ScalarObject *scalar = (Py@Name@ScalarObject *)self;

    static char fmt[3] = "@fmt@";

    view->ndim = 0;
    view->len = sizeof(scalar->obval);
    view->itemsize = sizeof(scalar->obval);
    view->shape = NULL;
    view->strides = NULL;
    view->suboffsets = NULL;
    view->readonly = 1;
    Py_INCREF(self);
    view->obj = self;
    view->buf = &(scalar->obval);

    if ((flags & PyBUF_FORMAT) != PyBUF_FORMAT) {
        /* It is unnecessary to find the correct format */
        view->format = NULL;
        return 0;
    }

    view->format = fmt;

    return 0;
}

static PyBufferProcs @name@_arrtype_as_buffer = {
    .bf_getbuffer = @name@_getbuffer,
    /* No need to release the buffer */
};

/**end repeat**/

static int
unicode_getbuffer(PyObject *self, Py_buffer *view, int flags)
{
    if ((flags & PyBUF_WRITEABLE) == PyBUF_WRITEABLE) {
        PyErr_SetString(PyExc_BufferError, "scalar buffer is readonly");
        return -1;
    }
    PyUnicodeScalarObject *scalar = (PyUnicodeScalarObject *)self;
    Py_ssize_t length = PyUnicode_GetLength(self);

    view->ndim = 0;
    view->len = length * 4;
    view->itemsize = length * 4;
    view->shape = NULL;
    view->strides = NULL;
    view->suboffsets = NULL;
    view->readonly = 1;
    Py_INCREF(self);
    view->obj = self;

    if (scalar->obval == NULL) {
        /*
         * Unicode may not have the representation available, `scalar_value`
         * ensures materialization.
         */
        PyArray_Descr *descr = PyArray_DescrFromType(NPY_UNICODE);
        scalar_value(self, descr);
        Py_DECREF(descr);
        if (scalar->obval == NULL) {
            /* allocating memory failed */
            Py_SETREF(view->obj, NULL);
            return -1;
        }
    }
    view->buf = scalar->obval;

    if ((flags & PyBUF_FORMAT) != PyBUF_FORMAT) {
        /* It is unnecessary to find the correct format */
        view->format = NULL;
        return 0;
    }

    if (scalar->buffer_fmt != NULL) {
        view->format = scalar->buffer_fmt;
    }
    else {
        scalar->buffer_fmt = PyMem_Malloc(22);
        if (scalar->buffer_fmt == NULL) {
            Py_SETREF(view->obj, NULL);
            return -1;
        }
        PyOS_snprintf(scalar->buffer_fmt, 22, "%" NPY_INTP_FMT "w", length);
        view->format = scalar->buffer_fmt;
    }

    return 0;
}

static PyBufferProcs unicode_arrtype_as_buffer = {
    .bf_getbuffer = unicode_getbuffer,
    /* No need to release the buffer */
};


/**begin repeat
 * #name = datetime, timedelta#
 * #Name = Datetime, Timedelta#
 */

static int
@name@_getbuffer(PyObject *self, Py_buffer *view, int flags)
{
    if ((flags & PyBUF_WRITEABLE) == PyBUF_WRITEABLE) {
        PyErr_SetString(PyExc_BufferError, "scalar buffer is readonly");
        return -1;
    }
    Py@Name@ScalarObject *scalar = (Py@Name@ScalarObject *)self;

    view->ndim = 1;
    view->len = 8;
    view->itemsize = 1;
    static Py_ssize_t length = 8;
    view->shape = &length;
    view->strides = NULL;
    view->suboffsets = NULL;
    view->readonly = 1;
    Py_INCREF(self);
    view->obj = self;

    view->buf = &(scalar->obval);

    if ((flags & PyBUF_FORMAT) != PyBUF_FORMAT) {
        /* It is unnecessary to find the correct format */
        view->format = NULL;
        return 0;
    }

    /* export datetime scalars as bytes (although arrays are not exported) */
    view->format = "B";

    return 0;
}

static PyBufferProcs @name@_arrtype_as_buffer = {
        .bf_getbuffer = @name@_getbuffer,
        /* No need to release the buffer */
};

/**end repeat**/

static PyBufferProcs void_arrtype_as_buffer = {
        .bf_getbuffer = void_getbuffer,  /* defined in buffer.c */
        /* No need to release the buffer */
};


#define BASEFLAGS Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE
#define LEAFFLAGS  Py_TPFLAGS_DEFAULT

NPY_NO_EXPORT PyTypeObject PyGenericArrType_Type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "numpy.generic",
    .tp_basicsize = sizeof(PyObject),
};


static void
void_dealloc(PyVoidScalarObject *v)
{
    if (v->flags & NPY_ARRAY_OWNDATA) {
        npy_free_cache(v->obval, Py_SIZE(v));
    }
    Py_XDECREF(v->descr);
    Py_XDECREF(v->base);
    if (_buffer_info_free(v->_buffer_info, (PyObject *)v) < 0) {
        PyErr_WriteUnraisable(NULL);
    }
    Py_TYPE(v)->tp_free(v);
}


static PyObject *
object_arrtype_alloc(PyTypeObject *type, Py_ssize_t items)
{
    /*
     * Object scalars should not actually exist, if they exist we should
     * consider it to be a bug.
     */
    if (PyErr_WarnEx(npy_static_pydata.VisibleDeprecationWarning,
            "Creating a NumPy object scalar.  NumPy object scalars should "
            "never be created.  If you see this message please inform the "
            "NumPy developers.  Since this message should never be shown "
            "this will raise a TypeError in the future.", 1) < 0) {
        return NULL;
    }
    return gentype_alloc(type, items);
}


static void
object_arrtype_dealloc(PyObject *v)
{
    Py_XDECREF(PyArrayScalar_VAL(v, Object));
    Py_TYPE(v)->tp_free(v);
}

static void
unicode_arrtype_dealloc(PyObject *v)
{
    /* note: may be null if it was never requested */
    PyMem_Free(PyArrayScalar_VAL(v, Unicode));
    PyMem_Free(((PyUnicodeScalarObject *)v)->buffer_fmt);
    /* delegate to the base class */
    PyUnicode_Type.tp_dealloc(v);
}

/**begin repeat
 * #name = byte, short, int, long, longlong, ubyte, ushort, uint, ulong,
 *         ulonglong, half, float, double, longdouble, cfloat, cdouble,
 *         clongdouble, string, unicode#
 * #Name = Byte, Short, Int, Long, LongLong, UByte, UShort, UInt, ULong,
 *         ULongLong, Half, Float, Double, LongDouble, CFloat, CDouble,
 *         CLongDouble, String, Unicode#
 * #TYPE = BYTE, SHORT, INT, LONG, LONGLONG, UBYTE, USHORT, UINT, ULONG,
 *         ULONGLONG, HALF, FLOAT, DOUBLE, LONGDOUBLE, CFLOAT, CDOUBLE,
 *         CLONGDOUBLE, STRING, UNICODE#
 */

/* used as a pattern for testing token equality */
#define _@TYPE@_IS_@TYPE@

static PyObject *
@name@_arrtype_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    /* allow base-class (if any) to do conversion */
#if defined(_@TYPE@_IS_UNICODE)
    PyObject *from_superclass = PyUnicode_Type.tp_new(type, args, kwds);
#elif defined(_@TYPE@_IS_STRING)
    PyObject *from_superclass = PyBytes_Type.tp_new(type, args, kwds);
#elif defined(_@TYPE@_IS_DOUBLE)
    PyObject *from_superclass = PyFloat_Type.tp_new(type, args, kwds);
#endif
#if defined(_@TYPE@_IS_UNICODE) || defined(_@TYPE@_IS_STRING) || defined(_@TYPE@_IS_DOUBLE)
    if (from_superclass == NULL) {
        /* don't clear the exception unless numpy can handle the arguments */
        if (PyTuple_GET_SIZE(args) != 1 || (kwds && PyDict_Size(kwds) != 0)) {
            return NULL;
        }
        PyErr_Clear();
    }
    else {
#if defined(_@TYPE@_IS_UNICODE)
        PyArrayScalar_VAL(from_superclass, Unicode) = NULL;
#endif
        return from_superclass;
    }
#endif

    /* TODO: include type name in error message, which is not @name@ */
    PyObject *obj = NULL;
#if defined(_@TYPE@_IS_CDOUBLE) || defined(_@TYPE@_IS_CFLOAT)
    static char *kwnames[] = {"", "", NULL};  /* positional-only */
    static char *parse_error = "Could not convert arguments into a complex scalar. '%R' given.";
    PyObject *real_obj = NULL;
    PyObject *imag_obj = NULL;
    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OO", kwnames, &real_obj, &imag_obj)) {
        return NULL;
    }
    if (!((imag_obj == NULL) ||
          ((PyNumber_Check(real_obj) && PyNumber_Check(imag_obj)) &&
           !(PyComplex_Check(real_obj) || PyComplex_Check(imag_obj))))) {
        PyErr_Format(PyExc_TypeError, parse_error, args);
        return NULL;
    }
    if (imag_obj == NULL) {
        obj = real_obj;
        Py_XINCREF(obj);
    }
    else {
        obj = PyObject_CallObject((PyObject *)&PyComplex_Type, args);
        if (obj == NULL) {
            return NULL;
        }
    }
#else
    static char *kwnames[] = {"", NULL};  /* positional-only */
    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O", kwnames, &obj)) {
        return NULL;
    }
#endif
    // getting the typecode like this cannot fail
    PyArray_Descr *typecode = PyArray_DescrFromType(NPY_@TYPE@);
    if (obj == NULL) {
        PyObject *robj = PyArray_Scalar(NULL, typecode, NULL);
        Py_DECREF(typecode);
        if (robj == NULL) {
            return NULL;
        }
#if !defined(_@TYPE@_IS_STRING) && !defined(_@TYPE@_IS_UNICODE)
        memset(&PyArrayScalar_VAL(robj, @Name@), 0, sizeof(npy_@name@));
#endif
        return robj;
    }

    /* PyArray_FromAny steals a reference, reclaim it before it's gone */
    Py_INCREF(typecode);
    PyArrayObject *arr = (PyArrayObject *)PyArray_FromAny(
            obj, typecode, 0, 0, NPY_ARRAY_FORCECAST, NULL);
#if defined(_@TYPE@_IS_CDOUBLE) || defined(_@TYPE@_IS_CFLOAT)
    Py_DECREF(obj);
#endif
    if (arr == NULL) {
        Py_DECREF(typecode);
        return NULL;
    }
    if (PyArray_NDIM(arr) > 0) {
        Py_DECREF(typecode);
        return (PyObject *)arr;
    }

    /* Convert the 0-d array to a scalar*/
    PyObject *robj = PyArray_ToScalar(PyArray_DATA(arr), arr);
    Py_DECREF(arr);

    if (robj == NULL || Py_TYPE(robj) == type) {
        Py_DECREF(typecode);
        return robj;
    }

    /*
     * `typecode` does not contain any subclass information, as it was thrown
     * out by the call to `PyArray_DescrFromType` - we need to add this back.
     *
     * FIXME[gh-15467]: This branch is also hit for the "shadowed" builtin
     * types like `longdouble` (which on platforms where they are the same size
     * is shadowed by `double`), because `PyArray_FromAny` returns the
     * shadowing type rather than the requested one.
     */

    /* Need to allocate new type and copy data-area over */
    int itemsize;
    if (type->tp_itemsize) {
        itemsize = PyBytes_GET_SIZE(robj);
    }
    else {
        itemsize = 0;
    }
    PyObject *new_obj = type->tp_alloc(type, itemsize);
    if (new_obj == NULL) {
        Py_DECREF(robj);
        Py_DECREF(typecode);
        return NULL;
    }
    void *dest = scalar_value(new_obj, typecode);
    void *src = scalar_value(robj, typecode);
    Py_DECREF(typecode);
#if defined(_@TYPE@_IS_STRING) || defined(_@TYPE@_IS_UNICODE)
    if (itemsize == 0) { /* unicode */
        itemsize = PyUnicode_GetLength(robj) * PyUnicode_KIND(robj);
    }
    memcpy(dest, src, itemsize);
#else
    *((npy_@name@ *)dest) = *((npy_@name@ *)src);
#endif
    Py_DECREF(robj);
    return new_obj;
}
#undef _@TYPE@_IS_@TYPE@

/**end repeat**/

static PyObject *
object_arrtype_new(PyTypeObject *NPY_UNUSED(type), PyObject *args, PyObject *kwds)
{
    PyObject *obj = Py_None;
    static char *kwnames[] = {"", NULL};  /* positional-only */
    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O:object_", kwnames, &obj)) {
        return NULL;
    }
    PyArray_Descr *typecode = PyArray_DescrFromType(NPY_OBJECT);
    if (typecode == NULL) {
        return NULL;
    }
    PyArrayObject *arr = (PyArrayObject *)PyArray_FromAny(obj, typecode,
                                    0, 0, NPY_ARRAY_FORCECAST, NULL);
    return PyArray_Return(arr);
}

/**begin repeat
 * #name = datetime, timedelta#
 * #Name = Datetime, Timedelta#
 * #NAME = DATETIME, TIMEDELTA#
 * #is_datetime = 1, 0#
 */

static PyObject *
@name@_arrtype_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    PyObject *obj = NULL, *meta_obj = NULL;
    Py@Name@ScalarObject *ret;

    static char *kwnames[] = {"", "", NULL};  /* positional-only */
    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OO", kwnames, &obj, &meta_obj)) {
        return NULL;
    }

    /* Allocate the return scalar */
    ret = (Py@Name@ScalarObject *)Py@Name@ArrType_Type.tp_alloc(
                                            &Py@Name@ArrType_Type, 0);
    if (ret == NULL) {
        return NULL;
    }

    /* Incorporate the metadata if its provided */
    if (meta_obj != NULL) {
        /* Parse the provided metadata input */
        if (convert_pyobject_to_datetime_metadata(meta_obj, &ret->obmeta)
                                                                    < 0) {
            Py_DECREF(ret);
            return NULL;
        }
    }
    else {
        /*
         * A unit of -1 signals that convert_pyobject_to_datetime
         * should populate.
         */
        ret->obmeta.base = -1;
    }

    if (obj == NULL) {
        if (ret->obmeta.base == -1) {
            ret->obmeta.base = NPY_DATETIME_DEFAULTUNIT;
            ret->obmeta.num = 1;
        }

        /* Make datetime default to NaT, timedelta default to zero */
#if @is_datetime@
        ret->obval = NPY_DATETIME_NAT;
#else
        ret->obval = 0;
#endif
    }
    else if (convert_pyobject_to_@name@(&ret->obmeta, obj,
                            NPY_SAME_KIND_CASTING, &ret->obval) < 0) {
        Py_DECREF(ret);
        return NULL;
    }

    return (PyObject *)ret;
}
/**end repeat**/

/* bool->tp_new only returns Py_True or Py_False */
static PyObject *
bool_arrtype_new(PyTypeObject *NPY_UNUSED(type), PyObject *args, PyObject *kwds)
{
    PyObject *obj = NULL;
    PyArrayObject *arr;

    static char *kwnames[] = {"", NULL};  /* positional-only */
    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O:bool_", kwnames, &obj)) {
        return NULL;
    }
    if (obj == NULL) {
        PyArrayScalar_RETURN_FALSE;
    }
    if (obj == Py_False) {
        PyArrayScalar_RETURN_FALSE;
    }
    if (obj == Py_True) {
        PyArrayScalar_RETURN_TRUE;
    }
    arr = (PyArrayObject *)PyArray_FROM_OTF(obj,
                                NPY_BOOL, NPY_ARRAY_FORCECAST);
    if (arr && 0 == PyArray_NDIM(arr)) {
        npy_bool val = *((npy_bool *)PyArray_DATA(arr));
        Py_DECREF(arr);
        PyArrayScalar_RETURN_BOOL_FROM_LONG(val);
    }
    return PyArray_Return((PyArrayObject *)arr);
}

static PyObject *
bool_arrtype_and(PyObject *a, PyObject *b)
{
    if (PyArray_IsScalar(a, Bool) && PyArray_IsScalar(b, Bool)) {
        PyArrayScalar_RETURN_BOOL_FROM_LONG
            ((a == PyArrayScalar_True) & (b == PyArrayScalar_True));
    }
    return PyGenericArrType_Type.tp_as_number->nb_and(a, b);
}

static PyObject *
bool_arrtype_or(PyObject *a, PyObject *b)
{
    if (PyArray_IsScalar(a, Bool) && PyArray_IsScalar(b, Bool)) {
        PyArrayScalar_RETURN_BOOL_FROM_LONG
            ((a == PyArrayScalar_True)|(b == PyArrayScalar_True));
    }
    return PyGenericArrType_Type.tp_as_number->nb_or(a, b);
}

static PyObject *
bool_arrtype_xor(PyObject *a, PyObject *b)
{
    if (PyArray_IsScalar(a, Bool) && PyArray_IsScalar(b, Bool)) {
        PyArrayScalar_RETURN_BOOL_FROM_LONG
            ((a == PyArrayScalar_True)^(b == PyArrayScalar_True));
    }
    return PyGenericArrType_Type.tp_as_number->nb_xor(a, b);
}

static int
bool_arrtype_nonzero(PyObject *a)
{
    return a == PyArrayScalar_True;
}

/**begin repeat
 * #name = byte, short, int, long, ubyte, ushort, longlong, uint,
 *         ulong, ulonglong#
 * #Name = Byte, Short, Int, Long, UByte, UShort, LongLong, UInt,
 *         ULong, ULongLong#
 * #type = PyLong_FromLong*6, PyLong_FromLongLong*1,
 *         PyLong_FromUnsignedLong*2, PyLong_FromUnsignedLongLong#
 */
static PyNumberMethods @name@_arrtype_as_number;
static PyObject *
@name@_index(PyObject *self)
{
    return @type@(PyArrayScalar_VAL(self, @Name@));
}
/**end repeat**/

/**begin repeat
 *  #name = half, float, double, longdouble,
 *          cfloat, cdouble, clongdouble#
 *  #NAME = Half, Float, Double, LongDouble,
 *          CFloat, CDouble, CLongDouble#
 */
static PyNumberMethods @name@_arrtype_as_number;
/**end repeat**/

/* Arithmetic methods -- only so we can override &, |, ^. */
NPY_NO_EXPORT PyNumberMethods bool_arrtype_as_number = {
    .nb_bool = (inquiry)bool_arrtype_nonzero,
    .nb_and = (binaryfunc)bool_arrtype_and,
    .nb_xor = (binaryfunc)bool_arrtype_xor,
    .nb_or = (binaryfunc)bool_arrtype_or,
};

static PyObject *
void_arrtype_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    PyObject *obj, *arr;
    PyArray_Descr *descr = NULL;

    static char *kwnames[] = {"", "dtype", NULL};
    if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|O&:void", kwnames,
            &obj, &PyArray_DescrConverter2, &descr)) {
        return NULL;
    }
    /*
     * For a VOID scalar first see if obj is an integer or long
     * and create new memory of that size (filled with 0) for the scalar
     */
    if (descr == NULL && (
            PyLong_Check(obj) ||
            PyArray_IsScalar(obj, Integer) ||
            (PyArray_Check(obj) &&
                     PyArray_NDIM((PyArrayObject *)obj)==0 &&
                     PyArray_ISINTEGER((PyArrayObject *)obj)))) {

        PyObject *length = Py_TYPE(obj)->tp_as_number->nb_int(obj);
        if (length == NULL) {
            return NULL;
        }

        PyObject *ret;
        char *destptr;
        npy_ulonglong memu = PyLong_AsUnsignedLongLong(length);
        Py_DECREF(length);
        if (PyErr_Occurred() || (memu > NPY_MAX_INT)) {
            PyErr_Clear();
            PyErr_Format(PyExc_OverflowError,
                    "size must be non-negative and not greater than %d",
                    (int) NPY_MAX_INT);
            return NULL;
        }
        if (memu == 0) {
            memu = 1;
        }
        destptr = npy_alloc_cache_zero(memu, 1);
        if (destptr == NULL) {
            return PyErr_NoMemory();
        }
        ret = type->tp_alloc(type, 0);
        if (ret == NULL) {
            npy_free_cache(destptr, memu);
            return PyErr_NoMemory();
        }
        ((PyVoidScalarObject *)ret)->obval = destptr;
        Py_SET_SIZE((PyVoidScalarObject *)ret, (int) memu);
        ((PyVoidScalarObject *)ret)->flags = NPY_ARRAY_BEHAVED |
                                             NPY_ARRAY_OWNDATA;
        ((PyVoidScalarObject *)ret)->base = NULL;
        ((PyVoidScalarObject *)ret)->descr =
            (_PyArray_LegacyDescr *)PyArray_DescrNewFromType(NPY_VOID);
        if (((PyVoidScalarObject *)ret)->descr == NULL) {
            Py_DECREF(ret);
            return NULL;
        }
        ((PyVoidScalarObject *)ret)->descr->elsize = (int) memu;
        return ret;
    }

    if (descr == NULL) {
        /* Use the "size-less" void dtype to discover the size. */
        descr = PyArray_DescrNewFromType(NPY_VOID);
        if (descr == NULL) {
            return NULL;
        }
    }
    else if (descr->type_num != NPY_VOID || PyDataType_HASSUBARRAY(descr)) {
        /* we reject subarrays, since subarray scalars do not exist. */
        PyErr_Format(PyExc_TypeError,
                "void: descr must be a `void` dtype that is not "
                "a subarray dtype (structured or unstructured). "
                "Got '%.100R'.", descr);
        Py_DECREF(descr);
        return NULL;
    }

    arr = PyArray_FromAny(obj, descr, 0, 0, NPY_ARRAY_FORCECAST, NULL);
    return PyArray_Return((PyArrayObject *)arr);
}


/****************  Define Hash functions ********************/

/**begin repeat
 * #lname = bool, ubyte, ushort#
 * #name = Bool,UByte, UShort#
 */
static npy_hash_t
@lname@_arrtype_hash(PyObject *obj)
{
    return (npy_hash_t)(PyArrayScalar_VAL(obj, @name@));
}
/**end repeat**/

/**begin repeat
 * #lname = byte, short, uint#
 * #name = Byte, Short, UInt#
 */
static npy_hash_t
@lname@_arrtype_hash(PyObject *obj)
{
    npy_hash_t x = (npy_hash_t)(PyArrayScalar_VAL(obj, @name@));
    if (x == -1) {
        x = -2;
    }
    return x;
}
/**end repeat**/

static npy_hash_t
ulong_arrtype_hash(PyObject *obj)
{
    PyObject * l = PyLong_FromUnsignedLong(PyArrayScalar_VAL(obj, ULong));
    npy_hash_t x = PyObject_Hash(l);
    Py_DECREF(l);
    return x;
}

static npy_hash_t
int_arrtype_hash(PyObject *obj)
{
    npy_hash_t x = (npy_hash_t)(PyArrayScalar_VAL(obj, Int));
    if (x == -1) {
        x = -2;
    }
    return x;
}

static npy_hash_t
long_arrtype_hash(PyObject *obj)
{
    PyObject * l = PyLong_FromLong(PyArrayScalar_VAL(obj, Long));
    npy_hash_t x = PyObject_Hash(l);
    Py_DECREF(l);
    return x;
}

/**begin repeat
 * #char = ,u#
 * #Char = ,U#
 * #Word = ,Unsigned#
 */
static inline npy_hash_t
@char@longlong_arrtype_hash(PyObject *obj)
{
    PyObject * l = PyLong_From@Word@LongLong(
                                 PyArrayScalar_VAL(obj, @Char@LongLong));
    npy_hash_t x = PyObject_Hash(l);
    Py_DECREF(l);
    return x;
}
/**end repeat**/


/**begin repeat
 * #lname = datetime, timedelta#
 * #name = Datetime, Timedelta#
 */
static npy_hash_t
@lname@_arrtype_hash(PyObject *obj)
{
    PyArray_DatetimeMetaData *meta;
    npy_@lname@ val = PyArrayScalar_VAL(obj, @name@);

    if (val == NPY_DATETIME_NAT) {
        /* Use identity, similar to NaN */
        return PyBaseObject_Type.tp_hash(obj);
    }

    meta = &((PyDatetimeScalarObject *)obj)->obmeta;

    npy_hash_t res = @lname@_hash(meta, val);
    return res;
}
/**end repeat**/


/* Wrong thing to do for longdouble, but....*/

/**begin repeat
 * #lname = float, longdouble#
 * #name = Float, LongDouble#
 * #NAME = FLOAT, LONGDOUBLE#
 * #n = f, l#
 */
static npy_hash_t
@lname@_arrtype_hash(PyObject *obj)
{
    return Npy_HashDouble(obj, (double)PyArrayScalar_VAL(obj, @name@));
}

/* borrowed from complex_hash */
static npy_hash_t
c@lname@_arrtype_hash(PyObject *obj)
{
    npy_hash_t hashreal, hashimag, combined;
    hashreal = Npy_HashDouble(
            obj, (double)npy_creal@n@(PyArrayScalar_VAL(obj, C@name@)));

    if (hashreal == -1) {
        return -1;
    }
    hashimag = Npy_HashDouble(
            obj, (double)npy_cimag@n@(PyArrayScalar_VAL(obj, C@name@)));
    if (hashimag == -1) {
        return -1;
    }
    combined = hashreal + 1000003 * hashimag;
    if (combined == -1) {
        combined = -2;
    }
    return combined;
}
/**end repeat**/

static npy_hash_t
half_arrtype_hash(PyObject *obj)
{
    return Npy_HashDouble(
            obj, npy_half_to_double(PyArrayScalar_VAL(obj, Half)));
}

static npy_hash_t
object_arrtype_hash(PyObject *obj)
{
    return PyObject_Hash(PyArrayScalar_VAL(obj, Object));
}

/* we used to just hash the pointer */
/* now use tuplehash algorithm using voidtype_item to get the object
*/
static npy_hash_t
void_arrtype_hash(PyObject *obj)
{
    npy_hash_t x, y;
    Py_ssize_t len, n;
    PyVoidScalarObject *p;
    PyObject *element;
    npy_hash_t mult = 1000003L;
    x = 0x345678L;
    p = (PyVoidScalarObject *)obj;
    /* Cannot hash mutable void scalars */
    if (p->flags & NPY_ARRAY_WRITEABLE) {
       PyErr_SetString(PyExc_TypeError, "unhashable type: 'writeable void-scalar'");
       return -1;
    }
    len = voidtype_length(p);
    for (n=0; n < len; n++) {
        element = voidtype_item(p, n);
        y = PyObject_Hash(element);
        Py_DECREF(element);
        if (y == -1)
           return -1;
        x = (x ^ y) * mult;
        mult += (npy_hash_t)(82520L + len + len);
    }
    x += 97531L;
    if (x == -1)
        x = -2;
    return x;
}

/*object arrtype getattro and setattro */
static PyObject *
object_arrtype_getattro(PyObjectScalarObject *obj, PyObject *attr) {
    PyObject *res;

    /* first look in object and then hand off to generic type */

    res = PyObject_GenericGetAttr(obj->obval, attr);
    if (res) {
        return res;
    }
    PyErr_Clear();
    return  PyObject_GenericGetAttr((PyObject *)obj, attr);
}

static int
object_arrtype_setattro(PyObjectScalarObject *obj, PyObject *attr, PyObject *val) {
    int res;
    /* first look in object and then hand off to generic type */

    res = PyObject_GenericSetAttr(obj->obval, attr, val);
    if (res >= 0) {
        return res;
    }
    PyErr_Clear();
    return PyObject_GenericSetAttr((PyObject *)obj, attr, val);
}

static PyObject *
object_arrtype_concat(PyObjectScalarObject *self, PyObject *other)
{
    return PySequence_Concat(self->obval, other);
}

static Py_ssize_t
object_arrtype_length(PyObjectScalarObject *self)
{
    return PyObject_Length(self->obval);
}

static PyObject *
object_arrtype_repeat(PyObjectScalarObject *self, Py_ssize_t count)
{
    return PySequence_Repeat(self->obval, count);
}

static PyObject *
object_arrtype_subscript(PyObjectScalarObject *self, PyObject *key)
{
    return PyObject_GetItem(self->obval, key);
}

static int
object_arrtype_ass_subscript(PyObjectScalarObject *self, PyObject *key,
                             PyObject *value)
{
    return PyObject_SetItem(self->obval, key, value);
}

static int
object_arrtype_contains(PyObjectScalarObject *self, PyObject *ob)
{
    return PySequence_Contains(self->obval, ob);
}

static PyObject *
object_arrtype_inplace_concat(PyObjectScalarObject *self, PyObject *o)
{
    return PySequence_InPlaceConcat(self->obval, o);
}

static PyObject *
object_arrtype_inplace_repeat(PyObjectScalarObject *self, Py_ssize_t count)
{
    return PySequence_InPlaceRepeat(self->obval, count);
}

static PySequenceMethods object_arrtype_as_sequence = {
    .sq_length = (lenfunc)object_arrtype_length,
    .sq_concat = (binaryfunc)object_arrtype_concat,
    .sq_repeat = (ssizeargfunc)object_arrtype_repeat,
    .sq_contains = (objobjproc)object_arrtype_contains,
    .sq_inplace_concat = (binaryfunc)object_arrtype_inplace_concat,
    .sq_inplace_repeat = (ssizeargfunc)object_arrtype_inplace_repeat,
};

static PyMappingMethods object_arrtype_as_mapping = {
    .mp_length = (lenfunc)object_arrtype_length,
    .mp_subscript = (binaryfunc)object_arrtype_subscript,
    .mp_ass_subscript = (objobjargproc)object_arrtype_ass_subscript,
};

static int
object_arrtype_getbuffer(PyObjectScalarObject *self, Py_buffer *view, int flags)
{
    PyBufferProcs *pb = Py_TYPE(self->obval)->tp_as_buffer;
    if (pb == NULL || pb->bf_getbuffer == NULL) {
        PyErr_SetString(PyExc_TypeError,
                        "expected a readable buffer object");
        return -1;
    }
    return (*pb->bf_getbuffer)(self->obval, view, flags);
}

static void
object_arrtype_releasebuffer(PyObjectScalarObject *self, Py_buffer *view)
{
    PyBufferProcs *pb = Py_TYPE(self->obval)->tp_as_buffer;
    if (pb == NULL) {
        PyErr_SetString(PyExc_TypeError,
                        "expected a readable buffer object");
        return;
    }
    if (pb->bf_releasebuffer != NULL) {
        (*pb->bf_releasebuffer)(self->obval, view);
    }
}

static PyBufferProcs object_arrtype_as_buffer = {
    .bf_getbuffer = (getbufferproc)object_arrtype_getbuffer,
    .bf_releasebuffer = (releasebufferproc)object_arrtype_releasebuffer,
};

static PyObject *
object_arrtype_call(PyObjectScalarObject *obj, PyObject *args, PyObject *kwds)
{
    return PyObject_Call(obj->obval, args, kwds);
}

NPY_NO_EXPORT PyTypeObject PyObjectArrType_Type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "numpy." NPY_OBJECT_name,
    .tp_basicsize = sizeof(PyObjectScalarObject),
    .tp_dealloc = (destructor)object_arrtype_dealloc,
    .tp_as_sequence = &object_arrtype_as_sequence,
    .tp_as_mapping = &object_arrtype_as_mapping,
    .tp_call = (ternaryfunc)object_arrtype_call,
    .tp_getattro = (getattrofunc)object_arrtype_getattro,
    .tp_setattro = (setattrofunc)object_arrtype_setattro,
    .tp_as_buffer = &object_arrtype_as_buffer,
    .tp_alloc = object_arrtype_alloc,
};

static PyObject *
gen_arrtype_subscript(PyObject *self, PyObject *key)
{
    /*
     * Only [...], [...,<???>], [<???>, ...],
     * is allowed for indexing a scalar
     *
     * These return a new N-d array with a copy of
     * the data where N is the number of None's in <???>.
     */
    PyObject *res, *ret;

    res = PyArray_FromScalar(self, NULL);

    ret = array_subscript((PyArrayObject *)res, key);
    Py_DECREF(res);
    if (ret == NULL) {
        PyErr_SetString(PyExc_IndexError,
                        "invalid index to scalar variable.");
    }
    return ret;
}


/**begin repeat
 * #Name = Bool,
 *         Byte, Short, Int, Long, LongLong,
 *         UByte, UShort, UInt, ULong, ULongLong,
 *         Half, Float, Double, LongDouble,
 *         CFloat, CDouble, CLongDouble,
 *         String, Unicode, Void,
 *         Datetime, Timedelta#
 * #NAME = BOOL,
 *         BYTE, SHORT, INT, LONG, LONGLONG,
 *         UBYTE, USHORT, UINT, ULONG, ULONGLONG,
 *         HALF, FLOAT, DOUBLE, LONGDOUBLE,
 *         CFLOAT, CDOUBLE, CLONGDOUBLE,
 *         STRING, UNICODE, VOID,
 *         DATETIME, TIMEDELTA#
 */

NPY_NO_EXPORT PyTypeObject Py@Name@ArrType_Type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "numpy." NPY_@NAME@_name,
    .tp_basicsize = sizeof(Py@Name@ScalarObject),
};

/**end repeat**/


static PyMappingMethods gentype_as_mapping = {
    .mp_subscript = (binaryfunc)gen_arrtype_subscript,
};


/*
 * This table maps the built-in type numbers to their scalar
 * type numbers.  Note that signed integers are mapped to INTNEG_SCALAR,
 * which is different than what PyArray_ScalarKind returns.
 */
NPY_NO_EXPORT signed char
_npy_scalar_kinds_table[NPY_NTYPES_LEGACY];

/*
 * This table maps a scalar kind (excluding NPY_NOSCALAR)
 * to the smallest type number of that kind.
 */
NPY_NO_EXPORT signed char
_npy_smallest_type_of_kind_table[NPY_NSCALARKINDS];

/*
 * This table gives the type of the same kind, but next in the sequence
 * of sizes.
 */
NPY_NO_EXPORT signed char
_npy_next_larger_type_table[NPY_NTYPES_LEGACY];

/*
 * This table gives the smallest-size and smallest-kind type to which
 * the input types may be safely cast, according to _npy_can_cast_safely.
 */
NPY_NO_EXPORT signed char
_npy_type_promotion_table[NPY_NTYPES_LEGACY][NPY_NTYPES_LEGACY];

NPY_NO_EXPORT void
initialize_casting_tables(void)
{
    int i, j;

    _npy_smallest_type_of_kind_table[NPY_BOOL_SCALAR] = NPY_BOOL;
    _npy_smallest_type_of_kind_table[NPY_INTPOS_SCALAR] = NPY_UBYTE;
    _npy_smallest_type_of_kind_table[NPY_INTNEG_SCALAR] = NPY_BYTE;
    _npy_smallest_type_of_kind_table[NPY_FLOAT_SCALAR] = NPY_HALF;
    _npy_smallest_type_of_kind_table[NPY_COMPLEX_SCALAR] = NPY_CFLOAT;
    _npy_smallest_type_of_kind_table[NPY_OBJECT_SCALAR] = NPY_OBJECT;

    /* Default for built-in types is object scalar */
    memset(_npy_scalar_kinds_table, NPY_OBJECT_SCALAR,
                                        sizeof(_npy_scalar_kinds_table));
    /* Default for next largest type is -1, signalling no bigger */
    memset(_npy_next_larger_type_table, -1,
                                        sizeof(_npy_next_larger_type_table));

    /* Compile-time loop of scalar kinds */

    /**begin repeat
     * #NAME = BOOL,
     *         BYTE, UBYTE, SHORT, USHORT, INT, UINT,
     *         LONG, ULONG, LONGLONG, ULONGLONG,
     *         HALF, FLOAT, DOUBLE, LONGDOUBLE,
     *         CFLOAT, CDOUBLE, CLONGDOUBLE#
     * #BIGGERTYPE = -1,
     *         NPY_SHORT, NPY_USHORT, NPY_INT, NPY_UINT, NPY_LONG, NPY_ULONG,
     *         NPY_LONGLONG, NPY_ULONGLONG, -1, -1,
     *         NPY_FLOAT, NPY_DOUBLE, NPY_LONGDOUBLE, -1,
     *         NPY_CDOUBLE, NPY_CLONGDOUBLE, -1#
     * #SCKIND = BOOL,
     *           (INTNEG, INTPOS)*5,
     *           FLOAT*4,
     *           COMPLEX*3#
     */

    _npy_scalar_kinds_table[NPY_@NAME@] = NPY_@SCKIND@_SCALAR;
    _npy_next_larger_type_table[NPY_@NAME@] = @BIGGERTYPE@;

    /**end repeat**/

#undef _TO_NUM
#undef _TO_BSIZE

/**end repeat1**/

#undef _FROM_NUM
#undef _FROM_BSIZE

/**end repeat**/

    /*
     * Now that the _can_cast_safely table is finished, we can
     * use it to build the _type_promotion table
     */
    for (i = 0; i < NPY_NTYPES_LEGACY; ++i) {
        _npy_type_promotion_table[i][i] = i;
        /* Don't let number promote to string/unicode/void/datetime/timedelta */
        if (i == NPY_STRING || i == NPY_UNICODE || i == NPY_VOID ||
                i == NPY_DATETIME || i == NPY_TIMEDELTA) {
            /* Promoting these types requires examining their contents */
            _npy_type_promotion_table[i][i] = -1;
            for (j = i + 1; j < NPY_NTYPES_LEGACY; ++j) {
                _npy_type_promotion_table[i][j] = -1;
                _npy_type_promotion_table[j][i] = -1;
            }
            /* Except they can convert to OBJECT */
            _npy_type_promotion_table[i][NPY_OBJECT] = NPY_OBJECT;
            _npy_type_promotion_table[NPY_OBJECT][i] = NPY_OBJECT;
        }
        else {
            for (j = i + 1; j < NPY_NTYPES_LEGACY; ++j) {
                /* Don't let number promote to string/unicode/void */
                if (j == NPY_STRING || j == NPY_UNICODE || j == NPY_VOID) {
                    _npy_type_promotion_table[i][j] = -1;
                    _npy_type_promotion_table[j][i] = -1;
                }
                else if (_npy_can_cast_safely_table[i][j]) {
                    _npy_type_promotion_table[i][j] = j;
                    _npy_type_promotion_table[j][i] = j;
                }
                else if (_npy_can_cast_safely_table[j][i]) {
                    _npy_type_promotion_table[i][j] = i;
                    _npy_type_promotion_table[j][i] = i;
                }
                else {
                    int k, iskind, jskind, skind;
                    iskind = _npy_scalar_kinds_table[i];
                    jskind = _npy_scalar_kinds_table[j];
                    /* If there's no kind (void/string/etc) */
                    if (iskind == NPY_NOSCALAR || jskind == NPY_NOSCALAR) {
                        k = -1;
                    }
                    else {
                        /* Start with the type of larger kind */
                        if (iskind > jskind) {
                            skind = iskind;
                            k = i;
                        }
                        else {
                            skind = jskind;
                            k = j;
                        }
                        for (;;) {
                            /* Try the next larger type of this kind */
                            k = _npy_next_larger_type_table[k];

                            /* If there is no larger, try a larger kind */
                            if (k < 0) {
                                ++skind;
                                /* Use -1 to signal no promoted type found */
                                if (skind < NPY_NSCALARKINDS) {
                                    k = _npy_smallest_type_of_kind_table[skind];
                                }
                                else {
                                    k = -1;
                                    break;
                                }
                            }

                            if (_npy_can_cast_safely_table[i][k] &&
                                            _npy_can_cast_safely_table[j][k]) {
                                break;
                            }
                        }
                    }
                    _npy_type_promotion_table[i][j] = k;
                    _npy_type_promotion_table[j][i] = k;
                }
            }
        }
    }
}

static PyNumberMethods longdoubletype_as_number;
static PyNumberMethods clongdoubletype_as_number;
static void init_basetypes(void);


NPY_NO_EXPORT void
initialize_numeric_types(void)
{
    init_basetypes();
    PyGenericArrType_Type.tp_dealloc = (destructor)gentype_dealloc;
    PyGenericArrType_Type.tp_as_number = &gentype_as_number;
    PyGenericArrType_Type.tp_as_mapping = &gentype_as_mapping;
    PyGenericArrType_Type.tp_flags = BASEFLAGS;
    PyGenericArrType_Type.tp_methods = gentype_methods;
    PyGenericArrType_Type.tp_getset = gentype_getsets;
    PyGenericArrType_Type.tp_new = NULL;
    PyGenericArrType_Type.tp_alloc = gentype_alloc;
    PyGenericArrType_Type.tp_free = (freefunc)gentype_free;
    PyGenericArrType_Type.tp_richcompare = gentype_richcompare;
    PyGenericArrType_Type.tp_as_buffer = &gentype_arrtype_as_buffer;

    PyBoolArrType_Type.tp_as_number = &bool_arrtype_as_number;
    /*
     * need to add dummy versions with filled-in nb_index
     * in-order for PyType_Ready to fill in .__index__() method
     * also fill array_type_as_number struct with reasonable defaults
     */

    /**begin repeat
     * #name = byte, short, int, long, longlong, ubyte, ushort,
     *         uint, ulong, ulonglong#
     * #NAME = Byte, Short, Int, Long, LongLong, UByte, UShort,
     *         UInt, ULong, ULongLong#
     */
    @name@_arrtype_as_number = gentype_as_number;
    Py@NAME@ArrType_Type.tp_as_number = &@name@_arrtype_as_number;
    Py@NAME@ArrType_Type.tp_as_number->nb_index = (unaryfunc)@name@_index;

    /**end repeat**/

    /**begin repeat
     *  #name = half, float, double, longdouble,
     *          cfloat, cdouble, clongdouble#
     *  #NAME = Half, Float, Double, LongDouble,
     *          CFloat, CDouble, CLongDouble#
     */
    @name@_arrtype_as_number = gentype_as_number;
    Py@NAME@ArrType_Type.tp_as_number = &@name@_arrtype_as_number;

    /**end repeat**/

    PyStringArrType_Type.tp_alloc = NULL;
    PyStringArrType_Type.tp_free = NULL;

    PyStringArrType_Type.tp_repr = stringtype_repr;
    PyStringArrType_Type.tp_str = stringtype_str;

    PyUnicodeArrType_Type.tp_repr = unicodetype_repr;
    PyUnicodeArrType_Type.tp_str = unicodetype_str;

    PyVoidArrType_Type.tp_methods = voidtype_methods;
    PyVoidArrType_Type.tp_getset = voidtype_getsets;
    PyVoidArrType_Type.tp_as_mapping = &voidtype_as_mapping;
    PyVoidArrType_Type.tp_as_sequence = &voidtype_as_sequence;
    PyVoidArrType_Type.tp_repr = voidtype_repr;
    PyVoidArrType_Type.tp_str = voidtype_str;

    PyIntegerArrType_Type.tp_getset = inttype_getsets;

    PyNumberArrType_Type.tp_methods = numbertype_methods;

    /**begin repeat
     * #NAME= Number, Integer, SignedInteger, UnsignedInteger, Inexact,
     *        Floating, ComplexFloating, Character#
     */

    Py@NAME@ArrType_Type.tp_flags = BASEFLAGS;

    /**end repeat**/

    /**begin repeat
     * #name = bool, byte, short, int, long, longlong, ubyte, ushort, uint,
     *         ulong, ulonglong, half, float, double, longdouble, cfloat,
     *         cdouble, clongdouble, string, unicode, void, object, datetime,
     *         timedelta#
     * #NAME = Bool, Byte, Short, Int, Long, LongLong, UByte, UShort, UInt,
     *         ULong, ULongLong, Half, Float, Double, LongDouble, CFloat,
     *         CDouble, CLongDouble, String, Unicode, Void, Object, Datetime,
     *         Timedelta#
     */

    Py@NAME@ArrType_Type.tp_flags = BASEFLAGS;
    Py@NAME@ArrType_Type.tp_new = @name@_arrtype_new;
    Py@NAME@ArrType_Type.tp_richcompare = gentype_richcompare;

#define _IS_@NAME@  /* inherit string buffer */
#if !defined(_IS_String)
    Py@NAME@ArrType_Type.tp_as_buffer = &@name@_arrtype_as_buffer;
#endif
#undef _IS_@NAME@

    /**end repeat**/

    PyUnicodeArrType_Type.tp_dealloc = unicode_arrtype_dealloc;

    /**begin repeat
     * #name = bool, byte, short, ubyte, ushort, uint, ulong, ulonglong,
     *         half, float, longdouble, cfloat, clongdouble, void, object,
     *         datetime, timedelta#
     * #NAME = Bool, Byte, Short, UByte, UShort, UInt, ULong, ULongLong,
     *         Half, Float, LongDouble, CFloat, CLongDouble, Void, Object,
     *         Datetime, Timedelta#
     */

    Py@NAME@ArrType_Type.tp_hash = @name@_arrtype_hash;

    /**end repeat**/

    /**begin repeat
     * #name = cfloat, clongdouble, floating, integer#
     * #NAME = CFloat, CLongDouble, Floating, Integer#
     */

    Py@NAME@ArrType_Type.tp_methods = @name@type_methods;

    /**end repeat**/

    /**begin repeat
     * #name = byte, short, int, long, longlong,
     *         ubyte, ushort, uint, ulong, ulonglong#
     * #Name = Byte, Short, Int, Long, LongLong,
     *         UByte, UShort, UInt, ULong, ULongLong#
     */

    Py@Name@ArrType_Type.tp_methods = @name@type_methods;

    /**end repeat**/

    /**begin repeat
     * #name = half, float, double, longdouble#
     * #Name = Half, Float, Double, LongDouble#
     */

    Py@Name@ArrType_Type.tp_methods = @name@type_methods;

    /**end repeat**/

    /**begin repeat
     * #name = byte, short, int, long, longlong, ubyte, ushort,
     *         uint, ulong, ulonglong, timedelta, cdouble#
     * #Name = Byte, Short, Int, Long, LongLong, UByte, UShort,
     *         UInt, ULong, ULongLong, Timedelta, CDouble#
     */

    Py@Name@ArrType_Type.tp_methods = @name@type_methods;

    /**end repeat**/

    /* We won't be inheriting from Python Int type. */
    PyIntArrType_Type.tp_hash = int_arrtype_hash;

    /* We won't be inheriting from Python Int type. */
    PyLongArrType_Type.tp_hash = long_arrtype_hash;

    /* We won't be inheriting from Python Int type. */
    PyLongLongArrType_Type.tp_hash = longlong_arrtype_hash;

    /**begin repeat
     * #name = repr, str#
     */

    PyHalfArrType_Type.tp_@name@ = halftype_@name@;

    PyFloatArrType_Type.tp_@name@ = floattype_@name@;
    PyCFloatArrType_Type.tp_@name@ = cfloattype_@name@;

    PyDoubleArrType_Type.tp_@name@ = doubletype_@name@;
    PyCDoubleArrType_Type.tp_@name@  = cdoubletype_@name@;

    PyDatetimeArrType_Type.tp_@name@ = datetimetype_@name@;
    PyTimedeltaArrType_Type.tp_@name@ = timedeltatype_@name@;

    /**end repeat**/


    /**begin repeat
     * #Type = Byte, UByte, Short, UShort, Int, UInt, Long,
     *         ULong, LongLong, ULongLong#
     */

    Py@Type@ArrType_Type.tp_str = genint_type_str;
    Py@Type@ArrType_Type.tp_repr = genint_type_repr;

    /**end repeat**/

    PyBoolArrType_Type.tp_str = genbool_type_str;
    PyBoolArrType_Type.tp_repr = genbool_type_repr;


    /**begin repeat
     * #char = ,c#
     * #CHAR = ,C#
     */

    /*
     * These need to be coded specially because longdouble/clongdouble getitem
     * does not return a normal Python type
     */
    @char@longdoubletype_as_number.nb_float = @char@longdoubletype_float;
    @char@longdoubletype_as_number.nb_int  = @char@longdoubletype_long;

    Py@CHAR@LongDoubleArrType_Type.tp_as_number = &@char@longdoubletype_as_number;
    Py@CHAR@LongDoubleArrType_Type.tp_repr = @char@longdoubletype_repr;
    Py@CHAR@LongDoubleArrType_Type.tp_str = @char@longdoubletype_str;

    /**end repeat**/

    PyStringArrType_Type.tp_itemsize = sizeof(char);
    PyVoidArrType_Type.tp_dealloc = (destructor) void_dealloc;

    PyArrayIter_Type.tp_iter = PyObject_SelfIter;
    PyArrayMapIter_Type.tp_iter = PyObject_SelfIter;
}

typedef struct {
    PyTypeObject * type;
    int typenum;
} scalar_type;

static scalar_type typeobjects[] = {
    {&PyBoolArrType_Type, NPY_BOOL},
    {&PyByteArrType_Type, NPY_BYTE},
    {&PyUByteArrType_Type, NPY_UBYTE},
    {&PyShortArrType_Type, NPY_SHORT},
    {&PyUShortArrType_Type, NPY_USHORT},
    {&PyIntArrType_Type, NPY_INT},
    {&PyUIntArrType_Type, NPY_UINT},
    {&PyLongArrType_Type, NPY_LONG},
    {&PyULongArrType_Type, NPY_ULONG},
    {&PyLongLongArrType_Type, NPY_LONGLONG},
    {&PyULongLongArrType_Type, NPY_ULONGLONG},
    {&PyFloatArrType_Type, NPY_FLOAT},
    {&PyDoubleArrType_Type, NPY_DOUBLE},
    {&PyLongDoubleArrType_Type, NPY_LONGDOUBLE},
    {&PyCFloatArrType_Type, NPY_CFLOAT},
    {&PyCDoubleArrType_Type, NPY_CDOUBLE},
    {&PyCLongDoubleArrType_Type, NPY_CLONGDOUBLE},
    {&PyObjectArrType_Type, NPY_OBJECT},
    {&PyStringArrType_Type, NPY_STRING},
    {&PyUnicodeArrType_Type, NPY_UNICODE},
    {&PyVoidArrType_Type, NPY_VOID},
    {&PyDatetimeArrType_Type, NPY_DATETIME},
    {&PyTimedeltaArrType_Type, NPY_TIMEDELTA},
    {&PyHalfArrType_Type, NPY_HALF}
};

static int compare_types(const void * a_, const void * b_)
{
    const PyTypeObject * a = ((const scalar_type *)a_)->type;
    const PyTypeObject * b = ((const scalar_type *)b_)->type;
    if (a < b) {
        return -1;
    }
    else if (a > b) {
        return 1;
    }
    return 0;
}

static void init_basetypes(void)
{
    qsort(typeobjects, sizeof(typeobjects) / sizeof(typeobjects[0]),
          sizeof(typeobjects[0]),
          compare_types);
}


NPY_NO_EXPORT int
get_typeobj_idx(PyTypeObject * obj)
{
    npy_intp imin = 0, imax = sizeof(typeobjects) / sizeof(typeobjects[0]) - 1;
    while (imax >= imin)
    {
        npy_intp imid = ((imax - imin) / 2) + imin;
        if(typeobjects[imid].type == obj) {
            return imid;
        }
        else if (typeobjects[imid].type < obj) {
            imin = imid + 1;
        }
        else {
            imax = imid - 1;
        }
    }

    return -1;
}

NPY_NO_EXPORT int
is_anyscalar_exact(PyObject *obj)
{
    return get_typeobj_idx(Py_TYPE(obj)) >= 0;
}

NPY_NO_EXPORT int
_typenum_fromtypeobj(PyObject *type, int user)
{
    int typenum, i;

    typenum = NPY_NOTYPE;
    i = get_typeobj_idx((PyTypeObject*)type);
    if (i >= 0) {
        typenum = typeobjects[i].typenum;
    }

    if (!user) {
        return typenum;
    }
    /* Search any registered types */
    i = 0;
    while (i < NPY_NUMUSERTYPES) {
        if (type == (PyObject *)(userdescrs[i]->typeobj)) {
            typenum = i + NPY_USERDEF;
            break;
        }
        i++;
    }
    return typenum;
}
