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) <noreply@anthropic.com>
Signed-off-by: Hirohito Higashi <h.east.727@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
Hirohito HigashiandClaude Opus 5 authored and Christian Brabandt committed 2026-08-16 15:46:32 +00:00
1 parent a0f057dacf
commit d4703e569d
7 files changed
+106 -2

No files matched your search

+10 -1
View File
@@ -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*
+41
View File
@@ -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
+1
View File
@@ -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)
+2
View File
@@ -763,6 +763,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
958,
/**/
957,
/**/
+5
View File
@@ -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)
+45
View File
@@ -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;
}
+2 -1
View File
@@ -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;
}
/*