]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.1.1232: Vim script is missing the tuple data type v9.1.1232
authorYegappan Lakshmanan <yegappan@yahoo.com>
Sun, 23 Mar 2025 15:42:16 +0000 (16:42 +0100)
committerChristian Brabandt <cb@256bit.org>
Sun, 23 Mar 2025 15:42:16 +0000 (16:42 +0100)
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>
75 files changed:
Filelist
runtime/doc/builtin.txt
runtime/doc/eval.txt
runtime/doc/tags
runtime/doc/testing.txt
runtime/doc/usr_41.txt
runtime/doc/version9.txt
runtime/doc/vim9.txt
src/Make_ami.mak
src/Make_cyg_ming.mak
src/Make_mvc.mak
src/Make_vms.mms
src/Makefile
src/channel.c
src/dict.c
src/errors.h
src/eval.c
src/evalfunc.c
src/evalvars.c
src/ex_docmd.c
src/gc.c
src/globals.h
src/if_py_both.h
src/job.c
src/json.c
src/list.c
src/macros.h
src/netbeans.c
src/popupwin.c
src/proto.h
src/proto/eval.pro
src/proto/evalfunc.pro
src/proto/gc.pro
src/proto/list.pro
src/proto/testing.pro
src/proto/tuple.pro [new file with mode: 0644]
src/proto/typval.pro
src/proto/vim9instr.pro
src/proto/vim9type.pro
src/quickfix.c
src/structs.h
src/terminal.c
src/testdir/Make_all.mak
src/testdir/test_blob.vim
src/testdir/test_eval_stuff.vim
src/testdir/test_expr.vim
src/testdir/test_filter_map.vim
src/testdir/test_lambda.vim
src/testdir/test_let.vim
src/testdir/test_listdict.vim
src/testdir/test_method.vim
src/testdir/test_put.vim
src/testdir/test_tuple.vim [new file with mode: 0644]
src/testdir/test_vim9_assign.vim
src/testdir/test_vim9_builtin.vim
src/testdir/test_vim9_disassemble.vim
src/testdir/test_vimscript.vim
src/testdir/vim9.vim
src/testing.c
src/time.c
src/tuple.c [new file with mode: 0644]
src/typval.c
src/userfunc.c
src/version.c
src/vim.h
src/vim9.h
src/vim9class.c
src/vim9cmds.c
src/vim9compile.c
src/vim9execute.c
src/vim9expr.c
src/vim9instr.c
src/vim9script.c
src/vim9type.c
src/viminfo.c

index 95bed7d6bb3e7a5f4c8bce6ce3640a286e28f028..7302788fea3e787e351caae82ec479da22d4a78f 100644 (file)
--- a/Filelist
+++ b/Filelist
@@ -157,6 +157,7 @@ SRC_ALL =   \
                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_ALL =   \
                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 \
