To: vim_dev@googlegroups.com Subject: Patch 8.2.3435 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ 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 # 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('\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 -- Bram@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 ///