changeset 25605:6f13d9ea0d04 v8.2.3339

patch 8.2.3339: Vim9: cannot lock a member in a local dict Commit: https://github.com/vim/vim/commit/aacc966c5d0ed91e33ed32b08f17cf4df3ca1394 Author: Bram Moolenaar <Bram@vim.org> Date: Fri Aug 13 19:40:51 2021 +0200 patch 8.2.3339: Vim9: cannot lock a member in a local dict Problem: Vim9: cannot lock a member in a local dict. Solution: Get the local dict from the stack and pass it to get_lval().
author Bram Moolenaar <Bram@vim.org>
date Fri, 13 Aug 2021 19:45:03 +0200
parents 5ad1d3061d62
children 2d0203a72f20
files src/eval.c src/globals.h src/testdir/test_vim9_cmd.vim src/testdir/test_vim9_disassemble.vim src/version.c src/vim9.h src/vim9compile.c src/vim9execute.c
diffstat 8 files changed, 122 insertions(+), 32 deletions(-) [+]
line wrap: on
line diff
--- a/src/eval.c
+++ b/src/eval.c
@@ -902,17 +902,26 @@ get_lval(
     if ((*p != '[' && *p != '.') || lp->ll_name == NULL)
 	return p;
 
-    cc = *p;
-    *p = NUL;
-    // When we would write to the variable pass &ht and prevent autoload.
-    writing = !(flags & GLV_READ_ONLY);
-    v = find_var(lp->ll_name, writing ? &ht : NULL,
+    if (in_vim9script() && lval_root != NULL)
+    {
+	// using local variable
+	lp->ll_tv = lval_root;
+    }
+    else
+    {
+	cc = *p;
+	*p = NUL;
+	// When we would write to the variable pass &ht and prevent autoload.
+	writing = !(flags & GLV_READ_ONLY);
+	v = find_var(lp->ll_name, writing ? &ht : NULL,
 					 (flags & GLV_NO_AUTOLOAD) || writing);
-    if (v == NULL && !quiet)
-	semsg(_(e_undefined_variable_str), lp->ll_name);
-    *p = cc;
-    if (v == NULL)
-	return NULL;
+	if (v == NULL && !quiet)
+	    semsg(_(e_undefined_variable_str), lp->ll_name);
+	*p = cc;
+	if (v == NULL)
+	    return NULL;
+	lp->ll_tv = &v->di_tv;
+    }
 
     if (in_vim9script() && (flags & GLV_NO_DECL) == 0)
     {
@@ -924,7 +933,6 @@ get_lval(
     /*
      * Loop until no more [idx] or .key is following.
      */
-    lp->ll_tv = &v->di_tv;
     var1.v_type = VAR_UNKNOWN;
     var2.v_type = VAR_UNKNOWN;
     while (*p == '[' || (*p == '.' && p[1] != '=' && p[1] != '.'))
--- a/src/globals.h
+++ b/src/globals.h
@@ -1835,6 +1835,8 @@ EXTERN int  timer_busy INIT(= 0);   // w
 #endif
 #ifdef FEAT_EVAL
 EXTERN int  input_busy INIT(= 0);   // when inside get_user_input() then > 0
+
+EXTERN typval_T	*lval_root INIT(= NULL);
 #endif
 
 #ifdef FEAT_BEVAL_TERM
--- a/src/testdir/test_vim9_cmd.vim
+++ b/src/testdir/test_vim9_cmd.vim
@@ -1195,6 +1195,23 @@ def Test_lockvar()
   s:theList[1] = 44
   assert_equal([1, 44, 3], s:theList)
 
+  var d = {a: 1, b: 2}
+  d.a = 3
+  d.b = 4
+  assert_equal({a: 3, b: 4}, d)
+  lockvar d.a
+  d.b = 5
+  var ex = ''
+  try
+    d.a = 6
+  catch
+    ex = v:exception
+  endtry
+  assert_match('E1121:', ex)
+  unlockvar d.a
+  d.a = 7
+  assert_equal({a: 7, b: 5}, d)
+
   var lines =<< trim END
       vim9script
       var theList = [1, 2, 3]
--- a/src/testdir/test_vim9_disassemble.vim
+++ b/src/testdir/test_vim9_disassemble.vim
@@ -588,6 +588,25 @@ def Test_disassemble_unlet()
         res)
 enddef
 
+def s:LockLocal()
+  var d = {a: 1}
+  lockvar d.a
+enddef
+
+def Test_disassemble_locl_local()
+  var res = execute('disass s:LockLocal')
+  assert_match('<SNR>\d*_LockLocal\_s*' ..
+        'var d = {a: 1}\_s*' ..
+        '\d PUSHS "a"\_s*' ..
+        '\d PUSHNR 1\_s*' ..
+        '\d NEWDICT size 1\_s*' ..
+        '\d STORE $0\_s*' ..
+        'lockvar d.a\_s*' ..
+        '\d LOAD $0\_s*' ..
+        '\d LOCKUNLOCK lockvar d.a\_s*',
+        res)
+enddef
+
 def s:ScriptFuncTry()
   try
     echo "yes"
--- 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 */
 /**/
+    3339,
+/**/
     3338,
 /**/
     3337,
--- a/src/vim9.h
+++ b/src/vim9.h
@@ -70,6 +70,7 @@ typedef enum {
     ISN_UNLETINDEX,	// unlet item of list or dict
     ISN_UNLETRANGE,	// unlet items of list
 
+    ISN_LOCKUNLOCK,	// :lock and :unlock for local variable member
     ISN_LOCKCONST,	// lock constant value
 
     // constants
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -2262,12 +2262,12 @@ generate_PUT(cctx_T *cctx, int regname, 
 }
 
     static int
-generate_EXEC(cctx_T *cctx, char_u *line)
+generate_EXEC(cctx_T *cctx, isntype_T isntype, char_u *line)
 {
     isn_T	*isn;
 
     RETURN_OK_IF_SKIP(cctx);
-    if ((isn = generate_instr(cctx, ISN_EXEC)) == NULL)
+    if ((isn = generate_instr(cctx, isntype)) == NULL)
 	return FAIL;
     isn->isn_arg.string = vim_strsave(line);
     return OK;
@@ -7426,6 +7426,7 @@ compile_lock_unlock(
     int		ret = OK;
     size_t	len;
     char_u	*buf;
+    isntype_T	isn = ISN_EXEC;
 
     if (cctx->ctx_skip == SKIP_YES)
 	return OK;
@@ -7437,8 +7438,19 @@ compile_lock_unlock(
 
 	if (lookup_local(p, end - p, NULL, cctx) == OK)
 	{
-	    emsg(_(e_cannot_lock_unlock_local_variable));
-	    return FAIL;
+	    char_u *s = p;
+
+	    if (*end != '.' && *end != '[')
+	    {
+		emsg(_(e_cannot_lock_unlock_local_variable));
+		return FAIL;
+	    }
+
+	    // For "d.member" put the local variable on the stack, it will be
+	    // passed to ex_lockvar() indirectly.
+	    if (compile_load(&s, end, cctx, FALSE, FALSE) == FAIL)
+		return FAIL;
+	    isn = ISN_LOCKUNLOCK;
 	}
     }
 
@@ -7453,7 +7465,7 @@ compile_lock_unlock(
 	vim_snprintf((char *)buf, len, "%s %s",
 		eap->cmdidx == CMD_lockvar ? "lockvar" : "unlockvar",
 		p);
-	ret = generate_EXEC(cctx, buf);
+	ret = generate_EXEC(cctx, isn, buf);
 
 	vim_free(buf);
 	*name_end = cc;
@@ -9110,7 +9122,7 @@ compile_exec(char_u *line_arg, exarg_T *
 	generate_EXECCONCAT(cctx, count);
     }
     else
-	generate_EXEC(cctx, line);
+	generate_EXEC(cctx, ISN_EXEC, line);
 
 theend:
     if (*nextcmd != NUL)
@@ -10198,6 +10210,7 @@ delete_instr(isn_T *isn)
 	case ISN_LOADOPT:
 	case ISN_LOADT:
 	case ISN_LOADW:
+	case ISN_LOCKUNLOCK:
 	case ISN_PUSHEXC:
 	case ISN_PUSHFUNC:
 	case ISN_PUSHS:
--- a/src/vim9execute.c
+++ b/src/vim9execute.c
@@ -1396,6 +1396,27 @@ fill_partial_and_closure(partial_T *pt, 
     return OK;
 }
 
+/*
+ * Execute iptr->isn_arg.string as an Ex command.
+ */
+    static int
+exec_command(isn_T *iptr)
+{
+    source_cookie_T cookie;
+
+    SOURCING_LNUM = iptr->isn_lnum;
+    // Pass getsourceline to get an error for a missing ":end"
+    // command.
+    CLEAR_FIELD(cookie);
+    cookie.sourcing_lnum = iptr->isn_lnum - 1;
+    if (do_cmdline(iptr->isn_arg.string,
+		getsourceline, &cookie,
+			     DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED) == FAIL
+		|| did_emsg)
+	return FAIL;
+    return OK;
+}
+
 // used for v_instr of typval of VAR_INSTR
 struct instr_S {
     ectx_T	*instr_ectx;
@@ -1637,21 +1658,8 @@ exec_instructions(ectx_T *ectx)
 	{
 	    // execute Ex command line
 	    case ISN_EXEC:
-		{
-		    source_cookie_T cookie;
-
-		    SOURCING_LNUM = iptr->isn_lnum;
-		    // Pass getsourceline to get an error for a missing ":end"
-		    // command.
-		    CLEAR_FIELD(cookie);
-		    cookie.sourcing_lnum = iptr->isn_lnum - 1;
-		    if (do_cmdline(iptr->isn_arg.string,
-				getsourceline, &cookie,
-				   DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED)
-									== FAIL
-				|| did_emsg)
-			goto on_error;
-		}
+		if (exec_command(iptr) == FAIL)
+		    goto on_error;
 		break;
 
 	    // execute Ex command line split at NL characters.
@@ -2880,6 +2888,23 @@ exec_instructions(ectx_T *ectx)
 		vim_unsetenv(iptr->isn_arg.unlet.ul_name);
 		break;
 
+	    case ISN_LOCKUNLOCK:
+		{
+		    typval_T	*lval_root_save = lval_root;
+		    int		res;
+
+		    // Stack has the local variable, argument the whole :lock
+		    // or :unlock command, like ISN_EXEC.
+		    --ectx->ec_stack.ga_len;
+		    lval_root = STACK_TV_BOT(0);
+		    res = exec_command(iptr);
+		    clear_tv(lval_root);
+		    lval_root = lval_root_save;
+		    if (res == FAIL)
+			goto on_error;
+		}
+		break;
+
 	    case ISN_LOCKCONST:
 		item_lock(STACK_TV_BOT(-1), 100, TRUE, TRUE);
 		break;
@@ -5244,6 +5269,9 @@ list_instructions(char *pfx, isn_T *inst
 	    case ISN_UNLETRANGE:
 		smsg("%s%4d UNLETRANGE", pfx, current);
 		break;
+	    case ISN_LOCKUNLOCK:
+		smsg("%s%4d LOCKUNLOCK %s", pfx, current, iptr->isn_arg.string);
+		break;
 	    case ISN_LOCKCONST:
 		smsg("%s%4d LOCKCONST", pfx, current);
 		break;