Commit: patch 9.2.0958: Vim9: wrong type for the rest of a tuple in an unpack assignment

Christian Brabandt <[email protected]>
Newsgroups gmane.editors.vim.devel
Message-ID <[email protected]>
patch 9.2.0958: Vim9: wrong type for the rest of a tuple in an unpack assignment

Commit: https://github.com/vim/vim/commit/d4703e569d655c2db1642425ec649edeeaa57751
Author: Hirohito Higashi <[email protected]>
Date:   Sun Aug 16 15:42:56 2026 +0000

    patch 9.2.0958: Vim9: wrong type for the rest of a tuple in an unpack assignment
    
    Problem:  In an unpack assignment the variable after the ";" gets the type
              of the whole tuple instead of the type of the remaining items.
              When the assigned value has type "any" it even gets a list type,
              so that a tuple cannot be assigned to it later (Mao-Yining).
    Solution: Give the variable the type of the remaining items.  Keep "any"
              when it is not known whether a list or a tuple is assigned
              (Hirohito Higashi).
    
    fixes:  #21057
    fixes:  #21058
    closes: #21059
    
    Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
    Signed-off-by: Hirohito Higashi <[email protected]>
    Signed-off-by: Christian Brabandt <[email protected]>

diff --git a/runtime/doc/vim9.txt b/runtime/doc/vim9.txt
index be987bf02..e69b62acd 100644
--- a/runtime/doc/vim9.txt
+++ b/runtime/doc/vim9.txt
@@ -1,4 +1,4 @@
-*vim9.txt*	For Vim version 9.2.  Last change: 2026 Jul 29
+*vim9.txt*	For Vim version 9.2.  Last change: 2026 Aug 16
 
 
 		  VIM REFERENCE MANUAL	  by Bram Moolenaar
@@ -583,6 +583,15 @@ possible.  Each variable can have a type or infer it from the value: >
 Use this only when there is a list with values, declaring one variable per
 line is much easier to read and change later.
 
+The type of a variable that does not have a declared type is the type of the
+value it gets.  The variable after the ";" gets the remaining items: for a
+list this is a list of the same member type, for a tuple this is a tuple of
+the types of the remaining items: >
+	var [v1; v2] = [1, 2, 3]          # v2 has type list<number>
+	var [v3; v4] = (1, 'a', true)     # v4 has type tuple<string, bool>
+When the value has type "any" the item types are not known and every variable
+gets type "any".
+
 
 Constants ~
 						*vim9-const* *vim9-final*
diff --git a/src/testdir/test_tuple.vim b/src/testdir/test_tuple.vim
index ace890c04..00428a5b0 100644
--- a/src/testdir/test_tuple.vim
+++ b/src/testdir/test_tuple.vim
@@ -632,6 +632,47 @@ func Test_multi_assign_from_tuple()
   END
   call v9.CheckSourceSuccess(lines)
 
+  " The rest gets the type of the remaining items, not of the whole tuple
+  let lines =<< trim END
+    vim9script
+    def Fn()
+      var t: tuple<dict<any>, list<any>> = ({}, [])
+      var [a; b] = t
+      assert_equal('tuple<list<any>>', typename(b))
+      b = ([],)
+    enddef
+    Fn()
+  END
+  call v9.CheckSourceSuccess(lines)
+
+  " The rest of a tuple literal also gets the remaining item types
+  let lines =<< trim END
+    vim9script
+    def Fn()
+      var [a; b] = (true, false, true)
+      assert_equal((false, true), b)
+      assert_equal('tuple<bool, bool>', typename(b))
+    enddef
+    Fn()
+  END
+  call v9.CheckSourceSuccess(lines)
+
+  " The rest of a value with type "any" can be a tuple
+  let lines =<< trim END
+    vim9script
+    var d: dict<tuple<dict<any>, list<any>>>
+    def Fn(): tuple<dict<any>, list<any>>
+      return ({}, [])
+    enddef
+    def Test()
+      var [a; b] = d->get('a', (-1, {}, []))
+      b = Fn()
+      assert_equal('tuple<dict<any>, list<any>>', typename(b))
+    enddef
+    Test()
+  END
+  call v9.CheckSourceSuccess(lines)
+
   let lines =<< trim END
     VAR [v1, v2] = ('a', 'b', 'c')
   END
