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/runtime/doc/builtin.txt b/runtime/doc/builtin.txt
index 48bdc43..d7c9740 100644
--- a/runtime/doc/builtin.txt
+++ b/runtime/doc/builtin.txt
@@ -1,4 +1,4 @@
-*builtin.txt*	For Vim version 9.1.  Last change: 2025 Mar 22
+*builtin.txt*	For Vim version 9.1.  Last change: 2025 Mar 23
 
 
 		  VIM REFERENCE MANUAL	  by Bram Moolenaar
@@ -207,7 +207,7 @@
 foldlevel({lnum})		Number	fold level at {lnum}
 foldtext()			String	line displayed for closed fold
 foldtextresult({lnum})		String	text for closed fold at {lnum}
-foreach({expr1}, {expr2})	List/Dict/Blob/String
+foreach({expr1}, {expr2})	List/Tuple/Dict/Blob/String
 					for each item in {expr1} call {expr2}
 foreground()			Number	bring the Vim window to the foreground
 fullcommand({name} [, {vim9}])	String	get full command from {name}
@@ -348,7 +348,7 @@
 				Job	start a job
 job_status({job})		String	get the status of {job}
 job_stop({job} [, {how}])	Number	stop {job}
-join({list} [, {sep}])		String	join {list} items into one String
+join({expr} [, {sep}])		String	join items in {expr} into one String
 js_decode({string})		any	decode JS style JSON
 js_encode({expr})		String	encode JS style JSON
 json_decode({string})		any	decode JSON
@@ -364,6 +364,7 @@
 lispindent({lnum})		Number	Lisp indent for line {lnum}
 list2blob({list})		Blob	turn {list} of numbers into a Blob
 list2str({list} [, {utf8}])	String	turn {list} of numbers into a String
+list2tuple({list})		Tuple	turn {list} of items into a tuple
 listener_add({callback} [, {buf}])
 				Number	add a callback to listen to changes
 listener_flush([{buf}])		none	invoke listener callbacks
@@ -511,10 +512,10 @@
 					remove bytes {idx}-{end} from {blob}
 remove({dict}, {key})		any	remove entry {key} from {dict}
 rename({from}, {to})		Number	rename (move) file from {from} to {to}
-repeat({expr}, {count})		List/Blob/String
+repeat({expr}, {count})		List/Tuple/Blob/String
 					repeat {expr} {count} times
 resolve({filename})		String	get filename a shortcut points to
-reverse({obj})			List/Blob/String
+reverse({obj})			List/Tuple/Blob/String
 					reverse {obj}
 round({expr})			Float	round off {expr}
 rubyeval({expr})		any	evaluate |Ruby| expression
@@ -713,6 +714,7 @@
 test_null_list()		List	null value for testing
 test_null_partial()		Funcref	null value for testing
 test_null_string()		String	null value for testing
+test_null_tuple()		Tuple	null value for testing
 test_option_not_set({name})	none	reset flag indicating option was set
 test_override({expr}, {val})	none	test with Vim internal overrides
 test_refcount({expr})		Number	get the reference count of {expr}
@@ -734,6 +736,7 @@
 trim({text} [, {mask} [, {dir}]])
 				String	trim characters in {mask} from {text}
 trunc({expr})			Float	truncate Float {expr}
+tuple2list({tuple})		List	turn {tuple} of items into a list
 type({expr})			Number	type of value {expr}
 typename({expr})		String	representation of the type of {expr}
 undofile({name})		String	undo file name for {name}
@@ -2073,7 +2076,8 @@
 		that the original |List| can be changed without changing the
 		copy, and vice versa.  But the items are identical, thus
 		changing an item changes the contents of both |Lists|.
-		A |Dictionary| is copied in a similar way as a |List|.
+		A |Tuple| or |Dictionary| is copied in a similar way as a
+		|List|.
 		Also see |deepcopy()|.
 		Can also be used as a |method|: >
 			mylist->copy()
@@ -2116,10 +2120,10 @@
 
 count({comp}, {expr} [, {ic} [, {start}]])		*count()* *E706*
 		Return the number of times an item with value {expr} appears
-		in |String|, |List| or |Dictionary| {comp}.
+		in |String|, |List|, |Tuple| or |Dictionary| {comp}.
 
 		If {start} is given then start with the item with this index.
-		{start} can only be used with a |List|.
+		{start} can only be used with a |List| or a |Tuple|.
 
 		When {ic} is given and it's |TRUE| then case is ignored.
 
@@ -2239,7 +2243,8 @@
 		|Dictionary|, a copy for it is made, recursively.  Thus
 		changing an item in the copy does not change the contents of
 		the original |List|.
-		A |Dictionary| is copied in a similar way as a |List|.
+		A |Tuple| or |Dictionary| is copied in a similar way as a
+		|List|.
 
 		When {noref} is omitted or zero a contained |List| or
 		|Dictionary| is only copied once.  All references point to
@@ -2547,8 +2552,8 @@
 
 empty({expr})						*empty()*
 		Return the Number 1 if {expr} is empty, zero otherwise.
-		- A |List| or |Dictionary| is empty when it does not have any
-		  items.
+		- A |List|, |Tuple| or |Dictionary| is empty when it does
+		  not have any items.
 		- A |String| is empty when its length is zero.
 		- A |Number| and |Float| are empty when their value is zero.
 		- |v:false|, |v:none| and |v:null| are empty, |v:true| is not.
@@ -3475,8 +3480,9 @@
 		Return type: |String|
 
 
-foreach({expr1}, {expr2})					*foreach()*
-		{expr1} must be a |List|, |String|, |Blob| or |Dictionary|.
+foreach({expr1}, {expr2})				*foreach()* *E1525*
+		{expr1} must be a |List|, |Tuple|, |String|, |Blob| or
+		|Dictionary|.
 		For each item in {expr1} execute {expr2}. {expr1} is not
 		modified; its values may be, as with |:lockvar| 1. |E741|
 		See |map()| and |filter()| to modify {expr1}.
@@ -3485,10 +3491,10 @@
 
 		If {expr2} is a |string|, inside {expr2} |v:val| has the value
 		of the current item.  For a |Dictionary| |v:key| has the key
-		of the current item and for a |List| |v:key| has the index of
-		the current item.  For a |Blob| |v:key| has the index of the
-		current byte. For a |String| |v:key| has the index of the
-		current character.
+		of the current item and for a |List| or a |Tuple| |v:key| has
+		the index of the current item.  For a |Blob| |v:key| has the
+		index of the current byte. For a |String| |v:key| has the
+		index of the current character.
 		Examples: >
 			call foreach(mylist, 'used[v:val] = true')
 <		This records the items that are in the {expr1} list.
@@ -3514,8 +3520,8 @@
 		Can also be used as a |method|: >
 			mylist->foreach(expr2)
 <
-		Return type: |String|, |Blob| list<{type}> or dict<{type}>
-		depending on {expr1}
+		Return type: |String|, |Blob|, list<{type}>, tuple<{type}> or
+		dict<{type}> depending on {expr1}
 
 							*foreground()*
 foreground()	Move the Vim window to the foreground.  Useful when sent from
@@ -3688,6 +3694,15 @@
 <
 		Return type: any, depending on {list}
 
+get({tuple}, {idx} [, {default}])			*get()-tuple*
+		Get item {idx} from |Tuple| {tuple}.  When this item is not
+		available return {default}.  Return zero when {default} is
+		omitted.
+		Preferably used as a |method|: >
+			mytuple->get(idx)
+<
+		Return type: any, depending on {tuple}
+
 get({blob}, {idx} [, {default}])			*get()-blob*
 		Get byte {idx} from |Blob| {blob}.  When this byte is not
 		available return {default}.  Return -1 when {default} is
