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/Filelist b/Filelist
index 95bed7d..7302788 100644
--- a/Filelist
+++ b/Filelist
@@ -157,6 +157,7 @@
src/textobject.c \
src/textprop.c \
src/time.c \
+ src/tuple.c \
src/typval.c \
src/ui.c \
src/undo.c \
@@ -339,6 +340,7 @@
src/proto/textobject.pro \
src/proto/textprop.pro \
src/proto/time.pro \
+ src/proto/tuple.pro \
src/proto/typval.pro \
src/proto/ui.pro \
src/proto/undo.pro \
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
diff --git a/src/Make_ami.mak b/src/Make_ami.mak
index c779914..4dc86dd 100644
--- a/src/Make_ami.mak
+++ b/src/Make_ami.mak
@@ -169,6 +169,7 @@
textobject.c \
textprop.c \
time.c \
+ tuple.c \
typval.c \
ui.c \
undo.c \
diff --git a/src/Make_cyg_ming.mak b/src/Make_cyg_ming.mak
index 3f2a6da..991feb9 100644
--- a/src/Make_cyg_ming.mak
+++ b/src/Make_cyg_ming.mak
@@ -865,6 +865,7 @@
$(OUTDIR)/textobject.o \
$(OUTDIR)/textprop.o \
$(OUTDIR)/time.o \
+ $(OUTDIR)/tuple.o \
$(OUTDIR)/typval.o \
$(OUTDIR)/ui.o \
$(OUTDIR)/undo.o \
diff --git a/src/Make_mvc.mak b/src/Make_mvc.mak
index c24f0af..2825865 100644
--- a/src/Make_mvc.mak
+++ b/src/Make_mvc.mak
@@ -786,6 +786,7 @@
$(OUTDIR)\textobject.obj \
$(OUTDIR)\textprop.obj \
$(OUTDIR)\time.obj \
+ $(OUTDIR)\tuple.obj \
$(OUTDIR)\typval.obj \
$(OUTDIR)\ui.obj \
$(OUTDIR)\undo.obj \
@@ -1791,6 +1792,8 @@
$(OUTDIR)/time.obj: $(OUTDIR) time.c $(INCL)
+$(OUTDIR)/tuple.obj: $(OUTDIR) tuple.c $(INCL)
+
$(OUTDIR)/typval.obj: $(OUTDIR) typval.c $(INCL)
$(OUTDIR)/ui.obj: $(OUTDIR) ui.c $(INCL)
@@ -2005,6 +2008,7 @@
proto/textobject.pro \
proto/textprop.pro \
proto/time.pro \
+ proto/tuple.pro \
proto/typval.pro \
proto/ui.pro \
proto/undo.pro \
diff --git a/src/Make_vms.mms b/src/Make_vms.mms
index 2063023..a30ed65 100644
--- a/src/Make_vms.mms
+++ b/src/Make_vms.mms
@@ -433,6 +433,7 @@
textobject.c \
textprop.c \
time.c \
+ tuple.c \
typval.c \
ui.c \
undo.c \
@@ -567,6 +568,7 @@
textobject.obj \
textprop.obj \
time.obj \
+ tuple.obj \
typval.obj \
ui.obj \
undo.obj \
@@ -1169,6 +1171,9 @@
time.obj : time.c vim.h [.auto]config.h feature.h os_unix.h \
ascii.h keymap.h termdefs.h macros.h structs.h regexp.h gui.h beval.h \
[.proto]gui_beval.pro option.h ex_cmds.h proto.h errors.h globals.h
+tuple.obj : tuple.c vim.h [.auto]config.h feature.h os_unix.h \
+ ascii.h keymap.h termdefs.h macros.h structs.h regexp.h gui.h beval.h \
+ [.proto]gui_beval.pro option.h ex_cmds.h proto.h errors.h globals.h
typval.obj : typval.c vim.h [.auto]config.h feature.h os_unix.h \
ascii.h keymap.h termdefs.h macros.h structs.h regexp.h gui.h beval.h \
[.proto]gui_beval.pro option.h ex_cmds.h proto.h errors.h globals.h
diff --git a/src/Makefile b/src/Makefile
index d6c76b1..cde2e55 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -1584,6 +1584,7 @@
textobject.c \
textprop.c \
time.c \
+ tuple.c \
typval.c \
ui.c \
undo.c \
@@ -1744,6 +1745,7 @@
objects/textobject.o \
objects/textprop.o \
objects/time.o \
+ objects/tuple.o \
objects/typval.o \
objects/ui.o \
objects/undo.o \
@@ -1937,6 +1939,7 @@
textobject.pro \
textprop.pro \
time.pro \
+ tuple.pro \
typval.pro \
ui.pro \
undo.pro \
@@ -3568,6 +3571,9 @@
objects/time.o: time.c
$(CCC) -o $@ time.c
+objects/tuple.o: tuple.c
+ $(CCC) -o $@ tuple.c
+
objects/typval.o: typval.c
$(CCC) -o $@ typval.c
@@ -4248,6 +4254,11 @@
proto/gui_beval.pro structs.h regexp.h gui.h libvterm/include/vterm.h \
libvterm/include/vterm_keycodes.h alloc.h ex_cmds.h spell.h proto.h \
globals.h errors.h
+objects/tuple.o: tuple.c vim.h protodef.h auto/config.h feature.h os_unix.h \
+ auto/osdef.h ascii.h keymap.h termdefs.h macros.h option.h beval.h \
+ proto/gui_beval.pro structs.h regexp.h gui.h libvterm/include/vterm.h \
+ libvterm/include/vterm_keycodes.h alloc.h ex_cmds.h spell.h proto.h \
+ globals.h errors.h
objects/typval.o: typval.c vim.h protodef.h auto/config.h feature.h os_unix.h \
auto/osdef.h ascii.h keymap.h termdefs.h macros.h option.h beval.h \
proto/gui_beval.pro structs.h regexp.h gui.h libvterm/include/vterm.h \
diff --git a/src/channel.c b/src/channel.c
index 69bbff2..a369c71 100644
--- a/src/channel.c
+++ b/src/channel.c
@@ -5004,7 +5004,7 @@
{
tv.v_type = VAR_CHANNEL;
tv.vval.v_channel = channel;
- abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL);
+ abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL, NULL);
}
return abort;
}
diff --git a/src/dict.c b/src/dict.c
index 7975904..d43ca86 100644
--- a/src/dict.c
+++ b/src/dict.c
@@ -1554,7 +1554,7 @@
return;
if ((what == DICT2LIST_ITEMS
- ? check_for_string_or_list_or_dict_arg(argvars, 0)
+ ? check_for_string_list_tuple_or_dict_arg(argvars, 0)
: check_for_dict_arg(argvars, 0)) == FAIL)
return;
@@ -1617,6 +1617,8 @@
string2items(argvars, rettv);
else if (argvars[0].v_type == VAR_LIST)
list2items(argvars, rettv);
+ else if (argvars[0].v_type == VAR_TUPLE)
+ tuple2items(argvars, rettv);
else
dict2list(argvars, rettv, DICT2LIST_ITEMS);
}
diff --git a/src/errors.h b/src/errors.h
index ca5ec85..9331484 100644
--- a/src/errors.h
+++ b/src/errors.h
@@ -2932,8 +2932,8 @@
INIT(= N_("E1138: Using a Bool as a Number"));
EXTERN char e_missing_matching_bracket_after_dict_key[]
INIT(= N_("E1139: Missing matching bracket after dict key"));
-EXTERN char e_for_argument_must_be_sequence_of_lists[]
- INIT(= N_("E1140: :for argument must be a sequence of lists"));
+EXTERN char e_for_argument_must_be_sequence_of_lists_or_tuples[]
+ INIT(= N_("E1140: :for argument must be a sequence of lists or tuples"));
EXTERN char e_indexable_type_required[]
INIT(= N_("E1141: Indexable type required"));
EXTERN char e_calling_test_garbagecollect_now_while_v_testing_is_not_set[]
@@ -3146,8 +3146,8 @@
INIT(= N_("E1223: String or Dictionary required for argument %d"));
EXTERN char e_string_number_or_list_required_for_argument_nr[]
INIT(= N_("E1224: String, Number or List required for argument %d"));
-EXTERN char e_string_list_or_dict_required_for_argument_nr[]
- INIT(= N_("E1225: String, List or Dictionary required for argument %d"));
+EXTERN char e_string_list_tuple_or_dict_required_for_argument_nr[]
+ INIT(= N_("E1225: String, List, Tuple or Dictionary required for argument %d"));
EXTERN char e_list_or_blob_required_for_argument_nr[]
INIT(= N_("E1226: List or Blob required for argument %d"));
EXTERN char e_list_or_dict_required_for_argument_nr[]
@@ -3218,10 +3218,12 @@
#ifdef FEAT_EVAL
EXTERN char e_argument_of_str_must_be_list_string_dictionary_or_blob[]
INIT(= N_("E1250: Argument of %s must be a List, String, Dictionary or Blob"));
-EXTERN char e_list_dict_blob_or_string_required_for_argument_nr[]
- INIT(= N_("E1251: List, Dictionary, Blob or String required for argument %d"));
+EXTERN char e_list_tuple_dict_blob_or_string_required_for_argument_nr[]
+ INIT(= N_("E1251: List, Tuple, Dictionary, Blob or String required for argument %d"));
EXTERN char e_string_list_or_blob_required_for_argument_nr[]
INIT(= N_("E1252: String, List or Blob required for argument %d"));
+EXTERN char e_string_list_tuple_or_blob_required_for_argument_nr[]
+ INIT(= N_("E1253: String, List, Tuple or Blob required for argument %d"));
// E1253 unused
EXTERN char e_cannot_use_script_variable_in_for_loop[]
INIT(= N_("E1254: Cannot use script variable in for loop"));
@@ -3351,8 +3353,8 @@
#ifdef FEAT_EVAL
EXTERN char e_cannot_use_partial_with_dictionary_for_defer[]
INIT(= N_("E1300: Cannot use a partial with dictionary for :defer"));
-EXTERN char e_string_number_list_or_blob_required_for_argument_nr[]
- INIT(= N_("E1301: String, Number, List or Blob required for argument %d"));
+EXTERN char e_repeatable_type_required_for_argument_nr[]
+ INIT(= N_("E1301: String, Number, List, Tuple or Blob required for argument %d"));
EXTERN char e_script_variable_was_deleted[]
INIT(= N_("E1302: Script variable was deleted"));
EXTERN char e_custom_list_completion_function_does_not_return_list_but_str[]
@@ -3664,3 +3666,53 @@
INIT(= N_("E1515: Unable to convert from '%s' encoding"));
EXTERN char e_str_encoding_to_failed[]
INIT(= N_("E1516: Unable to convert to '%s' encoding"));
+#ifdef FEAT_EVAL
+EXTERN char e_can_only_compare_tuple_with_tuple[]
+ INIT(= N_("E1517: Can only compare Tuple with Tuple"));
+EXTERN char e_invalid_operation_for_tuple[]
+ INIT(= N_("E1518: Invalid operation for Tuple"));
+EXTERN char e_tuple_index_out_of_range_nr[]
+ INIT(= N_("E1519: Tuple index out of range: %ld"));
+EXTERN char e_using_tuple_as_number[]
+ INIT(= N_("E1520: Using a Tuple as a Number"));
+EXTERN char e_using_tuple_as_float[]
+ INIT(= N_("E1521: Using a Tuple as a Float"));
+EXTERN char e_using_tuple_as_string[]
+ INIT(= N_("E1522: Using a Tuple as a String"));
+EXTERN char e_string_list_tuple_or_blob_required[]
+ INIT(= N_("E1523: String, List, Tuple or Blob required"));
+EXTERN char e_cannot_use_tuple_with_function_str[]
+ INIT(= N_("E1524: Cannot use a tuple with function %s"));
+EXTERN char e_argument_of_str_must_be_list_tuple_string_dictionary_or_blob[]
+ INIT(= N_("E1525: Argument of %s must be a List, Tuple, String, Dictionary or Blob"));
+EXTERN char e_missing_end_of_tuple_rsp_str[]
+ INIT(= N_("E1526: Missing end of Tuple ')': %s"));
+EXTERN char e_missing_comma_in_tuple_str[]
+ INIT(= N_("E1527: Missing comma in Tuple: %s"));
+EXTERN char e_list_or_tuple_or_blob_required_for_argument_nr[]
+ INIT(= N_("E1528: List or Tuple or Blob required for argument %d"));
+EXTERN char e_list_or_tuple_required_for_argument_nr[]
+ INIT(= N_("E1529: List or Tuple required for argument %d"));
+EXTERN char e_list_or_tuple_or_dict_required_for_argument_nr[]
+ INIT(= N_("E1530: List or Tuple or Dictionary required for argument %d"));
+EXTERN char e_argument_of_str_must_be_list_tuple_dictionary_or_blob[]
+ INIT(= N_("E1531: Argument of %s must be a List, Tuple, Dictionary or Blob"));
+EXTERN char e_tuple_is_immutable[]
+ INIT(= N_("E1532: Cannot modify a tuple"));
+EXTERN char e_cannot_slice_tuple[]
+ INIT(= N_("E1533: Cannot slice a tuple"));
+EXTERN char e_tuple_required_for_argument_nr[]
+ INIT(= N_("E1534: Tuple required for argument %d"));
+EXTERN char e_list_or_tuple_required[]
+ INIT(= N_("E1535: List or Tuple required"));
+EXTERN char e_tuple_required[]
+ INIT(= N_("E1536: Tuple required"));
+EXTERN char e_less_targets_than_tuple_items[]
+ INIT(= N_("E1537: Less targets than Tuple items"));
+EXTERN char e_more_targets_than_tuple_items[]
+ INIT(= N_("E1538: More targets than Tuple items"));
+EXTERN char e_variadic_tuple_must_end_with_list_type_str[]
+ INIT(= N_("E1539: Variadic tuple must end with a list type: %s"));
+EXTERN char e_cannot_use_variadic_tuple_in_concatenation[]
+ INIT(= N_("E1540: Cannot use a variadic tuple in concatenation"));
+#endif
diff --git a/src/eval.c b/src/eval.c
index 9a140c1..bd8e7cf 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -107,7 +107,7 @@
// autoloaded script names
free_autoload_scriptnames();
- // unreferenced lists and dicts
+ // unreferenced lists, tuples and dicts
(void)garbage_collect(FALSE);
// functions not garbage collected
@@ -620,28 +620,48 @@
/*
* Convert "tv" to a string.
- * When "join_list" is TRUE convert a List into a sequence of lines.
+ * When "join_list" is TRUE convert a List or a Tuple into a sequence of lines.
* Returns an allocated string (NULL when out of memory).
*/
char_u *
typval2string(typval_T *tv, int join_list)
{
garray_T ga;
- char_u *retval;
+ char_u *retval = NULL;
- if (join_list && tv->v_type == VAR_LIST)
+ if (join_list && (tv->v_type == VAR_LIST || tv->v_type == VAR_TUPLE))
{
- ga_init2(&ga, sizeof(char), 80);
- if (tv->vval.v_list != NULL)
+ if (tv->v_type == VAR_LIST)
{
- list_join(&ga, tv->vval.v_list, (char_u *)"\n", TRUE, FALSE, 0);
- if (tv->vval.v_list->lv_len > 0)
- ga_append(&ga, NL);
+ ga_init2(&ga, sizeof(char), 80);
+ if (tv->vval.v_list != NULL)
+ {
+ list_join(&ga, tv->vval.v_list, (char_u *)"\n", TRUE, FALSE,
+ 0);
+ if (tv->vval.v_list->lv_len > 0)
+ ga_append(&ga, NL);
+ }
+ ga_append(&ga, NUL);
+ retval = (char_u *)ga.ga_data;
}
- ga_append(&ga, NUL);
- retval = (char_u *)ga.ga_data;
+ else
+ {
+ // tuple
+ ga_init2(&ga, sizeof(char), 80);
+ if (tv->vval.v_tuple != NULL)
+ {
+ tuple_join(&ga, tv->vval.v_tuple, (char_u *)"\n", TRUE, FALSE,
+ 0);
+ if (TUPLE_LEN(tv->vval.v_tuple) > 0)
+ ga_append(&ga, NL);
+ }
+ ga_append(&ga, NUL);
+ retval = (char_u *)ga.ga_data;
+ }
}
- else if (tv->v_type == VAR_LIST || tv->v_type == VAR_DICT)
+ else if (tv->v_type == VAR_LIST
+ || tv->v_type == VAR_TUPLE
+ || tv->v_type == VAR_DICT)
{
char_u *tofree;
char_u numbuf[NUMBUFLEN];
@@ -659,7 +679,8 @@
/*
* Top level evaluation function, returning a string. Does not handle line
* breaks.
- * When "join_list" is TRUE convert a List into a sequence of lines.
+ * When "join_list" is TRUE convert a List and a Tuple into a sequence of
+ * lines.
* Return pointer to allocated memory, or NULL for failure.
*/
char_u *
@@ -1095,7 +1116,7 @@
*
* This is typically called with "lval_root" as "root". For a class, find
* the name from lp in the class from root, fill in lval_T if found. For a
- * complex type, list/dict use it as the result; just put the root into
+ * complex type, list/tuple/dict use it as the result; just put the root into
* ll_tv.
*
* "lval_root" is a hack used during run-time/instr-execution to provide the
@@ -1322,8 +1343,11 @@
return GLV_FAIL;
}
lp->ll_list = NULL;
+ lp->ll_list = NULL;
+ lp->ll_blob = NULL;
lp->ll_object = NULL;
lp->ll_class = NULL;
+ lp->ll_tuple = NULL;
// a NULL dict is equivalent with an empty dict
if (lp->ll_tv->vval.v_dict == NULL)
@@ -1425,7 +1449,13 @@
{
long bloblen = blob_len(lp->ll_tv->vval.v_blob);
- // Get the number and item for the only or first index of the List.
+ lp->ll_list = NULL;
+ lp->ll_dict = NULL;
+ lp->ll_object = NULL;
+ lp->ll_class = NULL;
+ lp->ll_tuple = NULL;
+
+ // Get the number and item for the only or first index of a List or Tuple.
if (empty1)
lp->ll_n1 = 0;
else
@@ -1484,6 +1514,7 @@
lp->ll_dict = NULL;
lp->ll_object = NULL;
lp->ll_class = NULL;
+ lp->ll_tuple = NULL;
lp->ll_list = lp->ll_tv->vval.v_list;
lp->ll_li = check_range_index_one(lp->ll_list, &lp->ll_n1,
(flags & GLV_ASSIGN_WITH_OP) == 0, quiet);
@@ -1524,6 +1555,64 @@
}
/*
+ * Get a tuple lval variable that can be assigned a value to: "name",
+ * "na{me}", "name[expr]", "name[expr][expr]", etc.
+ *
+ * 'idx' specifies the tuple index.
+ * If 'quiet' is TRUE, then error messages are not displayed for an invalid
+ * index.
+ *
+ * The typval is returned in 'lp'. Returns GLV_OK on success and GLV_FAIL on
+ * failure.
+ */
+ static int
+get_lval_tuple(
+ lval_T *lp,
+ typval_T *idx,
+ int quiet)
+{
+ // is number or string
+ lp->ll_n1 = (long)tv_get_number(idx);
+
+ lp->ll_list = NULL;
+ lp->ll_dict = NULL;
+ lp->ll_blob = NULL;
+ lp->ll_object = NULL;
+ lp->ll_class = NULL;
+
+ lp->ll_tuple = lp->ll_tv->vval.v_tuple;
+ lp->ll_tv = tuple_find(lp->ll_tuple, lp->ll_n1);
+ if (lp->ll_tv == NULL)
+ {
+ if (!quiet)
+ semsg(_(e_tuple_index_out_of_range_nr), lp->ll_n1);
+ return GLV_FAIL;
+ }
+
+ // use the type of the member
+ if (lp->ll_valtype != NULL)
+ {
+ if (lp->ll_valtype != NULL
+ && lp->ll_valtype->tt_type == VAR_TUPLE
+ && lp->ll_valtype->tt_argcount == 1)
+ {
+ // a variadic tuple or a single item tuple
+ if (lp->ll_valtype->tt_flags & TTFLAG_VARARGS)
+ lp->ll_valtype = lp->ll_valtype->tt_args[0]->tt_member;
+ else
+ lp->ll_valtype = lp->ll_valtype->tt_args[0];
+ }
+ else
+ // If the LHS member type is not known (VAR_ANY), then get it from
+ // the tuple item (after indexing)
+ lp->ll_valtype = typval2type(lp->ll_tv, get_copyID(),
+ &lp->ll_type_list, TVTT_DO_MEMBER);
+ }
+
+ return GLV_OK;
+}
+
+/*
* Get a class or object lval method in class "cl". The 'key' argument points
* to the method name and 'key_end' points to the character after 'key'.
* 'v_type' is VAR_CLASS or VAR_OBJECT.
@@ -1630,6 +1719,7 @@
{
lp->ll_dict = NULL;
lp->ll_list = NULL;
+ lp->ll_tuple = NULL;
class_T *cl;
if (v_type == VAR_OBJECT)
@@ -1697,8 +1787,8 @@
/*
* Check whether left bracket ("[") is allowed after the variable "name" with
- * type "v_type". Only Dict, List and Blob types support a bracket after the
- * variable name. Returns TRUE if bracket is allowed after the name.
+ * type "v_type". Only Dict, List, Tuple and Blob types support a bracket
+ * after the variable name. Returns TRUE if bracket is allowed after the name.
*/
static int
bracket_allowed_after_type(char_u *name, vartype_T v_type, int quiet)
@@ -1716,14 +1806,18 @@
/*
* Check whether the variable "name" with type "v_type" can be followed by an
- * index. Only Dict, List, Blob, Object and Class types support indexing.
- * Returns TRUE if indexing is allowed after the name.
+ * index. Only Dict, List, Tuple, Blob, Object and Class types support
+ * indexing. Returns TRUE if indexing is allowed after the name.
*/
static int
index_allowed_after_type(char_u *name, vartype_T v_type, int quiet)
{
- if (v_type != VAR_LIST && v_type != VAR_DICT && v_type != VAR_BLOB &&
- v_type != VAR_OBJECT && v_type != VAR_CLASS)
+ if (v_type != VAR_LIST
+ && v_type != VAR_TUPLE
+ && v_type != VAR_DICT
+ && v_type != VAR_BLOB
+ && v_type != VAR_OBJECT
+ && v_type != VAR_CLASS)
{
if (!quiet)
semsg(_(e_index_not_allowed_after_str_str),
@@ -1735,8 +1829,8 @@
}
/*
- * Get the lval of a list/dict/blob/object/class subitem starting at "p". Loop
- * until no more [idx] or .key is following.
+ * Get the lval of a list/tuple/dict/blob/object/class subitem starting at "p".
+ * Loop until no more [idx] or .key is following.
*
* If "rettv" is not NULL it points to the value to be assigned.
* "unlet" is TRUE for ":unlet".
@@ -1863,6 +1957,12 @@
emsg(_(e_cannot_slice_dictionary));
goto done;
}
+ if (v_type == VAR_TUPLE)
+ {
+ if (!quiet)
+ emsg(_(e_cannot_slice_tuple));
+ goto done;
+ }
if (rettv != NULL
&& !(rettv->v_type == VAR_LIST
&& rettv->vval.v_list != NULL)
@@ -1932,6 +2032,11 @@
if (get_lval_list(lp, &var1, &var2, empty1, flags, quiet) == FAIL)
goto done;
}
+ else if (v_type == VAR_TUPLE)
+ {
+ if (get_lval_tuple(lp, &var1, quiet) == FAIL)
+ goto done;
+ }
else // v_type == VAR_CLASS || v_type == VAR_OBJECT
{
if (get_lval_class_or_obj(lp, key, p, v_type, cl_exec, flags,
@@ -1945,6 +2050,13 @@
var2.v_type = VAR_UNKNOWN;
}
+ if (lp->ll_tuple != NULL)
+ {
+ if (!quiet)
+ emsg(_(e_tuple_is_immutable));
+ goto done;
+ }
+
rc = OK;
done:
@@ -2575,6 +2687,7 @@
case VAR_OBJECT:
case VAR_CLASS:
case VAR_TYPEALIAS:
+ case VAR_TUPLE:
break;
case VAR_BLOB:
@@ -2619,6 +2732,7 @@
char_u *expr;
typval_T tv;
list_T *l;
+ tuple_T *tuple;
int skip = !(evalarg->eval_flags & EVAL_EVALUATE);
*errp = TRUE; // default: there is an error
@@ -2671,6 +2785,22 @@
fi->fi_lw.lw_item = l->lv_first;
}
}
+ else if (tv.v_type == VAR_TUPLE)
+ {
+ tuple = tv.vval.v_tuple;
+ if (tuple == NULL)
+ {
+ // a null tuple is like an empty tuple: do nothing
+ clear_tv(&tv);
+ }
+ else
+ {
+ // No need to increment the refcount, it's already set for
+ // the tuple being used in "tv".
+ fi->fi_tuple = tuple;
+ fi->fi_tuple_idx = 0;
+ }
+ }
else if (tv.v_type == VAR_BLOB)
{
fi->fi_bi = 0;
@@ -2695,7 +2825,7 @@
}
else
{
- emsg(_(e_string_list_or_blob_required));
+ emsg(_(e_string_list_tuple_or_blob_required));
clear_tv(&tv);
}
}
@@ -2780,6 +2910,22 @@
return result;
}
+ if (fi->fi_tuple != NULL)
+ {
+ typval_T tv;
+
+ if (fi->fi_tuple_idx >= TUPLE_LEN(fi->fi_tuple))
+ return FALSE;
+
+ copy_tv(TUPLE_ITEM(fi->fi_tuple, fi->fi_tuple_idx), &tv);
+ ++fi->fi_tuple_idx;
+ ++fi->fi_bi;
+ if (skip_assign)
+ return TRUE;
+ return ex_let_vars(arg, &tv, TRUE, fi->fi_semicolon,
+ fi->fi_varcount, flag, NULL) == OK;
+ }
+
item = fi->fi_lw.lw_item;
if (item == NULL)
result = FALSE;
@@ -2813,6 +2959,8 @@
}
else if (fi->fi_blob != NULL)
blob_unref(fi->fi_blob);
+ else if (fi->fi_tuple != NULL)
+ tuple_unref(fi->fi_tuple);
else
vim_free(fi->fi_string);
vim_free(fi);
@@ -3960,6 +4108,36 @@
}
/*
+ * Make a copy of tuple "tv1" and append tuple "tv2".
+ */
+ int
+eval_addtuple(typval_T *tv1, typval_T *tv2)
+{
+ int vim9script = in_vim9script();
+ typval_T var3;
+
+ if (vim9script && tv1->vval.v_tuple != NULL && tv2->vval.v_tuple != NULL
+ && tv1->vval.v_tuple->tv_type != NULL
+ && tv2->vval.v_tuple->tv_type != NULL)
+ {
+ if (!check_tuples_addable(tv1->vval.v_tuple->tv_type,
+ tv2->vval.v_tuple->tv_type))
+ return FAIL;
+ }
+
+ // concatenate tuples
+ if (tuple_concat(tv1->vval.v_tuple, tv2->vval.v_tuple, &var3) == FAIL)
+ {
+ clear_tv(tv1);
+ clear_tv(tv2);
+ return FAIL;
+ }
+ clear_tv(tv1);
+ *tv1 = var3;
+ return OK;
+}
+
+/*
* Left or right shift the number "tv1" by the number "tv2" and store the
* result in "tv1".
*
@@ -4231,6 +4409,7 @@
int concat;
typval_T var2;
int vim9script = in_vim9script();
+ long op_lnum = SOURCING_LNUM;
// "." is only string concatenation when scriptversion is 1
// "+=", "-=" and "..=" are assignments
@@ -4259,7 +4438,8 @@
*arg = p;
}
if ((op != '+' || (rettv->v_type != VAR_LIST
- && rettv->v_type != VAR_BLOB))
+ && rettv->v_type != VAR_TUPLE
+ && rettv->v_type != VAR_BLOB))
&& (op == '.' || rettv->v_type != VAR_FLOAT)
&& evaluate)
{
@@ -4302,6 +4482,8 @@
/*
* Compute the result.
*/
+ // use the line of the operation for messages
+ SOURCING_LNUM = op_lnum;
if (op == '.')
{
if (eval_concat_str(rettv, &var2) == FAIL)
@@ -4316,6 +4498,12 @@
if (eval_addlist(rettv, &var2) == FAIL)
return FAIL;
}
+ else if (op == '+' && rettv->v_type == VAR_TUPLE
+ && var2.v_type == VAR_TUPLE)
+ {
+ if (eval_addtuple(rettv, &var2) == FAIL)
+ return FAIL;
+ }
else
{
if (eval_addsub_number(rettv, &var2, op) == FAIL)
@@ -4681,13 +4869,23 @@
return OK;
}
break;
- case 10: if (STRNCMP(s, "null_class", 10) == 0)
+ case 10:
+ if (STRNCMP(s, "null_", 5) != 0)
+ break;
+ // null_class
+ if (STRNCMP(s + 5, "class", 5) == 0)
{
rettv->v_type = VAR_CLASS;
rettv->vval.v_class = NULL;
return OK;
}
- break;
+ if (STRNCMP(s + 5, "tuple", 5) == 0)
+ {
+ rettv->v_type = VAR_TUPLE;
+ rettv->vval.v_tuple = NULL;
+ return OK;
+ }
+ break;
case 11: if (STRNCMP(s, "null_string", 11) == 0)
{
rettv->v_type = VAR_STRING;
@@ -4796,16 +4994,26 @@
if (ret == NOTDONE)
{
*arg = skipwhite_and_linebreak(*arg + 1, evalarg);
- ret = eval1(arg, rettv, evalarg); // recursive!
-
- *arg = skipwhite_and_linebreak(*arg, evalarg);
if (**arg == ')')
- ++*arg;
- else if (ret == OK)
+ // empty tuple
+ ret = eval_tuple(arg, rettv, evalarg, TRUE);
+ else
{
- emsg(_(e_missing_closing_paren));
- clear_tv(rettv);
- ret = FAIL;
+ ret = eval1(arg, rettv, evalarg); // recursive!
+
+ *arg = skipwhite_and_linebreak(*arg, evalarg);
+
+ if (**arg == ',')
+ // tuple
+ ret = eval_tuple(arg, rettv, evalarg, TRUE);
+ else if (**arg == ')')
+ ++*arg;
+ else if (ret == OK)
+ {
+ emsg(_(e_missing_closing_paren));
+ clear_tv(rettv);
+ ret = FAIL;
+ }
}
}
@@ -4896,6 +5104,7 @@
* $VAR environment variable
* (expression) nested expression
* [expr, expr] List
+ * (expr, expr) Tuple
* {arg, arg -> expr} Lambda
* {key: val, key: val} Dictionary
* #{key: val, key: val} Dictionary with literal keys
@@ -4904,7 +5113,7 @@
* ! in front logical NOT
* - in front unary minus
* + in front unary plus (ignored)
- * trailing [] subscript in String or List
+ * trailing [] subscript in String or List or Tuple
* trailing .name entry in Dictionary
* trailing ->name() method call
*
@@ -5049,6 +5258,7 @@
/*
* nested expression: (expression).
* or lambda: (arg) => expr
+ * or tuple
*/
case '(': ret = eval9_nested_expr(arg, rettv, evalarg, evaluate);
break;
@@ -5484,7 +5694,8 @@
var1.v_type = VAR_STRING;
}
- if (vim9script && rettv->v_type == VAR_LIST)
+ if (vim9script && (rettv->v_type == VAR_LIST
+ || rettv->v_type == VAR_TUPLE))
tv_get_number_chk(&var1, &error);
else
error = tv_get_string_chk(&var1) == NULL;
@@ -5603,6 +5814,7 @@
case VAR_STRING:
case VAR_LIST:
+ case VAR_TUPLE:
case VAR_DICT:
case VAR_BLOB:
break;
@@ -5735,6 +5947,16 @@
return FAIL;
break;
+ case VAR_TUPLE:
+ if (var1 == NULL)
+ n1 = 0;
+ if (var2 == NULL)
+ n2 = VARNUM_MAX;
+ if (tuple_slice_or_index(rettv->vval.v_tuple,
+ is_range, n1, n2, exclusive, rettv, verbose) == FAIL)
+ return FAIL;
+ break;
+
case VAR_DICT:
{
dictitem_T *item;
@@ -6080,6 +6302,51 @@
}
/*
+ * Return a textual representation of a Tuple in "tv".
+ * If the memory is allocated "tofree" is set to it, otherwise NULL.
+ * When "copyID" is not zero replace recursive lists with "...". When
+ * "restore_copyID" is FALSE, repeated items in tuples are replaced with "...".
+ * May return NULL.
+ */
+ static char_u *
+tuple_tv2string(
+ typval_T *tv,
+ char_u **tofree,
+ int copyID,
+ int restore_copyID)
+{
+ tuple_T *tuple = tv->vval.v_tuple;
+ char_u *r = NULL;
+
+ if (tuple == NULL)
+ {
+ // NULL tuple is equivalent to an empty tuple.
+ *tofree = NULL;
+ r = (char_u *)"()";
+ }
+ else if (copyID != 0 && tuple->tv_copyID == copyID
+ && tuple->tv_items.ga_len > 0)
+ {
+ *tofree = NULL;
+ r = (char_u *)"(...)";
+ }
+ else
+ {
+ int old_copyID;
+ if (restore_copyID)
+ old_copyID = tuple->tv_copyID;
+
+ tuple->tv_copyID = copyID;
+ *tofree = tuple2string(tv, copyID, restore_copyID);
+ if (restore_copyID)
+ tuple->tv_copyID = old_copyID;
+ r = *tofree;
+ }
+
+ return r;
+}
+
+/*
* Return a textual representation of a Dict in "tv".
* If the memory is allocated "tofree" is set to it, otherwise NULL.
* When "copyID" is not zero replace recursive dicts with "...".
@@ -6316,6 +6583,10 @@
r = list_tv2string(tv, tofree, copyID, restore_copyID);
break;
+ case VAR_TUPLE:
+ r = tuple_tv2string(tv, tofree, copyID, restore_copyID);
+ break;
+
case VAR_DICT:
r = dict_tv2string(tv, tofree, copyID, restore_copyID);
break;
@@ -7257,6 +7528,23 @@
if (to->vval.v_list == NULL)
ret = FAIL;
break;
+ case VAR_TUPLE:
+ to->v_type = VAR_TUPLE;
+ to->v_lock = 0;
+ if (from->vval.v_tuple == NULL)
+ to->vval.v_tuple = NULL;
+ else if (copyID != 0 && from->vval.v_tuple->tv_copyID == copyID)
+ {
+ // use the copy made earlier
+ to->vval.v_tuple = from->vval.v_tuple->tv_copytuple;
+ ++to->vval.v_tuple->tv_refcount;
+ }
+ else
+ to->vval.v_tuple = tuple_copy(from->vval.v_tuple,
+ deep, top, copyID);
+ if (to->vval.v_tuple == NULL)
+ ret = FAIL;
+ break;
case VAR_BLOB:
ret = blob_copy(from->vval.v_blob, to);
break;
diff --git a/src/evalfunc.c b/src/evalfunc.c
index 5592471..21ed15e 100644
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -361,6 +361,15 @@
}
/*
+ * Check "type" is a tuple of 'any'.
+ */
+ static int
+arg_tuple_any(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
+{
+ return check_arg_type(&t_tuple_any, type, context);
+}
+
+/*
* Check "type" is a string.
*/
static int
@@ -430,6 +439,42 @@
}
/*
+ * Check "type" is a list of 'any' or a tuple.
+ */
+ static int
+arg_list_or_tuple(
+ type_T *type,
+ type_T *decl_type UNUSED,
+ argcontext_T *context)
+{
+ if (type->tt_type == VAR_LIST
+ || type->tt_type == VAR_TUPLE
+ || type_any_or_unknown(type))
+ return OK;
+ arg_type_mismatch(&t_list_any, type, context->arg_idx + 1);
+ return FAIL;
+}
+
+
+/*
+ * Check "type" is a list of 'any', a tuple or a blob.
+ */
+ static int
+arg_list_or_tuple_or_blob(
+ type_T *type,
+ type_T *decl_type UNUSED,
+ argcontext_T *context)
+{
+ if (type->tt_type == VAR_LIST
+ || type->tt_type == VAR_TUPLE
+ || type->tt_type == VAR_BLOB
+ || type_any_or_unknown(type))
+ return OK;
+ arg_type_mismatch(&t_list_any, type, context->arg_idx + 1);
+ return FAIL;
+}
+
+/*
* Check "type" is a string or a number
*/
static int
@@ -461,7 +506,10 @@
* Check "type" is a buffer or a dict of any
*/
static int
-arg_buffer_or_dict_any(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
+arg_buffer_or_dict_any(
+ type_T *type,
+ type_T *decl_type UNUSED,
+ argcontext_T *context)
{
if (type->tt_type == VAR_STRING
|| type->tt_type == VAR_NUMBER
@@ -490,7 +538,10 @@
* Check "type" is a string or a list of strings.
*/
static int
-arg_string_or_list_string(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
+arg_string_or_list_string(
+ type_T *type,
+ type_T *decl_type UNUSED,
+ argcontext_T *context)
{
if (type->tt_type == VAR_STRING
|| type_any_or_unknown(type))
@@ -512,7 +563,10 @@
* Check "type" is a string or a list of 'any'
*/
static int
-arg_string_or_list_any(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
+arg_string_or_list_any(
+ type_T *type,
+ type_T *decl_type UNUSED,
+ argcontext_T *context)
{
if (type->tt_type == VAR_STRING
|| type->tt_type == VAR_LIST
@@ -526,7 +580,10 @@
* Check "type" is a string or a dict of 'any'
*/
static int
-arg_string_or_dict_any(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
+arg_string_or_dict_any(
+ type_T *type,
+ type_T *decl_type UNUSED,
+ argcontext_T *context)
{
if (type->tt_type == VAR_STRING
|| type->tt_type == VAR_DICT
@@ -540,7 +597,10 @@
* Check "type" is a string or a blob
*/
static int
-arg_string_or_blob(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
+arg_string_or_blob(
+ type_T *type,
+ type_T *decl_type UNUSED,
+ argcontext_T *context)
{
if (type->tt_type == VAR_STRING
|| type->tt_type == VAR_BLOB
@@ -579,7 +639,25 @@
}
/*
- * Check "type" is a list of 'any' or a dict of 'any' or a blob.
+ * Check "type" is a list of 'any', a tuple of 'any' or dict of 'any'.
+ */
+ static int
+arg_list_or_tuple_or_dict(
+ type_T *type,
+ type_T *decl_type UNUSED,
+ argcontext_T *context)
+{
+ if (type->tt_type == VAR_LIST
+ || type->tt_type == VAR_TUPLE
+ || type->tt_type == VAR_DICT
+ || type_any_or_unknown(type))
+ return OK;
+ arg_type_mismatch(&t_list_any, type, context->arg_idx + 1);
+ return FAIL;
+}
+
+/*
+ * Check "type" is a list of 'any', a dict of 'any' or a blob.
* Also check if "type" is modifiable.
*/
static int
@@ -601,7 +679,10 @@
* Check "type" is a list of 'any' or a dict of 'any' or a blob or a string.
*/
static int
-arg_list_or_dict_or_blob_or_string(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
+arg_list_or_dict_or_blob_or_string(
+ type_T *type,
+ type_T *decl_type UNUSED,
+ argcontext_T *context)
{
if (type->tt_type == VAR_LIST
|| type->tt_type == VAR_DICT
@@ -629,11 +710,35 @@
}
/*
+ * Check "type" is a list of 'any', a tuple of 'any', a dict of 'any', a blob
+ * or a string.
+ */
+ static int
+arg_list_tuple_dict_blob_or_string(
+ type_T *type,
+ type_T *decl_type UNUSED,
+ argcontext_T *context)
+{
+ if (type->tt_type == VAR_LIST
+ || type->tt_type == VAR_TUPLE
+ || type->tt_type == VAR_DICT
+ || type->tt_type == VAR_BLOB
+ || type->tt_type == VAR_STRING
+ || type_any_or_unknown(type))
+ return OK;
+ arg_type_mismatch(&t_list_any, type, context->arg_idx + 1);
+ return FAIL;
+}
+
+
+/*
* Check second argument of map(), filter(), foreach().
*/
static int
-check_map_filter_arg2(type_T *type, argcontext_T *context,
- filtermap_T filtermap)
+check_map_filter_arg2(
+ type_T *type,
+ argcontext_T *context,
+ filtermap_T filtermap)
{
type_T *expected_member = NULL;
type_T *(args[2]);
@@ -801,7 +906,10 @@
* Also accept a number, one and zero are accepted.
*/
static int
-arg_string_or_func(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
+arg_string_or_func(
+ type_T *type,
+ type_T *decl_type UNUSED,
+ argcontext_T *context)
{
if (type->tt_type == VAR_STRING
|| type->tt_type == VAR_PARTIAL
@@ -835,12 +943,16 @@
}
/*
- * Check "type" is a list of 'any' or a blob or a string.
+ * Check "type" is a list of 'any', a tuple, a blob or a string.
*/
static int
-arg_string_list_or_blob(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
+arg_string_list_tuple_or_blob(
+ type_T *type,
+ type_T *decl_type UNUSED,
+ argcontext_T *context)
{
if (type->tt_type == VAR_LIST
+ || type->tt_type == VAR_TUPLE
|| type->tt_type == VAR_BLOB
|| type->tt_type == VAR_STRING
|| type_any_or_unknown(type))
@@ -850,12 +962,12 @@
}
/*
- * Check "type" is a modifiable list of 'any' or a blob or a string.
+ * Check "type" is a tuple or a modifiable list of 'any' or a blob or a string.
*/
static int
-arg_string_list_or_blob_mod(type_T *type, type_T *decl_type, argcontext_T *context)
+arg_reverse(type_T *type, type_T *decl_type, argcontext_T *context)
{
- if (arg_string_list_or_blob(type, decl_type, context) == FAIL)
+ if (arg_string_list_tuple_or_blob(type, decl_type, context) == FAIL)
return FAIL;
return arg_type_modifiable(type, context->arg_idx + 1);
}
@@ -901,7 +1013,10 @@
* Must not be used for the first argcheck_T entry.
*/
static int
-arg_same_struct_as_prev(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
+arg_same_struct_as_prev(
+ type_T *type,
+ type_T *decl_type UNUSED,
+ argcontext_T *context)
{
type_T *prev_type = context->arg_types[context->arg_idx - 1].type_curr;
@@ -935,7 +1050,10 @@
* Check "type" is a string or a number or a list
*/
static int
-arg_str_or_nr_or_list(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
+arg_str_or_nr_or_list(
+ type_T *type,
+ type_T *decl_type UNUSED,
+ argcontext_T *context)
{
if (type->tt_type == VAR_STRING
|| type->tt_type == VAR_NUMBER
@@ -950,7 +1068,10 @@
* Check "type" is a dict of 'any' or a string
*/
static int
-arg_dict_any_or_string(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
+arg_dict_any_or_string(
+ type_T *type,
+ type_T *decl_type UNUSED,
+ argcontext_T *context)
{
if (type->tt_type == VAR_DICT
|| type->tt_type == VAR_STRING
@@ -977,14 +1098,15 @@
}
/*
- * Check "type" which is the first argument of get() (blob or list or dict or
- * funcref)
+ * Check "type" which is the first argument of get() (a blob, a list, a tuple,
+ * a dict or a funcref)
*/
static int
arg_get1(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
{
if (type->tt_type == VAR_BLOB
|| type->tt_type == VAR_LIST
+ || type->tt_type == VAR_TUPLE
|| type->tt_type == VAR_DICT
|| type->tt_type == VAR_FUNC
|| type->tt_type == VAR_PARTIAL
@@ -996,8 +1118,8 @@
}
/*
- * Check "type" which is the first argument of len() (number or string or
- * blob or list or dict)
+ * Check "type" which is the first argument of len() (a string, a number, a
+ * blob, a list, a tuple, a dict or an object)
*/
static int
arg_len1(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
@@ -1006,6 +1128,7 @@
|| type->tt_type == VAR_NUMBER
|| type->tt_type == VAR_BLOB
|| type->tt_type == VAR_LIST
+ || type->tt_type == VAR_TUPLE
|| type->tt_type == VAR_DICT
|| type->tt_type == VAR_OBJECT
|| type_any_or_unknown(type))
@@ -1032,8 +1155,8 @@
}
/*
- * Check "type" which is the first argument of repeat() (string or number or
- * list or any)
+ * Check "type" which is the first argument of repeat() (a string, a number, a
+ * blob, a list, a tuple or any)
*/
static int
arg_repeat1(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
@@ -1042,6 +1165,7 @@
|| type->tt_type == VAR_NUMBER
|| type->tt_type == VAR_BLOB
|| type->tt_type == VAR_LIST
+ || type->tt_type == VAR_TUPLE
|| type_any_or_unknown(type))
return OK;
@@ -1050,13 +1174,14 @@
}
/*
- * Check "type" which is the first argument of slice() (list or blob or string
- * or any)
+ * Check "type" which is the first argument of slice() (a list, a tuple, a
+ * blob, a string or any)
*/
static int
arg_slice1(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
{
if (type->tt_type == VAR_LIST
+ || type->tt_type == VAR_TUPLE
|| type->tt_type == VAR_BLOB
|| type->tt_type == VAR_STRING
|| type_any_or_unknown(type))
@@ -1067,19 +1192,23 @@
}
/*
- * Check "type" which is the first argument of count() (string or list or dict
- * or any)
+ * Check "type" which is the first argument of count() (a string, a list, a
+ * tuple, a dict or any)
*/
static int
-arg_string_or_list_or_dict(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
+arg_string_list_tuple_or_dict(
+ type_T *type,
+ type_T *decl_type UNUSED,
+ argcontext_T *context)
{
if (type->tt_type == VAR_STRING
|| type->tt_type == VAR_LIST
+ || type->tt_type == VAR_TUPLE
|| type->tt_type == VAR_DICT
|| type_any_or_unknown(type))
return OK;
- semsg(_(e_string_list_or_dict_required_for_argument_nr),
+ semsg(_(e_string_list_tuple_or_dict_required_for_argument_nr),
context->arg_idx + 1);
return FAIL;
}
@@ -1114,11 +1243,12 @@
static argcheck_T arg1_float_or_nr[] = {arg_float_or_nr};
static argcheck_T arg1_job[] = {arg_job};
static argcheck_T arg1_list_any[] = {arg_list_any};
+static argcheck_T arg1_tuple_any[] = {arg_tuple_any};
static argcheck_T arg1_list_number[] = {arg_list_number};
-static argcheck_T arg1_string_or_list_or_blob_mod[] = {arg_string_list_or_blob_mod};
-static argcheck_T arg1_list_or_dict[] = {arg_list_or_dict};
+static argcheck_T arg1_reverse[] = {arg_reverse};
+static argcheck_T arg1_list_or_tuple_or_dict[] = {arg_list_or_tuple_or_dict};
static argcheck_T arg1_list_string[] = {arg_list_string};
-static argcheck_T arg1_string_or_list_or_dict[] = {arg_string_or_list_or_dict};
+static argcheck_T arg1_string_list_tuple_or_dict[] = {arg_string_list_tuple_or_dict};
static argcheck_T arg1_lnum[] = {arg_lnum};
static argcheck_T arg1_number[] = {arg_number};
static argcheck_T arg1_string[] = {arg_string};
@@ -1141,7 +1271,6 @@
static argcheck_T arg2_job_dict[] = {arg_job, arg_dict_any};
static argcheck_T arg2_job_string_or_number[] = {arg_job, arg_string_or_nr};
static argcheck_T arg2_list_any_number[] = {arg_list_any, arg_number};
-static argcheck_T arg2_list_any_string[] = {arg_list_any, arg_string};
static argcheck_T arg2_list_number[] = {arg_list_number, arg_list_number};
static argcheck_T arg2_list_number_bool[] = {arg_list_number, arg_bool};
static argcheck_T arg2_list_string_dict[] = {arg_list_string, arg_dict_any};
@@ -1168,6 +1297,7 @@
static argcheck_T arg2_string_or_list_number[] = {arg_string_or_list_any, arg_number};
static argcheck_T arg2_string_string_or_number[] = {arg_string, arg_string_or_nr};
static argcheck_T arg2_blob_dict[] = {arg_blob, arg_dict_any};
+static argcheck_T arg2_list_or_tuple_string[] = {arg_list_or_tuple, arg_string};
static argcheck_T arg3_any_list_dict[] = {arg_any, arg_list_any, arg_dict_any};
static argcheck_T arg3_buffer_lnum_lnum[] = {arg_buffer, arg_lnum, arg_lnum};
static argcheck_T arg3_buffer_number_number[] = {arg_buffer, arg_number, arg_number};
@@ -1205,7 +1335,7 @@
static argcheck_T arg4_browse[] = {arg_bool, arg_string, arg_string, arg_string};
static argcheck_T arg23_chanexpr[] = {arg_chan_or_job, arg_any, arg_dict_any};
static argcheck_T arg23_chanraw[] = {arg_chan_or_job, arg_string_or_blob, arg_dict_any};
-static argcheck_T arg24_count[] = {arg_string_or_list_or_dict, arg_any, arg_bool, arg_number};
+static argcheck_T arg24_count[] = {arg_string_list_tuple_or_dict, arg_any, arg_bool, arg_number};
static argcheck_T arg13_cursor[] = {arg_cursor1, arg_number, arg_number};
static argcheck_T arg12_deepcopy[] = {arg_any, arg_bool};
static argcheck_T arg12_execute[] = {arg_string_or_list_string, arg_string};
@@ -1215,14 +1345,14 @@
static argcheck_T arg23_get[] = {arg_get1, arg_string_or_nr, arg_any};
static argcheck_T arg14_glob[] = {arg_string, arg_bool, arg_bool, arg_bool};
static argcheck_T arg25_globpath[] = {arg_string, arg_string, arg_bool, arg_bool, arg_bool};
-static argcheck_T arg24_index[] = {arg_list_or_blob, arg_item_of_prev, arg_number, arg_bool};
-static argcheck_T arg23_index[] = {arg_list_or_blob, arg_filter_func, arg_dict_any};
+static argcheck_T arg24_index[] = {arg_list_or_tuple_or_blob, arg_item_of_prev, arg_number, arg_bool};
+static argcheck_T arg23_index[] = {arg_list_or_tuple_or_blob, arg_filter_func, arg_dict_any};
static argcheck_T arg23_insert[] = {arg_list_or_blob, arg_item_of_prev, arg_number};
static argcheck_T arg1_len[] = {arg_len1};
static argcheck_T arg3_libcall[] = {arg_string, arg_string, arg_string_or_nr};
static argcheck_T arg14_maparg[] = {arg_string, arg_string, arg_bool, arg_bool};
static argcheck_T arg2_filter[] = {arg_list_or_dict_or_blob_or_string_mod, arg_filter_func};
-static argcheck_T arg2_foreach[] = {arg_list_or_dict_or_blob_or_string, arg_foreach_func};
+static argcheck_T arg2_foreach[] = {arg_list_tuple_dict_blob_or_string, arg_foreach_func};
static argcheck_T arg2_instanceof[] = {arg_object, varargs_class, NULL };
static argcheck_T arg2_map[] = {arg_list_or_dict_or_blob_or_string_mod, arg_map_func};
static argcheck_T arg2_mapnew[] = {arg_list_or_dict_or_blob_or_string, arg_any};
@@ -1231,7 +1361,7 @@
static argcheck_T arg23_matchstrlist[] = {arg_list_string, arg_string, arg_dict_any};
static argcheck_T arg45_matchbufline[] = {arg_buffer, arg_string, arg_lnum, arg_lnum, arg_dict_any};
static argcheck_T arg119_printf[] = {arg_string_or_nr, arg_any, arg_any, arg_any, arg_any, arg_any, arg_any, arg_any, arg_any, arg_any, arg_any, arg_any, arg_any, arg_any, arg_any, arg_any, arg_any, arg_any, arg_any};
-static argcheck_T arg23_reduce[] = {arg_string_list_or_blob, arg_any, arg_any};
+static argcheck_T arg23_reduce[] = {arg_string_list_tuple_or_blob, arg_any, arg_any};
static argcheck_T arg24_remote_expr[] = {arg_string, arg_string, arg_string, arg_number};
static argcheck_T arg23_remove[] = {arg_list_or_dict_or_blob_mod, arg_remove2, arg_number};
static argcheck_T arg2_repeat[] = {arg_repeat1, arg_number};
@@ -1364,6 +1494,13 @@
return &t_list_list_list_number;
}
static type_T *
+ret_tuple_any(int argcount UNUSED,
+ type2_T *argtypes UNUSED,
+ type_T **decl_type UNUSED)
+{
+ return &t_tuple_any;
+}
+ static type_T *
ret_dict_any(int argcount UNUSED,
type2_T *argtypes UNUSED,
type_T **decl_type UNUSED)
@@ -1457,6 +1594,7 @@
case VAR_STRING: *decl_type = &t_string; break;
case VAR_BLOB: *decl_type = &t_blob; break;
case VAR_LIST: *decl_type = &t_list_any; break;
+ case VAR_TUPLE: *decl_type = &t_tuple_any; break;
default: break;
}
}
@@ -2288,7 +2426,7 @@
ret_number_bool, f_islocked},
{"isnan", 1, 1, FEARG_1, arg1_float_or_nr,
ret_number_bool, MATH_FUNC(f_isnan)},
- {"items", 1, 1, FEARG_1, arg1_string_or_list_or_dict,
+ {"items", 1, 1, FEARG_1, arg1_string_list_tuple_or_dict,
ret_list_items, f_items},
{"job_getchannel", 1, 1, FEARG_1, arg1_job,
ret_channel, JOB_FUNC(f_job_getchannel)},
@@ -2302,7 +2440,7 @@
ret_string, JOB_FUNC(f_job_status)},
{"job_stop", 1, 2, FEARG_1, arg2_job_string_or_number,
ret_number_bool, JOB_FUNC(f_job_stop)},
- {"join", 1, 2, FEARG_1, arg2_list_any_string,
+ {"join", 1, 2, FEARG_1, arg2_list_or_tuple_string,
ret_string, f_join},
{"js_decode", 1, 1, FEARG_1, arg1_string,
ret_any, f_js_decode},
@@ -2334,6 +2472,8 @@
ret_blob, f_list2blob},
{"list2str", 1, 2, FEARG_1, arg2_list_number_bool,
ret_string, f_list2str},
+ {"list2tuple", 1, 1, FEARG_1, arg1_list_any,
+ ret_tuple_any, f_list2tuple},
{"listener_add", 1, 2, FEARG_2, arg2_any_buffer,
ret_number, f_listener_add},
{"listener_flush", 0, 1, FEARG_1, arg1_buffer,
@@ -2392,7 +2532,7 @@
ret_list_any, f_matchstrlist},
{"matchstrpos", 2, 4, FEARG_1, arg24_match_func,
ret_list_any, f_matchstrpos},
- {"max", 1, 1, FEARG_1, arg1_list_or_dict,
+ {"max", 1, 1, FEARG_1, arg1_list_or_tuple_or_dict,
ret_number, f_max},
{"menu_info", 1, 2, FEARG_1, arg2_string,
ret_dict_any,
@@ -2402,7 +2542,7 @@
NULL
#endif
},
- {"min", 1, 1, FEARG_1, arg1_list_or_dict,
+ {"min", 1, 1, FEARG_1, arg1_list_or_tuple_or_dict,
ret_number, f_min},
{"mkdir", 1, 3, FEARG_1, arg3_string_string_number,
ret_number_bool, f_mkdir},
@@ -2588,7 +2728,7 @@
ret_repeat, f_repeat},
{"resolve", 1, 1, FEARG_1, arg1_string,
ret_string, f_resolve},
- {"reverse", 1, 1, FEARG_1, arg1_string_or_list_or_blob_mod,
+ {"reverse", 1, 1, FEARG_1, arg1_reverse,
ret_first_arg, f_reverse},
{"round", 1, 1, FEARG_1, arg1_float_or_nr,
ret_float, f_round},
@@ -2918,6 +3058,8 @@
ret_func_any, f_test_null_partial},
{"test_null_string", 0, 0, 0, NULL,
ret_string, f_test_null_string},
+ {"test_null_tuple", 0, 0, 0, NULL,
+ ret_tuple_any, f_test_null_tuple},
{"test_option_not_set", 1, 1, FEARG_1, arg1_string,
ret_void, f_test_option_not_set},
{"test_override", 2, 2, FEARG_2, arg2_string_number,
@@ -2954,6 +3096,8 @@
ret_string, f_trim},
{"trunc", 1, 1, FEARG_1, arg1_float_or_nr,
ret_float, f_trunc},
+ {"tuple2list", 1, 1, FEARG_1, arg1_tuple_any,
+ ret_list_any, f_tuple2list},
{"type", 1, 1, FEARG_1|FE_X, NULL,
ret_number, f_type},
{"typename", 1, 1, FEARG_1|FE_X, NULL,
@@ -4226,6 +4370,9 @@
n = argvars[0].vval.v_list == NULL
|| argvars[0].vval.v_list->lv_len == 0;
break;
+ case VAR_TUPLE:
+ n = tuple_len(argvars[0].vval.v_tuple) == 0;
+ break;
case VAR_DICT:
n = argvars[0].vval.v_dict == NULL
|| argvars[0].vval.v_dict->dv_hashtab.ht_used == 0;
@@ -5263,6 +5410,7 @@
{
listitem_T *li;
list_T *l;
+ tuple_T *tuple;
dictitem_T *di;
dict_T *d;
typval_T *tv = NULL;
@@ -5298,6 +5446,18 @@
tv = &li->li_tv;
}
}
+ else if (argvars[0].v_type == VAR_TUPLE)
+ {
+ if ((tuple = argvars[0].vval.v_tuple) != NULL)
+ {
+ int error = FALSE;
+ long idx;
+
+ idx = (long)tv_get_number_chk(&argvars[1], &error);
+ if (!error)
+ tv = tuple_find(tuple, idx);
+ }
+ }
else if (argvars[0].v_type == VAR_DICT)
{
if ((d = argvars[0].vval.v_dict) != NULL)
@@ -5400,7 +5560,7 @@
}
}
else
- semsg(_(e_argument_of_str_must_be_list_dictionary_or_blob), "get()");
+ semsg(_(e_argument_of_str_must_be_list_tuple_dictionary_or_blob), "get()");
if (tv == NULL)
{
@@ -7811,6 +7971,7 @@
switch (argvars[0].v_type)
{
case VAR_LIST:
+ case VAR_TUPLE:
case VAR_DICT:
case VAR_OBJECT:
case VAR_JOB:
@@ -7837,67 +7998,84 @@
}
/*
- * "index()" function
+ * index() function for a blob
*/
static void
-f_index(typval_T *argvars, typval_T *rettv)
+index_func_blob(typval_T *argvars, typval_T *rettv)
{
- list_T *l;
- listitem_T *item;
+ typval_T tv;
blob_T *b;
- long idx = 0;
+ int start = 0;
+ int error = FALSE;
+ int ic = FALSE;
+
+ b = argvars[0].vval.v_blob;
+ if (b == NULL)
+ return;
+
+ if (argvars[2].v_type != VAR_UNKNOWN)
+ {
+ start = tv_get_number_chk(&argvars[2], &error);
+ if (error)
+ return;
+ }
+
+ if (start < 0)
+ {
+ start = blob_len(b) + start;
+ if (start < 0)
+ start = 0;
+ }
+
+ for (int idx = start; idx < blob_len(b); ++idx)
+ {
+ tv.v_type = VAR_NUMBER;
+ tv.vval.v_number = blob_get(b, idx);
+ if (tv_equal(&tv, &argvars[1], ic))
+ {
+ rettv->vval.v_number = idx;
+ return;
+ }
+ }
+}
+
+/*
+ * index() function for a tuple
+ */
+ static void
+index_func_tuple(typval_T *argvars, typval_T *rettv)
+{
+ tuple_T *tuple = argvars[0].vval.v_tuple;
int ic = FALSE;
int error = FALSE;
- rettv->vval.v_number = -1;
-
- if (in_vim9script()
- && (check_for_list_or_blob_arg(argvars, 0) == FAIL
- || (argvars[0].v_type == VAR_BLOB
- && check_for_number_arg(argvars, 1) == FAIL)
- || check_for_opt_number_arg(argvars, 2) == FAIL
- || (argvars[2].v_type != VAR_UNKNOWN
- && check_for_opt_bool_arg(argvars, 3) == FAIL)))
+ if (tuple == NULL)
return;
- if (argvars[0].v_type == VAR_BLOB)
+ int start_idx = 0;
+ if (argvars[2].v_type != VAR_UNKNOWN)
{
- typval_T tv;
- int start = 0;
-
- if (argvars[2].v_type != VAR_UNKNOWN)
- {
- start = tv_get_number_chk(&argvars[2], &error);
- if (error)
- return;
- }
- b = argvars[0].vval.v_blob;
- if (b == NULL)
+ start_idx = tv_get_number_chk(&argvars[2], &error);
+ if (!error && argvars[3].v_type != VAR_UNKNOWN)
+ ic = (int)tv_get_bool_chk(&argvars[3], &error);
+ if (error)
return;
- if (start < 0)
- {
- start = blob_len(b) + start;
- if (start < 0)
- start = 0;
- }
+ }
- for (idx = start; idx < blob_len(b); ++idx)
- {
- tv.v_type = VAR_NUMBER;
- tv.vval.v_number = blob_get(b, idx);
- if (tv_equal(&tv, &argvars[1], ic))
- {
- rettv->vval.v_number = idx;
- return;
- }
- }
- return;
- }
- else if (argvars[0].v_type != VAR_LIST)
- {
- emsg(_(e_list_or_blob_required));
- return;
- }
+ rettv->vval.v_number = index_tuple(tuple, &argvars[1], start_idx, ic);
+}
+
+/*
+ * index() function for a list
+ */
+ static void
+index_func_list(typval_T *argvars, typval_T *rettv)
+{
+ list_T *l;
+ listitem_T *item;
+ long idx = 0;
+ int ic = FALSE;
+ int error = FALSE;
l = argvars[0].vval.v_list;
if (l == NULL)
@@ -7926,11 +8104,38 @@
}
/*
+ * "index()" function
+ */
+ static void
+f_index(typval_T *argvars, typval_T *rettv)
+{
+ rettv->vval.v_number = -1;
+
+ if (in_vim9script()
+ && (check_for_list_or_tuple_or_blob_arg(argvars, 0) == FAIL
+ || (argvars[0].v_type == VAR_BLOB
+ && check_for_number_arg(argvars, 1) == FAIL)
+ || check_for_opt_number_arg(argvars, 2) == FAIL
+ || (argvars[2].v_type != VAR_UNKNOWN
+ && check_for_opt_bool_arg(argvars, 3) == FAIL)))
+ return;
+
+ if (argvars[0].v_type == VAR_BLOB)
+ index_func_blob(argvars, rettv);
+ else if (argvars[0].v_type == VAR_TUPLE)
+ index_func_tuple(argvars, rettv);
+ else if (argvars[0].v_type == VAR_LIST)
+ index_func_list(argvars, rettv);
+ else
+ emsg(_(e_list_or_blob_required));
+}
+
+/*
* Evaluate 'expr' with the v:key and v:val arguments and return the result.
* The expression is expected to return a boolean value. The caller should set
* the VV_KEY and VV_VAL vim variables before calling this function.
*/
- static int
+ int
indexof_eval_expr(typval_T *expr)
{
typval_T argv[3];
@@ -8053,7 +8258,7 @@
rettv->vval.v_number = -1;
- if (check_for_list_or_blob_arg(argvars, 0) == FAIL
+ if (check_for_list_or_tuple_or_blob_arg(argvars, 0) == FAIL
|| check_for_string_or_func_arg(argvars, 1) == FAIL
|| check_for_opt_dict_arg(argvars, 2) == FAIL)
return;
@@ -8079,6 +8284,9 @@
if (argvars[0].v_type == VAR_BLOB)
rettv->vval.v_number = indexof_blob(argvars[0].vval.v_blob, startidx,
&argvars[1]);
+ else if (argvars[0].v_type == VAR_TUPLE)
+ rettv->vval.v_number = indexof_tuple(argvars[0].vval.v_tuple, startidx,
+ &argvars[1]);
else
rettv->vval.v_number = indexof_list(argvars[0].vval.v_list, startidx,
&argvars[1]);
@@ -8488,6 +8696,9 @@
case VAR_LIST:
rettv->vval.v_number = list_len(argvars[0].vval.v_list);
break;
+ case VAR_TUPLE:
+ rettv->vval.v_number = tuple_len(argvars[0].vval.v_tuple);
+ break;
case VAR_DICT:
rettv->vval.v_number = dict_len(argvars[0].vval.v_dict);
break;
@@ -9229,7 +9440,8 @@
varnumber_T i;
int error = FALSE;
- if (in_vim9script() && check_for_list_or_dict_arg(argvars, 0) == FAIL)
+ if (in_vim9script() &&
+ check_for_list_or_tuple_or_dict_arg(argvars, 0) == FAIL)
return;
if (argvars[0].v_type == VAR_LIST)
@@ -9271,6 +9483,12 @@
}
}
}
+ else if (argvars[0].v_type == VAR_TUPLE)
+ {
+ n = tuple_max_min(argvars[0].vval.v_tuple, domax, &error);
+ if (error)
+ return;
+ }
else if (argvars[0].v_type == VAR_DICT)
{
dict_T *d;
@@ -10076,83 +10294,114 @@
}
/*
- * "repeat()" function
+ * Repeat the list "l" "n" times and set "rettv" to the new list.
*/
static void
-f_repeat(typval_T *argvars, typval_T *rettv)
+repeat_list(list_T *l, int n, typval_T *rettv)
+{
+ if (rettv_list_alloc(rettv) == FAIL
+ || l == NULL
+ || n <= 0)
+ return;
+
+ while (n-- > 0)
+ if (list_extend(rettv->vval.v_list, l, NULL) == FAIL)
+ break;
+}
+
+/*
+ * Repeat the blob "b" "n" times and set "rettv" to the new blob.
+ */
+ static void
+repeat_blob(typval_T *blob_tv, int n, typval_T *rettv)
+{
+ int slen;
+ int len;
+ int i;
+ blob_T *blob = blob_tv->vval.v_blob;
+
+ if (rettv_blob_alloc(rettv) == FAIL
+ || blob == NULL
+ || n <= 0)
+ return;
+
+ slen = blob->bv_ga.ga_len;
+ len = (int)slen * n;
+ if (len <= 0)
+ return;
+
+ if (ga_grow(&rettv->vval.v_blob->bv_ga, len) == FAIL)
+ return;
+
+ rettv->vval.v_blob->bv_ga.ga_len = len;
+
+ for (i = 0; i < slen; ++i)
+ if (blob_get(blob, i) != 0)
+ break;
+
+ if (i == slen)
+ // No need to copy since all bytes are already zero
+ return;
+
+ for (i = 0; i < n; ++i)
+ blob_set_range(rettv->vval.v_blob,
+ (long)i * slen, ((long)i + 1) * slen - 1, blob_tv);
+}
+
+/*
+ * Repeat the string "str" "n" times and set "rettv" to the new string.
+ */
+ static void
+repeat_string(typval_T *str_tv, int n, typval_T *rettv)
{
char_u *p;
- varnumber_T n;
int slen;
int len;
char_u *r;
int i;
+ p = tv_get_string(str_tv);
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+
+ slen = (int)STRLEN(p);
+ len = slen * n;
+ if (len <= 0)
+ return;
+
+ r = alloc(len + 1);
+ if (r == NULL)
+ return;
+
+ for (i = 0; i < n; i++)
+ mch_memmove(r + i * slen, p, (size_t)slen);
+ r[len] = NUL;
+
+ rettv->vval.v_string = r;
+}
+
+/*
+ * "repeat()" function
+ */
+ static void
+f_repeat(typval_T *argvars, typval_T *rettv)
+{
+ varnumber_T n;
+
if (in_vim9script()
- && (check_for_string_or_number_or_list_or_blob_arg(argvars, 0)
- == FAIL
+ && (check_for_repeat_func_arg(argvars, 0) == FAIL
|| check_for_number_arg(argvars, 1) == FAIL))
return;
n = tv_get_number(&argvars[1]);
if (argvars[0].v_type == VAR_LIST)
- {
- if (rettv_list_alloc(rettv) == OK && argvars[0].vval.v_list != NULL)
- while (n-- > 0)
- if (list_extend(rettv->vval.v_list,
- argvars[0].vval.v_list, NULL) == FAIL)
- break;
- }
+ repeat_list(argvars[0].vval.v_list, n, rettv);
+ else if (argvars[0].v_type == VAR_TUPLE)
+ tuple_repeat(argvars[0].vval.v_tuple, n, rettv);
else if (argvars[0].v_type == VAR_BLOB)
- {
- if (rettv_blob_alloc(rettv) == FAIL
- || argvars[0].vval.v_blob == NULL
- || n <= 0)
- return;
-
- slen = argvars[0].vval.v_blob->bv_ga.ga_len;
- len = (int)slen * n;
- if (len <= 0)
- return;
-
- if (ga_grow(&rettv->vval.v_blob->bv_ga, len) == FAIL)
- return;
-
- rettv->vval.v_blob->bv_ga.ga_len = len;
-
- for (i = 0; i < slen; ++i)
- if (blob_get(argvars[0].vval.v_blob, i) != 0)
- break;
-
- if (i == slen)
- // No need to copy since all bytes are already zero
- return;
-
- for (i = 0; i < n; ++i)
- blob_set_range(rettv->vval.v_blob,
- (long)i * slen, ((long)i + 1) * slen - 1, argvars);
- }
+ repeat_blob(&argvars[0], n, rettv);
else
- {
- p = tv_get_string(&argvars[0]);
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
-
- slen = (int)STRLEN(p);
- len = slen * n;
- if (len <= 0)
- return;
-
- r = alloc(len + 1);
- if (r != NULL)
- {
- for (i = 0; i < n; i++)
- mch_memmove(r + i * slen, p, (size_t)slen);
- r[len] = NUL;
- }
-
- rettv->vval.v_string = r;
- }
+ repeat_string(&argvars[0], n, rettv);
}
#define SP_NOMOVE 0x01 // don't move cursor
@@ -12191,6 +12440,7 @@
case VAR_PARTIAL:
case VAR_FUNC: n = VAR_TYPE_FUNC; break;
case VAR_LIST: n = VAR_TYPE_LIST; break;
+ case VAR_TUPLE: n = VAR_TYPE_TUPLE; break;
case VAR_DICT: n = VAR_TYPE_DICT; break;
case VAR_FLOAT: n = VAR_TYPE_FLOAT; break;
case VAR_BOOL: n = VAR_TYPE_BOOL; break;
diff --git a/src/evalvars.c b/src/evalvars.c
index 2745ac2..9382842 100644
--- a/src/evalvars.c
+++ b/src/evalvars.c
@@ -162,6 +162,7 @@
{VV_NAME("t_enum", VAR_NUMBER), NULL, VV_RO},
{VV_NAME("t_enumvalue", VAR_NUMBER), NULL, VV_RO},
{VV_NAME("stacktrace", VAR_LIST), &t_list_dict_any, VV_RO},
+ {VV_NAME("t_tuple", VAR_NUMBER), NULL, VV_RO},
};
// shorthand
@@ -265,8 +266,9 @@
set_vim_var_nr(VV_TYPE_CLASS, VAR_TYPE_CLASS);
set_vim_var_nr(VV_TYPE_OBJECT, VAR_TYPE_OBJECT);
set_vim_var_nr(VV_TYPE_TYPEALIAS, VAR_TYPE_TYPEALIAS);
- set_vim_var_nr(VV_TYPE_ENUM, VAR_TYPE_ENUM);
+ set_vim_var_nr(VV_TYPE_ENUM, VAR_TYPE_ENUM);
set_vim_var_nr(VV_TYPE_ENUMVALUE, VAR_TYPE_ENUMVALUE);
+ set_vim_var_nr(VV_TYPE_TUPLE, VAR_TYPE_TUPLE);
set_vim_var_nr(VV_ECHOSPACE, sc_col - 1);
@@ -321,13 +323,13 @@
int
garbage_collect_globvars(int copyID)
{
- return set_ref_in_ht(&globvarht, copyID, NULL);
+ return set_ref_in_ht(&globvarht, copyID, NULL, NULL);
}
int
garbage_collect_vimvars(int copyID)
{
- return set_ref_in_ht(&vimvarht, copyID, NULL);
+ return set_ref_in_ht(&vimvarht, copyID, NULL, NULL);
}
int
@@ -340,7 +342,7 @@
for (i = 1; i <= script_items.ga_len; ++i)
{
- abort = abort || set_ref_in_ht(&SCRIPT_VARS(i), copyID, NULL);
+ abort = abort || set_ref_in_ht(&SCRIPT_VARS(i), copyID, NULL, NULL);
si = SCRIPT_ITEM(i);
for (idx = 0; idx < si->sn_var_vals.ga_len; ++idx)
@@ -348,7 +350,7 @@
svar_T *sv = ((svar_T *)si->sn_var_vals.ga_data) + idx;
if (sv->sv_name != NULL)
- abort = abort || set_ref_in_item(sv->sv_tv, copyID, NULL, NULL);
+ abort = abort || set_ref_in_item(sv->sv_tv, copyID, NULL, NULL, NULL);
}
}
@@ -1234,10 +1236,13 @@
{
char_u *arg = arg_start;
list_T *l;
+ tuple_T *tuple = NULL;
int i;
int var_idx = 0;
- listitem_T *item;
+ listitem_T *item = NULL;
typval_T ltv;
+ int is_list = tv->v_type == VAR_LIST;
+ int idx;
if (tv->v_type == VAR_VOID)
{
@@ -1253,58 +1258,121 @@
}
// ":let [v1, v2] = list" or ":for [v1, v2] in listlist"
- if (tv->v_type != VAR_LIST || (l = tv->vval.v_list) == NULL)
+ // or
+ // ":let [v1, v2] = tuple" or ":for [v1, v2] in tupletuple"
+ if (tv->v_type != VAR_LIST && tv->v_type != VAR_TUPLE)
{
- emsg(_(e_list_required));
+ emsg(_(e_list_or_tuple_required));
return FAIL;
}
+ if (is_list)
+ {
+ l = tv->vval.v_list;
+ if (l == NULL)
+ {
+ emsg(_(e_list_required));
+ return FAIL;
+ }
+ i = list_len(l);
+ }
+ else
+ {
+ tuple = tv->vval.v_tuple;
+ if (tuple == NULL)
+ {
+ emsg(_(e_tuple_required));
+ return FAIL;
+ }
+ i = tuple_len(tuple);
+ }
- i = list_len(l);
if (semicolon == 0 && var_count < i)
{
- emsg(_(e_less_targets_than_list_items));
+ emsg(_(is_list ? e_less_targets_than_list_items
+ : e_less_targets_than_tuple_items));
return FAIL;
}
if (var_count - semicolon > i)
{
- emsg(_(e_more_targets_than_list_items));
+ emsg(_(is_list ? e_more_targets_than_list_items
+ : e_more_targets_than_tuple_items));
return FAIL;
}
- CHECK_LIST_MATERIALIZE(l);
- item = l->lv_first;
+ if (is_list)
+ {
+ CHECK_LIST_MATERIALIZE(l);
+ item = l->lv_first;
+ }
+ else
+ idx = 0;
+
while (*arg != ']')
{
arg = skipwhite(arg + 1);
++var_idx;
- arg = ex_let_one(arg, &item->li_tv, TRUE,
- flags | ASSIGN_UNPACK, (char_u *)",;]", op, var_idx);
- item = item->li_next;
+ arg = ex_let_one(arg, is_list ? &item->li_tv : TUPLE_ITEM(tuple, idx),
+ TRUE, flags | ASSIGN_UNPACK, (char_u *)",;]", op,
+ var_idx);
+ if (is_list)
+ item = item->li_next;
+ else
+ idx++;
if (arg == NULL)
return FAIL;
arg = skipwhite(arg);
if (*arg == ';')
{
- // Put the rest of the list (may be empty) in the var after ';'.
- // Create a new list for this.
- l = list_alloc();
- if (l == NULL)
- return FAIL;
- while (item != NULL)
+ // Put the rest of the list or tuple (may be empty) in the var
+ // after ';'. Create a new list or tuple for this.
+ if (is_list)
{
- list_append_tv(l, &item->li_tv);
- item = item->li_next;
+ // Put the rest of the list (may be empty) in the var
+ // after ';'. Create a new list for this.
+ l = list_alloc();
+ if (l == NULL)
+ return FAIL;
+
+ // list
+ while (item != NULL)
+ {
+ list_append_tv(l, &item->li_tv);
+ item = item->li_next;
+ }
+
+ ltv.v_type = VAR_LIST;
+ ltv.v_lock = 0;
+ ltv.vval.v_list = l;
+ l->lv_refcount = 1;
+ }
+ else
+ {
+ tuple_T *new_tuple = tuple_alloc();
+ if (new_tuple == NULL)
+ return FAIL;
+
+ // Put the rest of the tuple (may be empty) in the var
+ // after ';'. Create a new tuple for this.
+ while (idx < TUPLE_LEN(tuple))
+ {
+ typval_T new_tv;
+
+ copy_tv(TUPLE_ITEM(tuple, idx), &new_tv);
+ if (tuple_append_tv(new_tuple, &new_tv) == FAIL)
+ return FAIL;
+ idx++;
+ }
+
+ ltv.v_type = VAR_TUPLE;
+ ltv.v_lock = 0;
+ ltv.vval.v_tuple = new_tuple;
+ new_tuple->tv_refcount = 1;
}
- ltv.v_type = VAR_LIST;
- ltv.v_lock = 0;
- ltv.vval.v_list = l;
- l->lv_refcount = 1;
++var_idx;
-
arg = ex_let_one(skipwhite(arg + 1), <v, FALSE,
- flags | ASSIGN_UNPACK, (char_u *)"]", op, var_idx);
+ flags | ASSIGN_UNPACK, (char_u *)"]", op, var_idx);
clear_tv(<v);
if (arg == NULL)
return FAIL;
@@ -2418,6 +2486,9 @@
}
}
break;
+ case VAR_TUPLE:
+ tuple_lock(tv->vval.v_tuple, deep, lock, check_refcount);
+ break;
case VAR_DICT:
if ((d = tv->vval.v_dict) != NULL
&& !(check_refcount && d->dv_refcount > 1))
@@ -3189,9 +3260,9 @@
}
}
- // If a list or dict variable wasn't initialized and has meaningful
- // type, do it now. Not for global variables, they are not
- // declared.
+ // If a list or tuple or dict variable wasn't initialized and has
+ // meaningful type, do it now. Not for global variables, they are
+ // not declared.
if (ht != &globvarht)
{
if (tv->v_type == VAR_DICT && tv->vval.v_dict == NULL
@@ -3220,6 +3291,19 @@
sv->sv_flags |= SVFLAG_ASSIGNED;
}
}
+ else if (tv->v_type == VAR_TUPLE && tv->vval.v_tuple == NULL
+ && ((type != NULL && !was_assigned)
+ || !in_vim9script()))
+ {
+ tv->vval.v_tuple = tuple_alloc();
+ if (tv->vval.v_tuple != NULL)
+ {
+ ++tv->vval.v_tuple->tv_refcount;
+ tv->vval.v_tuple->tv_type = alloc_type(type);
+ if (sv != NULL)
+ sv->sv_flags |= SVFLAG_ASSIGNED;
+ }
+ }
else if (tv->v_type == VAR_BLOB && tv->vval.v_blob == NULL
&& ((type != NULL && !was_assigned)
|| !in_vim9script()))
diff --git a/src/ex_docmd.c b/src/ex_docmd.c
index bda20f5..f341fd4 100644
--- a/src/ex_docmd.c
+++ b/src/ex_docmd.c
@@ -3799,8 +3799,12 @@
if (eq != NULL)
{
eq = skipwhite(eq);
- if (vim_strchr((char_u *)"+-*/%", *eq) != NULL)
+ if (vim_strchr((char_u *)"+-*/%.", *eq) != NULL)
+ {
+ if (eq[0] == '.' && eq[1] == '.')
+ ++eq;
++eq;
+ }
}
if (p == NULL || p == eap->cmd || *eq != '=')
{
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:
diff --git a/src/globals.h b/src/globals.h
index ef78b44..7e65af3 100644
--- a/src/globals.h
+++ b/src/globals.h
@@ -549,7 +549,14 @@
#define t_typealias (static_types[90])
#define t_const_typealias (static_types[91])
-EXTERN type_T static_types[92]
+#define t_tuple_any (static_types[92])
+#define t_const_tuple_any (static_types[93])
+
+#define t_tuple_empty (static_types[94])
+#define t_const_tuple_empty (static_types[95])
+
+
+EXTERN type_T static_types[96]
#ifdef DO_INIT
= {
// 0: t_unknown
@@ -735,6 +742,14 @@
// 90: t_typealias
{VAR_TYPEALIAS, 0, 0, TTFLAG_STATIC, NULL, NULL, NULL},
{VAR_TYPEALIAS, 0, 0, TTFLAG_STATIC|TTFLAG_CONST, NULL, NULL, NULL},
+
+ // 92: t_tuple_any
+ {VAR_TUPLE, -1, 0, TTFLAG_STATIC, NULL, NULL, NULL},
+ {VAR_TUPLE, -1, 0, TTFLAG_STATIC|TTFLAG_CONST, NULL, NULL, NULL},
+
+ // 94: t_tuple_empty
+ {VAR_TUPLE, 0, 0, TTFLAG_STATIC, NULL, NULL, NULL},
+ {VAR_TUPLE, 0, 0, TTFLAG_STATIC|TTFLAG_CONST, NULL, NULL, NULL},
}
#endif
;
diff --git a/src/if_py_both.h b/src/if_py_both.h
index a679be5..9f2f582 100644
--- a/src/if_py_both.h
+++ b/src/if_py_both.h
@@ -6248,7 +6248,7 @@
if (func->argc)
for (i = 0; !abort && i < func->argc; ++i)
abort = abort
- || set_ref_in_item(&func->argv[i], copyID, NULL, NULL);
+ || set_ref_in_item(&func->argv[i], copyID, NULL, NULL, NULL);
}
}
@@ -6777,6 +6777,7 @@
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:
diff --git a/src/job.c b/src/job.c
index 2a2e531..9d6c862 100644
--- a/src/job.c
+++ b/src/job.c
@@ -1087,7 +1087,7 @@
{
tv.v_type = VAR_JOB;
tv.vval.v_job = job;
- abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL);
+ abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL, NULL);
}
return abort;
}
diff --git a/src/json.c b/src/json.c
index 66b8bf9..faa35e5 100644
--- a/src/json.c
+++ b/src/json.c
@@ -267,6 +267,7 @@
char_u *res;
blob_T *b;
list_T *l;
+ tuple_T *tuple;
dict_T *d;
int i;
@@ -369,6 +370,42 @@
}
break;
+ case VAR_TUPLE:
+ tuple = val->vval.v_tuple;
+ if (tuple == NULL)
+ ga_concat(gap, (char_u *)"[]");
+ else
+ {
+ if (tuple->tv_copyID == copyID)
+ ga_concat(gap, (char_u *)"[]");
+ else
+ {
+ int len = TUPLE_LEN(tuple);
+
+ tuple->tv_copyID = copyID;
+ ga_append(gap, '[');
+ for (i = 0; i < len && !got_int; i++)
+ {
+ typval_T *t_item = TUPLE_ITEM(tuple, i);
+ if (json_encode_item(gap, t_item, copyID,
+ options & JSON_JS) == FAIL)
+ return FAIL;
+
+ if ((options & JSON_JS)
+ && i == len - 1
+ && t_item->v_type == VAR_SPECIAL
+ && t_item->vval.v_number == VVAL_NONE)
+ // add an extra comma if the last item is v:none
+ ga_append(gap, ',');
+ if (i <= len - 2)
+ ga_append(gap, ',');
+ }
+ ga_append(gap, ']');
+ tuple->tv_copyID = 0;
+ }
+ }
+ break;
+
case VAR_DICT:
d = val->vval.v_dict;
if (d == NULL)
diff --git a/src/list.c b/src/list.c
index 36ce494..c48c751 100644
--- a/src/list.c
+++ b/src/list.c
@@ -1520,15 +1520,13 @@
rettv->v_type = VAR_STRING;
- if (in_vim9script()
- && (check_for_list_arg(argvars, 0) == FAIL
- || check_for_opt_string_arg(argvars, 1) == FAIL))
+ if (check_for_list_or_tuple_arg(argvars, 0) == FAIL
+ || check_for_opt_string_arg(argvars, 1) == FAIL)
return;
- if (check_for_list_arg(argvars, 0) == FAIL)
- return;
-
- if (argvars[0].vval.v_list == NULL)
+ if ((argvars[0].v_type == VAR_LIST && argvars[0].vval.v_list == NULL)
+ || (argvars[0].v_type == VAR_TUPLE
+ && argvars[0].vval.v_tuple == NULL))
return;
if (argvars[1].v_type == VAR_UNKNOWN)
@@ -1539,7 +1537,10 @@
if (sep != NULL)
{
ga_init2(&ga, sizeof(char), 80);
- list_join(&ga, argvars[0].vval.v_list, sep, TRUE, FALSE, 0);
+ if (argvars[0].v_type == VAR_LIST)
+ list_join(&ga, argvars[0].vval.v_list, sep, TRUE, FALSE, 0);
+ else
+ tuple_join(&ga, argvars[0].vval.v_tuple, sep, TRUE, FALSE, 0);
ga_append(&ga, NUL);
rettv->vval.v_string = (char_u *)ga.ga_data;
}
@@ -1778,6 +1779,63 @@
}
/*
+ * "list2tuple()" function
+ */
+ void
+f_list2tuple(typval_T *argvars, typval_T *rettv)
+{
+ list_T *l;
+ listitem_T *li;
+ tuple_T *tuple;
+
+ rettv->v_type = VAR_TUPLE;
+ rettv->vval.v_tuple = NULL;
+
+ if (check_for_list_arg(argvars, 0) == FAIL)
+ return;
+
+ l = argvars[0].vval.v_list;
+ if (l == NULL)
+ return; // empty list results in empty tuple
+
+ CHECK_LIST_MATERIALIZE(l);
+
+ if (rettv_tuple_set_with_items(rettv, list_len(l)) == FAIL)
+ return;
+
+ tuple = rettv->vval.v_tuple;
+ FOR_ALL_LIST_ITEMS(l, li)
+ {
+ copy_tv(&li->li_tv, TUPLE_ITEM(tuple, TUPLE_LEN(tuple)));
+ tuple->tv_items.ga_len++;
+ }
+}
+
+/*
+ * "tuple2list()" function
+ */
+ void
+f_tuple2list(typval_T *argvars, typval_T *rettv)
+{
+ list_T *l;
+ tuple_T *tuple;
+
+ if (rettv_list_alloc(rettv) == FAIL)
+ return;
+
+ if (check_for_tuple_arg(argvars, 0) == FAIL)
+ return;
+
+ tuple = argvars[0].vval.v_tuple;
+ if (tuple == NULL)
+ return; // empty tuple results in empty list
+
+ l = rettv->vval.v_list;
+ for (int i = 0; i < tuple_len(tuple); i++)
+ list_append_tv(l, TUPLE_ITEM(tuple, i));
+}
+
+/*
* Remove item argvars[1] from List argvars[0]. If argvars[2] is supplied, then
* remove the range of items from argvars[1] to argvars[2] (inclusive).
*/
@@ -2560,10 +2618,16 @@
copy_tv(&argvars[0], rettv);
if (in_vim9script()
- && (check_for_list_or_dict_or_blob_or_string_arg(argvars, 0)
+ && (check_for_list_tuple_dict_blob_or_string_arg(argvars, 0)
== FAIL))
return;
+ if (argvars[0].v_type == VAR_TUPLE && filtermap != FILTERMAP_FOREACH)
+ {
+ semsg(_(e_cannot_use_tuple_with_function_str), func_name);
+ return;
+ }
+
if (filtermap == FILTERMAP_MAP && in_vim9script())
{
// Check that map() does not change the declared type of the list or
@@ -2577,11 +2641,17 @@
if (argvars[0].v_type != VAR_BLOB
&& argvars[0].v_type != VAR_LIST
+ && argvars[0].v_type != VAR_TUPLE
&& argvars[0].v_type != VAR_DICT
&& argvars[0].v_type != VAR_STRING)
{
- semsg(_(e_argument_of_str_must_be_list_string_dictionary_or_blob),
- func_name);
+ char *msg;
+
+ if (filtermap == FILTERMAP_FOREACH)
+ msg = e_argument_of_str_must_be_list_tuple_string_dictionary_or_blob;
+ else
+ msg = e_argument_of_str_must_be_list_string_dictionary_or_blob;
+ semsg(_(msg), func_name);
return;
}
@@ -2611,6 +2681,8 @@
arg_errmsg, rettv);
else if (argvars[0].v_type == VAR_STRING)
string_filter_map(tv_get_string(&argvars[0]), filtermap, expr, rettv);
+ else if (argvars[0].v_type == VAR_TUPLE)
+ tuple_foreach(argvars[0].vval.v_tuple, filtermap, expr);
else // argvars[0].v_type == VAR_LIST
list_filter_map(argvars[0].vval.v_list, filtermap, type, func_name,
arg_errmsg, expr, rettv);
@@ -2743,7 +2815,7 @@
int error = FALSE;
if (in_vim9script()
- && (check_for_string_or_list_or_dict_arg(argvars, 0) == FAIL
+ && (check_for_string_list_tuple_or_dict_arg(argvars, 0) == FAIL
|| check_for_opt_bool_arg(argvars, 2) == FAIL
|| (argvars[2].v_type != VAR_UNKNOWN
&& check_for_opt_number_arg(argvars, 3) == FAIL)))
@@ -2765,6 +2837,16 @@
if (!error)
n = list_count(argvars[0].vval.v_list, &argvars[1], idx, ic);
}
+ else if (!error && argvars[0].v_type == VAR_TUPLE)
+ {
+ long idx = 0;
+
+ if (argvars[2].v_type != VAR_UNKNOWN
+ && argvars[3].v_type != VAR_UNKNOWN)
+ idx = (long)tv_get_number_chk(&argvars[3], &error);
+ if (!error)
+ n = tuple_count(argvars[0].vval.v_tuple, &argvars[1], idx, ic);
+ }
else if (!error && argvars[0].v_type == VAR_DICT)
{
if (argvars[2].v_type != VAR_UNKNOWN
@@ -3033,7 +3115,7 @@
void
f_reverse(typval_T *argvars, typval_T *rettv)
{
- if (check_for_string_or_list_or_blob_arg(argvars, 0) == FAIL)
+ if (check_for_string_or_list_or_tuple_or_blob_arg(argvars, 0) == FAIL)
return;
if (argvars[0].v_type == VAR_BLOB)
@@ -3048,6 +3130,8 @@
}
else if (argvars[0].v_type == VAR_LIST)
list_reverse(argvars[0].vval.v_list, rettv);
+ else if (argvars[0].v_type == VAR_TUPLE)
+ tuple_reverse(argvars[0].vval.v_tuple, rettv);
}
/*
@@ -3153,6 +3237,7 @@
/*
* "reduce(list, { accumulator, element -> value } [, initial])" function
+ * "reduce(tuple, { accumulator, element -> value } [, initial])" function
* "reduce(blob, { accumulator, element -> value } [, initial])"
* "reduce(string, { accumulator, element -> value } [, initial])"
*/
@@ -3161,18 +3246,9 @@
{
char_u *func_name;
- if (in_vim9script()
- && check_for_string_or_list_or_blob_arg(argvars, 0) == FAIL)
+ if (check_for_string_or_list_or_tuple_or_blob_arg(argvars, 0) == FAIL)
return;
- if (argvars[0].v_type != VAR_STRING
- && argvars[0].v_type != VAR_LIST
- && argvars[0].v_type != VAR_BLOB)
- {
- emsg(_(e_string_list_or_blob_required));
- return;
- }
-
if (argvars[1].v_type == VAR_FUNC)
func_name = argvars[1].vval.v_string;
else if (argvars[1].v_type == VAR_PARTIAL)
@@ -3187,6 +3263,8 @@
if (argvars[0].v_type == VAR_LIST)
list_reduce(argvars, &argvars[1], rettv);
+ else if (argvars[0].v_type == VAR_TUPLE)
+ tuple_reduce(argvars, &argvars[1], rettv);
else if (argvars[0].v_type == VAR_STRING)
string_reduce(argvars, &argvars[1], rettv);
else
@@ -3202,6 +3280,7 @@
if (in_vim9script()
&& ((argvars[0].v_type != VAR_STRING
&& argvars[0].v_type != VAR_LIST
+ && argvars[0].v_type != VAR_TUPLE
&& argvars[0].v_type != VAR_BLOB
&& check_for_list_arg(argvars, 0) == FAIL)
|| check_for_number_arg(argvars, 1) == FAIL
diff --git a/src/macros.h b/src/macros.h
index 6c2931a..c11bfa5 100644
--- a/src/macros.h
+++ b/src/macros.h
@@ -487,3 +487,7 @@
// Iterate over all the items in a hash table
#define FOR_ALL_HASHTAB_ITEMS(ht, hi, todo) \
for ((hi) = (ht)->ht_array; (todo) > 0; ++(hi))
+
+#define TUPLE_LEN(t) (t->tv_items.ga_len)
+#define TUPLE_ITEM(t, i) \
+ (((typval_T *)t->tv_items.ga_data) + i)
diff --git a/src/netbeans.c b/src/netbeans.c
index 781caa8..5cbbab7 100644
--- a/src/netbeans.c
+++ b/src/netbeans.c
@@ -2561,7 +2561,7 @@
tv.v_type = VAR_CHANNEL;
tv.vval.v_channel = nb_channel;
- abort = set_ref_in_item(&tv, copyID, NULL, NULL);
+ abort = set_ref_in_item(&tv, copyID, NULL, NULL, NULL);
return abort;
}
#endif
diff --git a/src/popupwin.c b/src/popupwin.c
index 76ebf38..46e5483 100644
--- a/src/popupwin.c
+++ b/src/popupwin.c
@@ -4416,13 +4416,13 @@
{
tv.v_type = VAR_PARTIAL;
tv.vval.v_partial = wp->w_close_cb.cb_partial;
- abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL);
+ abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL, NULL);
}
if (wp->w_filter_cb.cb_partial != NULL)
{
tv.v_type = VAR_PARTIAL;
tv.vval.v_partial = wp->w_filter_cb.cb_partial;
- abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL);
+ abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL, NULL);
}
abort = abort || set_ref_in_list(wp->w_popup_mask, copyID);
return abort;
diff --git a/src/proto.h b/src/proto.h
index 091e093..f04ba05 100644
--- a/src/proto.h
+++ b/src/proto.h
@@ -207,6 +207,7 @@
# include "textobject.pro"
# include "textformat.pro"
# include "time.pro"
+# include "tuple.pro"
# include "typval.pro"
# include "ui.pro"
# include "undo.pro"
diff --git a/src/proto/eval.pro b/src/proto/eval.pro
index e945a28..df59383 100644
--- a/src/proto/eval.pro
+++ b/src/proto/eval.pro
@@ -45,6 +45,7 @@
int eval1(char_u **arg, typval_T *rettv, evalarg_T *evalarg);
void eval_addblob(typval_T *tv1, typval_T *tv2);
int eval_addlist(typval_T *tv1, typval_T *tv2);
+int eval_addtuple(typval_T *tv1, typval_T *tv2);
int eval_leader(char_u **arg, int vim9);
int handle_predefined(char_u *s, int len, typval_T *rettv);
int check_can_index(typval_T *rettv, int evaluate, int verbose);
diff --git a/src/proto/evalfunc.pro b/src/proto/evalfunc.pro
index a720b64..627af17 100644
--- a/src/proto/evalfunc.pro
+++ b/src/proto/evalfunc.pro
@@ -23,6 +23,7 @@
void f_exists(typval_T *argvars, typval_T *rettv);
void f_has(typval_T *argvars, typval_T *rettv);
int dynamic_feature(char_u *feature);
+int indexof_eval_expr(typval_T *expr);
void f_len(typval_T *argvars, typval_T *rettv);
void mzscheme_call_vim(char_u *name, typval_T *args, typval_T *rettv);
void range_list_materialize(list_T *list);
diff --git a/src/proto/gc.pro b/src/proto/gc.pro
index e13dbda..8b55030 100644
--- a/src/proto/gc.pro
+++ b/src/proto/gc.pro
@@ -1,12 +1,12 @@
/* gc.c */
int get_copyID(void);
int garbage_collect(int testing);
-int set_ref_in_ht(hashtab_T *ht, int copyID, list_stack_T **list_stack);
+int set_ref_in_ht(hashtab_T *ht, int copyID, list_stack_T **list_stack, tuple_stack_T **tuple_stack);
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);
+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_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);
-int set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack, list_stack_T **list_stack);
+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);
+int set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack, list_stack_T **list_stack, tuple_stack_T **tuple_stack);
/* vim: set ft=c : */
-
diff --git a/src/proto/list.pro b/src/proto/list.pro
index 27bea5e..cb05203 100644
--- a/src/proto/list.pro
+++ b/src/proto/list.pro
@@ -50,6 +50,8 @@
int write_list(FILE *fd, list_T *list, int binary);
void init_static_list(staticList10_T *sl);
void f_list2str(typval_T *argvars, typval_T *rettv);
+void f_list2tuple(typval_T *argvars, typval_T *rettv);
+void f_tuple2list(typval_T *argvars, typval_T *rettv);
void f_sort(typval_T *argvars, typval_T *rettv);
void f_uniq(typval_T *argvars, typval_T *rettv);
int filter_map_one(typval_T *tv, typval_T *expr, filtermap_T filtermap, funccall_T *fc, typval_T *newtv, int *remp);
diff --git a/src/proto/testing.pro b/src/proto/testing.pro
index dea4f75..1c93f8c 100644
--- a/src/proto/testing.pro
+++ b/src/proto/testing.pro
@@ -30,6 +30,7 @@
void f_test_null_function(typval_T *argvars, typval_T *rettv);
void f_test_null_partial(typval_T *argvars, typval_T *rettv);
void f_test_null_string(typval_T *argvars, typval_T *rettv);
+void f_test_null_tuple(typval_T *argvars, typval_T *rettv);
void f_test_unknown(typval_T *argvars, typval_T *rettv);
void f_test_void(typval_T *argvars, typval_T *rettv);
void f_test_setmouse(typval_T *argvars, typval_T *rettv);
diff --git a/src/proto/tuple.pro b/src/proto/tuple.pro
new file mode 100644
index 0000000..138648d
--- /dev/null
+++ b/src/proto/tuple.pro
@@ -0,0 +1,34 @@
+/* tuple.c */
+tuple_T *tuple_alloc(void);
+tuple_T *tuple_alloc_with_items(int count);
+void tuple_set_item(tuple_T *tuple, int idx, typval_T *tv);
+int rettv_tuple_alloc(typval_T *rettv);
+void rettv_tuple_set(typval_T *rettv, tuple_T *tuple);
+int rettv_tuple_set_with_items(typval_T *rettv, int count);
+void tuple_unref(tuple_T *tuple);
+int tuple_free_nonref(int copyID);
+void tuple_free_items(int copyID);
+void tuple_free(tuple_T *tuple);
+long tuple_len(tuple_T *tuple);
+int tuple_equal(tuple_T *t1, tuple_T *t2, int ic);
+typval_T *tuple_find(tuple_T *tuple, long n);
+int tuple_append_tv(tuple_T *tuple, typval_T *tv);
+int tuple_concat(tuple_T *t1, tuple_T *t2, typval_T *tv);
+tuple_T *tuple_slice(tuple_T *tuple, long n1, long n2);
+int tuple_slice_or_index(tuple_T *tuple, int range, varnumber_T n1_arg, varnumber_T n2_arg, int exclusive, typval_T *rettv, int verbose);
+tuple_T *tuple_copy(tuple_T *orig, int deep, int top, int copyID);
+int eval_tuple(char_u **arg, typval_T *rettv, evalarg_T *evalarg, int do_error);
+void tuple_lock(tuple_T *tuple, int deep, int lock, int check_refcount);
+int tuple_join(garray_T *gap, tuple_T *tuple, char_u *sep, int echo_style, int restore_copyID, int copyID);
+char_u *tuple2string(typval_T *tv, int copyID, int restore_copyID);
+void tuple_foreach(tuple_T *tuple, filtermap_T filtermap, typval_T *expr);
+long tuple_count(tuple_T *tuple, typval_T *needle, long idx, int ic);
+void tuple2items(typval_T *argvars, typval_T *rettv);
+int index_tuple(tuple_T *tuple, typval_T *tv, int start_idx, int ic);
+int indexof_tuple(tuple_T *tuple, long startidx, typval_T *expr);
+varnumber_T tuple_max_min(tuple_T *tuple, int domax, int *error);
+void tuple_repeat(tuple_T *tuple, int n, typval_T *rettv);
+void tuple_reverse(tuple_T *tuple, typval_T *rettv);
+void tuple_reduce(typval_T *argvars, typval_T *expr, typval_T *rettv);
+int check_tuples_addable(type_T *type1, type_T *type2);
+/* vim: set ft=c : */
diff --git a/src/proto/typval.pro b/src/proto/typval.pro
index 90dcc54..d30cded 100644
--- a/src/proto/typval.pro
+++ b/src/proto/typval.pro
@@ -18,13 +18,13 @@
int check_for_opt_number_arg(typval_T *args, int idx);
int check_for_float_or_nr_arg(typval_T *args, int idx);
int check_for_bool_arg(typval_T *args, int idx);
-int check_for_bool_or_number_arg(typval_T *args, int idx);
int check_for_opt_bool_arg(typval_T *args, int idx);
int check_for_opt_bool_or_number_arg(typval_T *args, int idx);
int check_for_blob_arg(typval_T *args, int idx);
int check_for_list_arg(typval_T *args, int idx);
int check_for_nonnull_list_arg(typval_T *args, int idx);
int check_for_opt_list_arg(typval_T *args, int idx);
+int check_for_tuple_arg(typval_T *args, int idx);
int check_for_dict_arg(typval_T *args, int idx);
int check_for_nonnull_dict_arg(typval_T *args, int idx);
int check_for_opt_dict_arg(typval_T *args, int idx);
@@ -41,18 +41,20 @@
int check_for_opt_lnum_arg(typval_T *args, int idx);
int check_for_string_or_blob_arg(typval_T *args, int idx);
int check_for_string_or_list_arg(typval_T *args, int idx);
-int check_for_string_or_list_or_blob_arg(typval_T *args, int idx);
+int check_for_string_or_list_or_tuple_or_blob_arg(typval_T *args, int idx);
int check_for_opt_string_or_list_arg(typval_T *args, int idx);
int check_for_string_or_dict_arg(typval_T *args, int idx);
int check_for_string_or_number_or_list_arg(typval_T *args, int idx);
int check_for_opt_string_or_number_or_list_arg(typval_T *args, int idx);
-int check_for_string_or_number_or_list_or_blob_arg(typval_T *args, int idx);
-int check_for_string_or_list_or_dict_arg(typval_T *args, int idx);
+int check_for_repeat_func_arg(typval_T *args, int idx);
+int check_for_string_list_tuple_or_dict_arg(typval_T *args, int idx);
int check_for_string_or_func_arg(typval_T *args, int idx);
int check_for_list_or_blob_arg(typval_T *args, int idx);
-int check_for_list_or_dict_arg(typval_T *args, int idx);
+int check_for_list_or_tuple_arg(typval_T *args, int idx);
+int check_for_list_or_tuple_or_blob_arg(typval_T *args, int idx);
+int check_for_list_or_tuple_or_dict_arg(typval_T *args, int idx);
int check_for_list_or_dict_or_blob_arg(typval_T *args, int idx);
-int check_for_list_or_dict_or_blob_or_string_arg(typval_T *args, int idx);
+int check_for_list_tuple_dict_blob_or_string_arg(typval_T *args, int idx);
int check_for_opt_buffer_or_dict_arg(typval_T *args, int idx);
int check_for_object_arg(typval_T *args, int idx);
int check_for_class_or_typealias_args(typval_T *args, int idx);
@@ -67,6 +69,7 @@
void copy_tv(typval_T *from, typval_T *to);
int typval_compare(typval_T *tv1, typval_T *tv2, exprtype_T type, int ic);
int typval_compare_list(typval_T *tv1, typval_T *tv2, exprtype_T type, int ic, int *res);
+int typval_compare_tuple(typval_T *tv1, typval_T *tv2, exprtype_T type, int ic, int *res);
int typval_compare_null(typval_T *tv1, typval_T *tv2);
int typval_compare_blob(typval_T *tv1, typval_T *tv2, exprtype_T type, int *res);
int typval_compare_object(typval_T *tv1, typval_T *tv2, exprtype_T type, int ic, int *res);
diff --git a/src/proto/vim9instr.pro b/src/proto/vim9instr.pro
index 641648f..a4504c5 100644
--- a/src/proto/vim9instr.pro
+++ b/src/proto/vim9instr.pro
@@ -44,6 +44,7 @@
int generate_OLDSCRIPT(cctx_T *cctx, isntype_T isn_type, char_u *name, int sid, type_T *type);
int generate_VIM9SCRIPT(cctx_T *cctx, isntype_T isn_type, int sid, int idx, type_T *type);
int generate_NEWLIST(cctx_T *cctx, int count, int use_null);
+int generate_NEWTUPLE(cctx_T *cctx, int count, int use_null);
int generate_NEWDICT(cctx_T *cctx, int count, int use_null);
int generate_FUNCREF(cctx_T *cctx, ufunc_T *ufunc, class_T *cl, int object_method, int fi, int *isn_idx);
int generate_NEWFUNC(cctx_T *cctx, char_u *lambda_name, char_u *func_name);
diff --git a/src/proto/vim9type.pro b/src/proto/vim9type.pro
index 093a5ec..865a93c 100644
--- a/src/proto/vim9type.pro
+++ b/src/proto/vim9type.pro
@@ -7,6 +7,7 @@
void free_type(type_T *type);
void set_tv_type(typval_T *tv, type_T *type);
type_T *get_list_type(type_T *member_type, garray_T *type_gap);
+type_T *get_tuple_type(garray_T *tuple_types_gap, garray_T *type_gap);
type_T *get_dict_type(type_T *member_type, garray_T *type_gap);
type_T *alloc_func_type(type_T *ret_type, int argcount, garray_T *type_gap);
type_T *get_func_type(type_T *ret_type, int argcount, garray_T *type_gap);
@@ -27,12 +28,14 @@
type_T *parse_type(char_u **arg, garray_T *type_gap, int give_error);
int equal_type(type_T *type1, type_T *type2, int flags);
void common_type(type_T *type1, type_T *type2, type_T **dest, garray_T *type_gap);
+type_T *get_item_type(type_T *type);
int push_type_stack(cctx_T *cctx, type_T *type);
int push_type_stack2(cctx_T *cctx, type_T *type, type_T *decl_type);
void set_type_on_stack(cctx_T *cctx, type_T *type, int offset);
type_T *get_type_on_stack(cctx_T *cctx, int offset);
type_T *get_decl_type_on_stack(cctx_T *cctx, int offset);
type_T *get_member_type_from_stack(int count, int skip, cctx_T *cctx);
+int get_tuple_type_from_stack(int count, garray_T *tuple_types_gap, cctx_T *cctx);
char *vartype_name(vartype_T type);
char *type_name(type_T *type, char **tofree);
void f_typename(typval_T *argvars, typval_T *rettv);
diff --git a/src/quickfix.c b/src/quickfix.c
index e498d5e..30353c5 100644
--- a/src/quickfix.c
+++ b/src/quickfix.c
@@ -8065,7 +8065,7 @@
typval_T* user_data = &qfp->qf_user_data;
if (user_data != NULL && user_data->v_type != VAR_NUMBER
&& user_data->v_type != VAR_STRING && user_data->v_type != VAR_FLOAT)
- abort = abort || set_ref_in_item(user_data, copyID, NULL, NULL);
+ abort = abort || set_ref_in_item(user_data, copyID, NULL, NULL, NULL);
}
}
return abort;
@@ -8088,7 +8088,7 @@
ctx = qi->qf_lists[i].qf_ctx;
if (ctx != NULL && ctx->v_type != VAR_NUMBER
&& ctx->v_type != VAR_STRING && ctx->v_type != VAR_FLOAT)
- abort = abort || set_ref_in_item(ctx, copyID, NULL, NULL);
+ abort = abort || set_ref_in_item(ctx, copyID, NULL, NULL, NULL);
cb = &qi->qf_lists[i].qf_qftf_cb;
abort = abort || set_ref_in_callback(cb, copyID);
diff --git a/src/structs.h b/src/structs.h
index ce98bce..1a3abcb 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -73,6 +73,7 @@
typedef struct dictvar_S dict_T;
typedef struct partial_S partial_T;
typedef struct blobvar_S blob_T;
+typedef struct tuplevar_S tuple_T;
typedef struct window_S win_T;
typedef struct wininfo_S wininfo_T;
@@ -1511,7 +1512,8 @@
VAR_INSTR, // "v_instr" is used
VAR_CLASS, // "v_class" is used (also used for interface)
VAR_OBJECT, // "v_object" is used
- VAR_TYPEALIAS // "v_typealias" is used
+ VAR_TYPEALIAS, // "v_typealias" is used
+ VAR_TUPLE // "v_tuple" is used
} vartype_T;
// A type specification.
@@ -1679,6 +1681,7 @@
class_T *v_class; // class value (can be NULL)
object_T *v_object; // object value (can be NULL)
typealias_T *v_typealias; // user-defined type name
+ tuple_T *v_tuple; // tuple
} vval;
};
@@ -1818,6 +1821,21 @@
char bv_lock; // zero, VAR_LOCKED, VAR_FIXED
};
+/*
+ * Structure to hold info about a tuple.
+ */
+struct tuplevar_S
+{
+ garray_T tv_items; // tuple items
+ type_T *tv_type; // current type, allocated by alloc_type()
+ tuple_T *tv_copytuple; // copied tuple used by deepcopy()
+ tuple_T *tv_used_next; // next tuple in used tuples list
+ tuple_T *tv_used_prev; // previous tuple in used tuples list
+ int tv_refcount; // reference count
+ int tv_copyID; // ID used by deepcopy()
+ char tv_lock; // zero, VAR_LOCKED, VAR_FIXED
+};
+
typedef int (*cfunc_T)(int argcount, typval_T *argvars, typval_T *rettv, void *state);
typedef void (*cfunc_free_T)(void *state);
@@ -1846,6 +1864,8 @@
blob_T *fi_blob; // blob being used
char_u *fi_string; // copy of string being used
int fi_byte_idx; // byte index in fi_string
+ tuple_T *fi_tuple; // tuple being used
+ int fi_tuple_idx; // tuple index in fi_tuple
int fi_cs_flags; // cs_flags or'ed together
} forinfo_T;
@@ -2821,6 +2841,15 @@
} list_stack_T;
/*
+ * structure used for explicit stack while garbage collecting tuples
+ */
+typedef struct tuple_stack_S
+{
+ tuple_T *tuple;
+ struct tuple_stack_S *prev;
+} tuple_stack_T;
+
+/*
* Structure used for iterating over dictionary items.
* Initialize with dict_iterate_start().
*/
@@ -4692,6 +4721,7 @@
char_u *ll_newkey; // New key for Dict in alloc. mem or NULL.
type_T *ll_valtype; // type expected for the value or NULL
blob_T *ll_blob; // The Blob or NULL
+ tuple_T *ll_tuple; // tuple or NULL
ufunc_T *ll_ufunc; // The function or NULL
object_T *ll_object; // The object or NULL, class is not NULL
class_T *ll_class; // The class or NULL, object may be NULL
diff --git a/src/terminal.c b/src/terminal.c
index 6edc21a..eb27fa5 100644
--- a/src/terminal.c
+++ b/src/terminal.c
@@ -5081,7 +5081,7 @@
{
tv.v_type = VAR_JOB;
tv.vval.v_job = term->tl_job;
- abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL);
+ abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL, NULL);
}
return abort;
}
diff --git a/src/testdir/Make_all.mak b/src/testdir/Make_all.mak
index 7d50a7e..55434b0 100644
--- a/src/testdir/Make_all.mak
+++ b/src/testdir/Make_all.mak
@@ -327,6 +327,7 @@
test_timers \
test_true_false \
test_trycatch \
+ test_tuple \
test_undo \
test_unlet \
test_user_func \
@@ -578,6 +579,7 @@
test_timers.res \
test_true_false.res \
test_trycatch.res \
+ test_tuple.res \
test_undo.res \
test_user_func.res \
test_usercommands.res \
diff --git a/src/testdir/test_blob.vim b/src/testdir/test_blob.vim
index f0e8209..a4a567d 100644
--- a/src/testdir/test_blob.vim
+++ b/src/testdir/test_blob.vim
@@ -35,7 +35,7 @@
call assert_fails('VAR b = 0z1.1')
call assert_fails('VAR b = 0z.')
call assert_fails('VAR b = 0z001122.')
- call assert_fails('call get("", 1)', 'E896:')
+ call assert_fails('call get("", 1)', 'E1531:')
call assert_equal(0, len(test_null_blob()))
call assert_equal(0z, copy(test_null_blob()))
END
@@ -786,6 +786,7 @@
func Test_blob_repeat()
call assert_equal(0z, repeat(0z00, 0))
+ call assert_equal(0z, repeat(0z, 1))
call assert_equal(0z00, repeat(0z00, 1))
call assert_equal(0z0000, repeat(0z00, 2))
call assert_equal(0z00000000, repeat(0z0000, 2))
diff --git a/src/testdir/test_eval_stuff.vim b/src/testdir/test_eval_stuff.vim
index 1b17108..d2f949e 100644
--- a/src/testdir/test_eval_stuff.vim
+++ b/src/testdir/test_eval_stuff.vim
Binary files differ
diff --git a/src/testdir/test_expr.vim b/src/testdir/test_expr.vim
index 5dedce5..41b54aa 100644
--- a/src/testdir/test_expr.vim
+++ b/src/testdir/test_expr.vim
@@ -658,10 +658,10 @@
endfunc
func Test_max_min_errors()
- call v9.CheckLegacyAndVim9Failure(['call max(v:true)'], ['E712:', 'E1013:', 'E1227:'])
- call v9.CheckLegacyAndVim9Failure(['call max(v:true)'], ['max()', 'E1013:', 'E1227:'])
- call v9.CheckLegacyAndVim9Failure(['call min(v:true)'], ['E712:', 'E1013:', 'E1227:'])
- call v9.CheckLegacyAndVim9Failure(['call min(v:true)'], ['min()', 'E1013:', 'E1227:'])
+ call v9.CheckLegacyAndVim9Failure(['call max(v:true)'], ['E712:', 'E1013:', 'E1530:'])
+ call v9.CheckLegacyAndVim9Failure(['call max(v:true)'], ['max()', 'E1013:', 'E1530:'])
+ call v9.CheckLegacyAndVim9Failure(['call min(v:true)'], ['E712:', 'E1013:', 'E1530:'])
+ call v9.CheckLegacyAndVim9Failure(['call min(v:true)'], ['min()', 'E1013:', 'E1530:'])
endfunc
func Test_function_with_funcref()
diff --git a/src/testdir/test_filter_map.vim b/src/testdir/test_filter_map.vim
index 37ebe84..39da767 100644
--- a/src/testdir/test_filter_map.vim
+++ b/src/testdir/test_filter_map.vim
@@ -173,6 +173,7 @@
call assert_fails("let l = filter([1, 2], {a, b, c -> 1})", 'E119:')
call assert_fails('call foreach([1], "xyzzy")', 'E492:')
call assert_fails('call foreach([1], "let a = foo")', 'E121:')
+ call assert_fails('call foreach(test_null_function(), "")', 'E1525:')
endfunc
func Test_map_and_modify()
diff --git a/src/testdir/test_lambda.vim b/src/testdir/test_lambda.vim
index 250ce5c..f05080e 100644
--- a/src/testdir/test_lambda.vim
+++ b/src/testdir/test_lambda.vim
@@ -362,7 +362,7 @@
func Test_lambda_error()
" This was causing a crash
- call assert_fails('ec{@{->{d->()()', 'E15:')
+ call assert_fails('ec{@{->{d->()()', 'E451:')
endfunc
func Test_closure_error()
diff --git a/src/testdir/test_let.vim b/src/testdir/test_let.vim
index e31b514..dffc5c6 100644
--- a/src/testdir/test_let.vim
+++ b/src/testdir/test_let.vim
@@ -310,7 +310,7 @@
call assert_fails('let [a]', 'E474:')
call assert_fails('let [a, b] = [', 'E697:')
call assert_fails('let [a, b] = [10, 20', 'E696:')
- call assert_fails('let [a, b] = 10', 'E714:')
+ call assert_fails('let [a, b] = 10', 'E1535:')
call assert_fails('let [a, , b] = [10, 20]', 'E475:')
call assert_fails('let [a, b&] = [10, 20]', 'E475:')
call assert_fails('let $ = 10', 'E475:')
diff --git a/src/testdir/test_listdict.vim b/src/testdir/test_listdict.vim
index f3bdcd4..fb350a8 100644
--- a/src/testdir/test_listdict.vim
+++ b/src/testdir/test_listdict.vim
@@ -192,6 +192,14 @@
END
call v9.CheckLegacyAndVim9Success(lines)
+ let lines =<< trim END
+ VAR [x, y] = test_null_list()
+ END
+ call v9.CheckLegacyAndVim9Failure(lines, [
+ \ 'E714: List required',
+ \ 'E1093: Expected 2 items but got 0',
+ \ 'E714: List required'])
+
let d = {'abc': [1, 2, 3]}
call assert_fails('let d.abc[0:0z10] = [10, 20]', 'E976: Using a Blob as a String')
endfunc
@@ -1000,7 +1008,7 @@
END
call v9.CheckLegacyAndVim9Success(lines)
- call assert_fails('call reverse({})', 'E1252:')
+ call assert_fails('call reverse({})', 'E1253:')
call assert_fails('call uniq([1, 2], {x, y -> []})', 'E745:')
call assert_fails("call sort([1, 2], function('min'), 1)", "E1206:")
call assert_fails("call sort([1, 2], function('invalid_func'))", "E700:")
@@ -1073,15 +1081,15 @@
call assert_fails("call reduce('', { acc, val -> acc + val })", 'E998: Reduce of an empty String with no initial value')
call assert_fails("call reduce(test_null_string(), { acc, val -> acc + val })", 'E998: Reduce of an empty String with no initial value')
- call assert_fails("call reduce({}, { acc, val -> acc + val }, 1)", 'E1098:')
- call assert_fails("call reduce(0, { acc, val -> acc + val }, 1)", 'E1098:')
+ call assert_fails("call reduce({}, { acc, val -> acc + val }, 1)", 'E1253:')
+ call assert_fails("call reduce(0, { acc, val -> acc + val }, 1)", 'E1253:')
call assert_fails("call reduce([1, 2], 'Xdoes_not_exist')", 'E117:')
call assert_fails("echo reduce(0z01, { acc, val -> 2 * acc + val }, '')", 'E1210:')
- call assert_fails("vim9 reduce(0, (acc, val) => (acc .. val), '')", 'E1252:')
- call assert_fails("vim9 reduce({}, (acc, val) => (acc .. val), '')", 'E1252:')
- call assert_fails("vim9 reduce(0.1, (acc, val) => (acc .. val), '')", 'E1252:')
- call assert_fails("vim9 reduce(function('tr'), (acc, val) => (acc .. val), '')", 'E1252:')
+ call assert_fails("vim9 reduce(0, (acc, val) => (acc .. val), '')", 'E1253:')
+ call assert_fails("vim9 reduce({}, (acc, val) => (acc .. val), '')", 'E1253:')
+ call assert_fails("vim9 reduce(0.1, (acc, val) => (acc .. val), '')", 'E1253:')
+ call assert_fails("vim9 reduce(function('tr'), (acc, val) => (acc .. val), '')", 'E1253:')
call assert_fails("call reduce('', { acc, val -> acc + val }, 1)", 'E1174:')
call assert_fails("call reduce('', { acc, val -> acc + val }, {})", 'E1174:')
call assert_fails("call reduce('', { acc, val -> acc + val }, 0.1)", 'E1174:')
@@ -1463,7 +1471,7 @@
let l = test_null_list()
call assert_equal([], extend(l, l, 0))
call assert_equal(0, insert(test_null_list(), 2, -1))
- call assert_fails('let s = join([1, 2], [])', 'E730:')
+ call assert_fails('let s = join([1, 2], [])', 'E1174:')
call assert_fails('call remove(l, 0, 2)', 'E684:')
call assert_fails('call insert(l, 2, -1)', 'E684:')
call assert_fails('call extend(test_null_list(), test_null_list())', 'E1134:')
@@ -1544,7 +1552,7 @@
call assert_fails('let i = indexof(l, "v:val == ''cyan''")', 'E735:')
call assert_fails('let i = indexof(l, "color == ''cyan''")', 'E121:')
call assert_fails('let i = indexof(l, {})', 'E1256:')
- call assert_fails('let i = indexof({}, "v:val == 2")', 'E1226:')
+ call assert_fails('let i = indexof({}, "v:val == 2")', 'E1528:')
call assert_fails('let i = indexof([], "v:val == 2", [])', 'E1206:')
func TestIdx(k, v)
diff --git a/src/testdir/test_method.vim b/src/testdir/test_method.vim
index f18ac14..99e4917 100644
--- a/src/testdir/test_method.vim
+++ b/src/testdir/test_method.vim
@@ -52,7 +52,7 @@
call assert_fails("let x = d->insert(0)", 'E899:')
call assert_true(d->has_key('two'))
call assert_equal([['one', 1], ['two', 2], ['three', 3]], d->items())
- call assert_fails("let x = d->join()", 'E1211:')
+ call assert_fails("let x = d->join()", 'E1529:')
call assert_equal(['one', 'two', 'three'], d->keys())
call assert_equal(3, d->len())
call assert_equal(#{one: 2, two: 3, three: 4}, d->map('v:val + 1'))
@@ -62,7 +62,7 @@
call assert_equal(2, d->remove("two"))
let d.two = 2
call assert_fails('let x = d->repeat(2)', 'E731:')
- call assert_fails('let x = d->reverse()', 'E1252:')
+ call assert_fails('let x = d->reverse()', 'E1253:')
call assert_fails('let x = d->sort()', 'E686:')
call assert_equal("{'one': 1, 'two': 2, 'three': 3}", d->string())
call assert_equal(v:t_dict, d->type())
diff --git a/src/testdir/test_put.vim b/src/testdir/test_put.vim
index 26eb7f0..1e123d1 100644
--- a/src/testdir/test_put.vim
+++ b/src/testdir/test_put.vim
Binary files differ
diff --git a/src/testdir/test_tuple.vim b/src/testdir/test_tuple.vim
new file mode 100644
index 0000000..fce5292
--- /dev/null
+++ b/src/testdir/test_tuple.vim
@@ -0,0 +1,2306 @@
+" Tests for the Tuple types
+
+import './vim9.vim' as v9
+
+func TearDown()
+ " Run garbage collection after every test
+ call test_garbagecollect_now()
+endfunc
+
+" Tuple declaration
+func Test_tuple_declaration()
+ let lines =<< trim END
+ var Fn = function('min')
+ var t = (1, 'a', true, 3.1, 0z10, ['x'], {'a': []}, Fn)
+ assert_equal((1, 'a', true, 3.1, 0z10, ['x'], {'a': []}, Fn), t)
+ END
+ call v9.CheckSourceDefAndScriptSuccess(lines)
+
+ " Multiline tuple declaration
+ let lines =<< trim END
+ var t = (
+ 'a',
+ 'b',
+ )
+ assert_equal(('a', 'b'), t)
+ END
+ call v9.CheckSourceDefAndScriptSuccess(lines)
+
+ " Tuple declaration with comments
+ let lines =<< trim END
+ var t = ( # xxx
+ # xxx
+ 'a', # xxx
+ # xxx
+ 'b', # xxx
+ ) # xxx
+ assert_equal(('a', 'b'), t)
+ END
+ call v9.CheckSourceDefAndScriptSuccess(lines)
+
+ " Tuple declaration separated by '|'
+ let lines =<< trim END
+ VAR t1 = ('a', 'b') | VAR t2 = ('c', 'd')
+ call assert_equal(('a', 'b'), t1)
+ call assert_equal(('c', 'd'), t2)
+ END
+ call v9.CheckSourceLegacyAndVim9Success(lines)
+
+ " Space after and before parens
+ let lines =<< trim END
+ var t = ( 1, 2 )
+ assert_equal((1, 2), t)
+ END
+ call v9.CheckSourceDefAndScriptSuccess(lines)
+endfunc
+
+" Tuple declaration error
+func Test_tuple_declaration_error()
+ let lines =<< trim END
+ var t: tuple<> = ('a', 'b')
+ END
+ call v9.CheckSourceDefAndScriptFailure(lines, "E1008: Missing <type> after > = ('a', 'b')")
+
+ let lines =<< trim END
+ var t: tuple = ('a', 'b')
+ END
+ call v9.CheckSourceDefAndScriptFailure(lines, "E1008: Missing <type> after tuple")
+
+ let lines =<< trim END
+ var t: tuple<number> = ('a','b')
+ END
+ call v9.CheckSourceDefAndScriptFailure(lines, "E1069: White space required after ',': ,'b')")
+
+ let lines =<< trim END
+ var t: tuple<number> = ('a', 'b','c')
+ END
+ call v9.CheckSourceDefAndScriptFailure(lines, "E1069: White space required after ',': ,'c')")
+
+ let lines =<< trim END
+ var t: tuple <number> = ()
+ END
+ call v9.CheckSourceDefAndScriptFailure(lines, "E1068: No white space allowed before '<'")
+
+ let lines =<< trim END
+ var t: tuple<number,string>
+ END
+ call v9.CheckSourceDefAndScriptFailure(lines, "E1069: White space required after ','")
+
+ let lines =<< trim END
+ var t: tuple<number , string>
+ END
+ call v9.CheckSourceDefFailure(lines, "E1068: No white space allowed before ','")
+
+ let lines =<< trim END
+ var t = ('a', 'b' , 'c')
+ END
+ call v9.CheckSourceDefAndScriptFailure(lines, [
+ \ "E1068: No white space allowed before ','",
+ \ "E1068: No white space allowed before ','"])
+
+ let lines =<< trim END
+ VAR t = ('a', 'b' 'c')
+ END
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ "E1527: Missing comma in Tuple: 'c')",
+ \ "E1527: Missing comma in Tuple: 'c')",
+ \ "E1527: Missing comma in Tuple: 'c')"])
+
+ let lines =<< trim END
+ VAR t = ('a', 'b',
+ END
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ "E1526: Missing end of Tuple ')'",
+ \ "E1526: Missing end of Tuple ')'",
+ \ "E1526: Missing end of Tuple ')'"])
+
+ let lines =<< trim END
+ var t: tuple<number, ...> = (1, 2, 3)
+ END
+ call v9.CheckSourceDefAndScriptFailure(lines, [
+ \ 'E1010: Type not recognized: ',
+ \ 'E1010: Type not recognized: '])
+
+ let lines =<< trim END
+ var t: tuple<number, ...number> = (1, 2, 3)
+ END
+ call v9.CheckSourceDefAndScriptFailure(lines, [
+ \ 'E1539: Variadic tuple must end with a list type: number',
+ \ 'E1539: Variadic tuple must end with a list type: number'])
+
+ " Invalid expression in the tuple
+ let lines =<< trim END
+ def Foo()
+ var t = (1, 1*2, 2)
+ enddef
+ defcompile
+ END
+ call v9.CheckSourceDefFailure(lines, 'E1004: White space required before and after ''*'' at "*2, 2)"')
+
+ let lines =<< trim END
+ VAR t = ('a', , 'b',)
+ END
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ 'E15: Invalid expression: ", ''b'',)"',
+ \ "E1068: No white space allowed before ',': , 'b',)",
+ \ 'E15: Invalid expression: ", ''b'',)"'])
+
+ let lines =<< trim END
+ VAR t = ('a', 'b', ,)
+ END
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ 'E15: Invalid expression: ",)"',
+ \ "E1068: No white space allowed before ',': ,)",
+ \ 'E15: Invalid expression: ",)"'])
+
+ let lines =<< trim END
+ VAR t = (, 'a', 'b')
+ END
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ 'E15: Invalid expression: ", ''a'', ''b'')"',
+ \ "E1015: Name expected: , 'a', 'b')",
+ \ 'E15: Invalid expression: ", ''a'', ''b'')"'])
+
+ let lines =<< trim END
+ var t: tupel<number> = (1,)
+ END
+ call v9.CheckSourceDefAndScriptFailure(lines, 'E1010: Type not recognized: tupel<number>')
+
+ let lines =<< trim END
+ var t: tuple<number> = [1, 2]
+ END
+ call v9.CheckSourceDefAndScriptFailure(lines, 'E1012: Type mismatch; expected tuple<number> but got list<number>')
+endfunc
+
+" Test for indexing a tuple
+func Test_tuple_indexing()
+ let lines =<< trim END
+ VAR t = ('a', 'b', 'c')
+ call assert_equal(['a', 'b', 'c'], [t[0], t[1], t[2]])
+ call assert_equal(['c', 'b', 'a'], [t[-1], t[-2], t[-3]])
+ END
+ call v9.CheckSourceLegacyAndVim9Success(lines)
+
+ " Indexing a tuple passed as a function argument
+ let lines =<< trim END
+ vim9script
+ def Fn(t: any)
+ call assert_equal(['a', 'b', 'c'], [t[0], t[1], t[2]])
+ call assert_equal(['c', 'b', 'a'], [t[-1], t[-2], t[-3]])
+ enddef
+ Fn(('a', 'b', 'c'))
+ END
+ call v9.CheckSourceSuccess(lines)
+
+ let lines =<< trim END
+ var t: tuple<...list<number>> = (10, 20)
+ var x: number = t[0]
+ assert_equal(10, x)
+ END
+ call v9.CheckSourceDefAndScriptSuccess(lines)
+
+ let lines =<< trim END
+ var t: tuple<...list<list<number>>> = ([1, 2], [3, 4])
+ t[0][1] = 5
+ assert_equal(([1, 5], [3, 4]), t)
+ END
+ call v9.CheckSourceDefAndScriptSuccess(lines)
+
+ let lines =<< trim END
+ var t: tuple<list<number>> = ([2, 4],)
+ t[0][1] = 6
+ assert_equal(([2, 6],), t)
+ END
+ call v9.CheckSourceDefAndScriptSuccess(lines)
+endfunc
+
+" Indexing a tuple in a Dict
+func Test_tuple_in_a_dict_index()
+ let lines =<< trim END
+ vim9script
+ def Fn()
+ var d = {a: (1, 2)}
+ var x = d.a[0]
+ assert_equal('number', typename(x))
+ enddef
+ Fn()
+ END
+ call v9.CheckSourceSuccess(lines)
+endfunc
+
+func Test_tuple_index_error()
+ let lines =<< trim END
+ echo ('a', 'b', 'c')[3]
+ END
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ 'E1519: Tuple index out of range: 3',
+ \ 'E1519: Tuple index out of range: 3',
+ \ 'E1519: Tuple index out of range: 3'])
+
+ let lines =<< trim END
+ echo ('a', 'b', 'c')[-4]
+ END
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ 'E1519: Tuple index out of range: -4',
+ \ 'E1519: Tuple index out of range: -4',
+ \ 'E1519: Tuple index out of range: -4'])
+
+ let lines =<< trim END
+ vim9script
+ def Fn(t: any)
+ echo t[3]
+ enddef
+ Fn(('a', 'b', 'c'))
+ END
+ call v9.CheckSourceFailure(lines, 'E1519: Tuple index out of range: 3')
+
+ let lines =<< trim END
+ vim9script
+ def Fn(t: any)
+ echo t[-4]
+ enddef
+ Fn(('a', 'b', 'c'))
+ END
+ call v9.CheckSourceFailure(lines, 'E1519: Tuple index out of range: -4')
+
+ let lines =<< trim END
+ vim9script
+ def Fn(t: any)
+ var x = t[0]
+ enddef
+ Fn(())
+ END
+ call v9.CheckSourceFailure(lines, 'E1519: Tuple index out of range: 0')
+
+ " Index a null tuple
+ let lines =<< trim END
+ VAR t = test_null_tuple()
+ LET t[0][0] = 10
+ END
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ 'E1519: Tuple index out of range: 0',
+ \ 'E1519: Tuple index out of range: 0',
+ \ 'E1519: Tuple index out of range: 0'])
+
+ let lines =<< trim END
+ var x = null_tuple
+ x[0][0] = 10
+ END
+ call v9.CheckSourceDefExecAndScriptFailure(lines, [
+ \ 'E1519: Tuple index out of range: 0',
+ \ 'E1519: Tuple index out of range: 0'])
+
+ " Use a float as the index
+ let lines =<< trim END
+ VAR t = (1, 2)
+ VAR x = t[0.1]
+ END
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ 'E805: Using a Float as a Number',
+ \ 'E1012: Type mismatch; expected number but got float',
+ \ 'E805: Using a Float as a Number'])
+endfunc
+
+" Test for slicing a tuple
+func Test_tuple_slice()
+ let lines =<< trim END
+ VAR t = (1, 3, 5, 7, 9)
+ call assert_equal((3, 5), t[1 : 2])
+ call assert_equal((9,), t[4 : 4])
+ call assert_equal((7, 9), t[3 : 6])
+ call assert_equal((1, 3, 5), t[: 2])
+ call assert_equal((5, 7, 9), t[2 :])
+ call assert_equal((1, 3, 5, 7, 9), t[:])
+ call assert_equal((), test_null_tuple()[:])
+ END
+ call v9.CheckSourceLegacyAndVim9Success(lines)
+
+ let lines =<< trim END
+ call assert_equal(('b', 'c'), ('a', 'b', 'c')[1 : 5])
+ END
+ call v9.CheckSourceLegacyAndVim9Success(lines)
+endfunc
+
+" Test for concatenating tuples
+func Test_tuple_concatenate()
+ let lines =<< trim END
+ VAR t1 = ('a', 'b') + ('c', 'd')
+ call assert_equal(('a', 'b', 'c', 'd'), t1)
+
+ VAR t2 = ('a',) + ('b',)
+ call assert_equal(('a', 'b'), t2)
+
+ VAR t3 = ('a',) + ()
+ call assert_equal(('a',), t3)
+
+ VAR t4 = () + ('b',)
+ call assert_equal(('b',), t4)
+
+ VAR t5 = ('a', 'b') + test_null_tuple()
+ call assert_equal(('a', 'b'), t5)
+ call assert_equal('tuple<string, string>', typename(t5))
+
+ VAR t6 = test_null_tuple() + ('c', 'd')
+ call assert_equal(('c', 'd'), t6)
+ call assert_equal('tuple<string, string>', typename(t6))
+
+ VAR t7 = ('a', 'b') + (8, 9)
+ call assert_equal(('a', 'b', 8, 9), t7)
+ call assert_equal('tuple<string, string, number, number>', typename(t7))
+ END
+ call v9.CheckSourceLegacyAndVim9Success(lines)
+
+ let lines =<< trim END
+ var t1: tuple<...list<tuple<number, number>>> = ()
+ var t2: tuple<...list<tuple<number, number>>> = ()
+ var t: tuple<...list<tuple<number, number>>> = t1 + t2
+ assert_equal((), t)
+ END
+ call v9.CheckSourceDefAndScriptSuccess(lines)
+
+ let lines =<< trim END
+ var t: tuple<...list<number>> = (1, 2) + ('a', 'b')
+ END
+ call v9.CheckSourceDefExecAndScriptFailure(lines, [
+ \ 'E1012: Type mismatch; expected tuple<...list<number>> but got tuple<number, number, string, string>',
+ \ 'E1012: Type mismatch; expected tuple<...list<number>> but got tuple<number, number, string, string>'])
+
+ let lines =<< trim END
+ var a: tuple<...list<number>> = (1, 2)
+ var b: tuple<...list<string>> = ('a', 'b')
+ var t = a + b
+ END
+ call v9.CheckSourceDefExecAndScriptFailure(lines, [
+ \ 'E1540: Cannot use a variadic tuple in concatenation',
+ \ 'E1540: Cannot use a variadic tuple in concatenation'])
+
+ let lines =<< trim END
+ var a: tuple<...list<number>> = (1, 2)
+ var b: tuple<string, string> = ('a', 'b')
+ var t = a + b
+ END
+ call v9.CheckSourceDefExecAndScriptFailure(lines, [
+ \ 'E1540: Cannot use a variadic tuple in concatenation',
+ \ 'E1540: Cannot use a variadic tuple in concatenation'])
+
+ let lines =<< trim END
+ var a: tuple<number, ...list<string>> = (1, 'a', 'b')
+ var b: tuple<number, ...list<string>> = (2, 'c', 'd')
+ var t = a + b
+ END
+ call v9.CheckSourceDefExecAndScriptFailure(lines, [
+ \ 'E1540: Cannot use a variadic tuple in concatenation',
+ \ 'E1540: Cannot use a variadic tuple in concatenation'])
+
+ let lines =<< trim END
+ var a: tuple<number, ...list<string>> = (1, 'a', 'b')
+ var b: tuple<...list<string>> = ('c', 'd')
+ var t = a + b
+ END
+ call v9.CheckSourceDefExecAndScriptFailure(lines, [
+ \ 'E1540: Cannot use a variadic tuple in concatenation',
+ \ 'E1540: Cannot use a variadic tuple in concatenation'])
+
+ let lines =<< trim END
+ var a: tuple<...list<string>> = ('a', 'b')
+ var b: tuple<number, ...list<string>> = (2, 'c', 'd')
+ var t = a + b
+ END
+ call v9.CheckSourceDefExecAndScriptFailure(lines, [
+ \ 'E1540: Cannot use a variadic tuple in concatenation',
+ \ 'E1540: Cannot use a variadic tuple in concatenation'])
+
+ let lines =<< trim END
+ var t1: tuple<...list<tuple<number, number>>> = ()
+ var t2: tuple<...list<tuple<number, string>>> = ()
+ var t = t1 + t2
+ END
+ call v9.CheckSourceDefExecAndScriptFailure(lines, [
+ \ 'E1540: Cannot use a variadic tuple in concatenation',
+ \ 'E1540: Cannot use a variadic tuple in concatenation'])
+
+ " Make sure the correct line number is used in the error message
+ let lines =<< trim END
+ vim9script
+ var t1: tuple<...list<tuple<number, number>>> = ()
+ var t2: tuple<...list<tuple<number, string>>> = ()
+ var t = t1 + t2
+
+ END
+ call v9.CheckSourceFailure(lines, 'E1540: Cannot use a variadic tuple in concatenation', 4)
+
+ let lines =<< trim END
+ vim9script
+
+ def Fn()
+ var t1: tuple<...list<tuple<number, number>>> = ()
+ var t2: tuple<...list<tuple<number, string>>> = ()
+ var t = t1 + t2
+
+ enddef
+ Fn()
+ END
+ call v9.CheckSourceFailure(lines, 'E1540: Cannot use a variadic tuple in concatenation', 3)
+
+ " One or both the operands are variadic tuples
+ let lines =<< trim END
+ var a1: tuple<number, number> = (1, 2)
+ var b1: tuple<...list<string>> = ('a', 'b')
+ var t1 = a1 + b1
+ assert_equal((1, 2, 'a', 'b'), t1)
+
+ var a2: tuple<string, string> = ('a', 'b')
+ var b2: tuple<number, ...list<string>> = (1, 'c', 'd')
+ var t2 = a2 + b2
+ assert_equal(('a', 'b', 1, 'c', 'd'), t2)
+
+ var a3: tuple<...list<string>> = ('a', 'b')
+ var b3: tuple<...list<string>> = ('c', 'd')
+ var t3 = a3 + b3
+ assert_equal(('a', 'b', 'c', 'd'), t3)
+
+ var a4: tuple<...list<number>> = (1, 2)
+ var t4 = a4 + ()
+ assert_equal((1, 2), t4)
+
+ var b5: tuple<...list<number>> = (1, 2)
+ var t5 = () + b5
+ assert_equal((1, 2), t5)
+
+ var a6: tuple<...list<number>> = (1, 2)
+ var t6 = a6 + null_tuple
+ assert_equal((1, 2), t6)
+
+ var b7: tuple<...list<string>> = ('a', 'b')
+ var t7 = null_tuple + b7
+ assert_equal(('a', 'b'), t7)
+ END
+ call v9.CheckSourceDefAndScriptSuccess(lines)
+
+ let lines =<< trim END
+ VAR t = test_null_tuple() + test_null_tuple()
+ call assert_equal(test_null_tuple(), t)
+ END
+ call v9.CheckSourceLegacyAndVim9Success(lines)
+
+ let lines =<< trim END
+ vim9script
+ def Fn(x: any, y: any): any
+ return x + y
+ enddef
+ assert_equal((1, 2), Fn((1,), (2,)))
+ assert_equal((1, 'a'), Fn((1,), ('a',)))
+ assert_equal((1,), Fn((1,), null_tuple))
+ assert_equal(('a',), Fn(null_tuple, ('a',)))
+ assert_equal((), Fn(null_tuple, null_tuple))
+ END
+ call v9.CheckSourceScriptSuccess(lines)
+
+ " Test for concatenating to lists containing tuples
+ let lines =<< trim END
+ var x = [test_null_tuple()] + [test_null_tuple()]
+ assert_equal([(), ()], x)
+ var y = [()] + [()]
+ assert_equal([(), ()], y)
+ END
+ call v9.CheckSourceDefAndScriptSuccess(lines)
+endfunc
+
+" Test for comparing tuples
+func Test_tuple_compare()
+ let lines =<< trim END
+ call assert_false((1, 2) == (1, 3))
+ call assert_true((1, 2) == (1, 2))
+ call assert_true((1,) == (1,))
+ call assert_true(() == ())
+ call assert_false((1, 2) == (1, 2, 3))
+ call assert_false((1, 2) == test_null_tuple())
+ VAR t1 = (1, 2)
+ VAR t2 = t1
+ call assert_true(t1 == t2)
+ END
+ call v9.CheckSourceLegacyAndVim9Success(lines)
+
+ let lines =<< trim END
+ echo (1.0, ) == 1.0
+ END
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ 'E1517: Can only compare Tuple with Tuple',
+ \ 'E1072: Cannot compare tuple with float',
+ \ 'E1072: Cannot compare tuple with float'])
+
+ let lines =<< trim END
+ echo 1.0 == (1.0,)
+ END
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ 'E1517: Can only compare Tuple with Tuple',
+ \ 'E1072: Cannot compare float with tuple',
+ \ 'E1072: Cannot compare float with tuple'])
+
+ let lines =<< trim END
+ echo (1, 2) =~ []
+ END
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ 'E691: Can only compare List with List',
+ \ 'E1072: Cannot compare tuple with list',
+ \ 'E1072: Cannot compare tuple with list'])
+
+ let lines =<< trim END
+ echo (1, 2) =~ (1, 2)
+ END
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ 'E1518: Invalid operation for Tuple',
+ \ 'E1518: Invalid operation for Tuple',
+ \ 'E1518: Invalid operation for Tuple'])
+endfunc
+
+" Test for assigning multiple items from a tuple
+func Test_multi_assign_from_tuple()
+ let lines =<< trim END
+ VAR [v1, v2] = ('a', 'b')
+ call assert_equal(['a', 'b'], [v1, v2])
+
+ VAR [v3] = ('c',)
+ call assert_equal('c', v3)
+
+ VAR [v4; v5] = ('a', 'b', 'c')
+ call assert_equal('a', v4)
+ call assert_equal(('b', 'c'), v5)
+
+ VAR [v6; v7] = ('a',)
+ call assert_equal('a', v6)
+ call assert_equal((), v7)
+
+ VAR sum = 0
+ for [v8, v9] in ((2, 2), (2, 3))
+ LET sum += v8 * v9
+ endfor
+ call assert_equal(10, sum)
+
+ #" for: rest of the items in a List
+ LET sum = 0
+ for [v10; v11] in ((2, 1, 2, 5), (2, 1, 2, 10))
+ LET sum += v10 * max(v11)
+ endfor
+ call assert_equal(30, sum)
+
+ #" for: one item in the list
+ LET sum = 0
+ for [v12; v13] in ((2, 6), (2, 7))
+ LET sum += v12 * max(v13)
+ endfor
+ call assert_equal(26, sum)
+
+ #" for: zero items in the list
+ LET sum = 0
+ for [v14; v15] in ((4,), (5,))
+ LET sum += v14 + max(v15)
+ endfor
+ call assert_equal(9, sum)
+
+ #" A null tuple should be treated like an empty tuple
+ for [v16, v17] in test_null_tuple()
+ endfor
+ END
+ call v9.CheckSourceLegacyAndVim9Success(lines)
+
+ let lines =<< trim END
+ var t: tuple<...list<number>> = (4, 8)
+ var [x: number, y: number] = t
+ assert_equal([4, 8], [x, y])
+ END
+ call v9.CheckSourceDefAndScriptSuccess(lines)
+
+ " Test a mix lists and tuples with "any" type
+ let lines =<< trim END
+ vim9script
+ def Fn(x: any): string
+ var str = ''
+ for [a, b] in x
+ str ..= a .. b
+ endfor
+ return str
+ enddef
+ # List of lists
+ assert_equal('abcd', Fn([['a', 'b'], ['c', 'd']]))
+ # List of tuples
+ assert_equal('abcd', Fn([('a', 'b'), ('c', 'd')]))
+ # Tuple of lists
+ assert_equal('abcd', Fn((['a', 'b'], ['c', 'd'])))
+ # Tuple of tuples
+ assert_equal('abcd', Fn((('a', 'b'), ('c', 'd'))))
+ END
+ call v9.CheckSourceSuccess(lines)
+
+ let lines =<< trim END
+ VAR [v1, v2] = ('a', 'b', 'c')
+ END
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ 'E1537: Less targets than Tuple items',
+ \ 'E1093: Expected 2 items but got 3',
+ \ 'E1537: Less targets than Tuple items'])
+
+ let lines =<< trim END
+ VAR [v1, v2, v3] = ('a', 'b')
+ END
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ 'E1538: More targets than Tuple items',
+ \ 'E1093: Expected 3 items but got 2',
+ \ 'E1538: More targets than Tuple items'])
+
+ let lines =<< trim END
+ VAR [v1; v2] = test_null_tuple()
+ END
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ 'E1536: Tuple required',
+ \ 'E1093: Expected 1 items but got 0',
+ \ 'E1536: Tuple required'])
+
+ let lines =<< trim END
+ for [v1, v2] in (('a', 'b', 'c'),)
+ endfor
+ END
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ 'E1537: Less targets than Tuple items',
+ \ 'E1537: Less targets than Tuple items',
+ \ 'E1537: Less targets than Tuple items'])
+
+ let lines =<< trim END
+ for [v1, v2] in (('a',),)
+ endfor
+ END
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ 'E1538: More targets than Tuple items',
+ \ 'E1538: More targets than Tuple items',
+ \ 'E1538: More targets than Tuple items'])
+
+ let lines =<< trim END
+ for [v1, v2] in (test_null_tuple(),)
+ endfor
+ END
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ 'E1536: Tuple required',
+ \ 'E1538: More targets than Tuple items',
+ \ 'E1536: Tuple required'])
+
+ let lines =<< trim END
+ for [v1; v2] in (test_null_tuple(),)
+ endfor
+ END
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ 'E1536: Tuple required',
+ \ 'E1538: More targets than Tuple items',
+ \ 'E1536: Tuple required'])
+
+ " List assignment errors using a function tuple argument
+ let lines =<< trim END
+ vim9script
+ def Fn(x: tuple<...list<number>>)
+ var [a, b] = x
+ enddef
+ Fn((1, 2, 3))
+ END
+ call v9.CheckSourceFailure(lines, 'E1093: Expected 2 items but got 3')
+
+ let lines =<< trim END
+ vim9script
+ def Fn(x: tuple<number>)
+ var [a, b] = x
+ enddef
+ Fn((1,))
+ END
+ call v9.CheckSourceFailure(lines, 'E1093: Expected 2 items but got 1')
+
+ let lines =<< trim END
+ vim9script
+ def Fn(x: tuple<number>)
+ var [a, b] = x
+ enddef
+ Fn(null_tuple)
+ END
+ call v9.CheckSourceFailure(lines, 'E1093: Expected 2 items but got 0')
+endfunc
+
+" Test for performing an arithmetic operation on multiple variables using
+" items from a tuple
+func Test_multi_arithmetic_op_from_tuple()
+ let lines =<< trim END
+ VAR x = 10
+ VAR y = 10
+ LET [x, y] += (2, 4)
+ call assert_equal([12, 14], [x, y])
+ LET [x, y] -= (4, 2)
+ call assert_equal([8, 12], [x, y])
+ LET [x, y] *= (2, 3)
+ call assert_equal([16, 36], [x, y])
+ LET [x, y] /= (4, 2)
+ call assert_equal([4, 18], [x, y])
+ LET [x, y] %= (3, 5)
+ call assert_equal([1, 3], [x, y])
+ END
+ call v9.CheckSourceLegacyAndVim9Success(lines)
+
+ " The "." operator is supported only in Vim script
+ let lines =<< trim END
+ let x = 'a'
+ let y = 'b'
+ let [x, y] .= ('a', 'b')
+ call assert_equal(['aa', 'bb'], [x, y])
+ END
+ call v9.CheckSourceSuccess(lines)
+
+ let lines =<< trim END
+ VAR x = 'a'
+ VAR y = 'b'
+ LET [x, y] ..= ('a', 'b')
+ call assert_equal(('aa', 'bb'), (x, y))
+ END
+ call v9.CheckSourceLegacyAndVim9Success(lines)
+endfunc
+
+" Test for using a tuple in a for statement
+func Test_tuple_for()
+ let lines =<< trim END
+ VAR sum = 0
+ for v1 in (1, 3, 5)
+ LET sum += v1
+ endfor
+ call assert_equal(9, sum)
+
+ LET sum = 0
+ for v2 in ()
+ LET sum += v2
+ endfor
+ call assert_equal(0, sum)
+
+ LET sum = 0
+ for v2 in test_null_tuple()
+ LET sum += v2
+ endfor
+ call assert_equal(0, sum)
+ END
+ call v9.CheckSourceLegacyAndVim9Success(lines)
+
+ " ignoring the for loop assignment using '_'
+ let lines =<< trim END
+ vim9script
+ var count = 0
+ for _ in (1, 2, 3)
+ count += 1
+ endfor
+ assert_equal(3, count)
+ END
+ call v9.CheckSourceSuccess(lines)
+
+ let lines =<< trim END
+ var sum = 0
+ for v in null_tuple
+ sum += v
+ endfor
+ assert_equal(0, sum)
+ END
+ call v9.CheckSourceDefAndScriptSuccess(lines)
+
+ let lines =<< trim END
+ vim9script
+ def Foo()
+ for x in ((1, 2), (3, 4))
+ endfor
+ enddef
+ Foo()
+ END
+ call v9.CheckSourceSuccess(lines)
+
+ " Test for assigning multiple items from a tuple in a for loop
+ let lines =<< trim END
+ vim9script
+ def Fn()
+ for [x, y] in ([1, 2],)
+ assert_equal([1, 2], [x, y])
+ endfor
+ enddef
+ defcompile
+ Fn()
+ END
+ call v9.CheckSourceSuccess(lines)
+
+ " iterate over tuple<...list<number>
+ let lines =<< trim END
+ vim9script
+ def Fn()
+ var t: tuple<...list<number>> = (1, 2)
+ var sum = 0
+ for i: number in t
+ sum += i
+ endfor
+ assert_equal(3, sum)
+ enddef
+ Fn()
+ END
+ call v9.CheckSourceSuccess(lines)
+
+ " iterate over tuple<...list<list<number>>>
+ let lines =<< trim END
+ vim9script
+ def Fn()
+ var t: tuple<...list<list<number>>> = ([1, 2], [3, 4])
+ var sum = 0
+ for [x: number, y: number] in t
+ sum += x + y
+ endfor
+ assert_equal(10, sum)
+ enddef
+ Fn()
+ END
+ call v9.CheckSourceSuccess(lines)
+
+ " iterate over tuple<...list<tuple<...list<number>>>>
+ let lines =<< trim END
+ vim9script
+ def Fn()
+ var t: tuple<...list<tuple<...list<number>>>> = ((1, 2), (3, 4))
+ var sum = 0
+ for [x: number, y: number] in t
+ sum += x + y
+ endfor
+ assert_equal(10, sum)
+ enddef
+ Fn()
+ END
+ call v9.CheckSourceSuccess(lines)
+
+ " iterate over tuple<...list<list<number>>>
+ let lines =<< trim END
+ vim9script
+ def Fn()
+ var t: tuple<...list<list<number>>> = ([1, 2], [3, 4])
+ var sum = 0
+ for [x: number, y: number] in t
+ sum += x + y
+ endfor
+ assert_equal(10, sum)
+ enddef
+ Fn()
+ END
+ call v9.CheckSourceSuccess(lines)
+
+ " iterate over a tuple<...list<any>>
+ let lines =<< trim END
+ vim9script
+ def Fn()
+ var t: tuple<...list<any>> = (1, 'x', true, [], {}, ())
+ var str = ''
+ for v in t
+ str ..= string(v)
+ endfor
+ assert_equal("1'x'true[]{}()", str)
+ enddef
+ Fn()
+ END
+ call v9.CheckSourceSuccess(lines)
+
+ " use multiple variable assignment syntax with a tuple<...list<number>>
+ let lines =<< trim END
+ vim9script
+ def Fn()
+ var t: tuple<...list<number>> = (1, 2, 3)
+ for [i] in t
+ endfor
+ enddef
+ Fn()
+ END
+ call v9.CheckSourceFailure(lines, 'E1140: :for argument must be a sequence of lists or tuples', 2)
+endfunc
+
+" Test for checking the tuple type in assignment and return value
+func Test_tuple_type_check()
+ let lines =<< trim END
+ var t: tuple<...list<number>> = ('a', 'b')
+ END
+ call v9.CheckSourceDefFailure(lines, 'E1012: Type mismatch; expected tuple<...list<number>> but got tuple<string, string>', 1)
+
+ let lines =<< trim END
+ var t1: tuple<...list<string>> = ('a', 'b')
+ assert_equal(('a', 'b'), t1)
+ var t2 = (1, 2)
+ assert_equal((1, 2), t2)
+ var t = null_tuple
+ assert_equal(null_tuple, t)
+ t = test_null_tuple()
+ assert_equal(test_null_tuple(), t)
+ END
+ call v9.CheckSourceDefAndScriptSuccess(lines)
+
+ let lines =<< trim END
+ var t = ('a', 'b')
+ t = (1, 2)
+ END
+ call v9.CheckSourceDefFailure(lines, 'E1012: Type mismatch; expected tuple<string, string> but got tuple<number, number>', 2)
+
+ let lines =<< trim END
+ var t: tuple<number> = []
+ END
+ call v9.CheckSourceDefFailure(lines, 'E1012: Type mismatch; expected tuple<number> but got list<any>', 1)
+
+ let lines =<< trim END
+ var t: tuple<number> = {}
+ END
+ call v9.CheckSourceDefFailure(lines, 'E1012: Type mismatch; expected tuple<number> but got dict<any>', 1)
+
+ let lines =<< trim END
+ var l: list<number> = (1, 2)
+ END
+ call v9.CheckSourceDefFailure(lines, 'E1012: Type mismatch; expected list<number> but got tuple<number, number>', 1)
+
+ let lines =<< trim END
+ vim9script
+ def Fn(): tuple<...list<tuple<...list<string>>>>
+ return ((1, 2), (3, 4))
+ enddef
+ defcompile
+ END
+ call v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected tuple<...list<tuple<...list<string>>>> but got tuple<tuple<number, number>, tuple<number, number>>', 1)
+
+ let lines =<< trim END
+ var t: tuple<number> = ()
+ END
+ call v9.CheckSourceDefSuccess(lines)
+
+ let lines =<< trim END
+ vim9script
+ def Fn(): tuple<tuple<string>>
+ return ()
+ enddef
+ defcompile
+ END
+ call v9.CheckSourceSuccess(lines)
+
+ let lines =<< trim END
+ vim9script
+ def Fn(t: tuple<...list<number>>)
+ enddef
+ Fn(('a', 'b'))
+ END
+ call v9.CheckSourceFailure(lines, 'E1013: Argument 1: type mismatch, expected tuple<...list<number>> but got tuple<string, string>')
+
+ let lines =<< trim END
+ var t: any = (1, 2)
+ t = ('a', 'b')
+ END
+ call v9.CheckSourceDefSuccess(lines)
+
+ let lines =<< trim END
+ var t: tuple<...list<any>> = (1, 2)
+ t = ('a', 'b')
+ END
+ call v9.CheckSourceDefSuccess(lines)
+
+ let lines =<< trim END
+ var nll: tuple<list<number>> = ([1, 2],)
+ nll->copy()[0]->extend(['x'])
+ END
+ call v9.CheckSourceDefAndScriptFailure(lines, [
+ \ 'E1013: Argument 2: type mismatch, expected list<number> but got list<string>',
+ \ 'E1013: Argument 2: type mismatch, expected list<number> but got list<string> in extend()'])
+
+ let lines =<< trim END
+ vim9script
+ def Fn(y: tuple<number, ...list<bool>>)
+ var x: tuple<number, ...list<string>>
+ x = y
+ enddef
+
+ var t: tuple<number, ...list<bool>> = (1, true, false)
+ Fn(t)
+ END
+ call v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected tuple<number, ...list<string>> but got tuple<number, ...list<bool>>')
+endfunc
+
+" Test for setting the type of a script variable to tuple
+func Test_tuple_scriptvar_type()
+ " Uninitialized script variable should retain the type
+ let lines =<< trim END
+ vim9script
+ var foobar: tuple<list<string>>
+ def Foo()
+ var x = foobar
+ assert_equal('tuple<list<string>>', typename(x))
+ enddef
+ Foo()
+ END
+ call v9.CheckSourceScriptSuccess(lines)
+
+ " Initialized script variable should retain the type
+ let lines =<< trim END
+ vim9script
+ var foobar: tuple<...list<string>> = ('a', 'b')
+ def Foo()
+ var x = foobar
+ assert_equal('tuple<...list<string>>', typename(x))
+ enddef
+ Foo()
+ END
+ call v9.CheckSourceScriptSuccess(lines)
+endfunc
+
+" Test for modifying a tuple
+func Test_tuple_modify()
+ let lines =<< trim END
+ var t = (1, 2)
+ t[0] = 3
+ END
+ call v9.CheckSourceDefAndScriptFailure(lines, ['E1532: Cannot modify a tuple', 'E1532: Cannot modify a tuple'])
+endfunc
+
+def Test_using_null_tuple()
+ var lines =<< trim END
+ var x = null_tuple
+ assert_true(x is null_tuple)
+ var y = copy(x)
+ assert_true(y is null_tuple)
+ call assert_true((1, 2) != null_tuple)
+ call assert_true(null_tuple != (1, 2))
+ assert_equal(0, count(null_tuple, 'xx'))
+ var z = deepcopy(x)
+ assert_true(z is null_tuple)
+ assert_equal(1, empty(x))
+ assert_equal('xx', get(x, 0, 'xx'))
+ assert_equal(-1, index(null_tuple, 10))
+ assert_equal(-1, indexof(null_tuple, 'v:val == 2'))
+ assert_equal('', join(null_tuple))
+ assert_equal(0, len(x))
+ assert_equal(0, min(null_tuple))
+ assert_equal(0, max(null_tuple))
+ assert_equal((), repeat(null_tuple, 3))
+ assert_equal((), reverse(null_tuple))
+ assert_equal((), slice(null_tuple, 0, 0))
+ assert_equal('()', string(x))
+ assert_equal('tuple<any>', typename(x))
+ assert_equal(17, type(x))
+ END
+ v9.CheckSourceDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ # An uninitialized tuple is not equal to null
+ var t1: tuple<any>
+ assert_true(t1 != null)
+
+ # An empty tuple is equal to null_tuple but not equal to null
+ var t2: tuple<any> = ()
+ assert_true(t2 == null_tuple)
+ assert_true(t2 != null)
+
+ # null_tuple is equal to null
+ assert_true(null_tuple == null)
+ END
+ v9.CheckSourceDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var x = null_tupel
+ END
+ v9.CheckSourceDefAndScriptFailure(lines, [
+ \ 'E1001: Variable not found: null_tupel',
+ \ 'E121: Undefined variable: null_tupel'])
+enddef
+
+" Test for modifying a mutable item in a tuple
+func Test_tuple_modify_mutable_item()
+ let lines =<< trim END
+ VAR t = ('a', ['b', 'c'], {'a': 10, 'b': 20})
+ LET t[1][1] = 'x'
+ LET t[2].a = 30
+ call assert_equal(('a', ['b', 'x'], {'a': 30, 'b': 20}), t)
+ END
+ call v9.CheckSourceLegacyAndVim9Success(lines)
+
+ let lines =<< trim END
+ VAR t = ('a', (['b'], 'c'))
+ LET t[1][0][0] = 'x'
+ call assert_equal(('a', (['x'], 'c')), t)
+ END
+ call v9.CheckSourceLegacyAndVim9Success(lines)
+
+ " Use a negative index
+ let lines =<< trim END
+ VAR t = ([1, 2], [3])
+ LET t[-2][-2] = 5
+ call assert_equal(([5, 2], [3]), t)
+ END
+ call v9.CheckSourceLegacyAndVim9Success(lines)
+
+ let lines =<< trim END
+ VAR t = ('a', ('b', 'c'))
+ LET t[1][0] = 'x'
+ END
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ 'E1532: Cannot modify a tuple',
+ \ 'E1532: Cannot modify a tuple',
+ \ 'E1532: Cannot modify a tuple'])
+
+ let lines =<< trim END
+ VAR t = ['a', ('b', 'c')]
+ LET t[1][0] = 'x'
+ END
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ 'E1532: Cannot modify a tuple',
+ \ 'E1532: Cannot modify a tuple',
+ \ 'E1532: Cannot modify a tuple'])
+
+ let lines =<< trim END
+ VAR t = {'a': ('b', 'c')}
+ LET t['a'][0] = 'x'
+ END
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ 'E1532: Cannot modify a tuple',
+ \ 'E1532: Cannot modify a tuple',
+ \ 'E1532: Cannot modify a tuple'])
+
+ let lines =<< trim END
+ VAR t = {'a': ['b', ('c',)]}
+ LET t['a'][1][0] = 'x'
+ END
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ 'E1532: Cannot modify a tuple',
+ \ 'E1532: Cannot modify a tuple',
+ \ 'E1532: Cannot modify a tuple'])
+
+ let lines =<< trim END
+ let t = ('a', 'b', 'c', 'd')
+ let t[1 : 2] = ('x', 'y')
+ END
+ call v9.CheckSourceFailure(lines, 'E1533: Cannot slice a tuple')
+
+ let lines =<< trim END
+ var t: tuple<...list<string>> = ('a', 'b', 'c', 'd')
+ t[1 : 2] = ('x', 'y')
+ END
+ call v9.CheckSourceDefAndScriptFailure(lines, [
+ \ 'E1533: Cannot slice a tuple',
+ \ 'E1533: Cannot slice a tuple'])
+
+ let lines =<< trim END
+ var t: tuple<...list<string>> = ('a', 'b', 'c', 'd')
+ t[ : 2] = ('x', 'y')
+ END
+ call v9.CheckSourceDefAndScriptFailure(lines, [
+ \ 'E1533: Cannot slice a tuple',
+ \ 'E1533: Cannot slice a tuple'])
+
+ let lines =<< trim END
+ let t = ('a', 'b', 'c', 'd')
+ let t[ : ] = ('x', 'y')
+ END
+ call v9.CheckSourceFailure(lines, 'E1533: Cannot slice a tuple')
+
+ let lines =<< trim END
+ var t: tuple<...list<string>> = ('a', 'b', 'c', 'd')
+ t[ : ] = ('x', 'y')
+ END
+ call v9.CheckSourceDefAndScriptFailure(lines, [
+ \ 'E1533: Cannot slice a tuple',
+ \ 'E1533: Cannot slice a tuple'])
+
+ let lines =<< trim END
+ VAR t = ('abc',)
+ LET t[0][1] = 'x'
+ END
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ "E689: Index not allowed after a string: t[0][1] = 'x'",
+ \ 'E1148: Cannot index a string',
+ \ "E689: Index not allowed after a string: t[0][1] = 'x'"])
+
+ " Out of range indexing
+ let lines =<< trim END
+ VAR t = ([1, 2], [3])
+ LET t[2][0] = 5
+ END
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ 'E1519: Tuple index out of range: 2',
+ \ 'E1519: Tuple index out of range: 2',
+ \ 'E1519: Tuple index out of range: 2'])
+
+ let lines =<< trim END
+ VAR t = ([1, 2], [3])
+ LET t[-3][0] = 5
+ END
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ 'E1519: Tuple index out of range: -3',
+ \ 'E1519: Tuple index out of range: -3',
+ \ 'E1519: Tuple index out of range: -3'])
+
+ " Use a null tuple
+ let lines =<< trim END
+ VAR t = test_null_tuple()
+ LET t[0][0] = 5
+ END
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ 'E1519: Tuple index out of range: 0',
+ \ 'E1519: Tuple index out of range: 0',
+ \ 'E1519: Tuple index out of range: 0'])
+endfunc
+
+" Test for locking and unlocking a tuple variable
+func Test_tuple_lock()
+ let lines =<< trim END
+ VAR t = ([0, 1],)
+ call add(t[0], 2)
+ call assert_equal(([0, 1, 2], ), t)
+ END
+ call v9.CheckSourceLegacyAndVim9Success(lines)
+
+ let lines =<< trim END
+ VAR t = ([0, 1],)
+ lockvar 2 t
+ call add(t[0], 2)
+ END
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ 'E741: Value is locked: add() argument',
+ \ 'E1178: Cannot lock or unlock a local variable',
+ \ 'E741: Value is locked: add() argument'])
+
+ let lines =<< trim END
+ LET g:t = ([0, 1],)
+ lockvar 2 g:t
+ unlockvar 2 g:t
+ call add(g:t[0], 3)
+ call assert_equal(([0, 1, 3], ), g:t)
+ unlet g:t
+ END
+ call v9.CheckSourceLegacyAndVim9Success(lines)
+
+ let lines =<< trim END
+ VAR t1 = (1, 2)
+ const t2 = t1
+ LET t2 = ()
+ END
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ 'E741: Value is locked: t2',
+ \ 'E1018: Cannot assign to a constant: t2',
+ \ 'E46: Cannot change read-only variable "t2"'])
+endfunc
+
+" Test for using a class as a tuple item
+func Test_tuple_use_class_item()
+ let lines =<< trim END
+ vim9script
+ class A
+ endclass
+ var t = (A,)
+ END
+ call v9.CheckSourceScriptFailure(lines, 'E1405: Class "A" cannot be used as a value', 4)
+
+ let lines =<< trim END
+ vim9script
+ class A
+ endclass
+ var t = ('a', A)
+ END
+ call v9.CheckSourceScriptFailure(lines, 'E1405: Class "A" cannot be used as a value', 4)
+
+ let lines =<< trim END
+ vim9script
+ class A
+ endclass
+ def Fn()
+ var t = (A,)
+ enddef
+ defcompile
+ END
+ call v9.CheckSourceScriptFailure(lines, 'E1405: Class "A" cannot be used as a value', 1)
+
+ let lines =<< trim END
+ vim9script
+ class A
+ endclass
+ def Fn()
+ var t = ('a', A)
+ enddef
+ defcompile
+ END
+ call v9.CheckSourceScriptFailure(lines, 'E1405: Class "A" cannot be used as a value', 1)
+endfunc
+
+" Test for using a user-defined type as a tuple item
+func Test_tuple_user_defined_type_as_item()
+ let lines =<< trim END
+ vim9script
+ type N = number
+ var t = (N,)
+ END
+ call v9.CheckSourceScriptFailure(lines, 'E1403: Type alias "N" cannot be used as a value', 3)
+
+ let lines =<< trim END
+ vim9script
+ type N = number
+ var t = ('a', N)
+ END
+ call v9.CheckSourceScriptFailure(lines, 'E1403: Type alias "N" cannot be used as a value', 3)
+
+ let lines =<< trim END
+ vim9script
+ type N = number
+ def Fn()
+ var t = (N,)
+ enddef
+ defcompile
+ END
+ call v9.CheckSourceScriptFailure(lines, 'E1407: Cannot use a Typealias as a variable or value', 1)
+
+ let lines =<< trim END
+ vim9script
+ type N = number
+ def Fn()
+ var t = ('a', N)
+ enddef
+ defcompile
+ END
+ call v9.CheckSourceScriptFailure(lines, 'E1407: Cannot use a Typealias as a variable or value', 1)
+endfunc
+
+" Test for using a tuple as a function argument
+func Test_tuple_func_arg()
+ let lines =<< trim END
+ vim9script
+ def Fn(t: tuple<...list<string>>): tuple<...list<string>>
+ return t[:]
+ enddef
+ var r1 = Fn(('a', 'b'))
+ assert_equal(('a', 'b'), r1)
+ var r2 = Fn(('a',))
+ assert_equal(('a',), r2)
+ var r3 = Fn(())
+ assert_equal((), r3)
+ var r4 = Fn(null_tuple)
+ assert_equal((), r4)
+ END
+ call v9.CheckSourceScriptSuccess(lines)
+
+ func TupleArgFunc(t)
+ return a:t[:]
+ endfunc
+ let r = TupleArgFunc(('a', 'b'))
+ call assert_equal(('a', 'b'), r)
+ let r = TupleArgFunc(('a',))
+ call assert_equal(('a',), r)
+ let r = TupleArgFunc(())
+ call assert_equal((), r)
+ let r = TupleArgFunc(test_null_tuple())
+ call assert_equal((), r)
+ delfunc TupleArgFunc
+endfunc
+
+" Test for tuple identity
+func Test_tuple_identity()
+ let lines =<< trim END
+ call assert_false((1, 2) is (1, 2))
+ call assert_true((1, 2) isnot (1, 2))
+ call assert_true((1, 2) isnot test_null_tuple())
+ VAR t1 = ('abc', 'def')
+ VAR t2 = t1
+ call assert_true(t2 is t1)
+ VAR t3 = (1, 2)
+ call assert_false(t3 is t1)
+ END
+ call v9.CheckSourceLegacyAndVim9Success(lines)
+endfunc
+
+" Test for using a compound op with a tuple
+func Test_tuple_compound_op()
+ let lines =<< trim END
+ VAR t = (1, 2)
+ LET t += (3,)
+ END
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ 'E734: Wrong variable type for +=',
+ \ 'E734: Wrong variable type for +=',
+ \ 'E734: Wrong variable type for +='])
+
+ for op in ['-', '*', '/', '%']
+ let lines =<< trim eval END
+ VAR t = (1, 2)
+ LET t {op}= (3,)
+ END
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ $'E734: Wrong variable type for {op}=',
+ \ $'E734: Wrong variable type for {op}=',
+ \ $'E734: Wrong variable type for {op}='])
+ endfor
+
+ let lines =<< trim END
+ VAR t = (1, 2)
+ LET t ..= (3,)
+ END
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ 'E734: Wrong variable type for .=',
+ \ 'E1019: Can only concatenate to string',
+ \ 'E734: Wrong variable type for .='])
+endfunc
+
+" Test for using the falsy operator with tuple
+func Test_tuple_falsy_op()
+ let lines =<< trim END
+ VAR t = test_null_tuple()
+ call assert_equal('null tuple', t ?? 'null tuple')
+ END
+ call v9.CheckSourceLegacyAndVim9Success(lines)
+endfunc
+
+" Test for tuple typecasting
+def Test_tuple_typecast()
+ var lines =<< trim END
+ var x = <tuple<number>>('a', 'b')
+ END
+ v9.CheckSourceDefAndScriptFailure(lines, [
+ \ 'E1012: Type mismatch; expected tuple<number> but got tuple<string, string>',
+ \ 'E1012: Type mismatch; expected tuple<number> but got tuple<string, string>'])
+enddef
+
+" Test for using a tuple in string interpolation
+def Test_tuple_string_interop()
+ var lines =<< trim END
+ VAR emptytuple = ()
+ call assert_equal("a()b", $'a{emptytuple}b')
+ VAR nulltuple = test_null_tuple()
+ call assert_equal("a()b", $'a{nulltuple}b')
+
+ #" Tuple interpolation
+ VAR t = ('a', 'b', 'c')
+ call assert_equal("x('a', 'b', 'c')x", $'x{t}x')
+ END
+ v9.CheckSourceLegacyAndVim9Success(lines)
+
+ lines =<< trim END
+ call assert_equal("a()b", $'a{null_tuple}b')
+ END
+ v9.CheckSourceDefAndScriptSuccess(lines)
+
+ #" Tuple evaluation in heredoc
+ lines =<< trim END
+ VAR t1 = ('a', 'b', 'c')
+ VAR data =<< eval trim DATA
+ let x = {t1}
+ DATA
+ call assert_equal(["let x = ('a', 'b', 'c')"], data)
+ END
+ v9.CheckSourceLegacyAndVim9Success(lines)
+
+ #" Empty tuple evaluation in heredoc
+ lines =<< trim END
+ VAR t1 = ()
+ VAR data =<< eval trim DATA
+ let x = {t1}
+ DATA
+ call assert_equal(["let x = ()"], data)
+ END
+ v9.CheckSourceLegacyAndVim9Success(lines)
+
+ #" Null tuple evaluation in heredoc
+ lines =<< trim END
+ VAR t1 = test_null_tuple()
+ VAR data =<< eval trim DATA
+ let x = {t1}
+ DATA
+ call assert_equal(["let x = ()"], data)
+ END
+ v9.CheckSourceLegacyAndVim9Success(lines)
+
+ lines =<< trim END
+ var t1 = null_tuple
+ var data =<< eval trim DATA
+ let x = {t1}
+ DATA
+ call assert_equal(["let x = ()"], data)
+ END
+ v9.CheckSourceDefAndScriptSuccess(lines)
+enddef
+
+" Test for a return in "finally" block overriding the tuple return value in a
+" try block.
+func Test_try_finally_with_tuple_return()
+ let lines =<< trim END
+ func s:Fn()
+ try
+ return (1, 2)
+ finally
+ return (3, 4)
+ endtry
+ endfunc
+ call assert_equal((3, 4), s:Fn())
+ delfunc s:Fn
+ END
+ call v9.CheckSourceSuccess(lines)
+
+ let lines =<< trim END
+ vim9script
+ def Fn(): tuple<...list<number>>
+ try
+ return (1, 2)
+ finally
+ return (3, 4)
+ endtry
+ enddef
+ assert_equal((3, 4), Fn())
+ END
+ call v9.CheckSourceSuccess(lines)
+endfunc
+
+" Test for add() with a tuple
+func Test_tuple_add()
+ let lines =<< trim END
+ VAR t = (1, 2)
+ call add(t, 3)
+ END
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ 'E897: List or Blob required',
+ \ 'E1013: Argument 1: type mismatch, expected list<any> but got tuple<number, number>',
+ \ 'E1226: List or Blob required for argument 1'])
+endfunc
+
+" Test for copy()
+func Test_tuple_copy()
+ let lines =<< trim END
+ VAR t1 = (['a', 'b'], ['c', 'd'], ['e', 'f'])
+ VAR t2 = copy(t1)
+ VAR t3 = t1
+ call assert_false(t2 is t1)
+ call assert_true(t3 is t1)
+ call assert_true(t2[1] is t1[1])
+ call assert_equal((), copy(()))
+ call assert_equal((), copy(test_null_tuple()))
+ END
+ call v9.CheckSourceLegacyAndVim9Success(lines)
+endfunc
+
+" Test for count()
+func Test_tuple_count()
+ let lines =<< trim END
+ VAR t = ('ab', 'cd', 'ab')
+ call assert_equal(2, count(t, 'ab'))
+ call assert_equal(0, count(t, 'xx'))
+ call assert_equal(0, count((), 'xx'))
+ call assert_equal(0, count(test_null_tuple(), 'xx'))
+ call assert_fails("call count((1, 2), 1, v:true, 2)", 'E1519: Tuple index out of range: 2')
+ END
+ call v9.CheckSourceLegacyAndVim9Success(lines)
+endfunc
+
+" Test for deepcopy()
+func Test_tuple_deepcopy()
+ let lines =<< trim END
+ VAR t1 = (['a', 'b'], ['c', 'd'], ['e', 'f'])
+ VAR t2 = deepcopy(t1)
+ VAR t3 = t1
+ call assert_false(t2 is t1)
+ call assert_true(t3 is t1)
+ call assert_false(t2[1] is t1[1])
+ call assert_equal((), deepcopy(()))
+ call assert_equal((), deepcopy(test_null_tuple()))
+
+ #" copy a recursive tuple
+ VAR l = []
+ VAR tuple = (l,)
+ call add(l, tuple)
+ call assert_equal('([(...)], )', string(deepcopy(tuple)))
+ END
+ call v9.CheckSourceLegacyAndVim9Success(lines)
+endfunc
+
+" Test for empty()
+func Test_tuple_empty()
+ let lines =<< trim END
+ call assert_true(empty(()))
+ call assert_true(empty(test_null_tuple()))
+ call assert_false(empty((1, 2)))
+ VAR t = ('abc', 'def')
+ call assert_false(empty(t))
+ END
+ call v9.CheckSourceLegacyAndVim9Success(lines)
+endfunc
+
+" Test for eval()
+func Test_tuple_eval()
+ let lines =<< trim END
+ call assert_equal((), eval('()'))
+ call assert_equal(([],), eval('([],)'))
+ call assert_equal((1, 2, 3), eval('(1, 2, 3)'))
+ END
+ call v9.CheckSourceLegacyAndVim9Success(lines)
+endfunc
+
+" Test for extend() with a tuple
+func Test_tuple_extend()
+ let lines =<< trim END
+ VAR t = (1, 2, 3)
+ call extend(t, (4, 5))
+ call extendnew(t, (4, 5))
+ END
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ 'E712: Argument of extend() must be a List or Dictionary',
+ \ 'E1013: Argument 1: type mismatch, expected list<any> but got tuple<number, number, number>',
+ \ 'E712: Argument of extend() must be a List or Dictionary'])
+endfunc
+
+" Test for filter() with a tuple
+func Test_tuple_filter()
+ let lines =<< trim END
+ VAR t = (1, 2, 3)
+ call filter(t, 'v:val == 2')
+ END
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ 'E1524: Cannot use a tuple with function filter()',
+ \ 'E1013: Argument 1: type mismatch, expected list<any> but got tuple<number, number, number>',
+ \ 'E1524: Cannot use a tuple with function filter()'])
+endfunc
+
+" Test for flatten() with a tuple
+func Test_tuple_flatten()
+ let t = ([1, 2], [3, 4], [5, 6])
+ call assert_fails("call flatten(t, 2)", 'E686: Argument of flatten() must be a List')
+endfunc
+
+" Test for flattennew() with a tuple
+func Test_tuple_flattennew()
+ let lines =<< trim END
+ var t = ([1, 2], [3, 4], [5, 6])
+ flattennew(t, 2)
+ END
+ call v9.CheckSourceDefFailure(lines, 'E1013: Argument 1: type mismatch, expected list<any> but got tuple<list<number>, list<number>, list<number>>')
+endfunc
+
+" Test for foreach() with a tuple
+func Test_tuple_foreach()
+ let t = ('a', 'b', 'c')
+ let str = ''
+ call foreach(t, 'let str ..= v:val')
+ call assert_equal('abc', str)
+
+ let sum = 0
+ call foreach(test_null_tuple(), 'let sum += v:val')
+ call assert_equal(0, sum)
+
+ let lines =<< trim END
+ def Concatenate(k: number, v: string)
+ g:str ..= v
+ enddef
+ var t = ('a', 'b', 'c')
+ var str = 0
+ g:str = ''
+ call foreach(t, Concatenate)
+ call assert_equal('abc', g:str)
+
+ g:str = ''
+ call foreach(test_null_tuple(), Concatenate)
+ call assert_equal('', g:str)
+ END
+ call v9.CheckSourceDefAndScriptSuccess(lines)
+
+ let lines =<< trim END
+ LET g:sum = 0
+ call foreach((1, 2, 3), 'LET g:sum += x')
+ END
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ 'E121: Undefined variable: x',
+ \ 'E121: Undefined variable: x',
+ \ 'E121: Undefined variable: x'])
+endfunc
+
+" Test for get()
+func Test_tuple_get()
+ let lines =<< trim END
+ VAR t = (10, 20, 30)
+ for [i, v] in [[0, 10], [1, 20], [2, 30], [3, 0]]
+ call assert_equal(v, get(t, i))
+ endfor
+
+ for [i, v] in [[-1, 30], [-2, 20], [-3, 10], [-4, 0]]
+ call assert_equal(v, get(t, i))
+ endfor
+ call assert_equal(0, get((), 5))
+ call assert_equal('c', get(('a', 'b'), 2, 'c'))
+ call assert_equal('x', get(test_null_tuple(), 0, 'x'))
+ END
+ call v9.CheckSourceLegacyAndVim9Success(lines)
+endfunc
+
+" Test for id()
+func Test_tuple_id()
+ let lines =<< trim END
+ VAR t1 = (['a'], ['b'], ['c'])
+ VAR t2 = (['a'], ['b'], ['c'])
+ VAR t3 = t1
+ call assert_true(id(t1) != id(t2))
+ call assert_true(id(t1) == id(t3))
+ END
+ call v9.CheckSourceLegacyAndVim9Success(lines)
+endfunc
+
+" Test for index() function
+func Test_tuple_index_func()
+ let lines =<< trim END
+ VAR t = (88, 33, 99, 77)
+ call assert_equal(3, index(t, 77))
+ call assert_equal(2, index(t, 99, 1))
+ call assert_equal(2, index(t, 99, -4))
+ call assert_equal(2, index(t, 99, -5))
+ call assert_equal(-1, index(t, 66))
+ call assert_equal(-1, index(t, 77, 4))
+ call assert_equal(-1, index((), 8))
+ call assert_equal(-1, index(test_null_tuple(), 9))
+ END
+ call v9.CheckSourceLegacyAndVim9Success(lines)
+
+ let lines =<< trim END
+ VAR t = (88, 33, 99, 77)
+ call assert_equal(-1, index(t, 77, []))
+ END
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ 'E745: Using a List as a Number',
+ \ 'E1013: Argument 3: type mismatch, expected number but got list<any>',
+ \ 'E1210: Number required for argument 3'])
+
+ let lines =<< trim END
+ VAR t = (88,)
+ call assert_equal(-1, index(t, 77, 1, ()))
+ END
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ 'E1520: Using a Tuple as a Number',
+ \ 'E1013: Argument 4: type mismatch, expected bool but got tuple<any>',
+ \ 'E1212: Bool required for argument 4'])
+endfunc
+
+" Test for indexof()
+func Test_tuple_indexof()
+ let lines =<< trim END
+ VAR t = ('a', 'b', 'c', 'd')
+ call assert_equal(2, indexof(t, 'v:val =~ "c"'))
+ call assert_equal(2, indexof(t, 'v:val =~ "c"', {'startidx': 2}))
+ call assert_equal(-1, indexof(t, 'v:val =~ "c"', {'startidx': 3}))
+ call assert_equal(2, indexof(t, 'v:val =~ "c"', {'startidx': -3}))
+ call assert_equal(2, indexof(t, 'v:val =~ "c"', {'startidx': -6}))
+ call assert_equal(-1, indexof(t, 'v:val =~ "e"'))
+ call assert_equal(-1, indexof((), 'v:val == 1'))
+ call assert_equal(-1, indexof(test_null_tuple(), 'v:val == 2'))
+ END
+ call v9.CheckSourceLegacyAndVim9Success(lines)
+
+ func g:MyIndexOf(k, v)
+ echoerr 'MyIndexOf failed'
+ endfunc
+ let lines =<< trim END
+ VAR t = (1, 2, 3)
+ echo indexof(t, function('g:MyIndexOf'))
+ END
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ 'MyIndexOf failed',
+ \ 'MyIndexOf failed',
+ \ 'MyIndexOf failed'])
+ delfunc g:MyIndexOf
+endfunc
+
+" Test for insert()
+func Test_tuple_insert()
+ let lines =<< trim END
+ VAR t = (1, 2, 3)
+ call insert(t, 4)
+ call insert(t, 4, 2)
+ END
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ 'E899: Argument of insert() must be a List or Blob',
+ \ 'E1013: Argument 1: type mismatch, expected list<any> but got tuple<number, number, number>',
+ \ 'E1226: List or Blob required for argument 1'])
+endfunc
+
+" Test for items()
+func Test_tuple_items()
+ let lines =<< trim END
+ VAR t = ([], {}, ())
+ call assert_equal([[0, []], [1, {}], [2, ()]], items(t))
+ call assert_equal([[0, 1]], items((1, )))
+ call assert_equal([], items(()))
+ call assert_equal([], items(test_null_tuple()))
+ END
+ call v9.CheckSourceLegacyAndVim9Success(lines)
+endfunc
+
+" Test for join()
+func Test_tuple_join()
+ let lines =<< trim END
+ VAR t = ('a', 'b', 'c')
+ call assert_equal('a b c', join(t))
+ call assert_equal('f o o', ('f', 'o', 'o')->join())
+ call assert_equal('a-b-c', join(t, '-'))
+ call assert_equal('', join(()))
+ call assert_equal('', join(test_null_tuple()))
+ END
+ call v9.CheckSourceLegacyAndVim9Success(lines)
+endfunc
+
+" Test for js_encode()
+func Test_tuple_js_encode()
+ let lines =<< trim END
+ call assert_equal('["a","b","c"]', js_encode(('a', 'b', 'c')))
+ call assert_equal('["a","b"]', js_encode(('a', 'b')))
+ call assert_equal('["a"]', js_encode(('a',)))
+ call assert_equal("[]", js_encode(()))
+ call assert_equal("[]", js_encode(test_null_tuple()))
+ call assert_equal('["a",,]', js_encode(('a', v:none)))
+
+ #" encode a recursive tuple
+ VAR l = []
+ VAR tuple = (l,)
+ call add(l, tuple)
+ call assert_equal("[[[]]]", js_encode(tuple))
+ END
+ call v9.CheckSourceLegacyAndVim9Success(lines)
+endfunc
+
+" Test for json_encode()
+func Test_tuple_json_encode()
+ let lines =<< trim END
+ call assert_equal('["a","b","c"]', json_encode(('a', 'b', 'c')))
+ call assert_equal('["a","b"]', json_encode(('a', 'b')))
+ call assert_equal('["a"]', json_encode(('a',)))
+ call assert_equal("[]", json_encode(()))
+ call assert_equal("[]", json_encode(test_null_tuple()))
+
+ #" encode a recursive tuple
+ VAR l = []
+ VAR tuple = (l,)
+ call add(l, tuple)
+ call assert_equal("[[[]]]", json_encode(tuple))
+ END
+ call v9.CheckSourceLegacyAndVim9Success(lines)
+
+ let lines =<< trim END
+ VAR t = (function('min'), function('max'))
+ VAR s = json_encode(t)
+ END
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ 'E1161: Cannot json encode a func',
+ \ 'E1161: Cannot json encode a func',
+ \ 'E1161: Cannot json encode a func'])
+endfunc
+
+" Test for len()
+func Test_tuple_len()
+ let lines =<< trim END
+ call assert_equal(0, len(()))
+ call assert_equal(0, len(test_null_tuple()))
+ call assert_equal(1, len(("abc",)))
+ call assert_equal(3, len(("abc", "def", "ghi")))
+ END
+ call v9.CheckSourceLegacyAndVim9Success(lines)
+endfunc
+
+" Test for map() with a tuple
+func Test_tuple_map()
+ let t = (1, 3, 5)
+ call assert_fails("call map(t, 'v:val + 1')", 'E1524: Cannot use a tuple with function map()')
+endfunc
+
+" Test for max()
+func Test_tuple_max()
+ let lines =<< trim END
+ VAR t1 = (1, 3, 5)
+ call assert_equal(5, max(t1))
+ VAR t2 = (6,)
+ call assert_equal(6, max(t2))
+ call assert_equal(0, max(()))
+ call assert_equal(0, max(test_null_tuple()))
+ END
+ call v9.CheckSourceLegacyAndVim9Success(lines)
+
+ let lines =<< trim END
+ vim9script
+ var x = max(('a', 2))
+ END
+ call v9.CheckSourceFailure(lines, 'E1030: Using a String as a Number: "a"')
+
+ let lines =<< trim END
+ vim9script
+ var x = max((1, 'b'))
+ END
+ call v9.CheckSourceFailure(lines, 'E1030: Using a String as a Number: "b"')
+
+ let lines =<< trim END
+ vim9script
+ def Fn()
+ var x = max(('a', 'b'))
+ enddef
+ Fn()
+ END
+ call v9.CheckSourceFailure(lines, 'E1030: Using a String as a Number: "a"')
+
+ let lines =<< trim END
+ echo max([('a', 'b'), 20])
+ END
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ 'E1520: Using a Tuple as a Number',
+ \ 'E1520: Using a Tuple as a Number',
+ \ 'E1520: Using a Tuple as a Number'])
+endfunc
+
+" Test for min()
+func Test_tuple_min()
+ let lines =<< trim END
+ VAR t1 = (5, 3, 1)
+ call assert_equal(1, min(t1))
+ VAR t2 = (6,)
+ call assert_equal(6, min(t2))
+ call assert_equal(0, min(()))
+ call assert_equal(0, min(test_null_tuple()))
+ END
+ call v9.CheckSourceLegacyAndVim9Success(lines)
+
+ let lines =<< trim END
+ vim9script
+ var x = min(('a', 2))
+ END
+ call v9.CheckSourceFailure(lines, 'E1030: Using a String as a Number: "a"')
+
+ let lines =<< trim END
+ vim9script
+ var x = min((1, 'b'))
+ END
+ call v9.CheckSourceFailure(lines, 'E1030: Using a String as a Number: "b"')
+
+
+ let lines =<< trim END
+ vim9script
+ def Fn()
+ var x = min(('a', 'b'))
+ enddef
+ Fn()
+ END
+ call v9.CheckSourceFailure(lines, 'E1030: Using a String as a Number: "a"')
+endfunc
+
+" Test for reduce()
+func Test_tuple_reduce()
+ let lines =<< trim END
+ call assert_equal(1, reduce((), LSTART acc, val LMIDDLE acc + val LEND, 1))
+ call assert_equal(10, reduce((1, 3, 5), LSTART acc, val LMIDDLE acc + val LEND, 1))
+ call assert_equal(2 * (2 * ((2 * 1) + 2) + 3) + 4, reduce((2, 3, 4), LSTART acc, val LMIDDLE 2 * acc + val LEND, 1))
+ call assert_equal('a x y z', ('x', 'y', 'z')->reduce(LSTART acc, val LMIDDLE acc .. ' ' .. val LEND, 'a'))
+
+ VAR t = ('x', 'y', 'z')
+ call assert_equal(42, reduce(t, function('get'), {'x': {'y': {'z': 42 } } }))
+ call assert_equal(('x', 'y', 'z'), t)
+ call assert_equal(1, reduce((1,), LSTART acc, val LMIDDLE acc + val LEND))
+ call assert_equal('x y z', reduce(('x', 'y', 'z'), LSTART acc, val LMIDDLE acc .. ' ' .. val LEND))
+ call assert_equal(5, reduce(test_null_tuple(), LSTART acc, val LMIDDLE acc + val LEND, 5))
+ END
+ call v9.CheckSourceLegacyAndVim9Success(lines)
+
+ call assert_equal({'x': 1, 'y': 1, 'z': 1 }, ('x', 'y', 'z')->reduce({ acc, val -> extend(acc, { val: 1 }) }, {}))
+
+ call assert_fails("call reduce((), { acc, val -> acc + val })", 'E998: Reduce of an empty Tuple with no initial value')
+ call assert_fails("call reduce(test_null_tuple(), { acc, val -> acc + val })", 'E998: Reduce of an empty Tuple with no initial value')
+
+ let lines =<< trim END
+ echo reduce((1, 2, 3), LSTART acc, val LMIDDLE acc + foo LEND)
+ END
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ 'E121: Undefined variable: foo',
+ \ 'E1001: Variable not found: foo',
+ \ 'E1001: Variable not found: foo'])
+endfunc
+
+" Test for remove()
+func Test_tuple_remove()
+ let lines =<< trim END
+ VAR t = (1, 3, 5)
+ call remove(t, 1)
+ END
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ 'E896: Argument of remove() must be a List, Dictionary or Blob',
+ \ 'E1013: Argument 1: type mismatch, expected list<any> but got tuple<number, number, number>',
+ \ 'E1228: List, Dictionary or Blob required for argument 1'])
+endfunc
+
+" Test for test_refcount()
+func Test_tuple_refcount()
+ let lines =<< trim END
+ VAR t = (1, 2, 3)
+ call assert_equal(1, test_refcount(t))
+ VAR x = t
+ call assert_equal(2, test_refcount(t))
+ LET x = (4, 5, 6)
+ call assert_equal(1, test_refcount(t))
+ for n in t
+ call assert_equal(2, test_refcount(t))
+ endfor
+ call assert_equal(1, test_refcount(t))
+ END
+ call v9.CheckSourceLegacyAndVim9Success(lines)
+endfunc
+
+" Test for repeat()
+func Test_tuple_repeat()
+ let lines =<< trim END
+ VAR t = ('a', 'b')
+ call assert_equal(('a', 'b', 'a', 'b', 'a', 'b'), repeat(('a', 'b'), 3))
+ call assert_equal(('x', 'x', 'x'), repeat(('x',), 3))
+ call assert_equal((), repeat((), 3))
+ call assert_equal((), repeat((), 0))
+ call assert_equal((), repeat((), -1))
+ call assert_equal((), repeat(test_null_tuple(), 3))
+ END
+ call v9.CheckSourceLegacyAndVim9Success(lines)
+endfunc
+
+" Test for reverse()
+func Test_tuple_reverse()
+ let lines =<< trim END
+ VAR t = (['a'], ['b'], ['c'])
+ call assert_equal((['c'], ['b'], ['a']), reverse(t))
+ call assert_equal(('a',), reverse(('a',)))
+ call assert_equal((), reverse(()))
+ call assert_equal((), reverse(test_null_tuple()))
+ END
+ call v9.CheckSourceLegacyAndVim9Success(lines)
+endfunc
+
+" Test for slicing a tuple
+func Test_tuple_slice_func()
+ let lines =<< trim END
+ VAR t = (1, 3, 5, 7, 9)
+ call assert_equal((9,), slice(t, 4))
+ call assert_equal((5, 7, 9), slice(t, 2))
+ call assert_equal((), slice(t, 5))
+ call assert_equal((), slice((), 1, 2))
+ call assert_equal((), slice(test_null_tuple(), 1, 2))
+ END
+ call v9.CheckSourceLegacyAndVim9Success(lines)
+
+ " return value of slice() should be the correct tuple type
+ let lines =<< trim END
+ var t: tuple<...list<number>> = (1, 3, 5)
+ var x: tuple<...list<number>> = slice(t, 1, 2)
+ assert_equal((3,), x)
+ END
+ call v9.CheckSourceDefAndScriptSuccess(lines)
+endfunc
+
+" Test for sort()
+func Test_tuple_sort()
+ let lines =<< trim END
+ call sort([1.1, (1.2,)], 'f')
+ END
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ 'E1521: Using a Tuple as a Float',
+ \ 'E1521: Using a Tuple as a Float',
+ \ 'E1521: Using a Tuple as a Float'])
+endfunc
+
+" Test for stridx()
+func Test_tuple_stridx()
+ let lines =<< trim END
+ call stridx(('abc', ), 'a')
+ END
+ call v9.CheckSourceLegacyAndVim9Failure(lines, [
+ \ 'E1522: Using a Tuple as a String',
+ \ 'E1013: Argument 1: type mismatch, expected string but got tuple<string>',
+ \ 'E1174: String required for argument 1'])
+endfunc
+
+" Test for string()
+func Test_tuple_string()
+ let lines =<< trim END
+ VAR t1 = (1, 'as''d', [1, 2, function("strlen")], {'a': 1}, )
+ call assert_equal("(1, 'as''d', [1, 2, function('strlen')], {'a': 1})", string(t1))
+
+ #" empty tuple
+ VAR t2 = ()
+ call assert_equal("()", string(t2))
+
+ #" one item tuple
+ VAR t3 = ("a", )
+ call assert_equal("('a', )", string(t3))
+
+ call assert_equal("()", string(test_null_tuple()))
+ END
+ call v9.CheckSourceLegacyAndVim9Success(lines)
+
+ " recursive tuple
+ let lines =<< trim END
+ VAR l = []
+ VAR t = (l,)
+ call add(l, t)
+ call assert_equal('([(...)], )', string(t))
+ END
+ call v9.CheckSourceLegacyAndVim9Success(lines)
+endfunc
+
+" Test for type()
+func Test_tuple_type()
+ let lines =<< trim END
+ VAR t = (1, 2)
+ call assert_equal(17, type(t))
+ call assert_equal(v:t_tuple, type(t))
+ call assert_equal(v:t_tuple, type(()))
+ call assert_equal(v:t_tuple, type(test_null_tuple()))
+ END
+ call v9.CheckSourceLegacyAndVim9Success(lines)
+endfunc
+
+" Test for typename()
+func Test_tuple_typename()
+ let lines =<< trim END
+ call assert_equal('tuple<number, number>', typename((1, 2)))
+ call assert_equal('tuple<string, string>', typename(('a', 'b')))
+ call assert_equal('tuple<bool, bool>', typename((v:true, v:true)))
+ call assert_equal('tuple<number, string>', typename((1, 'b')))
+ call assert_equal('tuple<any>', typename(()))
+ call assert_equal('tuple<dict<any>>', typename(({}, )))
+ call assert_equal('tuple<list<any>>', typename(([], )))
+ call assert_equal('tuple<list<number>>', typename(([1, 2], )))
+ call assert_equal('tuple<list<string>>', typename((['a', 'b'], )))
+ call assert_equal('tuple<list<list<number>>>', typename(([[1], [2]], )))
+ call assert_equal('tuple<tuple<number, number>>', typename(((1, 2), )))
+ VAR t1 = (([1, 2],), (['a', 'b'],))
+ call assert_equal('tuple<tuple<list<number>>, tuple<list<string>>>', typename(t1))
+ call assert_equal('list<tuple<number>>', typename([(1,)]))
+ call assert_equal('list<tuple<any>>', typename([()]))
+ call assert_equal('tuple<any>', typename(test_null_tuple()))
+ END
+ call v9.CheckSourceLegacyAndVim9Success(lines)
+
+ let lines =<< trim END
+ var d: dict<any> = {a: 0}
+ var t2 = (d,)
+ t2[0].e = {b: t2}
+ call assert_equal('tuple<dict<any>>', typename(t2))
+ END
+ call v9.CheckSourceDefAndScriptSuccess(lines)
+
+ " check the type of a circular reference tuple
+ let lines =<< trim END
+ # circular reference tuple
+ var l: list<tuple<any>> = []
+ var t = (l,)
+ add(l, t)
+ assert_equal('tuple<list<tuple<any>>>', typename(t))
+ assert_equal('list<tuple<any>>', typename(l))
+ END
+ call v9.CheckSourceDefAndScriptSuccess(lines)
+
+ " When a tuple item is used in a "for" loop, the type is tuple<any>
+ let lines =<< trim END
+ vim9script
+ var l = [(1, 2)]
+ for t in l
+ assert_equal('tuple<any>', typename(t))
+ endfor
+ END
+ call v9.CheckSourceScriptSuccess(lines)
+
+ " type of a tuple copy should be the same
+ let lines =<< trim END
+ var t: tuple<...list<number>> = (1, 2)
+ var x: tuple<...list<number>> = t
+ assert_equal('tuple<...list<number>>', typename(x))
+ END
+ call v9.CheckSourceDefAndScriptSuccess(lines)
+endfunc
+
+" Test for saving and restoring tuples from a viminfo file
+func Test_tuple_viminfo()
+ let viminfo_save = &viminfo
+ set viminfo^=!
+
+ let g:MYTUPLE = ([1, 2], [3, 4], 'a', 'b', 1, 2)
+
+ " create a tuple with circular reference
+ " This should not be saved in the viminfo file
+ let l = []
+ let g:CIRCTUPLE = (l,)
+ call add(l, g:CIRCTUPLE)
+
+ wviminfo! Xviminfo
+ unlet g:MYTUPLE
+ unlet g:CIRCTUPLE
+ rviminfo! Xviminfo
+ call assert_equal(([1, 2], [3, 4], 'a', 'b', 1, 2), g:MYTUPLE)
+ call assert_false(exists('g:CIRCTUPLE'))
+ let &viminfo = viminfo_save
+ call delete('Xviminfo')
+endfunc
+
+" Test for list2tuple()
+func Test_list2tuple()
+ let lines =<< trim END
+ call assert_equal((), list2tuple([]))
+ call assert_equal((), list2tuple(test_null_list()))
+ call assert_equal(('a', ['b'], {'n': 20}), list2tuple(['a', ['b'], {'n': 20}]))
+
+ VAR l = ['a', 'b']
+ VAR t = list2tuple(l)
+ LET l[0] = 'x'
+ call assert_equal(('a', 'b'), t)
+
+ call assert_equal((0, 1, 2), list2tuple(range(3)))
+
+ call assert_equal(((),), [()]->list2tuple())
+ END
+ call v9.CheckSourceLegacyAndVim9Success(lines)
+
+ call assert_fails('call list2tuple(())', 'E1211: List required for argument 1')
+
+ " Check the returned type
+ let lines =<< trim END
+ var l1 = [1, 2]
+ var t1: tuple<...list<number>> = list2tuple(l1)
+ assert_equal('tuple<...list<number>>', typename(t1))
+ var l2 = ['a', 'b']
+ var t2: tuple<...list<string>> = list2tuple(l2)
+ assert_equal('tuple<...list<string>>', typename(t2))
+ var l3 = []
+ var t3 = list2tuple(l3)
+ assert_equal('tuple<any>', typename(t3))
+ var l4 = [([{}])]
+ var t4: tuple<list<dict<any>>> = list2tuple(l4)
+ assert_equal('tuple<list<dict<any>>>', typename(t4))
+ END
+ call v9.CheckSourceDefAndScriptSuccess(lines)
+endfunc
+
+" Test for tuple2list()
+func Test_tuple2list()
+ let lines =<< trim END
+ call assert_equal([], tuple2list(()))
+ call assert_equal([], tuple2list(test_null_tuple()))
+
+ VAR t1 = ('a', ['b'], {'n': 20}, ('a',))
+ call assert_equal(['a', ['b'], {'n': 20}, ('a',)], tuple2list(t1))
+
+ VAR t = ('a', 'b')
+ VAR l = tuple2list(t)
+ LET l[0] = 'x'
+ call assert_equal(('a', 'b'), t)
+
+ call assert_equal([[]], ([],)->tuple2list())
+ END
+ call v9.CheckSourceLegacyAndVim9Success(lines)
+
+ call assert_fails('call tuple2list([])', 'E1534: Tuple required for argument 1')
+
+ " Check the returned type
+ let lines =<< trim END
+ var t1 = (1, 2)
+ var l1 = tuple2list(t1)
+ assert_equal('list<number>', typename(l1))
+ var t2 = ('a', 'b')
+ var l2 = tuple2list(t2)
+ assert_equal('list<string>', typename(l2))
+ var t3 = ()
+ var l3 = tuple2list(t3)
+ assert_equal('list<any>', typename(l3))
+ var t4 = ([({},)],)
+ var l4 = tuple2list(t4)
+ assert_equal('list<list<tuple<dict<any>>>>', typename(l4))
+ END
+ call v9.CheckSourceDefAndScriptSuccess(lines)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_vim9_assign.vim b/src/testdir/test_vim9_assign.vim
index 616e224..97c11a7 100644
--- a/src/testdir/test_vim9_assign.vim
+++ b/src/testdir/test_vim9_assign.vim
@@ -538,7 +538,7 @@
var v2: number
[v1, v2] = ''
END
- v9.CheckDefFailure(lines, 'E1012: Type mismatch; expected list<any> but got string', 3)
+ v9.CheckDefFailure(lines, 'E1535: List or Tuple required', 3)
lines =<< trim END
g:values = [false, 0]
diff --git a/src/testdir/test_vim9_builtin.vim b/src/testdir/test_vim9_builtin.vim
index 80ed2b2..f8e1306 100644
--- a/src/testdir/test_vim9_builtin.vim
+++ b/src/testdir/test_vim9_builtin.vim
@@ -882,7 +882,7 @@
def Test_count()
count('ABC ABC ABC', 'b', true)->assert_equal(3)
count('ABC ABC ABC', 'b', false)->assert_equal(0)
- v9.CheckSourceDefAndScriptFailure(['count(10, 1)'], 'E1225: String, List or Dictionary required for argument 1')
+ v9.CheckSourceDefAndScriptFailure(['count(10, 1)'], 'E1225: String, List, Tuple or Dictionary required for argument 1')
v9.CheckSourceDefAndScriptFailure(['count("a", [1], 2)'], ['E1013: Argument 3: type mismatch, expected bool but got number', 'E1212: Bool required for argument 3'])
v9.CheckSourceDefAndScriptFailure(['count("a", [1], 0, "b")'], ['E1013: Argument 4: type mismatch, expected number but got string', 'E1210: Number required for argument 4'])
count([1, 2, 2, 3], 2)->assert_equal(2)
@@ -1530,7 +1530,7 @@
END
v9.CheckSourceScriptSuccess(lines)
- v9.CheckSourceDefAndScriptFailure(['filter(1.1, "1")'], ['E1013: Argument 1: type mismatch, expected list<any> but got float', 'E1251: List, Dictionary, Blob or String required for argument 1'])
+ v9.CheckSourceDefAndScriptFailure(['filter(1.1, "1")'], ['E1013: Argument 1: type mismatch, expected list<any> but got float', 'E1251: List, Tuple, Dictionary, Blob or String required for argument 1'])
v9.CheckSourceDefAndScriptFailure(['filter([1, 2], 4)'], ['E1256: String or function required for argument 2', 'E1024: Using a Number as a String'])
lines =<< trim END
@@ -1647,6 +1647,10 @@
assert_equal('', foldtextresult('.'))
enddef
+def Test_foreach()
+ v9.CheckSourceDefAndScriptFailure(['foreach(test_null_job(), "")'], ['E1013: Argument 1: type mismatch, expected list<any> but got job', 'E1251: List, Tuple, Dictionary, Blob or String required for argument 1'])
+enddef
+
def Test_fullcommand()
assert_equal('next', fullcommand('n'))
assert_equal('noremap', fullcommand('no'))
@@ -1755,7 +1759,7 @@
enddef
def Test_get()
- v9.CheckSourceDefAndScriptFailure(['get("a", 1)'], ['E1013: Argument 1: type mismatch, expected list<any> but got string', 'E896: Argument of get() must be a List, Dictionary or Blob'])
+ v9.CheckSourceDefAndScriptFailure(['get("a", 1)'], ['E1013: Argument 1: type mismatch, expected list<any> but got string', 'E1531: Argument of get() must be a List, Tuple, Dictionary or Blob'])
[3, 5, 2]->get(1)->assert_equal(5)
[3, 5, 2]->get(3)->assert_equal(0)
[3, 5, 2]->get(3, 9)->assert_equal(9)
@@ -2276,7 +2280,7 @@
def Test_index()
index(['a', 'b', 'a', 'B'], 'b', 2, true)->assert_equal(3)
- v9.CheckSourceDefAndScriptFailure(['index("a", "a")'], ['E1013: Argument 1: type mismatch, expected list<any> but got string', 'E1226: List or Blob required for argument 1'])
+ v9.CheckSourceDefAndScriptFailure(['index("a", "a")'], ['E1013: Argument 1: type mismatch, expected list<any> but got string', 'E1528: List or Tuple or Blob required for argument 1'])
v9.CheckSourceDefFailure(['index(["1"], 1)'], 'E1013: Argument 2: type mismatch, expected string but got number')
v9.CheckSourceDefAndScriptFailure(['index(0z10, "b")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
v9.CheckSourceDefAndScriptFailure(['index([1], 1, "c")'], ['E1013: Argument 3: type mismatch, expected number but got string', 'E1210: Number required for argument 3'])
@@ -2539,7 +2543,7 @@
enddef
def Test_join()
- v9.CheckSourceDefAndScriptFailure(['join("abc")'], ['E1013: Argument 1: type mismatch, expected list<any> but got string', 'E1211: List required for argument 1'])
+ v9.CheckSourceDefAndScriptFailure(['join("abc")'], ['E1013: Argument 1: type mismatch, expected list<any> but got string', 'E1529: List or Tuple required for argument 1'])
v9.CheckSourceDefAndScriptFailure(['join([], 2)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
join([''], '')->assert_equal('')
enddef
@@ -2660,9 +2664,9 @@
def Test_map()
if has('channel')
- v9.CheckSourceDefAndScriptFailure(['map(test_null_channel(), "1")'], ['E1013: Argument 1: type mismatch, expected list<any> but got channel', 'E1251: List, Dictionary, Blob or String required for argument 1'])
+ v9.CheckSourceDefAndScriptFailure(['map(test_null_channel(), "1")'], ['E1013: Argument 1: type mismatch, expected list<any> but got channel', 'E1251: List, Tuple, Dictionary, Blob or String required for argument 1'])
endif
- v9.CheckSourceDefAndScriptFailure(['map(1, "1")'], ['E1013: Argument 1: type mismatch, expected list<any> but got number', 'E1251: List, Dictionary, Blob or String required for argument 1'])
+ v9.CheckSourceDefAndScriptFailure(['map(1, "1")'], ['E1013: Argument 1: type mismatch, expected list<any> but got number', 'E1251: List, Tuple, Dictionary, Blob or String required for argument 1'])
v9.CheckSourceDefAndScriptFailure(['map([1, 2], 4)'], ['E1256: String or function required for argument 2', 'E1024: Using a Number as a String'])
# type of dict remains dict<any> even when type of values changes
@@ -2893,9 +2897,9 @@
def Test_mapnew()
if has('channel')
- v9.CheckSourceDefAndScriptFailure(['mapnew(test_null_job(), "1")'], ['E1013: Argument 1: type mismatch, expected list<any> but got job', 'E1251: List, Dictionary, Blob or String required for argument 1'])
+ v9.CheckSourceDefAndScriptFailure(['mapnew(test_null_job(), "1")'], ['E1013: Argument 1: type mismatch, expected list<any> but got job', 'E1251: List, Tuple, Dictionary, Blob or String required for argument 1'])
endif
- v9.CheckSourceDefAndScriptFailure(['mapnew(1, "1")'], ['E1013: Argument 1: type mismatch, expected list<any> but got number', 'E1251: List, Dictionary, Blob or String required for argument 1'])
+ v9.CheckSourceDefAndScriptFailure(['mapnew(1, "1")'], ['E1013: Argument 1: type mismatch, expected list<any> but got number', 'E1251: List, Tuple, Dictionary, Blob or String required for argument 1'])
enddef
def Test_mapset()
@@ -3072,7 +3076,7 @@
? [1, max([2, 3])]
: [4, 5]
assert_equal([4, 5], l2)
- v9.CheckSourceDefAndScriptFailure(['max(5)'], ['E1013: Argument 1: type mismatch, expected list<any> but got number', 'E1227: List or Dictionary required for argument 1'])
+ v9.CheckSourceDefAndScriptFailure(['max(5)'], ['E1013: Argument 1: type mismatch, expected list<any> but got number', 'E1530: List or Tuple or Dictionary required for argument 1'])
enddef
def Test_menu_info()
@@ -3094,7 +3098,7 @@
? [1, min([2, 3])]
: [4, 5]
assert_equal([4, 5], l2)
- v9.CheckSourceDefAndScriptFailure(['min(5)'], ['E1013: Argument 1: type mismatch, expected list<any> but got number', 'E1227: List or Dictionary required for argument 1'])
+ v9.CheckSourceDefAndScriptFailure(['min(5)'], ['E1013: Argument 1: type mismatch, expected list<any> but got number', 'E1530: List or Tuple or Dictionary required for argument 1'])
enddef
def Test_mkdir()
@@ -3453,7 +3457,7 @@
enddef
def Test_reduce()
- v9.CheckSourceDefAndScriptFailure(['reduce({a: 10}, "1")'], ['E1013: Argument 1: type mismatch, expected list<any> but got dict<number>', 'E1252: String, List or Blob required for argument 1'])
+ v9.CheckSourceDefAndScriptFailure(['reduce({a: 10}, "1")'], ['E1013: Argument 1: type mismatch, expected list<any> but got dict<number>', 'E1253: String, List, Tuple or Blob required for argument 1'])
assert_equal(6, [1, 2, 3]->reduce((r, c) => r + c, 0))
assert_equal(11, 0z0506->reduce((r, c) => r + c, 0))
enddef
@@ -3616,8 +3620,8 @@
enddef
def Test_repeat()
- v9.CheckSourceDefAndScriptFailure(['repeat(1.1, 2)'], ['E1013: Argument 1: type mismatch, expected string but got float', 'E1301: String, Number, List or Blob required for argument 1'])
- v9.CheckSourceDefAndScriptFailure(['repeat({a: 10}, 2)'], ['E1013: Argument 1: type mismatch, expected string but got dict<', 'E1301: String, Number, List or Blob required for argument 1'])
+ v9.CheckSourceDefAndScriptFailure(['repeat(1.1, 2)'], ['E1013: Argument 1: type mismatch, expected string but got float', 'E1301: String, Number, List, Tuple or Blob required for argument 1'])
+ v9.CheckSourceDefAndScriptFailure(['repeat({a: 10}, 2)'], ['E1013: Argument 1: type mismatch, expected string but got dict<', 'E1301: String, Number, List, Tuple or Blob required for argument 1'])
var lines =<< trim END
assert_equal('aaa', repeat('a', 3))
assert_equal('111', repeat(1, 3))
@@ -3638,7 +3642,7 @@
enddef
def Test_reverse()
- v9.CheckSourceDefAndScriptFailure(['reverse(10)'], ['E1013: Argument 1: type mismatch, expected list<any> but got number', 'E1252: String, List or Blob required for argument 1'])
+ v9.CheckSourceDefAndScriptFailure(['reverse(10)'], ['E1013: Argument 1: type mismatch, expected list<any> but got number', 'E1253: String, List, Tuple or Blob required for argument 1'])
enddef
def Test_reverse_return_type()
diff --git a/src/testdir/test_vim9_disassemble.vim b/src/testdir/test_vim9_disassemble.vim
index 27b71fe..4877933 100644
--- a/src/testdir/test_vim9_disassemble.vim
+++ b/src/testdir/test_vim9_disassemble.vim
@@ -3695,4 +3695,184 @@
unlet g:instr
enddef
+" Disassemble the code generated for indexing a tuple
+def Test_disassemble_tuple_indexing()
+ var lines =<< trim END
+ vim9script
+ def Fn(): tuple<...list<number>>
+ var t = (5, 6, 7)
+ var i = t[2]
+ var j = t[1 : 2]
+ return t
+ enddef
+ g:instr = execute('disassemble Fn')
+ END
+ v9.CheckScriptSuccess(lines)
+ assert_match('<SNR>\d\+_Fn\_s*' ..
+ 'var t = (5, 6, 7)\_s*' ..
+ '0 PUSHNR 5\_s*' ..
+ '1 PUSHNR 6\_s*' ..
+ '2 PUSHNR 7\_s*' ..
+ '3 NEWTUPLE size 3\_s*' ..
+ '4 SETTYPE tuple<number, number, number>\_s*' ..
+ '5 STORE $0\_s*' ..
+ 'var i = t\[2\]\_s*' ..
+ '6 LOAD $0\_s*' ..
+ '7 PUSHNR 2\_s*' ..
+ '8 TUPLEINDEX\_s*' ..
+ '9 STORE $1\_s*' ..
+ 'var j = t\[1 : 2\]\_s*' ..
+ '10 LOAD $0\_s*' ..
+ '11 PUSHNR 1\_s*' ..
+ '12 PUSHNR 2\_s*' ..
+ '13 TUPLESLICE\_s*' ..
+ '14 SETTYPE tuple<number, number, number>\_s*' ..
+ '15 STORE $2\_s*' ..
+ 'return t\_s*' ..
+ '16 LOAD $0\_s*' ..
+ '17 RETURN', g:instr)
+ unlet g:instr
+enddef
+
+" Disassemble the code generated for assigning a tuple to a default value
+def Test_disassemble_tuple_default_value()
+ var lines =<< trim END
+ vim9script
+ def Fn()
+ var t: tuple<number>
+ enddef
+ g:instr = execute('disassemble Fn')
+ END
+ v9.CheckScriptSuccess(lines)
+ assert_match('<SNR>\d\+_Fn\_s*' ..
+ 'var t: tuple<number>\_s*' ..
+ '0 NEWTUPLE size 0\_s*' ..
+ '1 SETTYPE tuple<number>\_s*' ..
+ '2 STORE $0\_s*' ..
+ '3 RETURN void', g:instr)
+ unlet g:instr
+enddef
+
+" Disassemble the code generated for comparing tuples
+def Test_disassemble_tuple_compare()
+ var lines =<< trim END
+ vim9script
+ def Fn()
+ var t1 = (1, 2)
+ var t2 = t1
+ var x = t1 == t2
+ enddef
+ g:instr = execute('disassemble Fn')
+ END
+ v9.CheckScriptSuccess(lines)
+ assert_match('<SNR>\d\+_Fn\_s*' ..
+ 'var t1 = (1, 2)\_s*' ..
+ '0 PUSHNR 1\_s*' ..
+ '1 PUSHNR 2\_s*' ..
+ '2 NEWTUPLE size 2\_s*' ..
+ '3 SETTYPE tuple<number, number>\_s*' ..
+ '4 STORE $0\_s*' ..
+ 'var t2 = t1\_s*' ..
+ '5 LOAD $0\_s*' ..
+ '6 SETTYPE tuple<number, number>\_s*' ..
+ '7 STORE $1\_s*' ..
+ 'var x = t1 == t2\_s*' ..
+ '8 LOAD $0\_s*' ..
+ '9 LOAD $1\_s*' ..
+ '10 COMPARETUPLE ==\_s*' ..
+ '11 STORE $2\_s*' ..
+ '12 RETURN void', g:instr)
+ unlet g:instr
+enddef
+
+" Disassemble the code generated for concatenating tuples
+def Test_disassemble_tuple_concatenate()
+ var lines =<< trim END
+ vim9script
+ def Fn()
+ var t1 = (1,) + (2,)
+ enddef
+ g:instr = execute('disassemble Fn')
+ END
+ v9.CheckScriptSuccess(lines)
+ assert_match('<SNR>\d\+_Fn\_s*' ..
+ 'var t1 = (1,) + (2,)\_s*' ..
+ '0 PUSHNR 1\_s*' ..
+ '1 NEWTUPLE size 1\_s*' ..
+ '2 PUSHNR 2\_s*' ..
+ '3 NEWTUPLE size 1\_s*' ..
+ '4 ADDTUPLE\_s*' ..
+ '5 SETTYPE tuple<number, number>\_s*' ..
+ '6 STORE $0\_s*' ..
+ '7 RETURN void', g:instr)
+ unlet g:instr
+enddef
+
+" Disassemble the code generated for a constant tupe
+def Test_disassemble_tuple_const()
+ var lines =<< trim END
+ vim9script
+ def Fn()
+ const t = (1, 2, 3)
+ var x = t[1 : 2]
+ enddef
+ g:instr = execute('disassemble Fn')
+ END
+ v9.CheckScriptSuccess(lines)
+ assert_match('<SNR>\d\+_Fn\_s*' ..
+ 'const t = (1, 2, 3)\_s*' ..
+ '0 PUSHNR 1\_s*' ..
+ '1 PUSHNR 2\_s*' ..
+ '2 PUSHNR 3\_s*' ..
+ '3 NEWTUPLE size 3\_s*' ..
+ '4 LOCKCONST\_s*' ..
+ '5 SETTYPE tuple<number, number, number>\_s*' ..
+ '6 STORE $0\_s*' ..
+ 'var x = t\[1 : 2\]\_s*' ..
+ '7 LOAD $0\_s*' ..
+ '8 PUSHNR 1\_s*' ..
+ '9 PUSHNR 2\_s*' ..
+ '10 TUPLESLICE\_s*' ..
+ '11 SETTYPE tuple<number, number, number>\_s*' ..
+ '12 STORE $1\_s*' ..
+ '13 RETURN void', g:instr)
+ unlet g:instr
+enddef
+
+" Disassemble the code generated for setting the type when using a tuple in an
+" assignment
+def Test_disassemble_assign_tuple_set_type()
+ var lines =<< trim END
+ vim9script
+ def Fn()
+ var x = (1,)
+ enddef
+ g:instr = execute('disassemble Fn')
+ END
+ v9.CheckScriptSuccess(lines)
+ assert_match('<SNR>\d\+_Fn\_s*' ..
+ 'var x = (1,)\_s*' ..
+ '0 PUSHNR 1\_s*' ..
+ '1 NEWTUPLE size 1\_s*' ..
+ '2 SETTYPE tuple<number>\_s*' ..
+ '3 STORE $0\_s*' ..
+ '4 RETURN void', g:instr)
+
+ lines =<< trim END
+ vim9script
+ def Fn()
+ var x = ()
+ enddef
+ g:instr = execute('disassemble Fn')
+ END
+ v9.CheckScriptSuccess(lines)
+ assert_match('<SNR>\d\+_Fn\_s*' ..
+ 'var x = ()\_s*' ..
+ '0 NEWTUPLE size 0\_s*' ..
+ '1 STORE $0\_s*' ..
+ '2 RETURN void', g:instr)
+
+ unlet g:instr
+enddef
+
" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
diff --git a/src/testdir/test_vimscript.vim b/src/testdir/test_vimscript.vim
index 21f894e..5c88ec6 100644
--- a/src/testdir/test_vimscript.vim
+++ b/src/testdir/test_vimscript.vim
@@ -7519,6 +7519,16 @@
endfor
endfunc
+" Test for 'for' loop failures
+func Test_for_loop_failure()
+ func ForFn()
+ for x in test_null_job()
+ endfor
+ endfunc
+ call assert_fails('call ForFn()', 'E1523: String, List, Tuple or Blob required')
+ delfunc ForFn
+endfunc
+
" Test for deeply nested :source command {{{1
func Test_deeply_nested_source()
let lines =<< trim END
diff --git a/src/testdir/vim9.vim b/src/testdir/vim9.vim
index ff4db7b..64922b7 100644
--- a/src/testdir/vim9.vim
+++ b/src/testdir/vim9.vim
@@ -191,19 +191,23 @@
endtry
endfunc
+# Translate "lines" to legacy Vim script
+def LegacyTrans(lines: list<string>): list<string>
+ return lines->mapnew((_, v) =>
+ v->substitute('\<VAR\>', 'let', 'g')
+ ->substitute('\<LET\>', 'let', 'g')
+ ->substitute('\<LSTART\>', '{', 'g')
+ ->substitute('\<LMIDDLE\>', '->', 'g')
+ ->substitute('\<LEND\>', '}', 'g')
+ ->substitute('\<TRUE\>', '1', 'g')
+ ->substitute('\<FALSE\>', '0', 'g')
+ ->substitute('#"', ' "', 'g'))
+enddef
+
# Execute "lines" in a legacy function, translated as in
# CheckLegacyAndVim9Success()
export def CheckTransLegacySuccess(lines: list<string>)
- var legacylines = lines->mapnew((_, v) =>
- v->substitute('\<VAR\>', 'let', 'g')
- ->substitute('\<LET\>', 'let', 'g')
- ->substitute('\<LSTART\>', '{', 'g')
- ->substitute('\<LMIDDLE\>', '->', 'g')
- ->substitute('\<LEND\>', '}', 'g')
- ->substitute('\<TRUE\>', '1', 'g')
- ->substitute('\<FALSE\>', '0', 'g')
- ->substitute('#"', ' "', 'g'))
- CheckLegacySuccess(legacylines)
+ CheckLegacySuccess(LegacyTrans(lines))
enddef
export def Vim9Trans(lines: list<string>): list<string>
@@ -264,16 +268,87 @@
var legacylines = lines->mapnew((_, v) =>
v->substitute('\<VAR\>', 'let', 'g')
->substitute('\<LET\>', 'let', 'g')
+ ->substitute('\<LSTART\>', '{', 'g')
+ ->substitute('\<LMIDDLE\>', '->', 'g')
+ ->substitute('\<LEND\>', '}', 'g')
+ ->substitute('\<TRUE\>', '1', 'g')
+ ->substitute('\<FALSE\>', '0', 'g')
->substitute('#"', ' "', 'g'))
CheckLegacyFailure(legacylines, legacyError)
var vim9lines = lines->mapnew((_, v) =>
v->substitute('\<VAR\>', 'var', 'g')
- ->substitute('\<LET ', '', 'g'))
+ ->substitute('\<LET ', '', 'g')
+ ->substitute('\<LSTART\>', '(', 'g')
+ ->substitute('\<LMIDDLE\>', ') =>', 'g')
+ ->substitute(' *\<LEND\> *', '', 'g')
+ ->substitute('\<TRUE\>', 'true', 'g')
+ ->substitute('\<FALSE\>', 'false', 'g'))
CheckDefExecFailure(vim9lines, defError)
CheckScriptFailure(['vim9script'] + vim9lines, scriptError)
enddef
+# Check that "lines" inside a legacy function has no error.
+export func CheckSourceLegacySuccess(lines)
+ let cwd = getcwd()
+ new
+ call setline(1, ['func Func()'] + a:lines + ['endfunc', 'call Func()'])
+ let bnr = bufnr()
+ try
+ :source
+ finally
+ delfunc! Func
+ call chdir(cwd)
+ exe $':bw! {bnr}'
+ endtry
+endfunc
+
+# Check that "lines" inside a legacy function results in the expected error
+export func CheckSourceLegacyFailure(lines, error)
+ let cwd = getcwd()
+ new
+ call setline(1, ['func Func()'] + a:lines + ['endfunc', 'call Func()'])
+ let bnr = bufnr()
+ try
+ call assert_fails('source', a:error)
+ finally
+ delfunc! Func
+ call chdir(cwd)
+ exe $':bw! {bnr}'
+ endtry
+endfunc
+
+# Execute "lines" in a legacy function, translated as in
+# CheckSourceLegacyAndVim9Success()
+export def CheckSourceTransLegacySuccess(lines: list<string>)
+ CheckSourceLegacySuccess(LegacyTrans(lines))
+enddef
+
+# Execute "lines" in a :def function, translated as in
+# CheckLegacyAndVim9Success()
+export def CheckSourceTransDefSuccess(lines: list<string>)
+ CheckSourceDefSuccess(Vim9Trans(lines))
+enddef
+
+# Execute "lines" in a Vim9 script, translated as in
+# CheckLegacyAndVim9Success()
+export def CheckSourceTransVim9Success(lines: list<string>)
+ CheckSourceScriptSuccess(['vim9script'] + Vim9Trans(lines))
+enddef
+
+# Execute "lines" in a legacy function, :def function and Vim9 script.
+# Use 'VAR' for a declaration.
+# Use 'LET' for an assignment
+# Use ' #"' for a comment
+# Use LSTART arg LMIDDLE expr LEND for lambda
+# Use 'TRUE' for 1 in legacy, true in Vim9
+# Use 'FALSE' for 0 in legacy, false in Vim9
+export def CheckSourceLegacyAndVim9Success(lines: list<string>)
+ CheckSourceTransLegacySuccess(lines)
+ CheckSourceTransDefSuccess(lines)
+ CheckSourceTransVim9Success(lines)
+enddef
+
# :source a list of "lines" and check whether it fails with "error"
export def CheckSourceScriptFailure(lines: list<string>, error: string, lnum = -3)
var cwd = getcwd()
@@ -317,18 +392,6 @@
endtry
enddef
-export def CheckSourceSuccess(lines: list<string>)
- CheckSourceScriptSuccess(lines)
-enddef
-
-export def CheckSourceFailure(lines: list<string>, error: string, lnum = -3)
- CheckSourceScriptFailure(lines, error, lnum)
-enddef
-
-export def CheckSourceFailureList(lines: list<string>, errors: list<string>, lnum = -3)
- CheckSourceScriptFailureList(lines, errors, lnum)
-enddef
-
# :source a List of "lines" inside a ":def" function and check that no error
# occurs when called.
export func CheckSourceDefSuccess(lines)
@@ -346,11 +409,6 @@
endtry
endfunc
-export def CheckSourceDefAndScriptSuccess(lines: list<string>)
- CheckSourceDefSuccess(lines)
- CheckSourceScriptSuccess(['vim9script'] + lines)
-enddef
-
# Check that "lines" inside a ":def" function has no error when compiled.
export func CheckSourceDefCompileSuccess(lines)
let cwd = getcwd()
@@ -447,3 +505,45 @@
CheckSourceScriptFailure(['vim9script'] + lines, errorScript, lnum + 1)
enddef
+export def CheckSourceSuccess(lines: list<string>)
+ CheckSourceScriptSuccess(lines)
+enddef
+
+export def CheckSourceFailure(lines: list<string>, error: string, lnum = -3)
+ CheckSourceScriptFailure(lines, error, lnum)
+enddef
+
+export def CheckSourceFailureList(lines: list<string>, errors: list<string>, lnum = -3)
+ CheckSourceScriptFailureList(lines, errors, lnum)
+enddef
+
+export def CheckSourceDefAndScriptSuccess(lines: list<string>)
+ CheckSourceDefSuccess(lines)
+ CheckSourceScriptSuccess(['vim9script'] + lines)
+enddef
+
+# Execute "lines" in a legacy function, :def function and Vim9 script.
+# Use 'VAR' for a declaration.
+# Use 'LET' for an assignment
+# Use ' #"' for a comment
+export def CheckSourceLegacyAndVim9Failure(lines: list<string>, error: any)
+ var legacyError: string
+ var defError: string
+ var scriptError: string
+
+ if type(error) == type('string')
+ legacyError = error
+ defError = error
+ scriptError = error
+ else
+ legacyError = error[0]
+ defError = error[1]
+ scriptError = error[2]
+ endif
+
+ CheckSourceLegacyFailure(LegacyTrans(lines), legacyError)
+ var vim9lines = Vim9Trans(lines)
+ CheckSourceDefExecFailure(vim9lines, defError)
+ CheckSourceScriptFailure(['vim9script'] + vim9lines, scriptError)
+enddef
+
diff --git a/src/testing.c b/src/testing.c
index 7ab109c..b316b64 100644
--- a/src/testing.c
+++ b/src/testing.c
@@ -1132,6 +1132,10 @@
if (argvars[0].vval.v_list != NULL)
retval = argvars[0].vval.v_list->lv_refcount - 1;
break;
+ case VAR_TUPLE:
+ if (argvars[0].vval.v_tuple != NULL)
+ retval = argvars[0].vval.v_tuple->tv_refcount - 1;
+ break;
case VAR_DICT:
if (argvars[0].vval.v_dict != NULL)
retval = argvars[0].vval.v_dict->dv_refcount - 1;
@@ -1249,6 +1253,12 @@
}
void
+f_test_null_tuple(typval_T *argvars UNUSED, typval_T *rettv)
+{
+ rettv_tuple_set(rettv, NULL);
+}
+
+ void
f_test_unknown(typval_T *argvars UNUSED, typval_T *rettv)
{
rettv->v_type = VAR_UNKNOWN;
diff --git a/src/time.c b/src/time.c
index 8725a88..2fb0c39 100644
--- a/src/time.c
+++ b/src/time.c
@@ -778,7 +778,7 @@
tv.v_type = VAR_FUNC;
tv.vval.v_string = timer->tr_callback.cb_name;
}
- abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL);
+ abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL, NULL);
}
return abort;
}
diff --git a/src/tuple.c b/src/tuple.c
new file mode 100644
index 0000000..eff4bdc
--- /dev/null
+++ b/src/tuple.c
@@ -0,0 +1,1122 @@
+/* vi:set ts=8 sts=4 sw=4 noet:
+ *
+ * VIM - Vi IMproved by Bram Moolenaar
+ *
+ * Do ":help uganda" in Vim to read copying and usage conditions.
+ * Do ":help credits" in Vim to see a list of people who contributed.
+ * See README.txt for an overview of the Vim source code.
+ */
+
+/*
+ * tuple.c: Tuple support functions.
+ */
+
+#include "vim.h"
+
+#if defined(FEAT_EVAL) || defined(PROTO)
+
+// Tuple heads for garbage collection.
+static tuple_T *first_tuple = NULL; // list of all tuples
+
+ static void
+tuple_init(tuple_T *tuple)
+{
+ // Prepend the tuple to the list of tuples for garbage collection.
+ if (first_tuple != NULL)
+ first_tuple->tv_used_prev = tuple;
+ tuple->tv_used_prev = NULL;
+ tuple->tv_used_next = first_tuple;
+ first_tuple = tuple;
+
+ ga_init2(&tuple->tv_items, sizeof(typval_T), 20);
+}
+
+/*
+ * Allocate an empty header for a tuple.
+ * Caller should take care of the reference count.
+ */
+ tuple_T *
+tuple_alloc(void)
+{
+ tuple_T *tuple;
+
+ tuple = ALLOC_CLEAR_ONE(tuple_T);
+ if (tuple != NULL)
+ tuple_init(tuple);
+ return tuple;
+}
+
+/*
+ * Allocate space for a tuple with "count" items.
+ * This uses one allocation for efficiency.
+ * The reference count is not set.
+ * Next tuple_set_item() must be called for each item.
+ */
+ tuple_T *
+tuple_alloc_with_items(int count)
+{
+ tuple_T *tuple;
+
+ tuple = tuple_alloc();
+ if (tuple == NULL)
+ return NULL;
+
+ if (count <= 0)
+ return tuple;
+
+ if (ga_grow(&tuple->tv_items, count) == FAIL)
+ {
+ tuple_free(tuple);
+ return NULL;
+ }
+
+ return tuple;
+}
+
+/*
+ * Set item "idx" for a tuple previously allocated with
+ * tuple_alloc_with_items().
+ * The contents of "tv" is copied into the tuple item.
+ * Each item must be set exactly once.
+ */
+ void
+tuple_set_item(tuple_T *tuple, int idx, typval_T *tv)
+{
+ *TUPLE_ITEM(tuple, idx) = *tv;
+ tuple->tv_items.ga_len++;
+}
+
+/*
+ * Allocate an empty tuple for a return value, with reference count set.
+ * Returns OK or FAIL.
+ */
+ int
+rettv_tuple_alloc(typval_T *rettv)
+{
+ tuple_T *tuple = tuple_alloc();
+
+ if (tuple == NULL)
+ return FAIL;
+
+ rettv->v_lock = 0;
+ rettv_tuple_set(rettv, tuple);
+ return OK;
+}
+
+/*
+ * Set a tuple as the return value. Increments the reference count.
+ */
+ void
+rettv_tuple_set(typval_T *rettv, tuple_T *tuple)
+{
+ rettv->v_type = VAR_TUPLE;
+ rettv->vval.v_tuple = tuple;
+ if (tuple != NULL)
+ ++tuple->tv_refcount;
+}
+
+/*
+ * Set a new tuple with "count" items as the return value.
+ * Returns OK on success and FAIL on allocation failure.
+ */
+ int
+rettv_tuple_set_with_items(typval_T *rettv, int count)
+{
+ tuple_T *new_tuple;
+
+ new_tuple = tuple_alloc_with_items(count);
+ if (new_tuple == NULL)
+ return FAIL;
+
+ rettv_tuple_set(rettv, new_tuple);
+
+ return OK;
+}
+
+/*
+ * Unreference a tuple: decrement the reference count and free it when it
+ * becomes zero.
+ */
+ void
+tuple_unref(tuple_T *tuple)
+{
+ if (tuple != NULL && --tuple->tv_refcount <= 0)
+ tuple_free(tuple);
+}
+
+/*
+ * Free a tuple, including all non-container items it points to.
+ * Ignores the reference count.
+ */
+ static void
+tuple_free_contents(tuple_T *tuple)
+{
+ for (int i = 0; i < TUPLE_LEN(tuple); i++)
+ clear_tv(TUPLE_ITEM(tuple, i));
+
+ ga_clear(&tuple->tv_items);
+}
+
+/*
+ * Go through the list of tuples and free items without the copyID.
+ * But don't free a tuple that has a watcher (used in a for loop), these
+ * are not referenced anywhere.
+ */
+ int
+tuple_free_nonref(int copyID)
+{
+ tuple_T *tt;
+ int did_free = FALSE;
+
+ for (tt = first_tuple; tt != NULL; tt = tt->tv_used_next)
+ if ((tt->tv_copyID & COPYID_MASK) != (copyID & COPYID_MASK))
+ {
+ // Free the Tuple and ordinary items it contains, but don't recurse
+ // into Lists and Dictionaries, they will be in the list of dicts
+ // or list of lists.
+ tuple_free_contents(tt);
+ did_free = TRUE;
+ }
+ return did_free;
+}
+
+ static void
+tuple_free_list(tuple_T *tuple)
+{
+ // Remove the tuple from the list of tuples for garbage collection.
+ if (tuple->tv_used_prev == NULL)
+ first_tuple = tuple->tv_used_next;
+ else
+ tuple->tv_used_prev->tv_used_next = tuple->tv_used_next;
+ if (tuple->tv_used_next != NULL)
+ tuple->tv_used_next->tv_used_prev = tuple->tv_used_prev;
+
+ free_type(tuple->tv_type);
+ vim_free(tuple);
+}
+
+ void
+tuple_free_items(int copyID)
+{
+ tuple_T *tt, *tt_next;
+
+ for (tt = first_tuple; tt != NULL; tt = tt_next)
+ {
+ tt_next = tt->tv_used_next;
+ if ((tt->tv_copyID & COPYID_MASK) != (copyID & COPYID_MASK))
+ {
+ // Free the tuple and ordinary items it contains, but don't recurse
+ // into Lists and Dictionaries, they will be in the list of dicts
+ // or list of lists.
+ tuple_free_list(tt);
+ }
+ }
+}
+
+ void
+tuple_free(tuple_T *tuple)
+{
+ if (in_free_unref_items)
+ return;
+
+ tuple_free_contents(tuple);
+ tuple_free_list(tuple);
+}
+
+/*
+ * Get the number of items in a tuple.
+ */
+ long
+tuple_len(tuple_T *tuple)
+{
+ if (tuple == NULL)
+ return 0L;
+ return tuple->tv_items.ga_len;
+}
+
+/*
+ * Return TRUE when two tuples have exactly the same values.
+ */
+ int
+tuple_equal(
+ tuple_T *t1,
+ tuple_T *t2,
+ int ic) // ignore case for strings
+{
+ if (t1 == t2)
+ return TRUE;
+
+ int t1_len = tuple_len(t1);
+ int t2_len = tuple_len(t2);
+
+ if (t1_len != t2_len)
+ return FALSE;
+
+ if (t1_len == 0)
+ // empty and NULL tuples are considered equal
+ return TRUE;
+
+ // If the tuples "t1" or "t2" is NULL, then it is handled by the length
+ // checks above.
+
+ for (int i = 0, j = 0; i < t1_len && j < t2_len; i++, j++)
+ if (!tv_equal(TUPLE_ITEM(t1, i), TUPLE_ITEM(t2, j), ic))
+ return FALSE;
+
+ return TRUE;
+}
+
+/*
+ * Locate item with index "n" in tuple "tuple" and return it.
+ * A negative index is counted from the end; -1 is the last item.
+ * Returns NULL when "n" is out of range.
+ */
+ typval_T *
+tuple_find(tuple_T *tuple, long n)
+{
+ if (tuple == NULL)
+ return NULL;
+
+ // Negative index is relative to the end.
+ if (n < 0)
+ n = TUPLE_LEN(tuple) + n;
+
+ // Check for index out of range.
+ if (n < 0 || n >= TUPLE_LEN(tuple))
+ return NULL;
+
+ return TUPLE_ITEM(tuple, n);
+}
+
+ int
+tuple_append_tv(tuple_T *tuple, typval_T *tv)
+{
+ if (ga_grow(&tuple->tv_items, 1) == FAIL)
+ return FAIL;
+
+ tuple_set_item(tuple, TUPLE_LEN(tuple), tv);
+
+ return OK;
+}
+
+/*
+ * Concatenate tuples "t1" and "t2" into a new tuple, stored in "tv".
+ * Return FAIL when out of memory.
+ */
+ int
+tuple_concat(tuple_T *t1, tuple_T *t2, typval_T *tv)
+{
+ tuple_T *tuple;
+
+ // make a copy of the first tuple.
+ if (t1 == NULL)
+ tuple = tuple_alloc();
+ else
+ tuple = tuple_copy(t1, FALSE, TRUE, 0);
+ if (tuple == NULL)
+ return FAIL;
+
+ tv->v_type = VAR_TUPLE;
+ tv->v_lock = 0;
+ tv->vval.v_tuple = tuple;
+ if (t1 == NULL)
+ ++tuple->tv_refcount;
+
+ // append all the items from the second tuple
+ for (int i = 0; i < tuple_len(t2); i++)
+ {
+ typval_T new_tv;
+
+ copy_tv(TUPLE_ITEM(t2, i), &new_tv);
+
+ if (tuple_append_tv(tuple, &new_tv) == FAIL)
+ {
+ tuple_free(tuple);
+ return FAIL;
+ }
+ }
+
+ return OK;
+}
+
+/*
+ * Return a slice of tuple starting at index n1 and ending at index n2,
+ * inclusive (tuple[n1 : n2])
+ */
+ tuple_T *
+tuple_slice(tuple_T *tuple, long n1, long n2)
+{
+ tuple_T *new_tuple;
+
+ new_tuple = tuple_alloc_with_items(n2 - n1 + 1);
+ if (new_tuple == NULL)
+ return NULL;
+
+ for (int i = n1; i <= n2; i++)
+ {
+ typval_T new_tv;
+
+ copy_tv(TUPLE_ITEM(tuple, i), &new_tv);
+
+ if (tuple_append_tv(new_tuple, &new_tv) == FAIL)
+ {
+ tuple_free(new_tuple);
+ return NULL;
+ }
+ }
+
+ return new_tuple;
+}
+
+ int
+tuple_slice_or_index(
+ tuple_T *tuple,
+ int range,
+ varnumber_T n1_arg,
+ varnumber_T n2_arg,
+ int exclusive,
+ typval_T *rettv,
+ int verbose)
+{
+ long len = tuple_len(tuple);
+ varnumber_T n1 = n1_arg;
+ varnumber_T n2 = n2_arg;
+ typval_T var1;
+
+ if (n1 < 0)
+ n1 = len + n1;
+ if (n1 < 0 || n1 >= len)
+ {
+ // For a range we allow invalid values and for legacy script return an
+ // empty tuple, for Vim9 script start at the first item.
+ // A tuple index out of range is an error.
+ if (!range)
+ {
+ if (verbose)
+ semsg(_(e_tuple_index_out_of_range_nr), (long)n1_arg);
+ return FAIL;
+ }
+ if (in_vim9script())
+ n1 = n1 < 0 ? 0 : len;
+ else
+ n1 = len;
+ }
+ if (range)
+ {
+ tuple_T *new_tuple;
+
+ if (n2 < 0)
+ n2 = len + n2;
+ else if (n2 >= len)
+ n2 = len - (exclusive ? 0 : 1);
+ if (exclusive)
+ --n2;
+ if (n2 < 0 || n2 + 1 < n1)
+ n2 = -1;
+ new_tuple = tuple_slice(tuple, n1, n2);
+ if (new_tuple == NULL)
+ return FAIL;
+ clear_tv(rettv);
+ rettv_tuple_set(rettv, new_tuple);
+ }
+ else
+ {
+ // copy the item to "var1" to avoid that freeing the tuple makes it
+ // invalid.
+ copy_tv(tuple_find(tuple, n1), &var1);
+ clear_tv(rettv);
+ *rettv = var1;
+ }
+ return OK;
+}
+
+/*
+ * Make a copy of tuple "orig". Shallow if "deep" is FALSE.
+ * The refcount of the new tuple is set to 1.
+ * See item_copy() for "top" and "copyID".
+ * Returns NULL when out of memory.
+ */
+ tuple_T *
+tuple_copy(tuple_T *orig, int deep, int top, int copyID)
+{
+ tuple_T *copy;
+ int idx;
+
+ if (orig == NULL)
+ return NULL;
+
+ copy = tuple_alloc_with_items(TUPLE_LEN(orig));
+ if (copy == NULL)
+ return NULL;
+
+ if (orig->tv_type == NULL || top || deep)
+ copy->tv_type = NULL;
+ else
+ copy->tv_type = alloc_type(orig->tv_type);
+ if (copyID != 0)
+ {
+ // Do this before adding the items, because one of the items may
+ // refer back to this tuple.
+ orig->tv_copyID = copyID;
+ orig->tv_copytuple = copy;
+ }
+
+ for (idx = 0; idx < TUPLE_LEN(orig) && !got_int; idx++)
+ {
+ copy->tv_items.ga_len++;
+ if (deep)
+ {
+ if (item_copy(TUPLE_ITEM(orig, idx), TUPLE_ITEM(copy, idx),
+ deep, FALSE, copyID) == FAIL)
+ break;
+ }
+ else
+ copy_tv(TUPLE_ITEM(orig, idx), TUPLE_ITEM(copy, idx));
+ }
+
+ ++copy->tv_refcount;
+ if (idx != TUPLE_LEN(orig))
+ {
+ tuple_unref(copy);
+ copy = NULL;
+ }
+
+ return copy;
+}
+
+/*
+ * Allocate a variable for a tuple and fill it from "*arg".
+ * "*arg" points to the "," after the first element.
+ * "rettv" contains the first element.
+ * Returns OK or FAIL.
+ */
+ int
+eval_tuple(char_u **arg, typval_T *rettv, evalarg_T *evalarg, int do_error)
+{
+ int evaluate = evalarg == NULL ? FALSE
+ : evalarg->eval_flags & EVAL_EVALUATE;
+ tuple_T *tuple = NULL;
+ typval_T tv;
+ int vim9script = in_vim9script();
+ int had_comma;
+
+ if (check_typval_is_value(rettv) == FAIL)
+ {
+ // the first item is not a valid value type
+ clear_tv(rettv);
+ return FAIL;
+ }
+
+ if (evaluate)
+ {
+ tuple = tuple_alloc();
+ if (tuple == NULL)
+ return FAIL;
+
+ if (rettv->v_type != VAR_UNKNOWN)
+ {
+ // Add the first item to the tuple from "rettv"
+ if (tuple_append_tv(tuple, rettv) == FAIL)
+ return FAIL;
+ }
+ }
+
+ if (**arg == ')')
+ // empty tuple
+ goto done;
+
+ if (vim9script && !IS_WHITE_NL_OR_NUL((*arg)[1]) && (*arg)[1] != ')')
+ {
+ semsg(_(e_white_space_required_after_str_str), ",", *arg);
+ goto failret;
+ }
+
+ *arg = skipwhite_and_linebreak(*arg + 1, evalarg);
+ while (**arg != ')' && **arg != NUL)
+ {
+ if (eval1(arg, &tv, evalarg) == FAIL) // recursive!
+ goto failret;
+ if (check_typval_is_value(&tv) == FAIL)
+ {
+ if (evaluate)
+ clear_tv(&tv);
+ goto failret;
+ }
+
+ if (evaluate)
+ {
+ if (tuple_append_tv(tuple, &tv) == FAIL)
+ {
+ clear_tv(&tv);
+ goto failret;
+ }
+ }
+
+ if (!vim9script)
+ *arg = skipwhite(*arg);
+
+ // the comma must come after the value
+ had_comma = **arg == ',';
+ if (had_comma)
+ {
+ if (vim9script && !IS_WHITE_NL_OR_NUL((*arg)[1]) && (*arg)[1] != ')')
+ {
+ semsg(_(e_white_space_required_after_str_str), ",", *arg);
+ goto failret;
+ }
+ *arg = skipwhite(*arg + 1);
+ }
+
+ // The ")" can be on the next line. But a double quoted string may
+ // follow, not a comment.
+ *arg = skipwhite_and_linebreak(*arg, evalarg);
+ if (**arg == ')')
+ break;
+
+ if (!had_comma)
+ {
+ if (do_error)
+ {
+ if (**arg == ',')
+ semsg(_(e_no_white_space_allowed_before_str_str),
+ ",", *arg);
+ else
+ semsg(_(e_missing_comma_in_tuple_str), *arg);
+ }
+ goto failret;
+ }
+ }
+
+ if (**arg != ')')
+ {
+ if (do_error)
+ semsg(_(e_missing_end_of_tuple_rsp_str), *arg);
+failret:
+ if (evaluate)
+ tuple_free(tuple);
+ return FAIL;
+ }
+
+done:
+ *arg += 1;
+ if (evaluate)
+ rettv_tuple_set(rettv, tuple);
+
+ return OK;
+}
+
+/*
+ * Lock or unlock a tuple. "deep" is number of levels to go.
+ * When "check_refcount" is TRUE do not lock a tuple with a reference
+ * count larger than 1.
+ */
+ void
+tuple_lock(tuple_T *tuple, int deep, int lock, int check_refcount)
+{
+ if (tuple == NULL || (check_refcount && tuple->tv_refcount > 1))
+ return;
+
+ if (lock)
+ tuple->tv_lock |= VAR_LOCKED;
+ else
+ tuple->tv_lock &= ~VAR_LOCKED;
+
+ if (deep < 0 || deep > 1)
+ {
+ // recursive: lock/unlock the items the Tuple contains
+ for (int i = 0; i < TUPLE_LEN(tuple); i++)
+ item_lock(TUPLE_ITEM(tuple, i), deep - 1, lock, check_refcount);
+ }
+}
+
+typedef struct join_S {
+ char_u *s;
+ char_u *tofree;
+} join_T;
+
+ static int
+tuple_join_inner(
+ garray_T *gap, // to store the result in
+ tuple_T *tuple,
+ char_u *sep,
+ int echo_style,
+ int restore_copyID,
+ int copyID,
+ garray_T *join_gap) // to keep each tuple item string
+{
+ int i;
+ join_T *p;
+ int len;
+ int sumlen = 0;
+ int first = TRUE;
+ char_u *tofree;
+ char_u numbuf[NUMBUFLEN];
+ char_u *s;
+ typval_T *tv;
+
+ // Stringify each item in the tuple.
+ for (i = 0; i < TUPLE_LEN(tuple) && !got_int; i++)
+ {
+ tv = TUPLE_ITEM(tuple, i);
+ s = echo_string_core(tv, &tofree, numbuf, copyID,
+ echo_style, restore_copyID, !echo_style);
+ if (s == NULL)
+ return FAIL;
+
+ len = (int)STRLEN(s);
+ sumlen += len;
+
+ (void)ga_grow(join_gap, 1);
+ p = ((join_T *)join_gap->ga_data) + (join_gap->ga_len++);
+ if (tofree != NULL || s != numbuf)
+ {
+ p->s = s;
+ p->tofree = tofree;
+ }
+ else
+ {
+ p->s = vim_strnsave(s, len);
+ p->tofree = p->s;
+ }
+
+ line_breakcheck();
+ if (did_echo_string_emsg) // recursion error, bail out
+ break;
+ }
+
+ // Allocate result buffer with its total size, avoid re-allocation and
+ // multiple copy operations. Add 2 for a tailing ')' and NUL.
+ if (join_gap->ga_len >= 2)
+ sumlen += (int)STRLEN(sep) * (join_gap->ga_len - 1);
+ if (ga_grow(gap, sumlen + 2) == FAIL)
+ return FAIL;
+
+ for (i = 0; i < join_gap->ga_len && !got_int; ++i)
+ {
+ if (first)
+ first = FALSE;
+ else
+ ga_concat(gap, sep);
+ p = ((join_T *)join_gap->ga_data) + i;
+
+ if (p->s != NULL)
+ ga_concat(gap, p->s);
+ line_breakcheck();
+ }
+
+ // If there is only one item in the tuple, then add the separator after
+ // that.
+ if (join_gap->ga_len == 1)
+ ga_concat(gap, sep);
+
+ return OK;
+}
+
+/*
+ * Join tuple "tuple" into a string in "*gap", using separator "sep".
+ * When "echo_style" is TRUE use String as echoed, otherwise as inside a Tuple.
+ * Return FAIL or OK.
+ */
+ int
+tuple_join(
+ garray_T *gap,
+ tuple_T *tuple,
+ char_u *sep,
+ int echo_style,
+ int restore_copyID,
+ int copyID)
+{
+ garray_T join_ga;
+ int retval;
+ join_T *p;
+ int i;
+
+ if (TUPLE_LEN(tuple) < 1)
+ return OK; // nothing to do
+ ga_init2(&join_ga, sizeof(join_T), TUPLE_LEN(tuple));
+ retval = tuple_join_inner(gap, tuple, sep, echo_style, restore_copyID,
+ copyID, &join_ga);
+
+ if (join_ga.ga_data == NULL)
+ return retval;
+
+ // Dispose each item in join_ga.
+ p = (join_T *)join_ga.ga_data;
+ for (i = 0; i < join_ga.ga_len; ++i)
+ {
+ vim_free(p->tofree);
+ ++p;
+ }
+ ga_clear(&join_ga);
+
+ return retval;
+}
+
+/*
+ * Return an allocated string with the string representation of a tuple.
+ * May return NULL.
+ */
+ char_u *
+tuple2string(typval_T *tv, int copyID, int restore_copyID)
+{
+ garray_T ga;
+
+ if (tv->vval.v_tuple == NULL)
+ return NULL;
+ ga_init2(&ga, sizeof(char), 80);
+ ga_append(&ga, '(');
+ if (tuple_join(&ga, tv->vval.v_tuple, (char_u *)", ",
+ FALSE, restore_copyID, copyID) == FAIL)
+ {
+ vim_free(ga.ga_data);
+ return NULL;
+ }
+ ga_append(&ga, ')');
+ ga_append(&ga, NUL);
+ return (char_u *)ga.ga_data;
+}
+
+/*
+ * Implementation of foreach() for a Tuple. Apply "expr" to
+ * every item in Tuple "tuple" and return the result in "rettv".
+ */
+ void
+tuple_foreach(
+ tuple_T *tuple,
+ filtermap_T filtermap,
+ typval_T *expr)
+{
+ int len = tuple_len(tuple);
+ int rem;
+ typval_T newtv;
+ funccall_T *fc;
+
+ // set_vim_var_nr() doesn't set the type
+ set_vim_var_type(VV_KEY, VAR_NUMBER);
+
+ // Create one funccall_T for all eval_expr_typval() calls.
+ fc = eval_expr_get_funccal(expr, &newtv);
+
+ for (int idx = 0; idx < len; idx++)
+ {
+ set_vim_var_nr(VV_KEY, idx);
+ if (filter_map_one(TUPLE_ITEM(tuple, idx), expr, filtermap, fc,
+ &newtv, &rem) == FAIL)
+ break;
+ }
+
+ if (fc != NULL)
+ remove_funccal();
+}
+
+/*
+ * Count the number of times item "needle" occurs in Tuple "l" starting at index
+ * "idx". Case is ignored if "ic" is TRUE.
+ */
+ long
+tuple_count(tuple_T *tuple, typval_T *needle, long idx, int ic)
+{
+ long n = 0;
+
+ if (tuple == NULL)
+ return 0;
+
+ int len = TUPLE_LEN(tuple);
+ if (len == 0)
+ return 0;
+
+ if (idx < 0 || idx >= len)
+ {
+ semsg(_(e_tuple_index_out_of_range_nr), idx);
+ return 0;
+ }
+
+ for (int i = idx; i < len; i++)
+ {
+ if (tv_equal(TUPLE_ITEM(tuple, i), needle, ic))
+ ++n;
+ }
+
+ return n;
+}
+
+/*
+ * "items(tuple)" function
+ * Caller must have already checked that argvars[0] is a tuple.
+ */
+ void
+tuple2items(typval_T *argvars, typval_T *rettv)
+{
+ tuple_T *tuple = argvars[0].vval.v_tuple;
+ varnumber_T idx;
+
+ if (rettv_list_alloc(rettv) == FAIL)
+ return;
+
+ if (tuple == NULL)
+ return; // null tuple behaves like an empty list
+
+ for (idx = 0; idx < TUPLE_LEN(tuple); idx++)
+ {
+ list_T *l = list_alloc();
+
+ if (l == NULL)
+ break;
+
+ if (list_append_list(rettv->vval.v_list, l) == FAIL)
+ {
+ vim_free(l);
+ break;
+ }
+ if (list_append_number(l, idx) == FAIL
+ || list_append_tv(l, TUPLE_ITEM(tuple, idx)) == FAIL)
+ break;
+ }
+}
+
+/*
+ * Search for item "tv" in tuple "tuple" starting from index "start_idx".
+ * If "ic" is set to TRUE, then case is ignored.
+ *
+ * Returns the index where "tv" is present or -1 if it is not found.
+ */
+ int
+index_tuple(tuple_T *tuple, typval_T *tv, int start_idx, int ic)
+{
+ if (start_idx < 0)
+ {
+ start_idx = TUPLE_LEN(tuple) + start_idx;
+ if (start_idx < 0)
+ start_idx = 0;
+ }
+
+ for (int idx = start_idx; idx < TUPLE_LEN(tuple); idx++)
+ {
+ if (tv_equal(TUPLE_ITEM(tuple, idx), tv, ic))
+ return idx;
+ }
+
+ return -1; // "tv" not found
+}
+
+/*
+ * Evaluate 'expr' for each item in the Tuple 'tuple' starting with the item at
+ * 'startidx' and return the index of the item where 'expr' is TRUE. Returns
+ * -1 if 'expr' doesn't evaluate to TRUE for any of the items.
+ */
+ int
+indexof_tuple(tuple_T *tuple, long startidx, typval_T *expr)
+{
+ long idx = 0;
+ int len;
+ int found;
+
+ if (tuple == NULL)
+ return -1;
+
+ len = TUPLE_LEN(tuple);
+
+ if (startidx < 0)
+ {
+ // negative index: index from the end
+ startidx = len + startidx;
+ if (startidx < 0)
+ startidx = 0;
+ }
+
+ set_vim_var_type(VV_KEY, VAR_NUMBER);
+
+ int called_emsg_start = called_emsg;
+
+ for (idx = startidx; idx < len; idx++)
+ {
+ set_vim_var_nr(VV_KEY, idx);
+ copy_tv(TUPLE_ITEM(tuple, idx), get_vim_var_tv(VV_VAL));
+
+ found = indexof_eval_expr(expr);
+ clear_tv(get_vim_var_tv(VV_VAL));
+
+ if (found)
+ return idx;
+
+ if (called_emsg != called_emsg_start)
+ return -1;
+ }
+
+ return -1;
+}
+
+/*
+ * Return the max or min of the items in tuple "tuple".
+ * If a tuple item is not a number, then "error" is set to TRUE.
+ */
+ varnumber_T
+tuple_max_min(tuple_T *tuple, int domax, int *error)
+{
+ varnumber_T n = 0;
+ varnumber_T v;
+
+ if (tuple == NULL || TUPLE_LEN(tuple) == 0)
+ return 0;
+
+ n = tv_get_number_chk(TUPLE_ITEM(tuple, 0), error);
+ if (*error)
+ return n; // type error; errmsg already given
+
+ for (int idx = 1; idx < TUPLE_LEN(tuple); idx++)
+ {
+ v = tv_get_number_chk(TUPLE_ITEM(tuple, idx), error);
+ if (*error)
+ return n; // type error; errmsg already given
+ if (domax ? v > n : v < n)
+ n = v;
+ }
+
+ return n;
+}
+
+/*
+ * Repeat the tuple "tuple" "n" times and set "rettv" to the new tuple.
+ */
+ void
+tuple_repeat(tuple_T *tuple, int n, typval_T *rettv)
+{
+ rettv->v_type = VAR_TUPLE;
+ rettv->vval.v_tuple = NULL;
+
+ if (tuple == NULL || TUPLE_LEN(tuple) == 0 || n <= 0)
+ return;
+
+ if (rettv_tuple_set_with_items(rettv, TUPLE_LEN(tuple) * n) == FAIL)
+ return;
+
+ tuple_T *new_tuple = rettv->vval.v_tuple;
+ for (int count = 0; count < n; count++)
+ {
+ for (int idx = 0; idx < TUPLE_LEN(tuple); idx++)
+ {
+ copy_tv(TUPLE_ITEM(tuple, idx),
+ TUPLE_ITEM(new_tuple, TUPLE_LEN(new_tuple)));
+ new_tuple->tv_items.ga_len++;
+ }
+ }
+}
+
+/*
+ * Reverse "tuple" and return the new tuple in "rettv"
+ */
+ void
+tuple_reverse(tuple_T *tuple, typval_T *rettv)
+{
+ rettv->v_type = VAR_TUPLE;
+ rettv->vval.v_tuple = NULL;
+
+ int len = tuple_len(tuple);
+
+ if (len == 0)
+ return;
+
+ if (rettv_tuple_set_with_items(rettv, len) == FAIL)
+ return;
+
+ tuple_T *new_tuple = rettv->vval.v_tuple;
+ for (int i = 0; i < len; i++)
+ copy_tv(TUPLE_ITEM(tuple, i), TUPLE_ITEM(new_tuple, len - i - 1));
+ new_tuple->tv_items.ga_len = tuple->tv_items.ga_len;
+}
+
+/*
+ * Tuple reduce() function
+ */
+ void
+tuple_reduce(typval_T *argvars, typval_T *expr, typval_T *rettv)
+{
+ tuple_T *tuple = argvars[0].vval.v_tuple;
+ int called_emsg_start = called_emsg;
+ typval_T initial;
+ int idx = 0;
+ funccall_T *fc;
+ typval_T argv[3];
+ int r;
+
+ if (argvars[2].v_type == VAR_UNKNOWN)
+ {
+ if (tuple == NULL || TUPLE_LEN(tuple) == 0)
+ {
+ semsg(_(e_reduce_of_an_empty_str_with_no_initial_value), "Tuple");
+ return;
+ }
+ initial = *TUPLE_ITEM(tuple, 0);
+ idx = 1;
+ }
+ else
+ {
+ initial = argvars[2];
+ idx = 0;
+ }
+
+ copy_tv(&initial, rettv);
+
+ if (tuple == NULL)
+ return;
+
+ // Create one funccall_T for all eval_expr_typval() calls.
+ fc = eval_expr_get_funccal(expr, rettv);
+
+ for ( ; idx < TUPLE_LEN(tuple); idx++)
+ {
+ argv[0] = *rettv;
+ rettv->v_type = VAR_UNKNOWN;
+ argv[1] = *TUPLE_ITEM(tuple, idx);
+
+ r = eval_expr_typval(expr, TRUE, argv, 2, fc, rettv);
+
+ clear_tv(&argv[0]);
+
+ if (r == FAIL || called_emsg != called_emsg_start)
+ break;
+ }
+
+ if (fc != NULL)
+ remove_funccal();
+}
+
+/*
+ * Returns TRUE if two tuples with types "type1" and "type2" are addable.
+ * Otherwise returns FALSE.
+ */
+ int
+check_tuples_addable(type_T *type1, type_T *type2)
+{
+ int addable = TRUE;
+
+ // If the first operand is a variadic tuple and the second argument is
+ // non-variadic, then concatenation is not possible.
+ if ((type1->tt_flags & TTFLAG_VARARGS)
+ && !(type2->tt_flags & TTFLAG_VARARGS)
+ && (type2->tt_argcount > 0))
+ addable = FALSE;
+
+ if ((type1->tt_flags & TTFLAG_VARARGS)
+ && (type2->tt_flags & TTFLAG_VARARGS))
+ {
+ // two variadic tuples
+ if (type1->tt_argcount > 1 || type2->tt_argcount > 1)
+ // one of the variadic tuple has fixed number of items
+ addable = FALSE;
+ else if ((type1->tt_argcount == 1 && type2->tt_argcount == 1)
+ && !equal_type(type1->tt_args[0], type2->tt_args[0], 0))
+ // the tuples have different item types
+ addable = FALSE;
+ }
+
+ if (!addable)
+ {
+ emsg(_(e_cannot_use_variadic_tuple_in_concatenation));
+ return FAIL;
+ }
+
+ return OK;
+}
+
+#endif // defined(FEAT_EVAL)
diff --git a/src/typval.c b/src/typval.c
index cd39a0d..59ac611 100644
--- a/src/typval.c
+++ b/src/typval.c
@@ -72,6 +72,9 @@
case VAR_LIST:
list_unref(varp->vval.v_list);
break;
+ case VAR_TUPLE:
+ tuple_unref(varp->vval.v_tuple);
+ break;
case VAR_DICT:
dict_unref(varp->vval.v_dict);
break;
@@ -138,6 +141,10 @@
list_unref(varp->vval.v_list);
varp->vval.v_list = NULL;
break;
+ case VAR_TUPLE:
+ tuple_unref(varp->vval.v_tuple);
+ varp->vval.v_tuple = NULL;
+ break;
case VAR_DICT:
dict_unref(varp->vval.v_dict);
varp->vval.v_dict = NULL;
@@ -234,6 +241,9 @@
case VAR_LIST:
emsg(_(e_using_list_as_number));
break;
+ case VAR_TUPLE:
+ emsg(_(e_using_tuple_as_number));
+ break;
case VAR_DICT:
emsg(_(e_using_dictionary_as_number));
break;
@@ -368,6 +378,9 @@
case VAR_LIST:
emsg(_(e_using_list_as_float));
break;
+ case VAR_TUPLE:
+ emsg(_(e_using_tuple_as_float));
+ break;
case VAR_DICT:
emsg(_(e_using_dictionary_as_float));
break;
@@ -529,7 +542,7 @@
/*
* Give an error and return FAIL unless "args[idx]" is a bool or a number.
*/
- int
+ static int
check_for_bool_or_number_arg(typval_T *args, int idx)
{
if (args[idx].v_type != VAR_BOOL && args[idx].v_type != VAR_NUMBER)
@@ -620,6 +633,20 @@
}
/*
+ * Give an error and return FAIL unless "args[idx]" is a tuple.
+ */
+ int
+check_for_tuple_arg(typval_T *args, int idx)
+{
+ if (args[idx].v_type != VAR_TUPLE)
+ {
+ semsg(_(e_tuple_required_for_argument_nr), idx + 1);
+ return FAIL;
+ }
+ return OK;
+}
+
+/*
* Give an error and return FAIL unless "args[idx]" is a dict.
*/
int
@@ -827,17 +854,18 @@
}
/*
- * Give an error and return FAIL unless "args[idx]" is a string, a list or a
- * blob.
+ * Give an error and return FAIL unless "args[idx]" is a string, a list, a
+ * tuple or a blob.
*/
int
-check_for_string_or_list_or_blob_arg(typval_T *args, int idx)
+check_for_string_or_list_or_tuple_or_blob_arg(typval_T *args, int idx)
{
if (args[idx].v_type != VAR_STRING
&& args[idx].v_type != VAR_LIST
+ && args[idx].v_type != VAR_TUPLE
&& args[idx].v_type != VAR_BLOB)
{
- semsg(_(e_string_list_or_blob_required_for_argument_nr), idx + 1);
+ semsg(_(e_string_list_tuple_or_blob_required_for_argument_nr), idx + 1);
return FAIL;
}
return OK;
@@ -897,35 +925,37 @@
}
/*
- * Give an error and return FAIL unless "args[idx]" is a string or a number
- * or a list or a blob.
+ * Give an error and return FAIL unless "args[idx]" is a string, a number, a
+ * list, a tuple or a blob.
*/
int
-check_for_string_or_number_or_list_or_blob_arg(typval_T *args, int idx)
+check_for_repeat_func_arg(typval_T *args, int idx)
{
if (args[idx].v_type != VAR_STRING
&& args[idx].v_type != VAR_NUMBER
&& args[idx].v_type != VAR_LIST
+ && args[idx].v_type != VAR_TUPLE
&& args[idx].v_type != VAR_BLOB)
{
- semsg(_(e_string_number_list_or_blob_required_for_argument_nr), idx + 1);
+ semsg(_(e_repeatable_type_required_for_argument_nr), idx + 1);
return FAIL;
}
return OK;
}
/*
- * Give an error and return FAIL unless "args[idx]" is a string or a list
- * or a dict.
+ * Give an error and return FAIL unless "args[idx]" is a string, a list, a
+ * tuple or a dict.
*/
int
-check_for_string_or_list_or_dict_arg(typval_T *args, int idx)
+check_for_string_list_tuple_or_dict_arg(typval_T *args, int idx)
{
if (args[idx].v_type != VAR_STRING
&& args[idx].v_type != VAR_LIST
+ && args[idx].v_type != VAR_TUPLE
&& args[idx].v_type != VAR_DICT)
{
- semsg(_(e_string_list_or_dict_required_for_argument_nr), idx + 1);
+ semsg(_(e_string_list_tuple_or_dict_required_for_argument_nr), idx + 1);
return FAIL;
}
return OK;
@@ -963,15 +993,48 @@
}
/*
- * Give an error and return FAIL unless "args[idx]" is a list or dict
+ * Give an error and return FAIL unless "args[idx]" is a list or a tuple.
*/
int
-check_for_list_or_dict_arg(typval_T *args, int idx)
+check_for_list_or_tuple_arg(typval_T *args, int idx)
+{
+ if (args[idx].v_type != VAR_LIST && args[idx].v_type != VAR_TUPLE)
+ {
+ semsg(_(e_list_or_tuple_required_for_argument_nr), idx + 1);
+ return FAIL;
+ }
+ return OK;
+}
+
+/*
+ * Give an error and return FAIL unless "args[idx]" is a list, a tuple or a
+ * blob.
+ */
+ int
+check_for_list_or_tuple_or_blob_arg(typval_T *args, int idx)
{
if (args[idx].v_type != VAR_LIST
+ && args[idx].v_type != VAR_TUPLE
+ && args[idx].v_type != VAR_BLOB)
+ {
+ semsg(_(e_list_or_tuple_or_blob_required_for_argument_nr), idx + 1);
+ return FAIL;
+ }
+ return OK;
+}
+
+/*
+ * Give an error and return FAIL unless "args[idx]" is a list, a tuple or a
+ * dict
+ */
+ int
+check_for_list_or_tuple_or_dict_arg(typval_T *args, int idx)
+{
+ if (args[idx].v_type != VAR_LIST
+ && args[idx].v_type != VAR_TUPLE
&& args[idx].v_type != VAR_DICT)
{
- semsg(_(e_list_or_dict_required_for_argument_nr), idx + 1);
+ semsg(_(e_list_or_tuple_or_dict_required_for_argument_nr), idx + 1);
return FAIL;
}
return OK;
@@ -995,18 +1058,19 @@
}
/*
- * Give an error and return FAIL unless "args[idx]" is a list or dict or a
- * blob or a string.
+ * Give an error and return FAIL unless "args[idx]" is a list, a tuple, a dict,
+ * a blob or a string.
*/
int
-check_for_list_or_dict_or_blob_or_string_arg(typval_T *args, int idx)
+check_for_list_tuple_dict_blob_or_string_arg(typval_T *args, int idx)
{
if (args[idx].v_type != VAR_LIST
+ && args[idx].v_type != VAR_TUPLE
&& args[idx].v_type != VAR_DICT
&& args[idx].v_type != VAR_BLOB
&& args[idx].v_type != VAR_STRING)
{
- semsg(_(e_list_dict_blob_or_string_required_for_argument_nr), idx + 1);
+ semsg(_(e_list_tuple_dict_blob_or_string_required_for_argument_nr), idx + 1);
return FAIL;
}
return OK;
@@ -1149,6 +1213,9 @@
case VAR_LIST:
emsg(_(e_using_list_as_string));
break;
+ case VAR_TUPLE:
+ emsg(_(e_using_tuple_as_string));
+ break;
case VAR_DICT:
emsg(_(e_using_dictionary_as_string));
break;
@@ -1267,6 +1334,10 @@
if (tv->vval.v_list != NULL)
lock = tv->vval.v_list->lv_lock;
break;
+ case VAR_TUPLE:
+ if (tv->vval.v_tuple != NULL)
+ lock = tv->vval.v_tuple->tv_lock;
+ break;
case VAR_DICT:
if (tv->vval.v_dict != NULL)
lock = tv->vval.v_dict->dv_lock;
@@ -1364,6 +1435,15 @@
++to->vval.v_list->lv_refcount;
}
break;
+ case VAR_TUPLE:
+ if (from->vval.v_tuple == NULL)
+ to->vval.v_tuple = NULL;
+ else
+ {
+ to->vval.v_tuple = from->vval.v_tuple;
+ ++to->vval.v_tuple->tv_refcount;
+ }
+ break;
case VAR_DICT:
if (from->vval.v_dict == NULL)
to->vval.v_dict = NULL;
@@ -1452,6 +1532,15 @@
}
n1 = res;
}
+ else if (tv1->v_type == VAR_TUPLE || tv2->v_type == VAR_TUPLE)
+ {
+ if (typval_compare_tuple(tv1, tv2, type, ic, &res) == FAIL)
+ {
+ clear_tv(tv1);
+ return FAIL;
+ }
+ n1 = res;
+ }
else if (tv1->v_type == VAR_OBJECT || tv2->v_type == VAR_OBJECT)
{
if (typval_compare_object(tv1, tv2, type, ic, &res) == FAIL)
@@ -1650,6 +1739,47 @@
}
/*
+ * Compare "tv1" to "tv2" as tuples according to "type" and "ic".
+ * Put the result, false or true, in "res".
+ * Return FAIL and give an error message when the comparison can't be done.
+ */
+ int
+typval_compare_tuple(
+ typval_T *tv1,
+ typval_T *tv2,
+ exprtype_T type,
+ int ic,
+ int *res)
+{
+ int val = 0;
+
+ if (type == EXPR_IS || type == EXPR_ISNOT)
+ {
+ val = (tv1->v_type == tv2->v_type
+ && tv1->vval.v_tuple == tv2->vval.v_tuple);
+ if (type == EXPR_ISNOT)
+ val = !val;
+ }
+ else if (tv1->v_type != tv2->v_type
+ || (type != EXPR_EQUAL && type != EXPR_NEQUAL))
+ {
+ if (tv1->v_type != tv2->v_type)
+ emsg(_(e_can_only_compare_tuple_with_tuple));
+ else
+ emsg(_(e_invalid_operation_for_tuple));
+ return FAIL;
+ }
+ else
+ {
+ val = tuple_equal(tv1->vval.v_tuple, tv2->vval.v_tuple, ic);
+ if (type == EXPR_NEQUAL)
+ val = !val;
+ }
+ *res = val;
+ return OK;
+}
+
+/*
* Compare v:null with another type. Return TRUE if the value is NULL.
*/
int
@@ -1674,6 +1804,7 @@
case VAR_JOB: return tv->vval.v_job == NULL;
#endif
case VAR_LIST: return tv->vval.v_list == NULL;
+ case VAR_TUPLE: return tv->vval.v_tuple == NULL;
case VAR_OBJECT: return tv->vval.v_object == NULL;
case VAR_PARTIAL: return tv->vval.v_partial == NULL;
case VAR_STRING: return tv->vval.v_string == NULL;
@@ -2078,6 +2209,12 @@
--recursive_cnt;
return r;
+ case VAR_TUPLE:
+ ++recursive_cnt;
+ r = tuple_equal(tv1->vval.v_tuple, tv2->vval.v_tuple, ic);
+ --recursive_cnt;
+ return r;
+
case VAR_DICT:
++recursive_cnt;
r = dict_equal(tv1->vval.v_dict, tv2->vval.v_dict, ic);
diff --git a/src/userfunc.c b/src/userfunc.c
index d8d7014..b328cf5 100644
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -7286,9 +7286,9 @@
for (fc = previous_funccal; fc != NULL; fc = fc->fc_caller)
{
fc->fc_copyID = copyID + 1;
- if (set_ref_in_ht(&fc->fc_l_vars.dv_hashtab, copyID + 1, NULL)
- || set_ref_in_ht(&fc->fc_l_avars.dv_hashtab, copyID + 1, NULL)
- || set_ref_in_list_items(&fc->fc_l_varlist, copyID + 1, NULL))
+ if (set_ref_in_ht(&fc->fc_l_vars.dv_hashtab, copyID + 1, NULL, NULL)
+ || set_ref_in_ht(&fc->fc_l_avars.dv_hashtab, copyID + 1, NULL, NULL)
+ || set_ref_in_list_items(&fc->fc_l_varlist, copyID + 1, NULL, NULL))
return TRUE;
}
return FALSE;
@@ -7300,9 +7300,9 @@
if (fc->fc_copyID != copyID)
{
fc->fc_copyID = copyID;
- if (set_ref_in_ht(&fc->fc_l_vars.dv_hashtab, copyID, NULL)
- || set_ref_in_ht(&fc->fc_l_avars.dv_hashtab, copyID, NULL)
- || set_ref_in_list_items(&fc->fc_l_varlist, copyID, NULL)
+ if (set_ref_in_ht(&fc->fc_l_vars.dv_hashtab, copyID, NULL, NULL)
+ || set_ref_in_ht(&fc->fc_l_avars.dv_hashtab, copyID, NULL, NULL)
+ || set_ref_in_list_items(&fc->fc_l_varlist, copyID, NULL, NULL)
|| set_ref_in_func(NULL, fc->fc_func, copyID))
return TRUE;
}
@@ -7365,7 +7365,7 @@
for (i = 0; i < funcargs.ga_len; ++i)
if (set_ref_in_item(((typval_T **)funcargs.ga_data)[i],
- copyID, NULL, NULL))
+ copyID, NULL, NULL, NULL))
return TRUE;
return FALSE;
}
diff --git a/src/version.c b/src/version.c
index dedfd0a..7ee6d0a 100644
--- a/src/version.c
+++ b/src/version.c
@@ -705,6 +705,8 @@
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 1232,
+/**/
1231,
/**/
1230,
diff --git a/src/vim.h b/src/vim.h
index 85fad6c..dd04f65 100644
--- a/src/vim.h
+++ b/src/vim.h
@@ -2201,7 +2201,8 @@
#define VV_TYPE_ENUM 108
#define VV_TYPE_ENUMVALUE 109
#define VV_STACKTRACE 110
-#define VV_LEN 111 // number of v: vars
+#define VV_TYPE_TUPLE 111
+#define VV_LEN 112 // number of v: vars
// used for v_number in VAR_BOOL and VAR_SPECIAL
#define VVAL_FALSE 0L // VAR_BOOL
@@ -2227,6 +2228,7 @@
#define VAR_TYPE_TYPEALIAS 14
#define VAR_TYPE_ENUM 15
#define VAR_TYPE_ENUMVALUE 16
+#define VAR_TYPE_TUPLE 17
#define DICT_MAXNEST 100 // maximum nesting of lists and dicts
diff --git a/src/vim9.h b/src/vim9.h
index 7c731fa..c10d435 100644
--- a/src/vim9.h
+++ b/src/vim9.h
@@ -105,6 +105,8 @@
ISN_PUSHCLASS, // push class, uses isn_arg.classarg
ISN_NEWLIST, // push list from stack items, size is isn_arg.number
// -1 for null_list
+ ISN_NEWTUPLE, // push tuple from stack items, size is isn_arg.number
+ // -1 for null_list
ISN_NEWDICT, // push dict from stack items, size is isn_arg.number
// -1 for null_dict
ISN_NEWPARTIAL, // push NULL partial
@@ -149,6 +151,7 @@
// more expression operations
ISN_ADDLIST, // add two lists
+ ISN_ADDTUPLE, // add two tuples
ISN_ADDBLOB, // add two blobs
// operation with two arguments; isn_arg.op.op_type is exprtype_T
@@ -165,6 +168,7 @@
ISN_COMPARESTRING,
ISN_COMPAREBLOB,
ISN_COMPARELIST,
+ ISN_COMPARETUPLE,
ISN_COMPAREDICT,
ISN_COMPAREFUNC,
ISN_COMPAREANY,
@@ -177,6 +181,8 @@
ISN_LISTAPPEND, // append to a list, like add()
ISN_LISTINDEX, // [expr] list index
ISN_LISTSLICE, // [expr:expr] list slice
+ ISN_TUPLEINDEX, // [expr] tuple index
+ ISN_TUPLESLICE, // [expr:expr] tuple slice
ISN_BLOBINDEX, // [expr] blob index
ISN_BLOBSLICE, // [expr:expr] blob slice
ISN_ANYINDEX, // [expr] runtime index
diff --git a/src/vim9class.c b/src/vim9class.c
index 1c54474..5249f40 100644
--- a/src/vim9class.c
+++ b/src/vim9class.c
@@ -3650,7 +3650,7 @@
set_ref_in_classes(int copyID)
{
for (class_T *cl = first_class; cl != NULL; cl = cl->class_next_used)
- set_ref_in_item_class(cl, copyID, NULL, NULL);
+ set_ref_in_item_class(cl, copyID, NULL, NULL, NULL);
return FALSE;
}
diff --git a/src/vim9cmds.c b/src/vim9cmds.c
index aeb742e..6295090 100644
--- a/src/vim9cmds.c
+++ b/src/vim9cmds.c
@@ -871,6 +871,40 @@
}
/*
+ * When compiling a for loop to iterate over a tuple, get the type of the loop
+ * variable to use.
+ */
+ static type_T *
+compile_for_tuple_get_vartype(type_T *vartype, int var_list)
+{
+ // If this is not a variadic tuple, or all the tuple items don't have
+ // the same type, then use t_any
+ if (!(vartype->tt_flags & TTFLAG_VARARGS) || vartype->tt_argcount != 1)
+ return &t_any;
+
+ // variadic tuple
+ type_T *member_type = vartype->tt_args[0]->tt_member;
+ if (member_type->tt_type == VAR_ANY)
+ return &t_any;
+
+ if (!var_list)
+ // for x in tuple<...list<xxx>>
+ return member_type;
+
+ if (member_type->tt_type == VAR_LIST
+ && member_type->tt_member->tt_type != VAR_ANY)
+ // for [x, y] in tuple<...list<list<xxx>>>
+ return member_type->tt_member;
+ else if (member_type->tt_type == VAR_TUPLE
+ && member_type->tt_flags & TTFLAG_VARARGS
+ && member_type->tt_argcount == 1)
+ // for [x, y] in tuple<...list<tuple<...list<xxx>>>>
+ return member_type->tt_args[0]->tt_member;
+
+ return &t_any;
+}
+
+/*
* Compile "for var in expr":
*
* Produces instructions:
@@ -1000,6 +1034,7 @@
// give an error now.
vartype = get_type_on_stack(cctx, 0);
if (vartype->tt_type != VAR_LIST
+ && vartype->tt_type != VAR_TUPLE
&& vartype->tt_type != VAR_STRING
&& vartype->tt_type != VAR_BLOB
&& vartype->tt_type != VAR_ANY
@@ -1024,6 +1059,8 @@
&& vartype->tt_member->tt_member->tt_type != VAR_ANY)
item_type = vartype->tt_member->tt_member;
}
+ else if (vartype->tt_type == VAR_TUPLE)
+ item_type = compile_for_tuple_get_vartype(vartype, var_list);
// CMDMOD_REV must come before the FOR instruction.
generate_undo_cmdmods(cctx);
diff --git a/src/vim9compile.c b/src/vim9compile.c
index cb7b948..34a78da 100644
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -524,8 +524,10 @@
return TRUE;
if (actual->tt_type == VAR_OBJECT && expected->tt_type == VAR_OBJECT)
return TRUE;
- if ((actual->tt_type == VAR_LIST || actual->tt_type == VAR_DICT)
- && actual->tt_type == expected->tt_type)
+ if ((actual->tt_type == VAR_LIST
+ || actual->tt_type == VAR_TUPLE
+ || actual->tt_type == VAR_DICT)
+ && actual->tt_type == expected->tt_type)
// This takes care of a nested list or dict.
return use_typecheck(actual->tt_member, expected->tt_member);
return FALSE;
@@ -2642,7 +2644,10 @@
&& lhs->lhs_type != &t_blob
&& lhs->lhs_type != &t_any)
{
- semsg(_(e_cannot_use_range_with_assignment_str), var_start);
+ if (lhs->lhs_type->tt_type == VAR_TUPLE)
+ emsg(_(e_cannot_slice_tuple));
+ else
+ semsg(_(e_cannot_use_range_with_assignment_str), var_start);
return FAIL;
}
@@ -2743,7 +2748,10 @@
}
else
{
- emsg(_(e_indexable_type_required));
+ if (dest_type == VAR_TUPLE)
+ emsg(_(e_tuple_is_immutable));
+ else
+ emsg(_(e_indexable_type_required));
return FAIL;
}
@@ -2785,6 +2793,9 @@
case VAR_LIST:
r = generate_NEWLIST(cctx, 0, FALSE);
break;
+ case VAR_TUPLE:
+ r = generate_NEWTUPLE(cctx, 0, FALSE);
+ break;
case VAR_DICT:
r = generate_NEWDICT(cctx, 0, FALSE);
break;
@@ -3015,11 +3026,31 @@
return FAIL;
}
- if (need_type(stacktype, &t_list_any, FALSE, -1, 0, cctx,
- FALSE, FALSE) == FAIL)
+ if (stacktype->tt_type != VAR_LIST && stacktype->tt_type != VAR_TUPLE
+ && stacktype->tt_type != VAR_ANY)
+ {
+ emsg(_(e_list_or_tuple_required));
+ return FAIL;
+ }
+
+ if (need_type(stacktype,
+ stacktype->tt_type == VAR_TUPLE ? &t_tuple_any : &t_list_any,
+ FALSE, -1, 0, cctx, FALSE, FALSE) == FAIL)
return FAIL;
- if (stacktype->tt_member != NULL)
+ if (stacktype->tt_type == VAR_TUPLE)
+ {
+ if (stacktype->tt_argcount != 1)
+ cac->cac_rhs_type = &t_any;
+ else
+ {
+ if (stacktype->tt_flags & TTFLAG_VARARGS)
+ cac->cac_rhs_type = stacktype->tt_args[0]->tt_member;
+ else
+ cac->cac_rhs_type = stacktype->tt_args[0];
+ }
+ }
+ else if (stacktype->tt_member != NULL)
cac->cac_rhs_type = stacktype->tt_member;
return OK;
@@ -3043,7 +3074,7 @@
isn_T *isn = ((isn_T *)cac->cac_instr->ga_data) +
cac->cac_instr->ga_len - 1;
- if (isn->isn_type == ISN_NEWLIST)
+ if (isn->isn_type == ISN_NEWLIST || isn->isn_type == ISN_NEWTUPLE)
{
did_check = TRUE;
if (cac->cac_semicolon ?
@@ -3408,6 +3439,17 @@
type_T *expected;
type_T *stacktype = NULL;
+ if (cac->cac_lhs.lhs_type->tt_type == VAR_TUPLE)
+ {
+ // compound operators are not supported with a tuple
+ char_u op[2];
+
+ op[0] = *cac->cac_op;
+ op[1] = NUL;
+ semsg(_(e_wrong_variable_type_for_str_equal), op);
+ return FAIL;
+ }
+
if (*cac->cac_op == '.')
{
if (may_generate_2STRING(-1, TOSTRING_NONE, cctx) == FAIL)
@@ -3486,8 +3528,11 @@
&& lhs->lhs_type->tt_member != NULL
&& lhs->lhs_type->tt_member != &t_any
&& lhs->lhs_type->tt_member != &t_unknown)
- // Set the type in the list or dict, so that it can be checked,
- // also in legacy script.
+ // Set the type in the list or dict, so that it can be
+ // checked, also in legacy script.
+ generate_SETTYPE(cctx, lhs->lhs_type);
+ else if (lhs->lhs_type->tt_type == VAR_TUPLE
+ && lhs->lhs_type->tt_argcount != 0)
generate_SETTYPE(cctx, lhs->lhs_type);
else if (inferred_type != NULL
&& (inferred_type->tt_type == VAR_DICT
@@ -3495,8 +3540,12 @@
&& inferred_type->tt_member != NULL
&& inferred_type->tt_member != &t_unknown
&& inferred_type->tt_member != &t_any)
- // Set the type in the list or dict, so that it can be checked,
- // also in legacy script.
+ // Set the type in the list or dict, so that it can be
+ // checked, also in legacy script.
+ generate_SETTYPE(cctx, inferred_type);
+ else if (inferred_type != NULL
+ && inferred_type->tt_type == VAR_TUPLE
+ && inferred_type->tt_argcount > 0)
generate_SETTYPE(cctx, inferred_type);
if (!cac->cac_skip_store &&
@@ -4030,13 +4079,15 @@
else
push_default_value(cctx, m->ocm_type->tt_type, FALSE, NULL);
- if ((m->ocm_type->tt_type == VAR_DICT
- || m->ocm_type->tt_type == VAR_LIST)
- && m->ocm_type->tt_member != NULL
- && m->ocm_type->tt_member != &t_any
- && m->ocm_type->tt_member != &t_unknown)
- // Set the type in the list or dict, so that it can be checked,
- // also in legacy script.
+ if (((m->ocm_type->tt_type == VAR_DICT
+ || m->ocm_type->tt_type == VAR_LIST)
+ && m->ocm_type->tt_member != NULL
+ && m->ocm_type->tt_member != &t_any
+ && m->ocm_type->tt_member != &t_unknown)
+ || (m->ocm_type->tt_type == VAR_TUPLE
+ && m->ocm_type->tt_argcount > 0))
+ // Set the type in the list, tuple or dict, so that it can be
+ // checked, also in legacy script.
generate_SETTYPE(cctx, m->ocm_type);
generate_STORE_THIS(cctx, i);
diff --git a/src/vim9execute.c b/src/vim9execute.c
index c0c3103..55f9d43 100644
--- a/src/vim9execute.c
+++ b/src/vim9execute.c
@@ -110,6 +110,11 @@
// Get pointer to a local variable on the stack. Negative for arguments.
#define STACK_TV_VAR(idx) (((typval_T *)ectx->ec_stack.ga_data) + ectx->ec_frame_idx + STACK_FRAME_SIZE + idx)
+// Return value for functions used to execute instructions
+#define EXEC_FAIL 0
+#define EXEC_OK 1
+#define EXEC_DONE 2
+
void
to_string_error(vartype_T vartype)
{
@@ -207,6 +212,46 @@
}
/*
+ * Create a new tuple from "count" items at the bottom of the stack.
+ * When "count" is zero an empty tuple is added to the stack.
+ * When "count" is -1 a NULL tuple is added to the stack.
+ */
+ static int
+exe_newtuple(int count, ectx_T *ectx)
+{
+ tuple_T *tuple = NULL;
+ int idx;
+ typval_T *tv;
+
+ if (count >= 0)
+ {
+ tuple = tuple_alloc_with_items(count);
+ if (tuple == NULL)
+ return FAIL;
+ for (idx = 0; idx < count; ++idx)
+ tuple_set_item(tuple, idx, STACK_TV_BOT(idx - count));
+ }
+
+ if (count > 0)
+ ectx->ec_stack.ga_len -= count - 1;
+ else if (GA_GROW_FAILS(&ectx->ec_stack, 1))
+ {
+ tuple_unref(tuple);
+ return FAIL;
+ }
+ else
+ ++ectx->ec_stack.ga_len;
+ tv = STACK_TV_BOT(-1);
+ tv->v_type = VAR_TUPLE;
+ tv->vval.v_tuple = tuple;
+ tv->v_lock = 0;
+ if (tuple != NULL)
+ ++tuple->tv_refcount;
+
+ return OK;
+}
+
+/*
* Implementation of ISN_NEWDICT.
* Returns FAIL on total failure, MAYBE on error.
*/
@@ -923,7 +968,7 @@
int i;
for (i = 0; i < funcstack->fs_ga.ga_len; ++i)
- if (set_ref_in_item(stack + i, copyID, NULL, NULL))
+ if (set_ref_in_item(stack + i, copyID, NULL, NULL, NULL))
return TRUE; // abort
}
return FALSE;
@@ -1718,6 +1763,10 @@
if (tv->vval.v_list == NULL && sv->sv_type != &t_list_empty)
(void)rettv_list_alloc(tv);
break;
+ case VAR_TUPLE:
+ if (tv->vval.v_tuple == NULL && sv->sv_type != &t_tuple_empty)
+ (void)rettv_tuple_alloc(tv);
+ break;
case VAR_DICT:
if (tv->vval.v_dict == NULL && sv->sv_type != &t_dict_empty)
(void)rettv_dict_alloc(tv);
@@ -2464,6 +2513,11 @@
clear_tv(&otv[lidx]);
otv[lidx] = *tv;
}
+ else if (dest_type == VAR_TUPLE)
+ {
+ emsg(_(e_tuple_is_immutable));
+ status = FAIL;
+ }
else
{
status = FAIL;
@@ -2802,6 +2856,20 @@
++ectx->ec_stack.ga_len;
}
}
+ else if (ltv->v_type == VAR_TUPLE)
+ {
+ tuple_T *tuple = ltv->vval.v_tuple;
+
+ // push the next item from the tuple
+ ++idxtv->vval.v_number;
+ if (tuple == NULL || idxtv->vval.v_number >= TUPLE_LEN(tuple))
+ jump = TRUE;
+ else
+ {
+ copy_tv(TUPLE_ITEM(tuple, idxtv->vval.v_number), STACK_TV_BOT(0));
+ ++ectx->ec_stack.ga_len;
+ }
+ }
else if (ltv->v_type == VAR_STRING)
{
char_u *str = ltv->vval.v_string;
@@ -3066,7 +3134,7 @@
int i;
for (i = 0; i < loopvars->lvs_ga.ga_len; ++i)
- if (set_ref_in_item(stack + i, copyID, NULL, NULL))
+ if (set_ref_in_item(stack + i, copyID, NULL, NULL, NULL))
return TRUE; // abort
}
return FALSE;
@@ -3305,6 +3373,145 @@
}
/*
+ * Execute the ISN_UNPACK instruction for a List
+ */
+ static int
+exec_unpack_list(ectx_T *ectx, isn_T *iptr, typval_T *tv)
+{
+ int count = iptr->isn_arg.unpack.unp_count;
+ int semicolon = iptr->isn_arg.unpack.unp_semicolon;
+ list_T *l;
+ listitem_T *li;
+ int i;
+
+ l = tv->vval.v_list;
+ if (l == NULL
+ || l->lv_len < (semicolon ? count - 1 : count))
+ {
+ SOURCING_LNUM = iptr->isn_lnum;
+ emsg(_(e_list_value_does_not_have_enough_items));
+ return EXEC_FAIL;
+ }
+ else if (!semicolon && l->lv_len > count)
+ {
+ SOURCING_LNUM = iptr->isn_lnum;
+ emsg(_(e_list_value_has_more_items_than_targets));
+ return EXEC_FAIL;
+ }
+
+ CHECK_LIST_MATERIALIZE(l);
+ if (GA_GROW_FAILS(&ectx->ec_stack, count - 1))
+ return EXEC_DONE;
+ ectx->ec_stack.ga_len += count - 1;
+
+ // Variable after semicolon gets a list with the remaining
+ // items.
+ if (semicolon)
+ {
+ list_T *rem_list =
+ list_alloc_with_items(l->lv_len - count + 1);
+
+ if (rem_list == NULL)
+ return EXEC_DONE;
+ tv = STACK_TV_BOT(-count);
+ tv->vval.v_list = rem_list;
+ ++rem_list->lv_refcount;
+ tv->v_lock = 0;
+ li = l->lv_first;
+ for (i = 0; i < count - 1; ++i)
+ li = li->li_next;
+ for (i = 0; li != NULL; ++i)
+ {
+ typval_T tvcopy;
+
+ copy_tv(&li->li_tv, &tvcopy);
+ list_set_item(rem_list, i, &tvcopy);
+ li = li->li_next;
+ }
+ --count;
+ }
+
+ // Produce the values in reverse order, first item last.
+ li = l->lv_first;
+ for (i = 0; i < count; ++i)
+ {
+ tv = STACK_TV_BOT(-i - 1);
+ copy_tv(&li->li_tv, tv);
+ li = li->li_next;
+ }
+
+ list_unref(l);
+
+ return EXEC_OK;
+}
+
+/*
+ * Execute the ISN_UNPACK instruction for a Tuple
+ */
+ static int
+exec_unpack_tuple(ectx_T *ectx, isn_T *iptr, typval_T *tv)
+{
+ int count = iptr->isn_arg.unpack.unp_count;
+ int semicolon = iptr->isn_arg.unpack.unp_semicolon;
+ tuple_T *tuple;
+ int i;
+
+ tuple = tv->vval.v_tuple;
+ if (tuple == NULL
+ || TUPLE_LEN(tuple) < (semicolon ? count - 1 : count))
+ {
+ SOURCING_LNUM = iptr->isn_lnum;
+ emsg(_(e_more_targets_than_tuple_items));
+ return EXEC_FAIL;
+ }
+ else if (!semicolon && TUPLE_LEN(tuple) > count)
+ {
+ SOURCING_LNUM = iptr->isn_lnum;
+ emsg(_(e_less_targets_than_tuple_items));
+ return EXEC_FAIL;
+ }
+
+ if (GA_GROW_FAILS(&ectx->ec_stack, count - 1))
+ return EXEC_DONE;
+ ectx->ec_stack.ga_len += count - 1;
+
+ // Variable after semicolon gets a list with the remaining
+ // items.
+ if (semicolon)
+ {
+ list_T *rem_list =
+ list_alloc_with_items(TUPLE_LEN(tuple) - count + 1);
+
+ if (rem_list == NULL)
+ return EXEC_DONE;
+ tv = STACK_TV_BOT(-count);
+ tv->v_type = VAR_LIST;
+ tv->vval.v_list = rem_list;
+ ++rem_list->lv_refcount;
+ tv->v_lock = 0;
+ for (i = count - 1; i < TUPLE_LEN(tuple); ++i)
+ {
+ typval_T tvcopy;
+
+ copy_tv(TUPLE_ITEM(tuple, i), &tvcopy);
+ list_set_item(rem_list, i - (count - 1), &tvcopy);
+ }
+ --count;
+ }
+
+ // Produce the values in reverse order, first item last.
+ for (i = 0; i < count; ++i)
+ {
+ tv = STACK_TV_BOT(-i - 1);
+ copy_tv(TUPLE_ITEM(tuple, i), tv);
+ }
+
+ tuple_unref(tuple);
+
+ return EXEC_OK;
+}
+
+/*
* Execute instructions in execution context "ectx".
* Return OK or FAIL;
*/
@@ -4534,6 +4741,12 @@
goto theend;
break;
+ // create a tuple from items on the stack
+ case ISN_NEWTUPLE:
+ if (exe_newtuple(iptr->isn_arg.number, ectx) == FAIL)
+ goto theend;
+ break;
+
// create a dict from items on the stack
case ISN_NEWDICT:
{
@@ -4938,9 +5151,9 @@
size_t argidx = ufunc->uf_def_args.ga_len
+ iptr->isn_arg.jumparg.jump_arg_off
+ STACK_FRAME_SIZE;
- type_T *t = ufunc->uf_arg_types[argidx];
+ type_T *tuple = ufunc->uf_arg_types[argidx];
CLEAR_POINTER(tv);
- tv->v_type = t->tt_type;
+ tv->v_type = tuple->tt_type;
}
if (iptr->isn_type == ISN_JUMP_IF_ARG_SET ? arg_set : !arg_set)
@@ -5299,6 +5512,7 @@
break;
case ISN_COMPARELIST:
+ case ISN_COMPARETUPLE:
case ISN_COMPAREDICT:
case ISN_COMPAREFUNC:
case ISN_COMPARESTRING:
@@ -5318,6 +5532,11 @@
status = typval_compare_list(tv1, tv2,
exprtype, ic, &res);
}
+ else if (iptr->isn_type == ISN_COMPARETUPLE)
+ {
+ status = typval_compare_tuple(tv1, tv2,
+ exprtype, ic, &res);
+ }
else if (iptr->isn_type == ISN_COMPAREDICT)
{
status = typval_compare_dict(tv1, tv2,
@@ -5370,6 +5589,7 @@
break;
case ISN_ADDLIST:
+ case ISN_ADDTUPLE:
case ISN_ADDBLOB:
{
typval_T *tv1 = STACK_TV_BOT(-2);
@@ -5385,6 +5605,8 @@
else
eval_addlist(tv1, tv2);
}
+ else if (iptr->isn_type == ISN_ADDTUPLE)
+ eval_addtuple(tv1, tv2);
else
eval_addblob(tv1, tv2);
clear_tv(tv2);
@@ -5455,6 +5677,14 @@
--ectx->ec_stack.ga_len;
break;
}
+ else if (tv1->v_type == VAR_TUPLE
+ && tv2->v_type == VAR_TUPLE)
+ {
+ eval_addtuple(tv1, tv2);
+ clear_tv(tv2);
+ --ectx->ec_stack.ga_len;
+ break;
+ }
else if (tv1->v_type == VAR_BLOB
&& tv2->v_type == VAR_BLOB)
{
@@ -5574,20 +5804,25 @@
case ISN_LISTINDEX:
case ISN_LISTSLICE:
+ case ISN_TUPLEINDEX:
+ case ISN_TUPLESLICE:
case ISN_BLOBINDEX:
case ISN_BLOBSLICE:
{
int is_slice = iptr->isn_type == ISN_LISTSLICE
- || iptr->isn_type == ISN_BLOBSLICE;
+ || iptr->isn_type == ISN_TUPLESLICE
+ || iptr->isn_type == ISN_BLOBSLICE;
int is_blob = iptr->isn_type == ISN_BLOBINDEX
|| iptr->isn_type == ISN_BLOBSLICE;
+ int is_tuple = iptr->isn_type == ISN_TUPLEINDEX
+ || iptr->isn_type == ISN_TUPLESLICE;
varnumber_T n1, n2;
typval_T *val_tv;
// list index: list is at stack-2, index at stack-1
// list slice: list is at stack-3, indexes at stack-2 and
// stack-1
- // Same for blob.
+ // Same for tuple and blob.
val_tv = is_slice ? STACK_TV_BOT(-3) : STACK_TV_BOT(-2);
tv = STACK_TV_BOT(-1);
@@ -5610,6 +5845,12 @@
n1, n2, FALSE, tv) == FAIL)
goto on_error;
}
+ else if (is_tuple)
+ {
+ if (tuple_slice_or_index(val_tv->vval.v_tuple,
+ is_slice, n1, n2, FALSE, tv, TRUE) == FAIL)
+ goto on_error;
+ }
else
{
if (list_slice_or_index(val_tv->vval.v_list, is_slice,
@@ -5648,24 +5889,48 @@
case ISN_SLICE:
{
- list_T *list;
int count = iptr->isn_arg.number;
// type will have been checked to be a list
tv = STACK_TV_BOT(-1);
- list = tv->vval.v_list;
-
- // no error for short list, expect it to be checked earlier
- if (list != NULL && list->lv_len >= count)
+ if (tv->v_type == VAR_LIST)
{
- list_T *newlist = list_slice(list,
- count, list->lv_len - 1);
+ list_T *list = tv->vval.v_list;
- if (newlist != NULL)
+ // no error for short list, expect it to be checked
+ // earlier
+ if (list != NULL && list->lv_len >= count)
{
- list_unref(list);
- tv->vval.v_list = newlist;
- ++newlist->lv_refcount;
+ list_T *newlist = list_slice(list,
+ count, list->lv_len - 1);
+
+ if (newlist != NULL)
+ {
+ list_unref(list);
+ tv->vval.v_list = newlist;
+ ++newlist->lv_refcount;
+ }
+ }
+ }
+ else
+ {
+ tuple_T *tuple = tv->vval.v_tuple;
+
+ // no error for short tuple, expect it to be checked
+ // earlier
+ if (tuple != NULL && TUPLE_LEN(tuple) >= count)
+ {
+ tuple_T *newtuple;
+
+ newtuple = tuple_slice(tuple, count,
+ TUPLE_LEN(tuple) - 1);
+ if (newtuple != NULL)
+ {
+ tuple_unref(tuple);
+ tv->v_type = VAR_TUPLE;
+ tv->vval.v_tuple = newtuple;
+ ++newtuple->tv_refcount;
+ }
}
}
}
@@ -5674,17 +5939,24 @@
case ISN_GETITEM:
{
listitem_T *li;
+ typval_T *item_tv;
getitem_T *gi = &iptr->isn_arg.getitem;
// Get list item: list is at stack-1, push item.
// List type and length is checked for when compiling.
tv = STACK_TV_BOT(-1 - gi->gi_with_op);
- li = list_find(tv->vval.v_list, gi->gi_index);
+ if (tv->v_type == VAR_LIST)
+ {
+ li = list_find(tv->vval.v_list, gi->gi_index);
+ item_tv = &li->li_tv;
+ }
+ else
+ item_tv = TUPLE_ITEM(tv->vval.v_tuple, gi->gi_index);
if (GA_GROW_FAILS(&ectx->ec_stack, 1))
goto theend;
++ectx->ec_stack.ga_len;
- copy_tv(&li->li_tv, STACK_TV_BOT(-1));
+ copy_tv(item_tv, STACK_TV_BOT(-1));
// Useful when used in unpack assignment. Reset at
// ISN_DROP.
@@ -5920,17 +6192,40 @@
{
int min_len = iptr->isn_arg.checklen.cl_min_len;
list_T *list = NULL;
+ tuple_T *tuple = NULL;
+ int len = 0;
tv = STACK_TV_BOT(-1);
+
+ int len_check_failed = FALSE;
if (tv->v_type == VAR_LIST)
- list = tv->vval.v_list;
- if (list == NULL || list->lv_len < min_len
+ {
+ list = tv->vval.v_list;
+ if (list == NULL || list->lv_len < min_len
|| (list->lv_len > min_len
&& !iptr->isn_arg.checklen.cl_more_OK))
+ len_check_failed = TRUE;
+ if (list != NULL)
+ len = list->lv_len;
+ }
+ else if (tv->v_type == VAR_TUPLE)
+ {
+ tuple = tv->vval.v_tuple;
+ if (tuple == NULL || TUPLE_LEN(tuple) < min_len
+ || (TUPLE_LEN(tuple) > min_len
+ && !iptr->isn_arg.checklen.cl_more_OK))
+ len_check_failed = TRUE;
+ if (tuple != NULL)
+ len = TUPLE_LEN(tuple);
+ }
+ else
+ len_check_failed = TRUE;
+
+ if (len_check_failed)
{
SOURCING_LNUM = iptr->isn_lnum;
semsg(_(e_expected_nr_items_but_got_nr),
- min_len, list == NULL ? 0 : list->lv_len);
+ min_len, len);
goto on_error;
}
}
@@ -6026,78 +6321,25 @@
break;
case ISN_UNPACK:
+ // Check there is a valid list to unpack.
+ tv = STACK_TV_BOT(-1);
+ if (tv->v_type != VAR_LIST && tv->v_type != VAR_TUPLE)
{
- int count = iptr->isn_arg.unpack.unp_count;
- int semicolon = iptr->isn_arg.unpack.unp_semicolon;
- list_T *l;
- listitem_T *li;
- int i;
+ SOURCING_LNUM = iptr->isn_lnum;
+ emsg(_(e_for_argument_must_be_sequence_of_lists_or_tuples));
+ goto on_error;
+ }
- // Check there is a valid list to unpack.
- tv = STACK_TV_BOT(-1);
- if (tv->v_type != VAR_LIST)
- {
- SOURCING_LNUM = iptr->isn_lnum;
- emsg(_(e_for_argument_must_be_sequence_of_lists));
+ int rc;
+ if (tv->v_type == VAR_LIST)
+ rc = exec_unpack_list(ectx, iptr, tv);
+ else
+ rc = exec_unpack_tuple(ectx, iptr, tv);
+ if (rc != EXEC_OK)
+ {
+ if (rc == EXEC_FAIL)
goto on_error;
- }
- l = tv->vval.v_list;
- if (l == NULL
- || l->lv_len < (semicolon ? count - 1 : count))
- {
- SOURCING_LNUM = iptr->isn_lnum;
- emsg(_(e_list_value_does_not_have_enough_items));
- goto on_error;
- }
- else if (!semicolon && l->lv_len > count)
- {
- SOURCING_LNUM = iptr->isn_lnum;
- emsg(_(e_list_value_has_more_items_than_targets));
- goto on_error;
- }
-
- CHECK_LIST_MATERIALIZE(l);
- if (GA_GROW_FAILS(&ectx->ec_stack, count - 1))
- goto theend;
- ectx->ec_stack.ga_len += count - 1;
-
- // Variable after semicolon gets a list with the remaining
- // items.
- if (semicolon)
- {
- list_T *rem_list =
- list_alloc_with_items(l->lv_len - count + 1);
-
- if (rem_list == NULL)
- goto theend;
- tv = STACK_TV_BOT(-count);
- tv->vval.v_list = rem_list;
- ++rem_list->lv_refcount;
- tv->v_lock = 0;
- li = l->lv_first;
- for (i = 0; i < count - 1; ++i)
- li = li->li_next;
- for (i = 0; li != NULL; ++i)
- {
- typval_T tvcopy;
-
- copy_tv(&li->li_tv, &tvcopy);
- list_set_item(rem_list, i, &tvcopy);
- li = li->li_next;
- }
- --count;
- }
-
- // Produce the values in reverse order, first item last.
- li = l->lv_first;
- for (i = 0; i < count; ++i)
- {
- tv = STACK_TV_BOT(-i - 1);
- copy_tv(&li->li_tv, tv);
- li = li->li_next;
- }
-
- list_unref(l);
+ goto theend;
}
break;
@@ -7183,6 +7425,10 @@
smsg("%s%4d NEWLIST size %lld", pfx, current,
(varnumber_T)(iptr->isn_arg.number));
break;
+ case ISN_NEWTUPLE:
+ smsg("%s%4d NEWTUPLE size %lld", pfx, current,
+ (varnumber_T)(iptr->isn_arg.number));
+ break;
case ISN_NEWDICT:
smsg("%s%4d NEWDICT size %lld", pfx, current,
(varnumber_T)(iptr->isn_arg.number));
@@ -7474,6 +7720,7 @@
case ISN_COMPARESTRING:
case ISN_COMPAREBLOB:
case ISN_COMPARELIST:
+ case ISN_COMPARETUPLE:
case ISN_COMPAREDICT:
case ISN_COMPAREFUNC:
case ISN_COMPAREOBJECT:
@@ -7512,6 +7759,7 @@
type = "COMPARESTRING"; break;
case ISN_COMPAREBLOB: type = "COMPAREBLOB"; break;
case ISN_COMPARELIST: type = "COMPARELIST"; break;
+ case ISN_COMPARETUPLE: type = "COMPARETUPLE"; break;
case ISN_COMPAREDICT: type = "COMPAREDICT"; break;
case ISN_COMPAREFUNC: type = "COMPAREFUNC"; break;
case ISN_COMPAREOBJECT:
@@ -7525,6 +7773,7 @@
break;
case ISN_ADDLIST: smsg("%s%4d ADDLIST", pfx, current); break;
+ case ISN_ADDTUPLE: smsg("%s%4d ADDTUPLE", pfx, current); break;
case ISN_ADDBLOB: smsg("%s%4d ADDBLOB", pfx, current); break;
// expression operations
@@ -7540,6 +7789,8 @@
case ISN_BLOBAPPEND: smsg("%s%4d BLOBAPPEND", pfx, current); break;
case ISN_LISTINDEX: smsg("%s%4d LISTINDEX", pfx, current); break;
case ISN_LISTSLICE: smsg("%s%4d LISTSLICE", pfx, current); break;
+ case ISN_TUPLEINDEX: smsg("%s%4d TUPLEINDEX", pfx, current); break;
+ case ISN_TUPLESLICE: smsg("%s%4d TUPLESLICE", pfx, current); break;
case ISN_ANYINDEX: smsg("%s%4d ANYINDEX", pfx, current); break;
case ISN_ANYSLICE: smsg("%s%4d ANYSLICE", pfx, current); break;
case ISN_SLICE: smsg("%s%4d SLICE %lld",
@@ -7817,6 +8068,8 @@
return tv->vval.v_string != NULL && *tv->vval.v_string != NUL;
case VAR_LIST:
return tv->vval.v_list != NULL && tv->vval.v_list->lv_len > 0;
+ case VAR_TUPLE:
+ return tuple_len(tv->vval.v_tuple) > 0;
case VAR_DICT:
return tv->vval.v_dict != NULL
&& tv->vval.v_dict->dv_hashtab.ht_used > 0;
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]
diff --git a/src/vim9instr.c b/src/vim9instr.c
index 3da56bf..1b322f6 100644
--- a/src/vim9instr.c
+++ b/src/vim9instr.c
@@ -224,6 +224,7 @@
// conversion possible when tolerant
case VAR_LIST:
+ case VAR_TUPLE:
case VAR_DICT:
if (tostring_flags & TOSTRING_TOLERANT)
{
@@ -281,6 +282,58 @@
}
/*
+ * Append the tuple item types from "tuple_type" to the grow array "gap".
+ */
+ static int
+ga_append_tuple_types(type_T *tuple_type, garray_T *gap)
+{
+ for (int i = 0; i < tuple_type->tt_argcount; i++)
+ {
+ if (ga_grow(gap, 1) == FAIL)
+ return FAIL;
+
+ ((type_T **)gap->ga_data)[gap->ga_len] = tuple_type->tt_args[i];
+ gap->ga_len++;
+ }
+
+ return OK;
+}
+
+/*
+ * When concatenating two tuples, the resulting tuple gets a union of item
+ * types from both the tuples. This function sets the union tuple type in the
+ * stack.
+ *
+ * Returns OK on success and FAIL on memory allocation failure.
+ */
+ static int
+set_tuple_union_type_on_stack(type_T *type1, type_T *type2, cctx_T *cctx)
+{
+ // The concatenated tuple has the union of types from both the tuples
+ garray_T tuple_types_ga;
+
+ ga_init2(&tuple_types_ga, sizeof(type_T *), 10);
+
+ if (type1->tt_argcount > 0)
+ ga_append_tuple_types(type1, &tuple_types_ga);
+ if (!(type1->tt_flags & TTFLAG_VARARGS) && (type2->tt_argcount > 0))
+ ga_append_tuple_types(type2, &tuple_types_ga);
+
+ type_T *new_tuple_type = get_tuple_type(&tuple_types_ga,
+ cctx->ctx_type_list);
+ // result inherits the variadic flag from the operands
+ new_tuple_type->tt_flags |= (type1->tt_flags & TTFLAG_VARARGS)
+ | (type2->tt_flags & TTFLAG_VARARGS);
+
+ // set the type on the stack for the resulting tuple
+ set_type_on_stack(cctx, new_tuple_type, 0);
+
+ ga_clear(&tuple_types_ga);
+
+ return OK;
+}
+
+/*
* Generate instruction for "+". For a list this creates a new list.
*/
int
@@ -294,11 +347,12 @@
isn_T *isn = generate_instr_drop(cctx,
vartype == VAR_NUMBER ? ISN_OPNR
: vartype == VAR_LIST ? ISN_ADDLIST
+ : vartype == VAR_TUPLE ? ISN_ADDTUPLE
: vartype == VAR_BLOB ? ISN_ADDBLOB
: vartype == VAR_FLOAT ? ISN_OPFLOAT
: ISN_OPANY, 1);
- if (vartype != VAR_LIST && vartype != VAR_BLOB
+ if (vartype != VAR_LIST && vartype != VAR_BLOB && vartype != VAR_TUPLE
&& type1->tt_type != VAR_ANY
&& type1->tt_type != VAR_UNKNOWN
&& type2->tt_type != VAR_ANY
@@ -320,6 +374,14 @@
&& type1->tt_type == VAR_LIST && type2->tt_type == VAR_LIST
&& type1->tt_member != type2->tt_member)
set_type_on_stack(cctx, &t_list_any, 0);
+ else if (vartype == VAR_TUPLE)
+ {
+ if (!check_tuples_addable(type1, type2))
+ return FAIL;
+
+ if (set_tuple_union_type_on_stack(type1, type2, cctx) == FAIL)
+ return FAIL;
+ }
return isn == NULL ? FAIL : OK;
}
@@ -335,6 +397,7 @@
if (type1->tt_type == type2->tt_type
&& (type1->tt_type == VAR_NUMBER
|| type1->tt_type == VAR_LIST
+ || type1->tt_type == VAR_TUPLE
|| type1->tt_type == VAR_FLOAT
|| type1->tt_type == VAR_BLOB))
return type1->tt_type;
@@ -461,6 +524,7 @@
case VAR_STRING: isntype = ISN_COMPARESTRING; break;
case VAR_BLOB: isntype = ISN_COMPAREBLOB; break;
case VAR_LIST: isntype = ISN_COMPARELIST; break;
+ case VAR_TUPLE: isntype = ISN_COMPARETUPLE; break;
case VAR_DICT: isntype = ISN_COMPAREDICT; break;
case VAR_FUNC: isntype = ISN_COMPAREFUNC; break;
case VAR_OBJECT: isntype = ISN_COMPAREOBJECT; break;
@@ -744,6 +808,11 @@
iemsg("non-empty list constant not supported");
generate_NEWLIST(cctx, 0, TRUE);
break;
+ case VAR_TUPLE:
+ if (tv->vval.v_tuple != NULL)
+ iemsg("non-empty tuple constant not supported");
+ generate_NEWTUPLE(cctx, 0, TRUE);
+ break;
case VAR_DICT:
if (tv->vval.v_dict != NULL)
iemsg("non-empty dict constant not supported");
@@ -1009,7 +1078,7 @@
RETURN_OK_IF_SKIP(cctx);
- item_type = type->tt_member;
+ item_type = get_item_type(type);
if ((isn = generate_instr(cctx, ISN_GETITEM)) == NULL)
return FAIL;
isn->isn_arg.getitem.gi_index = index;
@@ -1370,6 +1439,45 @@
}
/*
+ * Generate an ISN_NEWTUPLE instruction for "count" items.
+ * "use_null" is TRUE for null_tuple.
+ */
+ int
+generate_NEWTUPLE(cctx_T *cctx, int count, int use_null)
+{
+ isn_T *isn;
+ type_T *type;
+ type_T *decl_type;
+
+ RETURN_OK_IF_SKIP(cctx);
+ if ((isn = generate_instr(cctx, ISN_NEWTUPLE)) == NULL)
+ return FAIL;
+ isn->isn_arg.number = use_null ? -1 : count;
+
+ // Get the member type and the declared member type from all the items on
+ // the stack.
+ garray_T tuple_types_ga;
+ ga_init2(&tuple_types_ga, sizeof(type_T *), 10);
+
+ if (get_tuple_type_from_stack(count, &tuple_types_ga, cctx) < 0)
+ {
+ ga_clear(&tuple_types_ga);
+ return FAIL;
+ }
+
+ type = get_tuple_type(&tuple_types_ga, cctx->ctx_type_list);
+ decl_type = &t_tuple_any;
+
+ ga_clear(&tuple_types_ga);
+
+ // drop the value types
+ cctx->ctx_type_stack.ga_len -= count;
+
+ // add the tuple type to the type stack
+ return push_type_stack2(cctx, type, decl_type);
+}
+
+/*
* Generate an ISN_NEWDICT instruction.
* "use_null" is TRUE for null_dict.
*/
@@ -2738,6 +2846,7 @@
case ISN_2STRING_ANY:
case ISN_ADDBLOB:
case ISN_ADDLIST:
+ case ISN_ADDTUPLE:
case ISN_ANYINDEX:
case ISN_ANYSLICE:
case ISN_BCALL:
@@ -2756,6 +2865,7 @@
case ISN_COMPAREFLOAT:
case ISN_COMPAREFUNC:
case ISN_COMPARELIST:
+ case ISN_COMPARETUPLE:
case ISN_COMPARENR:
case ISN_COMPARENULL:
case ISN_COMPAREOBJECT:
@@ -2787,6 +2897,8 @@
case ISN_LISTAPPEND:
case ISN_LISTINDEX:
case ISN_LISTSLICE:
+ case ISN_TUPLEINDEX:
+ case ISN_TUPLESLICE:
case ISN_LOAD:
case ISN_LOADBDICT:
case ISN_LOADGDICT:
@@ -2800,6 +2912,7 @@
case ISN_NEGATENR:
case ISN_NEWDICT:
case ISN_NEWLIST:
+ case ISN_NEWTUPLE:
case ISN_NEWPARTIAL:
case ISN_OPANY:
case ISN_OPFLOAT:
diff --git a/src/vim9script.c b/src/vim9script.c
index 3035889..cdacf0b 100644
--- a/src/vim9script.c
+++ b/src/vim9script.c
@@ -1127,6 +1127,7 @@
"null_dict",
"null_function",
"null_list",
+ "null_tuple",
"null_partial",
"null_string",
"null_channel",
diff --git a/src/vim9type.c b/src/vim9type.c
index 1f044d3..abf4daf 100644
--- a/src/vim9type.c
+++ b/src/vim9type.c
@@ -202,7 +202,7 @@
{
return type->tt_member != NULL
&& (type->tt_member->tt_type == VAR_DICT
- || type->tt_member->tt_type == VAR_LIST)
+ || type->tt_member->tt_type == VAR_LIST)
&& type->tt_member->tt_member != NULL
&& type->tt_member->tt_member != &t_any
&& type->tt_member->tt_member != &t_unknown;
@@ -262,7 +262,37 @@
}
/*
- * Set the type of "tv" to "type" if it is a list or dict.
+ * Set the type of Tuple "tuple" to "type"
+ */
+ static void
+set_tv_type_tuple(tuple_T *tuple, type_T *type)
+{
+ if (tuple->tv_type == type)
+ return;
+
+ free_type(tuple->tv_type);
+ tuple->tv_type = alloc_type(type);
+
+ if (type->tt_argcount <= 0)
+ return;
+
+ // recursively set the type of list items
+ type_T *item_type;
+ for (int i = 0; i < tuple_len(tuple); i++)
+ {
+ if ((type->tt_flags & TTFLAG_VARARGS) && (i >= type->tt_argcount - 1))
+ // For a variadic tuple, the last type is a List. So use the
+ // List member type.
+ item_type = type->tt_args[type->tt_argcount - 1]->tt_member;
+ else
+ item_type = type->tt_args[i];
+
+ set_tv_type(TUPLE_ITEM(tuple, i), item_type);
+ }
+}
+
+/*
+ * Set the type of "tv" to "type" if it is a list or tuple or dict.
*/
void
set_tv_type(typval_T *tv, type_T *type)
@@ -276,6 +306,31 @@
set_tv_type_dict(tv->vval.v_dict, type);
else if (tv->v_type == VAR_LIST && tv->vval.v_list != NULL)
set_tv_type_list(tv->vval.v_list, type);
+ else if (tv->v_type == VAR_TUPLE && tv->vval.v_tuple != NULL)
+ set_tv_type_tuple(tv->vval.v_tuple, type);
+}
+
+/*
+ * For a tuple type, reserve space for "typecount" types (including the
+ * repeated type).
+ */
+ static int
+tuple_type_add_types(
+ type_T *tupletype,
+ int typecount,
+ garray_T *type_gap)
+{
+ // To make it easy to free the space needed for the types, add the
+ // pointer to type_gap.
+ if (ga_grow(type_gap, 1) == FAIL)
+ return FAIL;
+ tupletype->tt_args = ALLOC_CLEAR_MULT(type_T *, typecount);
+ if (tupletype->tt_args == NULL)
+ return FAIL;
+ ((type_T **)type_gap->ga_data)[type_gap->ga_len] =
+ (void *)tupletype->tt_args;
+ ++type_gap->ga_len;
+ return OK;
}
type_T *
@@ -307,6 +362,41 @@
return type;
}
+/*
+ * Create and return a tuple type from the tuple item types in
+ * "tuple_types_ga".
+ */
+ type_T *
+get_tuple_type(
+ garray_T *tuple_types_gap,
+ garray_T *type_gap)
+{
+ type_T *type;
+ type_T **tuple_types = tuple_types_gap->ga_data;
+ int typecount = tuple_types_gap->ga_len;
+
+ // recognize commonly used types
+ if (typecount == 0)
+ return &t_tuple_any;
+
+ // Not a common type, create a new entry.
+ type = get_type_ptr(type_gap);
+ if (type == NULL)
+ return &t_any;
+ type->tt_type = VAR_TUPLE;
+ type->tt_member = NULL;
+ if (typecount > 0)
+ {
+ if (tuple_type_add_types(type, typecount, type_gap) == FAIL)
+ return NULL;
+ mch_memmove(type->tt_args, tuple_types, sizeof(type_T *) * typecount);
+ }
+ type->tt_argcount = typecount;
+ type->tt_flags = 0;
+
+ return type;
+}
+
type_T *
get_dict_type(type_T *member_type, garray_T *type_gap)
{
@@ -354,6 +444,23 @@
}
/*
+ * Allocate a new type for a tuple.
+ */
+ static type_T *
+alloc_tuple_type(int typecount, garray_T *type_gap)
+{
+ type_T *type = get_type_ptr(type_gap);
+
+ if (type == NULL)
+ return &t_any;
+ type->tt_type = VAR_TUPLE;
+ type->tt_member = NULL;
+ type->tt_argcount = typecount;
+ type->tt_args = NULL;
+ return type;
+}
+
+/*
* Get a function type, based on the return type "ret_type".
* "argcount" must be -1 or 0, a predefined type can be used.
*/
@@ -507,6 +614,64 @@
}
/*
+ * Get a type_T for a Tuple typval in "tv".
+ * When "flags" has TVTT_DO_MEMBER also get the member type, otherwise use
+ * "any".
+ * When "flags" has TVTT_MORE_SPECIFIC get the more specific member type if it
+ * is "any".
+ */
+ static type_T *
+tuple_typval2type(typval_T *tv, int copyID, garray_T *type_gap, int flags)
+{
+ tuple_T *tuple = tv->vval.v_tuple;
+ int len = tuple_len(tuple);
+ type_T *type = NULL;
+
+ // An empty tuple has type tuple<unknown>, unless the type was specified
+ // and is not tuple<any>. This matters when assigning to a variable
+ // with a specific tuple type.
+ if (tuple == NULL || (len == 0 && (tuple->tv_type == NULL
+ || tuple->tv_type->tt_argcount == 0)))
+ return &t_tuple_empty;
+
+ if ((flags & TVTT_DO_MEMBER) == 0)
+ return &t_tuple_any;
+
+ // If the type is tuple<any> go through the members, it may end up a
+ // more specific type.
+ if (tuple->tv_type != NULL && (len == 0
+ || (flags & TVTT_MORE_SPECIFIC) == 0))
+ // make a copy, tv_type may be freed if the tuple is freed
+ return copy_type_deep(tuple->tv_type, type_gap);
+
+ if (tuple->tv_copyID == copyID)
+ // avoid recursion
+ return &t_tuple_any;
+
+ tuple->tv_copyID = copyID;
+
+ garray_T tuple_types_ga;
+ ga_init2(&tuple_types_ga, sizeof(type_T *), 10);
+ for (int i = 0; i < len; i++)
+ {
+ type = typval2type(TUPLE_ITEM(tuple, i), copyID, type_gap,
+ TVTT_DO_MEMBER);
+ if (ga_grow(&tuple_types_ga, 1) == FAIL)
+ {
+ ga_clear(&tuple_types_ga);
+ return NULL;
+ }
+ ((type_T **)tuple_types_ga.ga_data)[tuple_types_ga.ga_len] = type;
+ tuple_types_ga.ga_len++;
+ }
+
+ type_T *tuple_type = get_tuple_type(&tuple_types_ga, type_gap);
+ ga_clear(&tuple_types_ga);
+
+ return tuple_type;
+}
+
+/*
* Get a type_T for a Dict typval in "tv".
* When "flags" has TVTT_DO_MEMBER also get the member type, otherwise use
* "any".
@@ -723,6 +888,9 @@
case VAR_LIST:
return list_typval2type(tv, copyID, type_gap, flags);
+ case VAR_TUPLE:
+ return tuple_typval2type(tv, copyID, type_gap, flags);
+
case VAR_DICT:
return dict_typval2type(tv, copyID, type_gap, flags);
@@ -950,6 +1118,63 @@
}
/*
+ * Check if the expected and actual types match for a tuple
+ */
+ static int
+check_tuple_type_maybe(
+ type_T *expected,
+ type_T *actual,
+ where_T where)
+{
+ if (expected->tt_argcount == -1 || actual->tt_argcount == -1
+ || expected->tt_args == NULL || actual->tt_args == NULL)
+ return OK;
+
+ // For a non-variadic tuple, the number of items must match
+ if (!(expected->tt_flags & TTFLAG_VARARGS)
+ && expected->tt_argcount != actual->tt_argcount)
+ return FAIL;
+
+ // compare the type of each tuple item
+ for (int i = 0; i < actual->tt_argcount; ++i)
+ {
+ type_T *exp_type;
+ type_T *actual_type;
+
+ if (expected->tt_flags & TTFLAG_VARARGS)
+ {
+ if (i < expected->tt_argcount - 1)
+ exp_type = expected->tt_args[i];
+ else
+ // For a variadic tuple, the last type is a List. So use the
+ // List member type.
+ exp_type = expected->tt_args[expected->tt_argcount - 1]->tt_member;
+ }
+ else
+ exp_type = expected->tt_args[i];
+
+ if (actual->tt_flags & TTFLAG_VARARGS)
+ {
+ if (i < actual->tt_argcount - 1)
+ actual_type = actual->tt_args[i];
+ else
+ // For a variadic tuple, the last type is a List. So use the
+ // List member type.
+ actual_type = actual->tt_args[actual->tt_argcount - 1]->tt_member;
+ }
+ else
+ actual_type = actual->tt_args[i];
+
+ // Allow for using "any" type for a tuple item
+ if (actual->tt_args[i] != &t_any && check_type(exp_type, actual_type,
+ FALSE, where) == FAIL)
+ return FAIL;
+ }
+
+ return OK;
+}
+
+/*
* Check if the expected and actual types match.
* Does not allow for assigning "any" to a specific type.
* When "argidx" > 0 it is included in the error message.
@@ -1018,6 +1243,8 @@
ret = check_type_maybe(expected->tt_member, actual->tt_member,
FALSE, where);
}
+ else if (expected->tt_type == VAR_TUPLE && actual != &t_any)
+ ret = check_tuple_type_maybe(expected, actual, where);
else if (expected->tt_type == VAR_FUNC && actual != &t_any)
{
// If the return type is unknown it can be anything, including
@@ -1200,11 +1427,33 @@
// Skip over "<type>"; this is permissive about white space.
if (*skipwhite(p) == '<')
{
- p = skipwhite(p);
- p = skip_type(skipwhite(p + 1), FALSE);
- p = skipwhite(p);
- if (*p == '>')
- ++p;
+ if (STRNCMP("tuple", start, 5) == 0)
+ {
+ // handle tuple<{type1}, {type2}, ....<type>>
+ p = skipwhite(p + 1);
+ while (*p != '>' && *p != NUL)
+ {
+ char_u *sp = p;
+
+ if (STRNCMP(p, "...", 3) == 0)
+ p += 3;
+ p = skip_type(p, TRUE);
+ if (p == sp)
+ return p; // syntax error
+ if (*p == ',')
+ p = skipwhite(p + 1);
+ }
+ if (*p == '>')
+ p++;
+ }
+ else
+ {
+ p = skipwhite(p);
+ p = skip_type(skipwhite(p + 1), FALSE);
+ p = skipwhite(p);
+ if (*p == '>')
+ ++p;
+ }
}
else if ((*p == '(' || (*p == ':' && VIM_ISWHITE(p[1])))
&& STRNCMP("func", start, 4) == 0)
@@ -1423,6 +1672,116 @@
}
/*
+ * Parse a "tuple" type at "*arg" and advance over it.
+ * When "give_error" is TRUE give error messages, otherwise be quiet.
+ * Return NULL for failure.
+ */
+ static type_T *
+parse_type_tuple(char_u **arg, garray_T *type_gap, int give_error)
+{
+ char_u *p;
+ type_T *type;
+ type_T *ret_type = NULL;
+ int typecount = -1;
+ int flags = 0;
+ garray_T tuple_types_ga;
+
+ ga_init2(&tuple_types_ga, sizeof(type_T *), 10);
+
+ // tuple<{type}, {type}>
+ // tuple<{type}, ...{type}>
+ if (**arg != '<')
+ {
+ if (give_error)
+ {
+ if (*skipwhite(*arg) == '<')
+ semsg(_(e_no_white_space_allowed_before_str_str), "<", *arg);
+ else
+ semsg(_(e_missing_type_after_str), "tuple");
+ }
+
+ // only "tuple" is specified
+ return NULL;
+ }
+
+ p = ++*arg;
+ typecount = 0;
+ while (*p != NUL && *p != '>')
+ {
+ if (STRNCMP(p, "...", 3) == 0)
+ {
+ flags |= TTFLAG_VARARGS;
+ p += 3;
+ }
+
+ type = parse_type(&p, type_gap, give_error);
+ if (type == NULL)
+ goto on_err;
+
+ if ((flags & TTFLAG_VARARGS) != 0 && type->tt_type != VAR_LIST)
+ {
+ char *tofree;
+ semsg(_(e_variadic_tuple_must_end_with_list_type_str),
+ type_name(type, &tofree));
+ vim_free(tofree);
+ goto on_err;
+ }
+
+ // Add the item type
+ if (ga_grow(&tuple_types_ga, 1) == FAIL)
+ goto on_err;
+ ((type_T **)tuple_types_ga.ga_data)[tuple_types_ga.ga_len] = type;
+ tuple_types_ga.ga_len++;
+ typecount++;
+
+ // Nothing comes after "...{type}".
+ if (flags & TTFLAG_VARARGS)
+ break;
+
+ if (*p != ',' && *skipwhite(p) == ',')
+ {
+ if (give_error)
+ semsg(_(e_no_white_space_allowed_before_str_str), ",", p);
+ goto on_err;
+ }
+ if (*p == ',')
+ {
+ ++p;
+ if (!VIM_ISWHITE(*p))
+ {
+ if (give_error)
+ semsg(_(e_white_space_required_after_str_str),
+ ",", p - 1);
+ goto on_err;
+ }
+ }
+ p = skipwhite(p);
+ }
+
+ p = skipwhite(p);
+ if (*p != '>' || typecount <= 0)
+ {
+ if (give_error)
+ semsg(_(e_missing_type_after_str), p);
+ goto on_err;
+ }
+ *arg = p + 1;
+
+ ret_type = alloc_tuple_type(typecount, type_gap);
+ ret_type->tt_flags = flags;
+ ret_type->tt_argcount = typecount;
+ if (tuple_type_add_types(ret_type, typecount, type_gap) == FAIL)
+ return NULL;
+ mch_memmove(ret_type->tt_args, tuple_types_ga.ga_data,
+ sizeof(type_T *) * typecount);
+
+on_err:
+ ga_clear(&tuple_types_ga);
+
+ return ret_type;
+}
+
+/*
* Parse a user defined type at "*arg" and advance over it.
* It can be a class or an interface or a typealias name, possibly imported.
* Return NULL if a type is not found.
@@ -1577,6 +1936,13 @@
return &t_string;
}
break;
+ case 't':
+ if (len == 5 && STRNCMP(*arg, "tuple", len) == 0)
+ {
+ *arg += len;
+ return parse_type_tuple(arg, type_gap, give_error);
+ }
+ break;
case 'v':
if (len == 4 && STRNCMP(*arg, "void", len) == 0)
{
@@ -1625,6 +1991,18 @@
case VAR_LIST:
case VAR_DICT:
return equal_type(type1->tt_member, type2->tt_member, flags);
+ case VAR_TUPLE:
+ if (type1->tt_argcount != type2->tt_argcount)
+ return FALSE;
+ if (type1->tt_argcount < 0
+ || type1->tt_args == NULL || type2->tt_args == NULL)
+ return TRUE;
+ for (i = 0; i < type1->tt_argcount; ++i)
+ if ((flags & ETYPE_ARG_UNKNOWN) == 0
+ && !equal_type(type1->tt_args[i], type2->tt_args[i],
+ flags))
+ return FALSE;
+ return TRUE;
case VAR_FUNC:
case VAR_PARTIAL:
if (!equal_type(type1->tt_member, type2->tt_member, flags)
@@ -1725,7 +2103,8 @@
if (type1->tt_type == type2->tt_type)
{
- if (type1->tt_type == VAR_LIST || type2->tt_type == VAR_DICT)
+ if (type1->tt_type == VAR_LIST
+ || type1->tt_type == VAR_DICT)
{
type_T *common;
@@ -1736,8 +2115,7 @@
*dest = get_dict_type(common, type_gap);
return;
}
-
- if (type1->tt_type == VAR_FUNC)
+ else if (type1->tt_type == VAR_FUNC)
{
common_type_var_func(type1, type2, dest, type_gap);
return;
@@ -1748,6 +2126,26 @@
}
/*
+ * Return the item type of a List, Dict or a Tuple
+ */
+ type_T *
+get_item_type(type_T *type)
+{
+ if (type->tt_type == VAR_TUPLE)
+ {
+ if (type->tt_argcount != 1)
+ return &t_any;
+
+ if (type->tt_flags & TTFLAG_VARARGS)
+ return type->tt_args[0]->tt_member;
+ else
+ return type->tt_args[0];
+ }
+
+ return type->tt_member;
+}
+
+/*
* Push an entry onto the type stack. "type" used both for the current type
* and the declared type.
* Returns FAIL when out of memory.
@@ -1864,6 +2262,40 @@
return result;
}
+/*
+ * Get the types of items in a tuple on the stack of "cctx".
+ * Returns the number of types. Returns -1 on failure.
+ */
+ int
+get_tuple_type_from_stack(
+ int count,
+ garray_T *tuple_types_gap,
+ cctx_T *cctx)
+{
+ garray_T *stack = &cctx->ctx_type_stack;
+ type2_T *typep;
+ type_T *type = NULL;
+
+ // Use "unknown" for an empty tuple
+ if (count == 0)
+ return 0;
+
+ // Find the common type from following items.
+ typep = ((type2_T *)stack->ga_data) + stack->ga_len;
+ for (int i = 0; i < count; i++)
+ {
+ type = (typep - (count - i))->type_curr;
+ if (check_type_is_value(type) == FAIL)
+ return -1;
+ if (ga_grow(tuple_types_gap, 1) == FAIL)
+ return -1;
+ ((type_T **)tuple_types_gap->ga_data)[tuple_types_gap->ga_len] = type;
+ tuple_types_gap->ga_len++;
+ }
+
+ return tuple_types_gap->ga_len;
+}
+
char *
vartype_name(vartype_T type)
{
@@ -1881,6 +2313,7 @@
case VAR_JOB: return "job";
case VAR_CHANNEL: return "channel";
case VAR_LIST: return "list";
+ case VAR_TUPLE: return "tuple";
case VAR_DICT: return "dict";
case VAR_INSTR: return "instr";
case VAR_CLASS: return "class";
@@ -1919,6 +2352,65 @@
}
/*
+ * Return the type name of a tuple.
+ * The result may be in allocated memory, in which case "tofree" is set.
+ */
+ static char *
+type_name_tuple(type_T *type, char **tofree)
+{
+ garray_T ga;
+ int i;
+ int varargs = (type->tt_flags & TTFLAG_VARARGS) ? 1 : 0;
+ char *arg_free = NULL;
+
+ ga_init2(&ga, 1, 100);
+ if (ga_grow(&ga, 20) == FAIL)
+ goto failed;
+ STRCPY(ga.ga_data, "tuple<");
+ ga.ga_len += 6;
+
+ if (type->tt_argcount <= 0)
+ // empty tuple
+ ga_concat(&ga, (char_u *)"any");
+ else
+ {
+ if (type->tt_args == NULL)
+ ga_concat(&ga, (char_u *)"[unknown]");
+ else
+ {
+ for (i = 0; i < type->tt_argcount; ++i)
+ {
+ char *arg_type;
+ int len;
+
+ arg_type = type_name(type->tt_args[i], &arg_free);
+ if (i > 0)
+ {
+ STRCPY((char *)ga.ga_data + ga.ga_len, ", ");
+ ga.ga_len += 2;
+ }
+ len = (int)STRLEN(arg_type);
+ if (ga_grow(&ga, len + 8) == FAIL)
+ goto failed;
+ if (varargs && i == type->tt_argcount - 1)
+ ga_concat(&ga, (char_u *)"...");
+ ga_concat(&ga, (char_u *)arg_type);
+ VIM_CLEAR(arg_free);
+ }
+ }
+ }
+
+ STRCPY((char *)ga.ga_data + ga.ga_len, ">");
+ *tofree = ga.ga_data;
+ return ga.ga_data;
+
+failed:
+ vim_free(arg_free);
+ ga_clear(&ga);
+ return "[unknown]";
+}
+
+/*
* Return the type name of a Class (class<name>) or Object (object<name>).
* The result may be in allocated memory, in which case "tofree" is set.
*/
@@ -2035,6 +2527,9 @@
case VAR_DICT:
return type_name_list_or_dict(name, type, tofree);
+ case VAR_TUPLE:
+ return type_name_tuple(type, tofree);
+
case VAR_CLASS:
case VAR_OBJECT:
return type_name_class_or_obj(name, type, tofree);
diff --git a/src/viminfo.c b/src/viminfo.c
index 5f1ad74..44d5487 100644
--- a/src/viminfo.c
+++ b/src/viminfo.c
@@ -1263,6 +1263,7 @@
case 'L': type = VAR_LIST; break;
case 'B': type = VAR_BLOB; break;
case 'X': type = VAR_SPECIAL; break;
+ case 'T': type = VAR_TUPLE; break;
}
tab = vim_strchr(tab, '\t');
@@ -1270,7 +1271,8 @@
{
tv.v_type = type;
if (type == VAR_STRING || type == VAR_DICT
- || type == VAR_LIST || type == VAR_BLOB)
+ || type == VAR_LIST || type == VAR_BLOB
+ || type == VAR_TUPLE)
tv.vval.v_string = viminfo_readstring(virp,
(int)(tab - virp->vir_line + 1), TRUE);
else if (type == VAR_FLOAT)
@@ -1282,7 +1284,7 @@
|| tv.vval.v_number == VVAL_TRUE))
tv.v_type = VAR_BOOL;
}
- if (type == VAR_DICT || type == VAR_LIST)
+ if (type == VAR_DICT || type == VAR_LIST || type == VAR_TUPLE)
{
typval_T *etv = eval_expr(tv.vval.v_string, NULL);
@@ -1370,7 +1372,7 @@
s = "DIC";
if (di != NULL && !set_ref_in_ht(
- &di->dv_hashtab, copyID, NULL)
+ &di->dv_hashtab, copyID, NULL, NULL)
&& di->dv_copyID == copyID)
// has a circular reference, can't turn the
// value into a string
@@ -1384,13 +1386,27 @@
s = "LIS";
if (l != NULL && !set_ref_in_list_items(
- l, copyID, NULL)
+ l, copyID, NULL, NULL)
&& l->lv_copyID == copyID)
// has a circular reference, can't turn the
// value into a string
continue;
break;
}
+ case VAR_TUPLE:
+ {
+ tuple_T *tuple = this_var->di_tv.vval.v_tuple;
+ int copyID = get_copyID();
+
+ s = "TUP";
+ if (tuple != NULL && !set_ref_in_tuple_items(
+ tuple, copyID, NULL, NULL)
+ && tuple->tv_copyID == copyID)
+ // has a circular reference, can't turn the
+ // value into a string
+ continue;
+ break;
+ }
case VAR_BLOB: s = "BLO"; break;
case VAR_BOOL: s = "XPL"; break; // backwards compat.
case VAR_SPECIAL: s = "XPL"; break;