diff --git a/src/testdir/test_vim9_disassemble.vim b/src/testdir/test_vim9_disassemble.vim
index 759226a41..88e794ec7 100644
--- a/src/testdir/test_vim9_disassemble.vim
+++ b/src/testdir/test_vim9_disassemble.vim
@@ -656,6 +656,7 @@ def Test_disassemble_list_assign()
         '\d\+ CHECKTYPE string stack\[-1\] var 2\_s*' ..
         '\d\+ STORE $1\_s*' ..
         '\d\+ SLICE 2\_s*' ..
+        '\d\+ CHECKTYPE list<any> stack\[-1\] var 3\_s*' ..
         '\d\+ STORE $2\_s*' ..
         '\d\+ RETURN void',
         res)
diff --git a/src/version.c b/src/version.c
index bfe225943..9be27f5a3 100644
--- a/src/version.c
+++ b/src/version.c
@@ -763,6 +763,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    958,
 /**/
     957,
 /**/
diff --git a/src/vim9compile.c b/src/vim9compile.c
index 65d7580f9..2aeef4095 100644
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -3060,6 +3060,11 @@ compile_assign_list_check_rhs_type(cctx_T *cctx, cac_T *cac)
 		  TYPECHK_TUPLE_OK, -1, 0, cctx, FALSE, FALSE) == FAIL)
 	return FAIL;
 
+    // The check accepts both a list and a tuple. Keep "any", making it a list
+    // would reject a tuple later on.
+    if (stacktype->tt_type == VAR_ANY)
+	set_type_on_stack(cctx, &t_any, 0);
+
     if (stacktype->tt_type == VAR_TUPLE)
     {
 	if (stacktype->tt_argcount != 1)
diff --git a/src/vim9instr.c b/src/vim9instr.c
index 5e9ab38b7..069818b35 100644
--- a/src/vim9instr.c
+++ b/src/vim9instr.c
@@ -1089,6 +1089,43 @@ generate_GETITEM(cctx_T *cctx, int index, int with_op)
     return push_type_stack(cctx, item_type);
 }
 
+/*
+ * Set the type of the tuple that is left after dropping the first "count"
+ * items of the tuple with type "type".
+ */
+    static void
+set_tuple_slice_type_on_stack(type_T *type, int count, cctx_T *cctx)
+{
+    garray_T	tuple_types_ga;
+
+    // The item types are only known for a tuple with a fixed number of items.
+    if ((type->tt_flags & TTFLAG_VARARGS) || type->tt_argcount <= count)
+    {
+	set_type_on_stack(cctx, &t_tuple_any, 0);
+	return;
+    }
+
+    ga_init2(&tuple_types_ga, sizeof(type_T *), 10);
+
+    for (int i = count; i < type->tt_argcount; i++)
+    {
+	if (ga_grow(&tuple_types_ga, 1) == FAIL)
+	{
+	    ga_clear(&tuple_types_ga);
+	    set_type_on_stack(cctx, &t_tuple_any, 0);
+	    return;
+	}
+	((type_T **)tuple_types_ga.ga_data)[tuple_types_ga.ga_len] =
+							     type->tt_args[i];
+	tuple_types_ga.ga_len++;
+    }
+
+    set_type_on_stack(cctx,
+		get_tuple_type(&tuple_types_ga, cctx->ctx_type_list), 0);
+
+    ga_clear(&tuple_types_ga);
+}
+
 /*
  * Generate an ISN_SLICE instruction with "count".
  */
@@ -1096,11 +1133,19 @@ generate_GETITEM(cctx_T *cctx, int index, int with_op)
 generate_SLICE(cctx_T *cctx, int count)
 {
     isn_T	*isn;
+    type_T	*type;
 
     RETURN_OK_IF_SKIP(cctx);
     if ((isn = generate_instr(cctx, ISN_SLICE)) == NULL)
 	return FAIL;
     isn->isn_arg.number = count;
+
+    // Slicing a tuple leaves a tuple with the remaining item types, not the
+    // type of the whole tuple.
+    type = get_type_on_stack(cctx, 0);
+    if (type->tt_type == VAR_TUPLE)
+	set_tuple_slice_type_on_stack(type, count, cctx);
+
     return OK;
 }
 
diff --git a/src/vim9type.c b/src/vim9type.c
index dfa6686a9..49179c722 100644
--- a/src/vim9type.c
+++ b/src/vim9type.c
@@ -2373,7 +2373,8 @@ get_item_type(type_T *type)
 	    return type->tt_args[0];
     }
 
-    return type->tt_member;
+    // For "any" the item type is not known.
+    return type->tt_member == NULL ? &t_any : type->tt_member;
 }
 
 /*

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups "vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].
To view this discussion visit https://groups.google.com/d/msgid/vim_dev/E1wvdHJ-007240-Nn%40256bit.org.
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.