@@ -5821,8 +5836,8 @@
 <		prevents {item} from being garbage collected and provides a
 		way to get the {item} from the `id`.
 
-		{item} may be a List, Dictionary, Object, Job, Channel or
-		Blob. If the item is not a permitted type, or it is a null
+		{item} may be a List, Tuple, Dictionary, Object, Job, Channel
+		or Blob. If the item is not a permitted type, or it is a null
 		value, then an empty String is returned.
 
 		Can also be used as a |method|: >
@@ -5849,12 +5864,12 @@
 		Find {expr} in {object} and return its index.  See
 		|indexof()| for using a lambda to select the item.
 
-		If {object} is a |List| return the lowest index where the item
-		has a value equal to {expr}.  There is no automatic
-		conversion, so the String "4" is different from the Number 4.
-		And the number 4 is different from the Float 4.0.  The value
-		of 'ignorecase' is not used here, case matters as indicated by
-		the {ic} argument.
+		If {object} is a |List| or a |Tuple| return the lowest index
+		where the item has a value equal to {expr}.  There is no
+		automatic conversion, so the String "4" is different from the
+		Number 4.  And the number 4 is different from the Float 4.0.
+		The value of 'ignorecase' is not used here, case matters as
+		indicated by the {ic} argument.
 
 		If {object} is |Blob| return the lowest index where the byte
 		value is equal to {expr}.
@@ -5878,11 +5893,11 @@
 
 indexof({object}, {expr} [, {opts}])			*indexof()*
 		Returns the index of an item in {object} where {expr} is
-		v:true.  {object} must be a |List| or a |Blob|.
+		v:true.  {object} must be a |List|, a |Tuple| or a |Blob|.
 
-		If {object} is a |List|, evaluate {expr} for each item in the
-		List until the expression is v:true and return the index of
-		this item.
+		If {object} is a |List| or a |Tuple|, evaluate {expr} for each
+		item in the List until the expression is v:true and return the
+		index of this item.
 
 		If {object} is a |Blob| evaluate {expr} for each byte in the
 		Blob until the expression is v:true and return the index of
@@ -5890,11 +5905,11 @@
 
 		{expr} must be a |string| or |Funcref|.
 
-		If {expr} is a |string|: If {object} is a |List|, inside
-		{expr} |v:key| has the index of the current List item and
-		|v:val| has the value of the item.  If {object} is a |Blob|,
-		inside {expr} |v:key| has the index of the current byte and
-		|v:val| has the byte value.
+		If {expr} is a |string|: If {object} is a |List| or a |Tuple|,
+		inside {expr} |v:key| has the index of the current List or
+		Tuple item and |v:val| has the value of the item.  If {object}
+		is a |Blob|, inside {expr} |v:key| has the index of the
+		current byte and |v:val| has the byte value.
 
 		If {expr} is a |Funcref| it must take two arguments:
 			1. the key or the index of the current item.
@@ -6204,9 +6219,9 @@
 			   echo key .. ': ' .. value
 			endfor
 <
-		A List or a String argument is also supported.  In these
-		cases, items() returns a List with the index and the value at
-		the index.
+		A |List|, a |Tuple| or a |String| argument is also supported.
+		In these cases, items() returns a List with the index and the
+		value at the index.
 
 		Can also be used as a |method|: >
 			mydict->items()
@@ -6217,16 +6232,17 @@
 job_ functions are documented here: |job-functions-details|
 
 
-join({list} [, {sep}])					*join()*
-		Join the items in {list} together into one String.
+join({expr} [, {sep}])					*join()*
+		Join the items in {expr} together into one String.  {expr} can
+		be a |List| or a |Tuple|.
 		When {sep} is specified it is put in between the items.  If
 		{sep} is omitted a single space is used.
 		Note that {sep} is not added at the end.  You might want to
 		add it there too: >
 			let lines = join(mylist, "\n") .. "\n"
-<		String items are used as-is.  |Lists| and |Dictionaries| are
-		converted into a string like with |string()|.
-		The opposite function is |split()|.
+<		String items are used as-is.  |Lists|, |Tuples| and
+		|Dictionaries| are converted into a string like with
+		|string()|.  The opposite function is |split()|.
 
 		Can also be used as a |method|: >
 			mylist->join()
@@ -6320,6 +6336,8 @@
 		   |Funcref|		not possible, error
 		   |List|		as an array (possibly null); when
 					used recursively: []
+		   |Tuple|		as an array (possibly null); when
+					used recursively: []
 		   |Dict|		as an object (possibly null); when
 					used recursively: {}
 		   |Blob|		as an array of the individual bytes
@@ -6368,6 +6386,8 @@
 		used, as with |strlen()|.
 		When {expr} is a |List| the number of items in the |List| is
 		returned.
+		When {expr} is a |Tuple| the number of items in the |Tuple| is
+		returned.
 		When {expr} is a |Blob| the number of bytes is returned.
 		When {expr} is a |Dictionary| the number of entries in the
 		|Dictionary| is returned.
@@ -6549,6 +6569,25 @@
 		Return type: |String|
 
 
+list2tuple({list})					*list2tuple()*
+		Create a Tuple from a shallow copy of the list items.
+		Examples: >
+			list2tuple([1, 2, 3])		returns (1, 2, 3)
+<		|tuple2list()| does the opposite.
+
+		This function doesn't recursively convert all the List items
+		in {list} to a Tuple.  Note that the items are identical
+		between the list and the tuple, changing an item changes the
+		contents of both the tuple and the list.
+
+		Returns an empty tuple on error.
+
+		Can also be used as a |method|: >
+			GetList()->list2tuple()
+<
+		Return type: tuple<{type}> (depending on the given |List|)
+
+
 listener_add({callback} [, {buf}])			*listener_add()*
 		Add a callback function that will be invoked when changes have
 		been made to buffer {buf}.
@@ -7464,11 +7503,12 @@
 		Return the maximum value of all items in {expr}. Example: >
 			echo max([apples, pears, oranges])
 
-<		{expr} can be a |List| or a |Dictionary|.  For a Dictionary,
-		it returns the maximum of all values in the Dictionary.
-		If {expr} is neither a List nor a Dictionary, or one of the
-		items in {expr} cannot be used as a Number this results in
-		an error.  An empty |List| or |Dictionary| results in zero.
+<		{expr} can be a |List|, a |Tuple| or a |Dictionary|.  For a
+		Dictionary, it returns the maximum of all values in the
+		Dictionary.  If {expr} is neither a List nor a Tuple nor a
+		Dictionary, or one of the items in {expr} cannot be used as a
+		Number this results in an error.  An empty |List|, |Tuple|
+		or |Dictionary| results in zero.
 
 		Can also be used as a |method|: >
 			mylist->max()
@@ -7555,11 +7595,12 @@
 		Return the minimum value of all items in {expr}. Example:  >
 			echo min([apples, pears, oranges])
 
-<		{expr} can be a |List| or a |Dictionary|.  For a Dictionary,
-		it returns the minimum of all values in the Dictionary.
-		If {expr} is neither a List nor a Dictionary, or one of the
-		items in {expr} cannot be used as a Number this results in
-		an error.  An empty |List| or |Dictionary| results in zero.
+<		{expr} can be a |List|, a |Tuple| or a |Dictionary|.  For a
+		Dictionary, it returns the minimum of all values in the
+		Dictionary.  If {expr} is neither a List nor a Tuple nor a
+		Dictionary, or one of the items in {expr} cannot be used as a
+		Number this results in an error.  An empty |List|, |Tuple| or
+		|Dictionary| results in zero.
 
 		Can also be used as a |method|: >
 			mylist->min()
