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/vim9expr.c b/src/vim9expr.c
index f875bc4..68de736 100644
--- a/src/vim9expr.c
+++ b/src/vim9expr.c
@@ -73,7 +73,74 @@
 }
 
 /*
- * Compile getting a member from a list/dict/string/blob.  Stack has the
+ * Compile getting a member from a tuple.  Stack has the indexable value and
+ * the index or the two indexes of a slice.
+ */
+    static int
+compile_tuple_member(
+    type2_T	*typep,
+    int		is_slice,
+    cctx_T	*cctx)
+{
+    if (is_slice)
+    {
+	if (generate_instr_drop(cctx, ISN_TUPLESLICE, 2) == FAIL)
+	    return FAIL;
+	// a copy is made so the member type is no longer declared
+	if (typep->type_decl->tt_type == VAR_TUPLE)
+	    typep->type_decl = &t_tuple_any;
+
+	// a copy is made, the composite is no longer "const"
+	if (typep->type_curr->tt_flags & TTFLAG_CONST)
+	{
+	    type_T *type = copy_type(typep->type_curr, cctx->ctx_type_list);
+
+	    if (type != typep->type_curr)  // did get a copy
+	    {
+		type->tt_flags &= ~(TTFLAG_CONST | TTFLAG_STATIC);
+		typep->type_curr = type;
+	    }
+	}
+    }
+    else
+    {
+	if (typep->type_curr->tt_type == VAR_TUPLE)
+	{
+	    if (typep->type_curr->tt_argcount == 1)
+	    {
+		if (typep->type_curr->tt_flags & TTFLAG_VARARGS)
+		    typep->type_curr
+				= typep->type_curr->tt_args[0]->tt_member;
+		else
+		    typep->type_curr = typep->type_curr->tt_args[0];
+	    }
+	    else
+		typep->type_curr = &t_any;
+	    if (typep->type_decl->tt_type == VAR_TUPLE)
+	    {
+		if (typep->type_decl->tt_argcount == 1)
+		{
+		    if (typep->type_decl->tt_flags & TTFLAG_VARARGS)
+			typep->type_decl
+				= typep->type_decl->tt_args[0]->tt_member;
+		    else
+			typep->type_decl = typep->type_decl->tt_args[0];
+		}
+		else
+		    typep->type_curr = &t_any;
+	    }
+	    else
+		typep->type_decl = typep->type_curr;
+	}
+	if (generate_instr_drop(cctx, ISN_TUPLEINDEX, 1) == FAIL)
+	    return FAIL;
+    }
+
+    return OK;
+}
+
+/*
+ * Compile getting a member from a list/tuple/dict/string/blob.  Stack has the
  * indexable value and the index or the two indexes of a slice.
  * "keeping_dict" is used for dict[func](arg) to pass dict to func.
  */
@@ -85,7 +152,7 @@
     vartype_T	vartype;
     type_T	*idxtype;
 
-    // We can index a list, dict and blob.  If we don't know the type
+    // We can index a list, tuple, dict and blob.  If we don't know the type
     // we can use the index value type.  If we still don't know use an "ANY"
     // instruction.
     // TODO: what about the decl type?
@@ -97,7 +164,8 @@
 		|| typep->type_curr->tt_type == VAR_UNKNOWN)
 						       && idxtype == &t_string)
 	vartype = VAR_DICT;
-    if (vartype == VAR_STRING || vartype == VAR_LIST || vartype == VAR_BLOB)
+    if (vartype == VAR_STRING || vartype == VAR_LIST || vartype == VAR_BLOB
+						|| vartype == VAR_TUPLE)
     {
 	if (need_type(idxtype, &t_number, FALSE,
 					    -1, 0, cctx, FALSE, FALSE) == FAIL)
@@ -174,6 +242,11 @@
 		return FAIL;
 	}
     }
