1
0
forked from aniani/vim

patch 8.2.3435: Vim9: dict is not passed to dict function

Problem:    Vim9: dict is not passed to dict function.
Solution:   Keep the dict used until a function call.
This commit is contained in:
Bram Moolenaar
2021-09-13 18:25:54 +02:00
parent 28e591dd50
commit b1b6f4de2b
6 changed files with 226 additions and 26 deletions

View File

@@ -412,7 +412,8 @@ def Test_disassemble_store_index()
'\d PUSHNR 0\_s*' .. '\d PUSHNR 0\_s*' ..
'\d LOAD $0\_s*' .. '\d LOAD $0\_s*' ..
'\d MEMBER dd\_s*' .. '\d MEMBER dd\_s*' ..
'\d STOREINDEX any\_s*' .. '\d\+ USEDICT\_s*' ..
'\d\+ STOREINDEX any\_s*' ..
'\d\+ RETURN void', '\d\+ RETURN void',
res) res)
enddef enddef
@@ -1625,11 +1626,13 @@ def Test_disassemble_dict_member()
'var res = d.item\_s*' .. 'var res = d.item\_s*' ..
'\d\+ LOAD $0\_s*' .. '\d\+ LOAD $0\_s*' ..
'\d\+ MEMBER item\_s*' .. '\d\+ MEMBER item\_s*' ..
'\d\+ USEDICT\_s*' ..
'\d\+ STORE $1\_s*' .. '\d\+ STORE $1\_s*' ..
'res = d\["item"\]\_s*' .. 'res = d\["item"\]\_s*' ..
'\d\+ LOAD $0\_s*' .. '\d\+ LOAD $0\_s*' ..
'\d\+ PUSHS "item"\_s*' .. '\d\+ PUSHS "item"\_s*' ..
'\d\+ MEMBER\_s*' .. '\d\+ MEMBER\_s*' ..
'\d\+ USEDICT\_s*' ..
'\d\+ STORE $1\_s*', '\d\+ STORE $1\_s*',
instr) instr)
assert_equal(1, DictMember()) assert_equal(1, DictMember())
@@ -2302,6 +2305,35 @@ def Test_debug_elseif()
res) res)
enddef 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() def s:EchoMessages()
echohl ErrorMsg | echom v:exception | echohl NONE echohl ErrorMsg | echom v:exception | echohl NONE
enddef enddef
@@ -2363,4 +2395,5 @@ def Test_disassemble_after_reload()
enddef enddef
" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker

View File

@@ -2557,6 +2557,37 @@ def Test_legacy_errors()
endfor endfor
enddef 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> def DoFilterThis(a: string): list<string>
# closure nested inside another closure using argument # closure nested inside another closure using argument
var Filter = (l) => filter(l, (_, v) => stridx(v, a) == 0) var Filter = (l) => filter(l, (_, v) => stridx(v, a) == 0)

View File

