patch 9.1.1232: Vim script is missing the tuple data type

Problem:  Vim script is missing the tuple data type
Solution: Add support for the tuple data type
          (Yegappan Lakshmanan)

closes: #16776

Signed-off-by: Yegappan Lakshmanan <yegappan@yahoo.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
diff --git a/src/gc.c b/src/gc.c
index 54c7444..b95b2ca 100644
--- a/src/gc.c
+++ b/src/gc.c
@@ -122,31 +122,31 @@
     // buffer-local variables
     FOR_ALL_BUFFERS(buf)
 	abort = abort || set_ref_in_item(&buf->b_bufvar.di_tv, copyID,
-								  NULL, NULL);
+							NULL, NULL, NULL);
 
     // window-local variables
     FOR_ALL_TAB_WINDOWS(tp, wp)
 	abort = abort || set_ref_in_item(&wp->w_winvar.di_tv, copyID,
-								  NULL, NULL);
+							NULL, NULL, NULL);
     // window-local variables in autocmd windows
     for (int i = 0; i < AUCMD_WIN_COUNT; ++i)
 	if (aucmd_win[i].auc_win != NULL)
 	    abort = abort || set_ref_in_item(
-		    &aucmd_win[i].auc_win->w_winvar.di_tv, copyID, NULL, NULL);
+		    &aucmd_win[i].auc_win->w_winvar.di_tv, copyID, NULL, NULL, NULL);
 #ifdef FEAT_PROP_POPUP
     FOR_ALL_POPUPWINS(wp)
 	abort = abort || set_ref_in_item(&wp->w_winvar.di_tv, copyID,
-								  NULL, NULL);
+								  NULL, NULL, NULL);
     FOR_ALL_TABPAGES(tp)
 	FOR_ALL_POPUPWINS_IN_TAB(tp, wp)
 		abort = abort || set_ref_in_item(&wp->w_winvar.di_tv, copyID,
-								  NULL, NULL);
+								  NULL, NULL, NULL);
 #endif
 
     // tabpage-local variables
     FOR_ALL_TABPAGES(tp)
 	abort = abort || set_ref_in_item(&tp->tp_winvar.di_tv, copyID,
-								  NULL, NULL);
+								  NULL, NULL, NULL);
     // global variables
     abort = abort || garbage_collect_globvars(copyID);
 
@@ -269,6 +269,9 @@
     // Go through the list of lists and free items without this copyID.
     did_free |= list_free_nonref(copyID);
 
+    // Go through the list of tuples and free items without this copyID.
+    did_free |= tuple_free_nonref(copyID);
+
     // Go through the list of objects and free items without this copyID.
     did_free |= object_free_nonref(copyID);
 
@@ -291,6 +294,7 @@
     object_free_items(copyID);
     dict_free_items(copyID);
     list_free_items(copyID);
+    tuple_free_items(copyID);
 
 #ifdef FEAT_JOB_CHANNEL
     // Go through the list of jobs and free items without the copyID. This
@@ -314,7 +318,11 @@
  * Returns TRUE if setting references failed somehow.
  */
     int
-set_ref_in_ht(hashtab_T *ht, int copyID, list_stack_T **list_stack)
+set_ref_in_ht(
+    hashtab_T		*ht,
+    int			copyID,
+    list_stack_T	**list_stack,
+    tuple_stack_T	**tuple_stack)
 {
     int		todo;
     int		abort = FALSE;
@@ -336,8 +344,9 @@
 		if (!HASHITEM_EMPTY(hi))
 		{
 		    --todo;
-		    abort = abort || set_ref_in_item(&HI2DI(hi)->di_tv, copyID,
-						       &ht_stack, list_stack);
+		    abort = abort
+			|| set_ref_in_item(&HI2DI(hi)->di_tv, copyID,
+				       &ht_stack, list_stack, tuple_stack);
 		}
 	}
 
@@ -366,7 +375,7 @@
     if (d != NULL && d->dv_copyID != copyID)
     {
 	d->dv_copyID = copyID;
-	return set_ref_in_ht(&d->dv_hashtab, copyID, NULL);
+	return set_ref_in_ht(&d->dv_hashtab, copyID, NULL, NULL);
     }
     return FALSE;
 }
@@ -382,7 +391,7 @@
     if (ll != NULL && ll->lv_copyID != copyID)
     {
 	ll->lv_copyID = copyID;
-	return set_ref_in_list_items(ll, copyID, NULL);
+	return set_ref_in_list_items(ll, copyID, NULL, NULL);
     }
     return FALSE;
 }
@@ -394,7 +403,11 @@
  * Returns TRUE if setting references failed somehow.
  */
     int
