Skip to content

Commit 792f786

Browse files
committed
patch 8.2.2034: Vim9: list unpack in for statement not compiled yet
Problem: Vim9: list unpack in for statement not compiled yet. Solution: Compile list unpack. (closes #7345)
1 parent 6abdcf8 commit 792f786

8 files changed

Lines changed: 269 additions & 35 deletions

File tree

src/errors.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ EXTERN char e_invalid_command[]
2121
#ifdef FEAT_EVAL
2222
EXTERN char e_invalid_command_str[]
2323
INIT(= N_("E476: Invalid command: %s"));
24+
EXTERN char e_list_value_has_more_items_than_targets[]
25+
INIT(= N_("E710: List value has more items than targets"));
26+
EXTERN char e_list_value_does_not_have_enough_items[]
27+
INIT(= N_("E711: List value does not have enough items"));
2428
EXTERN char e_cannot_slice_dictionary[]
2529
INIT(= N_("E719: Cannot slice a Dictionary"));
2630
EXTERN char e_assert_fails_second_arg[]
@@ -305,3 +309,5 @@ EXTERN char e_using_bool_as_number[]
305309
INIT(= N_("E1138: Using a Bool as a Number"));
306310
EXTERN char e_missing_matching_bracket_after_dict_key[]
307311
INIT(= N_("E1139: Missing matching bracket after dict key"));
312+
EXTERN char e_for_argument_must_be_sequence_of_lists[]
313+
INIT(= N_("E1140: For argument must be a sequence of lists"));

src/eval.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1397,11 +1397,11 @@ set_var_lval(
13971397
++lp->ll_n1;
13981398
}
13991399
if (ri != NULL)
1400-
emsg(_("E710: List value has more items than target"));
1400+
emsg(_(e_list_value_has_more_items_than_targets));
14011401
else if (lp->ll_empty2
14021402
? (lp->ll_li != NULL && lp->ll_li->li_next != NULL)
14031403
: lp->ll_n1 != lp->ll_n2)
1404-
emsg(_("E711: List value has not enough items"));
1404+
emsg(_(e_list_value_does_not_have_enough_items));
14051405
}
14061406
else
14071407
{

src/testdir/test_vim9_disassemble.vim

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,6 +1026,40 @@ def Test_disassemble_for_loop_eval()
10261026
instr)
10271027
enddef
10281028

1029+
def ForLoopUnpack()
1030+
for [x1, x2] in [[1, 2], [3, 4]]
1031+
echo x1 x2
1032+
endfor
1033+
enddef
1034+
1035+
def Test_disassemble_for_loop_unpack()
1036+
var instr = execute('disassemble ForLoopUnpack')
1037+
assert_match('ForLoopUnpack\_s*' ..
1038+
'for \[x1, x2\] in \[\[1, 2\], \[3, 4\]\]\_s*' ..
1039+
'\d\+ STORE -1 in $0\_s*' ..
1040+
'\d\+ PUSHNR 1\_s*' ..
1041+
'\d\+ PUSHNR 2\_s*' ..
1042+
'\d\+ NEWLIST size 2\_s*' ..
1043+
'\d\+ PUSHNR 3\_s*' ..
1044+
'\d\+ PUSHNR 4\_s*' ..
1045+
'\d\+ NEWLIST size 2\_s*' ..
1046+
'\d\+ NEWLIST size 2\_s*' ..
1047+
'\d\+ FOR $0 -> 16\_s*' ..
1048+
'\d\+ UNPACK 2\_s*' ..
1049+
'\d\+ STORE $1\_s*' ..
1050+
'\d\+ STORE $2\_s*' ..
1051+
'echo x1 x2\_s*' ..
1052+
'\d\+ LOAD $1\_s*' ..
1053+
'\d\+ LOAD $2\_s*' ..
1054+
'\d\+ ECHO 2\_s*' ..
1055+
'endfor\_s*' ..
1056+
'\d\+ JUMP -> 8\_s*' ..
1057+
'\d\+ DROP\_s*' ..
1058+
'\d\+ PUSHNR 0\_s*' ..
1059+
'\d\+ RETURN',
1060+
instr)
1061+
enddef
1062+
10291063
let g:number = 42
10301064