@@ -8582,8 +8623,8 @@
 
 reduce({object}, {func} [, {initial}])			*reduce()* *E998*
 		{func} is called for every item in {object}, which can be a
-		|String|, |List| or a |Blob|.  {func} is called with two
-		arguments: the result so far and current item.  After
+		|String|, |List|, |Tuple| or a |Blob|.  {func} is called with
+		two arguments: the result so far and current item.  After
 		processing all items the result is returned. *E1132*
 
 		{initial} is the initial result.  When omitted, the first item
@@ -8904,16 +8945,16 @@
 		result.  Example: >
 			:let separator = repeat('-', 80)
 <		When {count} is zero or negative the result is empty.
-		When {expr} is a |List| or a |Blob| the result is {expr}
-		concatenated {count} times.  Example: >
+		When {expr} is a |List|, a |Tuple| or a |Blob| the result is
+		{expr} concatenated {count} times.  Example: >
 			:let longlist = repeat(['a', 'b'], 3)
 <		Results in ['a', 'b', 'a', 'b', 'a', 'b'].
 
 		Can also be used as a |method|: >
 			mylist->repeat(count)
 <
-		Return type: |String|, |Blob| or list<{type}> depending on
-		{expr}
+		Return type: |String|, |Blob|, list<{type}> or tuple<{type}>
+		depending on {expr}
 
 
 resolve({filename})					*resolve()* *E655*
@@ -8940,18 +8981,19 @@
 
 reverse({object})					*reverse()*
 		Reverse the order of items in {object}.  {object} can be a
-		|List|, a |Blob| or a |String|.  For a List and a Blob the
-		items are reversed in-place and {object} is returned.
+		|List|, a |Tuple|, a |Blob| or a |String|.  For a List and a
+		Blob the items are reversed in-place and {object} is returned.
+		For a Tuple, a new Tuple is returned.
 		For a String a new String is returned.
-		Returns zero if {object} is not a List, Blob or a String.
-		If you want a List or Blob to remain unmodified make a copy
-		first: >
+		Returns zero if {object} is not a List, Tuple, Blob or a
+		String.  If you want a List or Blob to remain unmodified make
+		a copy first: >
 			:let revlist = reverse(copy(mylist))
 <		Can also be used as a |method|: >
 			mylist->reverse()
 <
-		Return type: |String|, |Blob| or list<{type}> depending on
-		{object}
+		Return type: |String|, |Blob|, list<{type}> or tuple<{type}>
+		depending on {object}
 
 
 round({expr})							*round()*
@@ -10304,7 +10346,7 @@
 		Can also be used as a |method|: >
 			GetList()->slice(offset)
 <
-		Return type: list<{type}>
+		Return type: list<{type}> or tuple<{type}>
 
 
 sort({list} [, {how} [, {dict}]])			*sort()* *E702*
@@ -10916,15 +10958,16 @@
 			Funcref		function('name')
 			Blob		0z00112233.44556677.8899
 			List		[item, item]
+			Tuple		(item, item)
 			Dictionary	{key: value, key: value}
 			Class		class SomeName
 			Object		object of SomeName {lnum: 1, col: 3}
 			Enum		enum EnumName
 			EnumValue	enum name.value {name: str, ordinal: nr}
 
-		When a |List| or |Dictionary| has a recursive reference it is
-		replaced by "[...]" or "{...}".  Using eval() on the result
-		will then fail.
+		When a |List|, |Tuple| or |Dictionary| has a recursive
+		reference it is replaced by "[...]" or "(...)" or "{...}".
+		Using eval() on the result will then fail.
 
 		For an object, invokes the string() method to get a textual
 		representation of the object.  If the method is not present,
@@ -11878,6 +11921,25 @@
 		Return type: |Float|
 
 
+tuple2list({list})					*tuple2list()*
+		Create a List from a shallow copy of the tuple items.
+		Examples: >
+			tuple2list((1, 2, 3))		returns [1, 2, 3]
+<		|list2tuple()| does the opposite.
+
+		This function doesn't recursively convert all the Tuple items
+		in {tuple} to a List.  Note that the items are identical
+		between the list and the tuple, changing an item changes the
+		contents of both the tuple and the list.
+
+		Returns an empty list on error.
+
+		Can also be used as a |method|: >
+			GetTuple()->tuple2list()
+<
+		Return type: list<{type}> (depending on the given |Tuple|)
+
+
 							*type()*
 type({expr})	The result is a Number representing the type of {expr}.
 		Instead of using the number directly, it is better to use the
@@ -11898,6 +11960,7 @@
 			Typealias: 14  |v:t_typealias|
 			Enum:	   15  |v:t_enum|
 			EnumValue: 16  |v:t_enumvalue|
+			Tuple:	   17  |v:t_tuple|
 		For backward compatibility, this method can be used: >
 			:if type(myvar) == type(0)
 			:if type(myvar) == type("")
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index 0ada1b2..bcd64e3 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -1,4 +1,4 @@
-*eval.txt*	For Vim version 9.1.  Last change: 2025 Feb 23
+*eval.txt*	For Vim version 9.1.  Last change: 2025 Mar 23
 
 
 		  VIM REFERENCE MANUAL	  by Bram Moolenaar
@@ -21,9 +21,10 @@
     1.1 Variable types
     1.2 Function references		|Funcref|
     1.3 Lists				|Lists|
-    1.4 Dictionaries			|Dictionaries|
-    1.5 Blobs				|Blobs|
-    1.6 More about variables		|more-variables|
+    1.4 Tuples				|Tuples|
+    1.5 Dictionaries			|Dictionaries|
+    1.6 Blobs				|Blobs|
+    1.7 More about variables		|more-variables|
 2.  Expression syntax		|expression-syntax|
 3.  Internal variable		|internal-variables|
 4.  Builtin Functions		|functions|
@@ -46,8 +47,8 @@
 
 1.1 Variable types ~
 					*E712* *E896* *E897* *E899* *E1098*
-					*E1107* *E1135* *E1138*
-There are ten types of variables:
+					*E1107* *E1135* *E1138* *E1523*
+There are eleven types of variables:
 
 							*Number* *Integer*
 Number		A 32 or 64 bit signed number.  |expr-number|
@@ -63,6 +64,10 @@
 List		An ordered sequence of items, see |List| for details.
 		Example: [1, 2, ['a', 'b']]
 
+Tuple		An ordered immutable sequence of items, see |Tuple| for
+		details.
+		Example: (1, 2, ('a', 'b'))
+
 Dictionary	An associative, unordered array: Each entry has a key and a
 		value. |Dictionary|
 		Examples:
@@ -165,16 +170,17 @@
 
 		*E611* *E745* *E728* *E703* *E729* *E730* *E731* *E908* *E910*
 		*E913* *E974* *E975* *E976* *E1319* *E1320* *E1321* *E1322*
-		*E1323* *E1324*
-|List|, |Dictionary|, |Funcref|, |Job|, |Channel|, |Blob|, |Class| and
-|object| types are not automatically converted.
+		*E1323* *E1324* *E1520* *E1522*
+|List|, |Tuple|, |Dictionary|, |Funcref|, |Job|, |Channel|, |Blob|, |Class|
+and |object| types are not automatically converted.
 
 							*E805* *E806* *E808*
 When mixing Number and Float the Number is converted to Float.  Otherwise
 there is no automatic conversion of Float.  You can use str2float() for String
 to Float, printf() for Float to String and float2nr() for Float to Number.
 
-			*E362* *E891* *E892* *E893* *E894* *E907* *E911* *E914*
+					*E362* *E891* *E892* *E893* *E894*
+					*E907* *E911* *E914* *E1521*
 When expecting a Float a Number can also be used, but nothing else.
 
 						*no-type-checking*
