changeset 25800:fe8d153cb268 v8.2.3435

patch 8.2.3435: Vim9: dict is not passed to dict function Commit: https://github.com/vim/vim/commit/b1b6f4de2b0edc3b6622912132ddb8994ec52709 Author: Bram Moolenaar <Bram@vim.org> 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.
author Bram Moolenaar <Bram@vim.org>
date Mon, 13 Sep 2021 18:30:04 +0200
parents 0493410eba74
children 7124fbe5db04
files src/testdir/test_vim9_disassemble.vim src/testdir/test_vim9_func.vim src/version.c src/vim9.h src/vim9compile.c src/vim9execute.c
diffstat 6 files changed, 228 insertions(+), 28 deletions(-) [+]
line wrap: on
line diff
--- 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('<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
--- 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<string>
   # closure nested inside another closure using argument
   var Filter = (l) => filter(l, (_, v) => stridx(v, a) == 0)
--- 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,
--- 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
--- 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;
     }
--- 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;