-set_ref_in_list_items(list_T *l, int copyID, ht_stack_T **ht_stack)
+set_ref_in_list_items(
+    list_T		*l,
+    int			copyID,
+    ht_stack_T		**ht_stack,
+    tuple_stack_T	**tuple_stack)
 {
     listitem_T	 *li;
     int		 abort = FALSE;
@@ -411,7 +424,7 @@
 	    // list_stack.
 	    for (li = cur_l->lv_first; !abort && li != NULL; li = li->li_next)
 		abort = abort || set_ref_in_item(&li->li_tv, copyID,
-						       ht_stack, &list_stack);
+				       ht_stack, &list_stack, tuple_stack);
 	if (list_stack == NULL)
 	    break;
 
@@ -426,6 +439,50 @@
 }
 
 /*
+ * Mark all lists and dicts referenced through tuple "t" with "copyID".
+ * "ht_stack" is used to add hashtabs to be marked.  Can be NULL.
+ *
+ * Returns TRUE if setting references failed somehow.
+ */
+    int
+set_ref_in_tuple_items(
+    tuple_T		*tuple,
+    int			copyID,
+    ht_stack_T		**ht_stack,
+    list_stack_T	**list_stack)
+{
+    int			abort = FALSE;
+    tuple_T		*cur_t;
+    tuple_stack_T	*tuple_stack = NULL;
+    tuple_stack_T	*tempitem;
+
+    cur_t = tuple;
+    for (;;)
+    {
+	// Mark each item in the tuple.  If the item contains a hashtab
+	// it is added to ht_stack, if it contains a list it is added to
+	// list_stack.
+	for (int i = 0; i < cur_t->tv_items.ga_len; i++)
+	{
+	    typval_T *tv = ((typval_T *)cur_t->tv_items.ga_data) + i;
+	    abort = abort
+		|| set_ref_in_item(tv, copyID,
+			ht_stack, list_stack, &tuple_stack);
+	}
+	if (tuple_stack == NULL)
+	    break;
+
+	// take an item from the stack
+	cur_t = tuple_stack->tuple;
+	tempitem = tuple_stack;
+	tuple_stack = tuple_stack->prev;
+	free(tempitem);
+    }
+
+    return abort;
+}
+
+/*
  * Mark the partial in callback 'cb' with "copyID".
  */
     int
@@ -438,7 +495,7 @@
 
     tv.v_type = VAR_PARTIAL;
     tv.vval.v_partial = cb->cb_partial;
-    return set_ref_in_item(&tv, copyID, NULL, NULL);
+    return set_ref_in_item(&tv, copyID, NULL, NULL, NULL);
 }
 
 /*
@@ -450,7 +507,8 @@
     dict_T		*dd,
     int			copyID,
     ht_stack_T		**ht_stack,
-    list_stack_T	**list_stack)
+    list_stack_T	**list_stack,
+    tuple_stack_T	**tuple_stack)
 {
     if (dd == NULL || dd->dv_copyID == copyID)
 	return FALSE;
@@ -458,7 +516,7 @@
     // Didn't see this dict yet.
     dd->dv_copyID = copyID;
     if (ht_stack == NULL)
-	return set_ref_in_ht(&dd->dv_hashtab, copyID, list_stack);
+	return set_ref_in_ht(&dd->dv_hashtab, copyID, list_stack, tuple_stack);
 
     ht_stack_T *newitem = ALLOC_ONE(ht_stack_T);
     if (newitem == NULL)
@@ -480,7 +538,8 @@
     list_T		*ll,
     int			copyID,
     ht_stack_T		**ht_stack,
-    list_stack_T	**list_stack)
+    list_stack_T	**list_stack,
+    tuple_stack_T	**tuple_stack)
 {
     if (ll == NULL || ll->lv_copyID == copyID)
 	return FALSE;
@@ -488,7 +547,7 @@
     // Didn't see this list yet.
     ll->lv_copyID = copyID;
     if (list_stack == NULL)
-	return set_ref_in_list_items(ll, copyID, ht_stack);
+	return set_ref_in_list_items(ll, copyID, ht_stack, tuple_stack);
 
     list_stack_T *newitem = ALLOC_ONE(list_stack_T);
     if (newitem == NULL)
@@ -502,6 +561,37 @@
 }
 
 /*
+ * Mark the tuple "tt" with "copyID".
+ * Also see set_ref_in_item().
+ */
+    static int
+set_ref_in_item_tuple(
+    tuple_T		*tt,
+    int			copyID,
+    ht_stack_T		**ht_stack,
+    list_stack_T	**list_stack,
+    tuple_stack_T	**tuple_stack)
+{
+    if (tt == NULL || tt->tv_copyID == copyID)
+	return FALSE;
+
+    // Didn't see this tuple yet.
+    tt->tv_copyID = copyID;
+    if (tuple_stack == NULL)
+	return set_ref_in_tuple_items(tt, copyID, ht_stack, list_stack);
+
+    tuple_stack_T *newitem = ALLOC_ONE(tuple_stack_T);
+    if (newitem == NULL)
+	return TRUE;
+
+    newitem->tuple = tt;
+    newitem->prev = *tuple_stack;
+    *tuple_stack = newitem;
+
+    return FALSE;
+}
+
+/*
  * Mark the partial "pt" with "copyID".
  * Also see set_ref_in_item().
  */
