# HG changeset patch # User Bram Moolenaar # Date 1631550604 -7200 # Node ID fe8d153cb2681ebb4038127f811358b956dacff3 # Parent 0493410eba748a81cb3ccd995d9f4a26c38d71c5 patch 8.2.3435: Vim9: dict is not passed to dict function Commit: https://github.com/vim/vim/commit/b1b6f4de2b0edc3b6622912132ddb8994ec52709 Author: Bram Moolenaar Date: Mon Sep 13 18:25:54 2021 +0200 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. diff --git a/src/testdir/test_vim9_disassemble.vim b/src/testdir/test_vim9_disassemble.vim --- a/src/testdir/test_vim9_disassemble.vim +++ b/src/testdir/test_vim9_disassemble.vim @@ -412,7 +412,8 @@ def Test_disassemble_store_index() '\d PUSHNR 0\_s*' .. '\d LOAD $0\_s*' .. '\d MEMBER dd\_s*' .. - '\d STOREINDEX any\_s*' .. + '\d\+ USEDICT\_s*' .. + '\d\+ STOREINDEX any\_s*' .. '\d\+ RETURN void', res) enddef @@ -1625,11 +1626,13 @@ def Test_disassemble_dict_member() '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,6 +2305,35 @@ def Test_debug_elseif() 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('\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,4 +2395,5 @@ def Test_disassemble_after_reload() enddef + " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker diff --git a/src/testdir/test_vim9_func.vim b/src/testdir/test_vim9_func.vim --- a/src/testdir/test_vim9_func.vim +++ b/src/testdir/test_vim9_func.vim @@ -2557,6 +2557,37 @@ def Test_legacy_errors() 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 # closure nested inside another closure using argument var Filter = (l) => filter(l, (_, v) => stridx(v, a) == 0) diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -756,6 +756,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 3435, +/**/ 3434, /**/ 3433, diff --git a/src/vim9.h b/src/vim9.h --- a/src/vim9.h +++ b/src/vim9.h @@ -162,6 +162,9 @@ typedef enum { 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 diff --git a/src/vim9compile.c b/src/vim9compile.c --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -2878,9 +2878,10 @@ clear_ppconst(ppconst_T *ppconst) /* * 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) + * "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,6 +2936,8 @@ compile_member(int is_slice, cctx_T *cct 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,6 +4317,7 @@ compile_subscript( ppconst_T *ppconst) { char_u *name_start = *end_leader; + int keeping_dict = FALSE; for (;;) { @@ -4360,6 +4364,12 @@ compile_subscript( 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,6 +4480,12 @@ compile_subscript( 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,7 +4553,13 @@ compile_subscript( } *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; } else if (*p == '.' && p[1] != '.') @@ -4562,18 +4584,21 @@ compile_subscript( 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; } - // 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). + // 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,7 +6686,7 @@ compile_load_lhs_with_index(lhs_T *lhs, } // Get the member. - if (compile_member(FALSE, cctx) == FAIL) + if (compile_member(FALSE, NULL, cctx) == FAIL) return FAIL; } return OK; @@ -10406,6 +10431,7 @@ delete_instr(isn_T *isn) case ISN_CEXPR_AUCMD: case ISN_CHECKLEN: case ISN_CHECKNR: + case ISN_CLEARDICT: case ISN_CMDMOD_REV: case ISN_COMPAREANY: case ISN_COMPAREBLOB: @@ -10482,6 +10508,7 @@ delete_instr(isn_T *isn) case ISN_UNLETINDEX: case ISN_UNLETRANGE: case ISN_UNPACK: + case ISN_USEDICT: // nothing allocated break; } diff --git a/src/vim9execute.c b/src/vim9execute.c --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -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. * This adds a stack frame and sets the instruction pointer to the start of the @@ -765,7 +834,8 @@ call_ufunc( partial_T *pt, int argcount, ectx_T *ectx, - isn_T *iptr) + isn_T *iptr, + dict_T *selfdict) { typval_T argvars[MAX_FUNC_ARGS]; funcexe_T funcexe; @@ -807,11 +877,12 @@ call_ufunc( 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, NULL); + STACK_TV_BOT(-1), &funcexe, funcexe.selfdict); // Clear the arguments. for (idx = 0; idx < argcount; ++idx) @@ -864,7 +935,8 @@ call_by_name( char_u *name, int argcount, ectx_T *ectx, - isn_T *iptr) + isn_T *iptr, + dict_T *selfdict) { 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; @@ -932,6 +1004,7 @@ call_partial( char_u *name = NULL; int called_emsg_before = called_emsg; int res = FAIL; + dict_T *selfdict = NULL; if (tv->v_type == VAR_PARTIAL) { @@ -953,9 +1026,10 @@ call_partial( 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); + return call_ufunc(pt->pt_func, pt, argcount, ectx, NULL, selfdict); name = pt->pt_name; } @@ -973,7 +1047,7 @@ call_partial( if (error != FCERR_NONE) res = FAIL; else - res = call_by_name(fname, argcount, ectx, NULL); + res = call_by_name(fname, argcount, ectx, NULL, selfdict); vim_free(tofree); } @@ -1325,7 +1399,7 @@ call_eval_func( int called_emsg_before = called_emsg; 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) { dictitem_T *v; @@ -1570,6 +1644,7 @@ exec_instructions(ectx_T *ectx) { 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,7 +4097,6 @@ exec_instructions(ectx_T *ectx) 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); @@ -4041,23 +4115,24 @@ exec_instructions(ectx_T *ectx) semsg(_(e_dictkey), key); // 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); --ectx->ec_stack.ga_len; tv = STACK_TV_BOT(-1); - clear_tv(tv); + (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; - // Clear the dict only after getting the item, to avoid - // that it makes the item invalid. + // Put the dict used on the dict stack, it might be used by + // a dict function later. tv = STACK_TV_BOT(-1); - temp_tv = *tv; + if (dict_stack_save(tv) == FAIL) + goto on_fatal_error; copy_tv(&di->di_tv, tv); - clear_tv(&temp_tv); } break; @@ -4066,7 +4141,6 @@ exec_instructions(ectx_T *ectx) { 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) @@ -4084,11 +4158,37 @@ exec_instructions(ectx_T *ectx) 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; + // 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); - 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; @@ -4478,6 +4578,7 @@ on_fatal_error: done: ret = OK; theend: + dict_stack_clear(dict_stack_len_at_start); ectx->ec_trylevel_at_start = save_trylevel_at_start; return ret; } @@ -5568,6 +5669,9 @@ list_instructions(char *pfx, isn_T *inst 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;