index 48bdc43a398673b1595a8725cfd193d30d573066..d7c97407b5afa5891a72dc1362357371fd354a59 100644 (file)
@@ -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 @@ foldclosedend({lnum})               Number  last line of fold at {lnum} if closed
 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({command} [, {options}])
                                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 @@ line2byte({lnum})           Number  byte count of line {lnum}
 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({blob}, {idx} [, {end}])   Number/Blob
                                        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_job()                     Job     null value for testing
 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 @@ tr({src}, {fromstr}, {tostr})       String  translate chars of {src} in {fromstr}
 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 @@ copy({expr})                                                      *copy()*
                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 @@ cosh({expr})                                            *cosh()*
 
 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 @@ deepcopy({expr} [, {noref}])                              *deepcopy()* *E698*
                |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 @@ echoraw({string})                                 *echoraw()*
 
 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 @@ foldtextresult({lnum})                                    *foldtextresult()*
                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 @@ foreach({expr1}, {expr2})                                       *foreach()*
 
                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 @@ foreach({expr1}, {expr2})                                 *foreach()*
                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 @@ get({list}, {idx} [, {default}])                 *get()* *get()-list*
 <
                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 @@ id({item})                                                        *id()*
 <              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 @@ index({object}, {expr} [, {start} [, {ic}]])                    *index()*
                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 @@ index({object}, {expr} [, {start} [, {ic}]])                    *index()*
 
 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 @@ indexof({object}, {expr} [, {opts}])                    *indexof()*
 
                {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 @@ items({dict})                                             *items()*
                           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 @@ items({dict})                                           *items()*
 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 @@ json_encode({expr})                                       *json_encode()*
                   |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 @@ len({expr})                                               *len()* *E701*
                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 @@ list2str({list} [, {utf8}])                              *list2str()*
                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 @@ max({expr})                                                     *max()*
                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 @@ min({expr})                                                     *min()*
                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 @@ readfile({fname} [, {type} [, {max}]])
 
 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 @@ repeat({expr}, {count})                                 *repeat()*
                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 @@ resolve({filename})                                     *resolve()* *E655*
 
 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 @@ slice({expr}, {start} [, {end}])                        *slice()*
                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 @@ string({expr})                                                        *string()*
                        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 @@ trunc({expr})                                                  *trunc()*
                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 @@ type({expr})    The result is a Number representing the type of {expr}.
                        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("")
index 0ada1b2159d2d1d66c3c1e6f2cc4f11416c88f55..bcd64e30aaca8d48e047603a18937b9ff4cff3f0 100644 (file)
@@ -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 @@ a remark is given.
     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 @@ Profiling is documented at |profiling|.
 
 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 @@ String               A NUL terminated string of 8-bit unsigned characters (bytes).
 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 @@ A List, Dictionary or Float is not a Number or String, thus evaluate to FALSE.
 
                *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 @@ position in the sequence.
 
 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 @@ similar to -1. >
        :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 @@ Changing the order of items in a list: >
 
 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 @@ It is also possible to put remaining items in a List variable: >
        :   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 @@ Don't forget that a combination of features can make things simple.  For
 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.
+
+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
+
 
-1.4 Dictionaries ~
+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 @@ ordering.
 
 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 @@ An extra comma after the last entry is ignored.
 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 @@ Functions that can be used with a Dictionary: >
        :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 @@ Making a copy of a Blob is done with the |copy()| function.  Using [:] also
 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 @@ Expression syntax summary, from least to most significant:
        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 @@ Expression syntax summary, from least to most significant:
        + 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 @@ Expression syntax summary, from least to most significant:
        "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 @@ Examples:
 "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 @@ Dictionary and arguments, use |get()| to get the function name: >
        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 @@ that: >
 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 @@ topmost bit (sometimes called the sign bit) is cleared.  If the right operand
 
 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 @@ in any order.  E.g., these are all possible:
        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 @@ Generally, if a |List| index is equal to or higher than the length of the
 |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 @@ just above. Also see |sublist| below.  Examples: >
        :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_typealias     Value of |typealias| type.  Read-only.  See: |type()|
 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 @@ declarations and assignments do not use a command.  |vim9-declaration|
 :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 @@ declarations and assignments do not use a command.  |vim9-declaration|
 :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 @@ text...
                        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 @@ text...
 :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
index 1eebe14b5efebb0778bef3308d249b17446403ad..52c36781dd9804db019968a2816eef6acf31b5d1 100644 (file)
@@ -4592,9 +4592,33 @@ E1513    message.txt     /*E1513*
 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 @@ TextChangedP      autocmd.txt     /*TextChangedP*
 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()-blob        builtin.txt     /*get()-blob*
 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-modification eval.txt        /*list-modification*
 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 @@ subscript       eval.txt        /*subscript*
 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_ti    term.txt        /*t_ti*
 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_job() testing.txt     /*test_null_job()*
 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-echoerr    eval.txt        /*try-echoerr*
 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_none        eval.txt        /*v:t_none*
 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-categories     vim9.txt        /*variable-categories*
 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*
index 7d0402c1be4956b6c20ec2e585edc12735cdc957..01e98cbe029e42b7beb0d1c077a5748f51d274e2 100644 (file)
@@ -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 @@ test_null_string()                                 *test_null_string()*
 
                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: >
index 0d09fc9c59e56d014e9a4209da662836185d1a0f..4c5e5ef4c69b23c7068e21ca1026d4e40fabe6cc 100644 (file)
@@ -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 @@ List manipulation:                                 *list-functions*
        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 @@ Testing:                              *test-functions*
        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 @@ More information about defining your own functions here: |user-functions|.
 ==============================================================================
 *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 @@ This looks into lines 1 to 50 (inclusive) and echoes any date found in there.
 
 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
 
index 377ab2ae00c3ac77a866bde7976a26c056f76e86..4f0da43a342e35b8256f175d1847c46ef1d38e0d 100644 (file)
@@ -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 @@ Include the "linematch" algorithm for the 'diffopt' setting.  This aligns
 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 @@ Functions: ~
 |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: ~
index bf500944ed37e347f15af52b7611d445b777a4f8..d06c250a3f564731c5f369c2a471b5449abb4e28 100644 (file)
@@ -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 @@ empty list and dict is falsy:
        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 @@ In Vim9 script one can use the following predefined values: >
        null_function
        null_job
        null_list
+       null_tuple
        null_object
        null_partial
        null_string
@@ -1467,15 +1469,16 @@ The following builtin types are supported:
        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 @@ return value) results in error *E1031*  *E1186* .
 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 @@ argument type checking: >
                         *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 @@ Categories of variables, defaults and null handling ~
                                *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 @@ An uninitialized variable is usually equal to null; it depends on its type:
        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 @@ A variable initialized to empty equals null_<type>; but not null:
        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
index c779914292b95f707c75db83c1863aaf472253de..4dc86ddc6cc13ea6b861c90cfb765beefd942174 100644 (file)
@@ -169,6 +169,7 @@ SRC += \
        textobject.c \
        textprop.c \
        time.c \
+       tuple.c \
        typval.c \
        ui.c \
        undo.c \
index 3f2a6dabdb0c342f20dbf24f953600c613071e0c..991feb963158ab93d85768300504cdfecc58d116 100644 (file)
@@ -865,6 +865,7 @@ OBJ = \
        $(OUTDIR)/textobject.o \
        $(OUTDIR)/textprop.o \
        $(OUTDIR)/time.o \
+       $(OUTDIR)/tuple.o \
        $(OUTDIR)/typval.o \
        $(OUTDIR)/ui.o \
        $(OUTDIR)/undo.o \
index c24f0af8554499ecbb50c62a00b529741e680cd6..282586536dc4f7aab75885b60f357d52910b3b33 100644 (file)
@@ -786,6 +786,7 @@ OBJ = \
        $(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)/textprop.obj:   $(OUTDIR) textprop.c  $(INCL)
 
 $(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.h: \
        proto/textobject.pro \
        proto/textprop.pro \
        proto/time.pro \
+       proto/tuple.pro \
        proto/typval.pro \
        proto/ui.pro \
        proto/undo.pro \
index 20630236fedb85f12828fc6ed93f4bae227aef0b..a30ed653e4dc65bb4c6be0a69a75b9c8bb5395df 100644 (file)
@@ -433,6 +433,7 @@ SRC = \
        textobject.c \
        textprop.c \
        time.c \
+       tuple.c \
        typval.c \
        ui.c \
        undo.c \
@@ -567,6 +568,7 @@ OBJ = \
        textobject.obj \
        textprop.obj \
        time.obj \
+       tuple.obj \
        typval.obj \
        ui.obj \
        undo.obj \
@@ -1169,6 +1171,9 @@ textprop.obj : textprop.c vim.h [.auto]config.h feature.h os_unix.h   \
 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
index d6c76b102e65b3bf00e7d150647928e500fadf28..cde2e5581d3fd7c9eeac62dd6aba6fa60fdd3aab 100644 (file)
@@ -1584,6 +1584,7 @@ BASIC_SRC = \
        textobject.c \
        textprop.c \
        time.c \
+       tuple.c \
        typval.c \
        ui.c \
        undo.c \
@@ -1744,6 +1745,7 @@ OBJ_COMMON = \
        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 @@ PRO_AUTO = \
        textobject.pro \
        textprop.pro \
        time.pro \
+       tuple.pro \
        typval.pro \
        ui.pro \
        undo.pro \
@@ -3568,6 +3571,9 @@ objects/textprop.o: textprop.c
 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 @@ objects/time.o: time.c vim.h protodef.h auto/config.h feature.h os_unix.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/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 \
index 69bbff24e9d07ebd02e19e98d30b61530860a7b5..a369c716096b78ad5210236ab2e8f5d7f2ce3eed 100644 (file)
@@ -5004,7 +5004,7 @@ set_ref_in_channel(int copyID)
        {
            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;
 }
index 7975904da7189f5c1126bcb545c3a02dc930228a..d43ca863e917667829f98d817cd5c7584c0e88bd 100644 (file)
@@ -1554,7 +1554,7 @@ dict2list(typval_T *argvars, typval_T *rettv, dict2list_T what)
        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 @@ f_items(typval_T *argvars, typval_T *rettv)
        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);
 }
index ca5ec850e1bf5f291aac1b597a10e65cb8f12cab..9331484ac5ee11ad727b33b79376af499a0b3bce 100644 (file)
@@ -2932,8 +2932,8 @@ EXTERN char e_using_bool_as_number[]
        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 @@ EXTERN char e_string_or_dict_required_for_argument_nr[]
        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 @@ EXTERN char e_highlight_group_name_too_long[]
 #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 @@ EXTERN char e_window_unexpectedly_close_while_searching_for_tags[]
 #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 @@ EXTERN char e_str_encoding_from_failed[]
        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
index 9a140c16609566e860bb4a3b30b9a1146f33d228..bd8e7cfcf3bc3be2541a1d2316fbd78eab071e08 100644 (file)
@@ -107,7 +107,7 @@ eval_clear(void)
     // 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 @@ skip_expr_concatenate(
 
 /*
  * 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;
+       }
+       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;
        }
-       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 @@ typval2string(typval_T *tv, int join_list)
 /*
  * 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 @@ flag_string_T glv_flag_strings[] = {
  *
  * 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 @@ get_lval_dict_item(
            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 @@ get_lval_blob(
 {
     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 @@ get_lval_list(
     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);
@@ -1523,6 +1554,64 @@ get_lval_list(
     return OK;
 }
 
+/*
+ * 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'.
@@ -1630,6 +1719,7 @@ get_lval_class_or_obj(
 {
     lp->ll_dict = NULL;
     lp->ll_list = NULL;
+    lp->ll_tuple = NULL;
 
     class_T *cl;
     if (v_type == VAR_OBJECT)
@@ -1697,8 +1787,8 @@ dot_allowed_after_type(char_u *name, vartype_T v_type, int quiet)
 
 /*
  * 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 @@ bracket_allowed_after_type(char_u *name, vartype_T v_type, int quiet)
 
 /*
  * 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 @@ index_allowed_after_type(char_u *name, vartype_T v_type, int quiet)
 }
 
 /*
- * 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 @@ get_lval_subscript(
                        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 @@ get_lval_subscript(
            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 @@ get_lval_subscript(
        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 @@ tv_op(typval_T *tv1, typval_T *tv2, char_u *op)
        case VAR_OBJECT:
        case VAR_CLASS:
        case VAR_TYPEALIAS:
+       case VAR_TUPLE:
            break;
 
        case VAR_BLOB:
@@ -2619,6 +2732,7 @@ eval_for_line(
     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 @@ eval_for_line(
                    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 @@ eval_for_line(
            }
            else
            {
-               emsg(_(e_string_list_or_blob_required));
+               emsg(_(e_string_list_tuple_or_blob_required));
                clear_tv(&tv);
            }
        }
@@ -2780,6 +2910,22 @@ next_for_item(void *fi_void, char_u *arg)
        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 @@ free_for_info(void *fi_void)
     }
     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);
@@ -3959,6 +4107,36 @@ eval_addlist(typval_T *tv1, typval_T *tv2)
     return OK;
 }
 
+/*
+ * 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 @@ eval6(char_u **arg, typval_T *rettv, evalarg_T *evalarg)
        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 @@ eval6(char_u **arg, typval_T *rettv, evalarg_T *evalarg)
            *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 @@ eval6(char_u **arg, typval_T *rettv, evalarg_T *evalarg)
            /*
             * 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 @@ eval6(char_u **arg, typval_T *rettv, evalarg_T *evalarg)
                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 @@ handle_predefined(char_u *s, int len, typval_T *rettv)
                    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 @@ eval9_nested_expr(
     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 @@ eval9_var_func_name(
  *  $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 @@ eval9_var_func_name(
  *  ! 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 @@ eval9(
     /*
      * nested expression: (expression).
      * or lambda: (arg) => expr
+     * or tuple
      */
     case '(':  ret = eval9_nested_expr(arg, rettv, evalarg, evaluate);
                break;
@@ -5484,7 +5694,8 @@ eval_index(
                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 @@ check_can_index(typval_T *rettv, int evaluate, int verbose)
 
        case VAR_STRING:
        case VAR_LIST:
+       case VAR_TUPLE:
        case VAR_DICT:
        case VAR_BLOB:
            break;
@@ -5735,6 +5947,16 @@ eval_index_inner(
                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;
@@ -6079,6 +6301,51 @@ list_tv2string(
     return r;
 }
 
+/*
+ * 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.
@@ -6316,6 +6583,10 @@ echo_string_core(
            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 @@ item_copy(
            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;
index 559247108111389c369c9b16ce28fc0c74292f2d..21ed15e981e460db59022a55b2120ecdbfc26c46 100644 (file)
@@ -360,6 +360,15 @@ arg_list_string(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
     return check_arg_type(&t_list_string, type, context);
 }
 
+/*
+ * 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.
  */
@@ -429,6 +438,42 @@ arg_list_or_blob_mod(
     return arg_type_modifiable(type, context->arg_idx + 1);
 }
 
+/*
+ * 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
  */
@@ -461,7 +506,10 @@ arg_buffer(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
  * 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 @@ arg_lnum(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
  * 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 @@ arg_string_or_list_string(type_T *type, type_T *decl_type UNUSED, argcontext_T *
  * 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 @@ arg_string_or_list_any(type_T *type, type_T *decl_type UNUSED, argcontext_T *con
  * 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 @@ arg_string_or_dict_any(type_T *type, type_T *decl_type UNUSED, argcontext_T *con
  * 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 @@ arg_list_or_dict_mod(
 }
 
 /*
- * 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 @@ arg_list_or_dict_or_blob_mod(
  * 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
@@ -628,12 +709,36 @@ arg_list_or_dict_or_blob_or_string_mod(
     return arg_type_modifiable(type, context->arg_idx + 1);
 }
 
+/*
+ * 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 @@ arg_sort_how(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
  * 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 @@ varargs_class(type_T *type UNUSED,
 }
 
 /*
- * 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 @@ arg_string_list_or_blob(type_T *type, type_T *decl_type UNUSED, argcontext_T *co
 }
 
 /*
- * 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 @@ arg_same_as_prev(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
  * 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 @@ arg_item_of_prev(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
  * 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 @@ arg_str_or_nr_or_list(type_T *type, type_T *decl_type UNUSED, argcontext_T *cont
  * 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 @@ arg_extend3(type_T *type, type_T *decl_type, argcontext_T *context)
 }
 
 /*
- * 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 @@ arg_get1(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
 }
 
 /*
- * 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 @@ arg_len1(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
            || 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 @@ arg_remove2(type_T *type, type_T *decl_type, argcontext_T *context)
 }
 
 /*
- * 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 @@ arg_repeat1(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
            || 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 @@ arg_repeat1(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
 }
 
 /*
- * 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 @@ arg_slice1(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
 }
 
 /*
- * 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_dict_or_string[] = {arg_dict_any_or_string};
 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_float_or_nr[] = {arg_float_or_nr, arg_float_or_nr};
 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_dict[] = {arg_string_or_list_any, arg_dict
 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 arg34_assert_inrange[] = {arg_float_or_nr, arg_float_or_nr, ar
 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_extendnew[] = {arg_list_or_dict, arg_same_struct_as_prev
 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 arg25_matchaddpos[] = {arg_string, arg_list_any, arg_number, a
 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 @@ ret_list_regionpos(int argcount UNUSED,
     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 @@ ret_slice(int argcount,
                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 @@ static funcentry_T global_functions[] =
                        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 @@ static funcentry_T global_functions[] =
                        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 @@ static funcentry_T global_functions[] =
                        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 @@ static funcentry_T global_functions[] =
                        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 @@ static funcentry_T global_functions[] =
            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 @@ static funcentry_T global_functions[] =
                        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 @@ static funcentry_T global_functions[] =
                        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 @@ static funcentry_T global_functions[] =
                        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 @@ f_empty(typval_T *argvars, typval_T *rettv)
            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 @@ f_get(typval_T *argvars, typval_T *rettv)
 {
     listitem_T *li;
     list_T     *l;
+    tuple_T    *tuple;
     dictitem_T *di;
     dict_T     *d;
     typval_T   *tv = NULL;
@@ -5298,6 +5446,18 @@ f_get(typval_T *argvars, typval_T *rettv)
                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 @@ f_get(typval_T *argvars, typval_T *rettv)
        }
     }
     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 @@ f_id(typval_T *argvars, typval_T *rettv)
     switch (argvars[0].v_type)
     {
        case VAR_LIST:
+       case VAR_TUPLE:
        case VAR_DICT:
        case VAR_OBJECT:
        case VAR_JOB:
@@ -7837,68 +7998,85 @@ f_id(typval_T *argvars, typval_T *rettv)
 }
 
 /*
- * "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                ic = FALSE;
+    int                start = 0;
     int                error = FALSE;
+    int                ic = 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)))
+    b = argvars[0].vval.v_blob;
+    if (b == NULL)
        return;
 
-    if (argvars[0].v_type == VAR_BLOB)
+    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 = tv_get_number_chk(&argvars[2], &error);
+       if (error)
            return;
+    }
+
+    if (start < 0)
+    {
+       start = blob_len(b) + start;
        if (start < 0)
-       {
-           start = blob_len(b) + start;
-           if (start < 0)
-               start = 0;
-       }
+           start = 0;
+    }
 
-       for (idx = start; idx < blob_len(b); ++idx)
+    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))
        {
-           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;
-           }
+           rettv->vval.v_number = idx;
+           return;
        }
-       return;
     }
-    else if (argvars[0].v_type != VAR_LIST)
-    {
-       emsg(_(e_list_or_blob_required));
+}
+
+/*
+ * 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;
+
+    if (tuple == NULL)
        return;
+
+    int        start_idx = 0;
+    if (argvars[2].v_type != VAR_UNKNOWN)
+    {
+       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;
     }
 
+    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)
        return;
@@ -7925,12 +8103,39 @@ f_index(typval_T *argvars, typval_T *rettv)
        }
 }
 
+/*
+ * "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 @@ f_indexof(typval_T *argvars, typval_T *rettv)
 
     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 @@ f_indexof(typval_T *argvars, typval_T *rettv)
     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 @@ f_len(typval_T *argvars, typval_T *rettv)
        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 @@ max_min(typval_T *argvars, typval_T *rettv, int domax)
     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 @@ max_min(typval_T *argvars, typval_T *rettv, int domax)
            }
        }
     }
+    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 @@ f_rename(typval_T *argvars, typval_T *rettv)
 }
 
 /*
- * "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)
 {
-    char_u     *p;
-    varnumber_T        n;
     int                slen;
     int                len;
-    char_u     *r;
     int                i;
+    blob_T     *blob = blob_tv->vval.v_blob;
 
-    if (in_vim9script()
-           && (check_for_string_or_number_or_list_or_blob_arg(argvars, 0)
-                   == FAIL
-               || check_for_number_arg(argvars, 1) == FAIL))
+    if (rettv_blob_alloc(rettv) == FAIL
+           || blob == NULL
+           || n <= 0)
        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;
-    }
-    else if (argvars[0].v_type == VAR_BLOB)
-    {
-       if (rettv_blob_alloc(rettv) == FAIL
-               || argvars[0].vval.v_blob == NULL
-               || n <= 0)
-           return;
+    slen = blob->bv_ga.ga_len;
+    len = (int)slen * n;
+    if (len <= 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;
 
-       if (ga_grow(&rettv->vval.v_blob->bv_ga, len) == FAIL)
-           return;
+    rettv->vval.v_blob->bv_ga.ga_len = len;
 
-       rettv->vval.v_blob->bv_ga.ga_len = len;
+    for (i = 0; i < slen; ++i)
+       if (blob_get(blob, i) != 0)
+           break;
 
-       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;
 
-       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);
+}
 
-       for (i = 0; i < n; ++i)
-           blob_set_range(rettv->vval.v_blob,
-                   (long)i * slen, ((long)i + 1) * slen - 1, argvars);
-    }
-    else
-    {
-       p = tv_get_string(&argvars[0]);
-       rettv->v_type = VAR_STRING;
-       rettv->vval.v_string = NULL;
+/*
+ * 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;
+    int                slen;
+    int                len;
+    char_u     *r;
+    int                i;
 
-       slen = (int)STRLEN(p);
-       len = slen * n;
-       if (len <= 0)
-           return;
+    p = tv_get_string(str_tv);
+    rettv->v_type = VAR_STRING;
+    rettv->vval.v_string = NULL;
 
-       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;
-       }
+    slen = (int)STRLEN(p);
+    len = slen * n;
+    if (len <= 0)
+       return;
 
-       rettv->vval.v_string = r;
-    }
+    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_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)
+       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)
+       repeat_blob(&argvars[0], n, rettv);
+    else
+       repeat_string(&argvars[0], n, rettv);
 }
 
 #define SP_NOMOVE      0x01        // don't move cursor
@@ -12191,6 +12440,7 @@ f_type(typval_T *argvars, typval_T *rettv)
        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;
index 2745ac271a6a733ae8ce7356e53d68066eb191de..9382842f37f46e6d6233b8bf31af627efdddcdf3 100644 (file)
@@ -162,6 +162,7 @@ static struct vimvar
     {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 @@ evalvars_init(void)
     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 @@ evalvars_clear(void)
     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 @@ garbage_collect_scriptvars(int copyID)
 
     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 @@ garbage_collect_scriptvars(int copyID)
            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 @@ ex_let_vars(
 {
     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 @@ ex_let_vars(
     }
 
     // ":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)
+           {
+               // 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
            {
-               list_append_tv(l, &item->li_tv);
-               item = item->li_next;
+               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), &ltv, FALSE,
-                           flags | ASSIGN_UNPACK, (char_u *)"]", op, var_idx);
+                       flags | ASSIGN_UNPACK, (char_u *)"]", op, var_idx);
            clear_tv(&ltv);
            if (arg == NULL)
                return FAIL;
@@ -2418,6 +2486,9 @@ item_lock(typval_T *tv, int deep, int lock, int check_refcount)
                }
            }
            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 @@ eval_variable(
                }
            }
 
-           // 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 @@ eval_variable(
                            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()))
index bda20f54cfce582739d6771f795d6533b1a90c7a..f341fd42a61310f4db584ef072e9dabf656dbdf0 100644 (file)
@@ -3799,8 +3799,12 @@ find_ex_command(
                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 != '=')
                {
index 54c744480498db8f88c36328b48baa01c4f9646d..b95b2cad29307414324db6343ff03948c539df3c 100644 (file)
--- a/src/gc.c
+++ b/src/gc.c
@@ -122,31 +122,31 @@ garbage_collect(int testing)
     // 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 @@ free_unref_items(int copyID)
     // 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 @@ free_unref_items(int copyID)
     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 @@ free_unref_items(int copyID)
  * 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 @@ set_ref_in_ht(hashtab_T *ht, int copyID, list_stack_T **list_stack)
                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 @@ set_ref_in_dict(dict_T *d, int copyID)
     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 @@ set_ref_in_list(list_T *ll, int copyID)
     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 @@ set_ref_in_list(list_T *ll, int copyID)
  * 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 @@ set_ref_in_list_items(list_T *l, int copyID, ht_stack_T **ht_stack)
            // 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;
 
@@ -425,6 +438,50 @@ set_ref_in_list_items(list_T *l, int copyID, ht_stack_T **ht_stack)
     return abort;
 }
 
+/*
+ * 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".
  */
@@ -438,7 +495,7 @@ set_ref_in_callback(callback_T *cb, int copyID)
 
     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 @@ set_ref_in_item_dict(
     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 @@ set_ref_in_item_dict(
     // 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 @@ set_ref_in_item_list(
     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 @@ set_ref_in_item_list(
     // 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)
@@ -501,6 +560,37 @@ set_ref_in_item_list(
     return FALSE;
 }
 
+/*
+ * 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 @@ set_ref_in_item_partial(
     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 @@ set_ref_in_item_partial(
 
        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 @@ set_ref_in_item_partial(
 
        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 @@ set_ref_in_item_job(
     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 @@ set_ref_in_item_job(
     {
        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 @@ set_ref_in_item_channel(
     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 @@ set_ref_in_item_channel(
     {
        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 @@ set_ref_in_item_class(
     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 @@ set_ref_in_item_class(
        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 @@ set_ref_in_item_object(
     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 @@ set_ref_in_item_object(
     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 @@ set_ref_in_item(
     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 @@ set_ref_in_item(
     {
        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 @@ set_ref_in_item(
 
        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 @@ set_ref_in_item(
        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:
index ef78b443399e38196edbae4e61c1ba374eefd9be..7e65af30d8e81947eaeabab1a018b275f09a399f 100644 (file)
@@ -549,7 +549,14 @@ EXTERN int garbage_collect_at_exit INIT(= FALSE);
 #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 @@ EXTERN type_T static_types[92]
     // 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
 ;
index a679be54afbc86a743f6ea9722b9c250f8a32cd0..9f2f5821bba2d93cf050a0bf86bc4c422624de1f 100644 (file)
@@ -6248,7 +6248,7 @@ set_ref_in_py(const int copyID)
            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 @@ ConvertToPyObject(typval_T *tv)
        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:
index 2a2e531017287d2370a84f9ca543ab43131b74cc..9d6c8628ba50156578e6e1284df3221656fd0c39 100644 (file)
--- a/src/job.c
+++ b/src/job.c
@@ -1087,7 +1087,7 @@ set_ref_in_job(int copyID)
        {
            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;
 }
index 66b8bf9d1466651b1de93ad0c55e05e80a8e8ba6..faa35e576d0fb6b2ee4adcc2b329cb0d2c21ffad 100644 (file)
@@ -267,6 +267,7 @@ json_encode_item(garray_T *gap, typval_T *val, int copyID, int options)
     char_u     *res;
     blob_T     *b;
     list_T     *l;
+    tuple_T    *tuple;
     dict_T     *d;
     int                i;
 
@@ -369,6 +370,42 @@ json_encode_item(garray_T *gap, typval_T *val, int copyID, int options)
            }
            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)
index 36ce494df86b4b1a20b95425ac9ad6bc372c2c64..c48c75152ba56554258aa7d830c58df3b0fe5e65 100644 (file)
@@ -1520,15 +1520,13 @@ f_join(typval_T *argvars, typval_T *rettv)
 
     rettv->v_type = VAR_STRING;
 
-    if (in_vim9script()
-           && (check_for_list_arg(argvars, 0) == FAIL
-               || check_for_opt_string_arg(argvars, 1) == FAIL))
-       return;
-
-    if (check_for_list_arg(argvars, 0) == FAIL)
+    if (check_for_list_or_tuple_arg(argvars, 0) == FAIL
+           || check_for_opt_string_arg(argvars, 1) == 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 @@ f_join(typval_T *argvars, typval_T *rettv)
     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;
     }
@@ -1777,6 +1778,63 @@ f_list2str(typval_T *argvars, typval_T *rettv)
     rettv->vval.v_string = ga.ga_data;
 }
 
+/*
+ * "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 @@ filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap)
        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 @@ filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap)
 
     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 @@ filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap)
                                                            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 @@ f_count(typval_T *argvars, typval_T *rettv)
     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 @@ f_count(typval_T *argvars, typval_T *rettv)
        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 @@ list_reverse(list_T *l, typval_T *rettv)
     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 @@ f_reverse(typval_T *argvars, typval_T *rettv)
     }
     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 @@ list_reduce(
 
 /*
  * "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 @@ f_reduce(typval_T *argvars, typval_T *rettv)
 {
     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 @@ f_reduce(typval_T *argvars, typval_T *rettv)
 
     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 @@ f_slice(typval_T *argvars, typval_T *rettv)
     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
index 6c2931a292ae18f3a3908b7cad03cc31ae313e12..c11bfa5f79358a17f2fec3c8ba01e8427ee0ea0a 100644 (file)
 // 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)
index 781caa8994493f0fc4ee20a6ec4e915da228d70c..5cbbab78b707ef3979499e487b9077b12279c704 100644 (file)
@@ -2561,7 +2561,7 @@ set_ref_in_nb_channel(int copyID)
 
     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
index 76ebf38e5224b5b62dfa721692f0f539be478d70..46e54837f5185f738a5e6989790e322db6077025 100644 (file)
@@ -4416,13 +4416,13 @@ set_ref_in_one_popup(win_T *wp, int copyID)
     {
        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;
index 091e09350ca864b9949a779e36d5966ea2fb9cac..f04ba05259b4dc5be4a5bc50979a58659f35ac33 100644 (file)
@@ -207,6 +207,7 @@ void mbyte_im_set_active(int active_arg);
 # include "textobject.pro"
 # include "textformat.pro"
 # include "time.pro"
+# include "tuple.pro"
 # include "typval.pro"
 # include "ui.pro"
 # include "undo.pro"
index e945a284cc678cf396bfa67971e8383f96b112c4..df593833805d5c90c40467d2feb72c18c100ad33 100644 (file)
@@ -45,6 +45,7 @@ int eval0_retarg(char_u *arg, typval_T *rettv, exarg_T *eap, evalarg_T *evalarg,
 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);
index a720b643e479060a243bfedff635c6d29de97bc5..627af17a8ba68e77dec6e888d1ede86668354e9c 100644 (file)
@@ -23,6 +23,7 @@ void execute_common(typval_T *argvars, typval_T *rettv, int arg_off);
 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);
index e13dbda31c9487e027814bad1bf8418839e8a73f..8b550300b0742445f83ab184f6e303923e8808ca 100644 (file)
@@ -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 : */
-
index 27bea5e8789e908bc5d32694042973c5b3b2ba67..cb052030b2d3172a51af1badbc18bcae23268fae 100644 (file)
@@ -50,6 +50,8 @@ int eval_list(char_u **arg, typval_T *rettv, evalarg_T *evalarg, int do_error);
 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);
index dea4f753525964f517fcc2ace9c159dad42d969d..1c93f8cddf798b709cc3d17b47d495a132e04003 100644 (file)
@@ -30,6 +30,7 @@ void f_test_null_list(typval_T *argvars, typval_T *rettv);
 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 (file)
index 0000000..138648d
--- /dev/null
@@ -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 : */
index 90dcc5434c6e46b86c90bdcb7542e40d94027abb..d30cdedc1834a436cb51b56d82e96b46686895f1 100644 (file)
@@ -18,13 +18,13 @@ int check_for_number_arg(typval_T *args, int idx);
 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_lnum_arg(typval_T *args, int idx);
 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 @@ int tv_check_lock(typval_T *tv, char_u *name, int use_gettext);
 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);
index 641648f4e9c63495db6beae15fce9b65080f9252..a4504c5137ef487123a91cf4ae7035d802764f33 100644 (file)
@@ -44,6 +44,7 @@ int generate_LOCKCONST(cctx_T *cctx);
 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);
index 093a5ec35f76bca221ef8cdfdea0cb49c600f697..865a93c1b99fa17977e69faeaae08d3953449644 100644 (file)
@@ -7,6 +7,7 @@ type_T *alloc_type(type_T *type);
 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 @@ char_u *skip_type(char_u *start, int optional);
 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);
index e498d5eba406686035798ac47a439117fe4c681f..30353c5424594c6bf9aae59f9c3214760319ce9c 100644 (file)
@@ -8065,7 +8065,7 @@ static int mark_quickfix_user_data(qf_info_T *qi, int copyID)
            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 @@ mark_quickfix_ctx(qf_info_T *qi, int copyID)
        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);
index ce98bcef50479aabbbbc9e34f9342c8f964d2559..1a3abcb72a60494df4219dfaf0471948765b65ed 100644 (file)
@@ -73,6 +73,7 @@ typedef struct listvar_S      list_T;
 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 @@ typedef enum
     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 @@ struct typval_S
        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 @@ struct blobvar_S
     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 @@ typedef struct
     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;
 
@@ -2820,6 +2840,15 @@ typedef struct list_stack_S
     struct list_stack_S        *prev;
 } 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 @@ typedef struct lval_S
     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
index 6edc21a11d9c56fe90b0c4ae2ca5b3cc6982c185..eb27fa5c4c5a2bb2d3faf50ae666ec3ec36b30f0 100644 (file)
@@ -5081,7 +5081,7 @@ set_ref_in_term(int copyID)
        {
            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;
 }
index 7d50a7eeb41be18cbacc4bb82118811ca75cc4b3..55434b0dc868383cc30aa7e95d8e5bf9f7edcaa4 100644 (file)
@@ -327,6 +327,7 @@ NEW_TESTS = \
        test_timers \
        test_true_false \
        test_trycatch \
+       test_tuple \
        test_undo \
        test_unlet \
        test_user_func \
@@ -578,6 +579,7 @@ NEW_TESTS_RES = \
        test_timers.res \
        test_true_false.res \
        test_trycatch.res \
+       test_tuple.res \
        test_undo.res \
        test_user_func.res \
        test_usercommands.res \
index f0e8209063f60a6807557f6c5567db225058bb83..a4a567d398138fd68b826f6b7e04a20a069d20ae 100644 (file)
@@ -35,7 +35,7 @@ func Test_blob_create()
       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 @@ endfunc
 
 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))
index 1b17108c4f3ff5daa3d940114576827aac225153..d2f949ed57b18dfd31c101d07edcfcf0dea20d59 100644 (file)
@@ -124,9 +124,9 @@ func Test_E963()
 endfunc
 
 func Test_for_invalid()
-  call assert_fails("for x in 99", 'E1098:')
-  call assert_fails("for x in function('winnr')", 'E1098:')
-  call assert_fails("for x in {'a': 9}", 'E1098:')
+  call assert_fails("for x in 99", 'E1523:')
+  call assert_fails("for x in function('winnr')", 'E1523:')
+  call assert_fails("for x in {'a': 9}", 'E1523:')
 
   let lines =<< trim END
     for v:maxcol in range(5)
index 5dedce583f364d0fe20da2f46b82c0eb69026b3f..41b54aac1399b7732112d2d380645669a1b58bbf 100644 (file)
@@ -658,10 +658,10 @@ func Test_printf_spec_b()
 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()
index 37ebe847b1aae626925f039edb4bfa1f799a2239..39da767fab5ca19f36a9258e72773a944587e99e 100644 (file)
@@ -173,6 +173,7 @@ func Test_map_filter_fails()
   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()
index 250ce5c481640ea0f077dc018bccd48899d585a9..f05080e0891a608f6ae6b3f3c118bf587f87f57c 100644 (file)
@@ -362,7 +362,7 @@ endfunc
 
 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()
index e31b514760afd6bd9dc73579df0aa433eade68fe..dffc5c69599847033eda393e124cb703829f07e1 100644 (file)
@@ -310,7 +310,7 @@ func Test_let_errors()
   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:')
index f3bdcd4bd1ab81a70bdf20959cd7732e7ecd818c..fb350a813c048751a08a33857026ecc04070238b 100644 (file)
@@ -192,6 +192,14 @@ func Test_list_assign()
   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 @@ func Test_reverse_sort_uniq()
   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 @@ func Test_reduce()
   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 @@ func Test_null_list()
   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 @@ func Test_indexof()
   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)
index f18ac14340f874f884e76afa15b41841a366a28a..99e4917c0cad24ec2e64f154eae1b79f6242d4fe 100644 (file)
@@ -52,7 +52,7 @@ func Test_dict_method()
   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 @@ func Test_dict_method()
   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())
index 26eb7f0eb451c8169b96d4bfa64798afd365b82b..1e123d15e5d047aabb1ba57f54233dbe7c789904 100644 (file)
@@ -433,4 +433,12 @@ func Test_put_inserted()
   bwipe!
 endfunc
 
+func Test_put_tuple()
+  new
+  let t = ('a', 'b', 'c')
+  put! =t
+  call assert_equal(['a', 'b', 'c', ''], getline(1, '$'))
+  bw!
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_tuple.vim b/src/testdir/test_tuple.vim
new file mode 100644 (file)
index 0000000..fce5292
--- /dev/null
@@ -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
index 616e224915c625feb0fe0736bb535d8d89c66f9e..97c11a740dd5f0089f84708091b481f1d9af6632 100644 (file)
@@ -538,7 +538,7 @@ def Test_assign_unpack()
       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]
