patch 9.1.1239: if_python: no tuple data type support
Problem: if_python: no tuple data type support (after v9.1.1232)
Solution: Add support for using Vim tuple in the python interface
(Yegappan Lakshmanan)
closes: #16964
Signed-off-by: Yegappan Lakshmanan <yegappan@yahoo.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
diff --git a/src/eval.c b/src/eval.c
index bd8e7cf..c2856fe 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -2050,7 +2050,7 @@
var2.v_type = VAR_UNKNOWN;
}
- if (lp->ll_tuple != NULL)
+ if (lp->ll_tuple != NULL && (flags & GLV_READ_ONLY) == 0)
{
if (!quiet)
emsg(_(e_tuple_is_immutable));
diff --git a/src/evalfunc.c b/src/evalfunc.c
index 21ed15e..125ba55 100644
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -8632,6 +8632,9 @@
else if (lv.ll_list != NULL)
// List item.
rettv->vval.v_number = tv_islocked(&lv.ll_li->li_tv);
+ else if (lv.ll_tuple != NULL)
+ // Tuple item.
+ rettv->vval.v_number = tv_islocked(lv.ll_tv);
else
// Dictionary item.
rettv->vval.v_number = tv_islocked(&lv.ll_di->di_tv);
diff --git a/src/ex_docmd.c b/src/ex_docmd.c
index f341fd4..03eaef4 100644
--- a/src/ex_docmd.c
+++ b/src/ex_docmd.c
@@ -289,7 +289,6 @@
# define ex_endif ex_ni
# define ex_endtry ex_ni
# define ex_endwhile ex_ni
-# define ex_enum ex_ni
# define ex_eval ex_ni
# define ex_execute ex_ni
# define ex_finally ex_ni
diff --git a/src/gc.c b/src/gc.c
index b95b2ca..c8aa5fa 100644
--- a/src/gc.c
+++ b/src/gc.c
@@ -439,6 +439,21 @@
}
/*
+ * Mark a tuple and its items with "copyID".
+ * Returns TRUE if setting references failed somehow.
+ */
+ int
+set_ref_in_tuple(tuple_T *tuple, int copyID)
+{
+ if (tuple != NULL && tuple->tv_copyID != copyID)
+ {
+ tuple->tv_copyID = copyID;
+ return set_ref_in_tuple_items(tuple, copyID, NULL, NULL);
+ }
+ return FALSE;
+}
+
+/*
* Mark all lists and dicts referenced through tuple "t" with "copyID".
* "ht_stack" is used to add hashtabs to be marked. Can be NULL.
*
diff --git a/src/if_py_both.h b/src/if_py_both.h
index 9f2f582..641c159 100644
--- a/src/if_py_both.h
+++ b/src/if_py_both.h
@@ -152,14 +152,15 @@
# define PyList_GET_ITEM(list, i) PyList_GetItem(list, i)
# define PyList_GET_SIZE(o) PyList_Size(o)
-# define PyTuple_GET_ITEM(o, pos) PyTuple_GetItem(o, pos)
-# define PyTuple_GET_SIZE(o) PyTuple_Size(o)
// PyList_SET_ITEM and PyList_SetItem have slightly different behaviors. The
// former will leave the old item dangling, and the latter will decref on it.
// Since we only use this on new lists, this difference doesn't matter.
# define PyList_SET_ITEM(list, i, item) PyList_SetItem(list, i, item)
+# define PyTuple_GET_ITEM(o, pos) PyTuple_GetItem(o, pos)
+# define PyTuple_GET_SIZE(o) PyTuple_Size(o)
+
# if Py_LIMITED_API < 0x03080000
// PyIter_check only became part of stable ABI in 3.8, and there is no easy way
// to check for it in the API. We simply return false as a compromise. This
@@ -1014,11 +1015,14 @@
// Check if we run into a recursive loop. The item must be in lookup_dict
// then and we can use it again.
if ((our_tv->v_type == VAR_LIST && our_tv->vval.v_list != NULL)
+ || (our_tv->v_type == VAR_TUPLE && our_tv->vval.v_tuple != NULL)
|| (our_tv->v_type == VAR_DICT && our_tv->vval.v_dict != NULL))
{
sprintf(ptrBuf, "%p",
our_tv->v_type == VAR_LIST ? (void *)our_tv->vval.v_list
- : (void *)our_tv->vval.v_dict);
+ : our_tv->v_type == VAR_TUPLE ?
+ (void *)our_tv->vval.v_tuple
+ : (void *)our_tv->vval.v_dict);
if ((ret = PyDict_GetItemString(lookup_dict, ptrBuf)))
{
@@ -1079,6 +1083,39 @@
Py_DECREF(newObj);
}
}
+ else if (our_tv->v_type == VAR_TUPLE)
+ {
+ tuple_T *tuple = our_tv->vval.v_tuple;
+ int len;
+
+ if (tuple == NULL)
+ return NULL;
+
+ len = TUPLE_LEN(tuple);
+
+ ret = PyTuple_New(len);
+ if (ret == NULL)
+ return NULL;
+
+ if (PyDict_SetItemString(lookup_dict, ptrBuf, ret))
+ {
+ Py_DECREF(ret);
+ return NULL;
+ }
+
+ for (int idx = 0; idx < len; idx++)
+ {
+ typval_T *item_tv = TUPLE_ITEM(tuple, idx);
+
+ newObj = VimToPython(item_tv, depth + 1, lookup_dict);
+ if (!newObj)
+ {
+ Py_DECREF(ret);
+ return NULL;
+ }
+ PyTuple_SET_ITEM(ret, idx, newObj);
+ }
+ }
else if (our_tv->v_type == VAR_DICT)
{
@@ -1800,6 +1837,7 @@
static pylinkedlist_T *lastdict = NULL;
static pylinkedlist_T *lastlist = NULL;
+static pylinkedlist_T *lasttuple = NULL;
static pylinkedlist_T *lastfunc = NULL;
static void
@@ -3216,6 +3254,355 @@
{ NULL, NULL, 0, NULL}
};
+DEFINE_PY_TYPE_OBJECT(TupleType);
+
+typedef struct
+{
+ PyObject_HEAD
+ tuple_T *tuple;
+ pylinkedlist_T ref;
+} TupleObject;
+
+#define NEW_TUPLE(tuple) TupleNew(TupleTypePtr, tuple)
+
+ static PyObject *
+TupleNew(PyTypeObject *subtype, tuple_T *tuple)
+{
+ TupleObject *self;
+
+ if (tuple == NULL)
+ return NULL;
+
+ self = (TupleObject *) Py_TYPE_GET_TP_ALLOC(subtype)(subtype, 0);
+ if (self == NULL)
+ return NULL;
+ self->tuple = tuple;
+ ++tuple->tv_refcount;
+
+ pyll_add((PyObject *)(self), &self->ref, &lasttuple);
+
+ return (PyObject *)(self);
+}
+
+ static tuple_T *
+py_tuple_alloc(void)
+{
+ tuple_T *ret;
+
+ if (!(ret = tuple_alloc()))
+ {
+ PyErr_NoMemory();
+ return NULL;
+ }
+ ++ret->tv_refcount;
+
+ return ret;
+}
+
+ static int
+tuple_py_concat(tuple_T *t, PyObject *obj, PyObject *lookup_dict)
+{
+ PyObject *iterator;
+ PyObject *item;
+
+ if (!(iterator = PyObject_GetIter(obj)))
+ return -1;
+
+ while ((item = PyIter_Next(iterator)))
+ {
+ typval_T new_tv;
+
+ if (_ConvertFromPyObject(item, &new_tv, lookup_dict) == -1)
+ {
+ Py_DECREF(item);
+ Py_DECREF(iterator);
+ return -1;
+ }
+
+ Py_DECREF(item);
+
+ if (tuple_append_tv(t, &new_tv) == FAIL)
+ {
+ Py_DECREF(iterator);
+ return -1;
+ }
+ }
+
+ Py_DECREF(iterator);
+
+ // Iterator may have finished due to an exception
+ if (PyErr_Occurred())
+ return -1;
+
+ return 0;
+}
+
+ static PyObject *
+TupleConstructor(PyTypeObject *subtype, PyObject *args, PyObject *kwargs)
+{
+ tuple_T *tuple;
+ PyObject *obj = NULL;
+
+ if (kwargs)
+ {
+ PyErr_SET_STRING(PyExc_TypeError,
+ N_("tuple constructor does not accept keyword arguments"));
+ return NULL;
+ }
+
+ if (!PyArg_ParseTuple(args, "|O", &obj))
+ return NULL;
+
+ if (!(tuple = py_tuple_alloc()))
+ return NULL;
+
+ if (obj)
+ {
+ PyObject *lookup_dict;
+
+ if (!(lookup_dict = PyDict_New()))
+ {
+ tuple_unref(tuple);
+ return NULL;
+ }
+
+ if (tuple_py_concat(tuple, obj, lookup_dict) == -1)
+ {
+ Py_DECREF(lookup_dict);
+ tuple_unref(tuple);
+ return NULL;
+ }
+
+ Py_DECREF(lookup_dict);
+ }
+
+ return TupleNew(subtype, tuple);
+}
+
+ static void
+TupleDestructor(PyObject *self_obj)
+{
+ TupleObject *self = (TupleObject *)self_obj;
+ pyll_remove(&self->ref, &lasttuple);
+ tuple_unref(self->tuple);
+
+ DESTRUCTOR_FINISH(self);
+}
+
+ static PyInt
+TupleLength(TupleObject *self)
+{
+ return ((PyInt)(tuple_len(self->tuple)));
+}
+
+ static PyObject *
+TupleIndex(TupleObject *self, Py_ssize_t index)
+{
+ PyInt len = TupleLength(self);
+
+ if (index < 0)
+ index = len + index;
+
+ if (index < 0 || index >= len)
+ {
+ PyErr_SET_STRING(PyExc_IndexError, N_("tuple index out of range"));
+ return NULL;
+ }
+ return ConvertToPyObject(TUPLE_ITEM(self->tuple, index));
+}
+
+/*
+ * Return a new tuple object for the tuple slice starting from the index
+ * "first" and of length "slicelen" skipping "step" items.
+ */
+ static PyObject *
+TupleSlice(
+ TupleObject *self,
+ Py_ssize_t first,
+ Py_ssize_t step,
+ Py_ssize_t slicelen)
+{
+ PyInt i;
+ PyObject *tuple;
+
+ tuple = PyTuple_New(slicelen);
+ if (tuple == NULL)
+ return NULL;
+
+ for (i = 0; i < slicelen; ++i)
+ {
+ PyObject *item;
+
+ item = TupleIndex(self, first + i * step);
+ if (item == NULL)
+ {
+ Py_DECREF(tuple);
+ return NULL;
+ }
+
+ PyTuple_SET_ITEM(tuple, i, item);
+ }
+
+ return tuple;
+}
+
+ static PyObject *
+TupleItem(TupleObject *self, PyObject* idx)
+{
+#if PY_MAJOR_VERSION < 3
+ if (PyInt_Check(idx))
+ {
+ long _idx = PyInt_AsLong(idx);
+ return TupleIndex(self, _idx);
+ }
+ else
+#endif
+ if (PyLong_Check(idx))
+ {
+ long _idx = PyLong_AsLong(idx);
+ return TupleIndex(self, _idx);
+ }
+ else if (PySlice_Check(idx))
+ {
+ Py_ssize_t start, stop, step, slicelen;
+
+ if (PySlice_GetIndicesEx((PySliceObject_T *)idx, TupleLength(self),
+ &start, &stop, &step, &slicelen) < 0)
+ return NULL;
+ return TupleSlice(self, start, step, slicelen);
+ }
+ else
+ {
+ RAISE_INVALID_INDEX_TYPE(idx);
+ return NULL;
+ }
+}
+
+typedef struct
+{
+ tuple_T *tuple;
+ int index;
+} tupleiterinfo_T;
+
+ static void
+TupleIterDestruct(void *arg)
+{
+ tupleiterinfo_T *tii = (tupleiterinfo_T*)arg;
+ tuple_unref(tii->tuple);
+ PyMem_Free(tii);
+}
+
+ static PyObject *
+TupleIterNext(void **arg)
+{
+ PyObject *ret;
+ tupleiterinfo_T **tii = (tupleiterinfo_T**)arg;
+
+ if ((*tii)->index >= TUPLE_LEN((*tii)->tuple))
+ return NULL;
+
+ if (!(ret = ConvertToPyObject(TUPLE_ITEM((*tii)->tuple, (*tii)->index))))
+ return NULL;
+
+ (*tii)->index++;
+
+ return ret;
+}
+
+ static PyObject *
+TupleIter(PyObject *self_obj)
+{
+ TupleObject *self = (TupleObject*)self_obj;
+ tupleiterinfo_T *tii;
+ tuple_T *t = self->tuple;
+
+ if (!(tii = PyMem_New(tupleiterinfo_T, 1)))
+ {
+ PyErr_NoMemory();
+ return NULL;
+ }
+
+ tii->tuple = t;
+ tii->index = 0;
+ ++t->tv_refcount;
+
+ return IterNew(tii,
+ TupleIterDestruct, TupleIterNext,
+ NULL, NULL, (PyObject *)self);
+}
+
+static char *TupleAttrs[] = {
+ "locked",
+ NULL
+};
+
+ static PyObject *
+TupleDir(PyObject *self, PyObject *args UNUSED)
+{
+ return ObjectDir(self, TupleAttrs);
+}
+
+ static int
+TupleSetattr(PyObject *self_obj, char *name, PyObject *valObject)
+{
+ TupleObject *self = (TupleObject*)self_obj;
+ if (valObject == NULL)
+ {
+ PyErr_SET_STRING(PyExc_AttributeError,
+ N_("cannot delete vim.Tuple attributes"));
+ return -1;
+ }
+
+ if (strcmp(name, "locked") == 0)
+ {
+ if (self->tuple->tv_lock == VAR_FIXED)
+ {
+ PyErr_SET_STRING(PyExc_TypeError, N_("cannot modify fixed tuple"));
+ return -1;
+ }
+ else
+ {
+ int istrue = PyObject_IsTrue(valObject);
+ if (istrue == -1)
+ return -1;
+ else if (istrue)
+ self->tuple->tv_lock = VAR_LOCKED;
+ else
+ self->tuple->tv_lock = 0;
+ }
+ return 0;
+ }
+ else
+ {
+ PyErr_FORMAT(PyExc_AttributeError, N_("cannot set attribute %s"), name);
+ return -1;
+ }
+}
+
+static PySequenceMethods TupleAsSeq = {
+ (lenfunc) TupleLength, // sq_length, len(x)
+ (binaryfunc) 0, // RangeConcat, sq_concat, x+y
+ 0, // RangeRepeat, sq_repeat, x*n
+ (PyIntArgFunc) TupleIndex, // sq_item, x[i]
+ 0, // was_sq_slice, x[i:j]
+ (PyIntObjArgProc) 0, // sq_as_item, x[i]=v
+ 0, // was_sq_ass_slice, x[i:j]=v
+ 0, // sq_contains
+ (binaryfunc) 0, // sq_inplace_concat
+ 0, // sq_inplace_repeat
+};
+
+static PyMappingMethods TupleAsMapping = {
+ /* mp_length */ (lenfunc) TupleLength,
+ /* mp_subscript */ (binaryfunc) TupleItem,
+ /* mp_ass_subscript */ (objobjargproc) 0,
+};
+
+static struct PyMethodDef TupleMethods[] = {
+ {"__dir__", (PyCFunction)TupleDir, METH_NOARGS, ""},
+ { NULL, NULL, 0, NULL}
+};
+
typedef struct
{
PyObject_HEAD
@@ -6219,6 +6606,7 @@
{
pylinkedlist_T *cur;
list_T *ll;
+ tuple_T *tt;
int i;
int abort = FALSE;
FunctionObject *func;
@@ -6239,6 +6627,15 @@
}
}
+ if (lasttuple != NULL)
+ {
+ for (cur = lasttuple ; !abort && cur != NULL ; cur = cur->pll_prev)
+ {
+ tt = ((TupleObject *) (cur->pll_obj))->tuple;
+ abort = set_ref_in_tuple(tt, copyID);
+ }
+ }
+
if (lastfunc != NULL)
{
for (cur = lastfunc ; !abort && cur != NULL ; cur = cur->pll_prev)
@@ -6461,6 +6858,27 @@
return 0;
}
+ static int
+pytuple_to_tv(PyObject *obj, typval_T *tv, PyObject *lookup_dict)
+{
+ tuple_T *t;
+
+ if (!(t = py_tuple_alloc()))
+ return -1;
+
+ tv->v_type = VAR_TUPLE;
+ tv->vval.v_tuple = t;
+
+ if (tuple_py_concat(t, obj, lookup_dict) == -1)
+ {
+ tuple_unref(t);
+ return -1;
+ }
+
+ --t->tv_refcount;
+ return 0;
+}
+
typedef int (*pytotvfunc)(PyObject *, typval_T *, PyObject *);
static int
@@ -6504,6 +6922,8 @@
++tv->vval.v_dict->dv_refcount;
else if (tv->v_type == VAR_LIST)
++tv->vval.v_list->lv_refcount;
+ else if (tv->v_type == VAR_TUPLE)
+ ++tv->vval.v_tuple->tv_refcount;
}
else
{
@@ -6607,6 +7027,12 @@
tv->vval.v_list = (((ListObject *)(obj))->list);
++tv->vval.v_list->lv_refcount;
}
+ else if (PyType_IsSubtype(obj->ob_type, TupleTypePtr))
+ {
+ tv->v_type = VAR_TUPLE;
+ tv->vval.v_tuple = (((TupleObject *)(obj))->tuple);
+ ++tv->vval.v_tuple->tv_refcount;
+ }
else if (PyType_IsSubtype(obj->ob_type, FunctionTypePtr))
{
FunctionObject *func = (FunctionObject *) obj;
@@ -6680,6 +7106,8 @@
if (PyErr_Occurred())
return -1;
}
+ else if (PyTuple_Check(obj))
+ return convert_dl(obj, tv, pytuple_to_tv, lookup_dict);
else if (PyDict_Check(obj))
return convert_dl(obj, tv, pydict_to_tv, lookup_dict);
else if (PyFloat_Check(obj))
@@ -6742,6 +7170,8 @@
return PyFloat_FromDouble((double) tv->vval.v_float);
case VAR_LIST:
return NEW_LIST(tv->vval.v_list);
+ case VAR_TUPLE:
+ return NEW_TUPLE(tv->vval.v_tuple);
case VAR_DICT:
return NEW_DICTIONARY(tv->vval.v_dict);
case VAR_FUNC:
@@ -6777,7 +7207,6 @@
case VAR_CLASS:
case VAR_OBJECT:
case VAR_TYPEALIAS:
- case VAR_TUPLE: // FIXME: Need to add support for tuple
Py_INCREF(Py_None);
return Py_None;
case VAR_BOOL:
@@ -7002,6 +7431,27 @@
ListType.tp_setattr = ListSetattr;
#endif
+ // Tuple type
+ CLEAR_FIELD(TupleType);
+ TupleType.tp_name = "vim.tuple";
+ TupleType.tp_dealloc = TupleDestructor;
+ TupleType.tp_basicsize = sizeof(TupleObject);
+ TupleType.tp_as_sequence = &TupleAsSeq;
+ TupleType.tp_as_mapping = &TupleAsMapping;
+ TupleType.tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE;
+ TupleType.tp_doc = "tuple pushing modifications to Vim structure";
+ TupleType.tp_methods = TupleMethods;
+ TupleType.tp_iter = TupleIter;
+ TupleType.tp_new = TupleConstructor;
+ TupleType.tp_alloc = PyType_GenericAlloc;
+#if PY_MAJOR_VERSION >= 3
+ TupleType.tp_getattro = TupleGetattro;
+ TupleType.tp_setattro = TupleSetattro;
+#else
+ TupleType.tp_getattr = TupleGetattr;
+ TupleType.tp_setattr = TupleSetattr;
+#endif
+
CLEAR_FIELD(FunctionType);
FunctionType.tp_name = "vim.function";
FunctionType.tp_basicsize = sizeof(FunctionObject);
@@ -7064,6 +7514,7 @@
PYTYPE_READY(CurrentType);
PYTYPE_READY(DictionaryType);
PYTYPE_READY(ListType);
+ PYTYPE_READY(TupleType);
PYTYPE_READY(FunctionType);
PYTYPE_READY(OptionsType);
PYTYPE_READY(OutputType);
@@ -7101,6 +7552,7 @@
PYTYPE_CLEANUP(CurrentType);
PYTYPE_CLEANUP(DictionaryType);
PYTYPE_CLEANUP(ListType);
+ PYTYPE_CLEANUP(TupleType);
PYTYPE_CLEANUP(FunctionType);
PYTYPE_CLEANUP(OptionsType);
PYTYPE_CLEANUP(OutputType);
@@ -7239,6 +7691,7 @@
{"TabPage", (PyObject *)TabPageTypePtr},
{"Dictionary", (PyObject *)DictionaryTypePtr},
{"List", (PyObject *)ListTypePtr},
+ {"Tuple", (PyObject *)TupleTypePtr},
{"Function", (PyObject *)FunctionTypePtr},
{"Options", (PyObject *)OptionsTypePtr},
#if PY_VERSION_HEX < 0x030700f0
diff --git a/src/if_python.c b/src/if_python.c
index 577807c..594d7e5 100644
--- a/src/if_python.c
+++ b/src/if_python.c
@@ -201,6 +201,10 @@
# define PyList_SetItem dll_PyList_SetItem
# define PyList_Size dll_PyList_Size
# define PyList_Type (*dll_PyList_Type)
+# define PyTuple_GetItem dll_PyTuple_GetItem
+# define PyTuple_New dll_PyTuple_New
+# define PyTuple_Size dll_PyTuple_Size
+# define PyTuple_Type (*dll_PyTuple_Type)
# define PySequence_Check dll_PySequence_Check
# define PySequence_Size dll_PySequence_Size
# define PySequence_GetItem dll_PySequence_GetItem
@@ -352,13 +356,16 @@
static int(*dll_PyList_SetItem)(PyObject *, PyInt, PyObject *);
static PyInt(*dll_PyList_Size)(PyObject *);
static PyTypeObject* dll_PyList_Type;
+static PyObject*(*dll_PyTuple_GetItem)(PyObject *, PyInt);
+static int(*dll_PyTuple_SetItem)(PyObject *, PyInt, PyObject *);
+static int(*dll_PyTuple_SET_ITEM)(PyObject *, PyInt, PyObject *);
+static PyObject*(*dll_PyTuple_New)(PyInt size);
+static PyInt(*dll_PyTuple_Size)(PyObject *);
+static PyTypeObject* dll_PyTuple_Type;
static int (*dll_PySequence_Check)(PyObject *);
static PyInt(*dll_PySequence_Size)(PyObject *);
static PyObject*(*dll_PySequence_GetItem)(PyObject *, PyInt);
static PyObject*(*dll_PySequence_Fast)(PyObject *, const char *);
-static PyInt(*dll_PyTuple_Size)(PyObject *);
-static PyObject*(*dll_PyTuple_GetItem)(PyObject *, PyInt);
-static PyTypeObject* dll_PyTuple_Type;
static int (*dll_PySlice_GetIndicesEx)(PySliceObject *r, PyInt length,
PyInt *start, PyInt *stop, PyInt *step,
PyInt *slicelen);
@@ -540,6 +547,12 @@
{"PyList_SetItem", (PYTHON_PROC*)&dll_PyList_SetItem},
{"PyList_Size", (PYTHON_PROC*)&dll_PyList_Size},
{"PyList_Type", (PYTHON_PROC*)&dll_PyList_Type},
+ {"PyTuple_GetItem", (PYTHON_PROC*)&dll_PyTuple_GetItem},
+ {"PyTuple_SetItem", (PYTHON_PROC*)&dll_PyTuple_SetItem},
+ {"PyTuple_SET_ITEM", (PYTHON_PROC*)&dll_PyTuple_SET_ITEM},
+ {"PyTuple_New", (PYTHON_PROC*)&dll_PyTuple_New},
+ {"PyTuple_Size", (PYTHON_PROC*)&dll_PyTuple_Size},
+ {"PyTuple_Type", (PYTHON_PROC*)&dll_PyTuple_Type},
{"PySequence_Size", (PYTHON_PROC*)&dll_PySequence_Size},
{"PySequence_Check", (PYTHON_PROC*)&dll_PySequence_Check},
{"PySequence_GetItem", (PYTHON_PROC*)&dll_PySequence_GetItem},
@@ -786,6 +799,7 @@
static PyObject *RangeGetattr(PyObject *, char *);
static PyObject *DictionaryGetattr(PyObject *, char*);
static PyObject *ListGetattr(PyObject *, char *);
+static PyObject *TupleGetattr(PyObject *, char *);
static PyObject *FunctionGetattr(PyObject *, char *);
#ifndef Py_VISIT
@@ -1511,6 +1525,17 @@
}
static PyObject *
+TupleGetattr(PyObject *self, char *name)
+{
+ if (strcmp(name, "locked") == 0)
+ return PyInt_FromLong(((TupleObject *)(self))->tuple->tv_lock);
+ else if (strcmp(name, "__members__") == 0)
+ return ObjectDir(NULL, TupleAttrs);
+
+ return Py_FindMethod(TupleMethods, self, name);
+}
+
+ static PyObject *
FunctionGetattr(PyObject *self, char *name)
{
PyObject *r;
diff --git a/src/if_python3.c b/src/if_python3.c
index aa934cb..b2eb1d4 100644
--- a/src/if_python3.c
+++ b/src/if_python3.c
@@ -187,12 +187,16 @@
# define PyList_New py3_PyList_New
# define PyList_SetItem py3_PyList_SetItem
# define PyList_Size py3_PyList_Size
+# define PyTuple_New py3_PyTuple_New
+# define PyTuple_GetItem py3_PyTuple_GetItem
+# define PyTuple_SetItem py3_PyTuple_SetItem
+# undef PyTuple_SET_ITEM
+# define PyTuple_SET_ITEM py3_PyTuple_SET_ITEM
+# define PyTuple_Size py3_PyTuple_Size
# define PySequence_Check py3_PySequence_Check
# define PySequence_Size py3_PySequence_Size
# define PySequence_GetItem py3_PySequence_GetItem
# define PySequence_Fast py3_PySequence_Fast
-# define PyTuple_Size py3_PyTuple_Size
-# define PyTuple_GetItem py3_PyTuple_GetItem
# if PY_VERSION_HEX >= 0x030601f0
# define PySlice_AdjustIndices py3_PySlice_AdjustIndices
# define PySlice_Unpack py3_PySlice_Unpack
@@ -371,11 +375,14 @@
static int (*py3_PyList_Append)(PyObject *, PyObject *);
static int (*py3_PyList_Insert)(PyObject *, int, PyObject *);
static Py_ssize_t (*py3_PyList_Size)(PyObject *);
+static PyObject* (*py3_PyTuple_New)(Py_ssize_t size);
+static int (*py3_PyTuple_SetItem)(PyObject *, Py_ssize_t, PyObject *);
+static int (*py3_PyTuple_SET_ITEM)(PyObject *, Py_ssize_t, PyObject *);
+static Py_ssize_t (*py3_PyTuple_Size)(PyObject *);
static int (*py3_PySequence_Check)(PyObject *);
static Py_ssize_t (*py3_PySequence_Size)(PyObject *);
static PyObject* (*py3_PySequence_GetItem)(PyObject *, Py_ssize_t);
static PyObject* (*py3_PySequence_Fast)(PyObject *, const char *);
-static Py_ssize_t (*py3_PyTuple_Size)(PyObject *);
static PyObject* (*py3_PyTuple_GetItem)(PyObject *, Py_ssize_t);
static int (*py3_PyMapping_Check)(PyObject *);
static PyObject* (*py3_PyMapping_Keys)(PyObject *);
@@ -585,12 +592,15 @@
{"PyList_Append", (PYTHON_PROC*)&py3_PyList_Append},
{"PyList_Insert", (PYTHON_PROC*)&py3_PyList_Insert},
{"PyList_Size", (PYTHON_PROC*)&py3_PyList_Size},
+ {"PyTuple_New", (PYTHON_PROC*)&py3_PyTuple_New},
+ {"PyTuple_GetItem", (PYTHON_PROC*)&py3_PyTuple_GetItem},
+ {"PyTuple_SetItem", (PYTHON_PROC*)&py3_PyTuple_SetItem},
+ {"PyTuple_SET_ITEM", (PYTHON_PROC*)&py3_PyTuple_SET_ITEM},
+ {"PyTuple_Size", (PYTHON_PROC*)&py3_PyTuple_Size},
{"PySequence_Check", (PYTHON_PROC*)&py3_PySequence_Check},
{"PySequence_Size", (PYTHON_PROC*)&py3_PySequence_Size},
{"PySequence_GetItem", (PYTHON_PROC*)&py3_PySequence_GetItem},
{"PySequence_Fast", (PYTHON_PROC*)&py3_PySequence_Fast},
- {"PyTuple_Size", (PYTHON_PROC*)&py3_PyTuple_Size},
- {"PyTuple_GetItem", (PYTHON_PROC*)&py3_PyTuple_GetItem},
# if PY_VERSION_HEX >= 0x030601f0
{"PySlice_AdjustIndices", (PYTHON_PROC*)&py3_PySlice_AdjustIndices},
{"PySlice_Unpack", (PYTHON_PROC*)&py3_PySlice_Unpack},
@@ -1113,6 +1123,8 @@
static int DictionarySetattro(PyObject *, PyObject *, PyObject *);
static PyObject *ListGetattro(PyObject *, PyObject *);
static int ListSetattro(PyObject *, PyObject *, PyObject *);
+static PyObject *TupleGetattro(PyObject *, PyObject *);
+static int TupleSetattro(PyObject *, PyObject *, PyObject *);
static PyObject *FunctionGetattro(PyObject *, PyObject *);
static struct PyModuleDef vimmodule;
@@ -2028,6 +2040,26 @@
return ListSetattr(self, name, val);
}
+// Tuple object - Definitions
+
+ static PyObject *
+TupleGetattro(PyObject *self, PyObject *nameobj)
+{
+ GET_ATTR_STRING(name, nameobj);
+
+ if (strcmp(name, "locked") == 0)
+ return PyLong_FromLong(((TupleObject *) (self))->tuple->tv_lock);
+
+ return PyObject_GenericGetAttr(self, nameobj);
+}
+
+ static int
+TupleSetattro(PyObject *self, PyObject *nameobj, PyObject *val)
+{
+ GET_ATTR_STRING(name, nameobj);
+ return TupleSetattr(self, name, val);
+}
+
// Function object - Definitions
static PyObject *
diff --git a/src/proto/gc.pro b/src/proto/gc.pro
index 8b55030..8c8f73f 100644
--- a/src/proto/gc.pro
+++ b/src/proto/gc.pro
@@ -5,6 +5,7 @@
int set_ref_in_dict(dict_T *d, int copyID);
int set_ref_in_list(list_T *ll, int copyID);
int set_ref_in_list_items(list_T *l, int copyID, ht_stack_T **ht_stack, tuple_stack_T **tuple_stack);
+int set_ref_in_tuple(tuple_T *tuple, int copyID);
int set_ref_in_tuple_items(tuple_T *tuple, int copyID, ht_stack_T **ht_stack, list_stack_T **list_stack);
int set_ref_in_callback(callback_T *cb, int copyID);
int set_ref_in_item_class(class_T *cl, int copyID, ht_stack_T **ht_stack, list_stack_T **list_stack, tuple_stack_T **tuple_stack);
diff --git a/src/testdir/test_python2.vim b/src/testdir/test_python2.vim
index 4ba0f8e..cafba3d 100644
--- a/src/testdir/test_python2.vim
+++ b/src/testdir/test_python2.vim
@@ -406,6 +406,107 @@
\ 'Vim(python):TypeError: index must be int or slice, not dict')
endfunc
+" Test for the python Tuple object
+func Test_python_tuple()
+ " Try to convert a null tuple
+ call AssertException(["py l = vim.eval('test_null_tuple()')"],
+ \ 'Vim(python):SystemError: error return without exception set')
+
+ " Try to convert a Tuple with a null Tuple item
+ call AssertException(["py t = vim.eval('(test_null_tuple(),)')"],
+ \ 'Vim(python):SystemError: error return without exception set')
+
+ " Try to convert a List with a null Tuple item
+ call AssertException(["py t = vim.eval('[test_null_tuple()]')"],
+ \ 'Vim(python):SystemError: error return without exception set')
+
+ " Try to convert a Tuple with a null List item
+ call AssertException(["py t = vim.eval('(test_null_list(),)')"],
+ \ 'Vim(python):SystemError: error return without exception set')
+
+ " Try to bind a null Tuple variable (works because an empty tuple is used)
+ let cmds =<< trim END
+ let t = test_null_tuple()
+ py tt = vim.bindeval('t')
+ END
+ call AssertException(cmds, '')
+
+ " Creating a tuple using different iterators
+ py t1 = vim.Tuple(['abc', 20, 1.2, (4, 5)])
+ call assert_equal(('abc', 20, 1.2, (4, 5)), pyeval('t1'))
+ py t2 = vim.Tuple('abc')
+ call assert_equal(('a', 'b', 'c'), pyeval('t2'))
+ py t3 = vim.Tuple({'color': 'red', 'model': 'ford'})
+ call assert_equal(('color', 'model'), pyeval('t3'))
+ py t4 = vim.Tuple()
+ call assert_equal((), pyeval('t4'))
+ py t5 = vim.Tuple(x**2 for x in range(5))
+ call assert_equal((0, 1, 4, 9, 16), pyeval('t5'))
+ py t6 = vim.Tuple(('abc', 20, 1.2, (4, 5)))
+ call assert_equal(('abc', 20, 1.2, (4, 5)), pyeval('t6'))
+
+ " Convert between Vim tuple/list and python tuple/list
+ py t = vim.Tuple(vim.bindeval("('a', ('b',), ['c'], {'s': 'd'})"))
+ call assert_equal(('a', ('b',), ['c'], {'s': 'd'}), pyeval('t'))
+ call assert_equal(['a', ('b',), ['c'], {'s': 'd'}], pyeval('list(t)'))
+ call assert_equal(('a', ('b',), ['c'], {'s': 'd'}), pyeval('tuple(t)'))
+
+ py l = vim.List(vim.bindeval("['e', ('f',), ['g'], {'s': 'h'}]"))
+ call assert_equal(('e', ('f',), ['g'], {'s': 'h'}), pyeval('tuple(l)'))
+
+ " Tuple assignment
+ py tt = vim.bindeval('("a", "b")')
+ call AssertException(['py tt[0] = 10'],
+ \ "Vim(python):TypeError: 'vim.tuple' object does not support item assignment")
+ py tt = vim.bindeval('("a", "b")')
+ call AssertException(['py tt[0:1] = (10, 20)'],
+ \ "Vim(python):TypeError: 'vim.tuple' object does not support item assignment")
+
+ " iterating over tuple from Python
+ py print([x for x in vim.bindeval("('a', 'b')")])
+
+ " modifying a list item within a tuple
+ let t = ('a', ['b', 'c'], 'd')
+ py vim.bindeval('t')[1][1] = 'x'
+ call assert_equal(('a', ['b', 'x'], 'd'), t)
+
+ " length of a tuple
+ let t = ()
+ py p_t = vim.bindeval('t')
+ call assert_equal(0, pyeval('len(p_t)'))
+ let t = ('a', )
+ py p_t = vim.bindeval('t')
+ call assert_equal(1, pyeval('len(p_t)'))
+ let t = ('a', 'b', 'c')
+ py p_t = vim.bindeval('t')
+ call assert_equal(3, pyeval('len(p_t)'))
+
+ " membership test
+ let t = ('a', 'b', 'c')
+ py p_t = vim.bindeval('t')
+ call assert_true(pyeval("b'c' in p_t"))
+ call assert_true(pyeval("b'd' not in p_t"))
+
+ py x = vim.eval('("a", (2), [3], {})')
+ call assert_equal(('a', '2', ['3'], {}), pyeval('x'))
+
+ " Using a keyword argument for a tuple
+ call AssertException(['py x = vim.Tuple(a=1)'],
+ \ 'Vim(python):TypeError: tuple constructor does not accept keyword arguments')
+
+ " Using dict as an index
+ call AssertException(['py x = tt[{}]'],
+ \ 'Vim(python):TypeError: index must be int or slice, not dict')
+ call AssertException(['py x = tt["abc"]'],
+ \ 'Vim(python):TypeError: index must be int or slice, not str')
+
+ call AssertException(['py del tt.locked'],
+ \ 'Vim(python):AttributeError: cannot delete vim.Tuple attributes')
+
+ call AssertException(['py tt.foobar = 1'],
+ \ 'Vim(python):AttributeError: cannot set attribute foobar')
+endfunc
+
" Test for the python Dict object
func Test_python_dict()
let d = {}
@@ -787,11 +888,24 @@
\ 'Vim(python):TypeError: cannot modify fixed list')
endfunc
+" Test for locking/unlocking a tuple
+func Test_tuple_lock()
+ let t = (1, 2, 3)
+ py t = vim.bindeval('t')
+ py t.locked = True
+ call assert_equal(1, islocked('t'))
+ py t.locked = False
+ call assert_equal(0, islocked('t'))
+endfunc
+
" Test for pyeval()
func Test_python_pyeval()
let l = pyeval('range(3)')
call assert_equal([0, 1, 2], l)
+ let t = pyeval('("a", "b", "c")')
+ call assert_equal(("a", "b", "c"), t)
+
let d = pyeval('{"a": "b", "c": 1, "d": ["e"]}')
call assert_equal([['a', 'b'], ['c', 1], ['d', ['e']]], sort(items(d)))
@@ -812,18 +926,20 @@
call AssertException(['let v = pyeval("vim")'], 'E859:')
endfunc
-" Test for py3eval with locals
+" Test for pyeval with locals
func Test_python_pyeval_locals()
let str = 'a string'
let num = 0xbadb33f
let d = {'a': 1, 'b': 2, 'c': str}
let l = [ str, num, d ]
+ let t = ( str, num, d )
let locals = #{
\ s: str,
\ n: num,
\ d: d,
\ l: l,
+ \ t: t,
\ }
" check basics
@@ -831,6 +947,8 @@
call assert_equal(0xbadb33f, pyeval('n', locals))
call assert_equal(d, pyeval('d', locals))
call assert_equal(l, pyeval('l', locals))
+ call assert_equal(t, pyeval('t', locals))
+ call assert_equal('a-b-c', 'b"-".join(t)'->pyeval({'t': ('a', 'b', 'c')}))
py << trim EOF
def __UpdateDict(d, upd):
@@ -997,6 +1115,92 @@
\ 'Vim(python):SystemError: error return without exception set')
endfunc
+" Slice
+func Test_python_tuple_slice()
+ py tt = vim.bindeval('(0, 1, 2, 3, 4, 5)')
+ py t = tt[:4]
+ call assert_equal((0, 1, 2, 3), pyeval('t'))
+ py t = tt[2:]
+ call assert_equal((2, 3, 4, 5), pyeval('t'))
+ py t = tt[:-4]
+ call assert_equal((0, 1), pyeval('t'))
+ py t = tt[-2:]
+ call assert_equal((4, 5), pyeval('t'))
+ py t = tt[2:4]
+ call assert_equal((2, 3), pyeval('t'))
+ py t = tt[4:2]
+ call assert_equal((), pyeval('t'))
+ py t = tt[-4:-2]
+ call assert_equal((2, 3), pyeval('t'))
+ py t = tt[-2:-4]
+ call assert_equal((), pyeval('t'))
+ py t = tt[:]
+ call assert_equal((0, 1, 2, 3, 4, 5), pyeval('t'))
+ py t = tt[0:6]
+ call assert_equal((0, 1, 2, 3, 4, 5), pyeval('t'))
+ py t = tt[-10:10]
+ call assert_equal((0, 1, 2, 3, 4, 5), pyeval('t'))
+ py t = tt[4:2:-1]
+ call assert_equal((4, 3), pyeval('t'))
+ py t = tt[::2]
+ call assert_equal((0, 2, 4), pyeval('t'))
+ py t = tt[4:2:1]
+ call assert_equal((), pyeval('t'))
+
+ " Error case: Use an invalid index
+ call AssertException(['py x = tt[-10]'], 'Vim(python):IndexError: tuple index out of range')
+
+ " Use a step value of 0
+ call AssertException(['py x = tt[0:3:0]'],
+ \ 'Vim(python):ValueError: slice step cannot be zero')
+
+ " Error case: Invalid slice type
+ call AssertException(["py x = tt['abc']"],
+ \ "Vim(python):TypeError: index must be int or slice, not str")
+
+ " Error case: List with a null tuple item
+ let t = (test_null_tuple(),)
+ py tt = vim.bindeval('t')
+ call AssertException(["py x = tt[:]"], 'Vim(python):SystemError: error return without exception set')
+endfunc
+
+func Test_python_pytuple_to_vimtuple()
+ let t = pyeval("('a', 'b')")
+ call assert_equal(('a', 'b'), t)
+ let t = pyeval("()")
+ call assert_equal((), t)
+ let t = pyeval("('x',)")
+ call assert_equal(('x',), t)
+ let t = pyeval("((1, 2), (), (3, 4))")
+ call assert_equal(((1, 2), (), (3, 4)), t)
+ let t = pyeval("((1, 2), {'a': 10}, [5, 6])")
+ call assert_equal(((1, 2), {'a': 10}, [5, 6]), t)
+
+ " Invalid python tuple
+ py << trim END
+ class FailingIter(object):
+ def __iter__(self):
+ raise NotImplementedError('iter')
+ END
+ call assert_fails('call pyeval("(1, FailingIter, 2)")',
+ \ 'E859: Failed to convert returned python object to a Vim value')
+
+ py del FailingIter
+endfunc
+
+" Test for tuple garbage collection
+func Test_python_tuple_garbage_collect()
+ let t = (1, (2, 3), [4, 5], {'a': 6})
+ py py_t = vim.bindeval('t')
+ let save_testing = v:testing
+ let v:testing = 1
+ call test_garbagecollect_now()
+ let v:testing = save_testing
+
+ let new_t = pyeval('py_t')
+ call assert_equal((1, (2, 3), [4, 5], {'a': 6}), new_t)
+endfunc
+
" Vars
func Test_python_vars()
let g:foo = 'bac'
@@ -1976,6 +2180,7 @@
('range', vim.current.range),
('dictionary', vim.bindeval('{}')),
('list', vim.bindeval('[]')),
+ ('tuple', vim.bindeval('()')),
('function', vim.bindeval('function("tr")')),
('output', sys.stdout),
):
@@ -1991,6 +2196,7 @@
range:__dir__,__members__,append,end,start
dictionary:__dir__,__members__,get,has_key,items,keys,locked,pop,popitem,scope,update,values
list:__dir__,__members__,extend,locked
+ tuple:__dir__,__members__,locked
function:__dir__,__members__,args,auto_rebind,self,softspace
output:__dir__,__members__,close,closed,flush,isatty,readable,seekable,softspace,writable,write,writelines
END
@@ -2003,7 +2209,9 @@
call assert_equal({'a': 1}, pyeval('vim.Dictionary(a=1)'))
call assert_equal({'a': 1}, pyeval('vim.Dictionary(((''a'', 1),))'))
call assert_equal([], pyeval('vim.List()'))
+ call assert_equal((), pyeval('vim.Tuple()'))
call assert_equal(['a', 'b', 'c', '7'], pyeval('vim.List(iter(''abc7''))'))
+ call assert_equal(('a', 'b', 'c', '7'), pyeval('vim.Tuple(iter(''abc7''))'))
call assert_equal(function('tr'), pyeval('vim.Function(''tr'')'))
call assert_equal(function('tr', [123, 3, 4]),
\ pyeval('vim.Function(''tr'', args=[123, 3, 4])'))
diff --git a/src/testdir/test_python3.vim b/src/testdir/test_python3.vim
index c044954..e59ddf9 100644
--- a/src/testdir/test_python3.vim
+++ b/src/testdir/test_python3.vim
@@ -8,6 +8,10 @@
return [1]
endfunction
+func Create_vim_tuple()
+ return ('a', 'b')
+endfunction
+
func Create_vim_dict()
return {'a': 1}
endfunction
@@ -627,6 +631,107 @@
\ 'Vim(py3):TypeError: index must be int or slice, not dict')
endfunc
+" Test for the python Tuple object
+func Test_python3_tuple()
+ " Try to convert a null tuple
+ call AssertException(["py3 l = vim.eval('test_null_tuple()')"],
+ \ s:system_error_pat)
+
+ " Try to convert a Tuple with a null Tuple item
+ call AssertException(["py3 t = vim.eval('(test_null_tuple(),)')"],
+ \ s:system_error_pat)
+
+ " Try to convert a List with a null Tuple item
+ call AssertException(["py3 t = vim.eval('[test_null_tuple()]')"],
+ \ s:system_error_pat)
+
+ " Try to convert a Tuple with a null List item
+ call AssertException(["py3 t = vim.eval('(test_null_list(),)')"],
+ \ s:system_error_pat)
+
+ " Try to bind a null Tuple variable (works because an empty tuple is used)
+ let cmds =<< trim END
+ let t = test_null_tuple()
+ py3 tt = vim.bindeval('t')
+ END
+ call AssertException(cmds, '')
+
+ " Creating a tuple using different iterators
+ py3 t1 = vim.Tuple(['abc', 20, 1.2, (4, 5)])
+ call assert_equal(('abc', 20, 1.2, (4, 5)), py3eval('t1'))
+ py3 t2 = vim.Tuple('abc')
+ call assert_equal(('a', 'b', 'c'), py3eval('t2'))
+ py3 t3 = vim.Tuple({'color': 'red', 'model': 'ford'})
+ call assert_equal(('color', 'model'), py3eval('t3'))
+ py3 t4 = vim.Tuple()
+ call assert_equal((), py3eval('t4'))
+ py3 t5 = vim.Tuple(x**2 for x in range(5))
+ call assert_equal((0, 1, 4, 9, 16), py3eval('t5'))
+ py3 t6 = vim.Tuple(('abc', 20, 1.2, (4, 5)))
+ call assert_equal(('abc', 20, 1.2, (4, 5)), py3eval('t6'))
+
+ " Convert between Vim tuple/list and python tuple/list
+ py3 t = vim.Tuple(vim.bindeval("('a', ('b',), ['c'], {'s': 'd'})"))
+ call assert_equal(('a', ('b',), ['c'], {'s': 'd'}), py3eval('t'))
+ call assert_equal(['a', ('b',), ['c'], {'s': 'd'}], py3eval('list(t)'))
+ call assert_equal(('a', ('b',), ['c'], {'s': 'd'}), py3eval('tuple(t)'))
+
+ py3 l = vim.List(vim.bindeval("['e', ('f',), ['g'], {'s': 'h'}]"))
+ call assert_equal(('e', ('f',), ['g'], {'s': 'h'}), py3eval('tuple(l)'))
+
+ " Tuple assignment
+ py3 tt = vim.bindeval('("a", "b")')
+ call AssertException(['py3 tt[0] = 10'],
+ \ "Vim(py3):TypeError: 'vim.tuple' object does not support item assignment")
+ py3 tt = vim.bindeval('("a", "b")')
+ call AssertException(['py3 tt[0:1] = (10, 20)'],
+ \ "Vim(py3):TypeError: 'vim.tuple' object does not support item assignment")
+
+ " iterating over tuple from Python
+ py3 print([x for x in vim.bindeval("('a', 'b')")])
+
+ " modifying a list item within a tuple
+ let t = ('a', ['b', 'c'], 'd')
+ py3 vim.bindeval('t')[1][1] = 'x'
+ call assert_equal(('a', ['b', 'x'], 'd'), t)
+
+ " length of a tuple
+ let t = ()
+ py3 p_t = vim.bindeval('t')
+ call assert_equal(0, py3eval('len(p_t)'))
+ let t = ('a', )
+ py3 p_t = vim.bindeval('t')
+ call assert_equal(1, py3eval('len(p_t)'))
+ let t = ('a', 'b', 'c')
+ py3 p_t = vim.bindeval('t')
+ call assert_equal(3, py3eval('len(p_t)'))
+
+ " membership test
+ let t = ('a', 'b', 'c')
+ py3 p_t = vim.bindeval('t')
+ call assert_true(py3eval("b'c' in p_t"))
+ call assert_true(py3eval("b'd' not in p_t"))
+
+ py3 x = vim.eval('("a", (2), [3], {})')
+ call assert_equal(('a', '2', ['3'], {}), py3eval('x'))
+
+ " Using a keyword argument for a tuple
+ call AssertException(['py3 x = vim.Tuple(a=1)'],
+ \ 'Vim(py3):TypeError: tuple constructor does not accept keyword arguments')
+
+ " Using dict as an index
+ call AssertException(['py3 x = tt[{}]'],
+ \ 'Vim(py3):TypeError: index must be int or slice, not dict')
+ call AssertException(['py3 x = tt["abc"]'],
+ \ 'Vim(py3):TypeError: index must be int or slice, not str')
+
+ call AssertException(['py3 del tt.locked'],
+ \ 'Vim(py3):AttributeError: cannot delete vim.Tuple attributes')
+
+ call AssertException(['py3 tt.foobar = 1'],
+ \ 'Vim(py3):AttributeError: cannot set attribute foobar')
+endfunc
+
" Test for the python Dict object
func Test_python3_dict()
" Try to convert a null Dict
@@ -1005,11 +1110,24 @@
\ 'Vim(py3):TypeError: cannot modify fixed list')
endfunc
+" Test for locking/unlocking a tuple
+func Test_tuple_lock()
+ let t = (1, 2, 3)
+ py3 t = vim.bindeval('t')
+ py3 t.locked = True
+ call assert_equal(1, islocked('t'))
+ py3 t.locked = False
+ call assert_equal(0, islocked('t'))
+endfunc
+
" Test for py3eval()
func Test_python3_pyeval()
let l = py3eval('[0, 1, 2]')
call assert_equal([0, 1, 2], l)
+ let t = py3eval('("a", "b", "c")')
+ call assert_equal(("a", "b", "c"), t)
+
let d = py3eval('{"a": "b", "c": 1, "d": ["e"]}')
call assert_equal([['a', 'b'], ['c', 1], ['d', ['e']]], sort(items(d)))
@@ -1036,12 +1154,14 @@
let num = 0xbadb33f
let d = {'a': 1, 'b': 2, 'c': str}
let l = [ str, num, d ]
+ let t = ( str, num, d )
let locals = #{
\ s: str,
\ n: num,
\ d: d,
\ l: l,
+ \ t: t,
\ }
" check basics
@@ -1049,9 +1169,11 @@
call assert_equal(0xbadb33f, py3eval('n', locals))
call assert_equal(d, py3eval('d', locals))
call assert_equal(l, py3eval('l', locals))
+ call assert_equal(t, py3eval('t', locals))
call assert_equal('a,b,c', py3eval('b",".join(l)', {'l': ['a', 'b', 'c']}))
call assert_equal('hello', 's'->py3eval({'s': 'hello'}))
call assert_equal('a,b,c', 'b",".join(l)'->py3eval({'l': ['a', 'b', 'c']}))
+ call assert_equal('a-b-c', 'b"-".join(t)'->py3eval({'t': ('a', 'b', 'c')}))
py3 << trim EOF
def __UpdateDict(d, upd):
@@ -1218,6 +1340,92 @@
\ s:system_error_pat)
endfunc
+" Slice
+func Test_python3_tuple_slice()
+ py3 tt = vim.bindeval('(0, 1, 2, 3, 4, 5)')
+ py3 t = tt[:4]
+ call assert_equal((0, 1, 2, 3), py3eval('t'))
+ py3 t = tt[2:]
+ call assert_equal((2, 3, 4, 5), py3eval('t'))
+ py3 t = tt[:-4]
+ call assert_equal((0, 1), py3eval('t'))
+ py3 t = tt[-2:]
+ call assert_equal((4, 5), py3eval('t'))
+ py3 t = tt[2:4]
+ call assert_equal((2, 3), py3eval('t'))
+ py3 t = tt[4:2]
+ call assert_equal((), py3eval('t'))
+ py3 t = tt[-4:-2]
+ call assert_equal((2, 3), py3eval('t'))
+ py3 t = tt[-2:-4]
+ call assert_equal((), py3eval('t'))
+ py3 t = tt[:]
+ call assert_equal((0, 1, 2, 3, 4, 5), py3eval('t'))
+ py3 t = tt[0:6]
+ call assert_equal((0, 1, 2, 3, 4, 5), py3eval('t'))
+ py3 t = tt[-10:10]
+ call assert_equal((0, 1, 2, 3, 4, 5), py3eval('t'))
+ py3 t = tt[4:2:-1]
+ call assert_equal((4, 3), py3eval('t'))
+ py3 t = tt[::2]
+ call assert_equal((0, 2, 4), py3eval('t'))
+ py3 t = tt[4:2:1]
+ call assert_equal((), py3eval('t'))
+
+ " Error case: Use an invalid index
+ call AssertException(['py3 x = tt[-10]'], 'Vim(py3):IndexError: tuple index out of range')
+
+ " Use a step value of 0
+ call AssertException(['py3 x = tt[0:3:0]'],
+ \ 'Vim(py3):ValueError: slice step cannot be zero')
+
+ " Error case: Invalid slice type
+ call AssertException(["py3 x = tt['abc']"],
+ \ "Vim(py3):TypeError: index must be int or slice, not str")
+
+ " Error case: List with a null tuple item
+ let t = (test_null_tuple(),)
+ py3 tt = vim.bindeval('t')
+ call AssertException(["py3 x = tt[:]"], s:system_error_pat)
+endfunc
+
+func Test_python3_pytuple_to_vimtuple()
+ let t = py3eval("('a', 'b')")
+ call assert_equal(('a', 'b'), t)
+ let t = py3eval("()")
+ call assert_equal((), t)
+ let t = py3eval("('x',)")
+ call assert_equal(('x',), t)
+ let t = py3eval("((1, 2), (), (3, 4))")
+ call assert_equal(((1, 2), (), (3, 4)), t)
+ let t = py3eval("((1, 2), {'a': 10}, [5, 6])")
+ call assert_equal(((1, 2), {'a': 10}, [5, 6]), t)
+
+ " Invalid python tuple
+ py3 << trim END
+ class FailingIter(object):
+ def __iter__(self):
+ raise NotImplementedError('iter')
+ END
+ call assert_fails('call py3eval("(1, FailingIter, 2)")',
+ \ 'E859: Failed to convert returned python object to a Vim value')
+
+ py3 del FailingIter
+endfunc
+
+" Test for tuple garbage collection
+func Test_python3_tuple_garbage_collect()
+ let t = (1, (2, 3), [4, 5], {'a': 6})
+ py3 py_t = vim.bindeval('t')
+ let save_testing = v:testing
+ let v:testing = 1
+ call test_garbagecollect_now()
+ let v:testing = save_testing
+
+ let new_t = py3eval('py_t')
+ call assert_equal((1, (2, 3), [4, 5], {'a': 6}), new_t)
+endfunc
+
" Vars
func Test_python3_vars()
let g:foo = 'bac'
@@ -2189,6 +2397,7 @@
('range', vim.current.range),
('dictionary', vim.bindeval('{}')),
('list', vim.bindeval('[]')),
+ ('tuple', vim.bindeval('()')),
('function', vim.bindeval('function("tr")')),
('output', sys.stdout),
):
@@ -2204,6 +2413,7 @@
range:__dir__,append,end,start
dictionary:__dir__,get,has_key,items,keys,locked,pop,popitem,scope,update,values
list:__dir__,extend,locked
+ tuple:__dir__,locked
function:__dir__,args,auto_rebind,self,softspace
output:__dir__,close,closed,flush,isatty,readable,seekable,softspace,writable,write,writelines
END
@@ -2216,7 +2426,9 @@
call assert_equal({'a': 1}, py3eval('vim.Dictionary(a=1)'))
call assert_equal({'a': 1}, py3eval('vim.Dictionary(((''a'', 1),))'))
call assert_equal([], py3eval('vim.List()'))
+ call assert_equal((), py3eval('vim.Tuple()'))
call assert_equal(['a', 'b', 'c', '7'], py3eval('vim.List(iter(''abc7''))'))
+ call assert_equal(('a', 'b', 'c', '7'), py3eval('vim.Tuple(iter(''abc7''))'))
call assert_equal(function('tr'), py3eval('vim.Function(''tr'')'))
call assert_equal(function('tr', [123, 3, 4]),
\ py3eval('vim.Function(''tr'', args=[123, 3, 4])'))
@@ -4065,6 +4277,7 @@
" Regression: Iterator for a Vim object should hold a reference.
func Test_python3_iter_ref()
let g:list_iter_ref_count_increase = -1
+ let g:tuple_iter_ref_count_increase = -1
let g:dict_iter_ref_count_increase = -1
let g:bufmap_iter_ref_count_increase = -1
let g:options_iter_ref_count_increase = -1
@@ -4080,6 +4293,12 @@
for el in v:
vim.vars['list_iter_ref_count_increase'] = sys.getrefcount(v) - base_ref_count
+ create_tuple = vim.Function('Create_vim_tuple')
+ v = create_tuple()
+ base_ref_count = sys.getrefcount(v)
+ for el in v:
+ vim.vars['tuple_iter_ref_count_increase'] = sys.getrefcount(v) - base_ref_count
+
create_dict = vim.Function('Create_vim_dict')
v = create_dict()
base_ref_count = sys.getrefcount(v)
@@ -4100,6 +4319,7 @@
EOF
call assert_equal(1, g:list_iter_ref_count_increase)
+ call assert_equal(1, g:tuple_iter_ref_count_increase)
call assert_equal(1, g:dict_iter_ref_count_increase)
if py3eval('sys.version_info[:2] < (3, 13)')
call assert_equal(1, g:bufmap_iter_ref_count_increase)
diff --git a/src/testdir/test_tuple.vim b/src/testdir/test_tuple.vim
index fce5292..875031f 100644
--- a/src/testdir/test_tuple.vim
+++ b/src/testdir/test_tuple.vim
@@ -1241,12 +1241,43 @@
" Test for locking and unlocking a tuple variable
func Test_tuple_lock()
+ " lockvar 0
+ let g:t = ([0, 1],)
let lines =<< trim END
- VAR t = ([0, 1],)
- call add(t[0], 2)
- call assert_equal(([0, 1, 2], ), t)
+ lockvar 0 g:t
+ LET g:t = ()
END
- call v9.CheckSourceLegacyAndVim9Success(lines)
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ 'E1122: Variable is locked: g:t',
+ \ 'E1122: Variable is locked: t',
+ \ 'E1122: Variable is locked: g:t'])
+ unlet g:t
+
+ " Tuple is immutable. So "lockvar 1" is not applicable to a tuple.
+
+ " lockvar 2
+ let g:t = ([0, 1],)
+ let lines =<< trim END
+ lockvar 2 g:t
+ call add(g:t[0], 2)
+ END
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ 'E741: Value is locked: add() argument',
+ \ 'E741: Value is locked: add() argument',
+ \ 'E741: Value is locked: add() argument'])
+ unlet g:t
+
+ " lockvar 3
+ let g:t = ([0, 1],)
+ let lines =<< trim END
+ lockvar 3 g:t
+ LET g:t[0][0] = 10
+ END
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ 'E741: Value is locked: g:t[0][0] = 10',
+ \ 'E1119: Cannot change locked list item',
+ \ 'E741: Value is locked: g:t[0][0] = 10'])
+ unlet g:t
let lines =<< trim END
VAR t = ([0, 1],)
@@ -1810,6 +1841,25 @@
\ 'E1226: List or Blob required for argument 1'])
endfunc
+" Test for islocked()
+func Test_tuple_islocked()
+ let lines =<< trim END
+ let t = (1, [2], 3)
+ call assert_equal(0, islocked('t'))
+ call assert_equal(0, islocked('t[1]'))
+ lockvar 1 t
+ call assert_equal(1, islocked('t'))
+ call assert_equal(0, islocked('t[1]'))
+ unlockvar t
+ call assert_equal(0, islocked('t'))
+ lockvar 2 t
+ call assert_equal(1, islocked('t[1]'))
+ unlockvar t
+ call assert_equal(0, islocked('t[1]'))
+ END
+ call v9.CheckSourceSuccess(lines)
+endfunc
+
" Test for items()
func Test_tuple_items()
let lines =<< trim END
diff --git a/src/typval.c b/src/typval.c
index 59ac611..b9f8f1c 100644
--- a/src/typval.c
+++ b/src/typval.c
@@ -2095,6 +2095,9 @@
|| (tv->v_type == VAR_LIST
&& tv->vval.v_list != NULL
&& (tv->vval.v_list->lv_lock & VAR_LOCKED))
+ || (tv->v_type == VAR_TUPLE
+ && tv->vval.v_tuple != NULL
+ && (tv->vval.v_tuple->tv_lock & VAR_LOCKED))
|| (tv->v_type == VAR_DICT
&& tv->vval.v_dict != NULL
&& (tv->vval.v_dict->dv_lock & VAR_LOCKED));
diff --git a/src/version.c b/src/version.c
index 2f4c3b3..dcb428e 100644
--- a/src/version.c
+++ b/src/version.c
@@ -705,6 +705,8 @@
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 1239,
+/**/
1238,
/**/
1237,