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/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