index 80ed2b230ac3e6ca5c6daa8ba6865434650b74a1..f8e130603dfa0c69fe96ad53ac4b1bb5db1aba96 100644 (file)
@@ -882,7 +882,7 @@ enddef
 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 @@ def Test_filter()
   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 @@ def Test_foldtextresult()
   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 @@ def Test_garbagecollect()
 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 @@ enddef
 
 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 @@ def Test_job_stop()
 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 @@ enddef
 
 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 @@ enddef
 
 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 @@ def Test_max()
           ? [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 @@ def Test_min()
           ? [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 @@ def Test_readfile()
 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 @@ def Test_rename()
 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 @@ def Test_resolve()
 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()
index 27b71feb2a0b5b41e928bdf395bab995771d3bb1..48779337ced0d95627f5731facc2bf55528f929d 100644 (file)
@@ -3695,4 +3695,184 @@ def Test_disassemble_using_script_local_var_in_obj_init()
   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
index 21f894e9a3e15abc7f92d2ad961173e74f15547f..5c88ec6237fb59a67c22184364791ada922521fb 100644 (file)
@@ -7519,6 +7519,16 @@ func Test_for_over_string()
   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
index ff4db7bbc598b54ac76b9500ecb6829b7d79909f..64922b7d184266380f9f997588b2ed8c467bbf83 100644 (file)
@@ -191,19 +191,23 @@ export func CheckLegacyFailure(lines, error)
   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 @@ export def CheckLegacyAndVim9Failure(lines: list<string>, error: any)
   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 @@ export def CheckSourceScriptSuccess(lines: list<string>)
   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 @@ export func CheckSourceDefSuccess(lines)
   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 @@ export def CheckSourceDefExecAndScriptFailure(lines: list<string>, error: any, l
   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
+
index 7ab109c10ca545b60cb0286b14dd13c8cd74ecec..b316b64c9242df3f21b0fb54ff57aad981f7849f 100644 (file)
@@ -1132,6 +1132,10 @@ f_test_refcount(typval_T *argvars, typval_T *rettv)
            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;
@@ -1248,6 +1252,12 @@ f_test_null_string(typval_T *argvars UNUSED, typval_T *rettv)
     rettv->vval.v_string = NULL;
 }
 
+    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)
 {
index 8725a8852cab1d22e8b1ae8deb6a513afc758b4d..2fb0c39317b83410dd9d4f6f792c2edf1e1d51c4 100644 (file)
@@ -778,7 +778,7 @@ set_ref_in_timer(int copyID)
            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 (file)
index 0000000..eff4bdc
--- /dev/null
@@ -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)
index cd39a0d97316dc413d8c3be2a83e9f3d5e3cc0a8..59ac61133e1560c3bd208354aba72f6efebd5865 100644 (file)
@@ -72,6 +72,9 @@ free_tv(typval_T *varp)
        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 @@ clear_tv(typval_T *varp)
            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 @@ tv_get_bool_or_number_chk(
        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 @@ tv_get_float_chk(typval_T *varp, int *error)
        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 @@ check_for_bool_arg(typval_T *args, int idx)
 /*
  * 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)
@@ -619,6 +632,20 @@ check_for_opt_list_arg(typval_T *args, int idx)
            || check_for_list_arg(args, idx) != FAIL) ? OK : FAIL;
 }
 
+/*
+ * 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.
  */
@@ -827,17 +854,18 @@ check_for_string_or_list_arg(typval_T *args, int idx)
 }
 
 /*
- * 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 @@ check_for_opt_string_or_number_or_list_arg(typval_T *args, int idx)
 }
 
 /*
- * 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 @@ check_for_list_or_blob_arg(typval_T *args, int idx)
 }
 
 /*
- * 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 @@ check_for_list_or_dict_or_blob_arg(typval_T *args, int idx)
 }
 
 /*
- * 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,
+ * 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 @@ tv_get_string_buf_chk_strict(typval_T *varp, char_u *buf, int strict)
        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 @@ tv_check_lock(typval_T *tv, char_u *name, int use_gettext)
            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 @@ copy_tv(typval_T *from, typval_T *to)
                ++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 @@ typval_compare(
        }
        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)
@@ -1649,6 +1738,47 @@ typval_compare_list(
     return OK;
 }
 
+/*
+ * 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.
  */
@@ -1674,6 +1804,7 @@ typval_compare_null(typval_T *tv1, typval_T *tv2)
            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 @@ tv_equal(
            --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);
index d8d7014aa16a9af98ba9de40c7dbb1df3796dc61..b328cf58ad7f43438b1e2caac80c75be75e979c1 100644 (file)
@@ -7286,9 +7286,9 @@ set_ref_in_previous_funccal(int copyID)
     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 @@ set_ref_in_funccal(funccall_T *fc, int copyID)
     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 @@ set_ref_in_func_args(int copyID)
 
     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;
 }