10311065
def TypeCast()

src/testdir/test_vim9_script.vim

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1862,6 +1862,44 @@ def Test_for_loop_fails()
18621862
CheckDefFailure(['for i in range(3)', 'echo 3'], 'E170:')
18631863
enddef
18641864

1865+
def Test_for_loop_unpack()
1866+
var result = []
1867+
for [v1, v2] in [[1, 2], [3, 4]]
1868+
result->add(v1)
1869+
result->add(v2)
1870+
endfor
1871+
assert_equal([1, 2, 3, 4], result)
1872+
1873+
result = []
1874+
for [v1, v2; v3] in [[1, 2], [3, 4, 5, 6]]
1875+
result->add(v1)
1876+
result->add(v2)
1877+
result->add(v3)
1878+
endfor
1879+
assert_equal([1, 2, [], 3, 4, [5, 6]], result)
1880+
1881+
var lines =<< trim END
1882+
for [v1, v2] in [[1, 2, 3], [3, 4]]
1883+
echo v1 v2
1884+
endfor
1885+
END
1886+
CheckDefExecFailure(lines, 'E710:', 1)
1887+
1888+
lines =<< trim END
1889+
for [v1, v2] in [[1], [3, 4]]
1890+
echo v1 v2
1891+
endfor
1892+
END
1893+
CheckDefExecFailure(lines, 'E711:', 1)
1894+
1895+
lines =<< trim END
1896+
for [v1, v1] in [[1, 2], [3, 4]]
1897+
echo v1
1898+
endfor
1899+
END
1900+
CheckDefExecFailure(lines, 'E1017:', 1)
1901+
enddef
1902+
18651903
def Test_while_loop()
18661904
var result = ''
18671905
var cnt = 0

src/version.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -750,6 +750,8 @@ static char *(features[]) =
750750

751751
static int included_patches[] =
752752
{ /* Add new patch number below this line */
753+
/**/
754+
2034,
753755
/**/
754756
2033,
755757
/**/

src/vim9.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ typedef enum {
146146
ISN_CMDMOD, // set cmdmod
147147
ISN_CMDMOD_REV, // undo ISN_CMDMOD
148148

149+
ISN_UNPACK, // unpack list into items, uses isn_arg.unpack
149150
ISN_SHUFFLE, // move item on stack up or down
150151
ISN_DROP // pop stack and discard value
151152
} isntype_T;
@@ -284,6 +285,12 @@ typedef struct {
284285
cmdmod_T *cf_cmdmod; // allocated
285286
} cmod_T;
286287

288+
// arguments to ISN_UNPACK
289+
typedef struct {
290+
int unp_count; // number of items to produce
291+
int unp_semicolon; // last item gets list of remainder
292+
} unpack_T;
293+
287294
/*
288295
* Instruction
289296
*/
@@ -321,6 +328,7 @@ struct isn_S {
321328
shuffle_T shuffle;
322329
put_T put;
323330
cmod_T cmdmod;
331+
unpack_T unpack;
324332
} isn_arg;
325333
};
326334

src/vim9compile.c

Lines changed: 99 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1888,6 +1888,19 @@ generate_EXECCONCAT(cctx_T *cctx, int count)
18881888
return OK;
18891889
}
18901890

1891+
static int
1892+
generate_UNPACK(cctx_T *cctx, int var_count, int semicolon)
1893+
{
1894+
isn_T *isn;
1895+
1896+
RETURN_OK_IF_SKIP(cctx);
1897+
if ((isn = generate_instr(cctx, ISN_UNPACK)) == NULL)
1898+
return FAIL;
1899+
isn->isn_arg.unpack.unp_count = var_count;
1900+
isn->isn_arg.unpack.unp_semicolon = semicolon;
1901+
return OK;
1902+
}
1903+
18911904
/*
18921905
* Generate an instruction for any command modifiers.
18931906
*/
@@ -6323,12 +6336,12 @@ compile_endif(char_u *arg, cctx_T *cctx)
63236336
}
63246337