@@ -755,6 +755,8 @@ static char *(features[]) =
static int included_patches[] = static int included_patches[] =
{ /* Add new patch number below this line */ { /* Add new patch number below this line */
/**/
3435,
/**/ /**/
3434, 3434,
/**/ /**/

View File

@@ -162,6 +162,9 @@ typedef enum {
ISN_CHECKLEN, // check list length is isn_arg.checklen.cl_min_len ISN_CHECKLEN, // check list length is isn_arg.checklen.cl_min_len
ISN_SETTYPE, // set dict type to isn_arg.type.ct_type 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_PUT, // ":put", uses isn_arg.put
ISN_CMDMOD, // set cmdmod ISN_CMDMOD, // set cmdmod

View File

@@ -2878,9 +2878,10 @@ clear_ppconst(ppconst_T *ppconst)
/* /*
* Compile getting a member from a list/dict/string/blob. Stack has the * 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. * 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 static int
compile_member(int is_slice, cctx_T *cctx) compile_member(int is_slice, int *keeping_dict, cctx_T *cctx)
{ {
type_T **typep; type_T **typep;
garray_T *stack = &cctx->ctx_type_stack; garray_T *stack = &cctx->ctx_type_stack;
@@ -2935,6 +2936,8 @@ compile_member(int is_slice, cctx_T *cctx)
return FAIL; return FAIL;
if (generate_instr_drop(cctx, ISN_MEMBER, 1) == FAIL) if (generate_instr_drop(cctx, ISN_MEMBER, 1) == FAIL)
return FAIL; return FAIL;
if (keeping_dict != NULL)
*keeping_dict = TRUE;
} }
else if (vartype == VAR_STRING) else if (vartype == VAR_STRING)
{ {
@@ -4314,6 +4317,7 @@ compile_subscript(
ppconst_T *ppconst) ppconst_T *ppconst)
{ {
char_u *name_start = *end_leader; char_u *name_start = *end_leader;
int keeping_dict = FALSE;
for (;;) for (;;)
{ {
@@ -4360,6 +4364,12 @@ compile_subscript(
return FAIL; return FAIL;
if (generate_PCALL(cctx, argcount, name_start, type, TRUE) == FAIL) if (generate_PCALL(cctx, argcount, name_start, type, TRUE) == FAIL)
return FAIL; return FAIL;
if (keeping_dict)
{
keeping_dict = FALSE;
if (generate_instr(cctx, ISN_CLEARDICT) == NULL)
return FAIL;
}
} }
else if (*p == '-' && p[1] == '>') else if (*p == '-' && p[1] == '>')
{ {
@@ -4470,6 +4480,12 @@ compile_subscript(
if (compile_call(arg, p - *arg, cctx, ppconst, 1) == FAIL) if (compile_call(arg, p - *arg, cctx, ppconst, 1) == FAIL)
return FAIL; return FAIL;
} }
if (keeping_dict)
{
keeping_dict = FALSE;
if (generate_instr(cctx, ISN_CLEARDICT) == NULL)
return FAIL;
}
} }
else if (**arg == '[') else if (**arg == '[')
{ {
@@ -4537,7 +4553,13 @@ compile_subscript(
} }
*arg = *arg + 1; *arg = *arg + 1;
if (compile_member(is_slice, cctx) == FAIL) 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; return FAIL;
} }
else if (*p == '.' && p[1] != '.') else if (*p == '.' && p[1] != '.')
@@ -4562,18 +4584,21 @@ compile_subscript(
semsg(_(e_syntax_error_at_str), *arg); semsg(_(e_syntax_error_at_str), *arg);
return FAIL; return FAIL;
} }
if (keeping_dict && generate_instr(cctx, ISN_CLEARDICT) == NULL)
return FAIL;
if (generate_STRINGMEMBER(cctx, *arg, p - *arg) == FAIL) if (generate_STRINGMEMBER(cctx, *arg, p - *arg) == FAIL)
return FAIL; return FAIL;
keeping_dict = TRUE;
*arg = p; *arg = p;
} }
else else
break; break;
} }
// TODO - see handle_subscript():
// Turn "dict.Func" into a partial for "Func" bound to "dict". // Turn "dict.Func" into a partial for "Func" bound to "dict".
// Don't do this when "Func" is already a partial that was bound // This needs to be done at runtime to be able to check the type.
// explicitly (pt_auto is FALSE). if (keeping_dict && generate_instr(cctx, ISN_USEDICT) == NULL)
return FAIL;
return OK; return OK;
} }
@@ -6661,7 +6686,7 @@ compile_load_lhs_with_index(lhs_T *lhs, char_u *var_start, cctx_T *cctx)
} }
// Get the member. // Get the member.
if (compile_member(FALSE, cctx) == FAIL) if (compile_member(FALSE, NULL, cctx) == FAIL)
return FAIL; return FAIL;
} }
return OK; return OK;
@@ -10406,6 +10431,7 @@ delete_instr(isn_T *isn)
case ISN_CEXPR_AUCMD: case ISN_CEXPR_AUCMD:
case ISN_CHECKLEN: case ISN_CHECKLEN:
case ISN_CHECKNR: case ISN_CHECKNR:
case ISN_CLEARDICT:
case ISN_CMDMOD_REV: case ISN_CMDMOD_REV:
case ISN_COMPAREANY: case ISN_COMPAREANY:
case ISN_COMPAREBLOB: case ISN_COMPAREBLOB:
@@ -10482,6 +10508,7 @@ delete_instr(isn_T *isn)
case ISN_UNLETINDEX: case ISN_UNLETINDEX:
case ISN_UNLETRANGE: case ISN_UNLETRANGE:
case ISN_UNPACK: case ISN_UNPACK:
case ISN_USEDICT:
// nothing allocated // nothing allocated
break; break;
} }

View File

@@ -165,6 +165,75 @@ update_has_breakpoint(ufunc_T *ufunc)
} }
} }
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. * Call compiled function "cdf_idx" from compiled code.
* This adds a stack frame and sets the instruction pointer to the start of the * This adds a stack frame and sets the instruction pointer to the start of the
@@ -765,7 +834,8 @@ call_ufunc(
partial_T *pt, partial_T *pt,
int argcount, int argcount,
ectx_T *ectx, ectx_T *ectx,
isn_T *iptr) isn_T *iptr,
dict_T *selfdict)
{ {
typval_T argvars[MAX_FUNC_ARGS]; typval_T argvars[MAX_FUNC_ARGS];
funcexe_T funcexe; funcexe_T funcexe;
@@ -807,11 +877,12 @@ call_ufunc(
return FAIL; return FAIL;
CLEAR_FIELD(funcexe); CLEAR_FIELD(funcexe);
funcexe.evaluate = TRUE; funcexe.evaluate = TRUE;
funcexe.selfdict = selfdict != NULL ? selfdict : dict_stack_get_dict();
// Call the user function. Result goes in last position on the stack. // Call the user function. Result goes in last position on the stack.
// TODO: add selfdict if there is one // TODO: add selfdict if there is one
error = call_user_func_check(ufunc, argcount, argvars, error = call_user_func_check(ufunc, argcount, argvars,
STACK_TV_BOT(-1), &funcexe, NULL); STACK_TV_BOT(-1), &funcexe, funcexe.selfdict);
// Clear the arguments. // Clear the arguments.
for (idx = 0; idx < argcount; ++idx) for (idx = 0; idx < argcount; ++idx)
@@ -864,7 +935,8 @@ call_by_name(
char_u *name, char_u *name,
int argcount, int argcount,
ectx_T *ectx, ectx_T *ectx,
isn_T *iptr) isn_T *iptr,
dict_T *selfdict)
{ {
ufunc_T *ufunc; ufunc_T *ufunc;
@@ -916,7 +988,7 @@ call_by_name(
} }
} }
return call_ufunc(ufunc, NULL, argcount, ectx, iptr); return call_ufunc(ufunc, NULL, argcount, ectx, iptr, selfdict);
} }
return FAIL; return FAIL;
@@ -932,6 +1004,7 @@ call_partial(
char_u *name = NULL; char_u *name = NULL;
int called_emsg_before = called_emsg; int called_emsg_before = called_emsg;
int res = FAIL; int res = FAIL;
dict_T *selfdict = NULL;
if (tv->v_type == VAR_PARTIAL) if (tv->v_type == VAR_PARTIAL)
{ {
@@ -953,9 +1026,10 @@ call_partial(
for (i = 0; i < pt->pt_argc; ++i) for (i = 0; i < pt->pt_argc; ++i)
copy_tv(&pt->pt_argv[i], STACK_TV_BOT(-argcount + i)); copy_tv(&pt->pt_argv[i], STACK_TV_BOT(-argcount + i));
} }
selfdict = pt->pt_dict;
if (pt->pt_func != NULL) if (pt->pt_func != NULL)
return call_ufunc(pt->pt_func, pt, argcount, ectx, NULL); return call_ufunc(pt->pt_func, pt, argcount, ectx, NULL, selfdict);
name = pt->pt_name; name = pt->pt_name;
} }
@@ -973,7 +1047,7 @@ call_partial(
if (error != FCERR_NONE) if (error != FCERR_NONE)
res = FAIL; res = FAIL;
else else
res = call_by_name(fname, argcount, ectx, NULL); res = call_by_name(fname, argcount, ectx, NULL, selfdict);
vim_free(tofree); vim_free(tofree);
} }
@@ -1325,7 +1399,7 @@ call_eval_func(
int called_emsg_before = called_emsg; int called_emsg_before = called_emsg;
int res; int res;
res = call_by_name(name, argcount, ectx, iptr); res = call_by_name(name, argcount, ectx, iptr, NULL);
if (res == FAIL && called_emsg == called_emsg_before) if (res == FAIL && called_emsg == called_emsg_before)
{ {
dictitem_T *v; dictitem_T *v;
@@ -1570,6 +1644,7 @@ exec_instructions(ectx_T *ectx)
{ {
int ret = FAIL; int ret = FAIL;
int save_trylevel_at_start = ectx->ec_trylevel_at_start; 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. // Start execution at the first instruction.
ectx->ec_iidx = 0; ectx->ec_iidx = 0;
@@ -4022,7 +4097,6 @@ exec_instructions(ectx_T *ectx)
dict_T *dict; dict_T *dict;
char_u *key; char_u *key;
dictitem_T *di; dictitem_T *di;
typval_T temp_tv;
// dict member: dict is at stack-2, key at stack-1 // dict member: dict is at stack-2, key at stack-1
tv = STACK_TV_BOT(-2); tv = STACK_TV_BOT(-2);
@@ -4041,23 +4115,24 @@ exec_instructions(ectx_T *ectx)
semsg(_(e_dictkey), key); semsg(_(e_dictkey), key);
// If :silent! is used we will continue, make sure the // If :silent! is used we will continue, make sure the
// stack contents makes sense. // stack contents makes sense and the dict stack is
// updated.
clear_tv(tv); clear_tv(tv);
--ectx->ec_stack.ga_len; --ectx->ec_stack.ga_len;
tv = STACK_TV_BOT(-1); tv = STACK_TV_BOT(-1);
clear_tv(tv); (void) dict_stack_save(tv);
tv->v_type = VAR_NUMBER; tv->v_type = VAR_NUMBER;
tv->vval.v_number = 0; tv->vval.v_number = 0;
goto on_fatal_error; goto on_fatal_error;
} }
clear_tv(tv); clear_tv(tv);
--ectx->ec_stack.ga_len; --ectx->ec_stack.ga_len;
// Clear the dict only after getting the item, to avoid // Put the dict used on the dict stack, it might be used by
// that it makes the item invalid. // a dict function later.
tv = STACK_TV_BOT(-1); tv = STACK_TV_BOT(-1);
temp_tv = *tv; if (dict_stack_save(tv) == FAIL)
goto on_fatal_error;
copy_tv(&di->di_tv, tv); copy_tv(&di->di_tv, tv);
clear_tv(&temp_tv);
} }
break; break;
@@ -4066,7 +4141,6 @@ exec_instructions(ectx_T *ectx)
{ {
dict_T *dict; dict_T *dict;
dictitem_T *di; dictitem_T *di;
typval_T temp_tv;
tv = STACK_TV_BOT(-1); tv = STACK_TV_BOT(-1);
if (tv->v_type != VAR_DICT || tv->vval.v_dict == NULL) if (tv->v_type != VAR_DICT || tv->vval.v_dict == NULL)
@@ -4084,11 +4158,37 @@ exec_instructions(ectx_T *ectx)
semsg(_(e_dictkey), iptr->isn_arg.string); semsg(_(e_dictkey), iptr->isn_arg.string);
goto on_error; goto on_error;
} }
// Clear the dict after getting the item, to avoid that it // Put the dict used on the dict stack, it might be used by
// make the item invalid. // a dict function later.
temp_tv = *tv; if (dict_stack_save(tv) == FAIL)
goto on_fatal_error;
copy_tv(&di->di_tv, tv); copy_tv(&di->di_tv, tv);
clear_tv(&temp_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; break;
@@ -4478,6 +4578,7 @@ on_fatal_error:
done: done:
ret = OK; ret = OK;
theend: theend:
dict_stack_clear(dict_stack_len_at_start);
ectx->ec_trylevel_at_start = save_trylevel_at_start; ectx->ec_trylevel_at_start = save_trylevel_at_start;
return ret; return ret;
} }
@@ -5568,6 +5669,9 @@ list_instructions(char *pfx, isn_T *instr, int instr_count, ufunc_T *ufunc)
case ISN_MEMBER: smsg("%s%4d MEMBER", pfx, current); break; case ISN_MEMBER: smsg("%s%4d MEMBER", pfx, current); break;
case ISN_STRINGMEMBER: smsg("%s%4d MEMBER %s", pfx, current, case ISN_STRINGMEMBER: smsg("%s%4d MEMBER %s", pfx, current,
iptr->isn_arg.string); break; 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_NEGATENR: smsg("%s%4d NEGATENR", pfx, current); break;
case ISN_CHECKNR: smsg("%s%4d CHECKNR", pfx, current); break; case ISN_CHECKNR: smsg("%s%4d CHECKNR", pfx, current); break;