index dedfd0af9da36df76423dbfe8f4f9509b25a78f8..7ee6d0a8d80e21d9f4bc54c679b6c09fb970ddf1 100644 (file)
@@ -704,6 +704,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1232,
 /**/
     1231,
 /**/
index 85fad6ce41228f7102ce5f530f129f1cb4b850b9..dd04f6504300dfb61bfcb273161e0f51eb80334d 100644 (file)
--- a/src/vim.h
+++ b/src/vim.h
@@ -2201,7 +2201,8 @@ typedef int sock_T;
 #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 @@ typedef int sock_T;
 #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
 
index 7c731fa60457176f6d341d985fc0ae0951ae2481..c10d435a2427fae8afa95a74a1ea731208d73fa9 100644 (file)
@@ -105,6 +105,8 @@ typedef enum {
     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 @@ typedef enum {
 
     // 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 @@ typedef enum {
     ISN_COMPARESTRING,
     ISN_COMPAREBLOB,
     ISN_COMPARELIST,
+    ISN_COMPARETUPLE,
     ISN_COMPAREDICT,
     ISN_COMPAREFUNC,
     ISN_COMPAREANY,
@@ -177,6 +181,8 @@ typedef enum {
     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
index 1c54474e45638ea0967be2a57f56bd6918133ca1..5249f40de41cd5840138cee2f9c3468e643d6fd7 100644 (file)
@@ -3650,7 +3650,7 @@ class_free_nonref(int copyID)
 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;
 }
index aeb742e5709ca74ee8e3b3035d5f826a52da03e8..6295090110824ca39f1816ce50e68136ced5b385 100644 (file)
@@ -870,6 +870,40 @@ compile_fill_loop_info(loop_info_T *loop_info, int funcref_idx, cctx_T *cctx)
     loop_info->li_closure_count = cctx->ctx_closure_count;
 }
 
+/*
+ * 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":
  *
@@ -1000,6 +1034,7 @@ compile_for(char_u *arg_start, cctx_T *cctx)
        // 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 @@ compile_for(char_u *arg_start, cctx_T *cctx)
                          && 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);
index cb7b94822663c7c79a2179336294fa4d43a4ac74..34a78daef4863b78c4e5d1e823c893cb826fd6c8 100644 (file)
@@ -524,8 +524,10 @@ use_typecheck(type_T *actual, type_T *expected)
        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 @@ compile_assign_unlet(
            && 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 @@ compile_assign_unlet(
     }
     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 @@ push_default_value(
        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 @@ compile_assign_list_check_rhs_type(cctx_T *cctx, cac_T *cac)
        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 (stacktype->tt_member != NULL)
+    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_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 @@ compile_assign_list_check_length(cctx_T *cctx, cac_T *cac)
        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 @@ compile_assign_compound_op(cctx_T *cctx, cac_T *cac)
     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 @@ compile_assign_generate_store(cctx_T *cctx, cac_T *cac)
                && 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 @@ compile_assign_generate_store(cctx_T *cctx, cac_T *cac)
                && 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 @@ obj_constructor_prologue(ufunc_T *ufunc, cctx_T *cctx)
        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);
index c0c3103d4449b1583bea7678b8ca233fc757d166..55f9d4327520e4daca68059e045d40c2c7bc7851 100644 (file)
@@ -110,6 +110,11 @@ static garray_T profile_info_ga = {0, 0, sizeof(profinfo_T), 20, NULL};
 // 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)
 {
@@ -206,6 +211,46 @@ exe_newlist(int count, ectx_T *ectx)
     return OK;
 }
 
+/*
+ * 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 @@ set_ref_in_funcstacks(int copyID)
        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 @@ allocate_if_null(svar_T *sv)
            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 @@ execute_storeindex(isn_T *iptr, ectx_T *ectx)
            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 @@ execute_for(isn_T *iptr, ectx_T *ectx)
            ++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 @@ set_ref_in_loopvars(int copyID)
        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;
@@ -3304,6 +3372,145 @@ isn_put_do(ectx_T *ectx, isn_T *iptr, typval_T *tv, int fixindent)
     vim_free(expr);
 }
 
+/*
+ * 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 @@ exec_instructions(ectx_T *ectx)
                    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 @@ exec_instructions(ectx_T *ectx)
                    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 @@ exec_instructions(ectx_T *ectx)
                break;
 
            case ISN_COMPARELIST:
+           case ISN_COMPARETUPLE:
            case ISN_COMPAREDICT:
            case ISN_COMPAREFUNC:
            case ISN_COMPARESTRING:
@@ -5318,6 +5532,11 @@ exec_instructions(ectx_T *ectx)
                        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 @@ exec_instructions(ectx_T *ectx)
                break;
 
            case ISN_ADDLIST:
+           case ISN_ADDTUPLE:
            case ISN_ADDBLOB:
                {
                    typval_T *tv1 = STACK_TV_BOT(-2);
@@ -5385,6 +5605,8 @@ exec_instructions(ectx_T *ectx)
                        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 @@ exec_instructions(ectx_T *ectx)
                            --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 @@ exec_instructions(ectx_T *ectx)
 
            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 @@ exec_instructions(ectx_T *ectx)
                                                    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 @@ exec_instructions(ectx_T *ectx)
 
            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;
+                   if (tv->v_type == VAR_LIST)
+                   {
+                       list_T *list = tv->vval.v_list;
 
-                   // no error for short list, expect it to be checked earlier
-                   if (list != NULL && list->lv_len >= count)
+                       // no error for short list, expect it to be checked
+                       // earlier
+                       if (list != NULL && list->lv_len >= count)
+                       {
+                           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
                    {
-                       list_T  *newlist = list_slice(list,
-                                                     count, list->lv_len - 1);
+                       tuple_T *tuple = tv->vval.v_tuple;
 
-                       if (newlist != NULL)
+                       // no error for short tuple, expect it to be checked
+                       // earlier
+                       if (tuple != NULL && TUPLE_LEN(tuple) >= count)
                        {
-                           list_unref(list);
-                           tv->vval.v_list = newlist;
-                           ++newlist->lv_refcount;
+                           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 @@ exec_instructions(ectx_T *ectx)
            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 @@ exec_instructions(ectx_T *ectx)
                {
                    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 @@ exec_instructions(ectx_T *ectx)
                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));
-                       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));
+               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;
-                   }
-
-                   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 @@ list_instructions(char *pfx, isn_T *instr, int instr_count, ufunc_T *ufunc)
                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 @@ list_instructions(char *pfx, isn_T *instr, int instr_count, ufunc_T *ufunc)
            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 @@ list_instructions(char *pfx, isn_T *instr, int instr_count, ufunc_T *ufunc)
                                                  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 @@ list_instructions(char *pfx, isn_T *instr, int instr_count, ufunc_T *ufunc)
                   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 @@ list_instructions(char *pfx, isn_T *instr, int instr_count, ufunc_T *ufunc)
            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 @@ tv2bool(typval_T *tv)
            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;
index f875bc404ac19321308c082d60aa6143f46aeccd..68de7360129320ace5500061b107c20bcda3bb45 100644 (file)
@@ -73,7 +73,74 @@ clear_ppconst(ppconst_T *ppconst)
 }
 
 /*
- * 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 @@ compile_member(int is_slice, int *keeping_dict, cctx_T *cctx)
     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 @@ compile_member(int is_slice, int *keeping_dict, cctx_T *cctx)
                || 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 @@ compile_member(int is_slice, int *keeping_dict, cctx_T *cctx)
                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)
     {
@@ -1465,6 +1538,82 @@ compile_list(char_u **arg, cctx_T *cctx, ppconst_T *ppconst)
     return generate_NEWLIST(cctx, count, FALSE);
 }
 
+/*
+ * 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 '('.
@@ -2168,6 +2317,11 @@ compile_parenthesis(char_u **arg, cctx_T *cctx, ppconst_T *ppconst)
 
     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 @@ compile_parenthesis(char_u **arg, cctx_T *cctx, ppconst_T *ppconst)
     }
     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 @@ compile_subscript(
            int         is_slice = FALSE;
 
            // list index: list[123]
+           // tuple index: tuple[123]
            // dict member: dict[key]
            // string index: text[123]
            // blob index: blob[123]
index 3da56bf97d500580774632c0a0b37ae727c1a179..1b322f64a1f4d7ae06a275b81e4a92c8dabf7421 100644 (file)
@@ -224,6 +224,7 @@ may_generate_2STRING(int offset, int tostring_flags, cctx_T *cctx)
 
        // conversion possible when tolerant
        case VAR_LIST:
+       case VAR_TUPLE:
        case VAR_DICT:
                         if (tostring_flags & TOSTRING_TOLERANT)
                         {
@@ -280,6 +281,58 @@ check_number_or_float(type_T *typ1, type_T *typ2, char_u *op)
     return OK;
 }
 
+/*
+ * 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.
  */
@@ -294,11 +347,12 @@ generate_add_instr(
     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 @@ generate_add_instr(
            && 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 @@ operator_type(type_T *type1, type_T *type2)
     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 @@ get_compare_isn(
            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 @@ generate_tv_PUSH(cctx_T *cctx, typval_T *tv)
                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 @@ generate_GETITEM(cctx_T *cctx, int index, int with_op)
 
     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;
@@ -1369,6 +1438,45 @@ generate_NEWLIST(cctx_T *cctx, int count, int use_null)
     return push_type_stack2(cctx, type, decl_type);
 }
 
+/*
+ * 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 @@ delete_instr(isn_T *isn)
        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 @@ delete_instr(isn_T *isn)
        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 @@ delete_instr(isn_T *isn)
        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 @@ delete_instr(isn_T *isn)
        case ISN_NEGATENR:
        case ISN_NEWDICT:
        case ISN_NEWLIST:
+       case ISN_NEWTUPLE:
        case ISN_NEWPARTIAL:
        case ISN_OPANY:
        case ISN_OPFLOAT:
index 30358896493844c63fd82daef9ca2ce2584af53a..cdacf0b2300cc0adc1d6b5eb2b0c0d9007845ad0 100644 (file)
@@ -1127,6 +1127,7 @@ static char *reserved[] = {
     "null_dict",
     "null_function",
     "null_list",
+    "null_tuple",
     "null_partial",
     "null_string",
     "null_channel",
index 1f044d3d43853c7e67b1b98cd55a789f9a98048c..abf4daf0f36bc7ff626eddefb9a1f38a8ada7563 100644 (file)
@@ -202,7 +202,7 @@ set_tv_type_recurse(type_T *type)
 {
     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_tv_type_list(list_T *l, type_T *type)
 }
 
 /*
- * 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(typval_T *tv, type_T *type)
        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 @@ get_list_type(type_T *member_type, garray_T *type_gap)
     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)
 {
@@ -353,6 +443,23 @@ alloc_func_type(type_T *ret_type, int argcount, garray_T *type_gap)
     return type;
 }
 
+/*
+ * 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.
@@ -506,6 +613,64 @@ list_typval2type(typval_T *tv, int copyID, garray_T *type_gap, int flags)
     return get_list_type(member_type, type_gap);
 }
 
+/*
+ * 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
@@ -723,6 +888,9 @@ typval2type_int(typval_T *tv, int copyID, garray_T *type_gap, int flags)
        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);
 
@@ -949,6 +1117,63 @@ type_mismatch_where(type_T *expected, type_T *actual, where_T where)
     vim_free(tofree2);
 }
 
+/*
+ * 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.
@@ -1018,6 +1243,8 @@ check_type_maybe(
                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_type(char_u *start, int optional)
     // 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)
@@ -1422,6 +1671,116 @@ parse_type_func(char_u **arg, size_t len, garray_T *type_gap, int give_error)
     return type;
 }
 
+/*
+ * 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.
@@ -1577,6 +1936,13 @@ parse_type(char_u **arg, garray_T *type_gap, int give_error)
                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 @@ equal_type(type_T *type1, type_T *type2, int flags)
        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 @@ common_type(type_T *type1, type_T *type2, type_T **dest, garray_T *type_gap)
 
     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 @@ common_type(type_T *type1, type_T *type2, type_T **dest, garray_T *type_gap)
                *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;
@@ -1747,6 +2125,26 @@ common_type(type_T *type1, type_T *type2, type_T **dest, garray_T *type_gap)
     *dest = &t_any;
 }
 
+/*
+ * 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.
@@ -1864,6 +2262,40 @@ get_member_type_from_stack(
     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 @@ vartype_name(vartype_T type)
        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";
@@ -1918,6 +2351,65 @@ type_name_list_or_dict(char *name, type_T *type, char **tofree)
     return *tofree;
 }
 
+/*
+ * 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 @@ type_name(type_T *type, char **tofree)
        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);
index 5f1ad74758eca23db591f37e9b896a4597246077..44d54879fcc3d80aa27e03fcad122b30b1b2b895 100644 (file)
@@ -1263,6 +1263,7 @@ read_viminfo_varlist(vir_T *virp, int writing)
                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 @@ read_viminfo_varlist(vir_T *virp, int writing)
            {
                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 @@ read_viminfo_varlist(vir_T *virp, int writing)
                                             || 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 @@ write_viminfo_varlist(FILE *fp)
 
                              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 @@ write_viminfo_varlist(FILE *fp)
 
                              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;