@@ -510,7 +600,8 @@
     partial_T		*pt,
     int			copyID,
     ht_stack_T		**ht_stack,
-    list_stack_T	**list_stack)
+    list_stack_T	**list_stack,
+    tuple_stack_T	**tuple_stack)
 {
     if (pt == NULL || pt->pt_copyID == copyID)
 	return FALSE;
@@ -526,7 +617,7 @@
 
 	dtv.v_type = VAR_DICT;
 	dtv.vval.v_dict = pt->pt_dict;
-	set_ref_in_item(&dtv, copyID, ht_stack, list_stack);
+	set_ref_in_item(&dtv, copyID, ht_stack, list_stack, tuple_stack);
     }
 
     if (pt->pt_obj != NULL)
@@ -535,12 +626,12 @@
 
 	objtv.v_type = VAR_OBJECT;
 	objtv.vval.v_object = pt->pt_obj;
-	set_ref_in_item(&objtv, copyID, ht_stack, list_stack);
+	set_ref_in_item(&objtv, copyID, ht_stack, list_stack, tuple_stack);
     }
 
     for (int i = 0; i < pt->pt_argc; ++i)
 	abort = abort || set_ref_in_item(&pt->pt_argv[i], copyID,
-		ht_stack, list_stack);
+		ht_stack, list_stack, tuple_stack);
     // pt_funcstack is handled in set_ref_in_funcstacks()
     // pt_loopvars is handled in set_ref_in_loopvars()
 
@@ -557,7 +648,8 @@
     job_T		*job,
     int			copyID,
     ht_stack_T		**ht_stack,
-    list_stack_T	**list_stack)
+    list_stack_T	**list_stack,
+    tuple_stack_T	**tuple_stack)
 {
     typval_T    dtv;
 
@@ -569,13 +661,13 @@
     {
 	dtv.v_type = VAR_CHANNEL;
 	dtv.vval.v_channel = job->jv_channel;
-	set_ref_in_item(&dtv, copyID, ht_stack, list_stack);
+	set_ref_in_item(&dtv, copyID, ht_stack, list_stack, tuple_stack);
     }
     if (job->jv_exit_cb.cb_partial != NULL)
     {
 	dtv.v_type = VAR_PARTIAL;
 	dtv.vval.v_partial = job->jv_exit_cb.cb_partial;
-	set_ref_in_item(&dtv, copyID, ht_stack, list_stack);
+	set_ref_in_item(&dtv, copyID, ht_stack, list_stack, tuple_stack);
     }
 
     return FALSE;
@@ -590,7 +682,8 @@
     channel_T		*ch,
     int			copyID,
     ht_stack_T		**ht_stack,
-    list_stack_T	**list_stack)
+    list_stack_T	**list_stack,
+    tuple_stack_T	**tuple_stack)
 {
     typval_T    dtv;
 
@@ -602,33 +695,33 @@
     {
 	for (jsonq_T *jq = ch->ch_part[part].ch_json_head.jq_next;
 		jq != NULL; jq = jq->jq_next)
-	    set_ref_in_item(jq->jq_value, copyID, ht_stack, list_stack);
+	    set_ref_in_item(jq->jq_value, copyID, ht_stack, list_stack, tuple_stack);
 	for (cbq_T *cq = ch->ch_part[part].ch_cb_head.cq_next; cq != NULL;
 		cq = cq->cq_next)
 	    if (cq->cq_callback.cb_partial != NULL)
 	    {
 		dtv.v_type = VAR_PARTIAL;
 		dtv.vval.v_partial = cq->cq_callback.cb_partial;
-		set_ref_in_item(&dtv, copyID, ht_stack, list_stack);
+		set_ref_in_item(&dtv, copyID, ht_stack, list_stack, tuple_stack);
 	    }
 	if (ch->ch_part[part].ch_callback.cb_partial != NULL)
 	{
 	    dtv.v_type = VAR_PARTIAL;
 	    dtv.vval.v_partial = ch->ch_part[part].ch_callback.cb_partial;
-	    set_ref_in_item(&dtv, copyID, ht_stack, list_stack);
+	    set_ref_in_item(&dtv, copyID, ht_stack, list_stack, tuple_stack);
 	}
     }
     if (ch->ch_callback.cb_partial != NULL)
     {
 	dtv.v_type = VAR_PARTIAL;
 	dtv.vval.v_partial = ch->ch_callback.cb_partial;
-	set_ref_in_item(&dtv, copyID, ht_stack, list_stack);
+	set_ref_in_item(&dtv, copyID, ht_stack, list_stack, tuple_stack);
     }
     if (ch->ch_close_cb.cb_partial != NULL)
     {
 	dtv.v_type = VAR_PARTIAL;
 	dtv.vval.v_partial = ch->ch_close_cb.cb_partial;
-	set_ref_in_item(&dtv, copyID, ht_stack, list_stack);
+	set_ref_in_item(&dtv, copyID, ht_stack, list_stack, tuple_stack);
     }
 
     return FALSE;
@@ -644,7 +737,8 @@
     class_T		*cl,
     int			copyID,
     ht_stack_T		**ht_stack,
-    list_stack_T	**list_stack)
+    list_stack_T	**list_stack,
+    tuple_stack_T	**tuple_stack)
 {
     int abort = FALSE;
 
@@ -659,7 +753,7 @@
 	for (int i = 0; !abort && i < cl->class_class_member_count; ++i)
 	    abort = abort || set_ref_in_item(
 		    &cl->class_members_tv[i],
-		    copyID, ht_stack, list_stack);
+		    copyID, ht_stack, list_stack, tuple_stack);
     }
 
     for (int i = 0; !abort && i < cl->class_class_function_count; ++i)