@@ -267,9 +273,9 @@
 
 List creation ~
 							*E696* *E697*
-A List is created with a comma-separated list of items in square brackets.
+A List is created with a comma-separated sequence of items in square brackets.
 Examples: >
-	:let mylist = [1, two, 3, "four"]
+	:let mylist = [1, "two", 3, "four"]
 	:let emptylist = []
 
 An item can be any expression.  Using a List for an item creates a
@@ -327,13 +333,13 @@
 	:let otherlist = mylist[:]	" make a copy of the List
 
 Notice that the last index is inclusive.  If you prefer using an exclusive
-index use the |slice()| method.
+index use the |slice()| function.
 
-If the first index is beyond the last item of the List or the second item is
+If the first index is beyond the last item of the List or the last index is
 before the first item, the result is an empty list.  There is no error
 message.
 
-If the second index is equal to or greater than the length of the list the
+If the last index is equal to or greater than the length of the list the
 length minus one is used: >
 	:let mylist = [0, 1, 2, 3]
 	:echo mylist[2:8]		" result: [2, 3]
@@ -463,8 +469,8 @@
 
 For loop ~
 
-The |:for| loop executes commands for each item in a List, String or Blob.
-A variable is set to each item in sequence.  Example with a List: >
+The |:for| loop executes commands for each item in a List, Tuple, String or
+Blob.  A variable is set to each item in sequence.  Example with a List: >
 	:for item in mylist
 	:   call Doit(item)
 	:endfor
@@ -497,6 +503,8 @@
 	:   endif
 	:endfor
 
+For a Tuple one tuple item at a time is used.
+
 For a Blob one byte at a time is used.
 
 For a String one character, including any composing characters, is used as a
@@ -527,8 +535,206 @@
 example, to add up all the numbers in a list: >
 	:exe 'let sum = ' .. join(nrlist, '+')
 
+1.4 Tuples ~
+						*tuple* *Tuple* *Tuples*
+						*E1532* *E1533*
+A Tuple is an ordered sequence of items.  An item can be of any type.  Items
+can be accessed by their index number.  A Tuple is immutable.
 
-1.4 Dictionaries ~
+A Tuple uses less memory compared to a List and provides O(1) lookup time.
+
+Tuple creation ~
+						*E1526* *E1527*
+A Tuple is created with a comma-separated sequence of items in parentheses.
+Examples: >
+	:let mytuple = (1, "two", 3, "four")
+	:let tuple = (5,)
+	:let emptytuple = ()
+
+An item can be any expression.  If there is only one item in the tuple, then
+the item must be followed by a comma.
+
+Using a Tuple for an item creates a Tuple of Tuples: >
+	:let nesttuple = ((11, 12), (21, 22), (31, 32))
+
+
+Tuple index ~
+							*tuple-index* *E1519*
+An item in the Tuple can be accessed by putting the index in square brackets
+after the Tuple.  Indexes are zero-based, thus the first item has index zero.
+>
+	:let item = mytuple[0]		" get the first item: 1
+	:let item = mytuple[2]		" get the third item: 3
+
+When the resulting item is a tuple this can be repeated: >
+	:let item = nesttuple[0][1]	" get the first tuple, second item: 12
+<
+A negative index is counted from the end.  Index -1 refers to the last item in
+the Tuple, -2 to the last but one item, etc. >
+	:let last = mytuple[-1]		" get the last item: "four"
+
+To avoid an error for an invalid index use the |get()| function.  When an item
+is not available it returns zero or the default value you specify: >
+	:echo get(mytuple, idx)
+	:echo get(mytuple, idx, "NONE")
+
+
+Tuple concatenation ~
+							*tuple-concatenation*
+Two tuples can be concatenated with the "+" operator: >
+	:let longtuple = mytuple + (5, 6)
+	:let longtuple = (5, 6) + mytuple
+To prepend or append an item, turn it into a tuple by putting () around it.
+The item must be followed by a comma.
+
+							*E1540*
+Two variadic tuples with same item type can be concatenated but with different
+item types cannot be concatenated.  Examples: >
+    var a: tuple<...list<number>> = (1, 2)
+    var b: tuple<...list<string>> = ('a', 'b')
+    echo a + b		# not allowed
+
+    var a: tuple<number, number> = (1, 2)
+    var b: tuple<...list<string>> = ('a', 'b')
+    echo a + b		# allowed
+
+    var a: tuple<...list<number>> = (1, 2)
+    var b: tuple<number, number> = (3, 4)
+    echo a + b		# not allowed
+
+    var a: tuple<...list<number>> = (1, 2)
+    var b: tuple<number, ...list<number>> = (3, 4)
+    echo a + b		# not allowed
+<
+Note that a tuple is immutable and items cannot be added or removed from a
+tuple.
+
+
+Subtuple ~
+							*subtuple*
+A part of the Tuple can be obtained by specifying the first and last index,
+separated by a colon in square brackets: >
+	:let shorttuple = mytuple[2:-1]	" get Tuple (3, "four")
+
+Omitting the first index is similar to zero.  Omitting the last index is
+similar to -1. >
+	:let endtuple = mytuple[2:]	" from item 2 to the end: (3, "four")
+	:let shorttuple = mytuple[2:2]	" Tuple with one item: (3,)
+	:let othertuple = mytuple[:]	" make a copy of the Tuple
+
+Notice that the last index is inclusive.  If you prefer using an exclusive
+index, use the |slice()| function.
+
+If the first index is beyond the last item of the Tuple or the last index is
+before the first item, the result is an empty tuple.  There is no error
+message.
+
+If the last index is equal to or greater than the length of the tuple, the
+length minus one is used: >
+	:let mytuple = (0, 1, 2, 3)
+	:echo mytuple[2:8]		" result: (2, 3)
+
+NOTE: mytuple[s:e] means using the variable "s:e" as index.  Watch out for
+using a single letter variable before the ":".  Insert a space when needed:
+mytuple[s : e].
+
+
+Tuple identity ~
+							*tuple-identity*
+When variable "aa" is a tuple and you assign it to another variable "bb", both
+variables refer to the same tuple: >
+	:let aa = (1, 2, 3)
+	:let bb = aa
+<
+
+Making a copy of a tuple is done with the |copy()| function.  Using [:] also
+works, as explained above.  This creates a shallow copy of the tuple: For
+example, changing a list item in the tuple will also change the item in the
+copied tuple: >
+	:let aa = ([1, 'a'], 2, 3)
+	:let bb = copy(aa)
+	:let aa[0][1] = 'aaa'
+	:echo aa
+<	([1, aaa], 2, 3) >
+	:echo bb
+<	([1, aaa], 2, 3)
+
+To make a completely independent tuple, use |deepcopy()|.  This also makes a
+copy of the values in the tuple, recursively.  Up to a hundred levels deep.
+
+The operator "is" can be used to check if two variables refer to the same
+Tuple.  "isnot" does the opposite.  In contrast, "==" compares if two tuples
+have the same value. >
+	:let atuple = (1, 2, 3)
+	:let btuple = (1, 2, 3)
+	:echo atuple is btuple
+<	0 >
+	:echo atuple == btuple
+<	1
+
+Note about comparing tuples: Two tuples are considered equal if they have the
+same length and all items compare equal, as with using "==".  There is one
+exception: When comparing a number with a string they are considered
+different.  There is no automatic type conversion, as with using "==" on
+variables.  Example: >
+	echo 4 == "4"
+<	1 >
+	echo (4,) == ("4",)
+<	0
+
+Thus comparing Tuples is more strict than comparing numbers and strings.  You
+can compare simple values this way too by putting them in a tuple: >
+
+	:let a = 5
+	:let b = "5"
+	:echo a == b
+<	1 >
+	:echo (a,) == (b,)
+<	0
+
+
+Tuple unpack ~
+
+To unpack the items in a tuple to individual variables, put the variables in
+square brackets, like list items: >
+	:let [var1, var2] = mytuple
+
+When the number of variables does not match the number of items in the tuple
+this produces an error.  To handle any extra items from the tuple, append ";"
+and a variable name (which will then be of type tuple): >
+	:let [var1, var2; rest] = mytuple
+
+This works like: >
+	:let var1 = mytuple[0]
+	:let var2 = mytuple[1]
+	:let rest = mytuple[2:]
+
+Except that there is no error if there are only two items.  "rest" will be an
+empty tuple then.
+
+
+Tuple functions ~
+						*E1536*
+Functions that are useful with a Tuple: >
+	:let xs = count(tuple, 'x')	" count number of 'x's in tuple
+	:if empty(tuple)		" check if tuple is empty
+	:let i = index(tuple, 'x')	" index of first 'x' in tuple
+	:let l = items(tuple)		" list of items in a tuple
+	:let string = join(tuple, ', ')	" create string from tuple items
+	:let l = len(tuple)		" number of items in tuple
+	:let big = max(tuple)		" maximum value in tuple
+	:let small = min(tuple)		" minimum value in tuple
+	:let r = repeat(tuple, n)	" repeat a tuple n times
+	:let r = reverse(tuple)		" reverse a tuple
+	:let s = slice(tuple, n1, n2)	" slice a tuple
+	:let s = string(tuple)		" String representation of tuple
+	:let l = tuple2list(tuple)	" convert a tuple to list
+	:let t = list2tuple(list)	" convert a list to tuple
+<
+						*E1524*
+A tuple cannot be used with the |map()|, |mapnew()| and |filter()| functions.
+
+1.5 Dictionaries ~
 				*dict* *Dict* *Dictionaries* *Dictionary*
 A Dictionary is an associative array: Each entry has a key and a value.  The
 entry can be located with the key.  The entries are stored without a specific