63256338
/*
6326-
* compile "for var in expr"
6339+
* Compile "for var in expr":
63276340
*
63286341
* Produces instructions:
63296342
* PUSHNR -1
63306343
* STORE loop-idx Set index to -1
6331-
* EVAL expr Push result of "expr"
6344+
* EVAL expr result of "expr" on top of stack
63326345
* top: FOR loop-idx, end Increment index, use list on bottom of stack
63336346
* - if beyond end, jump to "end"
63346347
* - otherwise get item from list and push it
@@ -6337,30 +6350,32 @@ compile_endif(char_u *arg, cctx_T *cctx)
63376350
* JUMP top Jump back to repeat
63386351
* end: DROP Drop the result of "expr"
63396352
*
6353+
* Compile "for [var1, var2] in expr" - as above, but instead of "STORE var":
6354+
* UNPACK 2 Split item in 2
6355+
* STORE var1 Store item in "var1"
6356+
* STORE var2 Store item in "var2"
63406357
*/
63416358
static char_u *
6342-
compile_for(char_u *arg, cctx_T *cctx)
6359+
compile_for(char_u *arg_start, cctx_T *cctx)
63436360
{
6361+
char_u *arg;
6362+
char_u *arg_end;
63446363
char_u *p;
6364+
int var_count = 0;
6365+
int semicolon = FALSE;
63456366
size_t varlen;
63466367
garray_T *instr = &cctx->ctx_instr;
63476368
garray_T *stack = &cctx->ctx_type_stack;
63486369
scope_T *scope;
63496370
lvar_T *loop_lvar; // loop iteration variable
63506371
lvar_T *var_lvar; // variable for "var"
63516372
type_T *vartype;
6373+
type_T *item_type = &t_any;
6374+
int idx;
63526375

6353-
// TODO: list of variables: "for [key, value] in dict"
6354-
// parse "var"
6355-
for (p = arg; eval_isnamec1(*p); ++p)
6356-
;
6357-
varlen = p - arg;
6358-
var_lvar = lookup_local(arg, varlen, cctx);
6359-
if (var_lvar != NULL)
6360-
{
6361-
semsg(_(e_variable_already_declared), arg);
6362-
return NULL;
6363-
}
6376+
p = skip_var_list(arg_start, TRUE, &var_count, &semicolon, FALSE);
6377+
if (var_count == 0)
6378+
var_count = 1;
63646379

63656380
// consume "in"
63666381
p = skipwhite(p);
@@ -6371,29 +6386,19 @@ compile_for(char_u *arg, cctx_T *cctx)
63716386
}
63726387
p = skipwhite(p + 2);
63736388

6374-
63756389
scope = new_scope(cctx, FOR_SCOPE);
63766390
if (scope == NULL)
63776391
return NULL;
63786392

6379-
// Reserve a variable to store the loop iteration counter.
6393+
// Reserve a variable to store the loop iteration counter and initialize it
6394+
// to -1.
63806395
loop_lvar = reserve_local(cctx, (char_u *)"", 0, FALSE, &t_number);
63816396
if (loop_lvar == NULL)
63826397
{
63836398
// out of memory
63846399
drop_scope(cctx);
63856400
return NULL;
63866401
}
6387-
6388-
// Reserve a variable to store "var"
6389-
var_lvar = reserve_local(cctx, arg, varlen, FALSE, &t_any);
6390-
if (var_lvar == NULL)
6391-
{
6392-
// out of memory or used as an argument
6393-
drop_scope(cctx);
6394-
return NULL;
6395-
}
6396-
63976402
generate_STORENR(cctx, loop_lvar->lv_idx, -1);
63986403

