]> granicus.if.org Git - vim/commitdiff
patch 8.2.3435: Vim9: dict is not passed to dict function v8.2.3435
authorBram Moolenaar <Bram@vim.org>
Mon, 13 Sep 2021 16:25:54 +0000 (18:25 +0200)
committerBram Moolenaar <Bram@vim.org>
Mon, 13 Sep 2021 16:25:54 +0000 (18:25 +0200)
Problem:    Vim9: dict is not passed to dict function.
Solution:   Keep the dict used until a function call.

src/testdir/test_vim9_disassemble.vim
src/testdir/test_vim9_func.vim
src/version.c
src/vim9.h
src/vim9compile.c
src/vim9execute.c

index e7c574673fc4b33e2a2f42482901cc6037afda56..840017418cace1e1d5e3ece78dc82ff9c6ac06fc 100644 (file)
@@ -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('<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,4 +2395,5 @@ def Test_disassemble_after_reload()
 enddef
 
 
+
 " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
index 4e2f70e05dfc17ff4c8743521fbb15bf39cce352..dda812d8f6bb112ce273e91e53f6c728351871b6 100644 (file)
@@ -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<string>
   # closure nested inside another closure using argument
   var Filter = (l) => filter(l, (_, v) => stridx(v, a) == 0)
index 84e80b1986245856f97b0f71cfa2f55ee8c6a510..8973e482b2bc20f1aa1e6a0d25c7b68945b842f9 100644 (file)
@@ -755,6 +755,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    3435,
 /**/
     3434,
 /**/
index 0fc1ab5d4cf67f85c3214ea463884781fd2b139a..66fc6cc39129480bfff5bfdd97a5a94676b54d58 100644 (file)
@@ -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
index e7c7c07a6926944f480af3d82bd77e3b0593c768..4807e3aa096cbf9a09c1b0a90f401ed251569b98 100644 (file)
@@ -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.
+ * "keeping_dict" is used for dict[func](arg) to pass dict to func.
  */
     static int
-compile_member(int is_slice, cctx_T *cctx)
+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 *cctx)
            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, char_u *var_start, cctx_T *cctx)
        }
 
        // 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;
     }
index 9fc942d3796b46cb96cd61fb243dca6c51bee339..0fa1a12f4cf2d7b0214661219989f0e098a166a3 100644 (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.
  * 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 *instr, int instr_count, ufunc_T *ufunc)
            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;