diff --git a/mypyc/build.py b/mypyc/build.py index 594a6f4be21e5..026db45471086 100644 --- a/mypyc/build.py +++ b/mypyc/build.py @@ -114,6 +114,8 @@ class ModDesc(NamedTuple): "vecs/vec_u8.c", "vecs/vec_float.c", "vecs/vec_bool.c", + "vecs/vec_t.c", + "vecs/vec_nested.c", ], ["vecs/librt_vecs.h", "vecs/vec_template.c"], ["vecs"], diff --git a/mypyc/lib-rt/setup.py b/mypyc/lib-rt/setup.py index 5250e83a593fe..1f9f48c22a77c 100644 --- a/mypyc/lib-rt/setup.py +++ b/mypyc/lib-rt/setup.py @@ -142,6 +142,8 @@ def run(self) -> None: "vecs/vec_u8.c", "vecs/vec_float.c", "vecs/vec_bool.c", + "vecs/vec_t.c", + "vecs/vec_nested.c", ], include_dirs=[".", "vecs"], extra_compile_args=cflags, diff --git a/mypyc/lib-rt/vecs/librt_vecs.c b/mypyc/lib-rt/vecs/librt_vecs.c index bbbefd90cf92b..b36ed24504a5a 100644 --- a/mypyc/lib-rt/vecs/librt_vecs.c +++ b/mypyc/lib-rt/vecs/librt_vecs.c @@ -96,9 +96,31 @@ typedef struct { static PyObject *vec_generic_alias_call(PyObject *self, PyObject *args, PyObject *kw) { - PyErr_SetString(PyExc_NotImplementedError, - "non-specialized vec construction not yet supported"); - return NULL; + static char *kwlist[] = {"", NULL}; + PyObject *init = NULL; + if (!PyArg_ParseTupleAndKeywords(args, kw, "|O:vec", kwlist, &init)) { + return NULL; + } + VecGenericAlias *p = (VecGenericAlias *)self; + if (p->depth == 0) { + if (init == NULL) { + VecT vec = VecT_New(0, 0, p->item_type); + if (VEC_IS_ERROR(vec)) + return NULL; + return VecT_Box(vec, p->item_type); + } else { + return VecT_FromIterable(p->item_type, init); + } + } else { + if (init == NULL) { + VecNested vec = VecNested_New(0, 0, p->item_type, p->depth); + if (VEC_IS_ERROR(vec)) + return NULL; + return VecNested_Box(vec); + } else { + return VecNested_FromIterable(p->item_type, p->depth, init); + } + } } static int @@ -597,6 +619,26 @@ static PyObject *vec_append(PyObject *self, PyObject *args) if (VEC_IS_ERROR(v)) return NULL; return VecBool_Box(v); + } else if (VecT_Check(vec)) { + VecT v = ((VecTObject *)vec)->vec; + if (!VecT_ItemCheck(v, item, v.buf->item_type)) { + return NULL; + } + VEC_INCREF(v); + v = VecT_Append(v, item, v.buf->item_type); + if (VEC_IS_ERROR(v)) + return NULL; + return VecT_Box(v, v.buf->item_type); + } else if (VecNested_Check(vec)) { + VecNested v = ((VecNestedObject *)vec)->vec; + VecNestedBufItem vecitem; + if (VecNested_UnboxItem(v, item, &vecitem) < 0) + return NULL; + VEC_INCREF(v); + v = VecNested_Append(v, vecitem); + if (VEC_IS_ERROR(v)) + return NULL; + return VecNested_Box(v); } else { PyErr_Format(PyExc_TypeError, "vec argument expected, got %.100s", Py_TYPE(vec)->tp_name); @@ -678,6 +720,26 @@ static PyObject *vec_remove(PyObject *self, PyObject *args) if (VEC_IS_ERROR(v)) return NULL; return VecBool_Box(v); + } else if (VecT_Check(vec)) { + VecT v = ((VecTObject *)vec)->vec; + if (!VecT_ItemCheck(v, item, v.buf->item_type)) { + return NULL; + } + VEC_INCREF(v); + v = VecT_Remove(v, item); + if (VEC_IS_ERROR(v)) + return NULL; + return VecT_Box(v, v.buf->item_type); + } else if (VecNested_Check(vec)) { + VecNested v = ((VecNestedObject *)vec)->vec; + VecNestedBufItem vecitem; + if (VecNested_UnboxItem(v, item, &vecitem) < 0) + return NULL; + VEC_INCREF(v); + v = VecNested_Remove(v, vecitem); + if (VEC_IS_ERROR(v)) + return NULL; + return VecNested_Box(v); } else { PyErr_Format(PyExc_TypeError, "vec argument expected, got %.100s", Py_TYPE(vec)->tp_name); @@ -756,6 +818,38 @@ static PyObject *vec_pop(PyObject *self, PyObject *args) if ((result_item0 = VecBool_Box(r.f0)) == NULL) return NULL; result_item1 = VecBool_BoxItem(r.f1); + } else if (VecT_Check(vec)) { + VecT v = ((VecTObject *)vec)->vec; + VEC_INCREF(v); + VecTPopResult r; + r = VecT_Pop(v, index); + if (VEC_IS_ERROR(r.f0)) + return NULL; + result_item0 = VecT_Box(r.f0, v.buf->item_type); + if (result_item0 == NULL) { + Py_DECREF(r.f1); + return NULL; + } + result_item1 = r.f1; + } else if (VecNested_Check(vec)) { + VecNested v = ((VecNestedObject *)vec)->vec; + VEC_INCREF(v); + VecNestedPopResult r; + r = VecNested_Pop(v, index); + if (VEC_IS_ERROR(r.f0)) + return NULL; + result_item0 = VecNested_Box(r.f0); + if (result_item0 == NULL) { + Py_DECREF(r.f0.buf); + Py_DECREF(r.f1.buf); + return NULL; + } + result_item1 = VecNested_BoxItem(r.f0, r.f1); + if (result_item1 == NULL) { + Py_DECREF(result_item0); + Py_DECREF(r.f1.buf); + return NULL; + } } else { PyErr_Format(PyExc_TypeError, "vec argument expected, got %.100s", Py_TYPE(vec)->tp_name); @@ -780,6 +874,8 @@ static PyObject *vec_pop(PyObject *self, PyObject *args) } static VecCapsule Capsule = { + &Vec_TAPI, + &Vec_NestedAPI, &Vec_I64API, &Vec_I32API, &Vec_I16API, @@ -846,6 +942,16 @@ librt_vecs_module_exec(PyObject *m) if (PyType_Ready(&VecGenericAliasType) < 0) return -1; + if (PyType_Ready(&VecTType) < 0) + return -1; + if (PyType_Ready(&VecTBufType) < 0) + return -1; + + if (PyType_Ready(&VecNestedType) < 0) + return -1; + if (PyType_Ready(&VecNestedBufType) < 0) + return -1; + if (PyType_Ready(&VecI64Type) < 0) return -1; if (PyType_Ready(&VecI64BufType) < 0) diff --git a/mypyc/lib-rt/vecs/librt_vecs.h b/mypyc/lib-rt/vecs/librt_vecs.h index f29d696bac665..25548e4c62df6 100644 --- a/mypyc/lib-rt/vecs/librt_vecs.h +++ b/mypyc/lib-rt/vecs/librt_vecs.h @@ -381,7 +381,72 @@ typedef struct _VecBoolAPI { // iter? } VecBoolAPI; +#ifndef MYPYC_DECLARED_tuple_T2VOO +#define MYPYC_DECLARED_tuple_T2VOO +typedef struct tuple_T2VOO { + VecT f0; + PyObject *f1; +} tuple_T2VOO; +static tuple_T2VOO tuple_undefined_T2VOO = { { -1, NULL } , NULL }; +#endif + +typedef tuple_T2VOO VecTPopResult; + +// vec[T] operations + type objects (stored in a capsule) +// +// T is a class type or class type | None +typedef struct _VecTAPI { + PyTypeObject *boxed_type; + PyTypeObject *buf_type; + VecT (*alloc)(Py_ssize_t, Py_ssize_t, size_t); + PyObject *(*box)(VecT, size_t); + VecT (*unbox)(PyObject *, size_t); + VecT (*convert_from_nested)(VecNestedBufItem); + VecT (*append)(VecT, PyObject *, size_t); + VecTPopResult (*pop)(VecT, Py_ssize_t); + VecT (*remove)(VecT, PyObject *); + // TODO: Py_ssize_t + VecT (*slice)(VecT, int64_t, int64_t); + // PyObject *(*extend)(PyObject *, PyObject *); + // PyObject *(*concat)(PyObject *, PyObject *); + // bool (*contains)(PyObject *, PyObject *); + // iter? +} VecTAPI; + + +#ifndef MYPYC_DECLARED_tuple_T2VvVi +#define MYPYC_DECLARED_tuple_T2VvVi +typedef struct tuple_T2VvVi { + VecNested f0; + VecNestedBufItem f1; +} tuple_T2VvVi; +static tuple_T2VvVi tuple_undefined_T2VvVi = { { -1, NULL } , { -1, NULL } }; +#endif + +typedef tuple_T2VvVi VecNestedPopResult; + +// Nested vec operations + type objects (stored in a capsule) +typedef struct _VecNestedAPI { + PyTypeObject *boxed_type; + PyTypeObject *buf_type; + VecNested (*alloc)(Py_ssize_t, Py_ssize_t, size_t, size_t depth); + PyObject *(*box)(VecNested); + VecNested (*unbox)(PyObject *, size_t, size_t depth); + VecNested (*convert_from_nested)(VecNestedBufItem); + VecNested (*append)(VecNested, VecNestedBufItem); + VecNestedPopResult (*pop)(VecNested, Py_ssize_t); + VecNested (*remove)(VecNested, VecNestedBufItem); + // TODO: Py_ssize_t + VecNested (*slice)(VecNested, int64_t, int64_t); + // PyObject *(*extend)(PyObject *, PyObject *); + // PyObject *(*concat)(PyObject *, PyObject *); + // bool (*contains)(PyObject *, PyObject *); + // iter? +} VecNestedAPI; + typedef struct { + VecTAPI *t; + VecNestedAPI *nested; VecI64API *i64; VecI32API *i32; VecI16API *i16; @@ -407,6 +472,8 @@ extern PyTypeObject VecI16BufType; extern PyTypeObject VecU8BufType; extern PyTypeObject VecFloatBufType; extern PyTypeObject VecBoolBufType; +extern PyTypeObject VecTBufType; +extern PyTypeObject VecNestedBufType; // Wrapper type objects for boxed vec values extern PyTypeObject VecI64Type; @@ -415,6 +482,8 @@ extern PyTypeObject VecI16Type; extern PyTypeObject VecU8Type; extern PyTypeObject VecFloatType; extern PyTypeObject VecBoolType; +extern PyTypeObject VecTType; +extern PyTypeObject VecNestedType; // Type objects corresponding to the 'i64', 'i32', 'i16, and 'u8' types extern PyTypeObject *LibRTVecs_I64TypeObj; @@ -428,6 +497,8 @@ extern VecI16API Vec_I16API; extern VecU8API Vec_U8API; extern VecFloatAPI Vec_FloatAPI; extern VecBoolAPI Vec_BoolAPI; +extern VecTAPI Vec_TAPI; +extern VecNestedAPI Vec_NestedAPI; static inline int Vec_CheckFloatError(PyObject *o) { if (PyFloat_Check(o)) { @@ -614,6 +685,138 @@ VecBool VecBool_Append(VecBool, char x); VecBool VecBool_Remove(VecBool, char x); VecBoolPopResult VecBool_Pop(VecBool v, Py_ssize_t index); +// vec[t] operations + +static inline int VecT_Check(PyObject *o) { + return o->ob_type == &VecTType; +} + +static inline int VecT_ItemCheck(VecT v, PyObject *item, size_t item_type) { + if (PyObject_TypeCheck(item, VEC_ITEM_TYPE(item_type))) { + return 1; + } else if ((item_type & 1) && item == Py_None) { + return 1; + } else { + // TODO: better error message + PyErr_SetString(PyExc_TypeError, "invalid item type"); + return 0; + } +} + +VecT VecT_New(Py_ssize_t size, Py_ssize_t cap, size_t item_type); +PyObject *VecT_FromIterable(size_t item_type, PyObject *iterable); +PyObject *VecT_Box(VecT vec, size_t item_type); +VecT VecT_Append(VecT vec, PyObject *x, size_t item_type); +VecT VecT_Remove(VecT vec, PyObject *x); +VecTPopResult VecT_Pop(VecT v, Py_ssize_t index); + +// Nested vec operations + +static inline int VecNested_Check(PyObject *o) { + return o->ob_type == &VecNestedType; +} + +VecNested VecNested_New(Py_ssize_t size, Py_ssize_t cap, size_t item_type, size_t depth); +PyObject *VecNested_FromIterable(size_t item_type, size_t depth, PyObject *iterable); +PyObject *VecNested_Box(VecNested); +VecNested VecNested_Append(VecNested vec, VecNestedBufItem x); +VecNested VecNested_Remove(VecNested vec, VecNestedBufItem x); +VecNestedPopResult VecNested_Pop(VecNested v, Py_ssize_t index); + +// Return 0 on success, -1 on error. Store unboxed item in *unboxed if successful. +// Return a *borrowed* reference. +static inline int VecNested_UnboxItem(VecNested v, PyObject *item, VecNestedBufItem *unboxed) { + size_t depth = v.buf->depth; + if (depth == 1) { + if (item->ob_type == &VecTType) { + VecNestedObject *o = (VecNestedObject *)item; + if (o->vec.buf->item_type == v.buf->item_type) { + unboxed->len = o->vec.len; + unboxed->buf = (PyObject *)o->vec.buf; + return 0; + } + } else if (item->ob_type == &VecI64Type && v.buf->item_type == VEC_ITEM_TYPE_I64) { + VecI64Object *o = (VecI64Object *)item; + unboxed->len = o->vec.len; + unboxed->buf = (PyObject *)o->vec.buf; + return 0; + } else if (item->ob_type == &VecU8Type && v.buf->item_type == VEC_ITEM_TYPE_U8) { + VecU8Object *o = (VecU8Object *)item; + unboxed->len = o->vec.len; + unboxed->buf = (PyObject *)o->vec.buf; + return 0; + } else if (item->ob_type == &VecFloatType && v.buf->item_type == VEC_ITEM_TYPE_FLOAT) { + VecFloatObject *o = (VecFloatObject *)item; + unboxed->len = o->vec.len; + unboxed->buf = (PyObject *)o->vec.buf; + return 0; + } else if (item->ob_type == &VecI32Type && v.buf->item_type == VEC_ITEM_TYPE_I32) { + VecI32Object *o = (VecI32Object *)item; + unboxed->len = o->vec.len; + unboxed->buf = (PyObject *)o->vec.buf; + return 0; + } else if (item->ob_type == &VecI16Type && v.buf->item_type == VEC_ITEM_TYPE_I16) { + VecI16Object *o = (VecI16Object *)item; + unboxed->len = o->vec.len; + unboxed->buf = (PyObject *)o->vec.buf; + return 0; + } else if (item->ob_type == &VecBoolType && v.buf->item_type == VEC_ITEM_TYPE_BOOL) { + VecBoolObject *o = (VecBoolObject *)item; + unboxed->len = o->vec.len; + unboxed->buf = (PyObject *)o->vec.buf; + return 0; + } + } else if (item->ob_type == &VecNestedType) { + VecNestedObject *o = (VecNestedObject *)item; + if (o->vec.buf->depth == v.buf->depth - 1 + && o->vec.buf->item_type == v.buf->item_type) { + unboxed->len = o->vec.len; + unboxed->buf = (PyObject *)o->vec.buf; + return 0; + } + } + // TODO: better error message + PyErr_SetString(PyExc_TypeError, "invalid item type"); + return -1; +} + +static inline PyObject *VecNested_BoxItem(VecNested v, VecNestedBufItem item) { + if (item.len < 0) + Py_RETURN_NONE; + Py_XINCREF(item.buf); + if (v.buf->depth > 1) { + // Item is a nested vec + VecNested v = { .len = item.len, .buf = (VecNestedBufObject *)item.buf }; + return VecNested_Box(v); + } else { + // Item is a non-nested vec + size_t item_type = v.buf->item_type; + if (item_type == VEC_ITEM_TYPE_I64) { + VecI64 v = { .len = item.len, .buf = (VecI64BufObject *)item.buf }; + return VecI64_Box(v); + } else if (item_type == VEC_ITEM_TYPE_U8) { + VecU8 v = { .len = item.len, .buf = (VecU8BufObject *)item.buf }; + return VecU8_Box(v); + } else if (item_type == VEC_ITEM_TYPE_FLOAT) { + VecFloat v = { .len = item.len, .buf = (VecFloatBufObject *)item.buf }; + return VecFloat_Box(v); + } else if (item_type == VEC_ITEM_TYPE_I32) { + VecI32 v = { .len = item.len, .buf = (VecI32BufObject *)item.buf }; + return VecI32_Box(v); + } else if (item_type == VEC_ITEM_TYPE_I16) { + VecI16 v = { .len = item.len, .buf = (VecI16BufObject *)item.buf }; + return VecI16_Box(v); + } else if (item_type == VEC_ITEM_TYPE_BOOL) { + VecBool v = { .len = item.len, .buf = (VecBoolBufObject *)item.buf }; + return VecBool_Box(v); + } else { + // Generic vec[t] + VecT v = { .len = item.len, .buf = (VecTBufObject *)item.buf }; + return VecT_Box(v, item_type); + } + } +} + // Misc helpers PyObject *Vec_TypeToStr(size_t item_type, size_t depth); diff --git a/mypyc/lib-rt/vecs/vec_nested.c b/mypyc/lib-rt/vecs/vec_nested.c new file mode 100644 index 0000000000000..2cee818bd1ccc --- /dev/null +++ b/mypyc/lib-rt/vecs/vec_nested.c @@ -0,0 +1,504 @@ +#ifdef MYPYC_EXPERIMENTAL +// Implementation of nested vec[t], when t is a vec type. +// +// Examples of types supported: +// - vec[vec[i64]] +// - vec[vec[vec[str]]] +// - vec[vec[str | None]] + +#define PY_SSIZE_T_CLEAN +#include +#include "librt_vecs.h" + +static inline VecNested vec_error() { + VecNested v = { .len = -1 }; + return v; +} + +static inline PyObject *box_vec_item_by_index(VecNested v, Py_ssize_t index) { + return VecNested_BoxItem(v, v.buf->items[index]); +} + +// Alloc a partially initialized vec. Caller *must* initialize len and buf->items of the +// return value. +static VecNested vec_alloc(Py_ssize_t size, size_t item_type, size_t depth) { + VecNestedBufObject *buf = PyObject_GC_NewVar(VecNestedBufObject, &VecNestedBufType, size); + if (buf == NULL) + return vec_error(); + buf->item_type = item_type; + buf->depth = depth; + if (!Vec_IsMagicItemType(item_type)) + Py_INCREF(VEC_BUF_ITEM_TYPE(buf)); + VecNested res = { .buf = buf }; + PyObject_GC_Track(buf); + return res; +} + +// Box a nested vec value, stealing 'vec'. On error, decref 'vec'. +PyObject *VecNested_Box(VecNested vec) { + VecNestedObject *obj = PyObject_GC_New(VecNestedObject, &VecNestedType); + if (obj == NULL) { + VEC_DECREF(vec); + return NULL; + } + obj->vec = vec; + PyObject_GC_Track(obj); + return (PyObject *)obj; +} + +VecNested VecNested_Unbox(PyObject *obj, size_t item_type, size_t depth) { + if (obj->ob_type == &VecNestedType) { + VecNested result = ((VecNestedObject *)obj)->vec; + if (result.buf->item_type == item_type && result.buf->depth == depth) { + VEC_INCREF(result); // TODO: Should we borrow instead? + return result; + } + } + // TODO: Better error message, with name of type + PyErr_SetString(PyExc_TypeError, "vec[t] expected"); + return vec_error(); +} + +VecNested VecNested_ConvertFromNested(VecNestedBufItem item) { + return (VecNested) { item.len, (VecNestedBufObject *)item.buf }; +} + +VecNested VecNested_New(Py_ssize_t size, Py_ssize_t cap, size_t item_type, size_t depth) { + if (cap < size) + cap = size; + VecNested vec = vec_alloc(cap, item_type, depth); + if (VEC_IS_ERROR(vec)) + return vec; + for (Py_ssize_t i = 0; i < cap; i++) { + vec.buf->items[i].len = -1; + vec.buf->items[i].buf = NULL; + } + vec.len = size; + return vec; +} + +static PyObject *vec_repr(PyObject *self) { + VecNested v = ((VecNestedObject *)self)->vec; + return Vec_GenericRepr(self, v.buf->item_type, v.buf->depth, 1); +} + +static PyObject *vec_get_item(PyObject *o, Py_ssize_t i) { + VecNested v = ((VecNestedObject *)o)->vec; + if ((size_t)i < (size_t)v.len) { + return box_vec_item_by_index(v, i); + } else if ((size_t)i + (size_t)v.len < (size_t)v.len) { + return box_vec_item_by_index(v, i + v.len); + } else { + PyErr_SetString(PyExc_IndexError, "index out of range"); + return NULL; + } +} + +VecNested VecNested_Slice(VecNested vec, int64_t start, int64_t end) { + if (start < 0) + start += vec.len; + if (end < 0) + end += vec.len; + if (start < 0) + start = 0; + if (start >= vec.len) + start = vec.len; + if (end < start) + end = start; + if (end > vec.len) + end = vec.len; + int64_t slicelength = end - start; + VecNested res = vec_alloc(slicelength, vec.buf->item_type, vec.buf->depth); + if (VEC_IS_ERROR(res)) + return res; + res.len = slicelength; + for (Py_ssize_t i = 0; i < slicelength; i++) { + VecNestedBufItem item = vec.buf->items[start + i]; + Py_XINCREF(item.buf); + res.buf->items[i] = item; + } + return res; +} + +static PyObject *vec_subscript(PyObject *self, PyObject *item) { + VecNested vec = ((VecNestedObject *)self)->vec; + if (PyIndex_Check(item)) { + Py_ssize_t i = PyNumber_AsSsize_t(item, PyExc_IndexError); + if (i == -1 && PyErr_Occurred()) + return NULL; + if ((size_t)i < (size_t)vec.len) { + return box_vec_item_by_index(vec, i); + } else if ((size_t)i + (size_t)vec.len < (size_t)vec.len) { + return box_vec_item_by_index(vec, i + vec.len); + } else { + PyErr_SetString(PyExc_IndexError, "index out of range"); + return NULL; + } + } else if (PySlice_Check(item)) { + Py_ssize_t start, stop, step; + if (PySlice_Unpack(item, &start, &stop, &step) < 0) + return NULL; + Py_ssize_t slicelength = PySlice_AdjustIndices(vec.len, &start, &stop, step); + VecNested res = vec_alloc(slicelength, vec.buf->item_type, vec.buf->depth); + if (VEC_IS_ERROR(res)) + return NULL; + res.len = slicelength; + Py_ssize_t j = start; + for (Py_ssize_t i = 0; i < slicelength; i++) { + VecNestedBufItem item = vec.buf->items[j]; + Py_INCREF(item.buf); + res.buf->items[i] = item; + j += step; + } + return VecNested_Box(res); + } else { + PyErr_Format(PyExc_TypeError, "vec indices must be integers or slices, not %.100s", + item->ob_type->tp_name); + return NULL; + } +} + +static int vec_ass_item(PyObject *self, Py_ssize_t i, PyObject *o) { + VecNested v = ((VecNestedObject *)self)->vec; + if ((size_t)i + (size_t)v.len < (size_t)v.len) { + i += v.len; + } + if ((size_t)i < (size_t)v.len) { + VecNestedBufItem item; + if (VecNested_UnboxItem(v, o, &item) < 0) + return -1; + VEC_INCREF(item); + VEC_DECREF(v.buf->items[i]); + v.buf->items[i] = item; + return 0; + } else { + PyErr_SetString(PyExc_IndexError, "index out of range"); + return -1; + } +} + +static PyObject *compare_vec_eq(VecNested x, VecNested y, int op) { + int cmp = 1; + PyObject *res; + if (x.len != y.len + || x.buf->item_type != y.buf->item_type + || x.buf->depth != y.buf->depth) { + cmp = 0; + } else { + for (Py_ssize_t i = 0; i < x.len; i++) { + PyObject *x_item = box_vec_item_by_index(x, i); + PyObject *y_item = box_vec_item_by_index(y, i); + int itemcmp = PyObject_RichCompareBool(x_item, y_item, Py_EQ); + Py_DECREF(x_item); + Py_DECREF(y_item); + if (itemcmp < 0) + return NULL; + if (!itemcmp) { + cmp = 0; + break; + } + } + } + if (op == Py_NE) + cmp = cmp ^ 1; + res = cmp ? Py_True : Py_False; + Py_INCREF(res); + return res; +} + +PyObject *vec_richcompare(PyObject *self, PyObject *other, int op) { + PyObject *res; + if (op == Py_EQ || op == Py_NE) { + if (other->ob_type != &VecNestedType) { + res = op == Py_EQ ? Py_False : Py_True; + } else { + return compare_vec_eq(((VecNestedObject *)self)->vec, + ((VecNestedObject *)other)->vec, op); + } + } else + res = Py_NotImplemented; + Py_INCREF(res); + return res; +} + +// Append item to 'vec', stealing 'vec'. Return 'vec' with item appended. +VecNested VecNested_Append(VecNested vec, VecNestedBufItem x) { + Py_ssize_t cap = VEC_CAP(vec); + VEC_INCREF(x); + if (vec.len < cap) { + // Slot may have duplicate ref from prior remove/pop + Py_XDECREF(vec.buf->items[vec.len].buf); + vec.buf->items[vec.len] = x; + vec.len++; + return vec; + } else { + Py_ssize_t new_size = 2 * cap + 1; + // TODO: Avoid initializing to zero here + VecNested new = vec_alloc(new_size, vec.buf->item_type, vec.buf->depth); + if (VEC_IS_ERROR(new)) { + VEC_DECREF(x); + // The input vec is being consumed/stolen by this function, so on error + // we must decref it to avoid leaking the buffer. + VEC_DECREF(vec); + return new; + } + // Copy items to new vec. + memcpy(new.buf->items, vec.buf->items, sizeof(VecNestedBufItem) * vec.len); + // TODO: How to safely represent deleted items? + memset(new.buf->items + vec.len, 0, sizeof(VecNestedBufItem) * (new_size - vec.len)); + // Clear the items in the old vec. We avoid reference count manipulation. + memset(vec.buf->items, 0, sizeof(VecNestedBufItem) * vec.len); + new.buf->items[vec.len] = x; + new.len = vec.len + 1; + VEC_DECREF(vec); + return new; + } +} + +// Remove item from 'vec', stealing 'vec'. Return 'vec' with item removed. +VecNested VecNested_Remove(VecNested self, VecNestedBufItem arg) { + VecNestedBufItem *items = self.buf->items; + + PyObject *boxed_arg = VecNested_BoxItem(self, arg); + if (boxed_arg == NULL) { + // The input self is being consumed/stolen by this function, so on error + // we must decref it to avoid leaking the buffer. + VEC_DECREF(self); + return vec_error(); + } + + for (Py_ssize_t i = 0; i < self.len; i++) { + int match = 0; + if (items[i].len == arg.len && items[i].buf == arg.buf) + match = 1; + else { + PyObject *item = box_vec_item_by_index(self, i); + if (item == NULL) { + Py_DECREF(boxed_arg); + // The input self is being consumed/stolen by this function, so on error + // we must decref it to avoid leaking the buffer. + VEC_DECREF(self); + return vec_error(); + } + int itemcmp = PyObject_RichCompareBool(item, boxed_arg, Py_EQ); + Py_DECREF(item); + if (itemcmp < 0) { + Py_DECREF(boxed_arg); + // The input self is being consumed/stolen by this function, so on error + // we must decref it to avoid leaking the buffer. + VEC_DECREF(self); + return vec_error(); + } + match = itemcmp; + } + if (match) { + if (i < self.len - 1) { + Py_CLEAR(items[i].buf); + for (; i < self.len - 1; i++) { + items[i] = items[i + 1]; + } + Py_XINCREF(items[self.len - 1].buf); + } + self.len--; + Py_DECREF(boxed_arg); + // Return the stolen reference without INCREF + return self; + } + } + Py_DECREF(boxed_arg); + PyErr_SetString(PyExc_ValueError, "vec.remove(x): x not in vec"); + // The input self is being consumed/stolen by this function, so on error + // we must decref it to avoid leaking the buffer. + VEC_DECREF(self); + return vec_error(); +} + +// Pop item from 'vec', stealing 'vec'. Return struct with modified 'vec' and the popped item. +VecNestedPopResult VecNested_Pop(VecNested v, Py_ssize_t index) { + VecNestedPopResult result; + + if (index < 0) + index += v.len; + + if (index < 0 || index >= v.len) { + PyErr_SetString(PyExc_IndexError, "index out of range"); + // The input v is being consumed/stolen by this function, so on error + // we must decref it to avoid leaking the buffer. + VEC_DECREF(v); + result.f0 = vec_error(); + result.f1.len = 0; + result.f1.buf = NULL; + return result; + } + + VecNestedBufItem *items = v.buf->items; + result.f1 = items[index]; + for (Py_ssize_t i = index; i < v.len - 1; i++) + items[i] = items[i + 1]; + if (v.len > 0) + Py_XINCREF(items[v.len - 1].buf); + v.len--; + // Return the stolen reference without INCREF + result.f0 = v; + return result; +} + +static int +VecNested_traverse(VecNestedObject *self, visitproc visit, void *arg) +{ + Py_VISIT(self->vec.buf); + return 0; +} + +static int +VecNested_clear(VecNestedObject *self) +{ + Py_CLEAR(self->vec.buf); + return 0; +} + +static void +VecNested_dealloc(VecNestedObject *self) +{ + PyObject_GC_UnTrack(self); + Py_TRASHCAN_BEGIN(self, VecNested_dealloc) + Py_CLEAR(self->vec.buf); + Py_TYPE(self)->tp_free((PyObject *)self); + Py_TRASHCAN_END +} + +static int +VecNestedBuf_traverse(VecNestedBufObject *self, visitproc visit, void *arg) +{ + if (!Vec_IsMagicItemType(self->item_type)) + Py_VISIT(VEC_BUF_ITEM_TYPE(self)); + for (Py_ssize_t i = 0; i < VEC_BUF_SIZE(self); i++) { + Py_VISIT(self->items[i].buf); + } + return 0; +} + +static inline int +VecNestedBuf_clear(VecNestedBufObject *self) +{ + if (self->item_type && !Vec_IsMagicItemType(self->item_type)) { + Py_DECREF(VEC_BUF_ITEM_TYPE(self)); + self->item_type = 0; + } + for (Py_ssize_t i = 0; i < VEC_BUF_SIZE(self); i++) { + Py_CLEAR(self->items[i].buf); + } + return 0; +} + +static void +VecNestedBuf_dealloc(VecNestedBufObject *self) +{ + PyObject_GC_UnTrack(self); + Py_TRASHCAN_BEGIN(self, VecNestedBuf_dealloc) + VecNestedBuf_clear(self); + Py_TYPE(self)->tp_free((PyObject *)self); + Py_TRASHCAN_END +} + +static Py_ssize_t vec_ext_length(PyObject *o) { + return ((VecNestedObject *)o)->vec.len; +} + +static PyMappingMethods VecNestedMapping = { + .mp_length = vec_ext_length, + .mp_subscript = vec_subscript, +}; + +static PySequenceMethods VecNestedSequence = { + .sq_item = vec_get_item, + .sq_ass_item = vec_ass_item, +}; + +static PyMethodDef vec_methods[] = { + {NULL, NULL, 0, NULL}, /* Sentinel */ +}; + +PyTypeObject VecNestedBufType = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "vecbuf", + .tp_doc = "Internal data buffer used by vec objects", + .tp_basicsize = sizeof(VecNestedBufObject) - sizeof(VecNestedBufItem), + .tp_itemsize = sizeof(VecNestedBufItem), + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, + .tp_traverse = (traverseproc)VecNestedBuf_traverse, + //.tp_new = vecbuf_i64_new, //?? + .tp_free = PyObject_GC_Del, + .tp_clear = (inquiry)VecNestedBuf_clear, + .tp_dealloc = (destructor)VecNestedBuf_dealloc, +}; + +PyTypeObject VecNestedType = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "vec", + .tp_doc = "Mutable sequence-like container optimized for compilation with mypyc", + .tp_basicsize = sizeof(VecNestedObject), + .tp_itemsize = 0, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, + .tp_traverse = (traverseproc)VecNested_traverse, + .tp_clear = (inquiry)VecNested_clear, + .tp_dealloc = (destructor)VecNested_dealloc, + //.tp_free = PyObject_GC_Del, + .tp_repr = (reprfunc)vec_repr, + .tp_as_sequence = &VecNestedSequence, + .tp_as_mapping = &VecNestedMapping, + .tp_richcompare = vec_richcompare, + .tp_methods = vec_methods, + // TODO: free +}; + +PyObject *VecNested_FromIterable(size_t item_type, size_t depth, PyObject *iterable) { + VecNested v = vec_alloc(0, item_type, depth); + if (VEC_IS_ERROR(v)) + return NULL; + v.len = 0; + + PyObject *iter = PyObject_GetIter(iterable); + if (iter == NULL) { + VEC_DECREF(v); + return NULL; + } + PyObject *item; + while ((item = PyIter_Next(iter)) != NULL) { + VecNestedBufItem vecitem; + if (VecNested_UnboxItem(v, item, &vecitem) < 0) { + Py_DECREF(iter); + VEC_DECREF(v); + Py_DECREF(item); + return NULL; + } + v = VecNested_Append(v, vecitem); + Py_DECREF(item); + if (VEC_IS_ERROR(v)) { + Py_DECREF(iter); + VEC_DECREF(v); + return NULL; + } + } + Py_DECREF(iter); + if (PyErr_Occurred()) { + VEC_DECREF(v); + return NULL; + } + return VecNested_Box(v); +} + +VecNestedAPI Vec_NestedAPI = { + &VecNestedType, + &VecNestedBufType, + VecNested_New, + VecNested_Box, + VecNested_Unbox, + VecNested_ConvertFromNested, + VecNested_Append, + VecNested_Pop, + VecNested_Remove, + VecNested_Slice, +}; + +#endif // MYPYC_EXPERIMENTAL diff --git a/mypyc/lib-rt/vecs/vec_t.c b/mypyc/lib-rt/vecs/vec_t.c new file mode 100644 index 0000000000000..a4bf1fa02cd07 --- /dev/null +++ b/mypyc/lib-rt/vecs/vec_t.c @@ -0,0 +1,497 @@ +#ifdef MYPYC_EXPERIMENTAL +// Implementation of generic vec[t], when t is a plain type object (possibly optional). +// +// Examples of types supported: +// +// - vec[str] +// - vec[str | None] +// - vec[object] +// - vec[UserClass] + +#define PY_SSIZE_T_CLEAN +#include +#include "librt_vecs.h" + +static inline VecT vec_error() { + VecT v = { .len = -1 }; + return v; +} + +static inline VecTBufObject *alloc_buf(Py_ssize_t size, size_t item_type) { + VecTBufObject *buf = PyObject_GC_NewVar(VecTBufObject, &VecTBufType, size); + if (buf == NULL) + return NULL; + buf->item_type = item_type; + Py_INCREF(VEC_BUF_ITEM_TYPE(buf)); + PyObject_GC_Track(buf); + return buf; +} + +// Alloc a partially initialized vec. Caller *must* immediately initialize len, and buf->items +// if size > 0. +static VecT vec_alloc(Py_ssize_t size, size_t item_type) { + VecTBufObject *buf; + + if (size == 0) { + buf = NULL; + } else { + buf = alloc_buf(size, item_type); + if (buf == NULL) + return vec_error(); + } + return (VecT) { .buf = buf }; +} + +// Box a VecT value, stealing 'vec'. On failure, return NULL and decref 'vec'. +PyObject *VecT_Box(VecT vec, size_t item_type) { + // An unboxed empty vec may have a NULL buf, but a boxed vec must have it + // allocated, since it contains the item type + if (vec.buf == NULL) { + vec.buf = alloc_buf(0, item_type); + if (vec.buf == NULL) + return NULL; + } + VecTObject *obj = PyObject_GC_New(VecTObject, &VecTType); + if (obj == NULL) { + // vec.buf is always defined, so no need for a NULL check + Py_DECREF(vec.buf); + return NULL; + } + obj->vec = vec; + PyObject_GC_Track(obj); + return (PyObject *)obj; +} + +VecT VecT_Unbox(PyObject *obj, size_t item_type) { + if (obj->ob_type == &VecTType) { + VecT result = ((VecTObject *)obj)->vec; + if (result.buf->item_type == item_type) { + VEC_INCREF(result); // TODO: Should we borrow instead? + return result; + } + } + // TODO: Better error message, with name of type + PyErr_SetString(PyExc_TypeError, "vec[t] expected"); + return vec_error(); +} + +VecT VecT_ConvertFromNested(VecNestedBufItem item) { + return (VecT) { item.len, (VecTBufObject *)item.buf }; +} + +VecT VecT_New(Py_ssize_t size, Py_ssize_t cap, size_t item_type) { + if (cap < size) + cap = size; + VecT vec = vec_alloc(cap, item_type); + if (VEC_IS_ERROR(vec)) + return vec; + for (Py_ssize_t i = 0; i < cap; i++) { + vec.buf->items[i] = NULL; + } + vec.len = size; + return vec; +} + +static PyObject *vec_repr(PyObject *self) { + VecTObject *v = (VecTObject *)self; + return Vec_GenericRepr(self, v->vec.buf->item_type, 0, 1); +} + +static PyObject *vec_get_item(PyObject *o, Py_ssize_t i) { + VecT v = ((VecTObject *)o)->vec; + if ((size_t)i < (size_t)v.len) { + PyObject *item = v.buf->items[i]; + Py_INCREF(item); + return item; + } else if ((size_t)i + (size_t)v.len < (size_t)v.len) { + PyObject *item = v.buf->items[i + v.len]; + Py_INCREF(item); + return item; + } else { + PyErr_SetString(PyExc_IndexError, "index out of range"); + return NULL; + } +} + +VecT VecT_Slice(VecT vec, int64_t start, int64_t end) { + if (start < 0) + start += vec.len; + if (end < 0) + end += vec.len; + if (start < 0) + start = 0; + if (start >= vec.len) + start = vec.len; + if (end < start) + end = start; + if (end > vec.len) + end = vec.len; + int64_t slicelength = end - start; + VecT res = vec_alloc(slicelength, vec.buf->item_type); + if (VEC_IS_ERROR(res)) + return res; + res.len = slicelength; + for (Py_ssize_t i = 0; i < slicelength; i++) { + PyObject *item = vec.buf->items[start + i]; + Py_INCREF(item); + res.buf->items[i] = item; + } + return res; +} + +static PyObject *vec_subscript(PyObject *self, PyObject *item) { + VecT vec = ((VecTObject *)self)->vec; + if (PyIndex_Check(item)) { + Py_ssize_t i = PyNumber_AsSsize_t(item, PyExc_IndexError); + if (i == -1 && PyErr_Occurred()) + return NULL; + if ((size_t)i < (size_t)vec.len) { + PyObject *result = vec.buf->items[i]; + Py_INCREF(result); + return result; + } else if ((size_t)i + (size_t)vec.len < (size_t)vec.len) { + PyObject *result = vec.buf->items[i + vec.len]; + Py_INCREF(result); + return result; + } else { + PyErr_SetString(PyExc_IndexError, "index out of range"); + return NULL; + } + } else if (PySlice_Check(item)) { + Py_ssize_t start, stop, step; + if (PySlice_Unpack(item, &start, &stop, &step) < 0) + return NULL; + Py_ssize_t slicelength = PySlice_AdjustIndices(vec.len, &start, &stop, step); + VecT res = vec_alloc(slicelength, vec.buf->item_type); + if (VEC_IS_ERROR(res)) + return NULL; + res.len = slicelength; + Py_ssize_t j = start; + for (Py_ssize_t i = 0; i < slicelength; i++) { + PyObject *item = vec.buf->items[j]; + Py_INCREF(item); + res.buf->items[i] = item; + j += step; + } + PyObject *result = VecT_Box(res, vec.buf->item_type); + if (result == NULL) { + VEC_DECREF(res); + } + return result; + } else { + PyErr_Format(PyExc_TypeError, "vec indices must be integers or slices, not %.100s", + item->ob_type->tp_name); + return NULL; + } +} + +static int vec_ass_item(PyObject *self, Py_ssize_t i, PyObject *o) { + VecT v = ((VecTObject *)self)->vec; + if (!VecT_ItemCheck(v, o, v.buf->item_type)) + return -1; + if ((size_t)i < (size_t)v.len) { + PyObject *old = v.buf->items[i]; + Py_INCREF(o); + v.buf->items[i] = o; + Py_XDECREF(old); + return 0; + } else if ((size_t)i + (size_t)v.len < (size_t)v.len) { + PyObject *old = v.buf->items[i + v.len]; + Py_INCREF(o); + v.buf->items[i + v.len] = o; + Py_XDECREF(old); + return 0; + } else { + PyErr_SetString(PyExc_IndexError, "index out of range"); + return -1; + } +} + +static PyObject *vec_richcompare(PyObject *self, PyObject *other, int op) { + PyObject *res; + if (op == Py_EQ || op == Py_NE) { + if (other->ob_type != &VecTType) { + res = op == Py_EQ ? Py_False : Py_True; + } else { + VecT x = ((VecTObject *)self)->vec; + VecT y = ((VecTObject *)other)->vec; + if (x.buf->item_type != y.buf->item_type) { + res = op == Py_EQ ? Py_False : Py_True; + } else { + // TODO: why pointers to len? + return Vec_GenericRichcompare(&x.len, x.buf->items, &y.len, y.buf->items, op); + } + } + } else + res = Py_NotImplemented; + Py_INCREF(res); + return res; +} + +// Append item to 'vec', stealing 'vec'. Return 'vec' with item appended. +VecT VecT_Append(VecT vec, PyObject *x, size_t item_type) { + if (vec.buf == NULL) { + VecT new = vec_alloc(1, item_type); + if (VEC_IS_ERROR(new)) + return new; + Py_INCREF(x); + new.len = 1; + new.buf->items[0] = x; + return new; + } + Py_ssize_t cap = VEC_CAP(vec); + Py_INCREF(x); + if (vec.len < cap) { + // Slot may have duplicate ref from prior remove/pop + Py_XSETREF(vec.buf->items[vec.len], x); + vec.len++; + return vec; + } else { + Py_ssize_t new_size = 2 * cap + 1; + // TODO: Avoid initializing to zero here + VecT new = vec_alloc(new_size, vec.buf->item_type); + if (VEC_IS_ERROR(new)) { + Py_DECREF(x); + // The input vec is being consumed/stolen by this function, so on error + // we must decref it to avoid leaking the buffer. + VEC_DECREF(vec); + return new; + } + // Copy items to new vec. + memcpy(new.buf->items, vec.buf->items, sizeof(PyObject *) * vec.len); + memset(new.buf->items + vec.len, 0, sizeof(PyObject *) * (new_size - vec.len)); + // Clear the items in the old vec. We avoid reference count manipulation. + memset(vec.buf->items, 0, sizeof(PyObject *) * vec.len); + new.buf->items[vec.len] = x; + new.len = vec.len + 1; + VEC_DECREF(vec); + return new; + } +} + +// Remove item from 'vec', stealing 'vec'. Return 'vec' with item removed. +VecT VecT_Remove(VecT v, PyObject *arg) { + PyObject **items = v.buf->items; + for (Py_ssize_t i = 0; i < v.len; i++) { + int match = 0; + if (items[i] == arg) + match = 1; + else { + int itemcmp = PyObject_RichCompareBool(items[i], arg, Py_EQ); + if (itemcmp < 0) { + // The input v is being consumed/stolen by this function, so on error + // we must decref it to avoid leaking the buffer. + VEC_DECREF(v); + return vec_error(); + } + match = itemcmp; + } + if (match) { + if (i < v.len - 1) { + Py_CLEAR(items[i]); + for (; i < v.len - 1; i++) { + items[i] = items[i + 1]; + } + // Keep a duplicate item, since there could be another reference + // to the buffer with a longer length, and they expect a valid reference. + Py_XINCREF(items[v.len - 1]); + } + v.len--; + // Return the stolen reference without INCREF + return v; + } + } + PyErr_SetString(PyExc_ValueError, "vec.remove(x): x not in vec"); + // The input v is being consumed/stolen by this function, so on error + // we must decref it to avoid leaking the buffer. + VEC_DECREF(v); + return vec_error(); +} + +// Pop item from 'vec', stealing 'vec'. Return struct with modified 'vec' and the popped item. +VecTPopResult VecT_Pop(VecT v, Py_ssize_t index) { + VecTPopResult result; + + if (index < 0) + index += v.len; + + if (index < 0 || index >= v.len) { + PyErr_SetString(PyExc_IndexError, "index out of range"); + // The input v is being consumed/stolen by this function, so on error + // we must decref it to avoid leaking the buffer. + VEC_DECREF(v); + result.f0 = vec_error(); + result.f1 = NULL; + return result; + } + + PyObject **items = v.buf->items; + result.f1 = items[index]; + for (Py_ssize_t i = index; i < v.len - 1; i++) + items[i] = items[i + 1]; + // Keep duplicate item, since there could be another reference + // to the buffer with a longer length, and they expect a valid reference. + Py_XINCREF(items[v.len - 1]); + v.len--; + // Return the stolen reference without INCREF + result.f0 = v; + return result; +} + +static int +VecT_traverse(VecTObject *self, visitproc visit, void *arg) +{ + Py_VISIT(self->vec.buf); + return 0; +} + +static int +VecT_clear(VecTObject *self) +{ + Py_CLEAR(self->vec.buf); + return 0; +} + +static void +VecT_dealloc(VecTObject *self) +{ + PyObject_GC_UnTrack(self); + Py_TRASHCAN_BEGIN(self, VecT_dealloc) + Py_CLEAR(self->vec.buf); + Py_TYPE(self)->tp_free((PyObject *)self); + Py_TRASHCAN_END +} + +static int +VecTBuf_traverse(VecTBufObject *self, visitproc visit, void *arg) +{ + Py_VISIT(VEC_BUF_ITEM_TYPE(self)); + for (Py_ssize_t i = 0; i < VEC_BUF_SIZE(self); i++) { + Py_VISIT(self->items[i]); + } + return 0; +} + +static inline int +VecTBuf_clear(VecTBufObject *self) +{ + if (self->item_type) { + Py_DECREF(VEC_BUF_ITEM_TYPE(self)); + self->item_type = 0; + } + for (Py_ssize_t i = 0; i < VEC_BUF_SIZE(self); i++) { + Py_CLEAR(self->items[i]); + } + return 0; +} + +static void +VecTBuf_dealloc(VecTBufObject *self) +{ + PyObject_GC_UnTrack(self); + Py_TRASHCAN_BEGIN(self, VecTBuf_dealloc) + VecTBuf_clear(self); + Py_TYPE(self)->tp_free((PyObject *)self); + Py_TRASHCAN_END +} + +static Py_ssize_t vec_length(PyObject *o) { + return ((VecTObject *)o)->vec.len; +} + +static PyMappingMethods VecTMapping = { + .mp_length = vec_length, + .mp_subscript = vec_subscript, +}; + +static PySequenceMethods VecTSequence = { + .sq_item = vec_get_item, + .sq_ass_item = vec_ass_item, +}; + +static PyMethodDef vec_methods[] = { + {NULL, NULL, 0, NULL}, /* Sentinel */ +}; + +PyTypeObject VecTBufType = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "vecbuf", + .tp_doc = "Internal data buffer used by vec objects", + .tp_basicsize = sizeof(VecTBufObject) - sizeof(PyObject *), + .tp_itemsize = sizeof(PyObject *), + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, + .tp_traverse = (traverseproc)VecTBuf_traverse, + //.tp_new = vecbuf_i64_new, //?? + .tp_free = PyObject_GC_Del, + .tp_clear = (inquiry)VecTBuf_clear, + .tp_dealloc = (destructor)VecTBuf_dealloc, +}; + +PyTypeObject VecTType = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "vec", + .tp_doc = "Mutable sequence-like container optimized for compilation with mypyc", + .tp_basicsize = sizeof(VecTObject), + .tp_itemsize = 0, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, + .tp_traverse = (traverseproc)VecT_traverse, + .tp_clear = (inquiry)VecT_clear, + .tp_dealloc = (destructor)VecT_dealloc, + //.tp_free = PyObject_GC_Del, + .tp_repr = (reprfunc)vec_repr, + .tp_as_sequence = &VecTSequence, + .tp_as_mapping = &VecTMapping, + .tp_richcompare = vec_richcompare, + .tp_methods = vec_methods, + // TODO: free +}; + +PyObject *VecT_FromIterable(size_t item_type, PyObject *iterable) { + VecT v = vec_alloc(0, item_type); + if (VEC_IS_ERROR(v)) + return NULL; + v.len = 0; + + PyObject *iter = PyObject_GetIter(iterable); + if (iter == NULL) { + VEC_DECREF(v); + return NULL; + } + PyObject *item; + while ((item = PyIter_Next(iter)) != NULL) { + if (!VecT_ItemCheck(v, item, item_type)) { + Py_DECREF(iter); + VEC_DECREF(v); + Py_DECREF(item); + return NULL; + } + v = VecT_Append(v, item, item_type); + Py_DECREF(item); + if (VEC_IS_ERROR(v)) { + Py_DECREF(iter); + VEC_DECREF(v); + return NULL; + } + } + Py_DECREF(iter); + if (PyErr_Occurred()) { + VEC_DECREF(v); + return NULL; + } + return VecT_Box(v, item_type); +} + +VecTAPI Vec_TAPI = { + &VecTType, + &VecTBufType, + VecT_New, + VecT_Box, + VecT_Unbox, + VecT_ConvertFromNested, + VecT_Append, + VecT_Pop, + VecT_Remove, + VecT_Slice, +}; + +#endif // MYPYC_EXPERIMENTAL diff --git a/mypyc/lib-rt/vecs/vec_template.c b/mypyc/lib-rt/vecs/vec_template.c index 864074cd10878..9ae50f1c392c8 100644 --- a/mypyc/lib-rt/vecs/vec_template.c +++ b/mypyc/lib-rt/vecs/vec_template.c @@ -356,7 +356,7 @@ static PyMethodDef vec_methods[] = { PyTypeObject BUF_TYPE = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "vecbuf[" ITEM_TYPE_STR "]", - .tp_doc = "vec doc", + .tp_doc = "Internal data buffer used by vec objects", .tp_basicsize = sizeof(BUF_OBJECT) - sizeof(ITEM_C_TYPE), .tp_itemsize = sizeof(ITEM_C_TYPE), .tp_flags = Py_TPFLAGS_DEFAULT, @@ -367,7 +367,7 @@ PyTypeObject BUF_TYPE = { PyTypeObject VEC_TYPE = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "vec[" ITEM_TYPE_STR "]", - .tp_doc = "vec doc", + .tp_doc = "Mutable sequence-like container optimized for compilation with mypyc", .tp_basicsize = sizeof(VEC_OBJECT), .tp_itemsize = 0, .tp_flags = Py_TPFLAGS_DEFAULT, diff --git a/mypyc/test-data/run-vecs-i64-interp.test b/mypyc/test-data/run-vecs-i64-interp.test index a1dcde6219c1e..52d816881b66b 100644 --- a/mypyc/test-data/run-vecs-i64-interp.test +++ b/mypyc/test-data/run-vecs-i64-interp.test @@ -187,6 +187,8 @@ def test_equality_different_types() -> None: assert_neq(vec[i64]([1]), None) assert_neq(vec[i64]([1]), [1]) assert_neq(vec[i64]([1]), (1,)) + assert_neq(vec[i64]([1]), vec[object]([1])) + assert_neq(vec[i64](), vec[object]()) def assert_eq(x: object, y: object) -> None: assert x == y diff --git a/mypyc/test-data/run-vecs-misc-interp.test b/mypyc/test-data/run-vecs-misc-interp.test index 459822d6776d0..6a1b11dc738fd 100644 --- a/mypyc/test-data/run-vecs-misc-interp.test +++ b/mypyc/test-data/run-vecs-misc-interp.test @@ -20,7 +20,6 @@ vec: Any = librt.vecs.vec append: Any = librt.vecs.append remove: Any = librt.vecs.remove pop: Any = librt.vecs.pop -getsizeof: Any = getattr(sys, "getsizeof") u8: Any = mypy_extensions.u8 i16: Any = mypy_extensions.i16 i32: Any = mypy_extensions.i32 diff --git a/mypyc/test-data/run-vecs-nested-interp.test b/mypyc/test-data/run-vecs-nested-interp.test new file mode 100644 index 0000000000000..dee037e14279f --- /dev/null +++ b/mypyc/test-data/run-vecs-nested-interp.test @@ -0,0 +1,319 @@ +[case testLibrtVecsNestedInterpreted_librt_experimental] +# Test cases for nested vecs, using generic operations (simulates use from interpreted code). + +import sys +from typing import Any +import typing + +import librt.vecs +import mypy_extensions + +from testutil import assertRaises, is_gil_disabled, is_64_bit_platform + +vec: Any = librt.vecs.vec +append: Any = librt.vecs.append +remove: Any = librt.vecs.remove +pop: Any = librt.vecs.pop +getsizeof: Any = getattr(sys, "getsizeof") +i64: Any = mypy_extensions.i64 + +# Work around test stub limitations +Optional: Any = getattr(getattr(sys, "modules")["typing"], "Optional") + + +def test_vec_indexing() -> None: + assert type(vec[vec[str]]) is type(vec[str]) + assert type(vec[vec[Optional[str]]]) is type(vec[str]) + assert type(vec[vec[i64]]) is type(vec[str]) + assert type(vec[vec[vec[vec[vec[vec[str]]]]]]) is type(vec[str]) + with assertRaises(TypeError): + vec[Optional[vec[str]]] + with assertRaises(TypeError): + vec[Optional[vec[Optional[str]]]] + +def test_type_str() -> None: + assert str(vec[Optional[str]]) == "" + assert str(vec[vec[str]]) == "" + assert str(vec[vec[i64]]) == "" + assert str(vec[vec[vec[vec[vec[str]]]]]) == "" + assert str( + vec[vec[vec[Optional[str]]]]) == "" + +def test_construct_empty_nested() -> None: + v = vec[vec[str]]() + assert len(v) == 0 + v2 = vec[vec[vec[vec[vec[str]]]]]() + assert len(v2) == 0 + v3 = vec[vec[i64]]() + assert len(v3) == 0 + +def test_construct_empty_nested_optional() -> None: + v = vec[vec[Optional[str]]]() + assert len(v) == 0 + +def test_construct_from_initializer_nested() -> None: + v = vec[vec[str]]([vec[str](['xyz'])]) + assert len(v) == 1 + assert v[0][0] == 'xyz' + with assertRaises(TypeError): + vec[str](1) + with assertRaises(TypeError): + vec[str]([1]) + with assertRaises(TypeError): + vec[str]([vec[bytes]()]) + +def test_repr() -> None: + assert str(vec[vec[str]]()) == "vec[vec[str]]([])" + assert str(vec[vec[Optional[str]]]()) == "vec[vec[str | None]]([])" + +def test_append_nested() -> None: + v = vec[vec[str]]() + vv = append(vec[str](), '1') + v = append(v, vv) + assert str(v) == "vec[vec[str]]([['1']])" + for n in range(2, 10): + vv = append(vec[str](), str(n)) + v = append(v, vv) + assert str(v) == ( + "vec[vec[str]]([['1'], ['2'], ['3'], ['4'], ['5'], ['6'], ['7'], ['8'], ['9']])" + ) + +def test_append_i64() -> None: + v = vec[vec[i64]]() + vv = append(vec[i64](), 1) + v = append(v, vv) + assert str(v) == "vec[vec[i64]]([[1]])" + for n in range(2, 10): + vv = append(vec[i64](), n) + v = append(v, vv) + assert str(v) == ( + "vec[vec[i64]]([[1], [2], [3], [4], [5], [6], [7], [8], [9]])" + ) + +def test_append_nested_optional() -> None: + v = vec[vec[Optional[str]]]() + v = append(v, vec[Optional[str]]()) + v = append(v, vec[Optional[str]](['x', None])) + assert str(v) == "vec[vec[str | None]]([[], ['x', None]])" + +def test_size() -> None: + # Only test on 64-bit, non-free-threading builds + if not is_gil_disabled() and is_64_bit_platform(): + v = vec[vec[str]]() + assert getsizeof(v) == 48 + v = append(v, vec[str]()) + assert getsizeof(v) == 48 + +def test_get_item() -> None: + v = append(vec[vec[str]](), append(vec[str](), 'x')) + assert repr(v[0]) == "vec[str](['x'])" + v = append(v, append(vec[str](), 'y')) + assert repr(v[0]) == "vec[str](['x'])" + assert repr(v[1]) == "vec[str](['y'])" + vv = append(vec[vec[Optional[str]]](), vec[Optional[str]]()) + assert vv[0] == vec[Optional[str]]() + +def test_get_item_error() -> None: + v = append(vec[vec[str]](), vec[str](['1'])) + v = append(v, vec[str](['2'])) + v = append(v, vec[str](['3'])) + with assertRaises(IndexError): + v[3] + with assertRaises(IndexError): + v[4] + n = 2 ** 100 + with assertRaises(IndexError): + v[n] + +def test_get_item_negative() -> None: + v = vec[vec[str]]([vec[str](['2']), vec[str](['5']), vec[str](['7'])]) + assert v[-1] == vec[str](['7']) + assert v[-2] == vec[str](['5']) + assert v[-3] == vec[str](['2']) + with assertRaises(IndexError): + v[-4] + with assertRaises(IndexError): + v[-2**62] + +def test_len() -> None: + v = vec[vec[str]]() + assert len(v) == 0 + v = append(v, vec[str](['1'])) + assert len(v) == 1 + v = append(v, vec[str]()) + assert len(v) == 2 + for i in range(100): + v = append(v, vec[str](['i'])) + assert len(v) == i + 3 + +def test_set_item() -> None: + v = vec[vec[str]]() + v = append(v, vec[str](['1'])) + v = append(v, vec[str](['2'])) + v[0] = vec[str](['3']) + assert v[0] == vec[str](['3']) + assert v[1] == vec[str](['2']) + v[1] = vec[str](['5']) + assert v[0] == vec[str](['3']) + assert v[1] == vec[str](['5']) + +def test_set_item_error_nested() -> None: + v = append(vec[vec[str]](), vec[str]()) + v = append(v, vec[str]()) + v = append(v, vec[str]()) + with assertRaises(IndexError): + v[3] = vec[str]() + with assertRaises(TypeError): + v[0] = 'x' + with assertRaises(TypeError): + v[0] = vec[vec[str]]() + with assertRaises(TypeError): + v[0] = vec[Optional[str]]() + assert repr(v[0]) == 'vec[str]([])' + +def test_set_item_negative() -> None: + v = vec[vec[str]]() + v = append(v, vec[str](['1'])) + v = append(v, vec[str](['2'])) + v[-2] = vec[str](['3']) + assert v[0] == vec[str](['3']) + assert v[1] == vec[str](['2']) + v[-1] = vec[str](['5']) + assert v[0] == vec[str](['3']) + assert v[1] == vec[str](['5']) + with assertRaises(IndexError): + v[-3] = vec[str](['5']) + +def test_equality() -> None: + assert_eq(vec[vec[str]](), vec[vec[str]]()) + assert_eq(vec[vec[str]]([vec[str](['x'])]), vec[vec[str]]([vec[str](['x'])])) + assert_neq(vec[vec[str]]([vec[str](['x'])]), vec[vec[str]]()) + assert_neq(vec[vec[str]]([vec[str](['x'])]), vec[vec[str]]([vec[str](['y'])])) + +def test_equality_different_types() -> None: + assert_neq(vec[vec[str]](), vec[str]()) + assert_neq(vec[vec[str]](), vec[vec[vec[str]]]()) + assert_neq(vec[vec[Optional[str]]](), vec[vec[str]]()) + +def assert_eq(x: object, y: object) -> None: + assert x == y + assert y == x + b = x != y + assert not b + b = y != x + assert not b + +def assert_neq(x: object, y: object) -> None: + assert x != y + assert y != x + b = x == y + assert not b + b = y == x + assert not b + +def test_slicing() -> None: + v = vec[vec[str]](vec[str]([str(i)]) for i in range(5)) + assert v[1:4] == vec[vec[str]]([v[1], v[2], v[3]]) + assert v[2:-1] == vec[vec[str]]([v[2], v[3]]) + assert v[-2:-1] == vec[vec[str]]([v[3]]) + assert v[1:] == vec[vec[str]]([v[1], v[2], v[3], v[4]]) + assert v[:-1] == vec[vec[str]]([v[0], v[1], v[2], v[3]]) + assert v[:] == v + assert v[:] is not v + assert v[0:5] ==v + assert v[0:5] is not v + assert v[2:100] == vec[vec[str]]([v[2], v[3], v[4]]) + assert v[-100:2] == vec[vec[str]]([v[0], v[1]]) + assert v[5:100] == vec[vec[str]]([]) + assert v[50:100] == vec[vec[str]]([]) + assert v[-100:-50] == vec[vec[str]]([]) + +def test_slicing_with_step() -> None: + v = vec[vec[str]](vec[str]([str(i)]) for i in range(10)) + assert v[1:5:2] == vec[vec[str]]([v[1], v[3]]) + assert v[1:6:2] == vec[vec[str]]([v[1], v[3], v[5]]) + assert v[5:1:-2] == vec[vec[str]]([v[5], v[3]]) + assert v[6:1:-2] == vec[vec[str]]([v[6], v[4], v[2]]) + v = vec[vec[str]](vec[str]([str(i)]) for i in range(5)) + assert v[::-1] == vec[vec[str]]([v[4], v[3], v[2], v[1], v[0]]) + with assertRaises(ValueError): + v[1:3:0] + +def test_slicing_with_different_item_types() -> None: + v = vec[Optional[str]](['x', None, 'y']) + assert v[1:] == vec[Optional[str]]([None, 'y']) + vv = vec[vec[str]]([vec[str](['x']), vec[str](['y'])]) + assert vv[1:] == vec[vec[str]]([vec[str](['y'])]) + +def test_remove() -> None: + a = ['4x', '7x', '9x'] + vv = [vec[str]([s]) for s in a] + for i, s in enumerate(a): + v = vec[vec[str]](vv) + v = remove(v, vv[i]) + assert v == vec[vec[str]]([j for j in vv if j != vv[i]]) + v = vec[vec[str]](vv) + # Make sure we have a different object identity + v = remove(v, vec[str]([a[i]])) + assert v == vec[vec[str]]([j for j in vv if j != vv[i]]) + v = vec[vec[str]](vv) + v = remove(v, vv[0]) + v = remove(v, vv[1]) + v = remove(v, vv[2]) + assert v == vec[vec[str]]() + with assertRaises(ValueError): + remove(v, vec[str](['4'])) + v = append(v, vec[str](['5'])) + with assertRaises(ValueError): + remove(v, vec[str](['7'])) + v = remove(v, vec[str](['5'])) + assert len(v) == 0 + v = v0 = vec[vec[str]]([vec[str](['x']) for _ in range(3)]) + v = remove(v, vec[str](['x'])) + assert v == v0[1:] + v = remove(v, vec[str](['x'])) + assert v == v0[2:] + v = remove(v, vec[str](['x'])) + assert v == vec[vec[str]]() + vb: Any = vec[bytes]([b'x']) + with assertRaises(TypeError): + remove(v, vb) + +def test_pop_last() -> None: + v = vec[vec[str]]([vec[str](['4']), vec[str](['5']), vec[str](['9'])]) + v, item = pop(v) + assert item == vec[str](['9']) + assert v == vec[vec[str]]([vec[str](['4']), vec[str](['5'])]) + v, item = pop(v) + assert item == vec[str](['5']) + assert v == vec[vec[str]]([vec[str](['4'])]) + v, item = pop(v) + assert item == vec[str](['4']) + assert v == vec[vec[str]]() + with assertRaises(IndexError): + pop(v) + +def test_pop_index() -> None: + v = vec[vec[str]](vec[str]([s]) for s in ['4', '7', '9', '15', '22']) + v, item = pop(v, 0) + assert item == vec[str](['4']) + assert v == vec[vec[str]](vec[str]([s]) for s in ['7', '9', '15', '22']) + v, item = pop(v, -1) + assert item == vec[str](['22']) + assert v == vec[vec[str]](vec[str]([s]) for s in ['7', '9', '15']) + v, item = pop(v, 1) + assert item == vec[str](['9']) + assert v == vec[vec[str]](vec[str]([s]) for s in ['7', '15']) + + with assertRaises(IndexError): + pop(v, 2) + + with assertRaises(IndexError): + pop(v, -3) + + v, item = pop(v, -2) + assert item == vec[str](['7']) + assert v == vec[vec[str]]([vec[str](['15'])]) + v, item = pop(v, 0) + assert item == vec[str](['15']) + assert v == vec[vec[str]]() diff --git a/mypyc/test-data/run-vecs-t-interp.test b/mypyc/test-data/run-vecs-t-interp.test new file mode 100644 index 0000000000000..41cab2cee0dc1 --- /dev/null +++ b/mypyc/test-data/run-vecs-t-interp.test @@ -0,0 +1,459 @@ +[case testLibrtVecsTInterpreted_librt_experimental] +# Test cases for vec[], using generic operations (simulates use from interpreted code). +import sys +from typing import cast, List, Any +import typing + +import librt.vecs +import mypy_extensions + +from testutil import assertRaises, is_gil_disabled, is_64_bit_platform +from helpers import ClassWithCustomMeta + +vec: Any = librt.vecs.vec +append: Any = librt.vecs.append +remove: Any = librt.vecs.remove +pop: Any = librt.vecs.pop +getsizeof: Any = getattr(sys, "getsizeof") +i64: Any = mypy_extensions.i64 +version_info: Any = sys.version_info + +# Work around test stub limitations +Optional: Any = getattr(getattr(sys, "modules")["typing"], "Optional") + +def assert_eq(x: object, y: object) -> None: + assert x == y + assert y == x + b = x != y + assert not b + b = y != x + assert not b + +def assert_neq(x: object, y: object) -> None: + assert x != y + assert y != x + b = x == y + assert not b + b = y == x + assert not b + +@mypy_extensions.mypyc_attr(native_class=False) +class StringSubclass(str): + pass + +class ClassWithRaisingEq: + def __eq__(self, other): + raise RuntimeError() + +def test_vec_indexing() -> None: + assert vec[str] is not vec[i64] + assert type(vec[str]) is not type + assert type(vec[Optional[str]]) is type(vec[str]) + +def test_type_str() -> None: + assert str(vec[str]) == "" + assert str(vec[Optional[str]]) == "" + +def test_construct_empty() -> None: + v = vec[str]() + assert len(v) == 0 + v2 = vec[Optional[str]]() + assert len(v2) == 0 + assert type(v) is type(v2) + +def test_construct_or_union() -> None: + if version_info >= (3, 10): + v = vec[str | None]() + assert len(v) == 0 + assert str(v) == 'vec[str | None]([])' + +def test_construct_from_initializer() -> None: + v = vec[str](['xyz']) + assert len(v) == 1 + assert v[0] == 'xyz' + v = vec[str](x + '!' for x in ['x', '', 'foo', 'bar']) + assert len(v) == 4 + assert v[0] == 'x!' + assert v[1] == '!' + assert v[2] == 'foo!' + assert v[3] == 'bar!' + with assertRaises(TypeError): + vec[str](1) + with assertRaises(TypeError): + vec[str]([1]) + with assertRaises(TypeError): + vec[str]([None]) + +def test_construct_from_initializer_optional() -> None: + v = vec[Optional[str]]([None, 'xyz']) + assert len(v) == 2 + assert v[0] is None + assert v[1] == 'xyz' + v = vec[Optional[str]](x for x in ['x', None, 'foo', 'bar']) + assert len(v) == 4 + assert v[0] == 'x' + assert v[1] is None + assert v[2] == 'foo' + assert v[3] == 'bar' + with assertRaises(TypeError): + vec[Optional[str]](1) + with assertRaises(TypeError): + vec[Optional[str]]([1]) + +def test_append() -> None: + v = vec[str]() + v = append(v, '1') + assert str(v) == "vec[str](['1'])" + for n in range(2, 10): + v = append(v, str(n)) + assert str(v) == "vec[str](['1', '2', '3', '4', '5', '6', '7', '8', '9'])" + + v2 = vec[Optional[str]]() + v2 = append(v2, 'x') + v2 = append(v2, None) + assert str(v2) == "vec[str | None](['x', None])" + +def test_append_error() -> None: + v: Any = vec[str]() + with assertRaises(TypeError): + v = append(v, b'x') + with assertRaises(TypeError): + v = append(v, ['x']) + with assertRaises(TypeError): + v = append(v, None) + assert len(v) == 0 + + v2: Any = vec[Optional[str]]() + with assertRaises(TypeError): + v2 = append(v2, b'x') + +def test_repr() -> None: + assert str(vec[str]()) == "vec[str]([])" + assert str(vec[Optional[str]]([None, 'x'])) == "vec[str | None]([None, 'x'])" + +def test_size() -> None: + # Only test on 64-bit, non-free-threading builds + if not is_gil_disabled() and is_64_bit_platform(): + v = vec[str]() + assert getsizeof(v) == 48 + v = append(v, '123') + assert getsizeof(v) == 48 + +def test_get_item() -> None: + v = append(vec[str](), '44') + assert v[0] == '44' + v = append(v, '56') + assert v[0] == '44' + assert v[1] == '56' + + v2 = vec[Optional[str]]([None, '12']) + assert v2[0] is None + assert v2[1] == '12' + +def test_get_item_error() -> None: + v = append(vec[str](), '1') + v = append(v, '2') + v = append(v, '3') + with assertRaises(IndexError): + v[3] + with assertRaises(IndexError): + v[-4] + n = 2 ** 100 + with assertRaises(IndexError): + v[n] + +def test_get_item_negative() -> None: + v = vec[str](['2', '5', '7']) + assert v[-1] == '7' + assert v[-2] == '5' + assert v[-3] == '2' + with assertRaises(IndexError): + v[-4] + with assertRaises(IndexError): + v[-2**62] + +def test_len() -> None: + v = vec[str]() + assert len(v) == 0 + v = append(v, '1') + assert len(v) == 1 + v = append(v, '2') + assert len(v) == 2 + for i in range(100): + v = append(v, 'i') + assert len(v) == i + 3 + +def test_set_item() -> None: + v = vec[str]() + v = append(v, '1') + v = append(v, '2') + v[0] = '3' + assert v[0] == '3' + assert v[1] == '2' + v[1] = '5' + assert v[0] == '3' + assert v[1] == '5' + +def test_set_item_optional() -> None: + v = vec[Optional[str]]() + v = append(v, '1') + v = append(v, None) + v[0] = '3' + assert v[0] == '3' + assert v[1 + int()] is None # TODO: The '+ int()' is due to mypy type narrowing bug + v[1] = '5' + assert v[0] == '3' + assert v[1] == '5' + v[1] = None + assert v[0] == '3' + assert v[1] is None + +def test_set_item_error() -> None: + v = append(vec[str](), '1') + v = append(v, '2') + v = append(v, '3') + with assertRaises(IndexError): + v[3] = '1' + with assertRaises(TypeError): + v[0] = b'x' + with assertRaises(TypeError): + v[0] = vec[str]() + with assertRaises(TypeError): + v[0] = None + assert v[0] == '1' + +def test_set_item_error_optional() -> None: + v = append(vec[Optional[str]](), '1') + v = append(v, None) + v = append(v, '3') + with assertRaises(IndexError): + v[3] = '1' + with assertRaises(IndexError): + v[3] = None + with assertRaises(IndexError): + v[-4] = '1' + with assertRaises(IndexError): + v[-4] = None + with assertRaises(TypeError): + v[0] = b'x' + with assertRaises(TypeError): + v[0] = vec[str]() + with assertRaises(TypeError): + v[0] = vec[Optional[str]]() + assert v[0] == '1' + +def test_set_item_negative() -> None: + v = vec[str]() + v = append(v, '1') + v = append(v, '2') + v[-2] = '3' + assert v[0] == '3' + assert v[1] == '2' + v[-1] = '5' + assert v[0] == '3' + assert v[1] == '5' + with assertRaises(IndexError): + v[-3] = '1' + +def test_nested_in_type() -> None: + cast(List[vec[str]], []) + +def test_cannot_overload_isinstance() -> None: + assert isinstance('', ClassWithCustomMeta) + v = vec[ClassWithCustomMeta]() + v = append(v, ClassWithCustomMeta()) + with assertRaises(TypeError): + v = append(v, '') + with assertRaises(TypeError): + v[0] = '' + +def test_add_subclass_instance() -> None: + v = vec[str]() + v = append(v, StringSubclass()) + v[0] = StringSubclass() + + v2 = vec[StringSubclass]() + with assertRaises(TypeError): + v2 = append(v2, '') + +def test_equality() -> None: + assert_eq(vec[str](), vec[str]()) + assert_eq(vec[str](['x']), vec[str](['x'])) + v = vec[str](['x']) + assert_eq(v, v) + assert_eq(vec[str](['x', 'y']), vec[str](['x', 'y'])) + + assert_neq(vec[str](), vec[str](['x'])) + assert_neq(vec[str](['x', 'y']), vec[str](['x'])) + assert_neq(vec[str](['x']), vec[str](['y'])) + assert_neq(vec[str](['x', 'y']), vec[str](['y', 'y'])) + assert_neq(vec[str](['x', 'y']), vec[str](['x', 'x'])) + + assert_eq(vec[Optional[str]](), vec[Optional[str]]()) + assert_eq(vec[Optional[str]](['x', None]), vec[Optional[str]](['x', None])) + assert_neq(vec[Optional[str]](['x', None]), vec[Optional[str]](['x', 'y'])) + +def test_equality_different_types() -> None: + assert_neq(vec[str](['x']), ['x']) + assert_neq(vec[str](['x']), ('x',)) + assert_neq(vec[str](['x']), vec[object](['x'])) + assert_neq(vec[str](), vec[object]()) + assert_neq(vec[str](), vec[Optional[str]]()) + +def test_exception_from_eq() -> None: + with assertRaises(RuntimeError): + vec[ClassWithRaisingEq]([ClassWithRaisingEq()]) == vec[ClassWithRaisingEq]([ClassWithRaisingEq()]) + +def test_iteration() -> None: + for x in vec[str](): + assert False + a = [] + for x in vec[str](['x']): + a.append(x) + assert a == ['x'] + a = [] + for x in vec[str](['11', '23', '45', '64']): + a.append(x) + assert a == ['11', '23', '45', '64'] + a = [] + for x in vec[Optional[str]](['11', None, '45', '64']): + a.append(x) + assert a == ['11', None, '45', '64'] + +def test_slicing() -> None: + v = vec[str](str(i) for i in range(5)) + assert v[1:4] == vec[str](['1', '2', '3']) + assert v[2:-1] == vec[str](['2', '3']) + assert v[-2:-1] == vec[str](['3']) + assert v[1:] == vec[str](['1', '2', '3', '4']) + assert v[:-1] == vec[str](['0', '1', '2', '3']) + assert v[:] == v + assert v[:] is not v + assert v[0:5] ==v + assert v[0:5] is not v + assert v[2:100] == vec[str](['2', '3', '4']) + assert v[-100:2] == vec[str](['0', '1']) + assert v[5:100] == vec[str]([]) + assert v[50:100] == vec[str]([]) + assert v[-100:-50] == vec[str]([]) + +def test_slicing_optional() -> None: + v = vec[Optional[str]](['x', None, 'y']) + assert v[:2] == vec[Optional[str]](['x', None]) + +def test_slicing_with_step() -> None: + v = vec[str](str(i) for i in range(10)) + assert v[1:5:2] == vec[str](['1', '3']) + assert v[1:6:2] == vec[str](['1', '3', '5']) + assert v[5:1:-2] == vec[str](['5', '3']) + assert v[6:1:-2] == vec[str](['6', '4', '2']) + v = vec[str](str(i) for i in range(5)) + assert v[::-1] == vec[str](['4', '3', '2', '1', '0']) + with assertRaises(ValueError): + v[1:3:0] + +def test_contains() -> None: + v = vec[str](str(i) for i in range(2, 7)) + for i in range(2, 7): + assert str(i) in v + assert 'x' not in v + assert '22' not in v + vv = vec[str]() + assert '3' not in vv + + v2 = vec[Optional[str]]() + assert '3' not in v2 + assert None not in v2 + v2 = vec[Optional[str]](['3', None]) + assert '3' in v2 + assert None in v2 + assert '4' not in v2 + +def test_remove() -> None: + a = ['4x', '7x', '9x'] + for i in a: + v = vec[str](a) + v = remove(v, i) + assert v == vec[str]([j for j in a if j != i]) + v = vec[str](a) + # Make sure we have a different id + v = remove(v, (i + 'x')[:-1]) + assert v == vec[str]([j for j in a if j != i]) + v = vec[str](a) + v = remove(v, '4x') + v = remove(v, '7x') + v = remove(v, '9x') + assert v == vec[str]() + with assertRaises(ValueError): + remove(v, '4') + v = append(v, '5') + with assertRaises(ValueError): + remove(v, '7') + v = remove(v, '5') + assert len(v) == 0 + v = vec[str](['x', 'x', 'x']) + v = remove(v, 'x') + assert v == vec[str](['x', 'x']) + v = remove(v, 'x') + assert v == vec[str](['x']) + v = remove(v, 'x') + assert v == vec[str]() + b: Any = b'x' + with assertRaises(TypeError): + remove(v, b) + +def test_remove_optional() -> None: + v = vec[Optional[str]](['x', None]) + v = remove(v, 'x') + assert str(v) == "vec[str | None]([None])" + v = remove(v, None) + assert str(v) == "vec[str | None]([])" + +def test_pop_last() -> None: + v = vec[str](['4', '7', '9']) + v, item = pop(v) + assert item == '9' + assert v == vec[str](['4', '7']) + v, item = pop(v) + assert item == '7' + assert v == vec[str](['4']) + v, item = pop(v) + assert item == '4' + assert v == vec[str]() + with assertRaises(IndexError): + pop(v) + +def test_pop_index() -> None: + v = vec[str](['4', '7', '9', '15', '22']) + v, item = pop(v, 0) + assert item == '4' + assert v == vec[str](['7', '9', '15', '22']) + v, item = pop(v, -1) + assert item == '22' + assert v == vec[str](['7', '9', '15']) + v, item = pop(v, 1) + assert item == '9' + assert v == vec[str](['7', '15']) + + with assertRaises(IndexError): + pop(v, 2) + + with assertRaises(IndexError): + pop(v, -3) + + v, item = pop(v, -2) + assert item == '7' + assert v == vec[str](['15']) + v, item = pop(v, 0) + assert item == '15' + assert v == vec[str]() + +[file helpers.py] +# Test helper classes + +class MetaWithInstanceCheck(type): + def __instancecheck__(cls, instance): + return isinstance(instance, str) + +class ClassWithCustomMeta(metaclass=MetaWithInstanceCheck): + pass diff --git a/mypyc/test/test_run.py b/mypyc/test/test_run.py index 7070aeeac009a..7b855841e220f 100644 --- a/mypyc/test/test_run.py +++ b/mypyc/test/test_run.py @@ -80,6 +80,8 @@ "run-match.test", "run-vecs-i64-interp.test", "run-vecs-misc-interp.test", + "run-vecs-t-interp.test", + "run-vecs-nested-interp.test", ] if sys.version_info >= (3, 12):