@@ -682,7 +776,8 @@
     object_T		*obj,
     int			copyID,
     ht_stack_T		**ht_stack,
-    list_stack_T	**list_stack)
+    list_stack_T	**list_stack,
+    tuple_stack_T	**tuple_stack)
 {
     int abort = FALSE;
 
@@ -696,7 +791,7 @@
     for (int i = 0; !abort
 	    && i < obj->obj_class->class_obj_member_count; ++i)
 	abort = abort || set_ref_in_item(mtv + i, copyID,
-		ht_stack, list_stack);
+		ht_stack, list_stack, tuple_stack);
 
     return abort;
 }
@@ -714,7 +809,8 @@
     typval_T	    *tv,
     int		    copyID,
     ht_stack_T	    **ht_stack,
-    list_stack_T    **list_stack)
+    list_stack_T    **list_stack,
+    tuple_stack_T   **tuple_stack)
 {
     int		abort = FALSE;
 
@@ -722,12 +818,15 @@
     {
 	case VAR_DICT:
 	    return set_ref_in_item_dict(tv->vval.v_dict, copyID,
-							 ht_stack, list_stack);
+					 ht_stack, list_stack, tuple_stack);
 
 	case VAR_LIST:
 	    return set_ref_in_item_list(tv->vval.v_list, copyID,
-							 ht_stack, list_stack);
+					 ht_stack, list_stack, tuple_stack);
 
+	case VAR_TUPLE:
+	    return set_ref_in_item_tuple(tv->vval.v_tuple, copyID,
+					 ht_stack, list_stack, tuple_stack);
 	case VAR_FUNC:
 	{
 	    abort = set_ref_in_func(tv->vval.v_string, NULL, copyID);
@@ -736,12 +835,12 @@
 
 	case VAR_PARTIAL:
 	    return set_ref_in_item_partial(tv->vval.v_partial, copyID,
-							ht_stack, list_stack);
+					ht_stack, list_stack, tuple_stack);
 
 	case VAR_JOB:
 #ifdef FEAT_JOB_CHANNEL
 	    return set_ref_in_item_job(tv->vval.v_job, copyID,
-							 ht_stack, list_stack);
+					ht_stack, list_stack, tuple_stack);
 #else
 	    break;
 #endif
@@ -749,18 +848,18 @@
 	case VAR_CHANNEL:
 #ifdef FEAT_JOB_CHANNEL
 	    return set_ref_in_item_channel(tv->vval.v_channel, copyID,
-							 ht_stack, list_stack);
+					ht_stack, list_stack, tuple_stack);
 #else
 	    break;
 #endif
 
 	case VAR_CLASS:
 	    return set_ref_in_item_class(tv->vval.v_class, copyID,
-							 ht_stack, list_stack);
+					ht_stack, list_stack, tuple_stack);
 
 	case VAR_OBJECT:
 	    return set_ref_in_item_object(tv->vval.v_object, copyID,
-							 ht_stack, list_stack);
+					ht_stack, list_stack, tuple_stack);
 
 	case VAR_UNKNOWN:
 	case VAR_ANY: