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:
1 parent
a0f057dacf
commit
d4703e569d
7 files changed
+106
-2
No files matched your search
+10
-1
@@ -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*
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -763,6 +763,8 @@ static char *(features[]) =
|
||||
|
||||
static int included_patches[] =
|
||||
{ /* Add new patch number below this line */
|
||||
/**/
|
||||
958,
|
||||
/**/
|
||||
957,
|
||||
/**/
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Reference in new issue
Block a user