@@ -537,10 +743,10 @@
 
 Dictionary creation ~
 						*E720* *E721* *E722* *E723*
-A Dictionary is created with a comma-separated list of entries in curly
+A Dictionary is created with a comma-separated sequence of entries in curly
 braces.  Each entry has a key and a value, separated by a colon.  Each key can
 only appear once.  Examples: >
-	:let mydict = {1: 'one', 2: 'two', 3: 'three'}
+	:let mydict = {'one': 1, 'two': 2, 'three': 3}
 	:let emptydict = {}
 <							*E713* *E716* *E717*
 A key is always a String.  You can use a Number, it will be converted to a
@@ -570,8 +776,11 @@
 Accessing entries ~
 
 The normal way to access an entry is by putting the key in square brackets: >
+	:let mydict = {'one': 1, 'two': 2, 'three': 3}
 	:let val = mydict["one"]
 	:let mydict["four"] = 4
+	:let val = mydict.one
+	:let mydict.four = 4
 
 You can add new entries to an existing Dictionary this way, unlike Lists.
 
@@ -709,7 +918,7 @@
 	:call map(dict, '">> " .. v:val')  " prepend ">> " to each item
 
 
-1.5 Blobs ~
+1.6 Blobs ~
 						*blob* *Blob* *Blobs* *E978*
 A Blob is a binary object.  It can be used to read an image from a file and
 send it over a channel, for example.
@@ -856,7 +1065,7 @@
 works, as explained above.
 
 
-1.6 More about variables ~
+1.7 More about variables ~
 							*more-variables*
 If you need to know the type of a variable or expression, use the |type()|
 function.
@@ -907,16 +1116,18 @@
 	etc.			As above, append ? for ignoring case, # for
 				matching case
 
-	expr5 is expr5		same |List|, |Dictionary| or |Blob| instance
-	expr5 isnot expr5	different |List|, |Dictionary| or |Blob|
+	expr5 is expr5		same |List|, |Tuple|, |Dictionary| or |Blob|
 				instance
+	expr5 isnot expr5	different |List|, |Tuple|, |Dictionary| or
+				|Blob| instance
 
 |expr5|	expr6
 	expr6 << expr6		bitwise left shift
 	expr6 >> expr6		bitwise right shift
 
 |expr6|	expr7
-	expr7 +	 expr7 ...	number addition, list or blob concatenation
+	expr7 +	 expr7 ...	number addition, list or tuple or blob
+				concatenation
 	expr7 -	 expr7 ...	number subtraction
 	expr7 .	 expr7 ...	string concatenation
 	expr7 .. expr7 ...	string concatenation
@@ -935,8 +1146,10 @@
 	+ expr9			unary plus
 
 |expr10|  expr11
-	expr10[expr1]		byte of a String or item of a |List|
+	expr10[expr1]		byte of a String or item of a |List| or
+				|Tuple|
 	expr10[expr1 : expr1]	substring of a String or sublist of a |List|
+				or a slice of a |Tuple|
 	expr10.name		entry in a |Dictionary|
 	expr10(expr1, ...)	function call with |Funcref| variable
 	expr10->name(expr1, ...)	|method| call
@@ -945,6 +1158,7 @@
 	"string"		string constant, backslash is special
 	'string'		string constant, ' is doubled
 	[expr1, ...]		|List|
+	(expr1, ...)		|Tuple|
 	{expr1: expr1, ...}	|Dictionary|
 	#{key: expr1, ...}	legacy |Dictionary|
 	&option			option value
@@ -1101,10 +1315,11 @@
 "abc" == "Abc"	  evaluates to 1 if 'ignorecase' is set, 0 otherwise
 NOTE: In |Vim9| script 'ignorecase' is not used.
 
-							*E691* *E692*
+						*E691* *E692* *E1517* *E1518*
 A |List| can only be compared with a |List| and only "equal", "not equal",
 "is" and "isnot" can be used.  This compares the values of the list,
 recursively.  Ignoring case means case is ignored when comparing item values.
+Same applies for a |Tuple|.
 
 							*E735* *E736*
 A |Dictionary| can only be compared with a |Dictionary| and only "equal", "not
@@ -1124,12 +1339,13 @@
 	if get(Part1, 'name') == get(Part2, 'name')
 	   " Part1 and Part2 refer to the same function
 <							*E1037*
-Using "is" or "isnot" with a |List|, |Dictionary| or |Blob| checks whether
-the expressions are referring to the same |List|, |Dictionary| or |Blob|
-instance.  A copy of a |List| is different from the original |List|.  When
-using "is" without a |List|, |Dictionary| or |Blob|, it is equivalent to
-using "equal", using "isnot" equivalent to using "not equal".  Except that
-a different type means the values are different: >
+Using "is" or "isnot" with a |List|, |Tuple|, |Dictionary| or |Blob| checks
+whether the expressions are referring to the same |List|, |Tuple|,
+|Dictionary| or |Blob| instance.  A copy of a |List| or |Tuple| is different
+from the original |List| or |Tuple|.  When using "is" without a |List|,
+|Tuple|, |Dictionary| or |Blob|, it is equivalent to using "equal", using
+"isnot" is equivalent to using "not equal".  Except that a different type
+means the values are different: >
 	echo 4 == '4'
 	1
 	echo 4 is '4'
@@ -1147,7 +1363,7 @@
 because 'x' converted to a Number is zero.  However: >
 	echo [0] == ['x']
 	0
-Inside a List or Dictionary this conversion is not used.
+Inside a List or Tuple or Dictionary this conversion is not used.
 
 In |Vim9| script the types must match.
 
