Patch 8.2.3435
Problem: Vim9: dict is not passed to dict function.
Solution: Keep the dict used until a function call.
Files: src/vim9compile.c, src/vim9execute.c, src/vim9.h,
src/testdir/test_vim9_func.vim,
src/testdir/test_vim9_disassemble.vim
*** ../vim-8.2.3434/src/vim9compile.c 2021-09-09 23:01:10.506519642 +0200
--- src/vim9compile.c 2021-09-13 15:48:42.500269599 +0200
***************
*** 2878,2886 ****
/*
* Compile getting a member from a list/dict/string/blob. Stack has the
* indexable value and the index or the two indexes of a slice.
*/
static int
! compile_member(int is_slice, cctx_T *cctx)
{
type_T **typep;
garray_T *stack = &cctx->ctx_type_stack;
--- 2878,2887 ----
/*
* Compile getting a member from a list/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.
*/
static int
! compile_member(int is_slice, int *keeping_dict, cctx_T *cctx)
{
type_T **typep;
garray_T *stack = &cctx->ctx_type_stack;
***************
*** 2935,2940 ****
--- 2936,2943 ----
return FAIL;
if (generate_instr_drop(cctx, ISN_MEMBER, 1) == FAIL)
return FAIL;
+ if (keeping_dict != NULL)
+ *keeping_dict = TRUE;
}
else if (vartype == VAR_STRING)
{
***************
*** 4314,4319 ****
--- 4317,4323 ----
ppconst_T *ppconst)
{
char_u *name_start = *end_leader;
+ int keeping_dict = FALSE;
for (;;)
{
***************
*** 4360,4365 ****
--- 4364,4375 ----
return FAIL;
if (generate_PCALL(cctx, argcount, name_start, type, TRUE) == FAIL)
return FAIL;
+ if (keeping_dict)
+ {
+ keeping_dict = FALSE;
+ if (generate_instr(cctx, ISN_CLEARDICT) == NULL)
+ return FAIL;
+ }
}
else if (*p == '-' && p[1] == '>')
{
***************
*** 4470,4475 ****
--- 4480,4491 ----
if (compile_call(arg, p - *arg, cctx, ppconst, 1) == FAIL)
return FAIL;
}
+ if (keeping_dict)
+ {
+ keeping_dict = FALSE;
+ if (generate_instr(cctx, ISN_CLEARDICT) == NULL)
+ return FAIL;
+ }
}
else if (**arg == '[')
{
***************
*** 4537,4543 ****
}
*arg = *arg + 1;
! if (compile_member(is_slice, cctx) == FAIL)
return FAIL;
}
else if (*p == '.' && p[1] != '.')
--- 4553,4565 ----
}
*arg = *arg + 1;
! if (keeping_dict)
! {
! keeping_dict = FALSE;
! if (generate_instr(cctx, ISN_CLEARDICT) == NULL)
! return FAIL;
! }
! if (compile_member(is_slice, &keeping_dict, cctx) == FAIL)
return FAIL;
}
else if (*p == '.' && p[1] != '.')
***************
*** 4562,4579 ****
semsg(_(e_syntax_error_at_str), *arg);
return FAIL;
}
if (generate_STRINGMEMBER(cctx, *arg, p - *arg) == FAIL)
return FAIL;
*arg = p;
}
else
break;
}
- // TODO - see handle_subscript():
// Turn "dict.Func" into a partial for "Func" bound to "dict".
! // Don't do this when "Func" is already a partial that was bound
! // explicitly (pt_auto is FALSE).
return OK;
}
--- 4584,4604 ----
semsg(_(e_syntax_error_at_str), *arg);
return FAIL;
}
+ if (keeping_dict && generate_instr(cctx, ISN_CLEARDICT) == NULL)
+ return FAIL;
if (generate_STRINGMEMBER(cctx, *arg, p - *arg) == FAIL)
return FAIL;
+ keeping_dict = TRUE;
*arg = p;
}
else
break;
}
// Turn "dict.Func" into a partial for "Func" bound to "dict".
! // This needs to be done at runtime to be able to check the type.
! if (keeping_dict && generate_instr(cctx, ISN_USEDICT) == NULL)
! return FAIL;
return OK;
}
***************
*** 6661,6667 ****
}
// Get the member.
! if (compile_member(FALSE, cctx) == FAIL)
return FAIL;
}
return OK;
--- 6686,6692 ----
}
// Get the member.
! if (compile_member(FALSE, NULL, cctx) == FAIL)
return FAIL;
}
return OK;
***************
*** 10406,10411 ****
--- 10431,10437 ----
case ISN_CEXPR_AUCMD:
case ISN_CHECKLEN:
case ISN_CHECKNR:
+ case ISN_CLEARDICT:
case ISN_CMDMOD_REV:
case ISN_COMPAREANY:
case ISN_COMPAREBLOB:
***************
*** 10482,10487 ****
--- 10508,10514 ----
case ISN_UNLETINDEX:
case ISN_UNLETRANGE:
case ISN_UNPACK:
+ case ISN_USEDICT:
// nothing allocated
break;
}
*** ../vim-8.2.3434/src/vim9execute.c 2021-09-09 23:01:10.506519642 +0200
--- src/vim9execute.c 2021-09-13 18:01:36.792768263 +0200
***************
*** 165,170 ****
--- 165,239 ----
}
}
+ static garray_T dict_stack = GA_EMPTY;
+
+ /*
+ * Put a value on the dict stack. This consumes "tv".
+ */
+ static int
+ dict_stack_save(typval_T *tv)
+ {
+ if (dict_stack.ga_growsize == 0)
+ ga_init2(&dict_stack, (int)sizeof(typval_T), 10);
+ if (ga_grow(&dict_stack, 1) == FAIL)
+ return FAIL;
+ ((typval_T *)dict_stack.ga_data)[dict_stack.ga_len] = *tv;
+ ++dict_stack.ga_len;
+ return OK;
+ }
+
+ /*
+ * Get the typval at top of the dict stack.
+ */
+ static typval_T *
+ dict_stack_get_tv(void)
+ {
+ if (dict_stack.ga_len == 0)
+ return NULL;
+ return ((typval_T *)dict_stack.ga_data) + dict_stack.ga_len - 1;
+ }
+
+ /*
+ * Get the dict at top of the dict stack.
+ */
+ static dict_T *
+ dict_stack_get_dict(void)
+ {
+ typval_T *tv;
+
+ if (dict_stack.ga_len == 0)
+ return NULL;
+ tv = ((typval_T *)dict_stack.ga_data) + dict_stack.ga_len - 1;
+ if (tv->v_type == VAR_DICT)
+ return tv->vval.v_dict;
+ return NULL;
+ }
+
+ /*
+ * Drop an item from the dict stack.
+ */
+ static void
+ dict_stack_drop(void)
+ {
+ if (dict_stack.ga_len == 0)
+ {
+ iemsg("Dict stack underflow");
+ return;
+ }
+ --dict_stack.ga_len;
+ clear_tv(((typval_T *)dict_stack.ga_data) + dict_stack.ga_len);
+ }
+
+ /*
+ * Drop items from the dict stack until the length is equal to "len".
+ */
+ static void
+ dict_stack_clear(int len)
+ {
+ while (dict_stack.ga_len > len)
+ dict_stack_drop();
+ }
+
/*
* Call compiled function "cdf_idx" from compiled code.
* This adds a stack frame and sets the instruction pointer to the start of the
***************
*** 765,771 ****
partial_T *pt,
int argcount,
ectx_T *ectx,
! isn_T *iptr)
{
typval_T argvars[MAX_FUNC_ARGS];
funcexe_T funcexe;
--- 834,841 ----
partial_T *pt,
int argcount,
ectx_T *ectx,
! isn_T *iptr,
! dict_T *selfdict)
{
typval_T argvars[MAX_FUNC_ARGS];
funcexe_T funcexe;
***************
*** 807,817 ****
return FAIL;
CLEAR_FIELD(funcexe);
funcexe.evaluate = TRUE;
// Call the user function. Result goes in last position on the stack.
// TODO: add selfdict if there is one
error = call_user_func_check(ufunc, argcount, argvars,
! STACK_TV_BOT(-1), &funcexe, NULL);
// Clear the arguments.
for (idx = 0; idx < argcount; ++idx)
--- 877,888 ----
return FAIL;
CLEAR_FIELD(funcexe);
funcexe.evaluate = TRUE;
+ funcexe.selfdict = selfdict != NULL ? selfdict : dict_stack_get_dict();
// Call the user function. Result goes in last position on the stack.
// TODO: add selfdict if there is one
error = call_user_func_check(ufunc, argcount, argvars,
! STACK_TV_BOT(-1), &funcexe, funcexe.selfdict);
// Clear the arguments.
for (idx = 0; idx < argcount; ++idx)
***************
*** 864,870 ****
char_u *name,
int argcount,
ectx_T *ectx,
! isn_T *iptr)
{
ufunc_T *ufunc;
--- 935,942 ----
char_u *name,
int argcount,
ectx_T *ectx,
! isn_T *iptr,
! dict_T *selfdict)
{
ufunc_T *ufunc;
***************
*** 916,922 ****
}
}
! return call_ufunc(ufunc, NULL, argcount, ectx, iptr);
}
return FAIL;
--- 988,994 ----
}
}
! return call_ufunc(ufunc, NULL, argcount, ectx, iptr, selfdict);
}
return FAIL;
***************
*** 932,937 ****
--- 1004,1010 ----
char_u *name = NULL;
int called_emsg_before = called_emsg;
int res = FAIL;
+ dict_T *selfdict = NULL;
if (tv->v_type == VAR_PARTIAL)
{
***************
*** 953,961 ****
for (i = 0; i < pt->pt_argc; ++i)
copy_tv(&pt->pt_argv[i], STACK_TV_BOT(-argcount + i));
}
if (pt->pt_func != NULL)
! return call_ufunc(pt->pt_func, pt, argcount, ectx, NULL);
name = pt->pt_name;
}
--- 1026,1035 ----
for (i = 0; i < pt->pt_argc; ++i)
copy_tv(&pt->pt_argv[i], STACK_TV_BOT(-argcount + i));
}
+ selfdict = pt->pt_dict;
if (pt->pt_func != NULL)
! return call_ufunc(pt->pt_func, pt, argcount, ectx, NULL, selfdict);
name = pt->pt_name;
}
***************
*** 973,979 ****
if (error != FCERR_NONE)
res = FAIL;
else
! res = call_by_name(fname, argcount, ectx, NULL);
vim_free(tofree);
}
--- 1047,1053 ----
if (error != FCERR_NONE)
res = FAIL;
else
! res = call_by_name(fname, argcount, ectx, NULL, selfdict);
vim_free(tofree);
}
***************
*** 1325,1331 ****
int called_emsg_before = called_emsg;
int res;
! res = call_by_name(name, argcount, ectx, iptr);
if (res == FAIL && called_emsg == called_emsg_before)
{
dictitem_T *v;
--- 1399,1405 ----
int called_emsg_before = called_emsg;
int res;
! res = call_by_name(name, argcount, ectx, iptr, NULL);
if (res == FAIL && called_emsg == called_emsg_before)
{
dictitem_T *v;
***************
*** 1570,1575 ****
--- 1644,1650 ----
{
int ret = FAIL;
int save_trylevel_at_start = ectx->ec_trylevel_at_start;
+ int dict_stack_len_at_start = dict_stack.ga_len;
// Start execution at the first instruction.
ectx->ec_iidx = 0;
***************
*** 4022,4028 ****
dict_T *dict;
char_u *key;
dictitem_T *di;
- typval_T temp_tv;
// dict member: dict is at stack-2, key at stack-1
tv = STACK_TV_BOT(-2);
--- 4097,4102 ----
***************
*** 4041,4063 ****
semsg(_(e_dictkey), key);
// If :silent! is used we will continue, make sure the
! // stack contents makes sense.
clear_tv(tv);
--ectx->ec_stack.ga_len;
tv = STACK_TV_BOT(-1);
! clear_tv(tv);
tv->v_type = VAR_NUMBER;
tv->vval.v_number = 0;
goto on_fatal_error;
}
clear_tv(tv);
--ectx->ec_stack.ga_len;
! // Clear the dict only after getting the item, to avoid
! // that it makes the item invalid.
tv = STACK_TV_BOT(-1);
! temp_tv = *tv;
copy_tv(&di->di_tv, tv);
- clear_tv(&temp_tv);
}
break;
--- 4115,4138 ----
semsg(_(e_dictkey), key);
// If :silent! is used we will continue, make sure the
! // stack contents makes sense and the dict stack is
! // updated.
clear_tv(tv);
--ectx->ec_stack.ga_len;
tv = STACK_TV_BOT(-1);
! (void) dict_stack_save(tv);
tv->v_type = VAR_NUMBER;
tv->vval.v_number = 0;
goto on_fatal_error;
}
clear_tv(tv);
--ectx->ec_stack.ga_len;
! // Put the dict used on the dict stack, it might be used by
! // a dict function later.
tv = STACK_TV_BOT(-1);
! if (dict_stack_save(tv) == FAIL)
! goto on_fatal_error;
copy_tv(&di->di_tv, tv);
}
break;
***************
*** 4066,4072 ****
{
dict_T *dict;
dictitem_T *di;
- typval_T temp_tv;
tv = STACK_TV_BOT(-1);
if (tv->v_type != VAR_DICT || tv->vval.v_dict == NULL)
--- 4141,4146 ----
***************
*** 4084,4094 ****
semsg(_(e_dictkey), iptr->isn_arg.string);
goto on_error;
}
! // Clear the dict after getting the item, to avoid that it
! // make the item invalid.
! temp_tv = *tv;
copy_tv(&di->di_tv, tv);
! clear_tv(&temp_tv);
}
break;
--- 4158,4194 ----
semsg(_(e_dictkey), iptr->isn_arg.string);
goto on_error;
}
! // Put the dict used on the dict stack, it might be used by
! // a dict function later.
! if (dict_stack_save(tv) == FAIL)
! goto on_fatal_error;
!
copy_tv(&di->di_tv, tv);
! }
! break;
!
! case ISN_CLEARDICT:
! dict_stack_drop();
! break;
!
! case ISN_USEDICT:
! {
! typval_T *dict_tv = dict_stack_get_tv();
!
! // Turn "dict.Func" into a partial for "Func" bound to
! // "dict". Don't do this when "Func" is already a partial
! // that was bound explicitly (pt_auto is FALSE).
! tv = STACK_TV_BOT(-1);
! if (dict_tv != NULL
! && dict_tv->v_type == VAR_DICT
! && dict_tv->vval.v_dict != NULL
! && (tv->v_type == VAR_FUNC
! || (tv->v_type == VAR_PARTIAL
! && (tv->vval.v_partial->pt_auto
! || tv->vval.v_partial->pt_dict == NULL))))
! dict_tv->vval.v_dict =
! make_partial(dict_tv->vval.v_dict, tv);
! dict_stack_drop();
}
break;
***************
*** 4478,4483 ****
--- 4578,4584 ----
done:
ret = OK;
theend:
+ dict_stack_clear(dict_stack_len_at_start);
ectx->ec_trylevel_at_start = save_trylevel_at_start;
return ret;
}
***************
*** 5568,5573 ****
--- 5669,5677 ----
case ISN_MEMBER: smsg("%s%4d MEMBER", pfx, current); break;
case ISN_STRINGMEMBER: smsg("%s%4d MEMBER %s", pfx, current,
iptr->isn_arg.string); break;
+ case ISN_CLEARDICT: smsg("%s%4d CLEARDICT", pfx, current); break;
+ case ISN_USEDICT: smsg("%s%4d USEDICT", pfx, current); break;
+
case ISN_NEGATENR: smsg("%s%4d NEGATENR", pfx, current); break;
case ISN_CHECKNR: smsg("%s%4d CHECKNR", pfx, current); break;
*** ../vim-8.2.3434/src/vim9.h 2021-09-02 18:49:02.748932320 +0200
--- src/vim9.h 2021-09-13 15:44:13.008598382 +0200
***************
*** 162,167 ****
--- 162,170 ----
ISN_CHECKLEN, // check list length is isn_arg.checklen.cl_min_len
ISN_SETTYPE, // set dict type to isn_arg.type.ct_type
+ ISN_CLEARDICT, // clear dict saved by ISN_MEMBER/ISN_STRINGMEMBER
+ ISN_USEDICT, // use or clear dict saved by ISN_MEMBER/ISN_STRINGMEMBER
+
ISN_PUT, // ":put", uses isn_arg.put
ISN_CMDMOD, // set cmdmod
*** ../vim-8.2.3434/src/testdir/test_vim9_func.vim 2021-09-09 22:30:48.128865561 +0200
--- src/testdir/test_vim9_func.vim 2021-09-13 17:46:39.853631142 +0200
***************
*** 2557,2562 ****
--- 2557,2593 ----
endfor
enddef
+ def Test_call_legacy_with_dict()
+ var lines =<< trim END
+ vim9script
+ func Legacy() dict
+ let g:result = self.value
+ endfunc
+ def TestDirect()
+ var d = {value: 'yes', func: Legacy}
+ d.func()
+ enddef
+ TestDirect()
+ assert_equal('yes', g:result)
+ unlet g:result
+
+ def TestIndirect()
+ var d = {value: 'foo', func: Legacy}
+ var Fi = d.func
+ Fi()
+ enddef
+ TestIndirect()
+ assert_equal('foo', g:result)
+ unlet g:result
+
+ var d = {value: 'bar', func: Legacy}
+ d.func()
+ assert_equal('bar', g:result)
+ unlet g:result
+ END
+ CheckScriptSuccess(lines)
+ enddef
+
def DoFilterThis(a: string): list<string>
# closure nested inside another closure using argument
var Filter = (l) => filter(l, (_, v) => stridx(v, a) == 0)
*** ../vim-8.2.3434/src/testdir/test_vim9_disassemble.vim 2021-08-22 13:34:23.423960112 +0200
--- src/testdir/test_vim9_disassemble.vim 2021-09-13 17:40:46.541970193 +0200
***************
*** 412,418 ****
'\d PUSHNR 0\_s*' ..
'\d LOAD $0\_s*' ..
'\d MEMBER dd\_s*' ..
! '\d STOREINDEX any\_s*' ..
'\d\+ RETURN void',
res)
enddef
--- 412,419 ----
'\d PUSHNR 0\_s*' ..
'\d LOAD $0\_s*' ..
'\d MEMBER dd\_s*' ..
! '\d\+ USEDICT\_s*' ..
! '\d\+ STOREINDEX any\_s*' ..
'\d\+ RETURN void',
res)
enddef
***************
*** 1625,1635 ****
--- 1626,1638 ----
'var res = d.item\_s*' ..
'\d\+ LOAD $0\_s*' ..
'\d\+ MEMBER item\_s*' ..
+ '\d\+ USEDICT\_s*' ..
'\d\+ STORE $1\_s*' ..
'res = d\["item"\]\_s*' ..
'\d\+ LOAD $0\_s*' ..
'\d\+ PUSHS "item"\_s*' ..
'\d\+ MEMBER\_s*' ..
+ '\d\+ USEDICT\_s*' ..
'\d\+ STORE $1\_s*',
instr)
assert_equal(1, DictMember())
***************
*** 2302,2307 ****
--- 2305,2339 ----
res)
enddef
+ func Legacy() dict
+ echo 'legacy'
+ endfunc
+
+ def s:UseMember()
+ var d = {func: Legacy}
+ var v = d.func()
+ enddef
+
+ def Test_disassemble_dict_stack()
+ var res = execute('disass s:UseMember')
+ assert_match('<SNR>\d*_UseMember\_s*' ..
+ 'var d = {func: Legacy}\_s*' ..
+ '\d PUSHS "func"\_s*' ..
+ '\d PUSHFUNC "Legacy"\_s*' ..
+ '\d NEWDICT size 1\_s*' ..
+ '\d STORE $0\_s*' ..
+
+ 'var v = d.func()\_s*' ..
+ '\d LOAD $0\_s*' ..
+ '\d MEMBER func\_s*' ..
+ '\d PCALL top (argc 0)\_s*' ..
+ '\d PCALL end\_s*' ..
+ '\d CLEARDICT\_s*' ..
+ '\d\+ STORE $1\_s*' ..
+ '\d\+ RETURN void*',
+ res)
+ enddef
+
def s:EchoMessages()
echohl ErrorMsg | echom v:exception | echohl NONE
enddef
***************
*** 2363,2366 ****
--- 2395,2399 ----
enddef
+
" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
*** ../vim-8.2.3434/src/version.c 2021-09-12 21:00:10.625837682 +0200
--- src/version.c 2021-09-13 15:41:27.616798274 +0200
***************
*** 757,758 ****
--- 757,760 ----
{ /* Add new patch number below this line */
+ /**/
+ 3435,
/**/
--
How To Keep A Healthy Level Of Insanity:
4. Put your garbage can on your desk and label it "in".
/// Bram Moolenaar -- Br...@Moolenaar.net --
http://www.Moolenaar.net \\\
/// \\\
\\\ sponsor Vim, vote for features --
http://www.Vim.org/sponsor/ ///
\\\ help me help AIDS victims --
http://ICCF-Holland.org ///