63996404
// compile "expr", it remains on the stack until "endfor"
@@ -6403,6 +6408,7 @@ compile_for(char_u *arg, cctx_T *cctx)
64036408
drop_scope(cctx);
64046409
return NULL;
64056410
}
6411+
arg_end = arg;
64066412

64076413
// Now that we know the type of "var", check that it is a list, now or at
64086414
// runtime.
@@ -6412,16 +6418,78 @@ compile_for(char_u *arg, cctx_T *cctx)
64126418
drop_scope(cctx);
64136419
return NULL;
64146420
}
6421+
64156422
if (vartype->tt_type == VAR_LIST && vartype->tt_member->tt_type != VAR_ANY)
6416-
var_lvar->lv_type = vartype->tt_member;
6423+
{
6424+
if (var_count == 1)
6425+
item_type = vartype->tt_member;
6426+
else if (vartype->tt_member->tt_type == VAR_LIST
6427+
&& vartype->tt_member->tt_member->tt_type != VAR_ANY)
6428+
item_type = vartype->tt_member->tt_member;
6429+
}
64176430

64186431
// "for_end" is set when ":endfor" is found
64196432
scope->se_u.se_for.fs_top_label = instr->ga_len;
6420-
64216433
generate_FOR(cctx, loop_lvar->lv_idx);
6422-
generate_STORE(cctx, ISN_STORE, var_lvar->lv_idx, NULL);
64236434

6424-
return arg;
6435+
arg = arg_start;
6436+
if (var_count > 1)
6437+
{
6438+
generate_UNPACK(cctx, var_count, semicolon);
6439+
arg = skipwhite(arg + 1); // skip white after '['
6440+
6441+
// the list item is replaced by a number of items
6442+
if (ga_grow(stack, var_count - 1) == FAIL)
6443+
{
6444+
drop_scope(cctx);
6445+
return NULL;
6446+
}
6447+
--stack->ga_len;
6448+
for (idx = 0; idx < var_count; ++idx)
6449+
{
6450+
((type_T **)stack->ga_data)[stack->ga_len] =
6451+
(semicolon && idx == 0) ? vartype : item_type;
6452+
++stack->ga_len;
6453+
}
6454+
}
6455+
6456+
for (idx = 0; idx < var_count; ++idx)
6457+
{
6458+
// TODO: use skip_var_one, also assign to @r, $VAR, etc.
6459+
p = arg;
6460+
while (eval_isnamec(*p))
6461+
++p;
6462+
varlen = p - arg;
6463+
var_lvar = lookup_local(arg, varlen, cctx);
6464+
if (var_lvar != NULL)
6465+
{
6466+
semsg(_(e_variable_already_declared), arg);
6467+
drop_scope(cctx);
6468+
return NULL;
6469+
}
6470+
6471+
// Reserve a variable to store "var".
6472+
// TODO: check for type
6473+
var_lvar = reserve_local(cctx, arg, varlen, FALSE, &t_any);
6474+
if (var_lvar == NULL)
6475+
{
6476+
// out of memory or used as an argument
6477+
drop_scope(cctx);
6478+
return NULL;
6479+
}
6480+
6481+
if (semicolon && idx == var_count - 1)
6482+
var_lvar->lv_type = vartype;
6483+
else
6484+
var_lvar->lv_type = item_type;
6485+
generate_STORE(cctx, ISN_STORE, var_lvar->lv_idx, NULL);
6486+
6487+
if (*p == ',' || *p == ';')
6488+
++p;
6489+
arg = skipwhite(p);
6490+
}
6491+
6492+
return arg_end;
64256493
}
64266494

64276495
/*
@@ -7957,6 +8025,7 @@ delete_instr(isn_T *isn)
79578025
case ISN_STRSLICE:
79588026
case ISN_THROW:
79598027
case ISN_TRY:
8028+
case ISN_UNPACK:
79608029
// nothing allocated
79618030
break;
79628031
}

0 commit comments

Comments
 (0)