@@ -1191,13 +1407,14 @@
 
 expr6 and expr7				*expr6* *expr7* *E1036* *E1051*
 ---------------
-expr7 + expr7   Number addition, |List| or |Blob| concatenation	*expr-+*
+								*expr-+*
+expr7 + expr7   Number addition, |List| or |Tuple| or |Blob| concatenation
 expr7 - expr7   Number subtraction				*expr--*
 expr7 . expr7   String concatenation				*expr-.*
 expr7 .. expr7  String concatenation				*expr-..*
 
 For |Lists| only "+" is possible and then both expr7 must be a list.  The
-result is a new list with the two lists Concatenated.
+result is a new list with the two lists concatenated.  Same for a |Tuple|.
 
 For String concatenation ".." is preferred, since "." is ambiguous, it is also
 used for |Dict| member access and floating point numbers.
@@ -1295,7 +1512,8 @@
 	expr10->(expr1, ...)[expr1]
 Evaluation is always from left to right.
 
-expr10[expr1]		item of String or |List|	*expr-[]* *E111*
+							*expr-[]* *E111*
+expr10[expr1]		item of String or |List| or |Tuple|
 						*E909* *subscript* *E1062*
 In legacy Vim script:
 If expr10 is a Number or String this results in a String that contains the
@@ -1328,6 +1546,8 @@
 |List|, or more negative than the length of the |List|, this results in an
 error.
 
+A |Tuple| index is similar to a |List| index as explained above.
+
 
 expr10[expr1a : expr1b]	substring or |sublist|		*expr-[:]* *substring*
 
@@ -1369,6 +1589,7 @@
 	:let l = mylist[:3]		" first four items
 	:let l = mylist[4:4]		" List with one item
 	:let l = mylist[:]		" shallow copy of a List
+A |Tuple| slice is similar to a |List| slice.
 
 If expr10 is a |Blob| this results in a new |Blob| with the bytes in the
 indexes expr1a and expr1b, inclusive.  Examples: >
@@ -2615,6 +2836,8 @@
 v:t_enum	Value of |enum| type.  Read-only.  See: |type()|
 					*v:t_enumvalue* *t_enumvalue-variable*
 v:t_enumvalue	Value of |enumvalue| type.  Read-only.  See: |type()|
+					*v:t_tuple* *t_tuple-variable*
+v:t_tuple	Value of |Tuple| type.  Read-only.  See: |type()|
 
 				*v:termresponse* *termresponse-variable*
 v:termresponse	The escape sequence returned by the terminal for the |t_RV|
@@ -2934,13 +3157,13 @@
 :let &g:{option-name} -= {expr1}
 			Like above, but only set the global value of an option
 			(if there is one).  Works like |:setglobal|.
-								*E1093*
+						*E1093* *E1537* *E1538* *E1535*
 :let [{name1}, {name2}, ...] = {expr1}		*:let-unpack* *E687* *E688*
-			{expr1} must evaluate to a |List|.  The first item in
-			the list is assigned to {name1}, the second item to
-			{name2}, etc.
+			{expr1} must evaluate to a |List| or a |Tuple|.  The
+			first item in the list or tuple is assigned to
+			{name1}, the second item to {name2}, etc.
 			The number of names must match the number of items in
-			the |List|.
+			the |List| or |Tuple|.
 			Each name can be one of the items of the ":let"
 			command as mentioned above.
 			Example: >
@@ -2957,16 +3180,22 @@
 :let [{name1}, {name2}, ...] .= {expr1}
 :let [{name1}, {name2}, ...] += {expr1}
 :let [{name1}, {name2}, ...] -= {expr1}
-			Like above, but append/add/subtract the value for each
-			|List| item.
+:let [{name1}, {name2}, ...] *= {expr1}
+:let [{name1}, {name2}, ...] /= {expr1}
+:let [{name1}, {name2}, ...] %= {expr1}
+			Like above, but append, add, subtract, multiply,
+			divide, or modulo the value for each |List| or |Tuple|
+			item.
 
 :let [{name}, ..., ; {lastname}] = {expr1}				*E452*
-			Like |:let-unpack| above, but the |List| may have more
-			items than there are names.  A list of the remaining
-			items is assigned to {lastname}.  If there are no
-			remaining items {lastname} is set to an empty list.
+			Like |:let-unpack| above, but the |List| or |Tuple|
+			may have more items than there are names.  A list or a
+			tuple of the remaining items is assigned to
+			{lastname}.  If there are no remaining items,
+			{lastname} is set to an empty list or tuple.
 			Example: >
 				:let [a, b; rest] = ["aval", "bval", 3, 4]
+				:let [a, b; rest] = ("aval", "bval", 3, 4)
 <
 :let [{name}, ..., ; {lastname}] .= {expr1}
 :let [{name}, ..., ; {lastname}] += {expr1}
@@ -3161,23 +3390,26 @@
 			get an error message: "E940: Cannot lock or unlock
 			variable {name}".
 
-			[depth] is relevant when locking a |List| or
-			|Dictionary|.  It specifies how deep the locking goes:
+			[depth] is relevant when locking a |List|, a |Tuple|
+			or a |Dictionary|.  It specifies how deep the locking
+			goes:
 				0	Lock the variable {name} but not its
 					value.
-				1	Lock the |List| or |Dictionary| itself,
-					cannot add or remove items, but can
-					still change their values.
+				1	Lock the |List| or |Tuple| or
+					|Dictionary| itself, cannot add or
+					remove items, but can still change
+					their values.
 				2	Also lock the values, cannot change
 					the items.  If an item is a |List| or
-					|Dictionary|, cannot add or remove
-					items, but can still change the
+					|Tuple| or |Dictionary|, cannot add or
+					remove items, but can still change the
 					values.
-				3	Like 2 but for the |List| /
-					|Dictionary| in the |List| /
+				3	Like 2 but for the |List| / |Tuple| /
+					|Dictionary| in the |List| / |Tuple| /
 					|Dictionary|, one level deeper.
-			The default [depth] is 2, thus when {name} is a |List|
-			or |Dictionary| the values cannot be changed.
+			The default [depth] is 2, thus when {name} is a
+			|List|, a |Tuple| or a |Dictionary| the values cannot
+			be changed.
 
 			Example with [depth] 0: >
 				let mylist = [1, 2, 3]
@@ -3282,7 +3514,7 @@
 :endfo[r]						*:endfo* *:endfor*
 			Repeat the commands between `:for` and `:endfor` for
 			each item in {object}.  {object} can be a |List|,
-			a |Blob| or a |String|. *E1177*
+			a |Tuple|, a |Blob| or a |String|. *E1177*
 
 			Variable {var} is set to the value of each item.
 			In |Vim9| script the loop variable must not have been
