diff src/vim9execute.c @ 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 f31cf0388eab
children dd4e86558836
line wrap: on
line diff
--- 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;