+    else if (vartype == VAR_TUPLE)
+    {
+	if (compile_tuple_member(typep, is_slice, cctx) == FAIL)
+	    return FAIL;
+    }
     else if (vartype == VAR_LIST || typep->type_curr->tt_type == VAR_ANY
 				 || typep->type_curr->tt_type == VAR_UNKNOWN)
     {
@@ -1466,6 +1539,82 @@
 }
 
 /*
+ * parse a tuple: (expr, expr)
+ * "*arg" points to the ','.
+ * ppconst->pp_is_const is set if all the items are constants.
+ */
+    static int
+compile_tuple(
+    char_u **arg,
+    cctx_T *cctx,
+    ppconst_T *ppconst,
+    int first_item_const)
+{
+    char_u	*p = *arg + 1;
+    char_u	*whitep = *arg + 1;
+    int		count = 0;
+    int		is_const;
+    int		is_all_const = TRUE;	// reset when non-const encountered
+    int		must_end = FALSE;
+
+    if (**arg != ')')
+    {
+	if (*p != ')' && !IS_WHITE_OR_NUL(*p))
+	{
+	    semsg(_(e_white_space_required_after_str_str), ",", p - 1);
+	    return FAIL;
+	}
+	count = 1;	// the first tuple item is already processed
+	is_all_const = first_item_const;
+	for (;;)
+	{
+	    if (may_get_next_line(whitep, &p, cctx) == FAIL)
+	    {
+		semsg(_(e_missing_end_of_tuple_rsp_str), *arg);
+		return FAIL;
+	    }
+	    if (*p == ',')
+	    {
+		semsg(_(e_no_white_space_allowed_before_str_str), ",", p);
+		return FAIL;
+	    }
+	    if (*p == ')')
+	    {
+		++p;
+		break;
+	    }
+	    if (must_end)
+	    {
+		semsg(_(e_missing_comma_in_tuple_str), p);
+		return FAIL;
+	    }
+	    if (compile_expr0_ext(&p, cctx, &is_const) == FAIL)
+		return FAIL;
+	    if (!is_const)
+		is_all_const = FALSE;
+	    ++count;
+	    if (*p == ',')
+	    {
+		++p;
+		if (*p != ')' && !IS_WHITE_OR_NUL(*p))
+		{
+		    semsg(_(e_white_space_required_after_str_str), ",", p - 1);
+		    return FAIL;
+		}
+	    }
+	    else
+		must_end = TRUE;
+	    whitep = p;
+	    p = skipwhite(p);
+	}
+    }
+    *arg = p;
+
+    ppconst->pp_is_const = is_all_const;
+    return generate_NEWTUPLE(cctx, count, FALSE);
+}
+
+/*
  * Parse a lambda: "(arg, arg) => expr"
  * "*arg" points to the '('.
  * Returns OK/FAIL when a lambda is recognized, NOTDONE if it's not a lambda.
@@ -2168,6 +2317,11 @@
 
     if (may_get_next_line_error(p, arg, cctx) == FAIL)
 	return FAIL;
+
+    if (**arg == ')')
+	// empty tuple
+	return compile_tuple(arg, cctx, ppconst, FALSE);
+
     if (ppconst->pp_used <= PPSIZE - 10)
     {
 	ret = compile_expr1(arg, cctx, ppconst);
@@ -2181,6 +2335,15 @@
     }
     if (may_get_next_line_error(*arg, arg, cctx) == FAIL)
 	return FAIL;
+    if (ret == OK && **arg == ',')
+    {
+	// tuple
+	int is_const = ppconst->pp_used > 0 || ppconst->pp_is_const;
+	if (generate_ppconst(cctx, ppconst) == FAIL)
+	    return FAIL;
+	return compile_tuple(arg, cctx, ppconst, is_const);
+    }
+
     if (**arg == ')')
 	++*arg;
     else if (ret == OK)
@@ -2440,6 +2603,7 @@
 	    int		is_slice = FALSE;
 
 	    // list index: list[123]
+	    // tuple index: tuple[123]
 	    // dict member: dict[key]
 	    // string index: text[123]
 	    // blob index: blob[123]