diff --git a/runtime/doc/tags b/runtime/doc/tags
index 1eebe14..52c3678 100644
--- a/runtime/doc/tags
+++ b/runtime/doc/tags
@@ -4592,9 +4592,33 @@
 E1514	options.txt	/*E1514*
 E1515	builtin.txt	/*E1515*
 E1516	builtin.txt	/*E1516*
+E1517	eval.txt	/*E1517*
+E1518	eval.txt	/*E1518*
+E1519	eval.txt	/*E1519*
 E152	helphelp.txt	/*E152*
+E1520	eval.txt	/*E1520*
+E1521	eval.txt	/*E1521*
+E1522	eval.txt	/*E1522*
+E1523	eval.txt	/*E1523*
+E1524	eval.txt	/*E1524*
+E1525	builtin.txt	/*E1525*
+E1526	eval.txt	/*E1526*
+E1527	eval.txt	/*E1527*
+E1528	vim9.txt	/*E1528*
+E1529	vim9.txt	/*E1529*
 E153	helphelp.txt	/*E153*
+E1530	vim9.txt	/*E1530*
+E1531	vim9.txt	/*E1531*
+E1532	eval.txt	/*E1532*
+E1533	eval.txt	/*E1533*
+E1534	vim9.txt	/*E1534*
+E1535	eval.txt	/*E1535*
+E1536	eval.txt	/*E1536*
+E1537	eval.txt	/*E1537*
+E1538	eval.txt	/*E1538*
+E1539	vim9.txt	/*E1539*
 E154	helphelp.txt	/*E154*
+E1540	eval.txt	/*E1540*
 E155	sign.txt	/*E155*
 E156	sign.txt	/*E156*
 E157	sign.txt	/*E157*
@@ -5785,6 +5809,8 @@
 TextChangedT	autocmd.txt	/*TextChangedT*
 TextYankPost	autocmd.txt	/*TextYankPost*
 Transact-SQL	ft_sql.txt	/*Transact-SQL*
+Tuple	eval.txt	/*Tuple*
+Tuples	eval.txt	/*Tuples*
 U	undo.txt	/*U*
 UTF-8	mbyte.txt	/*UTF-8*
 UTF8-xterm	mbyte.txt	/*UTF8-xterm*
@@ -7872,6 +7898,7 @@
 get()-dict	builtin.txt	/*get()-dict*
 get()-func	builtin.txt	/*get()-func*
 get()-list	builtin.txt	/*get()-list*
+get()-tuple	builtin.txt	/*get()-tuple*
 get-ms-debuggers	debug.txt	/*get-ms-debuggers*
 getbufinfo()	builtin.txt	/*getbufinfo()*
 getbufline()	builtin.txt	/*getbufline()*
@@ -8652,6 +8679,7 @@
 list-repeat	windows.txt	/*list-repeat*
 list2blob()	builtin.txt	/*list2blob()*
 list2str()	builtin.txt	/*list2str()*
+list2tuple()	builtin.txt	/*list2tuple()*
 listener_add()	builtin.txt	/*listener_add()*
 listener_flush()	builtin.txt	/*listener_flush()*
 listener_remove()	builtin.txt	/*listener_remove()*
@@ -10325,6 +10353,7 @@
 substitute()	builtin.txt	/*substitute()*
 substitute-CR	version6.txt	/*substitute-CR*
 substring	eval.txt	/*substring*
+subtuple	eval.txt	/*subtuple*
 suffixes	cmdline.txt	/*suffixes*
 suspend	starting.txt	/*suspend*
 swap-exists-choices	usr_11.txt	/*swap-exists-choices*
@@ -10574,6 +10603,7 @@
 t_tp	version4.txt	/*t_tp*
 t_ts	term.txt	/*t_ts*
 t_ts_old	version4.txt	/*t_ts_old*
+t_tuple-variable	eval.txt	/*t_tuple-variable*
 t_typealias-variable	eval.txt	/*t_typealias-variable*
 t_u7	term.txt	/*t_u7*
 t_ue	term.txt	/*t_ue*
@@ -10810,6 +10840,7 @@
 test_null_list()	testing.txt	/*test_null_list()*
 test_null_partial()	testing.txt	/*test_null_partial()*
 test_null_string()	testing.txt	/*test_null_string()*
+test_null_tuple()	testing.txt	/*test_null_tuple()*
 test_option_not_set()	testing.txt	/*test_option_not_set()*
 test_override()	testing.txt	/*test_override()*
 test_refcount()	testing.txt	/*test_refcount()*
@@ -10895,6 +10926,13 @@
 try-finally	eval.txt	/*try-finally*
 try-nested	eval.txt	/*try-nested*
 try-nesting	eval.txt	/*try-nesting*
+tuple	eval.txt	/*tuple*
+tuple-concatenation	eval.txt	/*tuple-concatenation*
+tuple-functions	usr_41.txt	/*tuple-functions*
+tuple-identity	eval.txt	/*tuple-identity*
+tuple-index	eval.txt	/*tuple-index*
+tuple-type	vim9.txt	/*tuple-type*
+tuple2list()	builtin.txt	/*tuple2list()*
 tutor	usr_01.txt	/*tutor*
 two-engines	pattern.txt	/*two-engines*
 type()	builtin.txt	/*type()*
@@ -11091,6 +11129,7 @@
 v:t_number	eval.txt	/*v:t_number*
 v:t_object	eval.txt	/*v:t_object*
 v:t_string	eval.txt	/*v:t_string*
+v:t_tuple	eval.txt	/*v:t_tuple*
 v:t_typealias	eval.txt	/*v:t_typealias*
 v:termblinkresp	eval.txt	/*v:termblinkresp*
 v:termrbgresp	eval.txt	/*v:termrbgresp*
@@ -11230,6 +11269,7 @@
 variable-scope	eval.txt	/*variable-scope*
 variable-types	vim9.txt	/*variable-types*
 variables	eval.txt	/*variables*
+variadic-tuple	vim9.txt	/*variadic-tuple*
 various	various.txt	/*various*
 various-cmds	various.txt	/*various-cmds*
 various-functions	usr_41.txt	/*various-functions*
diff --git a/runtime/doc/testing.txt b/runtime/doc/testing.txt
index 7d0402c..01e98cb 100644
--- a/runtime/doc/testing.txt
+++ b/runtime/doc/testing.txt
@@ -1,4 +1,4 @@
-*testing.txt*	For Vim version 9.1.  Last change: 2024 Jul 18
+*testing.txt*	For Vim version 9.1.  Last change: 2025 Mar 23
 
 
 		  VIM REFERENCE MANUAL	  by Bram Moolenaar
@@ -364,6 +364,11 @@
 
 		Return type: |String|
 
+test_null_tuple()					*test_null_tuple()*
+		Return a |Tuple| that is null. Only useful for testing.
+
+		Return type: |Tuple|
+
 test_option_not_set({name})				*test_option_not_set()*
 		Reset the flag that indicates option {name} was set.  Thus it
 		looks like it still has the default value. Use like this: >
diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt
index 0d09fc9..4c5e5ef 100644
--- a/runtime/doc/usr_41.txt
+++ b/runtime/doc/usr_41.txt
@@ -1,4 +1,4 @@
-*usr_41.txt*	For Vim version 9.1.  Last change: 2025 Feb 01
+*usr_41.txt*	For Vim version 9.1.  Last change: 2025 Mar 23
 
 		     VIM USER MANUAL - by Bram Moolenaar
 
@@ -839,6 +839,30 @@
 	repeat()		repeat a List multiple times
 	flatten()		flatten a List
 	flattennew()		flatten a copy of a List
+	items()			get List of List index-value pairs
+
+Tuple manipulation:					*tuple-functions*
+	copy()			make a shallow copy of a Tuple
+	count()			count number of times a value appears in a
+				Tuple
+	deepcopy()		make a full copy of a Tuple
+	empty()			check if Tuple is empty
+	foreach()		apply function to Tuple items
+	get()			get an item without error for wrong index
+	index()			index of a value in a Tuple
+	indexof()		index in a Tuple where an expression is true
+	items()			get List of Tuple index-value pairs
+	join()			join Tuple items into a String
+	len()			number of items in a Tuple
+	list2tuple()		convert a list of items into a Tuple
+	max()			maximum value in a Tuple
+	min()			minimum value in a Tuple
+	reduce()		reduce a Tuple to a value
+	repeat()		repeat a Tuple multiple times
+	reverse()		reverse the order of items in a Tuple
+	slice()			take a slice of a Tuple
+	string()		string representation of a Tuple
+	tuple2list()		convert a Tuple of items into a list
 
 Dictionary manipulation:				*dict-functions*
 	get()			get an entry without an error for a wrong key
@@ -1234,6 +1258,7 @@
 	test_null_list()	return a null List
 	test_null_partial()	return a null Partial function
 	test_null_string()	return a null String
+	test_null_tuple()	return a null Tuple
 	test_settime()		set the time Vim uses internally
 	test_setmouse()		set the mouse position
 	test_feedinput()	add key sequence to input buffer
@@ -1649,8 +1674,8 @@
 ==============================================================================
 *41.8*	Lists and Dictionaries
 
-So far we have used the basic types String and Number.  Vim also supports two
-composite types: List and Dictionary.
+So far we have used the basic types String and Number.  Vim also supports
+three composite types: List, Tuple and Dictionary.
 
 A List is an ordered sequence of items.  The items can be any kind of value,
 thus you can make a List of numbers, a List of Lists and even a List of mixed
@@ -1751,6 +1776,23 @@
 
 For further reading see |Lists|.
 
+TUPLE
+
+A Tuple is an immutable ordered sequence of items.  An item can be of any
+type.  Items can be accessed by their index number.  To create a Tuple with
+three strings: >
+
+	var atuple = ('one', 'two', 'three')
+
+The Tuple items are enclosed in parenthesis and separated by commas.  To
+create an empty Tuple: >
+
+	var atuple = ()
+
+The |:for| loop can be used to iterate over the items in a Tuple similar to a
+List.
+
+For further reading see |Tuples|.
 
 DICTIONARIES
 
diff --git a/runtime/doc/version9.txt b/runtime/doc/version9.txt
index 377ab2a..4f0da43 100644
--- a/runtime/doc/version9.txt
+++ b/runtime/doc/version9.txt
@@ -1,4 +1,4 @@
-*version9.txt*  For Vim version 9.1.  Last change: 2025 Mar 21
+*version9.txt*  For Vim version 9.1.  Last change: 2025 Mar 23
 
 
 		  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -41574,6 +41574,8 @@
 changes between buffers on similar lines improving the diff highlighting in
 Vim
 
+Support for the |Tuple| data type in Vim script and Vim9 script.
+
 							*changed-9.2*
 Changed~
 -------
@@ -41677,11 +41679,14 @@
 |getstacktrace()|	get current stack trace of Vim scripts
 |id()|			get unique identifier for a Dict, List, Object,
 			Channel or Blob variable
+|list2tuple()|		turn a List of items into a Tuple
 |matchbufline()|	all the matches of a pattern in a buffer
 |matchstrlist()|	all the matches of a pattern in a List of strings
 |ngettext()|		lookup single/plural message translation
 |popup_setbuf()|	switch to a different buffer in a popup
 |str2blob()|		convert a List of strings into a blob
+|test_null_tuple()|	return a null tuple
+|tuple2list()|		turn a Tuple of items into a List
 
 
 Autocommands: ~
diff --git a/runtime/doc/vim9.txt b/runtime/doc/vim9.txt
index bf50094..d06c250 100644
--- a/runtime/doc/vim9.txt
+++ b/runtime/doc/vim9.txt
@@ -1,4 +1,4 @@
-*vim9.txt*	For Vim version 9.1.  Last change: 2025 Mar 06
+*vim9.txt*	For Vim version 9.1.  Last change: 2025 Mar 23
 
 
 		  VIM REFERENCE MANUAL	  by Bram Moolenaar
@@ -1001,6 +1001,7 @@
 	string		non-empty
 	blob		non-empty
 	list		non-empty (different from JavaScript)
+	tuple		non-empty (different from JavaScript)
 	dictionary	non-empty (different from JavaScript)
 	func		when there is a function name
 	special		true or v:true
@@ -1048,6 +1049,7 @@
 	null_function
 	null_job
 	null_list
+	null_tuple
 	null_object
 	null_partial
 	null_string
@@ -1467,15 +1469,16 @@
 	dict<{type}>
 	job
 	channel
+	tuple<{type}>
+	tuple<{type}, {type}, ...>
+	tuple<...list<{type}>>
+	tuple<{type}, ...list<{type}>>
 	func
 	func: {type}
 	func({type}, ...)
 	func({type}, ...): {type}
 	void
 
-Not supported yet:
-	tuple<a: {type}, b: {type}, ...>
-
 These types can be used in declarations, but no simple value will actually
 have the "void" type.  Trying to use a void (e.g. a function without a
 return value) results in error *E1031*  *E1186* .
@@ -1483,6 +1486,32 @@
 There is no array type, use list<{type}> instead.  For a list constant an
 efficient implementation is used that avoids allocating a lot of small pieces
 of memory.
+							*tuple-type*
+A tuple type can be declared in more or less specific ways:
+tuple<number>			a tuple with a single item of type |Number|
+tuple<number, string>		a tuple with two items of type |Number| and
+				|String|
+tuple<number, float, bool>	a tuple with three items of type |Number|,
+				|Float| and |Boolean|.
+tuple<...list<number>>		a variadic tuple with zero or more items of
+				type |Number|.
+tuple<number, ...list<string>>	a tuple with an item of type |Number| followed
+				by zero or more items of type |String|.
+
+Examples: >
+    var myTuple: tuple<number> = (20,)
+    var myTuple: tuple<number, string> = (30, 'vim')
+    var myTuple: tuple<number, float, bool> = (40, 1.1, true)
+    var myTuple: tuple<...list<string>> = ('a', 'b', 'c')
+    var myTuple: tuple<number, ...list<string>> = (3, 'a', 'b', 'c')
+<
+						*variadic-tuple* *E1539*
+A variadic tuple has zero or more items of the same type.  The type of a
+variadic tuple must end with a list type.  Examples: >
+    var myTuple: tuple<...list<number>> = (1, 2, 3)
+    var myTuple: tuple<...list<string>> = ('a', 'b', 'c')
+    var myTuple: tuple<...list<bool>> = ()
+<
 				    *vim9-func-declaration* *E1005* *E1007*
 A partial and function can be declared in more or less specific ways:
 func				any kind of function reference, no type
@@ -1707,7 +1736,8 @@
 			 *E1211* *E1217* *E1218* *E1219* *E1220* *E1221*
 			 *E1222* *E1223* *E1224* *E1225* *E1226* *E1227*
 			 *E1228* *E1238* *E1250* *E1251* *E1252* *E1256*
-			 *E1297* *E1298* *E1301*
+			 *E1297* *E1298* *E1301* *E1528* *E1529* *E1530*
+			 *E1531* *E1534*
 Types are checked for most builtin functions to make it easier to spot
 mistakes.
 
@@ -1715,7 +1745,7 @@
 				*variable-categories* *null-variables*
 There are categories of variables:
 	primitive	number, float, boolean
-	container	string, blob, list, dict
+	container	string, blob, list, tuple, dict
 	specialized	function, job, channel, user-defined-object
 
 When declaring a variable without an initializer, an explicit type must be
@@ -1845,6 +1875,7 @@
 	var s: string		s == null
 	var b: blob		b != null   ***
 	var l: list<any>	l != null   ***
+	var t: tuple<any>	t != null   ***
 	var d: dict<any>	d != null   ***
 	var f: func		f == null
 	var j: job		j == null
@@ -1855,6 +1886,7 @@
 	var s2: string = ""	  == null_string	!= null
 	var b2: blob = 0z	  == null_blob		!= null
 	var l2: list<any> = []	  == null_list		!= null
+	var t2: tuple<any> = ()	  == null_tuple		!= null
 	var d2: dict<any> = {}	  == null_dict		!= null
 
 NOTE: the specialized variables, like job, default to null value and have no